From 449aee452271ea6d0c8c368fd5b169670c91298e Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 15:36:10 +0200 Subject: [PATCH 01/13] =?UTF-8?q?feat:=20add=20S=C3=A4tteri=20tab=20in=20t?= =?UTF-8?q?he=20external=20links=20recipe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/en/recipes/external-links.mdx | 171 +++++++++++++----- 1 file changed, 123 insertions(+), 48 deletions(-) diff --git a/src/content/docs/en/recipes/external-links.mdx b/src/content/docs/en/recipes/external-links.mdx index 890171dee5bb6..7ef3b4be157fe 100644 --- a/src/content/docs/en/recipes/external-links.mdx +++ b/src/content/docs/en/recipes/external-links.mdx @@ -4,7 +4,7 @@ description: Learn how to install a rehype plugin to add icons to external links i18nReady: true type: recipe --- -import { Steps } from '@astrojs/starlight/components'; +import { Steps, TabItem, Tabs } from '@astrojs/starlight/components'; import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; Using a rehype plugin, you can identify and modify links in your Markdown files that point to external sites. This example adds icons to the end of each external link, so that visitors will know they are leaving your site. @@ -15,54 +15,129 @@ Using a rehype plugin, you can identify and modify links in your Markdown files ## Recipe - -1. Install both the [`rehype-external-links`](https://www.npmjs.com/package/rehype-external-links) plugin and [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark). + + + + 1. Install both [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) and [`satteri`](https://www.npmjs.com/package/satteri). - - - ```shell - npm install rehype-external-links @astrojs/markdown-remark + + + ```shell + npm install @astrojs/markdown-satteri satteri + ``` + + + ```shell + pnpm add @astrojs/markdown-satteri satteri + ``` + + + ```shell + yarn add @astrojs/markdown-satteri satteri + ``` + + + + 2. Create a [Sätteri `hast` plugin](https://satteri.bruits.org/docs/plugins/#hast-plugins). + + Create a new file in your project, for example `src/hast/hast-external-links.ts`, and add the following code: + + ```ts title="src/hast/hast-external-links.ts" + import { defineHastPlugin } from 'satteri'; + + export const hastExternalLinks = defineHastPlugin({ + name: "hast-external-links", + element: { + filter: ["a"], + visit(node, context) { + if (node.properties.href?.startsWith("http")) { + context.appendChild(node, { + type: "element", + tagName: "span", + properties: { ariaHidden: "true" }, + children: [ + { + type: "text", + value: "🔗", + }, + ], + }); + } + }, + }, + }); ``` - - - ```shell - pnpm add rehype-external-links @astrojs/markdown-remark + + 3. Configure the plugin in your `astro.config.mjs` file. + + Import `satteri()` from `@astrojs/markdown-satteri` and define it as the Markdown processor to [define custom Sätteri plugins](/en/guides/markdown-content/#using-sätteri-plugins-and-features). Then, pass to `hastPlugins` an array containing your imported `hastExternalLinks` plugin. + + ```js title="astro.config.mjs" + import { satteri } from '@astrojs/markdown-satteri'; + import { defineConfig } from 'astro/config'; + import { hastExternalLinks } from './src/hast/hast-external-links'; + + export default defineConfig({ + markdown: { + processor: satteri({ + hastPlugins: [hastExternalLinks], + }), + }, + }); ``` - - - ```shell - yarn add rehype-external-links @astrojs/markdown-remark + + + + + 1. Install both the [`rehype-external-links`](https://www.npmjs.com/package/rehype-external-links) plugin and [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark). + + + + ```shell + npm install rehype-external-links @astrojs/markdown-remark + ``` + + + ```shell + pnpm add rehype-external-links @astrojs/markdown-remark + ``` + + + ```shell + yarn add rehype-external-links @astrojs/markdown-remark + ``` + + + + 2. Configure the plugin in your `astro.config.mjs` file. + + Import `unified()` and define it as the Markdown processor to [support remark plugins](/en/guides/markdown-content/#using-remark-and-rehype-plugins). Then, pass to `rehypePlugins` an array containing your imported `rehypeExternalLinks` plugin and an options object with a `content` property. Set this property's `type` to `text` if you want to add plain text to the end of the link. To add HTML to the end of the link instead, set the property `type` to `raw`. + + ```js title="astro.config.mjs" + import { unified } from '@astrojs/markdown-remark'; + import { defineConfig } from 'astro/config'; + import rehypeExternalLinks from 'rehype-external-links'; + + export default defineConfig({ + // ... + markdown: { + processor: unified({ + rehypePlugins: [ + [ + rehypeExternalLinks, + { + content: { type: 'text', value: ' 🔗' } + } + ], + ] + }), + }, + }); ``` - - - -2. Configure the plugin in your `astro.config.mjs` file. - - Import `unified()` and define it as the Markdown processor to [support remark plugins](/en/guides/markdown-content/#using-remark-and-rehype-plugins). Then, pass to `rehypePlugins` an array containing your imported `rehypeExternalLinks` plugin and an options object with a `content` property. Set this property's `type` to `text` if you want to add plain text to the end of the link. To add HTML to the end of the link instead, set the property `type` to `raw`. - - ```js title="astro.config.mjs" - import { unified } from '@astrojs/markdown-remark'; - import { defineConfig } from 'astro/config'; - import rehypeExternalLinks from 'rehype-external-links'; - - export default defineConfig({ - // ... - markdown: { - processor: unified({ - rehypePlugins: [ - [ - rehypeExternalLinks, - { - content: { type: 'text', value: ' 🔗' } - } - ], - ] - }), - }, - }); - ``` - - :::note - The value of the `content` property is [not represented in the accessibility tree](https://developer.mozilla.org/en-US/docs/Web/CSS/content#accessibility_concerns). As such, it's best to make clear that the link is external in the surrounding content, rather than relying on the icon alone. - ::: - + + :::note + The value of the `content` property is [not represented in the accessibility tree](https://developer.mozilla.org/en-US/docs/Web/CSS/content#accessibility_concerns). As such, it's best to make clear that the link is external in the surrounding content, rather than relying on the icon alone. + ::: + + + From 67d1197dfca45b3daa41997f44be19754fd46dd7 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 15:44:21 +0200 Subject: [PATCH 02/13] remove the unified note as this is a wrong statement --- src/content/docs/en/recipes/external-links.mdx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/content/docs/en/recipes/external-links.mdx b/src/content/docs/en/recipes/external-links.mdx index 7ef3b4be157fe..a3c94cfcd9386 100644 --- a/src/content/docs/en/recipes/external-links.mdx +++ b/src/content/docs/en/recipes/external-links.mdx @@ -119,7 +119,6 @@ Using a rehype plugin, you can identify and modify links in your Markdown files import rehypeExternalLinks from 'rehype-external-links'; export default defineConfig({ - // ... markdown: { processor: unified({ rehypePlugins: [ @@ -134,10 +133,6 @@ Using a rehype plugin, you can identify and modify links in your Markdown files }, }); ``` - - :::note - The value of the `content` property is [not represented in the accessibility tree](https://developer.mozilla.org/en-US/docs/Web/CSS/content#accessibility_concerns). As such, it's best to make clear that the link is external in the surrounding content, rather than relying on the icon alone. - ::: From 9d1e97a2dcb852c94e891dd59d5c4e0b360fada6 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 16:00:15 +0200 Subject: [PATCH 03/13] small rewording around "remark plugin" --- src/content/docs/en/recipes/external-links.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/docs/en/recipes/external-links.mdx b/src/content/docs/en/recipes/external-links.mdx index a3c94cfcd9386..78795bbc9fd24 100644 --- a/src/content/docs/en/recipes/external-links.mdx +++ b/src/content/docs/en/recipes/external-links.mdx @@ -1,13 +1,13 @@ --- title: Add icons to external links -description: Learn how to install a rehype plugin to add icons to external links in your Markdown files. +description: Learn how to use a Markdown plugin to add icons to external links in your Markdown files. i18nReady: true type: recipe --- import { Steps, TabItem, Tabs } from '@astrojs/starlight/components'; import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; -Using a rehype plugin, you can identify and modify links in your Markdown files that point to external sites. This example adds icons to the end of each external link, so that visitors will know they are leaving your site. +Using a Markdown plugin, you can identify and modify links in your Markdown files that point to external sites. This example adds an icon to the end of each external link, so that visitors will know they are leaving your site. ## Prerequisites From 194105743e5e713fea7ade53b1b1022157134833 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 16:33:31 +0200 Subject: [PATCH 04/13] =?UTF-8?q?add=20a=20S=C3=A4tteri=20tab=20to=20the?= =?UTF-8?q?=20modified=20time=20recipe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/content/docs/en/recipes/modified-time.mdx | 438 ++++++++++++------ 1 file changed, 301 insertions(+), 137 deletions(-) diff --git a/src/content/docs/en/recipes/modified-time.mdx b/src/content/docs/en/recipes/modified-time.mdx index fe01390ea43e7..827443e864a71 100644 --- a/src/content/docs/en/recipes/modified-time.mdx +++ b/src/content/docs/en/recipes/modified-time.mdx @@ -1,13 +1,13 @@ --- title: Add last modified time -description: Build a remark plugin to add the last modified time to your Markdown and MDX. +description: Build a Markdown plugin to add the last modified time to your Markdown and MDX. i18nReady: true type: recipe --- -import { Steps } from '@astrojs/starlight/components'; +import { Steps, TabItem, Tabs } from '@astrojs/starlight/components'; import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; -Learn how to build a [remark plugin](https://github.com/remarkjs/remark) that adds the last modified time as a [custom frontmatter property](/en/guides/markdown-content/#modifying-frontmatter-programmatically) of your Markdown and MDX files. Use this property to display the modified time in your pages. +Learn how to build a Markdown plugin that adds the last modified time as a [custom frontmatter property](/en/guides/markdown-content/#modifying-frontmatter-programmatically) of your Markdown and MDX files. Use this property to display the modified time in your pages. :::note[Uses Git history] This recipe calculates time based on your repository’s Git history and may not be accurate on some deployment platforms. Your host may be performing **shallow clones** which do not retrieve the full git history. @@ -15,137 +15,301 @@ This recipe calculates time based on your repository’s Git history and may not ## Recipe - -1. Install [`Day.js`](https://www.npmjs.com/package/dayjs) to modify and format times, and [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins): - - - - ```shell - npm install dayjs @astrojs/markdown-remark - ``` - - - ```shell - pnpm add dayjs @astrojs/markdown-remark - ``` - - - ```shell - yarn add dayjs @astrojs/markdown-remark - ``` - - - -2. Create a Remark Plugin - - This plugin uses `execSync` to run a Git command that returns the timestamp of the latest commit in ISO 8601 format. The timestamp is then added to the frontmatter of the file. - - ```js title="remark-modified-time.mjs" - import { execSync } from "node:child_process"; - - export function remarkModifiedTime() { - return function (tree, file) { - const filepath = file.history[0]; - const result = execSync(`git log -1 --pretty="format:%cI" "${filepath}"`); - file.data.astro.frontmatter.lastModified = result.toString(); - }; - } - ``` -
- Using the file system instead of Git - - Although using Git is the recommended way to get the last modified timestamp from a file, it is possible to use the file system modified time. - This plugin uses `statSync` to get the `mtime` (modified time) of the file in ISO 8601 format. The timestamp is then added to the frontmatter of the file. - - ```js title="remark-modified-time.mjs" - import { statSync } from "fs"; - - export function remarkModifiedTime() { - return function (tree, file) { - const filepath = file.history[0]; - const result = statSync(filepath); - file.data.astro.frontmatter.lastModified = result.mtime.toISOString(); - }; - } - ``` -
- -3. Add the plugin to your config - - ```js title="astro.config.mjs" - import { unified } from '@astrojs/markdown-remark'; - import { defineConfig } from 'astro/config'; - import { remarkModifiedTime } from './remark-modified-time.mjs'; - - export default defineConfig({ - markdown: { - processor: unified({ - remarkPlugins: [remarkModifiedTime], - }), - }, - }); - ``` - - Now all Markdown documents will have a `lastModified` property in their frontmatter. - -4. Display Last Modified Time - - If your content is stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then render `lastModified` in your template wherever you would like it to appear. - - ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,28} - --- - import { getCollection, render } from 'astro:content'; - import dayjs from "dayjs"; - import utc from "dayjs/plugin/utc"; - - dayjs.extend(utc); - - export async function getStaticPaths() { - const blog = await getCollection('blog'); - return blog.map(entry => ({ - params: { slug: entry.id }, - props: { entry }, - })); - } - - const { entry } = Astro.props; - const { Content, remarkPluginFrontmatter } = await render(entry); - - const lastModified = dayjs(remarkPluginFrontmatter.lastModified) - .utc() - .format("HH:mm:ss DD MMMM YYYY UTC"); - --- - - - ... - - ... -

Last Modified: {lastModified}

- ... - - - ``` - - If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `lastModified` frontmatter property from `Astro.props` in your layout template. - - ```astro title="src/layouts/BlogLayout.astro" {2-3,5,7-9,15} - --- - import dayjs from "dayjs"; - import utc from "dayjs/plugin/utc"; - - dayjs.extend(utc); - - const lastModified = dayjs() - .utc(Astro.props.frontmatter.lastModified) - .format("HH:mm:ss DD MMMM YYYY UTC"); - --- - - - ... - -

{lastModified}

- - - - ``` -
+ + + + 1. Install [`Day.js`](https://www.npmjs.com/package/dayjs) to modify and format times, [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to configure [the `satteri()` processor](/en/guides/markdown-content/#using-sätteri-plugins-and-features), and [`satteri`](https://www.npmjs.com/package/satteri) to create a [Sätteri `mdast` plugin](https://satteri.bruits.org/docs/plugins/#mdast-plugins): + + + + ```shell + npm install dayjs @astrojs/markdown-satteri satteri + ``` + + + ```shell + pnpm add dayjs @astrojs/markdown-satteri satteri + ``` + + + ```shell + yarn add dayjs @astrojs/markdown-satteri satteri + ``` + + + + 2. Create a mdast plugin + + This plugin uses `execSync` to run a Git command that returns the timestamp of the latest commit in ISO 8601 format. The timestamp is then added to the frontmatter of the file. + + ```js title="src/mdast/mdast-modified-time.ts" + import { execSync } from 'node:child_process'; + import { fileURLToPath } from 'node:url'; + import { defineMdastPlugin } from 'satteri'; + + export const mdastModifiedTimePlugin = defineMdastPlugin({ + name: 'mdast-modified-time', + text(node, context) { + if ( + typeof context.data.astro?.frontmatter.lastModified === 'string' || + !context.fileURL + ) + return; + + const filepath = fileURLToPath(context.fileURL); + const result = execSync(`git log -1 --pretty="format:%cI" "${filepath}"`); + + if (context.data.astro !== undefined) { + context.data.astro.frontmatter.lastModified = result.toString(); + } + }, + }); + ``` +
+ Using the file system instead of Git + + Although using Git is the recommended way to get the last modified timestamp from a file, it is possible to use the file system modified time. + This plugin uses `statSync` to get the `mtime` (modified time) of the file in ISO 8601 format. The timestamp is then added to the frontmatter of the file. + + ```js title="src/mdast/mdast-modified-time.ts" + import { statSync } from 'node:fs'; + import { fileURLToPath } from 'node:url'; + import { defineMdastPlugin } from 'satteri'; + + export const mdastModifiedTimePlugin = defineMdastPlugin({ + name: 'mdast-modified-time', + text(node, context) { + if ( + typeof context.data.astro?.frontmatter.lastModified === 'string' || + !context.fileURL + ) + return; + + const filepath = fileURLToPath(context.fileURL); + const result = statSync(filepath); + + if (context.data.astro !== undefined) { + context.data.astro.frontmatter.lastModified = result.mtime.toISOString(); + } + }, + }); + ``` +
+ + 3. Add the plugin to your config + + ```js title="astro.config.mjs" + import { satteri } from '@astrojs/markdown-satteri'; + import { defineConfig } from 'astro/config'; + import { mdastModifiedTimePlugin } from './src/mdast/mdast-modified-time'; + + export default defineConfig({ + markdown: { + processor: satteri({ + mdastPlugins: [mdastModifiedTimePlugin], + }), + }, + }); + ``` + + Now all Markdown documents will have a `lastModified` property in their frontmatter. + + 4. Display Last Modified Time + + If your content is stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then render `lastModified` in your template wherever you would like it to appear. + + ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,28} + --- + import { getCollection, render } from 'astro:content'; + import dayjs from "dayjs"; + import utc from "dayjs/plugin/utc"; + + dayjs.extend(utc); + + export async function getStaticPaths() { + const blog = await getCollection('blog'); + return blog.map(entry => ({ + params: { slug: entry.id }, + props: { entry }, + })); + } + + const { entry } = Astro.props; + const { Content, remarkPluginFrontmatter } = await render(entry); + + const lastModified = dayjs(remarkPluginFrontmatter.lastModified) + .utc() + .format("HH:mm:ss DD MMMM YYYY UTC"); + --- + + + ... + + ... +

Last Modified: {lastModified}

+ ... + + + ``` + + If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `lastModified` frontmatter property from `Astro.props` in your layout template. + + ```astro title="src/layouts/BlogLayout.astro" {2-3,5,7-9,15} + --- + import dayjs from "dayjs"; + import utc from "dayjs/plugin/utc"; + + dayjs.extend(utc); + + const lastModified = dayjs() + .utc(Astro.props.frontmatter.lastModified) + .format("HH:mm:ss DD MMMM YYYY UTC"); + --- + + + ... + +

{lastModified}

+ + + + ``` +
+
+ + + 1. Install [`Day.js`](https://www.npmjs.com/package/dayjs) to modify and format times, and [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins): + + + + ```shell + npm install dayjs @astrojs/markdown-remark + ``` + + + ```shell + pnpm add dayjs @astrojs/markdown-remark + ``` + + + ```shell + yarn add dayjs @astrojs/markdown-remark + ``` + + + + 2. Create a [Remark Plugin](https://github.com/remarkjs/remark) + + This plugin uses `execSync` to run a Git command that returns the timestamp of the latest commit in ISO 8601 format. The timestamp is then added to the frontmatter of the file. + + ```js title="remark-modified-time.mjs" + import { execSync } from "node:child_process"; + + export function remarkModifiedTime() { + return function (tree, file) { + const filepath = file.history[0]; + const result = execSync(`git log -1 --pretty="format:%cI" "${filepath}"`); + file.data.astro.frontmatter.lastModified = result.toString(); + }; + } + ``` +
+ Using the file system instead of Git + + Although using Git is the recommended way to get the last modified timestamp from a file, it is possible to use the file system modified time. + This plugin uses `statSync` to get the `mtime` (modified time) of the file in ISO 8601 format. The timestamp is then added to the frontmatter of the file. + + ```js title="remark-modified-time.mjs" + import { statSync } from "fs"; + + export function remarkModifiedTime() { + return function (tree, file) { + const filepath = file.history[0]; + const result = statSync(filepath); + file.data.astro.frontmatter.lastModified = result.mtime.toISOString(); + }; + } + ``` +
+ + 3. Add the plugin to your config + + ```js title="astro.config.mjs" + import { unified } from '@astrojs/markdown-remark'; + import { defineConfig } from 'astro/config'; + import { remarkModifiedTime } from './remark-modified-time.mjs'; + + export default defineConfig({ + markdown: { + processor: unified({ + remarkPlugins: [remarkModifiedTime], + }), + }, + }); + ``` + + Now all Markdown documents will have a `lastModified` property in their frontmatter. + + 4. Display Last Modified Time + + If your content is stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then render `lastModified` in your template wherever you would like it to appear. + + ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,28} + --- + import { getCollection, render } from 'astro:content'; + import dayjs from "dayjs"; + import utc from "dayjs/plugin/utc"; + + dayjs.extend(utc); + + export async function getStaticPaths() { + const blog = await getCollection('blog'); + return blog.map(entry => ({ + params: { slug: entry.id }, + props: { entry }, + })); + } + + const { entry } = Astro.props; + const { Content, remarkPluginFrontmatter } = await render(entry); + + const lastModified = dayjs(remarkPluginFrontmatter.lastModified) + .utc() + .format("HH:mm:ss DD MMMM YYYY UTC"); + --- + + + ... + + ... +

Last Modified: {lastModified}

+ ... + + + ``` + + If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `lastModified` frontmatter property from `Astro.props` in your layout template. + + ```astro title="src/layouts/BlogLayout.astro" {2-3,5,7-9,15} + --- + import dayjs from "dayjs"; + import utc from "dayjs/plugin/utc"; + + dayjs.extend(utc); + + const lastModified = dayjs() + .utc(Astro.props.frontmatter.lastModified) + .format("HH:mm:ss DD MMMM YYYY UTC"); + --- + + + ... + +

{lastModified}

+ + + + ``` +
+
+
From bf63cfe3381d1b479e620d25d44caa45749337c1 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 16:37:45 +0200 Subject: [PATCH 05/13] fix a few TS/formatting issues --- src/content/docs/en/recipes/modified-time.mdx | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/content/docs/en/recipes/modified-time.mdx b/src/content/docs/en/recipes/modified-time.mdx index 827443e864a71..3b6adeea9b4f4 100644 --- a/src/content/docs/en/recipes/modified-time.mdx +++ b/src/content/docs/en/recipes/modified-time.mdx @@ -120,34 +120,37 @@ This recipe calculates time based on your repository’s Git history and may not ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,28} --- - import { getCollection, render } from 'astro:content'; + import type { GetStaticPaths } from "astro"; + import { getCollection, render } from "astro:content"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; dayjs.extend(utc); - export async function getStaticPaths() { - const blog = await getCollection('blog'); - return blog.map(entry => ({ + export const getStaticPaths = (async () => { + const blog = await getCollection("blog"); + return blog.map((entry) => ({ params: { slug: entry.id }, props: { entry }, })); - } + }) satisfies GetStaticPaths; const { entry } = Astro.props; const { Content, remarkPluginFrontmatter } = await render(entry); - const lastModified = dayjs(remarkPluginFrontmatter.lastModified) .utc() .format("HH:mm:ss DD MMMM YYYY UTC"); --- - - ... + + + + {entry.data.title} + - ... +

{entry.data.title}

Last Modified: {lastModified}

- ... + ``` @@ -167,7 +170,9 @@ This recipe calculates time based on your repository’s Git history and may not --- - ... + + +

{lastModified}

@@ -256,34 +261,37 @@ This recipe calculates time based on your repository’s Git history and may not ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,28} --- - import { getCollection, render } from 'astro:content'; + import type { GetStaticPaths } from "astro"; + import { getCollection, render } from "astro:content"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; dayjs.extend(utc); - export async function getStaticPaths() { - const blog = await getCollection('blog'); - return blog.map(entry => ({ + export const getStaticPaths = (async () => { + const blog = await getCollection("blog"); + return blog.map((entry) => ({ params: { slug: entry.id }, props: { entry }, })); - } + }) satisfies GetStaticPaths; const { entry } = Astro.props; const { Content, remarkPluginFrontmatter } = await render(entry); - const lastModified = dayjs(remarkPluginFrontmatter.lastModified) .utc() .format("HH:mm:ss DD MMMM YYYY UTC"); --- - - ... + + + + {entry.data.title} + - ... +

{entry.data.title}

Last Modified: {lastModified}

- ... + ``` @@ -303,7 +311,9 @@ This recipe calculates time based on your repository’s Git history and may not --- - ... + + +

{lastModified}

From fd175ccb2411efeb0bccf653a1e04150c6101d53 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 16:42:24 +0200 Subject: [PATCH 06/13] revert a few changes, no need for getStaticPaths type actually --- src/content/docs/en/recipes/modified-time.mdx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/content/docs/en/recipes/modified-time.mdx b/src/content/docs/en/recipes/modified-time.mdx index 3b6adeea9b4f4..2429691c16556 100644 --- a/src/content/docs/en/recipes/modified-time.mdx +++ b/src/content/docs/en/recipes/modified-time.mdx @@ -120,35 +120,35 @@ This recipe calculates time based on your repository’s Git history and may not ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,28} --- - import type { GetStaticPaths } from "astro"; import { getCollection, render } from "astro:content"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; dayjs.extend(utc); - export const getStaticPaths = (async () => { + export async function getStaticPaths() { const blog = await getCollection("blog"); return blog.map((entry) => ({ params: { slug: entry.id }, props: { entry }, })); - }) satisfies GetStaticPaths; + } const { entry } = Astro.props; const { Content, remarkPluginFrontmatter } = await render(entry); + const lastModified = dayjs(remarkPluginFrontmatter.lastModified) .utc() .format("HH:mm:ss DD MMMM YYYY UTC"); --- - + {entry.data.title} -

{entry.data.title}

+ {entry.data.title}

Last Modified: {lastModified}

@@ -261,35 +261,35 @@ This recipe calculates time based on your repository’s Git history and may not ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,28} --- - import type { GetStaticPaths } from "astro"; import { getCollection, render } from "astro:content"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; dayjs.extend(utc); - export const getStaticPaths = (async () => { + export async function getStaticPaths() { const blog = await getCollection("blog"); return blog.map((entry) => ({ params: { slug: entry.id }, props: { entry }, })); - }) satisfies GetStaticPaths; + } const { entry } = Astro.props; const { Content, remarkPluginFrontmatter } = await render(entry); + const lastModified = dayjs(remarkPluginFrontmatter.lastModified) .utc() .format("HH:mm:ss DD MMMM YYYY UTC"); --- - + {entry.data.title} -

{entry.data.title}

+ {entry.data.title}

Last Modified: {lastModified}

From 6f581c2db724ce431f76d0ba86da63381aa58375 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 16:44:34 +0200 Subject: [PATCH 07/13] fix highlighting --- src/content/docs/en/recipes/modified-time.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/content/docs/en/recipes/modified-time.mdx b/src/content/docs/en/recipes/modified-time.mdx index 2429691c16556..09eeca3211914 100644 --- a/src/content/docs/en/recipes/modified-time.mdx +++ b/src/content/docs/en/recipes/modified-time.mdx @@ -118,7 +118,7 @@ This recipe calculates time based on your repository’s Git history and may not If your content is stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then render `lastModified` in your template wherever you would like it to appear. - ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,28} + ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,31} --- import { getCollection, render } from "astro:content"; import dayjs from "dayjs"; @@ -157,7 +157,7 @@ This recipe calculates time based on your repository’s Git history and may not If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `lastModified` frontmatter property from `Astro.props` in your layout template. - ```astro title="src/layouts/BlogLayout.astro" {2-3,5,7-9,15} + ```astro title="src/layouts/BlogLayout.astro" {2-3,5,7-9,17} --- import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; @@ -259,7 +259,7 @@ This recipe calculates time based on your repository’s Git history and may not If your content is stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then render `lastModified` in your template wherever you would like it to appear. - ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,28} + ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,31} --- import { getCollection, render } from "astro:content"; import dayjs from "dayjs"; @@ -298,7 +298,7 @@ This recipe calculates time based on your repository’s Git history and may not If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `lastModified` frontmatter property from `Astro.props` in your layout template. - ```astro title="src/layouts/BlogLayout.astro" {2-3,5,7-9,15} + ```astro title="src/layouts/BlogLayout.astro" {2-3,5,7-9,17} --- import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; From 0b7a914e67ef3b069096940ab340fe8c612f0142 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 16:58:28 +0200 Subject: [PATCH 08/13] =?UTF-8?q?add=20a=20S=C3=A4tteri=20tab=20to=20the?= =?UTF-8?q?=20reading=20time=20recipe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/content/docs/en/recipes/reading-time.mdx | 365 +++++++++++++------ 1 file changed, 255 insertions(+), 110 deletions(-) diff --git a/src/content/docs/en/recipes/reading-time.mdx b/src/content/docs/en/recipes/reading-time.mdx index 4e173d316da29..03dd7c6a8d5f8 100644 --- a/src/content/docs/en/recipes/reading-time.mdx +++ b/src/content/docs/en/recipes/reading-time.mdx @@ -1,120 +1,265 @@ --- title: Add reading time -description: Build a remark plugin to add reading time to your Markdown or MDX files. +description: Build a Markdown plugin to add reading time to your Markdown or MDX files. i18nReady: true type: recipe --- -import { Steps } from '@astrojs/starlight/components'; +import { Steps, TabItem, Tabs } from '@astrojs/starlight/components'; import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; -Create a [remark plugin](https://github.com/remarkjs/remark) which adds a reading time property to the frontmatter of your Markdown or MDX files. Use this property to display the reading time for each page. +Create a Markdown plugin which adds a reading time property to the frontmatter of your Markdown or MDX files. Use this property to display the reading time for each page. ## Recipe - -1. Install the following packages: - - [`reading-time`](https://www.npmjs.com/package/reading-time) to calculate minutes read - - [`mdast-util-to-string`](https://www.npmjs.com/package/mdast-util-to-string) to extract all text from your markdown - - [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins): - - - - ```shell - npm install reading-time mdast-util-to-string @astrojs/markdown-remark - ``` - - - ```shell - pnpm add reading-time mdast-util-to-string @astrojs/markdown-remark - ``` - - - ```shell - yarn add reading-time mdast-util-to-string @astrojs/markdown-remark - ``` - - - -2. Create a remark plugin. - - This plugin uses the `mdast-util-to-string` package to get the Markdown file's text. This text is then passed to the `reading-time` package to calculate the reading time in minutes. - - ```js title="remark-reading-time.mjs" - import getReadingTime from 'reading-time'; - import { toString } from 'mdast-util-to-string'; - - export function remarkReadingTime() { - return function (tree, { data }) { - const textOnPage = toString(tree); - const readingTime = getReadingTime(textOnPage); - // readingTime.text will give us minutes read as a friendly string, - // i.e. "3 min read" - data.astro.frontmatter.minutesRead = readingTime.text; - }; - } - ``` - -3. Add the plugin to your config: - - ```js title="astro.config.mjs" {1,3,7-9} - import { unified } from '@astrojs/markdown-remark'; - import { defineConfig } from 'astro/config'; - import { remarkReadingTime } from './remark-reading-time.mjs'; - - export default defineConfig({ - markdown: { - processor: unified({ - remarkPlugins: [remarkReadingTime], - }), - }, - }); - ``` - - Now all Markdown documents will have a calculated `minutesRead` property in their frontmatter. - -4. Display Reading Time - - If your blog posts are stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then, render `minutesRead` in your template wherever you would like it to appear. - - ```astro title="src/pages/posts/[slug].astro" "const { Content, remarkPluginFrontmatter } = await render(entry);" "

{remarkPluginFrontmatter.minutesRead}

" - --- - import { getCollection, render } from 'astro:content'; - - export async function getStaticPaths() { - const blog = await getCollection('blog'); - return blog.map(entry => ({ - params: { slug: entry.id }, - props: { entry }, - })); - } - - const { entry } = Astro.props; - const { Content, remarkPluginFrontmatter } = await render(entry); - --- - - - ... - - ... -

{remarkPluginFrontmatter.minutesRead}

- ... - - - ``` - - If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `minutesRead` frontmatter property from `Astro.props` in your layout template. - - ```astro title="src/layouts/BlogLayout.astro" "const { minutesRead } = Astro.props.frontmatter;" "

{minutesRead}

" - --- - const { minutesRead } = Astro.props.frontmatter; - --- - - - ... - -

{minutesRead}

- - - - ``` -
+ + + + 1. Install the following packages: + - [`reading-time`](https://www.npmjs.com/package/reading-time) to calculate minutes read + - [`mdast-util-to-string`](https://www.npmjs.com/package/mdast-util-to-string) to extract all text from your markdown + - [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins): + - [`satteri`](https://www.npmjs.com/package/satteri) to create a [Sätteri `mdast` plugin](https://satteri.bruits.org/docs/plugins/#mdast-plugins): + + + + ```shell + npm install reading-time mdast-util-to-string @astrojs/markdown-satteri satteri + ``` + + + ```shell + pnpm add reading-time mdast-util-to-string @astrojs/markdown-satteri satteri + ``` + + + ```shell + yarn add reading-time mdast-util-to-string @astrojs/markdown-satteri satteri + ``` + + + + 2. Create a Markdown plugin. + + This plugin uses the `mdast-util-to-string` package to get the Markdown file's text. This text is then passed to the `reading-time` package to calculate the reading time in minutes. + + ```js title="src/mdast/mdast-reading-time.ts" + import { toString } from 'mdast-util-to-string'; + import getReadingTime from 'reading-time'; + import { defineMdastPlugin, type MdastNode } from 'satteri'; + + const findRoot = ( + startNode: Readonly, + getParent: (node: Readonly) => Readonly | undefined + ): Readonly => { + let root = startNode; + let ancestor = getParent(root); + while (ancestor !== undefined) { + root = ancestor; + ancestor = getParent(root); + } + return root; + }; + + export const mdastReadingTimePlugin = defineMdastPlugin({ + name: 'mdast-reading-time', + text(node, context) { + if (typeof context.data.astro?.frontmatter.minutesRead === 'string') return; + + const tree = findRoot(node, (n) => context.parent(n)); + const textOnPage = toString(tree); + const readingTime = getReadingTime(textOnPage); + + if (context.data.astro !== undefined) { + // readingTime.text will give us minutes read as a friendly string, + // i.e. "3 min read" + context.data.astro.frontmatter.minutesRead = readingTime.text; + } + }, + }); + ``` + + 3. Add the plugin to your config: + + ```js title="astro.config.mjs" {1,3,7-9} + import { satteri } from '@astrojs/markdown-satteri'; + import { defineConfig } from 'astro/config'; + import { mdastReadingTimePlugin } from './src/mdast/mdast-reading-time'; + + export default defineConfig({ + markdown: { + processor: satteri({ + mdastPlugins: [mdastReadingTimePlugin], + }), + }, + }); + ``` + + Now all Markdown documents will have a calculated `minutesRead` property in their frontmatter. + + 4. Display Reading Time + + If your blog posts are stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then, render `minutesRead` in your template wherever you would like it to appear. + + ```astro title="src/pages/posts/[slug].astro" "const { Content, remarkPluginFrontmatter } = await render(entry);" "

{remarkPluginFrontmatter.minutesRead}

" + --- + import { getCollection, render } from "astro:content"; + + export async function getStaticPaths() { + const blog = await getCollection("blog"); + return blog.map((entry) => ({ + params: { slug: entry.id }, + props: { entry }, + })); + } + + const { entry } = Astro.props; + const { Content, remarkPluginFrontmatter } = await render(entry); + --- + + + + + {entry.data.title} + + + {entry.data.title} +

{remarkPluginFrontmatter.minutesRead}

+ + + + ``` + + If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `minutesRead` frontmatter property from `Astro.props` in your layout template. + + ```astro title="src/layouts/BlogLayout.astro" "const { minutesRead } = Astro.props.frontmatter;" "

{minutesRead}

" + --- + const { minutesRead } = Astro.props.frontmatter; + --- + + + + + + +

{minutesRead}

+ + + + ``` +
+
+ + + 1. Install the following packages: + - [`reading-time`](https://www.npmjs.com/package/reading-time) to calculate minutes read + - [`mdast-util-to-string`](https://www.npmjs.com/package/mdast-util-to-string) to extract all text from your markdown + - [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins): + + + + ```shell + npm install reading-time mdast-util-to-string @astrojs/markdown-remark + ``` + + + ```shell + pnpm add reading-time mdast-util-to-string @astrojs/markdown-remark + ``` + + + ```shell + yarn add reading-time mdast-util-to-string @astrojs/markdown-remark + ``` + + + + 2. Create a [Markdown plugin](https://github.com/remarkjs/remark). + + This plugin uses the `mdast-util-to-string` package to get the Markdown file's text. This text is then passed to the `reading-time` package to calculate the reading time in minutes. + + ```js title="remark-reading-time.mjs" + import getReadingTime from 'reading-time'; + import { toString } from 'mdast-util-to-string'; + + export function remarkReadingTime() { + return function (tree, { data }) { + const textOnPage = toString(tree); + const readingTime = getReadingTime(textOnPage); + // readingTime.text will give us minutes read as a friendly string, + // i.e. "3 min read" + data.astro.frontmatter.minutesRead = readingTime.text; + }; + } + ``` + + 3. Add the plugin to your config: + + ```js title="astro.config.mjs" {1,3,7-9} + import { unified } from '@astrojs/markdown-remark'; + import { defineConfig } from 'astro/config'; + import { remarkReadingTime } from './remark-reading-time.mjs'; + + export default defineConfig({ + markdown: { + processor: unified({ + remarkPlugins: [remarkReadingTime], + }), + }, + }); + ``` + + Now all Markdown documents will have a calculated `minutesRead` property in their frontmatter. + + 4. Display Reading Time + + If your blog posts are stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then, render `minutesRead` in your template wherever you would like it to appear. + + ```astro title="src/pages/posts/[slug].astro" "const { Content, remarkPluginFrontmatter } = await render(entry);" "

{remarkPluginFrontmatter.minutesRead}

" + --- + import { getCollection, render } from "astro:content"; + + export async function getStaticPaths() { + const blog = await getCollection("blog"); + return blog.map((entry) => ({ + params: { slug: entry.id }, + props: { entry }, + })); + } + + const { entry } = Astro.props; + const { Content, remarkPluginFrontmatter } = await render(entry); + --- + + + + + {entry.data.title} + + + {entry.data.title} +

{remarkPluginFrontmatter.minutesRead}

+ + + + ``` + + If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `minutesRead` frontmatter property from `Astro.props` in your layout template. + + ```astro title="src/layouts/BlogLayout.astro" "const { minutesRead } = Astro.props.frontmatter;" "

{minutesRead}

" + --- + const { minutesRead } = Astro.props.frontmatter; + --- + + + + + + +

{minutesRead}

+ + + + ``` +
+
+
From 78c73072a6e6ea85547c836f7f5dca05cf927927 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 22:21:27 +0200 Subject: [PATCH 09/13] inverse tabs and steps in `modified-time.mdx` --- src/content/docs/en/recipes/modified-time.mdx | 365 ++++++++---------- 1 file changed, 153 insertions(+), 212 deletions(-) diff --git a/src/content/docs/en/recipes/modified-time.mdx b/src/content/docs/en/recipes/modified-time.mdx index 09eeca3211914..8ce2371f46d51 100644 --- a/src/content/docs/en/recipes/modified-time.mdx +++ b/src/content/docs/en/recipes/modified-time.mdx @@ -15,10 +15,16 @@ This recipe calculates time based on your repository’s Git history and may not ## Recipe - - - - 1. Install [`Day.js`](https://www.npmjs.com/package/dayjs) to modify and format times, [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to configure [the `satteri()` processor](/en/guides/markdown-content/#using-sätteri-plugins-and-features), and [`satteri`](https://www.npmjs.com/package/satteri) to create a [Sätteri `mdast` plugin](https://satteri.bruits.org/docs/plugins/#mdast-plugins): + +1. Add the necessary packages to your project. + + Install [`Day.js`](https://www.npmjs.com/package/dayjs) to modify and format times. You also need additional packages depending on the Markdown processor you use. + + + + Install: + - [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to use [the `satteri()` processor](/en/guides/markdown-content/#using-sätteri-plugins-and-features) + - [`satteri`](https://www.npmjs.com/package/satteri) to create a [Sätteri `mdast` plugin](https://satteri.bruits.org/docs/plugins/#mdast-plugins) @@ -37,11 +43,37 @@ This recipe calculates time based on your repository’s Git history and may not ``` + + + Install: + - [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins) + + + + ```shell + npm install dayjs @astrojs/markdown-remark + ``` + + + ```shell + pnpm add dayjs @astrojs/markdown-remark + ``` + + + ```shell + yarn add dayjs @astrojs/markdown-remark + ``` + + + + - 2. Create a mdast plugin +2. Create a Markdown plugin. - This plugin uses `execSync` to run a Git command that returns the timestamp of the latest commit in ISO 8601 format. The timestamp is then added to the frontmatter of the file. + This plugin uses `execSync` to run a Git command that returns the timestamp of the latest commit in ISO 8601 format. The timestamp is then added to the frontmatter of the file. + + ```js title="src/mdast/mdast-modified-time.ts" import { execSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; @@ -65,12 +97,30 @@ This recipe calculates time based on your repository’s Git history and may not }, }); ``` -
- Using the file system instead of Git + + + ```js title="remark-modified-time.mjs" + import { execSync } from "node:child_process"; + + export function remarkModifiedTime() { + return function (tree, file) { + const filepath = file.history[0]; + const result = execSync(`git log -1 --pretty="format:%cI" "${filepath}"`); + file.data.astro.frontmatter.lastModified = result.toString(); + }; + } + ``` + + + +
+ Using the file system instead of Git - Although using Git is the recommended way to get the last modified timestamp from a file, it is possible to use the file system modified time. - This plugin uses `statSync` to get the `mtime` (modified time) of the file in ISO 8601 format. The timestamp is then added to the frontmatter of the file. - + Although using Git is the recommended way to get the last modified timestamp from a file, it is possible to use the file system modified time. + This plugin uses `statSync` to get the `mtime` (modified time) of the file in ISO 8601 format. The timestamp is then added to the frontmatter of the file. + + + ```js title="src/mdast/mdast-modified-time.ts" import { statSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; @@ -94,10 +144,27 @@ This recipe calculates time based on your repository’s Git history and may not }, }); ``` -
+ + + ```js title="remark-modified-time.mjs" + import { statSync } from "fs"; + + export function remarkModifiedTime() { + return function (tree, file) { + const filepath = file.history[0]; + const result = statSync(filepath); + file.data.astro.frontmatter.lastModified = result.mtime.toISOString(); + }; + } + ``` + + +
- 3. Add the plugin to your config +3. Add the plugin to your config: + + ```js title="astro.config.mjs" import { satteri } from '@astrojs/markdown-satteri'; import { defineConfig } from 'astro/config'; @@ -111,134 +178,8 @@ This recipe calculates time based on your repository’s Git history and may not }, }); ``` - - Now all Markdown documents will have a `lastModified` property in their frontmatter. - - 4. Display Last Modified Time - - If your content is stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then render `lastModified` in your template wherever you would like it to appear. - - ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,31} - --- - import { getCollection, render } from "astro:content"; - import dayjs from "dayjs"; - import utc from "dayjs/plugin/utc"; - - dayjs.extend(utc); - - export async function getStaticPaths() { - const blog = await getCollection("blog"); - return blog.map((entry) => ({ - params: { slug: entry.id }, - props: { entry }, - })); - } - - const { entry } = Astro.props; - const { Content, remarkPluginFrontmatter } = await render(entry); - - const lastModified = dayjs(remarkPluginFrontmatter.lastModified) - .utc() - .format("HH:mm:ss DD MMMM YYYY UTC"); - --- - - - - - {entry.data.title} - - - {entry.data.title} -

Last Modified: {lastModified}

- - - - ``` - - If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `lastModified` frontmatter property from `Astro.props` in your layout template. - - ```astro title="src/layouts/BlogLayout.astro" {2-3,5,7-9,17} - --- - import dayjs from "dayjs"; - import utc from "dayjs/plugin/utc"; - - dayjs.extend(utc); - - const lastModified = dayjs() - .utc(Astro.props.frontmatter.lastModified) - .format("HH:mm:ss DD MMMM YYYY UTC"); - --- - - - - - - -

{lastModified}

- - - - ``` -
-
- - - 1. Install [`Day.js`](https://www.npmjs.com/package/dayjs) to modify and format times, and [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins): - - - - ```shell - npm install dayjs @astrojs/markdown-remark - ``` - - - ```shell - pnpm add dayjs @astrojs/markdown-remark - ``` - - - ```shell - yarn add dayjs @astrojs/markdown-remark - ``` - - - - 2. Create a [Remark Plugin](https://github.com/remarkjs/remark) - - This plugin uses `execSync` to run a Git command that returns the timestamp of the latest commit in ISO 8601 format. The timestamp is then added to the frontmatter of the file. - - ```js title="remark-modified-time.mjs" - import { execSync } from "node:child_process"; - - export function remarkModifiedTime() { - return function (tree, file) { - const filepath = file.history[0]; - const result = execSync(`git log -1 --pretty="format:%cI" "${filepath}"`); - file.data.astro.frontmatter.lastModified = result.toString(); - }; - } - ``` -
- Using the file system instead of Git - - Although using Git is the recommended way to get the last modified timestamp from a file, it is possible to use the file system modified time. - This plugin uses `statSync` to get the `mtime` (modified time) of the file in ISO 8601 format. The timestamp is then added to the frontmatter of the file. - - ```js title="remark-modified-time.mjs" - import { statSync } from "fs"; - - export function remarkModifiedTime() { - return function (tree, file) { - const filepath = file.history[0]; - const result = statSync(filepath); - file.data.astro.frontmatter.lastModified = result.mtime.toISOString(); - }; - } - ``` -
- - 3. Add the plugin to your config - +
+ ```js title="astro.config.mjs" import { unified } from '@astrojs/markdown-remark'; import { defineConfig } from 'astro/config'; @@ -252,74 +193,74 @@ This recipe calculates time based on your repository’s Git history and may not }, }); ``` - - Now all Markdown documents will have a `lastModified` property in their frontmatter. - - 4. Display Last Modified Time - - If your content is stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then render `lastModified` in your template wherever you would like it to appear. - - ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,31} - --- - import { getCollection, render } from "astro:content"; - import dayjs from "dayjs"; - import utc from "dayjs/plugin/utc"; - - dayjs.extend(utc); - - export async function getStaticPaths() { - const blog = await getCollection("blog"); - return blog.map((entry) => ({ - params: { slug: entry.id }, - props: { entry }, - })); - } - - const { entry } = Astro.props; - const { Content, remarkPluginFrontmatter } = await render(entry); - - const lastModified = dayjs(remarkPluginFrontmatter.lastModified) - .utc() - .format("HH:mm:ss DD MMMM YYYY UTC"); - --- - - - - - {entry.data.title} - - - {entry.data.title} -

Last Modified: {lastModified}

- - - - ``` - - If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `lastModified` frontmatter property from `Astro.props` in your layout template. - - ```astro title="src/layouts/BlogLayout.astro" {2-3,5,7-9,17} - --- - import dayjs from "dayjs"; - import utc from "dayjs/plugin/utc"; - - dayjs.extend(utc); - - const lastModified = dayjs() - .utc(Astro.props.frontmatter.lastModified) - .format("HH:mm:ss DD MMMM YYYY UTC"); - --- - - - - - - -

{lastModified}

- - - - ``` - -
-
+ + + + Now all Markdown documents will have a `lastModified` property in their frontmatter. + +4. Display Last Modified Time + + If your content is stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then render `lastModified` in your template wherever you would like it to appear. + + ```astro title="src/pages/posts/[slug].astro" {3-4,6,17,19-21,31} + --- + import { getCollection, render } from "astro:content"; + import dayjs from "dayjs"; + import utc from "dayjs/plugin/utc"; + + dayjs.extend(utc); + + export async function getStaticPaths() { + const blog = await getCollection("blog"); + return blog.map((entry) => ({ + params: { slug: entry.id }, + props: { entry }, + })); + } + + const { entry } = Astro.props; + const { Content, remarkPluginFrontmatter } = await render(entry); + + const lastModified = dayjs(remarkPluginFrontmatter.lastModified) + .utc() + .format("HH:mm:ss DD MMMM YYYY UTC"); + --- + + + + + {entry.data.title} + + + {entry.data.title} +

Last Modified: {lastModified}

+ + + + ``` + + If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `lastModified` frontmatter property from `Astro.props` in your layout template. + + ```astro title="src/layouts/BlogLayout.astro" {2-3,5,7-9,17} + --- + import dayjs from "dayjs"; + import utc from "dayjs/plugin/utc"; + + dayjs.extend(utc); + + const lastModified = dayjs() + .utc(Astro.props.frontmatter.lastModified) + .format("HH:mm:ss DD MMMM YYYY UTC"); + --- + + + + + + +

{lastModified}

+ + + + ``` + From 6c0ce242c01f5e4989de7d80260968f3f0d599f3 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 22:25:15 +0200 Subject: [PATCH 10/13] inverse steps and tabs in `reading-time.mdx` --- src/content/docs/en/recipes/reading-time.mdx | 291 ++++++++----------- 1 file changed, 121 insertions(+), 170 deletions(-) diff --git a/src/content/docs/en/recipes/reading-time.mdx b/src/content/docs/en/recipes/reading-time.mdx index 03dd7c6a8d5f8..a39b4bf3e160e 100644 --- a/src/content/docs/en/recipes/reading-time.mdx +++ b/src/content/docs/en/recipes/reading-time.mdx @@ -11,37 +11,67 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o ## Recipe - - - - 1. Install the following packages: - - [`reading-time`](https://www.npmjs.com/package/reading-time) to calculate minutes read - - [`mdast-util-to-string`](https://www.npmjs.com/package/mdast-util-to-string) to extract all text from your markdown - - [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins): - - [`satteri`](https://www.npmjs.com/package/satteri) to create a [Sätteri `mdast` plugin](https://satteri.bruits.org/docs/plugins/#mdast-plugins): + +1. Install the following packages: + - [`reading-time`](https://www.npmjs.com/package/reading-time) to calculate minutes read + - [`mdast-util-to-string`](https://www.npmjs.com/package/mdast-util-to-string) to extract all text from your markdown + + You also need additional packages depending on the Markdown processor you use. + + + + Install: + - [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to use [the `satteri()` processor](/en/guides/markdown-content/#using-sätteri-plugins-and-features) + - [`satteri`](https://www.npmjs.com/package/satteri) to create a [Sätteri `mdast` plugin](https://satteri.bruits.org/docs/plugins/#mdast-plugins) ```shell - npm install reading-time mdast-util-to-string @astrojs/markdown-satteri satteri + npm install reading-time mdast-util-to-string @astrojs/markdown-satteri satteri ``` ```shell - pnpm add reading-time mdast-util-to-string @astrojs/markdown-satteri satteri + pnpm add reading-time mdast-util-to-string @astrojs/markdown-satteri satteri ``` ```shell - yarn add reading-time mdast-util-to-string @astrojs/markdown-satteri satteri + yarn add reading-time mdast-util-to-string @astrojs/markdown-satteri satteri ``` + + + Install: + - [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins) - 2. Create a Markdown plugin. + + + ```shell + npm install reading-time mdast-util-to-string @astrojs/markdown-remark + ``` + + + ```shell + pnpm add reading-time mdast-util-to-string @astrojs/markdown-remark + ``` + + + ```shell + yarn add reading-time mdast-util-to-string @astrojs/markdown-remark + ``` + + + + + +2. Create a Markdown plugin. - This plugin uses the `mdast-util-to-string` package to get the Markdown file's text. This text is then passed to the `reading-time` package to calculate the reading time in minutes. + This plugin uses the `mdast-util-to-string` package to get the Markdown file's text. This text is then passed to the `reading-time` package to calculate the reading time in minutes. + + ```js title="src/mdast/mdast-reading-time.ts" import { toString } from 'mdast-util-to-string'; import getReadingTime from 'reading-time'; @@ -77,106 +107,8 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o }, }); ``` - - 3. Add the plugin to your config: - - ```js title="astro.config.mjs" {1,3,7-9} - import { satteri } from '@astrojs/markdown-satteri'; - import { defineConfig } from 'astro/config'; - import { mdastReadingTimePlugin } from './src/mdast/mdast-reading-time'; - - export default defineConfig({ - markdown: { - processor: satteri({ - mdastPlugins: [mdastReadingTimePlugin], - }), - }, - }); - ``` - - Now all Markdown documents will have a calculated `minutesRead` property in their frontmatter. - - 4. Display Reading Time - - If your blog posts are stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then, render `minutesRead` in your template wherever you would like it to appear. - - ```astro title="src/pages/posts/[slug].astro" "const { Content, remarkPluginFrontmatter } = await render(entry);" "

{remarkPluginFrontmatter.minutesRead}

" - --- - import { getCollection, render } from "astro:content"; - - export async function getStaticPaths() { - const blog = await getCollection("blog"); - return blog.map((entry) => ({ - params: { slug: entry.id }, - props: { entry }, - })); - } - - const { entry } = Astro.props; - const { Content, remarkPluginFrontmatter } = await render(entry); - --- - - - - - {entry.data.title} - - - {entry.data.title} -

{remarkPluginFrontmatter.minutesRead}

- - - - ``` - - If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `minutesRead` frontmatter property from `Astro.props` in your layout template. - - ```astro title="src/layouts/BlogLayout.astro" "const { minutesRead } = Astro.props.frontmatter;" "

{minutesRead}

" - --- - const { minutesRead } = Astro.props.frontmatter; - --- - - - - - - -

{minutesRead}

- - - - ``` -
-
- - - 1. Install the following packages: - - [`reading-time`](https://www.npmjs.com/package/reading-time) to calculate minutes read - - [`mdast-util-to-string`](https://www.npmjs.com/package/mdast-util-to-string) to extract all text from your markdown - - [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins): - - - - ```shell - npm install reading-time mdast-util-to-string @astrojs/markdown-remark - ``` - - - ```shell - pnpm add reading-time mdast-util-to-string @astrojs/markdown-remark - ``` - - - ```shell - yarn add reading-time mdast-util-to-string @astrojs/markdown-remark - ``` - - - - 2. Create a [Markdown plugin](https://github.com/remarkjs/remark). - - This plugin uses the `mdast-util-to-string` package to get the Markdown file's text. This text is then passed to the `reading-time` package to calculate the reading time in minutes. - + + ```js title="remark-reading-time.mjs" import getReadingTime from 'reading-time'; import { toString } from 'mdast-util-to-string'; @@ -191,9 +123,28 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o }; } ``` + +
+ +3. Add the plugin to your config: - 3. Add the plugin to your config: + + + ```js title="astro.config.mjs" {1,3,7-9} + import { satteri } from '@astrojs/markdown-satteri'; + import { defineConfig } from 'astro/config'; + import { mdastReadingTimePlugin } from './src/mdast/mdast-reading-time'; + export default defineConfig({ + markdown: { + processor: satteri({ + mdastPlugins: [mdastReadingTimePlugin], + }), + }, + }); + ``` + + ```js title="astro.config.mjs" {1,3,7-9} import { unified } from '@astrojs/markdown-remark'; import { defineConfig } from 'astro/config'; @@ -207,59 +158,59 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o }, }); ``` - - Now all Markdown documents will have a calculated `minutesRead` property in their frontmatter. - - 4. Display Reading Time - - If your blog posts are stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then, render `minutesRead` in your template wherever you would like it to appear. - - ```astro title="src/pages/posts/[slug].astro" "const { Content, remarkPluginFrontmatter } = await render(entry);" "

{remarkPluginFrontmatter.minutesRead}

" - --- - import { getCollection, render } from "astro:content"; - - export async function getStaticPaths() { - const blog = await getCollection("blog"); - return blog.map((entry) => ({ - params: { slug: entry.id }, - props: { entry }, - })); - } - - const { entry } = Astro.props; - const { Content, remarkPluginFrontmatter } = await render(entry); - --- - - - - - {entry.data.title} - - - {entry.data.title} -

{remarkPluginFrontmatter.minutesRead}

- - - - ``` - - If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `minutesRead` frontmatter property from `Astro.props` in your layout template. - - ```astro title="src/layouts/BlogLayout.astro" "const { minutesRead } = Astro.props.frontmatter;" "

{minutesRead}

" - --- - const { minutesRead } = Astro.props.frontmatter; - --- - - - - - - -

{minutesRead}

- - - - ``` - -
-
+ + + + Now all Markdown documents will have a calculated `minutesRead` property in their frontmatter. + +4. Display Reading Time + + If your blog posts are stored in a [content collection](/en/guides/content-collections/), access the `remarkPluginFrontmatter` from the `render(entry)` function. Then, render `minutesRead` in your template wherever you would like it to appear. + + ```astro title="src/pages/posts/[slug].astro" "const { Content, remarkPluginFrontmatter } = await render(entry);" "

{remarkPluginFrontmatter.minutesRead}

" + --- + import { getCollection, render } from "astro:content"; + + export async function getStaticPaths() { + const blog = await getCollection("blog"); + return blog.map((entry) => ({ + params: { slug: entry.id }, + props: { entry }, + })); + } + + const { entry } = Astro.props; + const { Content, remarkPluginFrontmatter } = await render(entry); + --- + + + + + {entry.data.title} + + + {entry.data.title} +

{remarkPluginFrontmatter.minutesRead}

+ + + + ``` + + If you're using a [Markdown layout](/en/basics/layouts/#markdown-layouts), use the `minutesRead` frontmatter property from `Astro.props` in your layout template. + + ```astro title="src/layouts/BlogLayout.astro" "const { minutesRead } = Astro.props.frontmatter;" "

{minutesRead}

" + --- + const { minutesRead } = Astro.props.frontmatter; + --- + + + + + + +

{minutesRead}

+ + + + ``` + From c578b00cafa3ef7bad8af9c606bdea4527105b30 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Sat, 18 Jul 2026 22:31:39 +0200 Subject: [PATCH 11/13] add a new MarkdownProcessorTabs to make sure the same key is used --- .../tabs/MarkdownProcessorTabs.astro | 12 +++++ .../docs/en/guides/markdown-content.mdx | 15 +++--- .../docs/en/recipes/external-links.mdx | 15 +++--- src/content/docs/en/recipes/modified-time.mdx | 51 ++++++++++--------- src/content/docs/en/recipes/reading-time.mdx | 39 +++++++------- 5 files changed, 74 insertions(+), 58 deletions(-) create mode 100644 src/components/tabs/MarkdownProcessorTabs.astro diff --git a/src/components/tabs/MarkdownProcessorTabs.astro b/src/components/tabs/MarkdownProcessorTabs.astro new file mode 100644 index 0000000000000..ccb64addc6bae --- /dev/null +++ b/src/components/tabs/MarkdownProcessorTabs.astro @@ -0,0 +1,12 @@ +--- +import { Tabs, TabItem } from '@astrojs/starlight/components'; +--- + + + + + + + + + diff --git a/src/content/docs/en/guides/markdown-content.mdx b/src/content/docs/en/guides/markdown-content.mdx index d70052eafb2b5..c754ebdf7830f 100644 --- a/src/content/docs/en/guides/markdown-content.mdx +++ b/src/content/docs/en/guides/markdown-content.mdx @@ -9,8 +9,9 @@ import Since from '~/components/Since.astro'; import { FileTree } from '@astrojs/starlight/components'; import RecipeLinks from "~/components/RecipeLinks.astro"; import ReadMore from '~/components/ReadMore.astro'; -import { Steps, Tabs, TabItem } from '@astrojs/starlight/components'; +import { Steps } from '@astrojs/starlight/components'; import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; +import MarkdownProcessorTabs from '~/components/tabs/MarkdownProcessorTabs.astro'; [Markdown](https://daringfireball.net/projects/markdown/) is commonly used to author text-heavy content like blog posts and documentation. Astro includes built-in support for Markdown files that can also include [frontmatter YAML](https://dev.to/paulasantamaria/introduction-to-yaml-125f) (or [TOML](https://toml.io)) to define custom properties such as a title, description, and tags. @@ -170,8 +171,8 @@ You can customize these heading IDs with a [Markdown processor](#choosing-a-mark Astro injects `id` attributes after your custom plugins have run, so any ID set by a plugin is preserved. If one of your custom plugins needs to access the IDs injected by Astro, you can import Astro's heading ids plugin and place it before any plugins that rely on it: - - + + ```js title="astro.config.mjs" ins={2-3, 9} import { defineConfig } from 'astro/config'; import { satteri, satteriHeadingIdsPlugin } from '@astrojs/markdown-satteri'; @@ -188,8 +189,8 @@ Astro injects `id` attributes after your custom plugins have run, so any ID set }, }); ``` - - + + ```js title="astro.config.mjs" ins={2-3, 9} import { defineConfig } from 'astro/config'; import { unified, rehypeHeadingIds } from '@astrojs/markdown-remark'; @@ -206,8 +207,8 @@ Astro injects `id` attributes after your custom plugins have run, so any ID set }, }); ``` - - + + ## Markdown Plugins diff --git a/src/content/docs/en/recipes/external-links.mdx b/src/content/docs/en/recipes/external-links.mdx index 78795bbc9fd24..cdc029b186dfb 100644 --- a/src/content/docs/en/recipes/external-links.mdx +++ b/src/content/docs/en/recipes/external-links.mdx @@ -4,8 +4,9 @@ description: Learn how to use a Markdown plugin to add icons to external links i i18nReady: true type: recipe --- -import { Steps, TabItem, Tabs } from '@astrojs/starlight/components'; +import { Steps } from '@astrojs/starlight/components'; import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; +import MarkdownProcessorTabs from '~/components/tabs/MarkdownProcessorTabs.astro'; Using a Markdown plugin, you can identify and modify links in your Markdown files that point to external sites. This example adds an icon to the end of each external link, so that visitors will know they are leaving your site. @@ -15,8 +16,8 @@ Using a Markdown plugin, you can identify and modify links in your Markdown file ## Recipe - - + + 1. Install both [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) and [`satteri`](https://www.npmjs.com/package/satteri). @@ -86,8 +87,8 @@ Using a Markdown plugin, you can identify and modify links in your Markdown file }); ``` - - + + 1. Install both the [`rehype-external-links`](https://www.npmjs.com/package/rehype-external-links) plugin and [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark). @@ -134,5 +135,5 @@ Using a Markdown plugin, you can identify and modify links in your Markdown file }); ``` - - + + diff --git a/src/content/docs/en/recipes/modified-time.mdx b/src/content/docs/en/recipes/modified-time.mdx index 8ce2371f46d51..444f246599764 100644 --- a/src/content/docs/en/recipes/modified-time.mdx +++ b/src/content/docs/en/recipes/modified-time.mdx @@ -4,8 +4,9 @@ description: Build a Markdown plugin to add the last modified time to your Markd i18nReady: true type: recipe --- -import { Steps, TabItem, Tabs } from '@astrojs/starlight/components'; +import { Steps } from '@astrojs/starlight/components'; import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; +import MarkdownProcessorTabs from '~/components/tabs/MarkdownProcessorTabs.astro'; Learn how to build a Markdown plugin that adds the last modified time as a [custom frontmatter property](/en/guides/markdown-content/#modifying-frontmatter-programmatically) of your Markdown and MDX files. Use this property to display the modified time in your pages. @@ -20,8 +21,8 @@ This recipe calculates time based on your repository’s Git history and may not Install [`Day.js`](https://www.npmjs.com/package/dayjs) to modify and format times. You also need additional packages depending on the Markdown processor you use. - - + + Install: - [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to use [the `satteri()` processor](/en/guides/markdown-content/#using-sätteri-plugins-and-features) - [`satteri`](https://www.npmjs.com/package/satteri) to create a [Sätteri `mdast` plugin](https://satteri.bruits.org/docs/plugins/#mdast-plugins) @@ -43,8 +44,8 @@ This recipe calculates time based on your repository’s Git history and may not ``` - - + + Install: - [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins) @@ -65,15 +66,15 @@ This recipe calculates time based on your repository’s Git history and may not ``` - - + + 2. Create a Markdown plugin. This plugin uses `execSync` to run a Git command that returns the timestamp of the latest commit in ISO 8601 format. The timestamp is then added to the frontmatter of the file. - - + + ```js title="src/mdast/mdast-modified-time.ts" import { execSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; @@ -97,8 +98,8 @@ This recipe calculates time based on your repository’s Git history and may not }, }); ``` - - + + ```js title="remark-modified-time.mjs" import { execSync } from "node:child_process"; @@ -110,8 +111,8 @@ This recipe calculates time based on your repository’s Git history and may not }; } ``` - - + +
Using the file system instead of Git @@ -119,8 +120,8 @@ This recipe calculates time based on your repository’s Git history and may not Although using Git is the recommended way to get the last modified timestamp from a file, it is possible to use the file system modified time. This plugin uses `statSync` to get the `mtime` (modified time) of the file in ISO 8601 format. The timestamp is then added to the frontmatter of the file. - - + + ```js title="src/mdast/mdast-modified-time.ts" import { statSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; @@ -144,8 +145,8 @@ This recipe calculates time based on your repository’s Git history and may not }, }); ``` - - + + ```js title="remark-modified-time.mjs" import { statSync } from "fs"; @@ -157,14 +158,14 @@ This recipe calculates time based on your repository’s Git history and may not }; } ``` - - + +
3. Add the plugin to your config: - - + + ```js title="astro.config.mjs" import { satteri } from '@astrojs/markdown-satteri'; import { defineConfig } from 'astro/config'; @@ -178,8 +179,8 @@ This recipe calculates time based on your repository’s Git history and may not }, }); ``` - - + + ```js title="astro.config.mjs" import { unified } from '@astrojs/markdown-remark'; import { defineConfig } from 'astro/config'; @@ -193,8 +194,8 @@ This recipe calculates time based on your repository’s Git history and may not }, }); ``` - - + + Now all Markdown documents will have a `lastModified` property in their frontmatter. diff --git a/src/content/docs/en/recipes/reading-time.mdx b/src/content/docs/en/recipes/reading-time.mdx index a39b4bf3e160e..553153acef455 100644 --- a/src/content/docs/en/recipes/reading-time.mdx +++ b/src/content/docs/en/recipes/reading-time.mdx @@ -4,8 +4,9 @@ description: Build a Markdown plugin to add reading time to your Markdown or MDX i18nReady: true type: recipe --- -import { Steps, TabItem, Tabs } from '@astrojs/starlight/components'; +import { Steps } from '@astrojs/starlight/components'; import PackageManagerTabs from '~/components/tabs/PackageManagerTabs.astro'; +import MarkdownProcessorTabs from '~/components/tabs/MarkdownProcessorTabs.astro'; Create a Markdown plugin which adds a reading time property to the frontmatter of your Markdown or MDX files. Use this property to display the reading time for each page. @@ -18,8 +19,8 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o You also need additional packages depending on the Markdown processor you use. - - + + Install: - [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to use [the `satteri()` processor](/en/guides/markdown-content/#using-sätteri-plugins-and-features) - [`satteri`](https://www.npmjs.com/package/satteri) to create a [Sätteri `mdast` plugin](https://satteri.bruits.org/docs/plugins/#mdast-plugins) @@ -41,8 +42,8 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o ``` - - + + Install: - [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins) @@ -63,15 +64,15 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o ``` - - + + 2. Create a Markdown plugin. This plugin uses the `mdast-util-to-string` package to get the Markdown file's text. This text is then passed to the `reading-time` package to calculate the reading time in minutes. - - + + ```js title="src/mdast/mdast-reading-time.ts" import { toString } from 'mdast-util-to-string'; import getReadingTime from 'reading-time'; @@ -107,8 +108,8 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o }, }); ``` - - + + ```js title="remark-reading-time.mjs" import getReadingTime from 'reading-time'; import { toString } from 'mdast-util-to-string'; @@ -123,13 +124,13 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o }; } ``` - - + + 3. Add the plugin to your config: - - + + ```js title="astro.config.mjs" {1,3,7-9} import { satteri } from '@astrojs/markdown-satteri'; import { defineConfig } from 'astro/config'; @@ -143,8 +144,8 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o }, }); ``` - - + + ```js title="astro.config.mjs" {1,3,7-9} import { unified } from '@astrojs/markdown-remark'; import { defineConfig } from 'astro/config'; @@ -158,8 +159,8 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o }, }); ``` - - + + Now all Markdown documents will have a calculated `minutesRead` property in their frontmatter. From 5986fa0e9fad7507a6fbe345cb60dfda0c5f4b9a Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Tue, 21 Jul 2026 14:53:52 +0200 Subject: [PATCH 12/13] reduce verbosity in `external-links` recipe --- src/content/docs/en/recipes/external-links.mdx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/content/docs/en/recipes/external-links.mdx b/src/content/docs/en/recipes/external-links.mdx index cdc029b186dfb..463b40808fe24 100644 --- a/src/content/docs/en/recipes/external-links.mdx +++ b/src/content/docs/en/recipes/external-links.mdx @@ -19,7 +19,7 @@ Using a Markdown plugin, you can identify and modify links in your Markdown file - 1. Install both [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) and [`satteri`](https://www.npmjs.com/package/satteri). + 1. Install both [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) and [`satteri`](https://www.npmjs.com/package/satteri): @@ -39,9 +39,7 @@ Using a Markdown plugin, you can identify and modify links in your Markdown file - 2. Create a [Sätteri `hast` plugin](https://satteri.bruits.org/docs/plugins/#hast-plugins). - - Create a new file in your project, for example `src/hast/hast-external-links.ts`, and add the following code: + 2. Create a [Sätteri `hast` plugin](https://satteri.bruits.org/docs/plugins/#hast-plugins) that adds an icon if the link starts with `http`: ```ts title="src/hast/hast-external-links.ts" import { defineHastPlugin } from 'satteri'; From ca27346c5a926b73b341bf9a713513b28d9e3b8c Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Tue, 21 Jul 2026 15:20:57 +0200 Subject: [PATCH 13/13] reword and improve code snippet readability --- src/content/docs/en/recipes/modified-time.mdx | 56 +++++++++---------- src/content/docs/en/recipes/reading-time.mdx | 44 +++++++-------- 2 files changed, 47 insertions(+), 53 deletions(-) diff --git a/src/content/docs/en/recipes/modified-time.mdx b/src/content/docs/en/recipes/modified-time.mdx index 444f246599764..75718c133302a 100644 --- a/src/content/docs/en/recipes/modified-time.mdx +++ b/src/content/docs/en/recipes/modified-time.mdx @@ -17,14 +17,12 @@ This recipe calculates time based on your repository’s Git history and may not ## Recipe -1. Add the necessary packages to your project. - - Install [`Day.js`](https://www.npmjs.com/package/dayjs) to modify and format times. You also need additional packages depending on the Markdown processor you use. +1. Install the following packages depending on the Markdown processor used: - Install: - - [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to use [the `satteri()` processor](/en/guides/markdown-content/#using-sätteri-plugins-and-features) + - [`Day.js`](https://www.npmjs.com/package/dayjs) to modify and format times + - [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to configure [the `satteri()` processor](/en/guides/markdown-content/#using-sätteri-plugins-and-features) - [`satteri`](https://www.npmjs.com/package/satteri) to create a [Sätteri `mdast` plugin](https://satteri.bruits.org/docs/plugins/#mdast-plugins) @@ -46,7 +44,7 @@ This recipe calculates time based on your repository’s Git history and may not - Install: + - [`Day.js`](https://www.npmjs.com/package/dayjs) to modify and format times - [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins) @@ -76,18 +74,16 @@ This recipe calculates time based on your repository’s Git history and may not ```js title="src/mdast/mdast-modified-time.ts" - import { execSync } from 'node:child_process'; - import { fileURLToPath } from 'node:url'; - import { defineMdastPlugin } from 'satteri'; + import { execSync } from "node:child_process"; + import { fileURLToPath } from "node:url"; + import { defineMdastPlugin } from "satteri"; export const mdastModifiedTimePlugin = defineMdastPlugin({ - name: 'mdast-modified-time', + name: "mdast-modified-time", text(node, context) { - if ( - typeof context.data.astro?.frontmatter.lastModified === 'string' || - !context.fileURL - ) - return; + // Check if `lastModified` is already set for this file. + const isLastModifiedSet = !!context.data.astro?.frontmatter.lastModified; + if (isLastModifiedSet || !context.fileURL) return; const filepath = fileURLToPath(context.fileURL); const result = execSync(`git log -1 --pretty="format:%cI" "${filepath}"`); @@ -123,18 +119,16 @@ This recipe calculates time based on your repository’s Git history and may not ```js title="src/mdast/mdast-modified-time.ts" - import { statSync } from 'node:fs'; - import { fileURLToPath } from 'node:url'; - import { defineMdastPlugin } from 'satteri'; + import { statSync } from "node:fs"; + import { fileURLToPath } from "node:url"; + import { defineMdastPlugin } from "satteri"; export const mdastModifiedTimePlugin = defineMdastPlugin({ - name: 'mdast-modified-time', + name: "mdast-modified-time", text(node, context) { - if ( - typeof context.data.astro?.frontmatter.lastModified === 'string' || - !context.fileURL - ) - return; + // Check if `lastModified` is already set for this file. + const isLastModifiedSet = !!context.data.astro?.frontmatter.lastModified; + if (isLastModifiedSet || !context.fileURL) return; const filepath = fileURLToPath(context.fileURL); const result = statSync(filepath); @@ -148,7 +142,7 @@ This recipe calculates time based on your repository’s Git history and may not ```js title="remark-modified-time.mjs" - import { statSync } from "fs"; + import { statSync } from "node:fs"; export function remarkModifiedTime() { return function (tree, file) { @@ -167,9 +161,9 @@ This recipe calculates time based on your repository’s Git history and may not ```js title="astro.config.mjs" - import { satteri } from '@astrojs/markdown-satteri'; - import { defineConfig } from 'astro/config'; - import { mdastModifiedTimePlugin } from './src/mdast/mdast-modified-time'; + import { satteri } from "@astrojs/markdown-satteri"; + import { defineConfig } from "astro/config"; + import { mdastModifiedTimePlugin } from "./src/mdast/mdast-modified-time"; export default defineConfig({ markdown: { @@ -182,9 +176,9 @@ This recipe calculates time based on your repository’s Git history and may not ```js title="astro.config.mjs" - import { unified } from '@astrojs/markdown-remark'; - import { defineConfig } from 'astro/config'; - import { remarkModifiedTime } from './remark-modified-time.mjs'; + import { unified } from "@astrojs/markdown-remark"; + import { defineConfig } from "astro/config"; + import { remarkModifiedTime } from "./remark-modified-time.mjs"; export default defineConfig({ markdown: { diff --git a/src/content/docs/en/recipes/reading-time.mdx b/src/content/docs/en/recipes/reading-time.mdx index 553153acef455..91957aa15d8de 100644 --- a/src/content/docs/en/recipes/reading-time.mdx +++ b/src/content/docs/en/recipes/reading-time.mdx @@ -13,16 +13,13 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o ## Recipe -1. Install the following packages: - - [`reading-time`](https://www.npmjs.com/package/reading-time) to calculate minutes read - - [`mdast-util-to-string`](https://www.npmjs.com/package/mdast-util-to-string) to extract all text from your markdown - - You also need additional packages depending on the Markdown processor you use. +1. Install the following packages depending on the Markdown processor used: - Install: - - [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to use [the `satteri()` processor](/en/guides/markdown-content/#using-sätteri-plugins-and-features) + - [`reading-time`](https://www.npmjs.com/package/reading-time) to calculate minutes read + - [`mdast-util-to-string`](https://www.npmjs.com/package/mdast-util-to-string) to extract all text from your markdown + - [`@astrojs/markdown-satteri`](https://www.npmjs.com/package/@astrojs/markdown-satteri) to configure [the `satteri()` processor](/en/guides/markdown-content/#using-sätteri-plugins-and-features) - [`satteri`](https://www.npmjs.com/package/satteri) to create a [Sätteri `mdast` plugin](https://satteri.bruits.org/docs/plugins/#mdast-plugins) @@ -44,7 +41,8 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o - Install: + - [`reading-time`](https://www.npmjs.com/package/reading-time) to calculate minutes read + - [`mdast-util-to-string`](https://www.npmjs.com/package/mdast-util-to-string) to extract all text from your markdown - [`@astrojs/markdown-remark`](https://www.npmjs.com/package/@astrojs/markdown-remark) to use [the `unified()` processor](/en/guides/markdown-content/#using-remark-and-rehype-plugins) @@ -74,13 +72,13 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o ```js title="src/mdast/mdast-reading-time.ts" - import { toString } from 'mdast-util-to-string'; - import getReadingTime from 'reading-time'; - import { defineMdastPlugin, type MdastNode } from 'satteri'; + import { toString } from "mdast-util-to-string"; + import getReadingTime from "reading-time"; + import { defineMdastPlugin, type MdastNode } from "satteri"; const findRoot = ( startNode: Readonly, - getParent: (node: Readonly) => Readonly | undefined + getParent: (node: Readonly) => Readonly | undefined, ): Readonly => { let root = startNode; let ancestor = getParent(root); @@ -92,9 +90,11 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o }; export const mdastReadingTimePlugin = defineMdastPlugin({ - name: 'mdast-reading-time', + name: "mdast-reading-time", text(node, context) { - if (typeof context.data.astro?.frontmatter.minutesRead === 'string') return; + // Check if `minutesRead` is already set for this file. + const isMinutesReadSet = !!context.data.astro?.frontmatter.minutesRead; + if (isMinutesReadSet) return; const tree = findRoot(node, (n) => context.parent(n)); const textOnPage = toString(tree); @@ -111,8 +111,8 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o ```js title="remark-reading-time.mjs" - import getReadingTime from 'reading-time'; - import { toString } from 'mdast-util-to-string'; + import getReadingTime from "reading-time"; + import { toString } from "mdast-util-to-string"; export function remarkReadingTime() { return function (tree, { data }) { @@ -132,9 +132,9 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o ```js title="astro.config.mjs" {1,3,7-9} - import { satteri } from '@astrojs/markdown-satteri'; - import { defineConfig } from 'astro/config'; - import { mdastReadingTimePlugin } from './src/mdast/mdast-reading-time'; + import { satteri } from "@astrojs/markdown-satteri"; + import { defineConfig } from "astro/config"; + import { mdastReadingTimePlugin } from "./src/mdast/mdast-reading-time"; export default defineConfig({ markdown: { @@ -147,9 +147,9 @@ Create a Markdown plugin which adds a reading time property to the frontmatter o ```js title="astro.config.mjs" {1,3,7-9} - import { unified } from '@astrojs/markdown-remark'; - import { defineConfig } from 'astro/config'; - import { remarkReadingTime } from './remark-reading-time.mjs'; + import { unified } from "@astrojs/markdown-remark"; + import { defineConfig } from "astro/config"; + import { remarkReadingTime } from "./remark-reading-time.mjs"; export default defineConfig({ markdown: {