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:
Félix Malfait
2023-12-29 11:17:32 +01:00
committed by GitHub
parent fa8a04743c
commit c422045ea6
46 changed files with 7589 additions and 687 deletions

View File

@ -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>;
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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 }};
}

View File

@ -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>;
}