mirror of
https://github.com/DefectingCat/DefectingCat.github.io
synced 2025-07-15 16:51:37 +00:00
Add app directory
This commit is contained in:
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@ -1,3 +1,5 @@
|
||||
{
|
||||
"svn.ignoreMissingSvnWarning": true
|
||||
}
|
||||
"svn.ignoreMissingSvnWarning": true,
|
||||
"typescript.tsdk": "node_modules/.pnpm/typescript@5.0.2/node_modules/typescript/lib",
|
||||
"typescript.enablePromptUseWorkspaceTsdk": true
|
||||
}
|
29
app/layout.tsx
Normal file
29
app/layout.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import 'styles/globals.css';
|
||||
import RUAThemeProvider from './theme-provider';
|
||||
|
||||
export const metadata = {
|
||||
title: 'RUA',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||
<link
|
||||
rel="preconnect"
|
||||
href="https://ZUYZBUJBQW-dsn.algolia.net"
|
||||
crossOrigin=""
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<RUAThemeProvider>{children}</RUAThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
32
app/page.tsx
Normal file
32
app/page.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import clsx from 'clsx';
|
||||
import { gltfLoader, manager } from 'lib/gltf-loader';
|
||||
import { getMousePosition } from 'lib/utils';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Head from 'next/head';
|
||||
import Image from 'next/image';
|
||||
import { Suspense, useCallback } from 'react';
|
||||
import { InitFn, THREE, useThree } from 'rua-three';
|
||||
import styles from 'styles/index/index.module.css';
|
||||
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<main className="h-[calc(100vh-142px)] flex justify-center items-center text-xl">
|
||||
<div className="z-0 flex flex-col w-full h-full max-w-4xl px-4 py-32 text-2xl">
|
||||
<h1 className="flex pb-4 text-5xl">
|
||||
<span className={clsx('font-Aleo font-semibold', styles.gradient)}>
|
||||
Hi there
|
||||
</span>
|
||||
<span className="ml-3">
|
||||
<Image
|
||||
src="/images/img/hands.svg"
|
||||
alt="hands"
|
||||
width={36}
|
||||
height={36}
|
||||
/>
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
22
app/theme-provider.tsx
Normal file
22
app/theme-provider.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export default function RUAThemeProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
storageKey="rua-theme"
|
||||
themes={['light', 'dark']}
|
||||
enableSystem
|
||||
defaultTheme="system"
|
||||
>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
import clsx from 'clsx';
|
||||
|
||||
type Props = {
|
||||
children: React.ReactElement | React.ReactElement[];
|
||||
};
|
||||
|
||||
const BlogList = ({ children }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<h1
|
||||
className={clsx(
|
||||
'text-5xl font-bold text-center font-Barlow',
|
||||
'mt-8 mb-20 text-gray-800 dark:text-gray-200'
|
||||
)}
|
||||
>
|
||||
Blog posts
|
||||
</h1>
|
||||
|
||||
<div className="px-4 lg:px-0">{children}</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlogList;
|
@ -1,22 +0,0 @@
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
const Footer = dynamic(() => import('components/footer'));
|
||||
const HeadBar = dynamic(() => import('components/nav-bar'));
|
||||
const BackToTop = dynamic(() => import('components/common/back-to-top'));
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const MainLayout = ({ children }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<HeadBar />
|
||||
{children}
|
||||
<Footer />
|
||||
<BackToTop />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainLayout;
|
@ -11,7 +11,8 @@ const nextConfig = {
|
||||
images: isExport ? { unoptimized: true } : {},
|
||||
experimental: {
|
||||
// runtime: 'experimental-edge',
|
||||
largePageDataBytes: 512 * 1000,
|
||||
// largePageDataBytes: 512 * 1000,
|
||||
appDir: true,
|
||||
},
|
||||
compiler: {
|
||||
removeConsole: process.env.NODE_ENV === 'production',
|
||||
|
40
package.json
40
package.json
@ -14,20 +14,20 @@
|
||||
"pretty": "prettier --write \"./**/*.{js,jsx,ts,tsx,json,md,mdx,css}\" --ignore-unknown"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codesandbox/sandpack-react": "^1.20.6",
|
||||
"@codesandbox/sandpack-react": "^2.1.9",
|
||||
"@docsearch/react": "3",
|
||||
"@giscus/react": "^2.2.6",
|
||||
"@giscus/react": "^2.2.8",
|
||||
"@mapbox/rehype-prism": "^0.8.0",
|
||||
"@tweenjs/tween.js": "^18.6.4",
|
||||
"algoliasearch": "^4.14.3",
|
||||
"algoliasearch": "^4.15.0",
|
||||
"dayjs": "^1.11.7",
|
||||
"next": "13.1.6",
|
||||
"next-mdx-remote": "^4.3.0",
|
||||
"next": "13.2.4",
|
||||
"next-mdx-remote": "^4.4.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"octokit": "^2.0.14",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.7.1",
|
||||
"react-icons": "^4.8.0",
|
||||
"rehype-react": "^7.1.2",
|
||||
"rehype-slug": "^5.1.0",
|
||||
"remark-frontmatter": "^4.0.1",
|
||||
@ -37,29 +37,29 @@
|
||||
"rua-three": "^1.1.1",
|
||||
"sharp": "^0.31.3",
|
||||
"stats.js": "^0.17.0",
|
||||
"three": "^0.149.0",
|
||||
"three": "^0.150.1",
|
||||
"unified": "^10.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/node": "18.11.18",
|
||||
"@types/react": "18.0.27",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/node": "18.15.3",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/stats.js": "^0.17.0",
|
||||
"@types/three": "^0.148.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"@types/three": "^0.149.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"clsx": "^1.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.0.3",
|
||||
"eslint": "8.33.0",
|
||||
"eslint-config-next": "13.1.6",
|
||||
"eslint": "8.36.0",
|
||||
"eslint-config-next": "13.2.4",
|
||||
"gray-matter": "^4.0.3",
|
||||
"jest": "^29.4.1",
|
||||
"jest-environment-jsdom": "^29.4.1",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"postcss": "^8.4.21",
|
||||
"prettier": "^2.8.3",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"typescript": "4.9.4"
|
||||
"prettier": "^2.8.5",
|
||||
"tailwindcss": "^3.2.7",
|
||||
"typescript": "5.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
import useRouterLoading from 'lib/hooks/use-router-loading';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Head from 'next/head';
|
||||
import { Suspense, useEffect } from 'react';
|
||||
import 'styles/globals.css';
|
||||
import 'styles/prism-one-dark.css';
|
||||
import 'styles/prism-one-light.css';
|
||||
import 'styles/rua.css';
|
||||
import { AppPropsWithLayout } from 'types';
|
||||
|
||||
const VercelLoading = dynamic(
|
||||
() => import('components/rua/loading/vercel-loading'),
|
||||
{
|
||||
suspense: true,
|
||||
}
|
||||
);
|
||||
|
||||
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
const getLayout = Component.getLayout ?? ((page) => page);
|
||||
|
||||
const { loading } = useRouterLoading();
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.transition = 'all 0.3s ease-out';
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="shortcut icon" href="/images/favicon.ico" />
|
||||
<meta name="keywords" content="Blog RUA" />
|
||||
<meta name="description" content="Personal blog." />
|
||||
<meta name="author" content="Arthur,i@rua.plus" />
|
||||
<title>RUA</title>
|
||||
</Head>
|
||||
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
storageKey="rua-theme"
|
||||
themes={['light', 'dark']}
|
||||
enableSystem
|
||||
defaultTheme="system"
|
||||
>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</ThemeProvider>
|
||||
|
||||
<Suspense fallback>{loading && <VercelLoading />}</Suspense>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default MyApp;
|
@ -1,30 +0,0 @@
|
||||
import { Head, Html, Main, NextScript } from 'next/document';
|
||||
import { getSandpackCssText } from '@codesandbox/sandpack-react';
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Aleo&family=Aref+Ruqaa&family=Barlow:ital@0;1&family=JetBrains+Mono:ital,wght@0,300;0,400;0,500;0,600;1,300;1,400;1,500;1,600&family=Poppins:ital@0;1&display=swap"
|
||||
rel="stylesheet"
|
||||
></link>
|
||||
<link
|
||||
rel="preconnect"
|
||||
href="https://ZUYZBUJBQW-dsn.algolia.net"
|
||||
crossOrigin=""
|
||||
/>
|
||||
<style
|
||||
dangerouslySetInnerHTML={{ __html: getSandpackCssText() }}
|
||||
id="sandpack"
|
||||
/>
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
207
pages/about.tsx
207
pages/about.tsx
@ -1,207 +0,0 @@
|
||||
import TWEEN from '@tweenjs/tween.js';
|
||||
import clsx from 'clsx';
|
||||
import MainLayout from 'layouts/common/main-layout';
|
||||
import { gltfLoader, manager } from 'lib/gltf-loader';
|
||||
import { getMousePosition } from 'lib/utils';
|
||||
import { useTheme } from 'next-themes';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Suspense, useEffect, useRef, useState } from 'react';
|
||||
import { InitFn, THREE, useThree } from 'rua-three';
|
||||
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { NextPageWithLayout } from 'types';
|
||||
|
||||
const Loading = dynamic(() => import('components/rua/loading/rua-loading'), {
|
||||
suspense: true,
|
||||
});
|
||||
|
||||
const rotationY = 0.4;
|
||||
const rotationX = 0.2;
|
||||
|
||||
const About: NextPageWithLayout = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showLoading, setShowLoading] = useState(true);
|
||||
manager.onLoad = () => {
|
||||
setLoading(false);
|
||||
setTimeout(() => {
|
||||
setShowLoading(false);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
// After model loading, set theme to dark mode.
|
||||
const restore = useRef(false);
|
||||
const mounted = useRef(false);
|
||||
const { systemTheme, theme, setTheme } = useTheme();
|
||||
const currentTheme = theme === 'system' ? systemTheme : theme;
|
||||
// setDarkMode is async called by setTimeout, when component is unmounted
|
||||
// it should not be called.
|
||||
const setDarkMode = () => {
|
||||
if (!showLoading) return;
|
||||
if (currentTheme === 'dark') return;
|
||||
if (!mounted.current) return;
|
||||
restore.current = true;
|
||||
document.body.style.transition = 'all 1.2s ease-out';
|
||||
setTheme('dark');
|
||||
};
|
||||
useEffect(
|
||||
() => {
|
||||
mounted.current = true;
|
||||
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
if (!restore.current) return;
|
||||
setTheme('light');
|
||||
document.body.style.transition = 'all 0.3s ease-out';
|
||||
};
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
|
||||
const init: InitFn = ({
|
||||
scene,
|
||||
controls,
|
||||
camera,
|
||||
isOrbitControls,
|
||||
frameArea,
|
||||
isPerspectiveCamera,
|
||||
addRenderCallback,
|
||||
addWindowEvent,
|
||||
}) => {
|
||||
scene.add(new THREE.AmbientLight(0xffffff, 0.8));
|
||||
|
||||
if (isOrbitControls(controls)) {
|
||||
controls.enablePan = false;
|
||||
controls.enableZoom = false;
|
||||
controls.enableRotate = false;
|
||||
}
|
||||
|
||||
const handleLoad = (gltf: GLTF) => {
|
||||
const root = gltf.scene;
|
||||
scene.add(root);
|
||||
|
||||
const clock = new THREE.Clock();
|
||||
const mixer = new THREE.AnimationMixer(root);
|
||||
gltf.animations.forEach((clip) => {
|
||||
mixer.clipAction(clip).play();
|
||||
});
|
||||
addRenderCallback(() => {
|
||||
mixer.update(clock.getDelta());
|
||||
});
|
||||
|
||||
const box = new THREE.Box3().setFromObject(root);
|
||||
const boxSize = box.getSize(new THREE.Vector3()).length();
|
||||
const boxCenter = box.getCenter(new THREE.Vector3());
|
||||
|
||||
if (isPerspectiveCamera(camera)) {
|
||||
frameArea(boxSize, boxSize, boxCenter, camera);
|
||||
}
|
||||
controls.target.copy(boxCenter);
|
||||
controls.update();
|
||||
|
||||
// Rotate 180 degress
|
||||
root.rotation.y = Math.PI * 2;
|
||||
|
||||
// Enter animation
|
||||
const entryValue = {
|
||||
rotationY: root.rotation.y,
|
||||
meshY: root.position.y,
|
||||
cameraY: camera.position.y,
|
||||
z: camera.position.z,
|
||||
};
|
||||
const enter = new TWEEN.Tween(entryValue)
|
||||
.to(
|
||||
{
|
||||
rotationY: 0,
|
||||
meshY: entryValue.meshY - 1,
|
||||
cameraY: entryValue.cameraY + 0.5,
|
||||
z: entryValue.z - 16,
|
||||
},
|
||||
1200
|
||||
)
|
||||
.onUpdate((obj) => {
|
||||
// root.rotation.y = obj.rotationY;
|
||||
root.position.y = obj.meshY;
|
||||
camera.position.y = obj.cameraY;
|
||||
camera.position.z = obj.z;
|
||||
})
|
||||
.easing(TWEEN.Easing.Circular.Out)
|
||||
.onComplete(() => {
|
||||
document.body.style.transition = 'all 0.3s ease-out';
|
||||
});
|
||||
setTimeout(() => {
|
||||
enter.start();
|
||||
setDarkMode();
|
||||
}, 1000);
|
||||
|
||||
// Render animation
|
||||
addRenderCallback((time) => {
|
||||
TWEEN.update(time / 0.001);
|
||||
});
|
||||
|
||||
const halfWidth = Math.floor(window.innerWidth / 2);
|
||||
const halfHeight = Math.floor(window.innerHeight / 2);
|
||||
|
||||
const updateMousePosition = (e: MouseEvent | globalThis.TouchEvent) => {
|
||||
const { x, y } = getMousePosition(e);
|
||||
// > 0 is right, < 0 is left
|
||||
// if (directionX > 0) root.rotation.y += 0.01;
|
||||
root.rotation.y = rotationY * ((x - halfWidth) / halfWidth);
|
||||
root.rotation.x = rotationX * ((y - halfHeight) / halfHeight);
|
||||
};
|
||||
|
||||
addWindowEvent('mousemove', updateMousePosition, {
|
||||
passive: true,
|
||||
});
|
||||
addWindowEvent('touchmove', updateMousePosition, {
|
||||
passive: true,
|
||||
});
|
||||
};
|
||||
|
||||
gltfLoader.load('./models/cloud_station/modelDraco.gltf', handleLoad);
|
||||
};
|
||||
|
||||
const { ref } = useThree({
|
||||
init,
|
||||
alpha: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed top-0 left-0 -z-10">
|
||||
<canvas ref={ref} className="w-full h-full"></canvas>
|
||||
<Suspense fallback>
|
||||
{showLoading && (
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute top-0 left-0',
|
||||
'items-center flex justify-center',
|
||||
'w-full h-full transition-all duration-500',
|
||||
'bg-white dark:bg-rua-gray-900',
|
||||
loading ? 'opacity-1' : 'opacity-0'
|
||||
)}
|
||||
>
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
<main className="h-[calc(100vh-142px)] flex flex-col">
|
||||
<div
|
||||
className={clsx(
|
||||
'flex max-w-3xl',
|
||||
'px-10 py-4 mx-auto lg:px-0 lg:py-10'
|
||||
)}
|
||||
>
|
||||
<h1 className="text-5xl font-semibold font-Barlow">About</h1>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
About.getLayout = function getLayout(page) {
|
||||
return <MainLayout>{page}</MainLayout>;
|
||||
};
|
||||
|
||||
export default About;
|
@ -1,13 +0,0 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
type Data = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export default function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Data>
|
||||
) {
|
||||
res.status(200).json({ name: 'John Doe' });
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { Post } from 'types';
|
||||
import { postLists } from 'lib/posts';
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<Post[]>
|
||||
) {
|
||||
const getPosts = async () => {
|
||||
const posts = await postLists();
|
||||
res.status(200).json(posts);
|
||||
};
|
||||
|
||||
switch (req.method) {
|
||||
case 'GET':
|
||||
return getPosts();
|
||||
default:
|
||||
return res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
import PostCardLoading from 'components/rua/loading/post-card-loading';
|
||||
import MainLayout from 'layouts/common/main-layout';
|
||||
import { getPostListPath, postLists, PostPerPage } from 'lib/posts';
|
||||
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Fragment, ReactElement, Suspense } from 'react';
|
||||
import { Post } from 'types';
|
||||
|
||||
const PostCard = dynamic(() => import('components/post-card'), {
|
||||
suspense: true,
|
||||
});
|
||||
const BlogList = dynamic(() => import('layouts/common/blog-list'), {
|
||||
suspense: true,
|
||||
});
|
||||
const Pagination = dynamic(() => import('components/rua/rua-pagination'), {
|
||||
suspense: true,
|
||||
});
|
||||
|
||||
const BlogPage = ({
|
||||
posts,
|
||||
prev,
|
||||
next,
|
||||
total,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
return (
|
||||
<>
|
||||
<main className="max-w-4xl mx-auto">
|
||||
<Suspense fallback>
|
||||
<BlogList>
|
||||
{posts.map((post) => (
|
||||
<Fragment key={post.slug}>
|
||||
<Suspense fallback={<PostCardLoading />}>
|
||||
<PostCard post={post} />
|
||||
</Suspense>
|
||||
</Fragment>
|
||||
))}
|
||||
</BlogList>
|
||||
</Suspense>
|
||||
|
||||
<Suspense fallback>
|
||||
<Pagination
|
||||
className="py-6 mt-4 px-7 lg:px-5"
|
||||
hasPrev={!!prev}
|
||||
hasNext={next <= total}
|
||||
prevLink={prev === 1 ? '/blog' : `/blog/${prev}`}
|
||||
nextLink={`/blog/${next}`}
|
||||
current={next - 1}
|
||||
total={total}
|
||||
/>
|
||||
</Suspense>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
return {
|
||||
paths: await getPostListPath(),
|
||||
fallback: false,
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<{
|
||||
posts: Post[];
|
||||
prev: number;
|
||||
next: number;
|
||||
total: number;
|
||||
}> = async ({ params }) => {
|
||||
const page = Number(params?.page);
|
||||
if (!page) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
const posts = await postLists();
|
||||
|
||||
return {
|
||||
props: {
|
||||
posts: posts.slice((page - 1) * PostPerPage, PostPerPage * page),
|
||||
prev: page - 1,
|
||||
next: page + 1,
|
||||
total: Math.ceil(posts.length / PostPerPage),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
BlogPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <MainLayout>{page}</MainLayout>;
|
||||
};
|
||||
|
||||
export default BlogPage;
|
@ -1,75 +0,0 @@
|
||||
import PostCardLoading from 'components/rua/loading/post-card-loading';
|
||||
import MainLayout from 'layouts/common/main-layout';
|
||||
import { postLists, PostPerPage } from 'lib/posts';
|
||||
import { GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Fragment, ReactElement, Suspense } from 'react';
|
||||
import { Post } from 'types';
|
||||
|
||||
const PostCard = dynamic(() => import('components/post-card'), {
|
||||
suspense: true,
|
||||
});
|
||||
const BlogList = dynamic(() => import('layouts/common/blog-list'), {
|
||||
suspense: true,
|
||||
});
|
||||
const Pagination = dynamic(() => import('components/rua/rua-pagination'), {
|
||||
suspense: true,
|
||||
});
|
||||
|
||||
const Blog = ({
|
||||
posts,
|
||||
next,
|
||||
total,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
return (
|
||||
<>
|
||||
<main className="max-w-4xl mx-auto">
|
||||
<Suspense fallback>
|
||||
<BlogList>
|
||||
{posts.map((post) => (
|
||||
<Fragment key={post.slug}>
|
||||
<Suspense fallback={<PostCardLoading />}>
|
||||
<PostCard post={post} />
|
||||
</Suspense>
|
||||
</Fragment>
|
||||
))}
|
||||
</BlogList>
|
||||
</Suspense>
|
||||
|
||||
<Suspense fallback>
|
||||
<Pagination
|
||||
className="py-6 mt-4 px-7 lg:px-5"
|
||||
hasPrev={false}
|
||||
hasNext={next <= total}
|
||||
prevLink={''}
|
||||
nextLink={`/blog/${next}`}
|
||||
current={1}
|
||||
total={total}
|
||||
/>
|
||||
</Suspense>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<{
|
||||
posts: Post[];
|
||||
next: number;
|
||||
total: number;
|
||||
}> = async () => {
|
||||
const posts = await postLists();
|
||||
return {
|
||||
props: {
|
||||
// Latest posts.
|
||||
posts: posts.slice(0, PostPerPage),
|
||||
next: 2,
|
||||
total: Math.ceil(posts.length / PostPerPage),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Blog.getLayout = function getLayout(page: ReactElement) {
|
||||
return <MainLayout>{page}</MainLayout>;
|
||||
};
|
||||
|
||||
export default Blog;
|
132
pages/g/[id].tsx
132
pages/g/[id].tsx
@ -1,132 +0,0 @@
|
||||
import LinkAnchor from 'components/mdx/link-anchor';
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import MainLayout from 'layouts/common/main-layout';
|
||||
import { getSignalGist, SingalGist } from 'lib/fetcher';
|
||||
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import avatar from 'public/images/img/avatar.svg';
|
||||
import { Fragment, ReactElement, Suspense } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const GistsCode = dynamic(() => import('components/gists/gists-code'), {
|
||||
suspense: true,
|
||||
});
|
||||
const GistsSkeleton = dynamic(() => import('components/gists/gists-skeleton'), {
|
||||
suspense: true,
|
||||
});
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const Gist = ({ gist }: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
const router = useRouter();
|
||||
|
||||
if (router.isFallback) {
|
||||
return (
|
||||
<Suspense fallback>
|
||||
<GistsSkeleton />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="max-w-5xl px-4 mx-auto lg:px-0">
|
||||
<div className="pb-4 text-sm">
|
||||
<div className="flex items-center py-1 ">
|
||||
<Image
|
||||
src={avatar}
|
||||
alt="Avatar"
|
||||
priority
|
||||
width={32}
|
||||
height={32}
|
||||
className="rounded-full "
|
||||
/>
|
||||
<h1 className="ml-2 overflow-hidden text-xl whitespace-nowrap overflow-ellipsis">
|
||||
<Link href="/gists">
|
||||
<LinkAnchor external={false}>{gist.login}</LinkAnchor>
|
||||
</Link>
|
||||
/{Object.keys(gist.files)[0]}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<p className="pl-10 text-gray-400 ">
|
||||
Last active: {dayjs(gist.updated_at).fromNow()}
|
||||
</p>
|
||||
|
||||
<div className="py-4">
|
||||
<p className="pb-2 text-lg text-gray-500">{gist.description}</p>
|
||||
|
||||
{Object.keys(gist.files).map((f) => (
|
||||
<Fragment key={gist.files[f].raw_url}>
|
||||
<Suspense fallback>
|
||||
<GistsCode file={gist.files[f]} showFileName />
|
||||
</Suspense>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
// const result = await getGists();
|
||||
// const last = Number(result?.pageSize.last);
|
||||
// const paths: (
|
||||
// | string
|
||||
// | {
|
||||
// params: ParsedUrlQuery;
|
||||
// locale?: string | undefined;
|
||||
// }
|
||||
// )[] = [];
|
||||
// for (let i = 1; i <= last; i++) {
|
||||
// const result = await getGists(i);
|
||||
// paths.push(...(result?.gists.map((g) => ({ params: { id: g.id } })) ?? []));
|
||||
// }
|
||||
|
||||
return {
|
||||
paths: [],
|
||||
fallback: true,
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<{
|
||||
id: string | undefined;
|
||||
gist: SingalGist;
|
||||
}> = async ({ params }) => {
|
||||
if (typeof params?.id !== 'string') {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const gist = await getSignalGist(params.id);
|
||||
if (!gist || !gist.files) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
return {
|
||||
props: {
|
||||
id: params?.id?.toString(),
|
||||
gist,
|
||||
},
|
||||
revalidate: 600,
|
||||
};
|
||||
} catch (err) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Gist.getLayout = function getLayout(page: ReactElement) {
|
||||
return <MainLayout>{page}</MainLayout>;
|
||||
};
|
||||
|
||||
export default Gist;
|
@ -1,125 +0,0 @@
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import MainLayout from 'layouts/common/main-layout';
|
||||
import { GetGists, getGists, GetUser, getUser } from 'lib/fetcher';
|
||||
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { ReactElement, Suspense } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const UserInfo = dynamic(() => import('components/gists/user-info'), {
|
||||
suspense: true,
|
||||
});
|
||||
const FileContent = dynamic(() => import('components/gists/file-content'), {
|
||||
suspense: true,
|
||||
});
|
||||
const Pagination = dynamic(() => import('components/rua/rua-pagination'), {
|
||||
suspense: true,
|
||||
});
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const Gists = ({
|
||||
gists,
|
||||
user,
|
||||
prev,
|
||||
next,
|
||||
total,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
const router = useRouter();
|
||||
|
||||
if (router.isFallback) {
|
||||
return <>Loading...</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<main className="max-w-5xl px-4 mx-auto lg:px-0">
|
||||
<div className="md:flex">
|
||||
<Suspense fallback>
|
||||
<UserInfo user={user} />
|
||||
</Suspense>
|
||||
|
||||
<div className="flex-1 px-1 py-4 overflow-hidden md:pl-8">
|
||||
<Suspense fallback>
|
||||
<FileContent gists={gists.gists} />
|
||||
<Pagination
|
||||
className="mt-4"
|
||||
hasPrev={!!prev}
|
||||
hasNext={!!next}
|
||||
prevLink={prev === 1 ? `/gists/` : `/gists/${prev}`}
|
||||
nextLink={`/gists/${next}`}
|
||||
current={prev == null ? next - 1 : prev + 1}
|
||||
total={total}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
// const result = await getGists();
|
||||
// const next = Number(result?.pageSize.next);
|
||||
// const last = Number(result?.pageSize.last);
|
||||
// const paths: (
|
||||
// | string
|
||||
// | {
|
||||
// params: ParsedUrlQuery;
|
||||
// locale?: string | undefined;
|
||||
// }
|
||||
// )[] = [];
|
||||
// for (let i = next; i <= last; i++) {
|
||||
// paths.push({
|
||||
// params: {
|
||||
// p: i.toString(),
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
return {
|
||||
paths: [],
|
||||
fallback: true,
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<{
|
||||
gists: GetGists;
|
||||
user: GetUser;
|
||||
prev: number;
|
||||
next: number;
|
||||
total: number;
|
||||
}> = async ({ params }) => {
|
||||
if (typeof params?.p !== 'string') {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await getGists(Number(params?.p));
|
||||
if (!result) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const user = await getUser();
|
||||
return {
|
||||
props: {
|
||||
gists: result,
|
||||
user,
|
||||
prev: Number(result.pageSize.prev),
|
||||
next: Number(result.pageSize.next),
|
||||
total: Number(result.pageSize.last),
|
||||
},
|
||||
revalidate: 600,
|
||||
};
|
||||
};
|
||||
|
||||
Gists.getLayout = function getLayout(page: ReactElement) {
|
||||
return <MainLayout>{page}</MainLayout>;
|
||||
};
|
||||
|
||||
export default Gists;
|
@ -1,87 +0,0 @@
|
||||
import dayjs from 'dayjs';
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
import MainLayout from 'layouts/common/main-layout';
|
||||
import { GetGists, getGists, GetUser, getUser } from 'lib/fetcher';
|
||||
import { GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { ReactElement, Suspense } from 'react';
|
||||
|
||||
const UserInfo = dynamic(() => import('components/gists/user-info'), {
|
||||
suspense: true,
|
||||
});
|
||||
const FileContent = dynamic(() => import('components/gists/file-content'), {
|
||||
suspense: true,
|
||||
});
|
||||
const Pagination = dynamic(() => import('components/rua/rua-pagination'), {
|
||||
suspense: true,
|
||||
});
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
const Gists = ({
|
||||
gists,
|
||||
user,
|
||||
prev,
|
||||
next,
|
||||
total,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
return (
|
||||
<>
|
||||
<main className="max-w-5xl px-4 mx-auto lg:px-0">
|
||||
<div className="md:flex">
|
||||
<Suspense fallback>
|
||||
<UserInfo user={user} />
|
||||
</Suspense>
|
||||
|
||||
<div className="flex-1 px-1 py-4 overflow-hidden md:pl-8">
|
||||
<Suspense fallback>
|
||||
<FileContent gists={gists.gists} />
|
||||
<Pagination
|
||||
className="mt-4"
|
||||
hasPrev={!!prev}
|
||||
hasNext={!!next}
|
||||
prevLink={prev === 1 ? `/gists/` : `/gists/${prev}`}
|
||||
nextLink={`/gists/${next}`}
|
||||
current={prev == null ? next - 1 : prev + 1}
|
||||
total={total}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<{
|
||||
gists: GetGists;
|
||||
user: GetUser;
|
||||
prev: number;
|
||||
next: number;
|
||||
total: number;
|
||||
}> = async () => {
|
||||
const result = await getGists();
|
||||
if (!result)
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
|
||||
const user = await getUser();
|
||||
|
||||
return {
|
||||
props: {
|
||||
gists: result,
|
||||
user,
|
||||
prev: Number(result.pageSize.prev),
|
||||
next: Number(result.pageSize.next),
|
||||
total: Number(result.pageSize.last),
|
||||
},
|
||||
revalidate: 600,
|
||||
};
|
||||
};
|
||||
|
||||
Gists.getLayout = function getLayout(page: ReactElement) {
|
||||
return <MainLayout>{page}</MainLayout>;
|
||||
};
|
||||
|
||||
export default Gists;
|
187
pages/index.tsx
187
pages/index.tsx
@ -1,187 +0,0 @@
|
||||
import clsx from 'clsx';
|
||||
import MainLayout from 'layouts/common/main-layout';
|
||||
import { gltfLoader, manager } from 'lib/gltf-loader';
|
||||
import { getMousePosition } from 'lib/utils';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Head from 'next/head';
|
||||
import Image from 'next/image';
|
||||
import { Suspense, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { InitFn, THREE, useThree } from 'rua-three';
|
||||
import styles from 'styles/index/index.module.css';
|
||||
import { GLTF } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import type { NextPageWithLayout } from 'types';
|
||||
|
||||
const Loading = dynamic(() => import('components/rua/loading/rua-loading'), {
|
||||
suspense: true,
|
||||
});
|
||||
|
||||
const rotationY = 0.4;
|
||||
const rotationX = 0.18;
|
||||
|
||||
const Home: NextPageWithLayout = () => {
|
||||
const wrapper = useRef<HTMLDivElement>(null);
|
||||
const [size, setSize] = useState({
|
||||
width: 500,
|
||||
height: 300,
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [showLoading, setShowLoading] = useState(true);
|
||||
manager.onLoad = () => {
|
||||
setLoading(false);
|
||||
setTimeout(() => {
|
||||
setShowLoading(false);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const setCanvasSize = useCallback(() => {
|
||||
if (!wrapper.current) return;
|
||||
const width = wrapper.current.clientWidth;
|
||||
const height = wrapper.current.clientHeight;
|
||||
setSize({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
setCanvasSize();
|
||||
window.addEventListener('resize', setCanvasSize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', setCanvasSize);
|
||||
};
|
||||
}, [setCanvasSize]);
|
||||
|
||||
const init: InitFn = ({
|
||||
scene,
|
||||
camera,
|
||||
controls,
|
||||
frameArea,
|
||||
isOrbitControls,
|
||||
isPerspectiveCamera,
|
||||
addWindowEvent,
|
||||
addRenderCallback,
|
||||
}) => {
|
||||
if (isOrbitControls(controls)) {
|
||||
controls.enableRotate = false;
|
||||
controls.enablePan = false;
|
||||
controls.enableZoom = false;
|
||||
controls.minDistance = 1;
|
||||
controls.minPolarAngle = Math.PI * 0.2;
|
||||
controls.maxPolarAngle = Math.PI * 0.5;
|
||||
controls.maxAzimuthAngle = Math.PI * 0.2;
|
||||
}
|
||||
|
||||
const light = new THREE.SpotLight(0xffffff, 2, 100, 15);
|
||||
scene.add(new THREE.AmbientLight(0xffffff, 1));
|
||||
scene.add(light);
|
||||
|
||||
const handleLoad = (gltf: GLTF) => {
|
||||
const root = gltf.scene;
|
||||
scene.add(root);
|
||||
|
||||
const clock = new THREE.Clock();
|
||||
const mixer = new THREE.AnimationMixer(root);
|
||||
gltf.animations.forEach((clip) => {
|
||||
mixer.clipAction(clip).play();
|
||||
});
|
||||
addRenderCallback(() => {
|
||||
mixer.update(clock.getDelta());
|
||||
});
|
||||
|
||||
const box = new THREE.Box3().setFromObject(root);
|
||||
const boxSize = box.getSize(new THREE.Vector3()).length();
|
||||
const boxCenter = box.getCenter(new THREE.Vector3());
|
||||
|
||||
light.target = root;
|
||||
light.position.set(0, 2, 6);
|
||||
light.rotateX(Math.PI * 0.4);
|
||||
isPerspectiveCamera(camera) &&
|
||||
frameArea(boxSize * 0.8, boxSize, boxCenter, camera);
|
||||
|
||||
controls.target.copy(boxCenter);
|
||||
controls.update();
|
||||
root.position.y += 0.1;
|
||||
camera.position.z -= 0.2;
|
||||
|
||||
const halfWidth = Math.floor(window.innerWidth / 2);
|
||||
const halfHeight = Math.floor(window.innerHeight / 2);
|
||||
|
||||
const updateMousePosition = (e: MouseEvent | globalThis.TouchEvent) => {
|
||||
const { x, y } = getMousePosition(e);
|
||||
// > 0 is right, < 0 is left
|
||||
// if (directionX > 0) root.rotation.y += 0.01;
|
||||
root.rotation.y = rotationY * ((x - halfWidth) / halfWidth);
|
||||
root.rotation.x = rotationX * ((y - halfHeight) / halfHeight);
|
||||
};
|
||||
|
||||
addWindowEvent('mousemove', updateMousePosition, {
|
||||
passive: true,
|
||||
});
|
||||
addWindowEvent('touchmove', updateMousePosition, {
|
||||
passive: true,
|
||||
});
|
||||
};
|
||||
|
||||
gltfLoader.load('./models/just_a_hungry_cat/modelDraco.gltf', handleLoad);
|
||||
};
|
||||
const { ref } = useThree({
|
||||
init,
|
||||
...size,
|
||||
alpha: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>RUA - HOME</title>
|
||||
</Head>
|
||||
|
||||
<main className="h-[calc(100vh-142px)] flex justify-center items-center text-xl">
|
||||
<div className="z-0 flex flex-col w-full h-full max-w-4xl px-4 py-32 text-2xl">
|
||||
<h1 className="flex pb-4 text-5xl">
|
||||
<span className={clsx('font-Aleo font-semibold', styles.gradient)}>
|
||||
Hi there
|
||||
</span>
|
||||
<span className="ml-3">
|
||||
<Image
|
||||
src="/images/img/hands.svg"
|
||||
alt="hands"
|
||||
width={36}
|
||||
height={36}
|
||||
/>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<div
|
||||
className="relative flex-1 overflow-hidden rounded-xl"
|
||||
ref={wrapper}
|
||||
>
|
||||
<canvas ref={ref} className="absolute top-0 left-0"></canvas>
|
||||
<Suspense fallback>
|
||||
{showLoading && (
|
||||
<div
|
||||
className={clsx(
|
||||
'absolute top-0 left-0 z-10',
|
||||
'items-center flex justify-center',
|
||||
'w-full h-full transition-all duration-500',
|
||||
'bg-white',
|
||||
loading ? 'opacity-1' : 'opacity-0'
|
||||
)}
|
||||
>
|
||||
<Loading />
|
||||
</div>
|
||||
)}
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Home.getLayout = function getLayout(page) {
|
||||
return <MainLayout>{page}</MainLayout>;
|
||||
};
|
||||
|
||||
export default Home;
|
@ -1,102 +0,0 @@
|
||||
import rehypePrism from '@mapbox/rehype-prism';
|
||||
import components from 'components/mdx/components';
|
||||
import PostCommnetLine from 'components/post/post-commnet-line';
|
||||
import PostToc from 'components/post/post-toc';
|
||||
import data from 'content/mdx-data';
|
||||
import MainLayout from 'layouts/common/main-layout';
|
||||
import useInView from 'lib/hooks/use-in-view';
|
||||
import { allPostsPath, readSinglePost } from 'lib/posts';
|
||||
import { generateToc, SingleToc } from 'lib/utils';
|
||||
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote';
|
||||
import { serialize } from 'next-mdx-remote/serialize';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { ReactElement, Suspense } from 'react';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
const PostComment = dynamic(() => import('components/post/post-comment'), {
|
||||
suspense: true,
|
||||
});
|
||||
|
||||
const Slug = ({
|
||||
mdxSource,
|
||||
toc,
|
||||
tocLength,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
const { ref, inView } = useInView();
|
||||
|
||||
return (
|
||||
<>
|
||||
<main id="article" className="relative max-w-4xl px-4 mx-auto my-10">
|
||||
<h1>{mdxSource.frontmatter?.title}</h1>
|
||||
<time>{mdxSource.frontmatter?.date}</time>
|
||||
<PostToc toc={toc} tocLength={tocLength} />
|
||||
|
||||
<article id="post-content">
|
||||
<MDXRemote {...mdxSource} components={components as {}} />
|
||||
|
||||
<PostCommnetLine />
|
||||
<div ref={ref} className="mt-4">
|
||||
<Suspense fallback>{inView && <PostComment />}</Suspense>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticPaths: GetStaticPaths = async () => {
|
||||
return {
|
||||
paths: await allPostsPath(),
|
||||
fallback: false,
|
||||
};
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<{
|
||||
mdxSource: MDXRemoteSerializeResult;
|
||||
toc: SingleToc[];
|
||||
tocLength: number;
|
||||
}> = async ({ params }) => {
|
||||
const slug = params?.slug?.toString();
|
||||
if (!slug) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const post = await readSinglePost(slug);
|
||||
const toc = generateToc(post);
|
||||
|
||||
const calcLength = (prev: number, cur: SingleToc) => {
|
||||
const childLen = cur.children.length;
|
||||
return childLen ? prev + childLen + 1 : prev + 1;
|
||||
};
|
||||
const tocLength = toc.reduce(calcLength, 0);
|
||||
|
||||
const mdxSource = await serialize(post, {
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkGfm],
|
||||
rehypePlugins: [
|
||||
[rehypePrism, { alias: { vue: 'xml' }, ignoreMissing: true }],
|
||||
rehypeSlug,
|
||||
],
|
||||
},
|
||||
parseFrontmatter: true,
|
||||
scope: data,
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
mdxSource,
|
||||
toc,
|
||||
tocLength,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Slug.getLayout = function getLayout(page: ReactElement) {
|
||||
return <MainLayout>{page}</MainLayout>;
|
||||
};
|
||||
|
||||
export default Slug;
|
@ -1,182 +0,0 @@
|
||||
import clsx from 'clsx';
|
||||
import { GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Fragment, ReactElement, Suspense } from 'react';
|
||||
import {
|
||||
SiGitea,
|
||||
SiNextdotjs,
|
||||
SiRedux,
|
||||
SiThreedotjs,
|
||||
SiTsnode,
|
||||
SiVim,
|
||||
} from 'react-icons/si';
|
||||
import { VscGithubInverted } from 'react-icons/vsc';
|
||||
import { HiPhoto } from 'react-icons/hi2';
|
||||
import MainLayout from 'layouts/common/main-layout';
|
||||
|
||||
const ProjectCard = dynamic(() => import('components/pages/project-card'), {
|
||||
suspense: true,
|
||||
});
|
||||
|
||||
const iconMap = {
|
||||
gitea: <SiGitea />,
|
||||
nextjs: <SiNextdotjs />,
|
||||
github: <VscGithubInverted />,
|
||||
vim: <SiVim />,
|
||||
tsnode: <SiTsnode />,
|
||||
three: <SiThreedotjs />,
|
||||
photos: <HiPhoto />,
|
||||
redux: <SiRedux />,
|
||||
};
|
||||
|
||||
const Projects = ({
|
||||
projects,
|
||||
selfHosts,
|
||||
}: InferGetStaticPropsType<typeof getStaticProps>) => {
|
||||
return (
|
||||
<>
|
||||
<main className="max-w-4xl px-8 py-8 mx-auto lg:px-0">
|
||||
<div>
|
||||
{/* Git projects */}
|
||||
<div>
|
||||
<h1 className="mb-4 text-2xl">Projects</h1>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'grid grid-cols-1 lg:grid-cols-3',
|
||||
'md:grid-cols-2 gap-5'
|
||||
)}
|
||||
>
|
||||
{projects.map((item) => (
|
||||
<Fragment key={item.id}>
|
||||
<Suspense fallback>
|
||||
<ProjectCard
|
||||
icon={iconMap[item.icon ?? 'github']}
|
||||
project={item}
|
||||
/>
|
||||
</Suspense>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6">
|
||||
<div>
|
||||
<h1 className="mb-4 text-2xl">Seft Hosts</h1>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'grid grid-cols-1 lg:grid-cols-3',
|
||||
'md:grid-cols-2 gap-5'
|
||||
)}
|
||||
>
|
||||
{selfHosts.map((item) => (
|
||||
<Fragment key={item.id}>
|
||||
<Suspense fallback>
|
||||
<ProjectCard
|
||||
icon={iconMap[item.icon ?? 'github']}
|
||||
project={item}
|
||||
/>
|
||||
</Suspense>
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export type Project = {
|
||||
id: number;
|
||||
icon?: keyof typeof iconMap;
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<{
|
||||
projects: Project[];
|
||||
selfHosts: Project[];
|
||||
}> = async () => {
|
||||
const projects: Project[] = [
|
||||
{
|
||||
id: 0,
|
||||
icon: 'three',
|
||||
name: '3d-globe',
|
||||
description: 'A 3d globe made by three.js.',
|
||||
url: 'https://github.com/DefectingCat/3d-globe',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
icon: 'nextjs',
|
||||
name: 'Blog',
|
||||
description: 'This site.',
|
||||
url: 'https://github.com/DefectingCat/DefectingCat.github.io',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
icon: 'tsnode',
|
||||
name: 'boring-avatars-services',
|
||||
description: 'Random avatars.',
|
||||
url: 'https://github.com/DefectingCat/boring-avatars-services',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
icon: 'tsnode',
|
||||
name: 'RUA DDNS',
|
||||
description: 'DDNS Script for DNSPod',
|
||||
url: 'https://github.com/DefectingCat/rua-ddns',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
icon: 'vim',
|
||||
name: 'Dotfiles',
|
||||
description: 'Some dotfiles.',
|
||||
url: 'https://github.com/DefectingCat/dotfiles',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
icon: 'redux',
|
||||
name: 'RUA-Context',
|
||||
description: 'A global store for React.',
|
||||
url: 'https://github.com/rua-plus/rua-context',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
icon: 'three',
|
||||
name: 'RUA-Three',
|
||||
description: 'A three.js hooks for React.',
|
||||
url: 'https://github.com/rua-plus/rua-three',
|
||||
},
|
||||
];
|
||||
const selfHosts: Project[] = [
|
||||
{
|
||||
id: 0,
|
||||
icon: 'gitea',
|
||||
name: 'Gitea',
|
||||
description: 'Selfhost git.',
|
||||
url: 'https://git.rua.plus/',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
icon: 'photos',
|
||||
name: 'Photos',
|
||||
description: 'Some photos.',
|
||||
url: 'https://photos.rua.plus/browse',
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
props: {
|
||||
projects,
|
||||
selfHosts,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Projects.getLayout = function getLayout(page: ReactElement) {
|
||||
return <MainLayout>{page}</MainLayout>;
|
||||
};
|
||||
|
||||
export default Projects;
|
1317
pnpm-lock.yaml
generated
1317
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,11 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"target": "es6",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@ -15,8 +19,20 @@
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
// "typeRoots": ["./types", "./node_modules/@types"],
|
||||
"incremental": true
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user