✨ Post list
This commit is contained in:
parent
8cbdc7c870
commit
e123aabf0f
@ -2,24 +2,25 @@ import { SnAttachment } from '@/services/attachment'
|
|||||||
import { getAttachmentUrl } from '@/services/network'
|
import { getAttachmentUrl } from '@/services/network'
|
||||||
import { QuestionMark } from '@mui/icons-material'
|
import { QuestionMark } from '@mui/icons-material'
|
||||||
import { Link, Paper, Typography } from '@mui/material'
|
import { Link, Paper, Typography } from '@mui/material'
|
||||||
|
import { ComponentProps } from 'react'
|
||||||
|
|
||||||
export function AttachmentItem({ item }: { item: SnAttachment }) {
|
export function AttachmentItem({ item, ...rest }: { item: SnAttachment } & ComponentProps<'div'>) {
|
||||||
switch (item.mimetype.split('/')[0]) {
|
switch (item.mimetype.split('/')[0]) {
|
||||||
case 'image':
|
case 'image':
|
||||||
return (
|
return (
|
||||||
<Paper>
|
<Paper {...rest}>
|
||||||
<img src={getAttachmentUrl(item.rid)} alt={item.alt} style={{ objectFit: 'cover', borderRadius: '8px' }} />
|
<img src={getAttachmentUrl(item.rid)} alt={item.alt} style={{ objectFit: 'cover', borderRadius: '8px' }} />
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
case 'video':
|
case 'video':
|
||||||
return (
|
return (
|
||||||
<Paper>
|
<Paper {...rest}>
|
||||||
<video src={getAttachmentUrl(item.rid)} controls style={{ borderRadius: '8px' }} />
|
<video src={getAttachmentUrl(item.rid)} controls style={{ borderRadius: '8px' }} />
|
||||||
</Paper>
|
</Paper>
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<Paper sx={{ width: '100%', height: '100%', p: 5, textAlign: 'center' }}>
|
<Paper sx={{ width: '100%', height: '100%', p: 5, textAlign: 'center' }} {...rest}>
|
||||||
<QuestionMark sx={{ mb: 2 }} />
|
<QuestionMark sx={{ mb: 2 }} />
|
||||||
<Typography>Unknown</Typography>
|
<Typography>Unknown</Typography>
|
||||||
<Typography gutterBottom>{item.name}</Typography>
|
<Typography gutterBottom>{item.name}</Typography>
|
||||||
|
@ -44,7 +44,7 @@ export default function App({ Component, pageProps }: AppProps) {
|
|||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
|
|
||||||
<CapAppBar />
|
<CapAppBar />
|
||||||
<Box sx={{ height: 'calc(100vh - 64px)' }}>
|
<Box sx={{ minHeight: 'calc(100vh - 64px)' }}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</Box>
|
</Box>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
@ -83,7 +83,7 @@ export default function Login() {
|
|||||||
sx={{
|
sx={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
placeItems: 'center',
|
placeItems: 'center',
|
||||||
height: '100%',
|
height: 'calc(100vh - 64px)',
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -216,7 +216,7 @@ export default function Post({ post, attachments }: InferGetServerSidePropsType<
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{attachments.map((a) => (
|
{attachments.map((a) => (
|
||||||
<Grid size="grow">
|
<Grid size={1} key={a.id}>
|
||||||
<AttachmentItem item={a} />
|
<AttachmentItem item={a} />
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
|
135
src/pages/posts/index.tsx
Normal file
135
src/pages/posts/index.tsx
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import { AttachmentItem } from '@/components/attachments/AttachmentItem'
|
||||||
|
import { SnAttachment, listAttachment } from '@/services/attachment'
|
||||||
|
import { getAttachmentUrl, sni } from '@/services/network'
|
||||||
|
import { SnPost } from '@/services/post'
|
||||||
|
import { Avatar, Box, Container, Divider, Grid2 as Grid, Pagination, Paper, Typography } from '@mui/material'
|
||||||
|
import { GetServerSideProps, InferGetServerSidePropsType } from 'next'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import rehypeSanitize from 'rehype-sanitize'
|
||||||
|
import rehypeStringify from 'rehype-stringify'
|
||||||
|
import remarkBreaks from 'remark-breaks'
|
||||||
|
import remarkParse from 'remark-parse'
|
||||||
|
import remarkRehype from 'remark-rehype'
|
||||||
|
import { unified } from 'unified'
|
||||||
|
|
||||||
|
type SnPostWithAttachments = SnPost & { attachments: SnAttachment[] }
|
||||||
|
|
||||||
|
export const getServerSideProps = (async (context) => {
|
||||||
|
let page: number = parseInt(context.query.page as string)
|
||||||
|
if (isNaN(page)) page = 1
|
||||||
|
|
||||||
|
const countPerPage = 10
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data: resp } = await sni.get<{ data: SnPost[]; count: number }>('/cgi/co/posts', {
|
||||||
|
params: {
|
||||||
|
take: countPerPage,
|
||||||
|
offset: (page - 1) * countPerPage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let posts: SnPostWithAttachments[] = resp.data as SnPostWithAttachments[]
|
||||||
|
for (let idx = 0; idx < posts.length; idx++) {
|
||||||
|
let post = posts[idx]
|
||||||
|
if (post.body.content) {
|
||||||
|
let processor: any = unified().use(remarkParse)
|
||||||
|
if (post.type != 'article') {
|
||||||
|
processor = processor.use(remarkBreaks)
|
||||||
|
}
|
||||||
|
const out = await processor
|
||||||
|
.use(remarkRehype)
|
||||||
|
.use(rehypeSanitize)
|
||||||
|
.use(rehypeStringify)
|
||||||
|
.process(post.body.content)
|
||||||
|
post.body.rawContent = post.body.content
|
||||||
|
post.body.content = String(out)
|
||||||
|
}
|
||||||
|
if (post.body.attachments) {
|
||||||
|
post.attachments = await listAttachment(post.body.attachments)
|
||||||
|
}
|
||||||
|
posts[idx] = post
|
||||||
|
}
|
||||||
|
|
||||||
|
return { props: { posts, page, pages: Math.ceil(resp.count / countPerPage) } }
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) satisfies GetServerSideProps<{ posts: SnPostWithAttachments[]; page: number; pages: number }>
|
||||||
|
|
||||||
|
export default function PostList({ posts, page, pages }: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container sx={{ mt: 2, display: 'flex', flexDirection: 'column', gap: 2 }} maxWidth="md">
|
||||||
|
{posts.map((p) => (
|
||||||
|
<Paper key={p.id} sx={{ px: 2, py: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||||
|
<Avatar src={getAttachmentUrl(p.publisher.avatar)} />
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
|
<Typography fontWeight="bold">{p.publisher.nick}</Typography>
|
||||||
|
<Typography fontFamily="monospace" fontSize={13} lineHeight={1.2}>
|
||||||
|
@{p.publisher.name}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Link href={`/posts/${p.id}`} passHref>
|
||||||
|
<Box>
|
||||||
|
<Box sx={{ mt: 1.5, mb: 1 }} display="flex" flexDirection="column" gap={0.5}>
|
||||||
|
{(p.body.title || p.body.content) && (
|
||||||
|
<Box>
|
||||||
|
{p.body.title && <Typography variant="h6">{p.body.title}</Typography>}
|
||||||
|
{p.body.description && <Typography variant="subtitle1">{p.body.description}</Typography>}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
<Box display="flex" gap={2} sx={{ opacity: 0.8 }}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Published at {new Date(p.publishedAt ?? p.createdAt).toLocaleString()}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Box sx={{ maxWidth: 'unset' }} className="prose prose-md">
|
||||||
|
{p.body.content && <div dangerouslySetInnerHTML={{ __html: p.body.content }} />}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{p.attachments && (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={2}
|
||||||
|
columns={{
|
||||||
|
xs: 1,
|
||||||
|
sm: Math.min(2, p.attachments.length),
|
||||||
|
md: Math.min(3, p.attachments.length),
|
||||||
|
lg: Math.min(4, p.attachments.length),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{p.attachments.map((a) => (
|
||||||
|
<Grid size={1} key={a.id}>
|
||||||
|
<AttachmentItem item={a} />
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Pagination
|
||||||
|
count={pages}
|
||||||
|
page={page}
|
||||||
|
sx={{ mx: 'auto', mb: 5, mt: 3 }}
|
||||||
|
onChange={(_, page) => router.push('/posts?page=' + page)}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user