mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-09 12:57:45 +00:00
move rehype config out of astro.config.ts
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
49
src/utils/markdown/index.ts
Normal file
49
src/utils/markdown/index.ts
Normal 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,
|
||||
];
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -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, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
}
|
||||
|
||||
// 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";
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from "./rehype-transform";
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user