mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-09 04:22:01 +00:00
Reuse existing file tree rehype plugin from Astro docs
This commit is contained in:
@@ -32,6 +32,7 @@ import mdx from "@astrojs/mdx";
|
||||
import symlink from "symlink-dir";
|
||||
import * as path from "path";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import { rehypeFileTree } from "utils/markdown/file-tree/rehype-file-tree";
|
||||
|
||||
await symlink(path.resolve("content"), path.resolve("public/content"));
|
||||
|
||||
@@ -108,6 +109,7 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
],
|
||||
rehypeFileTree,
|
||||
rehypeHeaderText,
|
||||
/**
|
||||
* Insert custom HTML generation code here
|
||||
|
||||
@@ -71,6 +71,53 @@ We now have a basic demo application that we can extend by adding it to our mono
|
||||
|
||||
# Maintain Multiple Package Roots with Yarn Berry {#yarn-berry}
|
||||
|
||||
While the `react-native init` command is great for single apps, it doesn't do much to help us scaffold our monorepo.
|
||||
|
||||
Currently, with the newly created React Native project, our filesystem looks something like this:
|
||||
|
||||
<!-- filetree:start -->
|
||||
- `/`
|
||||
- `App.tsx`
|
||||
- `android/`
|
||||
- `app.json`
|
||||
- `babel.config.js`
|
||||
- `index.js`
|
||||
- `ios/`
|
||||
- `metro.config.js`
|
||||
- `node_modules`
|
||||
- `package.json`
|
||||
- `tsconfig.json`
|
||||
- `yarn.lock`
|
||||
<!-- filetree:end -->
|
||||
|
||||
In a monorepo, however, we might have multiple apps and packages that we want to keep in the same repository. To do this, our filesystem should look something akin to this structure:
|
||||
|
||||
<!-- filetree:start -->
|
||||
- `/`
|
||||
- `apps/`
|
||||
- `chat-app-mobile/`
|
||||
- `src`
|
||||
- `App.tsx`
|
||||
- `components/`
|
||||
- `hooks/`
|
||||
- `utils/`
|
||||
- `types/`
|
||||
- `android/`
|
||||
- `app.json`
|
||||
- `babel.config.js`
|
||||
- `index.js`
|
||||
- `ios/`
|
||||
- `metro.config.js`
|
||||
- `node_modules`
|
||||
- `package.json`
|
||||
- `tsconfig.json`
|
||||
- `yarn.lock`
|
||||
<!-- filetree:end -->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
https://twitter.com/larixer/status/1570459837498290178
|
||||
|
||||
|
||||
|
||||
756
src/utils/markdown/file-tree/file-tree-icons.ts
Normal file
756
src/utils/markdown/file-tree/file-tree-icons.ts
Normal file
File diff suppressed because one or more lines are too long
161
src/utils/markdown/file-tree/rehype-file-tree.ts
Normal file
161
src/utils/markdown/file-tree/rehype-file-tree.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* This was taken from Astro docs:
|
||||
* https://github.com/withastro/docs/blob/83e4e7946933b468f857c76f8d4f9861e37d7059/src/components/internal/rehype-file-tree.ts
|
||||
*
|
||||
* Then modified to work with HTML comments :)
|
||||
*/
|
||||
import { fromHtml } from "hast-util-from-html";
|
||||
import { toString } from "hast-util-to-string";
|
||||
import { h } from "hastscript";
|
||||
import type { Element, HChild } from "hastscript/lib/core";
|
||||
import { CONTINUE, SKIP, visit } from "unist-util-visit";
|
||||
import { getIcon } from "./file-tree-icons";
|
||||
import replaceAllBetween from "unist-util-replace-all-between";
|
||||
import { Node } from "unist";
|
||||
import { Root } from "hast";
|
||||
|
||||
/** Make a text node with the pass string as its contents. */
|
||||
const Text = (value = ""): { type: "text"; value: string } => ({
|
||||
type: "text",
|
||||
value,
|
||||
});
|
||||
|
||||
/** Convert an HTML string containing an SVG into a HAST element node. */
|
||||
const makeSVGIcon = (svgString: string) => {
|
||||
const root = fromHtml(svgString, { fragment: true });
|
||||
const svg = root.children[0] as Element;
|
||||
svg.properties = {
|
||||
...svg.properties,
|
||||
width: 16,
|
||||
height: 16,
|
||||
class: "tree-icon",
|
||||
"aria-hidden": "true",
|
||||
};
|
||||
return svg;
|
||||
};
|
||||
|
||||
const FileIcon = (filename: string) => {
|
||||
const { svg } = getIcon(filename);
|
||||
return makeSVGIcon(svg);
|
||||
};
|
||||
|
||||
const FolderIcon = makeSVGIcon(
|
||||
'<svg viewBox="-5 -5 26 26"><path d="M1.8 1A1.8 1.8 0 0 0 0 2.8v10.4c0 1 .8 1.8 1.8 1.8h12.4a1.8 1.8 0 0 0 1.8-1.8V4.8A1.8 1.8 0 0 0 14.2 3H7.5a.3.3 0 0 1-.2-.1l-.9-1.2A2 2 0 0 0 5 1H1.7z"/></svg>'
|
||||
);
|
||||
|
||||
export const rehypeFileTree = () => {
|
||||
return (tree) => {
|
||||
function replaceFiletreeNodes(nodes: Node[]) {
|
||||
const root = { type: "root", children: nodes } as Root;
|
||||
visit(root, "element", (node) => {
|
||||
// Strip nodes that only contain newlines
|
||||
node.children = node.children.filter(
|
||||
(child) =>
|
||||
child.type === "comment" ||
|
||||
child.type !== "text" ||
|
||||
!/^\n+$/.test(child.value)
|
||||
);
|
||||
|
||||
if (node.tagName !== "li") return CONTINUE;
|
||||
|
||||
// Ensure node has properties so we can assign classes later.
|
||||
if (!node.properties) node.properties = {};
|
||||
|
||||
const [firstChild, ...otherChildren] = node.children;
|
||||
|
||||
const comment: HChild[] = [];
|
||||
if (firstChild.type === "text") {
|
||||
const [filename, ...fragments] = firstChild.value.split(" ");
|
||||
firstChild.value = filename;
|
||||
comment.push(fragments.join(" "));
|
||||
}
|
||||
const subTreeIndex = otherChildren.findIndex(
|
||||
(child) => child.type === "element" && child.tagName === "ul"
|
||||
);
|
||||
const commentNodes =
|
||||
subTreeIndex > -1
|
||||
? otherChildren.slice(0, subTreeIndex)
|
||||
: [...otherChildren];
|
||||
otherChildren.splice(
|
||||
0,
|
||||
subTreeIndex > -1 ? subTreeIndex : otherChildren.length
|
||||
);
|
||||
comment.push(...commentNodes);
|
||||
|
||||
const firstChildTextContent = toString(firstChild);
|
||||
|
||||
// Decide a node is a directory if it ends in a `/` or contains another list.
|
||||
const isDirectory =
|
||||
/\/\s*$/.test(firstChildTextContent) ||
|
||||
otherChildren.some(
|
||||
(child) => child.type === "element" && child.tagName === "ul"
|
||||
);
|
||||
const isPlaceholder = /^\s*(\.{3}|…)\s*$/.test(firstChildTextContent);
|
||||
const isHighlighted =
|
||||
firstChild.type === "element" && firstChild.tagName === "strong";
|
||||
const hasContents = otherChildren.length > 0;
|
||||
|
||||
const fileExtension = isDirectory
|
||||
? "dir"
|
||||
: firstChildTextContent.trim().split(".").pop() || "";
|
||||
|
||||
const icon = h(
|
||||
"span",
|
||||
isDirectory ? FolderIcon : FileIcon(firstChildTextContent)
|
||||
);
|
||||
if (!icon.properties) icon.properties = {};
|
||||
if (isDirectory) {
|
||||
icon.properties["aria-label"] = "Directory";
|
||||
}
|
||||
|
||||
node.properties.class = isDirectory ? "directory" : "file";
|
||||
if (isPlaceholder) node.properties.class += " empty";
|
||||
node.properties["data-filetype"] = fileExtension;
|
||||
|
||||
const treeEntry = h(
|
||||
"span",
|
||||
{ class: "tree-entry" },
|
||||
h("span", { class: isHighlighted ? "highlight" : "" }, [
|
||||
isPlaceholder ? null : icon,
|
||||
firstChild,
|
||||
]),
|
||||
Text(comment.length > 0 ? " " : ""),
|
||||
comment.length > 0
|
||||
? h("span", { class: "comment" }, ...comment)
|
||||
: Text()
|
||||
);
|
||||
|
||||
if (isDirectory) {
|
||||
node.children = [
|
||||
h("details", { open: hasContents }, [
|
||||
h("summary", treeEntry),
|
||||
...(hasContents ? otherChildren : [h("ul", h("li", "…"))]),
|
||||
]),
|
||||
];
|
||||
// Continue down the tree.
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
node.children = [treeEntry, ...otherChildren];
|
||||
|
||||
// Files can’t contain further files or directories, so skip iterating children.
|
||||
return SKIP;
|
||||
});
|
||||
|
||||
return root.children;
|
||||
}
|
||||
|
||||
replaceAllBetween(
|
||||
tree,
|
||||
{ type: "raw", value: "<!-- filetree:start -->" } as never,
|
||||
{ type: "raw", value: "<!-- filetree:end -->" } as never,
|
||||
replaceFiletreeNodes
|
||||
);
|
||||
replaceAllBetween(
|
||||
tree,
|
||||
{ type: "comment", value: " filetree:start " } as never,
|
||||
{ type: "comment", value: " filetree:end " } as never,
|
||||
replaceFiletreeNodes
|
||||
);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user