diff --git a/astro.config.ts b/astro.config.ts index 8023ecdc..c88a7435 100644 --- a/astro.config.ts +++ b/astro.config.ts @@ -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 diff --git a/content/blog/setup-a-react-native-monorepo/index.md b/content/blog/setup-a-react-native-monorepo/index.md index ae5861ef..aaef5c47 100644 --- a/content/blog/setup-a-react-native-monorepo/index.md +++ b/content/blog/setup-a-react-native-monorepo/index.md @@ -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: + + +- `/` + - `App.tsx` + - `android/` + - `app.json` + - `babel.config.js` + - `index.js` + - `ios/` + - `metro.config.js` + - `node_modules` + - `package.json` + - `tsconfig.json` + - `yarn.lock` + + +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: + + +- `/` + - `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` + + + + + + https://twitter.com/larixer/status/1570459837498290178 diff --git a/src/utils/markdown/file-tree/file-tree-icons.ts b/src/utils/markdown/file-tree/file-tree-icons.ts new file mode 100644 index 00000000..8dbdaf5f --- /dev/null +++ b/src/utils/markdown/file-tree/file-tree-icons.ts @@ -0,0 +1,756 @@ +/** + * Based on https://github.com/elviswolcott/seti-icons which + * is derived from https://github.com/jesseweed/seti-ui/ + * + * Copyright (c) 2014 Jesse Weed + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const rawDefinitions = { + files: { + COMMIT_EDITMSG: ["git", "ignore"], + MERGE_MSG: ["git", "ignore"], + "karma.conf.js": ["karma", "green"], + "karma.conf.coffee": ["karma", "green"], + "README.md": ["info", "blue"], + "README.txt": ["info", "blue"], + README: ["info", "blue"], + "CHANGELOG.md": ["clock", "blue"], + "CHANGELOG.txt": ["clock", "blue"], + CHANGELOG: ["clock", "blue"], + "CHANGES.md": ["clock", "blue"], + "CHANGES.txt": ["clock", "blue"], + CHANGES: ["clock", "blue"], + "VERSION.md": ["clock", "blue"], + "VERSION.txt": ["clock", "blue"], + VERSION: ["clock", "blue"], + mvnw: ["maven", "red"], + "tsconfig.json": ["tsconfig", "blue"], + "swagger.json": ["json", "green"], + "swagger.yml": ["json", "green"], + "swagger.yaml": ["json", "green"], + "mime.types": ["config", "grey-light"], + Jenkinsfile: ["jenkins", "red"], + "babel.config.js": ["babel", "yellow"], + "babel.config.json": ["babel", "yellow"], + "babel.config.cjs": ["babel", "yellow"], + BUILD: ["bazel", "green"], + "BUILD.bazel": ["bazel", "green"], + WORKSPACE: ["bazel", "green"], + "WORKSPACE.bazel": ["bazel", "green"], + "bower.json": ["bower", "orange"], + "Bower.json": ["bower", "orange"], + "firebase.json": ["firebase", "orange"], + geckodriver: ["firefox", "orange"], + "Gruntfile.js": ["grunt", "orange"], + "gruntfile.babel.js": ["grunt", "orange"], + "Gruntfile.babel.js": ["grunt", "orange"], + "gruntfile.js": ["grunt", "orange"], + "Gruntfile.coffee": ["grunt", "orange"], + "gruntfile.coffee": ["grunt", "orange"], + "ionic.config.json": ["ionic", "blue"], + "Ionic.config.json": ["ionic", "blue"], + "ionic.project": ["ionic", "blue"], + "Ionic.project": ["ionic", "blue"], + "platformio.ini": ["platformio", "orange"], + "rollup.config.js": ["rollup", "red"], + "sass-lint.yml": ["sass", "pink"], + "stylelint.config.js": ["stylelint", "white"], + "stylelint.config.cjs": ["stylelint", "white"], + "yarn.clean": ["yarn", "blue"], + "yarn.lock": ["yarn", "blue"], + "webpack.config.js": ["webpack", "blue"], + "webpack.config.cjs": ["webpack", "blue"], + "webpack.config.ts": ["webpack", "blue"], + "webpack.config.build.js": ["webpack", "blue"], + "webpack.config.build.cjs": ["webpack", "blue"], + "webpack.config.build.ts": ["webpack", "blue"], + "webpack.common.js": ["webpack", "blue"], + "webpack.common.cjs": ["webpack", "blue"], + "webpack.common.ts": ["webpack", "blue"], + "webpack.dev.js": ["webpack", "blue"], + "webpack.dev.cjs": ["webpack", "blue"], + "webpack.dev.ts": ["webpack", "blue"], + "webpack.prod.js": ["webpack", "blue"], + "webpack.prod.cjs": ["webpack", "blue"], + "webpack.prod.ts": ["webpack", "blue"], + "npm-debug.log": ["npm_ignored", "ignore"], + }, + extensions: { + ".astro": ["astro", "red"], + ".bsl": ["bsl", "red"], + ".mdo": ["mdo", "red"], + ".cls": ["salesforce", "blue"], + ".apex": ["salesforce", "blue"], + ".asm": ["asm", "red"], + ".s": ["asm", "red"], + ".bicep": ["bicep", "blue"], + ".bzl": ["bazel", "green"], + ".bazel": ["bazel", "green"], + ".BUILD": ["bazel", "green"], + ".WORKSPACE": ["bazel", "green"], + ".bazelignore": ["bazel", "green"], + ".bazelversion": ["bazel", "green"], + ".c": ["c", "blue"], + ".h": ["c", "purple"], + ".m": ["c", "yellow"], + ".cs": ["c-sharp", "blue"], + ".cshtml": ["html", "blue"], + ".aspx": ["html", "blue"], + ".ascx": ["html", "green"], + ".asax": ["html", "yellow"], + ".master": ["html", "yellow"], + ".cc": ["cpp", "blue"], + ".cpp": ["cpp", "blue"], + ".cxx": ["cpp", "blue"], + ".c++": ["cpp", "blue"], + ".hh": ["cpp", "purple"], + ".hpp": ["cpp", "purple"], + ".hxx": ["cpp", "purple"], + ".h++": ["cpp", "purple"], + ".mm": ["cpp", "yellow"], + ".clj": ["clojure", "green"], + ".cljs": ["clojure", "green"], + ".cljc": ["clojure", "green"], + ".edn": ["clojure", "blue"], + ".cfc": ["coldfusion", "blue"], + ".cfm": ["coldfusion", "blue"], + ".coffee": ["coffee", "yellow"], + ".litcoffee": ["coffee", "yellow"], + ".config": ["config", "grey-light"], + ".cfg": ["config", "grey-light"], + ".conf": ["config", "grey-light"], + ".cr": ["crystal", "white"], + ".ecr": ["crystal_embedded", "white"], + ".slang": ["crystal_embedded", "white"], + ".cson": ["json", "yellow"], + ".css": ["css", "blue"], + ".css.map": ["css", "blue"], + ".sss": ["css", "blue"], + ".csv": ["csv", "green"], + ".xls": ["xls", "green"], + ".xlsx": ["xls", "green"], + ".cu": ["cu", "green"], + ".cuh": ["cu", "purple"], + ".hu": ["cu", "purple"], + ".cake": ["cake", "red"], + ".ctp": ["cake_php", "red"], + ".d": ["d", "red"], + ".doc": ["word", "blue"], + ".docx": ["word", "blue"], + ".ejs": ["ejs", "yellow"], + ".ex": ["elixir", "purple"], + ".exs": ["elixir_script", "purple"], + ".elm": ["elm", "blue"], + ".ico": ["favicon", "yellow"], + ".fs": ["f-sharp", "blue"], + ".fsx": ["f-sharp", "blue"], + ".gitignore": ["git", "ignore"], + ".gitconfig": ["git", "ignore"], + ".gitkeep": ["git", "ignore"], + ".gitattributes": ["git", "ignore"], + ".gitmodules": ["git", "ignore"], + ".go": ["go2", "blue"], + ".slide": ["go", "blue"], + ".article": ["go", "blue"], + ".gd": ["godot", "blue"], + ".godot": ["godot", "red"], + ".tres": ["godot", "yellow"], + ".tscn": ["godot", "purple"], + ".gradle": ["gradle", "blue"], + ".groovy": ["grails", "green"], + ".gsp": ["grails", "green"], + ".gql": ["graphql", "pink"], + ".graphql": ["graphql", "pink"], + ".graphqls": ["graphql", "pink"], + ".hack": ["hacklang", "orange"], + ".haml": ["haml", "red"], + ".handlebars": ["mustache", "orange"], + ".hbs": ["mustache", "orange"], + ".hjs": ["mustache", "orange"], + ".hs": ["haskell", "purple"], + ".lhs": ["haskell", "purple"], + ".hx": ["haxe", "orange"], + ".hxs": ["haxe", "yellow"], + ".hxp": ["haxe", "blue"], + ".hxml": ["haxe", "purple"], + ".html": ["html", "orange"], + ".jade": ["jade", "red"], + ".java": ["java", "red"], + ".class": ["java", "blue"], + ".classpath": ["java", "red"], + ".properties": ["java", "red"], + ".js": ["javascript", "yellow"], + ".js.map": ["javascript", "yellow"], + ".spec.js": ["javascript", "orange"], + ".test.js": ["javascript", "orange"], + ".es": ["javascript", "yellow"], + ".es5": ["javascript", "yellow"], + ".es6": ["javascript", "yellow"], + ".es7": ["javascript", "yellow"], + ".cjs": ["javascript", "yellow"], + ".mjs": ["javascript", "yellow"], + ".jinja": ["jinja", "red"], + ".jinja2": ["jinja", "red"], + ".json": ["json", "yellow"], + ".jl": ["julia", "purple"], + ".kt": ["kotlin", "orange"], + ".kts": ["kotlin", "orange"], + ".dart": ["dart", "blue"], + ".less": ["less", "blue"], + ".liquid": ["liquid", "green"], + ".ls": ["livescript", "blue"], + ".lua": ["lua", "blue"], + ".markdown": ["markdown", "blue"], + ".md": ["markdown", "blue"], + ".mdx": ["markdown", "blue"], + ".argdown": ["argdown", "blue"], + ".ad": ["argdown", "blue"], + ".mustache": ["mustache", "orange"], + ".stache": ["mustache", "orange"], + ".nim": ["nim", "yellow"], + ".nims": ["nim", "yellow"], + ".github-issues": ["github", "white"], + ".ipynb": ["notebook", "blue"], + ".njk": ["nunjucks", "green"], + ".nunjucks": ["nunjucks", "green"], + ".nunjs": ["nunjucks", "green"], + ".nunj": ["nunjucks", "green"], + ".njs": ["nunjucks", "green"], + ".nj": ["nunjucks", "green"], + ".npm-debug.log": ["npm", "ignore"], + ".npmignore": ["npm", "red"], + ".npmrc": ["npm", "red"], + ".ml": ["ocaml", "orange"], + ".mli": ["ocaml", "orange"], + ".cmx": ["ocaml", "orange"], + ".cmxa": ["ocaml", "orange"], + ".odata": ["odata", "orange"], + ".pl": ["perl", "blue"], + ".php": ["php", "purple"], + ".php.inc": ["php", "purple"], + ".pipeline": ["pipeline", "orange"], + ".pddl": ["pddl", "purple"], + ".plan": ["plan", "green"], + ".happenings": ["happenings", "blue"], + ".ps1": ["powershell", "blue"], + ".psd1": ["powershell", "blue"], + ".psm1": ["powershell", "blue"], + ".prisma": ["prisma", "blue"], + ".pug": ["pug", "red"], + ".pp": ["puppet", "yellow"], + ".epp": ["puppet", "yellow"], + ".purs": ["purescript", "white"], + ".py": ["python", "blue"], + ".jsx": ["react", "blue"], + ".spec.jsx": ["react", "orange"], + ".test.jsx": ["react", "orange"], + ".cjsx": ["react", "blue"], + ".spec.tsx": ["react", "orange"], + ".test.tsx": ["react", "orange"], + ".re": ["reasonml", "red"], + ".res": ["rescript", "red"], + ".resi": ["rescript", "pink"], + ".R": ["R", "blue"], + ".rmd": ["R", "blue"], + ".rb": ["ruby", "red"], + ".erb": ["html_erb", "red"], + ".erb.html": ["html_erb", "red"], + ".html.erb": ["html_erb", "red"], + ".rs": ["rust", "grey-light"], + ".sass": ["sass", "pink"], + ".scss": ["sass", "pink"], + ".springBeans": ["spring", "green"], + ".slim": ["slim", "orange"], + ".smarty.tpl": ["smarty", "yellow"], + ".tpl": ["smarty", "yellow"], + ".sbt": ["sbt", "blue"], + ".scala": ["scala", "red"], + ".sol": ["ethereum", "blue"], + ".styl": ["stylus", "green"], + ".svelte": ["svelte", "red"], + ".swift": ["swift", "orange"], + ".sql": ["db", "pink"], + ".soql": ["db", "blue"], + ".tf": ["terraform", "purple"], + ".tf.json": ["terraform", "purple"], + ".tfvars": ["terraform", "purple"], + ".tfvars.json": ["terraform", "purple"], + ".tex": ["tex", "blue"], + ".sty": ["tex", "yellow"], + ".dtx": ["tex", "orange"], + ".ins": ["tex", "white"], + ".txt": ["default", "white"], + ".toml": ["config", "grey-light"], + ".twig": ["twig", "green"], + ".ts": ["typescript", "blue"], + ".tsx": ["typescript", "blue"], + ".spec.ts": ["typescript", "orange"], + ".test.ts": ["typescript", "orange"], + ".vala": ["vala", "grey-light"], + ".vapi": ["vala", "grey-light"], + ".component": ["html", "orange"], + ".vue": ["vue", "green"], + ".wasm": ["wasm", "purple"], + ".wat": ["wat", "purple"], + ".xml": ["xml", "orange"], + ".yml": ["yml", "purple"], + ".yaml": ["yml", "purple"], + ".pro": ["prolog", "orange"], + ".zig": ["zig", "orange"], + ".jar": ["zip", "red"], + ".zip": ["zip", "grey-light"], + ".wgt": ["wgt", "blue"], + ".ai": ["illustrator", "yellow"], + ".psd": ["photoshop", "blue"], + ".pdf": ["pdf", "red"], + ".eot": ["font", "red"], + ".ttf": ["font", "red"], + ".woff": ["font", "red"], + ".woff2": ["font", "red"], + ".avif": ["image", "purple"], + ".gif": ["image", "purple"], + ".jpg": ["image", "purple"], + ".jpeg": ["image", "purple"], + ".png": ["image", "purple"], + ".pxm": ["image", "purple"], + ".svg": ["svg", "purple"], + ".svgx": ["image", "purple"], + ".tiff": ["image", "purple"], + ".webp": ["image", "purple"], + ".sublime-project": ["sublime", "orange"], + ".sublime-workspace": ["sublime", "orange"], + ".code-search": ["code-search", "purple"], + ".sh": ["shell", "green"], + ".zsh": ["shell", "green"], + ".fish": ["shell", "green"], + ".zshrc": ["shell", "green"], + ".bashrc": ["shell", "green"], + ".mov": ["video", "pink"], + ".ogv": ["video", "pink"], + ".webm": ["video", "pink"], + ".avi": ["video", "pink"], + ".mpg": ["video", "pink"], + ".mp4": ["video", "pink"], + ".mp3": ["audio", "purple"], + ".ogg": ["audio", "purple"], + ".wav": ["audio", "purple"], + ".flac": ["audio", "purple"], + ".3ds": ["svg", "blue"], + ".3dm": ["svg", "blue"], + ".stl": ["svg", "blue"], + ".obj": ["svg", "blue"], + ".dae": ["svg", "blue"], + ".bat": ["windows", "blue"], + ".cmd": ["windows", "blue"], + ".babelrc": ["babel", "yellow"], + ".babelrc.js": ["babel", "yellow"], + ".babelrc.cjs": ["babel", "yellow"], + ".bazelrc": ["bazel", "grey"], + ".bowerrc": ["bower", "orange"], + ".codeclimate.yml": ["code-climate", "green"], + ".eslintrc": ["eslint", "purple"], + ".eslintrc.js": ["eslint", "purple"], + ".eslintrc.cjs": ["eslint", "purple"], + ".eslintrc.yaml": ["eslint", "purple"], + ".eslintrc.yml": ["eslint", "purple"], + ".eslintrc.json": ["eslint", "purple"], + ".eslintignore": ["eslint", "grey"], + ".firebaserc": ["firebase", "orange"], + ".gitlab-ci.yml": ["gitlab", "orange"], + ".jshintrc": ["javascript", "blue"], + ".jscsrc": ["javascript", "blue"], + ".stylelintrc": ["stylelint", "white"], + ".stylelintrc.json": ["stylelint", "white"], + ".stylelintrc.yaml": ["stylelint", "white"], + ".stylelintrc.yml": ["stylelint", "white"], + ".stylelintrc.js": ["stylelint", "white"], + ".stylelintignore": ["stylelint", "grey"], + ".direnv": ["config", "grey-light"], + ".env": ["config", "grey-light"], + ".static": ["config", "grey-light"], + ".editorconfig": ["config", "grey-light"], + ".slugignore": ["config", "grey-light"], + ".tmp": ["clock", "grey-light"], + ".htaccess": ["config", "grey-light"], + ".key": ["lock", "green"], + ".cert": ["lock", "green"], + ".cer": ["lock", "green"], + ".crt": ["lock", "green"], + ".pem": ["lock", "green"], + ".DS_Store": ["ignored", "ignore"], + }, + partials: [ + ["TODO.md", ["todo", "blue"]], + ["TODO.txt", ["todo", "blue"]], + ["TODO", ["todo", "blue"]], + ["Procfile", ["heroku", "purple"]], + ["cmakelists.txt", ["makefile", "blue"]], + ["CMakeLists.txt", ["makefile", "blue"]], + ["CMAKELISTS.txt", ["makefile", "blue"]], + ["CMAKELISTS.TXT", ["makefile", "blue"]], + ["omakefile", ["makefile", "grey-light"]], + ["OMakefile", ["makefile", "grey-light"]], + ["OMAKEFILE", ["makefile", "grey-light"]], + ["qmakefile", ["makefile", "purple"]], + ["QMakefile", ["makefile", "purple"]], + ["QMAKEFILE", ["makefile", "purple"]], + ["makefile", ["makefile", "orange"]], + ["Makefile", ["makefile", "orange"]], + ["MAKEFILE", ["makefile", "orange"]], + ["CONTRIBUTING.md", ["license", "red"]], + ["CONTRIBUTING.txt", ["license", "red"]], + ["CONTRIBUTING", ["license", "red"]], + ["COMPILING.md", ["license", "orange"]], + ["COMPILING.txt", ["license", "orange"]], + ["COMPILING", ["license", "orange"]], + ["COPYING.md", ["license", "yellow"]], + ["COPYING.txt", ["license", "yellow"]], + ["COPYING", ["license", "yellow"]], + ["LICENCE.md", ["license", "yellow"]], + ["LICENSE.md", ["license", "yellow"]], + ["LICENCE.txt", ["license", "yellow"]], + ["LICENSE.txt", ["license", "yellow"]], + ["LICENCE", ["license", "yellow"]], + ["LICENSE", ["license", "yellow"]], + ["gulpfile.js", ["gulp", "red"]], + ["gulpfile", ["gulp", "red"]], + ["Gulpfile", ["gulp", "red"]], + ["GULPFILE", ["gulp", "red"]], + ["docker-compose.override.yaml", ["docker", "pink"]], + ["docker-compose.override.yml", ["docker", "pink"]], + ["docker-compose.yaml", ["docker", "pink"]], + ["docker-compose.yml", ["docker", "pink"]], + ["docker-healthcheck", ["docker", "green"]], + [".dockerignore", ["docker", "grey"]], + ["DOCKERFILE", ["docker", "blue"]], + ["Dockerfile", ["docker", "blue"]], + ["dockerfile", ["docker", "blue"]], + ["gemfile", ["ruby", "red"]], + ["Gemfile", ["ruby", "red"]], + ["mix", ["hex", "red"]], + ], + default: ["default", "white"], +}; + +const rawIcons = { + astro: + '', + bsl: '', + mdo: '', + salesforce: + '', + asm: '', + bicep: + '', + bazel: + '', + c: '', + "c-sharp": + '', + html: '', + cpp: '', + clojure: + '', + coldfusion: + '', + coffee: + '', + config: + '', + crystal: + '', + crystal_embedded: + '', + json: '', + css: '', + csv: '', + xls: '', + cu: '', + cake: '', + cake_php: + '', + d: '', + word: '', + ejs: '', + elixir: + '', + elixir_script: + '', + hex: '', + elm: '', + favicon: + '', + "f-sharp": + '', + git: '', + go2: '', + go: '', + godot: + '', + gradle: + '', + grails: + '', + graphql: + '', + hacklang: + '', + haml: '', + mustache: + '', + haskell: + '', + haxe: '', + jade: '', + java: '', + javascript: + '', + jinja: + '', + julia: + '', + karma: + '', + kotlin: + '', + dart: '', + less: '', + liquid: + '', + livescript: + '', + lua: '', + markdown: + '', + argdown: + '', + info: '', + clock: + '', + maven: + '', + nim: '', + github: + '', + notebook: + '', + nunjucks: + '', + npm: '', + ocaml: + '', + odata: + '', + perl: '', + php: '', + pipeline: + '', + pddl: '', + plan: '', + happenings: + '', + powershell: + '', + prisma: + '', + pug: '', + puppet: + '', + purescript: + '', + python: + '', + react: + '', + reasonml: + '', + rescript: + '', + R: '', + ruby: '', + html_erb: + '', + rust: '', + sass: '', + spring: + '', + slim: '', + smarty: + '', + sbt: '', + scala: + '', + ethereum: + '', + stylus: + '', + svelte: + '', + swift: + '', + db: '', + terraform: + '', + tex: '', + default: + '', + twig: '', + typescript: + '', + tsconfig: + '', + vala: '', + vue: '', + wasm: '', + wat: '', + xml: '', + yml: '', + prolog: + '', + zig: '', + zip: '', + wgt: '', + illustrator: + '', + photoshop: + '', + pdf: '', + font: '', + image: + '', + svg: '', + sublime: + '', + "code-search": + '', + shell: + '', + video: + '', + audio: + '', + windows: + '', + jenkins: + '', + babel: + '', + bower: + '', + docker: + '', + "code-climate": + '', + eslint: + '', + firebase: + '', + firefox: + '', + gitlab: + '', + grunt: + '', + gulp: '', + ionic: + '', + platformio: + '', + rollup: + '', + stylelint: + '', + yarn: '', + webpack: + '', + lock: '', + license: + '', + makefile: + '', + heroku: + '', + todo: '', + npm_ignored: + '', + ignored: + '', +}; + +type IconDetails = [string, string]; + +interface SetiTheme { + blue: string; + grey: string; + "grey-light": string; + green: string; + orange: string; + pink: string; + purple: string; + red: string; + white: string; + yellow: string; + ignore: string; +} + +type Color = keyof SetiTheme; + +interface Icon { + svg: string; + color: Color; +} + +const definitions = rawDefinitions as unknown as { + default: IconDetails; + extensions: { [extension: string]: IconDetails }; + files: { [file: string]: IconDetails }; + partials: [string, IconDetails][]; +}; +const icons = rawIcons as unknown as { + [icon: string]: string; +}; + +const getDetails = (fileName: string): IconDetails => { + if (definitions.files[fileName]) { + return definitions.files[fileName]; + } + let extension = fileName.slice(fileName.indexOf(".")); + while (extension !== "") { + if (definitions.extensions[extension]) { + return definitions.extensions[extension]; + } + // look for next "." + extension = extension.slice(1); + extension = extension.slice(extension.indexOf(".")); + } + for (const partial of definitions.partials) { + if (fileName.indexOf(partial[0]) > -1) { + return partial[1]; + } + } + return definitions.default; +}; + +export const getIcon = (fileName: string): Icon => { + const [icon, color] = getDetails(fileName); + return { svg: icons[icon], color } as Icon; +}; diff --git a/src/utils/markdown/file-tree/rehype-file-tree.ts b/src/utils/markdown/file-tree/rehype-file-tree.ts new file mode 100644 index 00000000..74e5caa3 --- /dev/null +++ b/src/utils/markdown/file-tree/rehype-file-tree.ts @@ -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( + '' +); + +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: "" } as never, + { type: "raw", value: "" } as never, + replaceFiletreeNodes + ); + replaceAllBetween( + tree, + { type: "comment", value: " filetree:start " } as never, + { type: "comment", value: " filetree:end " } as never, + replaceFiletreeNodes + ); + }; +};