Marketing improvements 3 (#3175)
* Improve marketing website * User guide with icons * Add TOC * Linter * Basic GraphQL playground * Very basic contributors page * Failed attempt to integrate REST playground * Yarn * Begin contributors DB * Improve contributors page
This commit is contained in:
@ -1,11 +0,0 @@
|
||||
import { getPost } from "@/app/user-guide/get-posts";
|
||||
|
||||
export default async function BlogPost({ params }: { params: { slug: string[] } }) {
|
||||
const post = await getPost(params.slug as string[]);
|
||||
console.log(post);
|
||||
|
||||
return <div>
|
||||
<h1>{post?.itemInfo.title}</h1>
|
||||
<div>{post?.content}</div>
|
||||
</div>;
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
import { ContentContainer } from '@/app/components/ContentContainer';
|
||||
import { getPosts, Directory, FileContent } from '@/app/get-posts';
|
||||
import Link from 'next/link';
|
||||
import * as TablerIcons from '@tabler/icons-react';
|
||||
import { FunctionComponent } from 'react';
|
||||
|
||||
function loadIcon(iconName?: string) {
|
||||
const name = iconName ? iconName : 'IconCategory';
|
||||
|
||||
try {
|
||||
const icon = TablerIcons[
|
||||
name as keyof typeof TablerIcons
|
||||
] as FunctionComponent;
|
||||
return icon as TablerIcons.Icon;
|
||||
} catch (error) {
|
||||
console.error('Icon not found:', iconName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const DirectoryItem = ({
|
||||
name,
|
||||
item,
|
||||
}: {
|
||||
name: string;
|
||||
item: Directory | FileContent;
|
||||
}) => {
|
||||
if ('content' in item) {
|
||||
// If the item is a file, we render a link.
|
||||
const Icon = loadIcon(item.itemInfo.icon);
|
||||
|
||||
return (
|
||||
<div key={name}>
|
||||
<Link
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
color: '#333',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
}}
|
||||
href={
|
||||
item.itemInfo.path != 'user-guide/home'
|
||||
? `/user-guide/${item.itemInfo.path}`
|
||||
: '/user-guide'
|
||||
}
|
||||
>
|
||||
{Icon ? <Icon size={12} /> : ''}
|
||||
{item.itemInfo.title}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// If the item is a directory, we render the title and the items in the directory.
|
||||
return (
|
||||
<div key={name}>
|
||||
<h4 style={{ textTransform: 'uppercase', color: '#B3B3B3' }}>
|
||||
{item.itemInfo.title}
|
||||
</h4>
|
||||
{Object.entries(item).map(([childName, childItem]) => {
|
||||
if (childName !== 'itemInfo') {
|
||||
return (
|
||||
<DirectoryItem
|
||||
key={childName}
|
||||
name={childName}
|
||||
item={childItem as Directory | FileContent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default async function UserGuideHome({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const basePath = '/src/content/user-guide';
|
||||
|
||||
const posts = await getPosts(basePath);
|
||||
|
||||
return (
|
||||
<ContentContainer>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<div
|
||||
style={{
|
||||
borderRight: '1px solid rgba(20, 20, 20, 0.08)',
|
||||
paddingRight: '24px',
|
||||
minWidth: '200px',
|
||||
paddingTop: '48px',
|
||||
}}
|
||||
>
|
||||
{posts['home.mdx'] && (
|
||||
<DirectoryItem
|
||||
name="home"
|
||||
item={posts['home.mdx'] as FileContent}
|
||||
/>
|
||||
)}
|
||||
{Object.entries(posts).map(([name, item]) => {
|
||||
if (name !== 'itemInfo' && name != 'home.mdx') {
|
||||
return (
|
||||
<DirectoryItem
|
||||
key={name}
|
||||
name={name}
|
||||
item={item as Directory | FileContent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
<div style={{ paddingLeft: '24px', paddingRight: '200px' }}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</ContentContainer>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
import { getPost } from '@/app/get-posts';
|
||||
|
||||
export default async function UserGuideHome({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string[] };
|
||||
}) {
|
||||
const basePath = '/src/content/user-guide';
|
||||
|
||||
const mainPost = await getPost(
|
||||
params.slug && params.slug.length ? params.slug : ['home'],
|
||||
basePath,
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>{mainPost?.itemInfo.title}</h2>
|
||||
<div>{mainPost?.content}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,97 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { compileMDX } from 'next-mdx-remote/rsc';
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
interface ItemInfo {
|
||||
title: string;
|
||||
position?: number;
|
||||
path: string;
|
||||
type: 'file' | 'directory';
|
||||
}
|
||||
|
||||
export interface FileContent {
|
||||
content: ReactElement;
|
||||
itemInfo: ItemInfo;
|
||||
}
|
||||
|
||||
export interface Directory {
|
||||
[key: string]: FileContent | Directory | ItemInfo;
|
||||
itemInfo: ItemInfo;
|
||||
}
|
||||
|
||||
const basePath = '/src/content/user-guide';
|
||||
|
||||
|
||||
async function getFiles(filePath: string, position: number = 0): Promise<Directory> {
|
||||
const entries = fs.readdirSync(filePath, { withFileTypes: true });
|
||||
|
||||
const urlpath = path.toString().split(basePath);
|
||||
const pathName = urlpath.length > 1 ? urlpath[1] : path.basename(filePath);
|
||||
console.log(pathName);
|
||||
|
||||
const directory: Directory = {
|
||||
itemInfo: {
|
||||
title: path.basename(filePath),
|
||||
position,
|
||||
type: 'directory',
|
||||
path: pathName,
|
||||
},
|
||||
};
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
directory[entry.name] = await getFiles(path.join(filePath, entry.name), position++);
|
||||
} else if (entry.isFile() && path.extname(entry.name) === '.mdx') {
|
||||
const fileContent = fs.readFileSync(path.join(filePath, entry.name), 'utf8');
|
||||
const { content, frontmatter } = await compileMDX<{ title: string, position?: number }>({ source: fileContent, options: { parseFrontmatter: true } });
|
||||
directory[entry.name] = { content, itemInfo: {...frontmatter, type: 'file', path: pathName + "/" + entry.name.replace(/\.mdx$/, '')} };
|
||||
}
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
async function parseFrontMatterAndCategory(directory: Directory, dirPath: string): Promise<Directory> {
|
||||
const parsedDirectory: Directory = {
|
||||
itemInfo: directory.itemInfo,
|
||||
};
|
||||
|
||||
for (const entry in directory) {
|
||||
if (entry !== 'itemInfo' && directory[entry] instanceof Object) {
|
||||
parsedDirectory[entry] = await parseFrontMatterAndCategory(directory[entry] as Directory, path.join(dirPath, entry));
|
||||
}
|
||||
}
|
||||
|
||||
const categoryPath = path.join(dirPath, '_category_.json');
|
||||
|
||||
if (fs.existsSync(categoryPath)) {
|
||||
const categoryJson: ItemInfo = JSON.parse(fs.readFileSync(categoryPath, 'utf8'));
|
||||
parsedDirectory.itemInfo = categoryJson;
|
||||
}
|
||||
|
||||
return parsedDirectory;
|
||||
}
|
||||
|
||||
export async function getPosts(): Promise<Directory> {
|
||||
const postsDirectory = path.join(process.cwd(), basePath);
|
||||
const directory = await getFiles(postsDirectory);
|
||||
return parseFrontMatterAndCategory(directory, postsDirectory);
|
||||
}
|
||||
|
||||
export async function getPost(slug: string[]): Promise<FileContent | null> {
|
||||
const postsDirectory = path.join(process.cwd(), basePath);
|
||||
const modifiedSlug = slug.join('/');
|
||||
const filePath = path.join(postsDirectory, `${modifiedSlug}.mdx`);
|
||||
|
||||
console.log(filePath);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fileContent = fs.readFileSync(filePath, 'utf8');
|
||||
const { content, frontmatter } = await compileMDX<{ title: string, position?: number }>({ source: fileContent, options: { parseFrontmatter: true } });
|
||||
|
||||
return { content, itemInfo: {...frontmatter, type: 'file', path: modifiedSlug }};
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
import { ContentContainer } from '@/app/components/ContentContainer';
|
||||
import { getPosts, Directory, FileContent } from '@/app/user-guide/get-posts';
|
||||
import Link from 'next/link';
|
||||
|
||||
|
||||
const DirectoryItem = ({ name, item }: { name: string, item: Directory | FileContent }) => {
|
||||
if ('content' in item) {
|
||||
// If the item is a file, we render a link.
|
||||
return (
|
||||
<div key={name}>
|
||||
<Link href={`/user-guide/${item.itemInfo.path}`}>
|
||||
{item.itemInfo.title}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// If the item is a directory, we render the title and the items in the directory.
|
||||
return (
|
||||
<div key={name}>
|
||||
<h2>{item.itemInfo.title}</h2>
|
||||
{Object.entries(item).map(([childName, childItem]) => {
|
||||
if (childName !== 'itemInfo') {
|
||||
return <DirectoryItem key={childName} name={childName} item={childItem as Directory | FileContent} />;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default async function BlogHome() {
|
||||
|
||||
const posts = await getPosts();
|
||||
|
||||
|
||||
return <ContentContainer>
|
||||
<h1>User Guide</h1>
|
||||
<div>
|
||||
{Object.entries(posts).map(([name, item]) => {
|
||||
if (name !== 'itemInfo') {
|
||||
return <DirectoryItem key={name} name={name} item={item as Directory | FileContent} />;
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</ContentContainer>;
|
||||
}
|
||||
Reference in New Issue
Block a user