fix(Docs): documentation renderer fixes

This commit is contained in:
Jake Turner 2025-12-23 15:59:58 -08:00 committed by Jake Turner
parent 6ac9d147cf
commit 0c8527921c
9 changed files with 264 additions and 55 deletions

View File

@ -0,0 +1,6 @@
import { Exception } from '@adonisjs/core/exceptions'
export default class InternalServerErrorException extends Exception {
static status = 500
static code = 'E_INTERNAL_SERVER_ERROR'
}

View File

@ -2,6 +2,7 @@ import Markdoc from '@markdoc/markdoc'
import { streamToString } from '../../util/docs.js' import { streamToString } from '../../util/docs.js'
import { getFile, getFileStatsIfExists, listDirectoryContentsRecursive } from '../utils/fs.js' import { getFile, getFileStatsIfExists, listDirectoryContentsRecursive } from '../utils/fs.js'
import path from 'path' import path from 'path'
import InternalServerErrorException from '#exceptions/internal_server_error_exception'
export class DocsService { export class DocsService {
private docsPath = path.join(process.cwd(), 'docs') private docsPath = path.join(process.cwd(), 'docs')
@ -23,35 +24,47 @@ export class DocsService {
} }
parse(content: string) { parse(content: string) {
const ast = Markdoc.parse(content) try {
const config = this.getConfig() const ast = Markdoc.parse(content)
const errors = Markdoc.validate(ast, config) const config = this.getConfig()
const errors = Markdoc.validate(ast, config)
if (errors.length > 0) { // Filter out attribute-undefined errors which may be caused by emojis and special characters
throw new Error(`Markdoc validation errors: ${errors.map((e) => e.error).join(', ')}`) const criticalErrors = errors.filter((e) => e.error.id !== 'attribute-undefined')
if (criticalErrors.length > 0) {
console.error('Markdoc validation errors:', errors.map((e) => JSON.stringify(e.error)).join(', '))
throw new Error('Markdoc validation failed')
}
return Markdoc.transform(ast, config)
} catch (error) {
console.log('Error parsing Markdoc content:', error)
throw new InternalServerErrorException(`Error parsing content: ${(error as Error).message}`)
} }
return Markdoc.transform(ast, config)
} }
async parseFile(_filename: string) { async parseFile(_filename: string) {
if (!_filename) { try {
throw new Error('Filename is required') if (!_filename) {
} throw new Error('Filename is required')
}
const filename = _filename.endsWith('.md') ? _filename : `${_filename}.md` const filename = _filename.endsWith('.md') ? _filename : `${_filename}.md`
const fileExists = await getFileStatsIfExists(path.join(this.docsPath, filename)) const fileExists = await getFileStatsIfExists(path.join(this.docsPath, filename))
if (!fileExists) { if (!fileExists) {
throw new Error(`File not found: ${filename}`) throw new Error(`File not found: ${filename}`)
} }
const fileStream = await getFile(path.join(this.docsPath, filename), 'stream') const fileStream = await getFile(path.join(this.docsPath, filename), 'stream')
if (!fileStream) { if (!fileStream) {
throw new Error(`Failed to read file stream: ${filename}`) throw new Error(`Failed to read file stream: ${filename}`)
}
const content = await streamToString(fileStream)
return this.parse(content)
} catch (error) {
throw new InternalServerErrorException(`Error parsing file: ${(error as Error).message}`)
} }
const content = await streamToString(fileStream)
return this.parse(content)
} }
private prettify(filename: string) { private prettify(filename: string) {
@ -87,6 +100,21 @@ export class DocsService {
id: { type: String }, id: { type: String },
}, },
}, },
list: {
render: 'List',
attributes: {
ordered: { type: Boolean },
start: { type: Number },
},
},
list_item: {
render: 'ListItem',
attributes: {
marker: { type: String },
className: { type: String },
class: { type: String }
}
},
}, },
} }
} }

5
admin/docs/about.md Normal file
View File

@ -0,0 +1,5 @@
# About Project N.O.M.A.D.
Project N.O.M.A.D. (Node for Offline Media, Archives, and Data; "Nomad" for short) is a project started in 2025 by Chris Sherwood of [Crosstalk Solutions, LLC.](https://crosstalksolutions.com). The goal of the project is not to create just another utility for storing offline resources, but rather to allow users to run their own ultime "survival computer".
While many similar offline survival computers are designed to be run on bare-minimum, lightweight hardware, Project N.O.M.A.D. is quite the opposite. To install and run the available AI tools, we highly encourage the use of a beefy, GPU-backed device to make the most of your install.

View File

@ -1,6 +1,148 @@
# Release Notes # Release Notes
## Version 1.1.0 - July 20, 2025 ## Version 1.10.1 - December 5, 2025
### ✨ Improvements
1. This is a test
- **Kiwix**: ZIM storage path
---
## Version 1.10.0 - December 5, 2025
### 🚀 Features
- Disk info monitoring
### ✨ Improvements
- **Install**: Add Redis env variables to compose file
- **Kiwix**: initial download and setup
---
## Version 1.9.0 - December 5, 2025
### 🚀 Features
- Background job management with BullMQ
### ✨ Improvements
- **Install**: Character escaping in env variables
- **Install**: Host env variable
---
## Version 1.8.0 - December 5, 2025
### 🚀 Features
- Alert and button styles redesign
- System info page redesign
- **Collections**: Curated ZIM Collections
- **Collections**: add Preppers Library
- **Collections**: add slug, icon, and language
- **Collections**: store additional data with resources list
- Custom map and ZIM file downloads (WIP)
- New maps system (WIP)
### ✨ Improvements
- **DockerService**: cleanup old OSM stuff
- **Install**: standardize compose file names
- Hide query devtools in prod
---
## Version 1.7.0 - December 5, 2025
### 🚀 Features
- Alert and button styles redesign
- System info page redesign
- **Collections**: Curated ZIM Collections
- **Collections**: add Preppers Library
- **Collections**: add slug, icon, and language
- **Collections**: store additional data with resources list
- Custom map and ZIM file downloads (WIP)
- New maps system (WIP)
### ✨ Improvements
- **DockerService**: cleanup old OSM stuff
- **Install**: standardize compose file names
- Hide query devtools in prod
---
## Version 1.6.0 - November 18, 2025
### 🚀 Features
- Added Kolibri to standard app library
### ✨ Improvements
- Standardize container names in management-compose
---
## Version 1.5.0 - November 18, 2025
### 🚀 Features
- Version footer and fix CI version handling
---
## Version 1.4.0 - November 18, 2025
### 🚀 Features
- **Services**: Friendly names and descriptions
### ✨ Improvements
- **Scripts**: logs directory creation improvements
- **Scripts**: fix type in management-compose file path
---
## Version 1.3.0 - October 9, 2025
### 🚀 New Features
- Uninstall script now removes non-management Nomad app containers
### ✨ Improvements
- **OpenStreetMap**: Apply dir permission fixes more robustly
---
## Version 1.2.0 - October 7, 2025
### 🚀 New Features
- Added CyberChef to standard app library
- Added Dozzle to core containers for enhanced logs and metrics
- Added FlatNotes to standard app library
- Uninstall helper script available
### ✨ Improvements
- **OpenStreetMap**:
- Fixed directory paths and access issues
- Improved error handling
- Fixed renderer file permissions
- Fixed absolute host path issue
- **ZIM Manager**:
- Initial ZIM download now hosted in Project Nomad GitHub repo for better availability
---
## Version 1.1.0 - August 20, 2025
### 🚀 New Features ### 🚀 New Features

View File

@ -1,5 +1,8 @@
import React, { JSX } from 'react' import React from 'react'
import Markdoc from '@markdoc/markdoc' import Markdoc from '@markdoc/markdoc'
import { Heading } from './markdoc/Heading'
import { List } from './markdoc/List'
import { ListItem } from './markdoc/ListItem'
// Custom components for Markdoc tags // Custom components for Markdoc tags
const Callout = ({ const Callout = ({
@ -27,37 +30,12 @@ const Callout = ({
) )
} }
const Heading = ({
level,
id,
children,
}: {
level: number
id: string
children: React.ReactNode
}) => {
const Tag = `h${level}` as keyof JSX.IntrinsicElements
const sizes = {
1: 'text-3xl font-bold',
2: 'text-2xl font-semibold',
3: 'text-xl font-semibold',
4: 'text-lg font-semibold',
5: 'text-base font-semibold',
6: 'text-sm font-semibold',
}
return (
// @ts-ignore
<Tag id={id} className={`${sizes[level]} mb-4 mt-6`}>
{children}
</Tag>
)
}
// Component mapping for Markdoc // Component mapping for Markdoc
const components = { const components = {
Callout, Callout,
Heading, Heading,
List,
ListItem,
} }
interface MarkdocRendererProps { interface MarkdocRendererProps {
@ -65,11 +43,9 @@ interface MarkdocRendererProps {
} }
const MarkdocRenderer: React.FC<MarkdocRendererProps> = ({ content }) => { const MarkdocRenderer: React.FC<MarkdocRendererProps> = ({ content }) => {
return ( console.log('Markdoc content:', content)
<div className="prose prose-lg max-w-none">
{Markdoc.renderers.react(content, React, { components })} return <div className="tracking-wide">{Markdoc.renderers.react(content, React, { components })}</div>
</div>
)
} }
export default MarkdocRenderer export default MarkdocRenderer

View File

@ -0,0 +1,28 @@
import React, { JSX } from 'react'
export function Heading({
level,
id,
children,
}: {
level: number
id: string
children: React.ReactNode
}) {
const Component = `h${level}` as keyof JSX.IntrinsicElements
const sizes = {
1: 'text-3xl font-bold',
2: 'text-2xl font-semibold',
3: 'text-xl font-semibold',
4: 'text-lg font-semibold',
5: 'text-base font-semibold',
6: 'text-sm font-semibold',
}
return (
// @ts-ignore
<Component id={id} className={`${sizes[level]} mb-2 mt-6`}>
{children}
</Component>
)
}

View File

@ -0,0 +1,20 @@
export function List({
ordered = false,
start,
children,
}: {
ordered?: boolean
start?: number
children: React.ReactNode
}) {
const className = ordered
? 'list-decimal list-outside !ml-12 mb-4 space-y-1'
: 'list-disc list-outside !ml-12 mb-4 space-y-1'
const Tag = ordered ? 'ol' : 'ul'
return (
// @ts-ignore
<Tag start={ordered ? start : undefined} className={className}>
{children}
</Tag>
)
}

View File

@ -0,0 +1,4 @@
export function ListItem({ children }: { children: React.ReactNode }) {
return <li className="ml-0 !pl-4">{children}</li>
}

View File

@ -8,5 +8,5 @@
"~/*": ["./*"], "~/*": ["./*"],
}, },
}, },
"include": ["./**/*.ts", "./**/*.tsx"], "include": ["./**/*.ts", "./**/*.tsx", "components/markdoc/nodes/heading.markdoc.js"],
} }