-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #240 from jermspeaks/feature/asides
Feature/asides
- Loading branch information
Showing
7 changed files
with
1,139 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import type { AstroIntegration } from "astro"; | ||
import type * as mdast from "mdast"; | ||
import remarkDirective from "remark-directive"; | ||
import type * as unified from "unified"; | ||
import { remove } from "unist-util-remove"; | ||
import { visit } from "unist-util-visit"; | ||
import { makeComponentNode } from "./utils/makeComponentNode"; | ||
|
||
const AsideTagname = "AutoImportedAside"; | ||
|
||
export const asideAutoImport: Record<string, [string, string][]> = { | ||
"../src/components/Aside.astro": [["default", AsideTagname]], | ||
}; | ||
|
||
/** | ||
* remark plugin that converts blocks delimited with `:::` into instances of | ||
* the `<Aside>` component. Depends on the `remark-directive` module for the | ||
* core parsing logic. | ||
* | ||
* For example, this Markdown | ||
* | ||
* ```md | ||
* :::tip[Did you know?] | ||
* Astro helps you build faster websites with “Islands Architecture”. | ||
* ::: | ||
* ``` | ||
* | ||
* will produce this output | ||
* | ||
* ```astro | ||
* <Aside type="tip" title="Did you know?"> | ||
* <p>Astro helps you build faster websites with “Islands Architecture”.</p> | ||
* </Aside> | ||
* ``` | ||
*/ | ||
function remarkAsides(): unified.Plugin<[], mdast.Root> { | ||
const variants = new Set(["note", "tip", "caution", "danger"]); | ||
|
||
const transformer: unified.Transformer<mdast.Root> = (tree) => { | ||
// @ts-expect-error Possibly infinite type instantiation we can’t do anything about. | ||
visit(tree, (node, index, parent) => { | ||
if (!parent || index === null || node.type !== "containerDirective") | ||
return; | ||
const type = node.name; | ||
if (!variants.has(type)) return; | ||
|
||
// remark-directive converts a container’s “label” to a paragraph in | ||
// its children, but we want to pass it as the title prop to <Aside>, so | ||
// we iterate over the children, find a directive label, store it for the | ||
// title prop, and remove the paragraph from children. | ||
let title: string | undefined; | ||
remove(node, (child) => { | ||
if (child.data?.directiveLabel) { | ||
if ("children" in child && "value" in child.children[0]) { | ||
title = child.children[0].value; | ||
} | ||
return true; | ||
} | ||
}); | ||
|
||
// Replace this node with the aside component it represents. | ||
parent.children[index] = makeComponentNode( | ||
AsideTagname, | ||
{ attributes: { type, title } }, | ||
...node.children | ||
); | ||
}); | ||
}; | ||
|
||
return function attacher() { | ||
return transformer; | ||
}; | ||
} | ||
|
||
/** | ||
* Astro integration that sets up the remark plugin and auto-imports the `<Aside>` component everywhere. | ||
*/ | ||
export function astroAsides(): AstroIntegration { | ||
return { | ||
name: "@astrojs/asides", | ||
hooks: { | ||
"astro:config:setup": ({ updateConfig }) => { | ||
updateConfig({ | ||
markdown: { | ||
remarkPlugins: [remarkDirective, remarkAsides()], | ||
}, | ||
}); | ||
}, | ||
}, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import type { BlockContent } from "mdast"; | ||
import type { MdxJsxAttribute, MdxJsxFlowElement } from "mdast-util-mdx-jsx"; | ||
|
||
interface NodeProps { | ||
attributes?: Record<string, string | boolean | number | undefined | null>; | ||
} | ||
|
||
/** | ||
* Create AST node for a custom component injection. | ||
* | ||
* @example | ||
* makeComponentNode('MyComponent', { prop: 'val' }, h('p', 'Paragraph inside component')) | ||
* | ||
*/ | ||
export function makeComponentNode( | ||
name: string, | ||
{ attributes = {} }: NodeProps = {}, | ||
...children: BlockContent[] | ||
): MdxJsxFlowElement { | ||
return { | ||
type: "mdxJsxFlowElement", | ||
name, | ||
attributes: Object.entries(attributes) | ||
// Filter out non-truthy attributes to avoid empty attrs being parsed as `true`. | ||
.filter(([_k, v]) => v !== false && Boolean(v)) | ||
.map(([name, value]) => ({ | ||
type: "mdxJsxAttribute", | ||
name, | ||
value: value as MdxJsxAttribute["value"], | ||
})), | ||
children, | ||
}; | ||
} |
Oops, something went wrong.