Pagination for gists

This commit is contained in:
DefectingCat
2022-06-21 15:51:03 +08:00
parent 74bd0a7ba0
commit 54af3eb68f
9 changed files with 166 additions and 34 deletions

33
components/RUA/Button.tsx Normal file
View 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;

View File

@ -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) => (

View File

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

View 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;

View File

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

View File

@ -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: {

View File

@ -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(),

View File

@ -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>
</> </>