mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-03-28 03:29:25 +01:00
fix(Docs): fix doc rendering
This commit is contained in:
parent
97655ef75d
commit
44b7bfee16
|
|
@ -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"]
|
||||
|
|
@ -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"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1,71 @@
|
|||
# This is a markdown 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)
|
||||
|
|
@ -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({
|
|||
<TransmitProvider baseUrl={window.location.origin} enableLogging={true}>
|
||||
<ModalsProvider>
|
||||
<App {...props} />
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</ModalsProvider>
|
||||
</TransmitProvider>
|
||||
</QueryClientProvider>
|
||||
|
|
|
|||
|
|
@ -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<StyledSidebarProps> = ({ 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<StyledSidebarProps> = ({ title, items }) => {
|
|||
'group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold'
|
||||
)}
|
||||
>
|
||||
<item.icon aria-hidden="true" className="size-6 shrink-0" />
|
||||
{item.icon && <item.icon aria-hidden="true" className="size-6 shrink-0" />}
|
||||
{item.name}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -1,35 +1,29 @@
|
|||
import { Cog6ToothIcon, CommandLineIcon, FolderIcon } from '@heroicons/react/24/outline'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useMemo } from 'react'
|
||||
import StyledSidebar from '~/components/StyledSidebar'
|
||||
import api from '~/lib/api'
|
||||
|
||||
const navigation = [
|
||||
{ name: 'Apps', href: '/settings/apps', icon: CommandLineIcon, current: false },
|
||||
{ name: 'ZIM Explorer', href: '/settings/zim', icon: FolderIcon, current: false },
|
||||
{ name: 'System', href: '/settings/system', icon: Cog6ToothIcon, current: true },
|
||||
]
|
||||
|
||||
export default function DocsLayout({ children }: { children: React.ReactNode }) {
|
||||
const [docs, setDocs] = useState<Array<{ title: string; slug: string }>>([])
|
||||
const { data, isLoading } = useQuery<Array<{ title: string; slug: string }>>({
|
||||
queryKey: ['docs'],
|
||||
queryFn: () => api.listDocs(),
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: Infinity,
|
||||
})
|
||||
|
||||
// Fetch docs when the component mounts
|
||||
useEffect(() => {
|
||||
fetchDocs()
|
||||
}, [])
|
||||
const items = useMemo(() => {
|
||||
if (isLoading || !data) return []
|
||||
|
||||
async function fetchDocs() {
|
||||
try {
|
||||
const data = await api.listDocs()
|
||||
console.log('Fetched docs:', data)
|
||||
setDocs(data)
|
||||
} catch (error) {
|
||||
console.error('Error fetching docs:', error)
|
||||
}
|
||||
}
|
||||
return data.map((doc) => ({
|
||||
name: doc.title,
|
||||
href: `/docs/${doc.slug}`,
|
||||
current: false,
|
||||
}))
|
||||
}, [data, isLoading])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-row bg-stone-50/90">
|
||||
<StyledSidebar title="Documentation" items={navigation} />
|
||||
<StyledSidebar title="Documentation" items={items} />
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ class API {
|
|||
|
||||
async listDocs() {
|
||||
try {
|
||||
const response = await this.client.get<{ articles: Array<{ title: string; slug: string }> }>("/docs/list");
|
||||
return response.data.articles;
|
||||
const response = await this.client.get<Array<{ title: string; slug: string }>>("/docs/list");
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Error listing docs:", error);
|
||||
throw error;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,11 @@ import { Head } from '@inertiajs/react'
|
|||
import MarkdocRenderer from '~/components/MarkdocRenderer'
|
||||
import DocsLayout from '~/layouts/DocsLayout'
|
||||
|
||||
export default function Show({ content, title }: { content: any; title: string }) {
|
||||
export default function Show({ content }: { content: any; }) {
|
||||
return (
|
||||
<DocsLayout>
|
||||
<Head title={`${title} | Documentation | Project N.O.M.A.D.`} />
|
||||
<Head title={'Documentation | Project N.O.M.A.D.'} />
|
||||
<div className="xl:pl-80 py-6">
|
||||
<h1 className='font-semibold text-xl'>{title}</h1>
|
||||
<MarkdocRenderer content={content} />
|
||||
</div>
|
||||
</DocsLayout>
|
||||
|
|
|
|||
42
admin/package-lock.json
generated
42
admin/package-lock.json
generated
|
|
@ -28,6 +28,7 @@
|
|||
"@tabler/icons-react": "^3.34.0",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@tanstack/react-query": "^5.81.5",
|
||||
"@tanstack/react-query-devtools": "^5.83.0",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@vinejs/vine": "^3.0.1",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
|
|
@ -3668,9 +3669,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-core": {
|
||||
"version": "5.81.5",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.81.5.tgz",
|
||||
"integrity": "sha512-ZJOgCy/z2qpZXWaj/oxvodDx07XcQa9BF92c0oINjHkoqUPsmm3uG08HpTaviviZ/N9eP1f9CM7mKSEkIo7O1Q==",
|
||||
"version": "5.83.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.0.tgz",
|
||||
"integrity": "sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/query-devtools": {
|
||||
"version": "5.81.2",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.81.2.tgz",
|
||||
"integrity": "sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
|
@ -3678,12 +3689,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query": {
|
||||
"version": "5.81.5",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.81.5.tgz",
|
||||
"integrity": "sha512-lOf2KqRRiYWpQT86eeeftAGnjuTR35myTP8MXyvHa81VlomoAWNEd8x5vkcAfQefu0qtYCvyqLropFZqgI2EQw==",
|
||||
"version": "5.83.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.83.0.tgz",
|
||||
"integrity": "sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.81.5"
|
||||
"@tanstack/query-core": "5.83.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
|
|
@ -3693,6 +3704,23 @@
|
|||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-query-devtools": {
|
||||
"version": "5.83.0",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.83.0.tgz",
|
||||
"integrity": "sha512-yfp8Uqd3I1jgx8gl0lxbSSESu5y4MO2ThOPBnGNTYs0P+ZFu+E9g5IdOngyUGuo6Uz6Qa7p9TLdZEX3ntik2fQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tanstack/query-devtools": "5.81.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"react": "^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/@tanstack/react-virtual": {
|
||||
"version": "3.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@
|
|||
"@tabler/icons-react": "^3.34.0",
|
||||
"@tailwindcss/vite": "^4.1.10",
|
||||
"@tanstack/react-query": "^5.81.5",
|
||||
"@tanstack/react-query-devtools": "^5.83.0",
|
||||
"@tanstack/react-virtual": "^3.13.12",
|
||||
"@vinejs/vine": "^3.0.1",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ server.errorHandler(() => import('#exceptions/handler'))
|
|||
*/
|
||||
server.use([
|
||||
() => import('#middleware/container_bindings_middleware'),
|
||||
() => import('@adonisjs/static/static_middleware'),
|
||||
() => import('@adonisjs/cors/cors_middleware'),
|
||||
() => import('@adonisjs/vite/vite_middleware'),
|
||||
() => import('@adonisjs/inertia/inertia_middleware')
|
||||
() => import('@adonisjs/inertia/inertia_middleware'),
|
||||
() => import('@adonisjs/static/static_middleware')
|
||||
])
|
||||
|
||||
/**
|
||||
|
|
|
|||
9
admin/util/docs.ts
Normal file
9
admin/util/docs.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { Readable } from 'stream';
|
||||
|
||||
export const streamToString = async (stream: Readable): Promise<string> => {
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of stream) {
|
||||
chunks.push(Buffer.from(chunk));
|
||||
}
|
||||
return Buffer.concat(chunks).toString('utf-8');
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user