From 44b7bfee1637738954b3b7c0f13e07b15bcc8ea3 Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Fri, 11 Jul 2025 15:31:07 -0700 Subject: [PATCH] fix(Docs): fix doc rendering --- admin/Dockerfile | 1 + admin/app/controllers/docs_controller.ts | 6 +- admin/app/services/docs_service.ts | 60 ++++++++++++++---- admin/config/drive.ts | 5 ++ admin/docs/home.md | 72 +++++++++++++++++++++- admin/inertia/app/app.tsx | 2 + admin/inertia/components/StyledSidebar.tsx | 6 +- admin/inertia/layouts/DocsLayout.tsx | 40 +++++------- admin/inertia/lib/api.ts | 4 +- admin/inertia/pages/docs/show.tsx | 5 +- admin/package-lock.json | 42 ++++++++++--- admin/package.json | 1 + admin/start/kernel.ts | 4 +- admin/util/docs.ts | 9 +++ 14 files changed, 199 insertions(+), 58 deletions(-) create mode 100644 admin/util/docs.ts diff --git a/admin/Dockerfile b/admin/Dockerfile index 9d45def..c992904 100644 --- a/admin/Dockerfile +++ b/admin/Dockerfile @@ -28,5 +28,6 @@ ENV NODE_ENV=production WORKDIR /app COPY --from=production-deps /app/node_modules /app/node_modules COPY --from=build /app/build /app +COPY ./docs /app/docs EXPOSE 8080 CMD ["node", "./bin/server.js"] \ No newline at end of file diff --git a/admin/app/controllers/docs_controller.ts b/admin/app/controllers/docs_controller.ts index ad69e2a..1c31448 100644 --- a/admin/app/controllers/docs_controller.ts +++ b/admin/app/controllers/docs_controller.ts @@ -9,15 +9,13 @@ export default class DocsController { ) { } async list({ }: HttpContext) { - const docs = await this.docsService.getDocs(); - return { articles: docs }; + return await this.docsService.getDocs(); } async show({ params, inertia }: HttpContext) { - const content = await this.docsService.parseFile(`${params.slug}.md`); + const content = await this.docsService.parseFile(params.slug); return inertia.render('docs/show', { content, - title: "Documentation" }); } } \ No newline at end of file diff --git a/admin/app/services/docs_service.ts b/admin/app/services/docs_service.ts index 31bcd89..81692e4 100644 --- a/admin/app/services/docs_service.ts +++ b/admin/app/services/docs_service.ts @@ -1,17 +1,28 @@ +import drive from '@adonisjs/drive/services/main'; import Markdoc from '@markdoc/markdoc'; -import { readdir, readFile } from 'node:fs/promises'; -import { join } from 'node:path'; +import { streamToString } from '../../util/docs.js'; export class DocsService { async getDocs() { - const docsPath = join(process.cwd(), '/docs'); - console.log(`Resolving docs path: ${docsPath}`); + const disk = drive.use('docs'); + if (!disk) { + throw new Error('Docs disk not configured'); + } - const files = await readdir(docsPath, { withFileTypes: true }); - const docs = files - .filter(file => file.isFile() && file.name.endsWith('.md')) - .map(file => file.name); - return docs; + const contents = await disk.listAll('/'); + const files: Array<{ title: string; slug: string }> = []; + + for (const item of contents.objects) { + if (item.isFile && item.name.endsWith('.md')) { + const cleaned = this.prettify(item.name); + files.push({ + title: cleaned, + slug: item.name.replace(/\.md$/, '') + }); + } + } + + return files.sort((a, b) => a.title.localeCompare(b.title)); } parse(content: string) { @@ -26,13 +37,36 @@ export class DocsService { return Markdoc.transform(ast, config); } - async parseFile(filename: string) { - const fullPath = join(process.cwd(), '/docs', filename); - console.log(`Resolving file path: ${fullPath}`); - const content = await readFile(fullPath, 'utf-8') + async parseFile(_filename: string) { + const disk = drive.use('docs'); + if (!disk) { + throw new Error('Docs disk not configured'); + } + + if (!_filename) { + throw new Error('Filename is required'); + } + + const filename = _filename.endsWith('.md') ? _filename : `${_filename}.md`; + + const fileExists = await disk.exists(filename); + if (!fileExists) { + throw new Error(`File not found: ${filename}`); + } + + const fileStream = await disk.getStream(filename); + if (!fileStream) { + throw new Error(`Failed to read file stream: ${filename}`); + } + const content = await streamToString(fileStream); return this.parse(content); } + private prettify(filename: string) { + const cleaned = filename.replace(/_/g, ' ').replace(/\.md$/, ''); + return cleaned.charAt(0).toUpperCase() + cleaned.slice(1); + } + private getConfig() { return { tags: { diff --git a/admin/config/drive.ts b/admin/config/drive.ts index c0b97ad..72aa24e 100644 --- a/admin/config/drive.ts +++ b/admin/config/drive.ts @@ -16,6 +16,11 @@ const driveConfig = defineConfig({ routeBasePath: '/storage', visibility: 'public', }), + docs: services.fs({ + location: app.makePath('docs'), + serveFiles: false, // Don't serve files directly - we handle this via routes/Inertia + visibility: 'public', + }), }, }) diff --git a/admin/docs/home.md b/admin/docs/home.md index 829b744..1516eac 100644 --- a/admin/docs/home.md +++ b/admin/docs/home.md @@ -1 +1,71 @@ -# This is a markdown file! \ No newline at end of file +# Lorem Ipsum Markdown Showcase + +--- + +## Introduction + +This document serves as a comprehensive example of **Markdown's various formatting possibilities**, using the classic *Lorem Ipsum* text as its content. From basic text styling to lists, code blocks, and tables, you'll find a demonstration of common Markdown features here. + +--- + +## Basic Text Formatting + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. + +* This text is **bold**. +* This text is *italic*. +* This text is ***bold and italic***. +* This text is ~~struck through~~. +* You can also use `backticks` for `inline code`. + +--- + +## Headers + +Markdown supports up to six levels of headers. + +# Header 1 +## Header 2 +### Header 3 +#### Header 4 +##### Header 5 +###### Header 6 + +--- + +## Lists + +### Unordered List + +* Lorem ipsum dolor sit amet. + * Consectetur adipiscing elit. + * Sed do eiusmod tempor. +* Incididunt ut labore et dolore magna. +* Aliqua ut enim ad minim veniam. + +### Ordered List + +1. Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +2. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. +3. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + +--- + +## Blockquotes + +> "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." +> +> — John Doe, *Lorem Ipsum Anthology* + +--- + +## Code Blocks + +```python +def fibonacci(n): + a, b = 0, 1 + for i in range(n): + print(a, end=" ") + a, b = b, a + b + +fibonacci(10) \ No newline at end of file diff --git a/admin/inertia/app/app.tsx b/admin/inertia/app/app.tsx index 9f93e73..1ca0ce4 100644 --- a/admin/inertia/app/app.tsx +++ b/admin/inertia/app/app.tsx @@ -9,6 +9,7 @@ import ModalsProvider from '~/providers/ModalProvider' import { TransmitProvider } from 'react-adonis-transmit' import { generateUUID } from '~/lib/util' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' const appName = import.meta.env.VITE_APP_NAME || 'Project N.O.M.A.D.' const queryClient = new QueryClient() @@ -36,6 +37,7 @@ createInertiaApp({ + diff --git a/admin/inertia/components/StyledSidebar.tsx b/admin/inertia/components/StyledSidebar.tsx index 8f1e097..75d434a 100644 --- a/admin/inertia/components/StyledSidebar.tsx +++ b/admin/inertia/components/StyledSidebar.tsx @@ -9,7 +9,7 @@ interface StyledSidebarProps { items: Array<{ name: string href: string - icon: React.ElementType + icon?: React.ElementType current: boolean }> } @@ -25,7 +25,7 @@ const StyledSidebar: React.FC = ({ title, items }) => { const ListItem = (item: { name: string href: string - icon: React.ElementType + icon?: React.ElementType current: boolean }) => { return ( @@ -39,7 +39,7 @@ const StyledSidebar: React.FC = ({ title, items }) => { 'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold' )} > -