Skip to content

Commit 4761693

Browse files
committed
docs: add Nextra 4 documentation site
- Set up Nextra 4 with Next.js 15 App Router - Configure GitHub Pages deployment workflow - Add custom styling to match SWR documentation site - Implement dark mode support for all components - Add code block copy button functionality - Configure Tailwind CSS 4 for styling - Add Geist Mono font for code blocks Signed-off-by: ryjiang <jiangruiyi@gmail.com>
1 parent 7838959 commit 4761693

File tree

18 files changed

+5303
-1
lines changed

18 files changed

+5303
-1
lines changed

.github/workflows/doc.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Simple workflow for deploying static content to GitHub Pages
2+
name: Deploy static content to Pages
3+
4+
on:
5+
# Runs on pushes targeting the default branch
6+
push:
7+
branches: ["main"]
8+
9+
# Allows you to run this workflow manually from the Actions tab
10+
workflow_dispatch:
11+
12+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13+
permissions:
14+
contents: read
15+
pages: write
16+
id-token: write
17+
18+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20+
concurrency:
21+
group: "pages"
22+
cancel-in-progress: false
23+
24+
jobs:
25+
# Single deploy job since we're just deploying
26+
deploy:
27+
environment:
28+
name: github-pages
29+
url: ${{ steps.deployment.outputs.page_url }}
30+
runs-on: ubuntu-latest
31+
steps:
32+
- name: Checkout
33+
uses: actions/checkout@v4
34+
- name: Setup Node.js
35+
uses: actions/setup-node@v4
36+
with:
37+
node-version: '18'
38+
- name: Setup Pages
39+
uses: actions/configure-pages@v5
40+
- name: Install dependencies
41+
working-directory: ./docs
42+
run: yarn install
43+
- name: Build documentation
44+
working-directory: ./docs
45+
run: yarn build
46+
- name: Upload artifact
47+
uses: actions/upload-pages-artifact@v3
48+
with:
49+
path: './docs/out'
50+
- name: Deploy to GitHub Pages
51+
id: deployment
52+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
node_modules
22
dist
3-
docs
43
coverage
54
.DS_Store
65
yarn-error.log

docs/.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
node_modules/
2+
.next/
3+
out/
4+
_pagefind/
5+
.DS_Store
6+
*.log
7+

docs/app/[[...mdxPath]]/page.jsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { generateStaticParamsFor, importPage } from 'nextra/pages';
2+
import { notFound } from 'next/navigation';
3+
import { useMDXComponents } from '../../mdx-components.jsx';
4+
5+
export const generateStaticParams = generateStaticParamsFor('mdxPath');
6+
7+
export default async function Page(props) {
8+
const params = await props.params;
9+
const mdxPath = params?.mdxPath || [];
10+
11+
// Ensure mdxPath is always an array
12+
const pathSegments = Array.isArray(mdxPath) ? mdxPath : [];
13+
14+
try {
15+
const result = await importPage(pathSegments, '');
16+
if (!result || !result.default) {
17+
console.error('Page not found, pathSegments:', pathSegments);
18+
notFound();
19+
}
20+
21+
const { default: MDXContent, toc, metadata } = result;
22+
const Wrapper = useMDXComponents().wrapper;
23+
24+
if (Wrapper) {
25+
return (
26+
<Wrapper toc={toc} metadata={metadata}>
27+
<MDXContent {...props} params={params} />
28+
</Wrapper>
29+
);
30+
}
31+
32+
return <MDXContent {...props} params={params} />;
33+
} catch (error) {
34+
console.error('Error loading page:', error);
35+
console.error('pathSegments:', pathSegments);
36+
console.error('error details:', error.message);
37+
notFound();
38+
}
39+
}

docs/app/layout.jsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Footer, Layout, Navbar } from 'nextra-theme-docs';
2+
import { Head, Search } from 'nextra/components';
3+
import { getPageMap } from 'nextra/page-map';
4+
import themeConfig from '../theme.config.jsx';
5+
import CodeBlockEnhancer from '../components/CodeBlockEnhancer.jsx';
6+
import 'nextra-theme-docs/style.css';
7+
import '../styles/custom.css';
8+
9+
export const metadata = {
10+
title: {
11+
default: 'Milvus Node.js SDK',
12+
template: '%s | Milvus Node.js SDK',
13+
},
14+
description: 'The official Milvus client for Node.js',
15+
openGraph: {
16+
type: 'website',
17+
locale: 'en_US',
18+
siteName: 'Milvus Node.js SDK',
19+
},
20+
};
21+
22+
export default async function RootLayout({ children }) {
23+
let pageMap = [];
24+
try {
25+
pageMap = await getPageMap();
26+
if (!Array.isArray(pageMap)) {
27+
pageMap = [];
28+
}
29+
} catch (error) {
30+
console.error('Error fetching page map:', error);
31+
pageMap = [];
32+
}
33+
34+
return (
35+
<html lang="en" dir="ltr" suppressHydrationWarning>
36+
<Head>
37+
<link rel="preconnect" href="https://fonts.googleapis.com" />
38+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
39+
<link
40+
href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;600;700&display=swap"
41+
rel="stylesheet"
42+
/>
43+
</Head>
44+
<body>
45+
<Layout
46+
navbar={
47+
<Navbar
48+
logo={themeConfig.logo}
49+
projectLink={themeConfig.project?.link}
50+
/>
51+
}
52+
pageMap={pageMap}
53+
docsRepositoryBase={themeConfig.docsRepositoryBase}
54+
editLink="Edit this page on GitHub"
55+
footer={<Footer>{themeConfig.footer?.text}</Footer>}
56+
search={
57+
<Search placeholder={themeConfig.search?.placeholder} />
58+
}
59+
>
60+
{children}
61+
<CodeBlockEnhancer />
62+
</Layout>
63+
</body>
64+
</html>
65+
);
66+
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
'use client';
2+
3+
import { useEffect } from 'react';
4+
5+
function addCopyButton(block) {
6+
// Check if button already exists
7+
if (block.querySelector('[data-copy-button]')) {
8+
return;
9+
}
10+
11+
const codeElement = block.querySelector('code');
12+
if (!codeElement) return;
13+
14+
// Create button container
15+
const buttonContainer = document.createElement('div');
16+
buttonContainer.setAttribute('data-copy-button', 'true');
17+
buttonContainer.style.position = 'absolute';
18+
buttonContainer.style.top = '5px';
19+
buttonContainer.style.right = '5px';
20+
buttonContainer.style.zIndex = '10';
21+
22+
// Create button
23+
const button = document.createElement('button');
24+
button.setAttribute('aria-label', 'Copy code');
25+
button.style.opacity = '0';
26+
button.style.transition = 'opacity 0.2s';
27+
button.style.width = '36px';
28+
button.style.height = '36px';
29+
button.style.display = 'inline-flex';
30+
button.style.alignItems = 'center';
31+
button.style.justifyContent = 'center';
32+
button.style.borderRadius = '6px';
33+
button.style.cursor = 'pointer';
34+
35+
// Set button styles based on dark mode
36+
const isDark = document.documentElement.classList.contains('dark');
37+
if (isDark) {
38+
button.style.backgroundColor = 'rgba(30, 30, 30, 0.8)';
39+
button.style.border = '1px solid rgba(255, 255, 255, 0.1)';
40+
button.style.color = 'rgba(255, 255, 255, 0.9)';
41+
} else {
42+
button.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
43+
button.style.border = '1px solid rgba(0, 0, 0, 0.1)';
44+
button.style.color = 'rgba(0, 0, 0, 0.9)';
45+
}
46+
button.style.backdropFilter = 'blur(8px)';
47+
48+
// Copy icon SVG
49+
const copyIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
50+
copyIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
51+
copyIcon.setAttribute('width', '14');
52+
copyIcon.setAttribute('height', '14');
53+
copyIcon.setAttribute('viewBox', '0 0 24 24');
54+
copyIcon.setAttribute('fill', 'none');
55+
copyIcon.setAttribute('stroke', 'currentColor');
56+
copyIcon.setAttribute('stroke-width', '2');
57+
copyIcon.setAttribute('stroke-linecap', 'round');
58+
copyIcon.setAttribute('stroke-linejoin', 'round');
59+
copyIcon.style.color = 'inherit';
60+
61+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
62+
rect.setAttribute('width', '14');
63+
rect.setAttribute('height', '14');
64+
rect.setAttribute('x', '8');
65+
rect.setAttribute('y', '8');
66+
rect.setAttribute('rx', '2');
67+
rect.setAttribute('ry', '2');
68+
69+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
70+
path.setAttribute('d', 'M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2');
71+
72+
copyIcon.appendChild(rect);
73+
copyIcon.appendChild(path);
74+
button.appendChild(copyIcon);
75+
76+
// Copy functionality
77+
let isCopied = false;
78+
button.addEventListener('click', async () => {
79+
if (isCopied) return;
80+
81+
const codeText = codeElement.textContent || codeElement.innerText || '';
82+
if (!codeText) return;
83+
84+
try {
85+
await navigator.clipboard.writeText(codeText);
86+
isCopied = true;
87+
88+
// Change icon to checkmark
89+
const checkIcon = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
90+
checkIcon.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
91+
checkIcon.setAttribute('width', '14');
92+
checkIcon.setAttribute('height', '14');
93+
checkIcon.setAttribute('viewBox', '0 0 24 24');
94+
checkIcon.setAttribute('fill', 'none');
95+
checkIcon.setAttribute('stroke', 'currentColor');
96+
checkIcon.setAttribute('stroke-width', '2');
97+
checkIcon.setAttribute('stroke-linecap', 'round');
98+
checkIcon.setAttribute('stroke-linejoin', 'round');
99+
100+
const polyline = document.createElementNS('http://www.w3.org/2000/svg', 'polyline');
101+
polyline.setAttribute('points', '20 6 9 17 4 12');
102+
checkIcon.appendChild(polyline);
103+
104+
button.innerHTML = '';
105+
button.appendChild(checkIcon);
106+
107+
setTimeout(() => {
108+
isCopied = false;
109+
button.innerHTML = '';
110+
button.appendChild(copyIcon.cloneNode(true));
111+
}, 2000);
112+
} catch (err) {
113+
console.error('Failed to copy code:', err);
114+
}
115+
});
116+
117+
// Show button on hover
118+
block.addEventListener('mouseenter', () => {
119+
button.style.opacity = '1';
120+
});
121+
block.addEventListener('mouseleave', () => {
122+
if (!isCopied) {
123+
button.style.opacity = '0';
124+
}
125+
});
126+
127+
// Update button hover style
128+
button.addEventListener('mouseenter', () => {
129+
const isCurrentlyDark = document.documentElement.classList.contains('dark');
130+
if (isCurrentlyDark) {
131+
button.style.backgroundColor = 'rgba(255, 255, 255, 0.1)';
132+
} else {
133+
button.style.backgroundColor = 'rgba(0, 0, 0, 0.05)';
134+
}
135+
});
136+
button.addEventListener('mouseleave', () => {
137+
const isCurrentlyDark = document.documentElement.classList.contains('dark');
138+
if (isCurrentlyDark) {
139+
button.style.backgroundColor = 'rgba(30, 30, 30, 0.8)';
140+
} else {
141+
button.style.backgroundColor = 'rgba(255, 255, 255, 0.8)';
142+
}
143+
});
144+
145+
buttonContainer.appendChild(button);
146+
block.style.position = 'relative';
147+
block.appendChild(buttonContainer);
148+
}
149+
150+
export default function CodeBlockEnhancer() {
151+
useEffect(() => {
152+
// Function to add copy buttons to all code blocks
153+
const enhanceCodeBlocks = () => {
154+
const codeBlocks = document.querySelectorAll('.nextra-code');
155+
codeBlocks.forEach(addCopyButton);
156+
};
157+
158+
// Run immediately
159+
enhanceCodeBlocks();
160+
161+
// Also run after a short delay to catch dynamically loaded content
162+
const timeoutId = setTimeout(enhanceCodeBlocks, 100);
163+
164+
// Use MutationObserver to watch for new code blocks
165+
const observer = new MutationObserver(() => {
166+
enhanceCodeBlocks();
167+
});
168+
169+
// Observe the document body for changes
170+
observer.observe(document.body, {
171+
childList: true,
172+
subtree: true,
173+
});
174+
175+
return () => {
176+
clearTimeout(timeoutId);
177+
observer.disconnect();
178+
};
179+
}, []);
180+
181+
return null;
182+
}
183+

docs/content/_meta.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default {
2+
index: 'Home',
3+
'getting-started': 'Getting Started',
4+
};

0 commit comments

Comments
 (0)