Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ yarn-error.log*

# typescript
*.tsbuildinfo
next-env.d.ts
next-env.d.ts

# Built resources
/public/media/**/*
33 changes: 30 additions & 3 deletions app/components/Code.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
import RenderMermaid from "react-x-mermaid";

export default function Index({
export default function Code({
code,
language = 'javascript'
}: {
code: string;
language?: string;
}) {
// Render Mermaid diagram at build time
if (language === 'mermaid') {
return (
<section className="text-sm bg-white rounded-lg p-0 mb-4">
<RenderMermaid
mermaidCode={code}
disableCopy={true}
disableDownload={true}
mermaidConfig={{
theme: "dark",
themeVariables: {
background: "transparent",
primaryColor: "#1e1e1e",
primaryTextColor: "#f2f2f2",
primaryBorderColor: "#333",
lineColor: "#666",
secondaryColor: "#2a2a2a",
tertiaryColor: "#141414"
}
}}
/>
</section>
);
}

// Render syntax-highlighted code
return (
<section className="text-sm">
<section className="text-sm bg-neutral-900/50 rounded-lg p-4 border border-neutral-600/30 mb-4">
<SyntaxHighlighter
language={language}
style={atomDark}
Expand All @@ -21,5 +48,5 @@ export default function Index({
{code}
</SyntaxHighlighter>
</section>
)
);
}
108 changes: 92 additions & 16 deletions app/components/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ import type { ReactElement } from 'react';
import Code from './Code';
import { kebabCase } from 'change-case';

export default function Markdown({ content }: { content: string }) {
interface MarkdownProps {
content: string;
currentPath?: string; // e.g., "architecture/index" or "quickstart/extension"
}

export default function Markdown({ content, currentPath }: MarkdownProps) {
const processedContent = useMemo(() => {
// Split content by H2 headings to group sections
const sections = content.split(/^## /gm);
Expand All @@ -22,7 +27,7 @@ export default function Markdown({ content }: { content: string }) {
<div key={`intro-${index}`} className="mb-8">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={getMarkdownComponents()}
components={getMarkdownComponents(currentPath)}
>
{section}
</ReactMarkdown>
Expand All @@ -42,7 +47,7 @@ export default function Markdown({ content }: { content: string }) {
</h2>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={getMarkdownComponents()}
components={getMarkdownComponents(currentPath)}
>
{sectionContent}
</ReactMarkdown>
Expand All @@ -57,7 +62,59 @@ export default function Markdown({ content }: { content: string }) {
return <div className="space-y-8">{processedContent}</div>;
}

function getMarkdownComponents() {
/**
* Convert a relative markdown link to the correct Next.js route
* @param href - The href from the markdown (e.g., "../quickstart/extension.md")
* @param currentPath - The current article path (e.g., "architecture/index")
* @returns The converted route (e.g., "/docs/quickstart/extension")
*/
function convertMarkdownLink(href: string, currentPath?: string): string {
// Only process relative links that end with .md
if (!href.endsWith('.md') || href.startsWith('http://') || href.startsWith('https://')) {
return href;
}

// Remove the .md extension
let linkPath = href.replace(/\.md$/, '');

// If it's a relative path, resolve it
if (linkPath.startsWith('../') || linkPath.startsWith('./')) {
if (!currentPath) {
// Fallback: just remove ./ and ../ and hope for the best
linkPath = linkPath.replace(/^(\.\.\/)+/, '').replace(/^\.\//, '');
} else {
// Get the directory of the current file
const currentDir = currentPath.includes('/')
? currentPath.substring(0, currentPath.lastIndexOf('/'))
: '';

// Split the link into parts
const linkParts = linkPath.split('/');
const currentParts = currentDir.split('/').filter(p => p);

// Process each part of the link
for (const part of linkParts) {
if (part === '..') {
// Go up one directory
currentParts.pop();
} else if (part !== '.') {
// Add to the path
currentParts.push(part);
}
}

linkPath = currentParts.join('/');
}
}

// Remove trailing /index if present
linkPath = linkPath.replace(/\/index$/, '');

// Add /docs prefix if not already present
return linkPath.startsWith('/') ? linkPath : `/docs/${linkPath}`;
}

function getMarkdownComponents(currentPath?: string) {
return {
// H1 headings (main page title from Markdown content)
h1: ({ children, ...props }: any) => (
Expand Down Expand Up @@ -132,7 +189,6 @@ function getMarkdownComponents() {

return (
<div
className="bg-neutral-900/50 rounded-lg p-4 border border-neutral-600/30 mb-4"
{...props}
>
<Code code={codeString} language={lang} />
Expand Down Expand Up @@ -244,18 +300,38 @@ function getMarkdownComponents() {
);
},

// Images
img: ({ src, alt, ...props }: any) => {
// Convert relative media paths to absolute paths
const imageSrc = src?.startsWith('media/') ? `/${src}` : src;

return (
<img
src={imageSrc}
alt={alt}
className="rounded-lg my-4 max-w-full h-auto"
{...props}
/>
);
},

// Links
a: ({ children, href, ...props }: any) => (
<a
href={href}
className="text-blue-400 hover:text-blue-300 transition-colors"
target="_blank"
rel="noopener noreferrer"
{...props}
>
{children}
</a>
),
a: ({ children, href, ...props }: any) => {
const convertedHref = convertMarkdownLink(href, currentPath);
const isExternal = convertedHref.startsWith('http://') || convertedHref.startsWith('https://');

return (
<a
href={convertedHref}
className="text-blue-400 hover:text-blue-300 transition-colors"
target={isExternal ? "_blank" : undefined}
rel={isExternal ? "noopener noreferrer" : undefined}
{...props}
>
{children}
</a>
);
},

// Strong text
strong: ({ children, ...props }: any) => (
Expand Down
5 changes: 4 additions & 1 deletion app/docs/[section]/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export default async function ArticlePage({ params }: PageProps) {
// Use title from frontmatter if available, otherwise fall back to navigation title or section name
const pageTitle = frontmatter.title || selectedNavItem?.title || section;

// Build the current path for the markdown component to resolve relative links
const currentPath = file === 'index' ? `${section}/index` : `${section}/${slug.join('/')}`;

return (
<div className="min-h-screen bg-neutral-900 relative overflow-hidden">
{/* Background elements */}
Expand Down Expand Up @@ -139,7 +142,7 @@ export default async function ArticlePage({ params }: PageProps) {
{frontmatter.layout === 'coming-soon' && <ComingSoon />}

{/* Markdown Content */}
<Markdown content={content} />
<Markdown content={content} currentPath={currentPath} />
</div>
</div>
</div>
Expand Down
5 changes: 4 additions & 1 deletion content.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@
],
"include": [
"**/*.md",
"**/*.yml"
"**/*.yml",
"**/media/**/*.png",
"**/media/**/*.jpg",
"**/media/**/*.gif"
],
"exclude": [
"**/{readme,README}.md"
Expand Down
Loading