Skip to content

Commit

Permalink
feat: implement markdoc static site generator with dark theme
Browse files Browse the repository at this point in the history
- Add Markdoc-based static site generation
- Implement dark theme with gradients and modern aesthetics
- Add tab functionality with smooth animations
- Create responsive sidebar with active indicators
- Add syntax highlighting for code blocks
- Generate og:image using Satori
- Implement build pipeline with TypeScript and Tailwind

Co-Authored-By: Mokshit Jain <[email protected]>
  • Loading branch information
devin-ai-integration[bot] and Mokshit06 committed Dec 11, 2024
1 parent f7ad261 commit a195f4e
Show file tree
Hide file tree
Showing 41 changed files with 17,463 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/markdoc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/\ndist/\nout/\n.DS_Store
1,795 changes: 1,795 additions & 0 deletions docs/markdoc/Inter-Bold.ttf

Large diffs are not rendered by default.

1,795 changes: 1,795 additions & 0 deletions docs/markdoc/Inter-Regular.ttf

Large diffs are not rendered by default.

Binary file added docs/markdoc/Inter-Regular.woff2
Binary file not shown.
1 change: 1 addition & 0 deletions docs/markdoc/NotoSans-Regular.ttf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
404: Not Found
3,084 changes: 3,084 additions & 0 deletions docs/markdoc/README.md

Large diffs are not rendered by default.

3,084 changes: 3,084 additions & 0 deletions docs/markdoc/attachments/README.md

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions docs/markdoc/attachments/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions docs/markdoc/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"formatter": {
"enabled": true,
"indentStyle": "space"
}
}
236 changes: 236 additions & 0 deletions docs/markdoc/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import fs from 'fs/promises';
import path from 'path';
import Markdoc from '@markdoc/markdoc';
import { execSync } from 'child_process';
import { title, description } from './constants.js';
import { decode } from 'html-entities';

function getNodeText(node: any): string {
if (typeof node === 'string') return decode(node);
if (Array.isArray(node)) return node.map(getNodeText).join('');
if (typeof node === 'object' && node.children) return getNodeText(node.children);
return '';
}

function sanitizeId(text: string): string {
return text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
}

async function main() {
try {
// Read README.md and create out directory
let source = await fs.readFile(path.join(process.cwd(), '..', 'attachments', 'README.md'), 'utf-8');
const outDir = path.join(process.cwd(), 'out');
await fs.mkdir(outDir, { recursive: true });

// Parse markdown
const ast = Markdoc.parse(source);

const headings: Array<{ id: string; text: string; level: number }> = [];
let tabGroupCounter = 0;

// Custom config for transforming nodes
const config = {
nodes: {
document: {
transform(node: any, config: any) {
const children = node.transformChildren(config);
return children;
}
},
heading: {
transform(node: any, config: any) {
const attributes = node.transformAttributes(config);
const children = node.transformChildren(config);
const text = getNodeText(children);
const id = text ? sanitizeId(text) : '';
const level = attributes.level || 1;

if (level === 2 || level === 3) {
headings.push({ id, text: decode(text), level });
}

return new Markdoc.Tag(
`h${level}`,
{
id,
class: `heading-${level}`,
'data-heading': 'true'
},
children.map((child: any) => typeof child === 'string' ? decode(child) : child)
);
}
},
paragraph: {
transform(node: any, config: any) {
const children = node.transformChildren(config);
const text = getNodeText(node);

// If the content is HTML, parse it carefully
if (text.includes('<') && text.includes('>')) {
// Clean up the HTML content
return text
.replace(/&quot;/g, '"')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&amp;/g, '&')
.replace(/<\/?p>/g, '') // Remove any p tags in the HTML content
.trim();
}

// Regular paragraph content
return new Markdoc.Tag('p', { class: 'mb-4' }, children);
}
},
image: {
transform(node: any, config: any) {
const attributes = node.transformAttributes(config);
const src = attributes.src || '';
const alt = attributes.alt || '';
const title = attributes.title;

return new Markdoc.Tag('img', {
src: src.startsWith('http') ? src : path.basename(src),
alt: decode(alt),
title: title ? decode(title) : undefined,
class: 'inline-block'
});
}
},
link: {
transform(node: any, config: any) {
const attributes = node.transformAttributes(config);
const children = node.transformChildren(config);
const href = attributes.href || '';
const title = attributes.title;

// Handle different types of links
let processedHref = '';
if (href.startsWith('#')) {
// Internal anchor links - keep as is
processedHref = href;
} else if (href.startsWith('http')) {
// External links - keep as is
processedHref = href;
} else {
// Convert relative links to anchors based on text content
const text = getNodeText(children);
processedHref = '#' + sanitizeId(text);
}

return new Markdoc.Tag('a', {
href: processedHref,
title: title ? decode(title) : undefined,
class: 'text-blue-400 hover:text-blue-300 transition-colors duration-200'
}, children.map((child: any) => typeof child === 'string' ? decode(child) : child));
}
},
fence: {
transform(node: any, config: any) {
const { language } = node.attributes;
const content = node.attributes.content;

// Handle tab groups
if (content.includes('===')) {
tabGroupCounter++;
const tabs: string[] = content.split('===').map((tab: string) => tab.trim());
const tabTitles: string[] = tabs.map((tab: string) => tab.split('\n')[0]);
const tabContents: string[] = tabs.map((tab: string) =>
tab.split('\n')
.slice(1)
.join('\n')
.trim()
);

const tabsHtml = tabTitles.map((title: string, i: number) =>
new Markdoc.Tag('button', {
class: `tab${i === 0 ? ' active' : ''}`,
'data-tab': i.toString(),
'data-group': tabGroupCounter.toString()
}, [title])
);

const contentHtml = tabContents.map((content: string, i: number) =>
new Markdoc.Tag('div', {
class: `tab-content${i === 0 ? ' active' : ''}`,
'data-tab': i.toString(),
'data-group': tabGroupCounter.toString()
}, [
new Markdoc.Tag('pre', { tabindex: '0' }, [
new Markdoc.Tag('code', {
class: `language-${language || 'text'}`,
'data-prism': 'true'
}, [content])
])
])
);

return new Markdoc.Tag('div', { class: 'tab-group' }, [
new Markdoc.Tag('div', { class: 'tab-list' }, tabsHtml),
new Markdoc.Tag('div', { class: 'tab-contents' }, contentHtml)
]);
}

// Regular code blocks
return new Markdoc.Tag('pre', { tabindex: '0' }, [
new Markdoc.Tag('code', {
class: `language-${language || 'text'}`,
'data-prism': 'true'
}, [content])
]);
}
}
}
};

// Transform content
const content = Markdoc.transform(ast, config);
let contentHtml = Markdoc.renderers.html(content);

// Clean up HTML structure
contentHtml = contentHtml
// Handle HTML entities in non-code content
.replace(/&quot;(?![^<]*<\/code>)/g, '"')
.replace(/&lt;(?![^<]*<\/code>)/g, '<')
.replace(/&gt;(?![^<]*<\/code>)/g, '>')
.replace(/&amp;(?![^<]*<\/code>)/g, '&')
// Clean up structure
.replace(/(<p[^>]*>)\s*(<p[^>]*>)/g, '$1')
.replace(/(<\/p>)\s*(<\/p>)/g, '$2')
.replace(/<h([1-6])[^>]*>\s*<\/h\1>/g, '')
.replace(/>\s+</g, '><')
.replace(/\s+/g, ' ');

// Generate sidebar HTML
const sidebarHtml = headings
.map(({ id, text, level }) => `
<a href="#${id}"
class="sidebar-link ${level === 2 ? 'pl-2' : 'pl-4'} block py-1.5 text-slate-300 hover:text-blue-400 transition-colors duration-200 rounded relative"
data-heading-link="${id}">
${decode(text)}
</a>
`)
.join('');

// Read template and replace content
const template = await fs.readFile(
path.join(process.cwd(), 'template.html'),
'utf-8'
);

const finalHtml = template
.replace(/\{\{\s*title\s*\}\}/g, title)
.replace(/\{\{\s*description\s*\}\}/g, description)
.replace(/\{\{\s*content\s*\}\}/g, contentHtml)
.replace(/\{\{\s*sidebar\s*\}\}/g, sidebarHtml);

await fs.writeFile(path.join(outDir, 'index.html'), finalHtml);
console.log('Build completed successfully');

} catch (error) {
console.error('Build failed:', error);
process.exit(1);
}
}

main();
7 changes: 7 additions & 0 deletions docs/markdoc/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { join } from 'path';
import os from 'os';

export const HOME_DIR = os.homedir();
export const OUT_DIR = join(process.cwd(), 'out');
export const title = 'Zod Documentation';
export const description = 'TypeScript-first schema validation with static type inference';
Loading

0 comments on commit a195f4e

Please sign in to comment.