mirror of
https://github.com/DefectingCat/DefectingCat.github.io
synced 2025-07-16 09:11:38 +00:00
✨ Pagination for gists
This commit is contained in:
33
components/RUA/Button.tsx
Normal file
33
components/RUA/Button.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
export type ButtonProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Button = ({ children, disabled }: ButtonProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className={classNames(
|
||||||
|
'bg-white border border-transparent hover:border-gray-200',
|
||||||
|
'outline-none hover:bg-gray-50 focus:ring-4 dark:border-transparent',
|
||||||
|
'focus:ring-cyan-200 font-medium rounded-lg text-sm',
|
||||||
|
'px-5 py-2.5 mr-2 mb-2 dark:bg-gray-800 dark:text-white ',
|
||||||
|
'dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:ring-cyan-800',
|
||||||
|
'transition-all disabled:hover:bg-gray-200',
|
||||||
|
'disabled:cursor-not-allowed disabled:dark:hover:bg-gray-700',
|
||||||
|
'disabled:hover:border-transparent',
|
||||||
|
'text-lg disabled:bg-gray-200 disabled:text-gray-500',
|
||||||
|
'dark:disabled:bg-gray-700 dark:disabled:text-gray-300',
|
||||||
|
'disabled:dark:hover:border-transparent'
|
||||||
|
)}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Button;
|
@ -16,7 +16,7 @@ type Props = {
|
|||||||
const FileContent = ({ gists }: Props) => {
|
const FileContent = ({ gists }: Props) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex-1 py-4 overflow-hidden md:pl-8">
|
<div className="overflow-hidden">
|
||||||
{gists.map((g) => (
|
{gists.map((g) => (
|
||||||
<div key={g.id}>
|
<div key={g.id}>
|
||||||
{Object.keys(g.files).map((f) => (
|
{Object.keys(g.files).map((f) => (
|
||||||
|
@ -7,7 +7,7 @@ import remarkParse from 'remark-parse';
|
|||||||
import remarkRehype from 'remark-rehype';
|
import remarkRehype from 'remark-rehype';
|
||||||
import { GistsFile } from 'types';
|
import { GistsFile } from 'types';
|
||||||
import { unified } from 'unified';
|
import { unified } from 'unified';
|
||||||
import styles from './styles.module.css';
|
import styles from './GistsCode.module.css';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
file: GistsFile;
|
file: GistsFile;
|
||||||
|
48
components/gists/Pagination.tsx
Normal file
48
components/gists/Pagination.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { pageSize } from 'lib/fetcher';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
const Button = dynamic(() => import('components/RUA/Button'));
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
pageSize: pageSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Pagination = ({ pageSize }: Props) => {
|
||||||
|
const prev = Number(pageSize.prev);
|
||||||
|
const next = Number(pageSize.next);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<nav>
|
||||||
|
<ul className="flex justify-between -space-x-px">
|
||||||
|
<li>
|
||||||
|
{!!prev ? (
|
||||||
|
<Link href={prev === 1 ? `/gists/` : `/gists/${prev}`} passHref>
|
||||||
|
<a>
|
||||||
|
<Button>Prev</Button>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Button disabled>Prev</Button>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
{!!next ? (
|
||||||
|
<Link href={`/gists/${next}`} passHref>
|
||||||
|
<a>
|
||||||
|
<Button>Next</Button>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<Button disabled>Next</Button>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Pagination;
|
@ -7,7 +7,7 @@ const octokit = new Octokit({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const linkMatch = /<(.*?)>/;
|
const linkMatch = /<(.*?)>/;
|
||||||
// const relMatch = /"(.*?)"/;
|
const relMatch = /"(.*?)"/;
|
||||||
|
|
||||||
export type GistData = {
|
export type GistData = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -17,10 +17,11 @@ export type GistData = {
|
|||||||
description: string | null;
|
description: string | null;
|
||||||
};
|
};
|
||||||
export type GetGists = {
|
export type GetGists = {
|
||||||
next: string | null;
|
pageSize: pageSize;
|
||||||
total: string | null;
|
|
||||||
gists: GistData[];
|
gists: GistData[];
|
||||||
};
|
};
|
||||||
|
export type pageSize = { [key in PageKeys]: string | null };
|
||||||
|
export type PageKeys = 'prev' | 'next' | 'last' | 'first';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all gists.
|
* Get all gists.
|
||||||
@ -31,15 +32,30 @@ export const getGists = async (page = 1, perPage = 10) => {
|
|||||||
page,
|
page,
|
||||||
per_page: perPage,
|
per_page: perPage,
|
||||||
});
|
});
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* <https://api.github.com/gists?page=1&per_page=1>; rel="prev",
|
||||||
|
* <https://api.github.com/gists?page=3&per_page=1>; rel="next",
|
||||||
|
* <https://api.github.com/gists?page=40&per_page=1>; rel="last",
|
||||||
|
* <https://api.github.com/gists?page=1&per_page=1>; rel="first"
|
||||||
|
*/
|
||||||
const link = result.headers.link?.split(',');
|
const link = result.headers.link?.split(',');
|
||||||
|
|
||||||
if (!link) return null;
|
if (!link) return null;
|
||||||
const next = new URLSearchParams(
|
|
||||||
link[0].match(linkMatch)?.[1].split('?')[1]
|
const pageSize: pageSize = {
|
||||||
).get('page');
|
prev: null,
|
||||||
const total = new URLSearchParams(
|
next: null,
|
||||||
link[1].match(linkMatch)?.[1].split('?')[1]
|
last: null,
|
||||||
).get('page');
|
first: null,
|
||||||
|
};
|
||||||
|
link.map((l) => {
|
||||||
|
const text = l.match(relMatch)?.[1] as PageKeys;
|
||||||
|
if (!text) return;
|
||||||
|
pageSize[text] = new URLSearchParams(
|
||||||
|
l.match(linkMatch)?.[1].split('?')[1]
|
||||||
|
).get('page');
|
||||||
|
});
|
||||||
|
|
||||||
const data: GistData[] = result.data.map((item) => ({
|
const data: GistData[] = result.data.map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
@ -62,8 +78,7 @@ export const getGists = async (page = 1, perPage = 10) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
next,
|
pageSize,
|
||||||
total,
|
|
||||||
gists: data,
|
gists: data,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -77,15 +92,47 @@ export const getUser = async () => {
|
|||||||
return (await octokit.rest.users.getAuthenticated()).data;
|
return (await octokit.rest.users.getAuthenticated()).data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GetSignalGist = Awaited<ReturnType<typeof getSignalGist>>;
|
||||||
|
export type SingalGist = {
|
||||||
|
login: string | undefined;
|
||||||
|
files: { [key: string]: GistsFile };
|
||||||
|
updated_at: string | undefined;
|
||||||
|
description: string | undefined | null;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Get one gist.
|
* Get one gist.
|
||||||
* @param id
|
* @param id
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getSignalGist = async (id: string) => {
|
export const getSignalGist = async (id: string) => {
|
||||||
return (
|
const result = (
|
||||||
await octokit.rest.gists.get({
|
await octokit.rest.gists.get({
|
||||||
gist_id: id,
|
gist_id: id,
|
||||||
})
|
})
|
||||||
).data;
|
).data;
|
||||||
|
|
||||||
|
if (result.files == null) return;
|
||||||
|
|
||||||
|
const data: SingalGist = {
|
||||||
|
login: result.owner?.login,
|
||||||
|
updated_at: result.updated_at,
|
||||||
|
description: result.description,
|
||||||
|
files: result.files as SingalGist['files'],
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
Object.keys(data.files).map(async (f) => {
|
||||||
|
const url = data.files?.[f]?.raw_url;
|
||||||
|
if (!url) return;
|
||||||
|
let target = data.files[f];
|
||||||
|
if (!target?.content)
|
||||||
|
target = {
|
||||||
|
...target,
|
||||||
|
content: '',
|
||||||
|
};
|
||||||
|
target.content = await fetch(url).then((res) => res.text());
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import Anchor from 'components/mdx/Anchor';
|
import Anchor from 'components/mdx/Anchor';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import { getSignalGist } from 'lib/fetcher';
|
import { getSignalGist, SingalGist } from 'lib/fetcher';
|
||||||
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next';
|
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import avatar from 'public/images/img/avatar.svg';
|
import avatar from 'public/images/img/avatar.svg';
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
import { SignalGist } from 'types';
|
|
||||||
|
|
||||||
const MainLayout = dynamic(() => import('layouts/MainLayout'));
|
const MainLayout = dynamic(() => import('layouts/MainLayout'));
|
||||||
const GistsCode = dynamic(() => import('components/gists/GistsCode'));
|
const GistsCode = dynamic(() => import('components/gists/GistsCode'));
|
||||||
@ -31,7 +30,7 @@ const Gist = ({ gist }: InferGetStaticPropsType<typeof getStaticProps>) => {
|
|||||||
/>
|
/>
|
||||||
<h1 className="ml-2 overflow-hidden text-xl whitespace-nowrap overflow-ellipsis">
|
<h1 className="ml-2 overflow-hidden text-xl whitespace-nowrap overflow-ellipsis">
|
||||||
<Link href="/gists" passHref>
|
<Link href="/gists" passHref>
|
||||||
<Anchor external={false}>{gist.owner.login}</Anchor>
|
<Anchor external={false}>{gist.login}</Anchor>
|
||||||
</Link>
|
</Link>
|
||||||
/{Object.keys(gist.files)[0]}
|
/{Object.keys(gist.files)[0]}
|
||||||
</h1>
|
</h1>
|
||||||
@ -67,23 +66,18 @@ export const getStaticPaths: GetStaticPaths = async () => {
|
|||||||
|
|
||||||
export const getStaticProps: GetStaticProps<{
|
export const getStaticProps: GetStaticProps<{
|
||||||
id: string | undefined;
|
id: string | undefined;
|
||||||
gist: SignalGist;
|
gist: SingalGist;
|
||||||
}> = async ({ params }) => {
|
}> = async ({ params }) => {
|
||||||
if (typeof params?.id !== 'string') {
|
if (typeof params?.id !== 'string')
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const gist = await getSignalGist(params?.id);
|
const gist = await getSignalGist(params.id);
|
||||||
|
if (!gist || !gist.files)
|
||||||
await Promise.all(
|
return {
|
||||||
Object.keys(gist.files).map(async (f) => {
|
notFound: true,
|
||||||
gist.files[f].content = await fetch(gist.files[f].raw_url).then((res) =>
|
};
|
||||||
res.text()
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
@ -9,6 +9,7 @@ import { ReactElement } from 'react';
|
|||||||
const MainLayout = dynamic(() => import('layouts/MainLayout'));
|
const MainLayout = dynamic(() => import('layouts/MainLayout'));
|
||||||
const UserInfo = dynamic(() => import('components/gists/UserInfo'));
|
const UserInfo = dynamic(() => import('components/gists/UserInfo'));
|
||||||
const FileContent = dynamic(() => import('components/gists/FileContent'));
|
const FileContent = dynamic(() => import('components/gists/FileContent'));
|
||||||
|
const Pagination = dynamic(() => import('components/gists/Pagination'));
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
@ -21,7 +22,11 @@ const Gists = ({
|
|||||||
<main className="max-w-5xl px-4 mx-auto lg:px-0">
|
<main className="max-w-5xl px-4 mx-auto lg:px-0">
|
||||||
<div className="md:flex">
|
<div className="md:flex">
|
||||||
<UserInfo user={user} />
|
<UserInfo user={user} />
|
||||||
<FileContent gists={gists.gists} />
|
|
||||||
|
<div className="flex-1 py-4 overflow-hidden md:pl-8">
|
||||||
|
<FileContent gists={gists.gists} />
|
||||||
|
<Pagination pageSize={gists.pageSize} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
@ -30,8 +35,8 @@ const Gists = ({
|
|||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
const result = await getGists();
|
const result = await getGists();
|
||||||
const next = Number(result?.next);
|
const next = Number(result?.pageSize.next);
|
||||||
const total = Number(result?.total);
|
const last = Number(result?.pageSize.last);
|
||||||
const paths: (
|
const paths: (
|
||||||
| string
|
| string
|
||||||
| {
|
| {
|
||||||
@ -39,7 +44,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
|
|||||||
locale?: string | undefined;
|
locale?: string | undefined;
|
||||||
}
|
}
|
||||||
)[] = [];
|
)[] = [];
|
||||||
for (let i = next; i <= total; i++) {
|
for (let i = next; i <= last; i++) {
|
||||||
paths.push({
|
paths.push({
|
||||||
params: {
|
params: {
|
||||||
p: i.toString(),
|
p: i.toString(),
|
||||||
|
@ -8,6 +8,7 @@ import { ReactElement } from 'react';
|
|||||||
const MainLayout = dynamic(() => import('layouts/MainLayout'));
|
const MainLayout = dynamic(() => import('layouts/MainLayout'));
|
||||||
const UserInfo = dynamic(() => import('components/gists/UserInfo'));
|
const UserInfo = dynamic(() => import('components/gists/UserInfo'));
|
||||||
const FileContent = dynamic(() => import('components/gists/FileContent'));
|
const FileContent = dynamic(() => import('components/gists/FileContent'));
|
||||||
|
const Pagination = dynamic(() => import('components/gists/Pagination'));
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
@ -20,7 +21,11 @@ const Gists = ({
|
|||||||
<main className="max-w-5xl px-4 mx-auto lg:px-0">
|
<main className="max-w-5xl px-4 mx-auto lg:px-0">
|
||||||
<div className="md:flex">
|
<div className="md:flex">
|
||||||
<UserInfo user={user} />
|
<UserInfo user={user} />
|
||||||
<FileContent gists={gists.gists} />
|
|
||||||
|
<div className="flex-1 py-4 overflow-hidden md:pl-8">
|
||||||
|
<FileContent gists={gists.gists} />
|
||||||
|
<Pagination pageSize={gists.pageSize} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
Reference in New Issue
Block a user