Skip to content

Commit 98b98fe

Browse files
committed
WIP: docs page
1 parent 03c31dd commit 98b98fe

File tree

9 files changed

+1010
-66
lines changed

9 files changed

+1010
-66
lines changed

apps/docs/next.config.mjs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
import createMDXPlugin from "@next/mdx"
2-
import { rehypePlugins, remarkPlugins } from "renoun/mdx"
2+
import rehypeRenoun from "@renoun/mdx/rehype"
3+
import remarkRenounAddHeadings from "@renoun/mdx/remark/add-headings"
4+
import remarkRenounRemoveParagraphs from "@renoun/mdx/remark/remove-immediate-paragraphs"
5+
import remarkRenounRelativeLinks from "@renoun/mdx/remark/transform-relative-links"
6+
import rehypeMdxImportMedia from "rehype-mdx-import-media"
7+
import remarkFrontmatter from "remark-frontmatter"
8+
import remarkGfm from "remark-gfm"
9+
import remarkMdxFrontmatter from "remark-mdx-frontmatter"
10+
import remarkSqueezeParagraphs from "remark-squeeze-paragraphs"
11+
import remarkStripBadges from "remark-strip-badges"
312

413
const withMDX = createMDXPlugin({
5-
extension: /\.mdx?$/,
614
options: {
7-
remarkPlugins,
8-
rehypePlugins,
15+
remarkPlugins: [
16+
remarkRenounAddHeadings,
17+
remarkFrontmatter,
18+
remarkMdxFrontmatter,
19+
remarkSqueezeParagraphs,
20+
remarkRenounRemoveParagraphs,
21+
remarkStripBadges,
22+
remarkRenounRelativeLinks,
23+
remarkGfm,
24+
],
25+
rehypePlugins: [rehypeRenoun, rehypeMdxImportMedia],
926
},
1027
})
1128

apps/docs/package.json

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,55 @@
44
"private": true,
55
"type": "module",
66
"scripts": {
7-
"build": "renoun next build",
7+
"build": "renoun next build && pnpm generate-pagefind",
88
"clean": "git clean -xdf .cache .turbo dist node_modules .next",
99
"clean:cache": "git clean -xdf .cache",
1010
"dev": "renoun next dev",
1111
"format": "prettier --check . --ignore-path ../../.gitignore",
12+
"generate-pagefind": "pagefind --site out --output-path out/pagefind",
1213
"lint": "eslint .",
14+
"lint:links": "node --import ./src/register.js --import tsx/esm src/link-check.ts",
1315
"start": "next start",
1416
"typecheck": "tsc --noEmit"
1517
},
1618
"prettier": "@vorsteh-queue/prettier-config",
1719
"dependencies": {
18-
"@next/mdx": "15.4.1",
20+
"@getcanary/web": "1.0.12",
21+
"@giscus/react": "3.1.0",
22+
"@mdx-js/loader": "3.1.0",
23+
"@mdx-js/node-loader": "3.1.0",
24+
"@mdx-js/react": "3.1.0",
25+
"@next/mdx": "15.4.2",
1926
"class-variance-authority": "0.7.1",
2027
"clsx": "2.1.1",
2128
"cmdk": "1.1.1",
2229
"date-fns": "4.1.0",
2330
"lucide-react": "0.525.0",
24-
"next": "15.4.1",
31+
"next": "15.4.2",
2532
"next-themes": "latest",
2633
"react": "19.1.0",
2734
"react-dom": "19.1.0",
35+
"rehype-mdx-import-media": "1.2.0",
36+
"remark-frontmatter": "5.0.0",
37+
"remark-gfm": "4.0.1",
38+
"remark-mdx-frontmatter": "5.2.0",
39+
"remark-squeeze-paragraphs": "6.0.0",
40+
"remark-strip-badges": "7.0.0",
2841
"renoun": "8.14.0",
29-
"ts-morph": "26.0.0"
42+
"ts-morph": "26.0.0",
43+
"zod": "4.0.5"
3044
},
3145
"devDependencies": {
3246
"@tailwindcss/postcss": "4.1.11",
33-
"@types/node": "22.16.4",
47+
"@types/node": "22.16.5",
3448
"@types/react": "19.1.8",
3549
"@types/react-dom": "19.1.6",
3650
"@vorsteh-queue/eslint-config": "workspace:*",
3751
"@vorsteh-queue/prettier-config": "workspace:*",
3852
"@vorsteh-queue/tsconfig": "workspace:*",
3953
"eslint": "9.31.0",
54+
"next-validate-link": "1.5.2",
55+
"pagefind": "1.3.0",
4056
"postcss": "8.5.6",
4157
"prettier": "3.6.2",
4258
"tailwind-merge": "3.3.1",

apps/docs/renoun.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"$schema": "https://renoun.dev/schema.json",
3+
"git": {
4+
"source": "https://github.com/noxify/vorsteh-queue",
5+
"branch": "main",
6+
"provider": "github"
7+
},
8+
"siteUrl": "https://vorsteh-queue.dev",
9+
10+
"theme": {
11+
"dark": "github-dark-default",
12+
"light": "github-light-default"
13+
},
14+
"languages": [
15+
"css",
16+
"javascript",
17+
"jsx",
18+
"typescript",
19+
"tsx",
20+
"markdown",
21+
"mdx",
22+
"shellscript",
23+
"json",
24+
"html",
25+
"python",
26+
"graphql",
27+
"yaml",
28+
"sql",
29+
"xml",
30+
"docker"
31+
]
32+
}

apps/docs/src/collections.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import type { z } from "zod"
2+
import { EntryGroup, isDirectory, isFile } from "renoun/file-system"
3+
4+
import type { frontmatterSchema } from "./validations"
5+
import { removeFromArray } from "./lib/utils"
6+
import { generateDirectories } from "./sources"
7+
8+
export const DocumentationGroup = new EntryGroup({
9+
entries: [...generateDirectories()],
10+
})
11+
12+
export type EntryType = Awaited<ReturnType<typeof DocumentationGroup.getEntry>>
13+
export type DirectoryType = Awaited<ReturnType<typeof DocumentationGroup.getDirectory>>
14+
15+
/**
16+
* Helper function to get the title for an element in the sidebar/navigation
17+
* @param collection {EntryType} the collection to get the title for
18+
* @param frontmatter {z.infer<typeof frontmatterSchema>} the frontmatter to get the title from
19+
* @param includeTitle? {boolean} whether to include the title in the returned string
20+
* @returns {string} the title to be displayed in the sidebar/navigation
21+
*/
22+
export function getTitle(
23+
collection: EntryType,
24+
frontmatter: z.infer<typeof frontmatterSchema>,
25+
includeTitle = false,
26+
): string {
27+
return includeTitle
28+
? (frontmatter.navTitle ?? frontmatter.title ?? collection.getTitle())
29+
: (frontmatter.navTitle ?? collection.getTitle())
30+
}
31+
32+
/**
33+
* Helper function to get the file content for a given source entry
34+
* This function will try to get the file based on the given path and the "mdx" extension
35+
* If the file is not found, it will try to get the index file based on the given path and the "mdx" extension
36+
* If there is also no index file, it will return null
37+
*
38+
* @param source {EntryType} the source entry to get the file content for
39+
*/
40+
export const getFileContent = async (source: EntryType) => {
41+
// first, try to get the file based on the given path
42+
43+
return await DocumentationGroup.getFile(source.getPathSegments(), "mdx").catch(async () => {
44+
return await DocumentationGroup.getFile([...source.getPathSegments(), "index"], "mdx").catch(
45+
() => null,
46+
)
47+
})
48+
}
49+
50+
/**
51+
* Helper function to get the sections for a given source entry
52+
* This function will try to get the sections based on the given path
53+
*
54+
* If there there are no entries/children for the current path, it will return an empty array
55+
*
56+
* @param source {EntryType} the source entry to get the sections for
57+
* @returns
58+
*/
59+
export async function getSections(source: EntryType) {
60+
if (source.getDepth() > -1) {
61+
if (isDirectory(source)) {
62+
return (
63+
await (await DocumentationGroup.getDirectory(source.getPathSegments())).getEntries()
64+
).filter((ele) => ele.getPath() !== source.getPath())
65+
}
66+
67+
if (isFile(source) && source.getBaseName() === "index") {
68+
return await source.getParent().getEntries()
69+
}
70+
return []
71+
} else {
72+
return (
73+
await (await DocumentationGroup.getDirectory(source.getPathSegments())).getEntries()
74+
).filter((ele) => ele.getPath() !== source.getPath())
75+
}
76+
}
77+
78+
/**
79+
* Helper function to get the breadcrumb items for a given slug
80+
*
81+
* @param slug {string[]} the slug to get the breadcrumb items for
82+
*/
83+
export const getBreadcrumbItems = async (slug: string[]) => {
84+
// we do not want to have "index" as breadcrumb element
85+
const cleanedSlug = removeFromArray(slug, ["index"])
86+
87+
const combinations = cleanedSlug.map((_, index) => cleanedSlug.slice(0, index + 1))
88+
89+
const items = []
90+
91+
for (const currentPageSegement of combinations) {
92+
let collection: EntryType
93+
let file: Awaited<ReturnType<typeof getFileContent>>
94+
let frontmatter: z.infer<typeof frontmatterSchema> | undefined
95+
try {
96+
collection = await DocumentationGroup.getEntry(currentPageSegement)
97+
if (collection.getPathSegments().includes("index")) {
98+
file = await getFileContent(collection.getParent())
99+
} else {
100+
file = await getFileContent(collection)
101+
}
102+
103+
frontmatter = await file?.getExportValue("frontmatter")
104+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
105+
} catch (e: unknown) {
106+
continue
107+
}
108+
109+
if (!frontmatter) {
110+
items.push({
111+
title: collection.getTitle(),
112+
path: ["docs", ...collection.getPathSegments()],
113+
})
114+
} else {
115+
const title = getTitle(collection, frontmatter, true)
116+
items.push({
117+
title,
118+
path: ["docs", ...removeFromArray(collection.getPathSegments(), ["index"])],
119+
})
120+
}
121+
}
122+
123+
return items
124+
}
125+
126+
/**
127+
* Checks if an entry is hidden (starts with an underscore)
128+
*
129+
* @param entry {EntryType} the entry to check for visibility
130+
*/
131+
export function isHidden(entry: EntryType) {
132+
return entry.getBaseName().startsWith("_")
133+
}

apps/docs/src/lib/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,17 @@ import { twMerge } from "tailwind-merge"
55
export function cn(...inputs: ClassValue[]) {
66
return twMerge(clsx(inputs))
77
}
8+
9+
/** Create a slug from a string. */
10+
// source: https://github.com/souporserious/renoun/blob/main/packages/renoun/src/utils/create-slug.ts
11+
export function createSlug(input: string) {
12+
return input
13+
.replace(/([a-z])([A-Z])/g, "$1-$2") // Add a hyphen between lower and upper case letters
14+
.replace(/([A-Z])([A-Z][a-z])/g, "$1-$2") // Add a hyphen between consecutive upper case letters followed by a lower case letter
15+
.replace(/[_\s]+/g, "-") // Replace underscores and spaces with a hyphen
16+
.toLowerCase() // Convert the entire string to lowercase
17+
}
18+
19+
export function removeFromArray<T>(array: T[], valueToRemove: T[]): T[] {
20+
return array.filter((value) => !valueToRemove.includes(value))
21+
}

0 commit comments

Comments
 (0)