Following the Astro Markdoc with Obsidian setup , I needed to replace features that aren't yet available in Astro's Markdoc integration, but before getting into all that, a sidebar on my Markdoc configuration.
NOTE: I have reverted my other posts to Markdown since I wrote this post because my writing tool (Obsidian) is not dealing well with .mdoc
extensions. This article, however, is still Markdoc to show you the result of the code samples on this page. The code samples do not include the copy-link or copy-code functionality.
Organized Markdoc configs
All my custom node and tag configurations sit in the src/markdoc/config
folder. I plan to create additional nodes and tags for Markdoc, so I want them well organized.
I export all my nodes:
and tags:
for easy use in the markdoc.config.mjs
file:
I also keep dedicated components in the markdoc
folder. If the component has uses outside of rendering nodes or tags, I move it to src/components/astro
(as with the AppLink.astro
component).
NOTE: All my examples use Tailwind CSS with some custom classes. If you get errors, remove any strange color-related and prose*
classes.
Prevent double wrapping with the article tag
If you, like me, handle the display of frontmatter data outside the .mdoc
file and only use the Astro <Content/>
component for rendering the post body, you may notice that Markdoc wraps the body in an article
tag.
At the same time, you may have already wrapped your post data in an article
tag - as I did. So now you have a nested article
tag that makes no sense.
To prevent this double wrapping:
Create a
document.markdoc.ts
file with the following content to only render the child nodes:Add the custom node to your
nodes
configuration inmarkdoc.config.mjs
.
Enable linked headings for the table of contents and copy-to-clipboard functionality
In Astro, you can add remark
and rehype
plugins to get linked headings in your markdown content and create a table of contents . But how about Markdoc?
In .mdoc
files, the headings
key returned by the Astro rendering function has an empty array value.
We can generate them with a structure similar to those generated from .md
files, so a Table of Contents component would work for both file formats if you're incrementally migrating to Markdoc.
We need the correct heading output structure to navigate to the heading and copy the link with one click.
NOTE: This example does not show how to implement the copy-to-clipboard functionality you are experiencing on this blog.
To define the custom heading tag:
Install the
slugify
package:In your utility files, define the options for slugifying strings:
Create a function to slugify the content of the heading:
Create a function to grab the full heading text content:
Define the custom heading node (mine sits in
/src/markdoc/config/nodes/heading.markdoc.ts
):NOTE: Feel free to use interpolation for the heading
id
and the linkhref
attributes. Markdown is complaining about this code block if I use interpolation.Add the custom node to your
nodes
configuration inmarkdoc.config.mjs
.
The document should now contain headings with the following rendered markup:
You can change the order of the link
and content
nodes if you prefer the link at the start of the heading.
You now have a span you can style and use for "click-to-copy" functionality.
Create the table of contents
With headings proudly displaying an id
attribute, we can now link to them from a table of contents.
To create the table of contents:
Stating from the recipe in the Markdoc documentation , define a function that can iterate over an abstract syntax tree (AST) matching our heading attributes:
In the Astro page, for example
src/pages/posts/[slug].astro
, prepare the headings:In the
src/component
folder, create anAppLink.astro
component to render links:NOTE: Having this component may seem overkill, but the bright side is that you can use it to render the links inside your .mdoc files too.
Create a
TableOfContents
component you can use with the headings. For example:In the Astro page, for example
src/pages/posts/[slug].astro
, import the new component:Use the component, passing the
toc
constant as thetoc
prop to the component. For example:
Add the reading time to your post/page
Showing the reading time on your articles is a good idea and a good reader experience. For example, looking at this article's reading time, unless you just want a quick look, you must set aside some time to go through it.
To add reading time to Markdoc content:
Define the function that computes the reading time:
Use the function where you want to display reading time. For example:
Add Shiki syntax highlighting to code blocks
Shiki support is not available with the Astro Markdoc extension. In my few days using Astro with plain Markdown, I customized a Shiki theme to match my overall theme better, so I didn't want to lose that.
To add Shiki support to your Markdoc documents:
Define a custom
code
tag to ensure things don't change with future updates. My customcode
definition sits insrc/markdoc/config/tags/code.markdoc.ts
:Add the custom tag to your
tags
configuration inmarkdoc.config.mjs
.Define a custom
Code
component. We will use this component to render a custom fence node in the following steps. TheCode
component uses the AstroCode
component:Define a custom
fence
Markdoc node that renders the newCode
component. My customcode
definition sits insrc/markdoc/config/nodes/fence.markdoc.ts
:Add the custom node to your
nodes
configuration inmarkdoc.config.mjs
.
You should be able to enjoy Shiki in your rendered code blocks now.
Handle links inside the Markdoc content
If you want to style links based on their type, you need to know what kind of a link it is.
If you want to open external links in a new tab, you have several options, but the fastest is making the link render with the necessary attributes.
When creating the table of contents, you already made the component that will help you get control over link rendering.
But it would help if you told Markdoc what to do with the links when it parses and transforms them.
To customize link tags in Markdoc:
Create a custom markdoc link tag configuration file
link.markdoc.ts
, using the previously createdAppLink.astro
component for rendering the tag:Add the custom node to your
nodes
configuration inmarkdoc.config.mjs
.
External links should now open in a new tab and have the rel
attributes. Adjust the logic to your needs in the AppLink.astro
component.
TIP: Did you notice the image
in the children
array? If you need to have linked images in your posts, you can do so now. For example:
renders this cute fellow, nicely wrapped in a link that opens in a new tab/window:
That's all for now! 🎊