move rehype config out of astro.config.ts

This commit is contained in:
James Fenn
2023-04-11 01:45:48 -04:00
parent f23c5d61d3
commit 608e8e778a
9 changed files with 76 additions and 147 deletions

View File

@@ -7,17 +7,7 @@ import oembedTransformer from "@remark-embedder/transformer-oembed";
import { TwitchTransformer } from "./src/utils/markdown/remark-embedder-twitch";
import remarkTwoslash from "remark-shiki-twoslash";
import { UserConfigSettings } from "shiki-twoslash";
import rehypeSlug from "rehype-slug-custom-id";
import { rehypeHeaderText } from "./src/utils/markdown/rehype-header-text";
import { rehypeTabs } from "./src/utils/markdown/tabs";
import { rehypeAstroImageMd } from "./src/utils/markdown/rehype-astro-image-md";
import { rehypeUnicornElementMap } from "./src/utils/markdown/rehype-unicorn-element-map";
import { rehypeExcerpt } from "./src/utils/markdown/rehype-excerpt";
import { rehypeUnicornPopulatePost } from "./src/utils/markdown/rehype-unicorn-populate-post";
import { rehypeWordCount } from "./src/utils/markdown/rehype-word-count";
import { rehypeUnicornGetSuggestedPosts } from "./src/utils/markdown/rehype-unicorn-get-suggested-posts";
import { rehypeUnicornIFrameClickToRun } from "./src/utils/markdown/iframes/rehype-transform";
import { rehypeHeadingLinks } from "./src/utils/markdown/heading-links/rehype-transform";
import { createRehypePlugins } from "./src/utils/markdown";
import preact from "@astrojs/preact";
import sitemap from "@astrojs/sitemap";
import { EnumChangefreq as ChangeFreq } from "sitemap";
@@ -25,7 +15,6 @@ import { siteUrl } from "./src/constants/site-config";
// TODO: Create types
import behead from "remark-behead";
import rehypeRaw from "rehype-raw";
import image from "@astrojs/image";
import mdx from "@astrojs/mdx";
@@ -84,51 +73,6 @@ export default defineConfig({
} as UserConfigSettings,
],
],
rehypePlugins: [
rehypeUnicornPopulatePost,
rehypeUnicornGetSuggestedPosts,
// This is required to handle unsafe HTML embedded into Markdown
[rehypeRaw, { passThrough: [`mdxjsEsm`] }],
// Do not add the tabs before the slug. We rely on some of the heading
// logic in order to do some of the subheading logic
[
rehypeSlug,
{
maintainCase: true,
removeAccents: true,
enableCustomId: true,
},
],
[
rehypeTabs,
{
injectSubheaderProps: true,
tabSlugifyProps: {
enableCustomId: true,
},
},
],
rehypeHeaderText,
/**
* Insert custom HTML generation code here
*/
[
rehypeAstroImageMd,
{
maxHeight: 768,
maxWidth: 768,
},
],
rehypeUnicornIFrameClickToRun,
rehypeHeadingLinks,
rehypeUnicornElementMap,
[
rehypeExcerpt,
{
maxLength: 150,
},
],
rehypeWordCount,
],
rehypePlugins: createRehypePlugins({ format: "html" }),
} as AstroUserConfig["markdown"] as never,
});

View File

@@ -1,2 +1,6 @@
// default sizing used for iframes (MarkdownRenderer/media.tsx)
export const EMBED_SIZE = { w: "100%", h: 500 };
export interface MarkdownConfig {
format: "html" | "epub";
}

View File

@@ -2,6 +2,7 @@ import { Root, Element } from "hast";
import { Plugin } from "unified";
import { visit } from "unist-util-visit";
import { HeaderLink } from "./heading-link";
import { toString } from "hast-util-to-string";
/**
* Rehype plugin that adds a link SVG icon adjacent to each heading
@@ -19,7 +20,7 @@ export const rehypeHeadingLinks: Plugin<[], Root> = () => {
// create an absolute link icon adjacent to the header contents
const hastHeader = HeaderLink({
slug: node.properties.id.toString(),
title: node.properties["data-header-text"].toString(),
title: toString(node),
});
node.children = [hastHeader, ...node.children];

View File

@@ -0,0 +1,49 @@
import { RehypePlugins } from "astro";
import rehypeSlug from "rehype-slug-custom-id";
import rehypeRaw from "rehype-raw";
import { rehypeTabs } from "./tabs/rehype-transform";
import { rehypeAstroImageMd } from "./rehype-astro-image-md";
import { rehypeUnicornElementMap } from "./rehype-unicorn-element-map";
import { rehypeExcerpt } from "./rehype-excerpt";
import { rehypeUnicornPopulatePost } from "./rehype-unicorn-populate-post";
import { rehypeWordCount } from "./rehype-word-count";
import { rehypeUnicornGetSuggestedPosts } from "./rehype-unicorn-get-suggested-posts";
import { rehypeUnicornIFrameClickToRun } from "./iframes/rehype-transform";
import { rehypeHeadingLinks } from "./heading-links/rehype-transform";
import { MarkdownConfig } from "./constants";
export function createRehypePlugins(config: MarkdownConfig): RehypePlugins {
return [
rehypeUnicornPopulatePost,
rehypeUnicornGetSuggestedPosts,
// This is required to handle unsafe HTML embedded into Markdown
[rehypeRaw, { passThrough: [`mdxjsEsm`] }],
// Do not add the tabs before the slug. We rely on some of the heading
// logic in order to do some of the subheading logic
[
rehypeSlug,
{
maintainCase: true,
removeAccents: true,
enableCustomId: true,
},
],
...(config.format === "html" && [
/**
* Insert custom HTML generation code here
*/
rehypeTabs,
rehypeAstroImageMd,
rehypeUnicornIFrameClickToRun,
rehypeHeadingLinks,
rehypeUnicornElementMap,
]),
[
rehypeExcerpt,
{
maxLength: 150,
},
],
rehypeWordCount,
];
}

View File

@@ -17,15 +17,10 @@ import { getLargestSourceSetSrc } from "../get-largest-source-set-src";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
interface RehypeAstroImageProps {
maxHeight?: number;
maxWidth?: number;
}
const MAX_WIDTH = 768;
const MAX_HEIGHT = 768;
export const rehypeAstroImageMd: Plugin<
[RehypeAstroImageProps | never],
Root
> = ({ maxHeight, maxWidth }) => {
export const rehypeAstroImageMd: Plugin<[], Root> = () => {
return async (tree, file) => {
const imgNodes: any[] = [];
visit(tree, (node: any) => {
@@ -79,14 +74,14 @@ export const rehypeAstroImageMd: Plugin<
const imgRatioHeight = dimensions.height / dimensions.width;
const imgRatioWidth = dimensions.width / dimensions.height;
if (maxHeight && dimensions.height > maxHeight) {
dimensions.height = maxHeight;
dimensions.width = maxHeight * imgRatioWidth;
if (dimensions.height > MAX_HEIGHT) {
dimensions.height = MAX_HEIGHT;
dimensions.width = MAX_HEIGHT * imgRatioWidth;
}
if (maxWidth && dimensions.width > maxWidth) {
dimensions.width = maxWidth;
dimensions.height = maxWidth * imgRatioHeight;
if (dimensions.width > MAX_WIDTH) {
dimensions.width = MAX_WIDTH;
dimensions.height = MAX_WIDTH * imgRatioHeight;
}
const pictureResult = await getPicture({

View File

@@ -1,36 +0,0 @@
import { headingRank } from "hast-util-heading-rank";
import { hasProperty } from "hast-util-has-property";
import { toString } from "hast-util-to-string";
import { Root, Parent } from "hast";
import { visit } from "unist-util-visit";
/**
* Plugin to add `data-header-text`s to headings.
*/
export const rehypeHeaderText = () => {
return (tree: Root, file) => {
visit(tree, "element", (node: Parent["children"][number]) => {
if (
headingRank(node) &&
"properties" in node &&
node.properties &&
!hasProperty(node, "data-header-text")
) {
const headerText = toString(node);
node.properties["data-header-text"] = headerText;
const headingWithID = {
value: headerText,
depth: headingRank(node)!,
slug: node.properties["id"] as string,
};
if (file.data.astro.frontmatter.headingsWithId) {
file.data.astro.frontmatter.headingsWithId.push(headingWithID);
} else {
file.data.astro.frontmatter.headingsWithId = [headingWithID];
}
}
});
};
};

View File

@@ -1,37 +1,19 @@
import { Root } from "hast";
import { Root, Element } from "hast";
import { Plugin } from "unified";
import { visit } from "unist-util-visit";
import { EMBED_SIZE } from "./constants";
import { getFullRelativePath, isRelativePath } from "../url-paths";
import { fromHtml } from "hast-util-from-html";
import path from "path";
interface RehypeUnicornElementMapProps {}
function escapeHTML(s) {
if (!s) return s;
return s
.replace(/&/g, "&amp;")
.replace(/"/g, "&quot;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
// TODO: Add switch/case and dedicated files ala "Components"
export const rehypeUnicornElementMap: Plugin<
[RehypeUnicornElementMapProps | never],
Root
> = () => {
export const rehypeUnicornElementMap: Plugin<[], Root> = () => {
return async (tree, file) => {
const splitFilePath = path.dirname(file.path).split(path.sep);
// "collections" | "blog"
const parentFolder = splitFilePath.at(-2);
const slug = splitFilePath.at(-1);
visit(tree, (node: any) => {
visit(tree, (node: Element) => {
if (node.tagName === "video") {
node.properties.muted ??= true;
node.properties.autoPlay ??= true;
@@ -43,13 +25,13 @@ export const rehypeUnicornElementMap: Plugin<
"/content/",
parentFolder,
slug,
node.properties.src
node.properties.src.toString()
);
}
if (node.tagName === "a") {
const href = node.properties.href;
const isInternalLink = isRelativePath(href || "");
const isInternalLink = isRelativePath(href?.toString() || "");
if (!isInternalLink) {
node.properties.target = "_blank";
node.properties.rel = "nofollow noopener noreferrer";

View File

@@ -1 +0,0 @@
export * from "./rehype-transform";

View File

@@ -60,11 +60,6 @@ const getApproxLineCount = (nodes: Node[], inParagraph?: boolean): number => {
return lines;
};
export interface RehypeTabsProps {
injectSubheaderProps?: boolean;
tabSlugifyProps?: Parameters<typeof getHeaderNodeId>[1];
}
/**
* Plugin to add Docsify's tab support.
* @see https://jhildenbiddle.github.io/docsify-tabs/
@@ -82,10 +77,7 @@ export interface RehypeTabsProps {
* To align with React Tabs package:
* @see https://github.com/reactjs/react-tabs
*/
export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
injectSubheaderProps = false,
tabSlugifyProps = {},
}) => {
export const rehypeTabs: Plugin<[], Root> = () => {
return (tree) => {
const replaceTabNodes = (nodes: Node[]) => {
let sectionStarted = false;
@@ -102,10 +94,9 @@ export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
if (isNodeLargestHeading(localNode, largestSize)) {
// Make sure that all tabs labeled "thing" aren't also labeled "thing2"
slugs.reset();
const { id: headerSlug } = getHeaderNodeId(
localNode,
tabSlugifyProps
);
const { id: headerSlug } = getHeaderNodeId(localNode, {
enableCustomId: true,
});
tabs.push({
slug: headerSlug,
@@ -118,7 +109,7 @@ export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
}
// For any other heading found in the tab contents, append to the nested headers array
if (isNodeHeading(localNode) && injectSubheaderProps) {
if (isNodeHeading(localNode)) {
const lastTab = tabs.at(-1);
// Store the related tab ID in the attributes of the header