✨ App drawer
💄 Support breaks for story posts
			
			
This commit is contained in:
		| @@ -3,11 +3,10 @@ import { | ||||
|   AppBar, | ||||
|   AppBarProps, | ||||
|   Avatar, | ||||
|   createTheme, | ||||
|   IconButton, | ||||
|   ThemeProvider, | ||||
|   Toolbar, | ||||
|   Typography, | ||||
|   useMediaQuery, | ||||
|   useScrollTrigger, | ||||
|   useTheme, | ||||
| } from '@mui/material' | ||||
| @@ -15,11 +14,13 @@ import { getAttachmentUrl } from '@/services/network' | ||||
| import MenuIcon from '@mui/icons-material/Menu' | ||||
| import AccountCircle from '@mui/icons-material/AccountCircle' | ||||
| import Link from 'next/link' | ||||
| import React from 'react' | ||||
| import React, { useState } from 'react' | ||||
| import { CapDrawer } from './CapDrawer' | ||||
|  | ||||
| interface ElevationAppBarProps { | ||||
|   elevation?: number | ||||
|   color?: any | ||||
|   isMobile: boolean | ||||
|   children?: React.ReactElement<{ elevation?: number } & AppBarProps> | ||||
| } | ||||
|  | ||||
| @@ -51,38 +52,63 @@ export function CapAppBar() { | ||||
|   const userStore = useUserStore() | ||||
|   const theme = useTheme() | ||||
|  | ||||
|   return ( | ||||
|     <ElevationScroll elevation={2} color="white"> | ||||
|       <AppBar position="sticky" elevation={0} color="transparent"> | ||||
|         <Toolbar> | ||||
|           <IconButton size="large" edge="start" color="inherit" aria-label="menu" sx={{ mr: 2 }}> | ||||
|             <MenuIcon /> | ||||
|           </IconButton> | ||||
|           <Link href="/" passHref style={{ flexGrow: 1 }}> | ||||
|             <Typography variant="h6" component="div"> | ||||
|               Capital | ||||
|             </Typography> | ||||
|           </Link> | ||||
|   const isMobile = useMediaQuery(theme.breakpoints.down('md')) | ||||
|  | ||||
|           <Link href={userStore.account ? '/users/me' : '/auth/login'} passHref> | ||||
|   const [open, setOpen] = useState(false) | ||||
|  | ||||
|   const drawerWidth = 280 | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <CapDrawer width={drawerWidth} open={open} onClose={() => setOpen(false)} /> | ||||
|  | ||||
|       <ElevationScroll isMobile={isMobile} elevation={2} color="white"> | ||||
|         <AppBar | ||||
|           position="sticky" | ||||
|           elevation={0} | ||||
|           color="transparent" | ||||
|           sx={{ | ||||
|             width: isMobile ? null : `calc(100% - ${drawerWidth}`, | ||||
|             marginLeft: isMobile ? null : `${drawerWidth}px`, | ||||
|           }} | ||||
|         > | ||||
|           <Toolbar> | ||||
|             <IconButton | ||||
|               size="large" | ||||
|               aria-label="account of current user" | ||||
|               aria-controls="primary-search-account-menu" | ||||
|               aria-haspopup="true" | ||||
|               edge="start" | ||||
|               color="inherit" | ||||
|               aria-label="menu" | ||||
|               sx={{ mr: 2 }} | ||||
|               onClick={() => setOpen(true)} | ||||
|             > | ||||
|               {userStore.account ? ( | ||||
|                 <Avatar sx={{ backgroundColor: 'transparent' }} src={getAttachmentUrl(userStore.account.avatar)} /> | ||||
|               ) : ( | ||||
|                 <Avatar sx={{ backgroundColor: 'transparent' }}> | ||||
|                   <AccountCircle sx={{ color: theme.palette.text.primary }} /> | ||||
|                 </Avatar> | ||||
|               )} | ||||
|               <MenuIcon /> | ||||
|             </IconButton> | ||||
|           </Link> | ||||
|         </Toolbar> | ||||
|       </AppBar> | ||||
|     </ElevationScroll> | ||||
|             <Link href="/" passHref style={{ flexGrow: 1 }}> | ||||
|               <Typography variant="h6" component="div"> | ||||
|                 Capital | ||||
|               </Typography> | ||||
|             </Link> | ||||
|  | ||||
|             <Link href={userStore.account ? '/users/me' : '/auth/login'} passHref> | ||||
|               <IconButton | ||||
|                 size="large" | ||||
|                 aria-label="account of current user" | ||||
|                 aria-controls="primary-search-account-menu" | ||||
|                 aria-haspopup="true" | ||||
|                 color="inherit" | ||||
|               > | ||||
|                 {userStore.account ? ( | ||||
|                   <Avatar sx={{ backgroundColor: 'transparent' }} src={getAttachmentUrl(userStore.account.avatar)} /> | ||||
|                 ) : ( | ||||
|                   <Avatar sx={{ backgroundColor: 'transparent' }}> | ||||
|                     <AccountCircle sx={{ color: theme.palette.text.primary }} /> | ||||
|                   </Avatar> | ||||
|                 )} | ||||
|               </IconButton> | ||||
|             </Link> | ||||
|           </Toolbar> | ||||
|         </AppBar> | ||||
|       </ElevationScroll> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|   | ||||
							
								
								
									
										87
									
								
								src/components/CapDrawer.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/components/CapDrawer.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| import { | ||||
|   Box, | ||||
|   List, | ||||
|   ListItem, | ||||
|   ListItemButton, | ||||
|   ListItemIcon, | ||||
|   ListItemText, | ||||
|   Divider, | ||||
|   Drawer, | ||||
|   Toolbar, | ||||
| } from '@mui/material' | ||||
| import { JSX } from 'react' | ||||
|  | ||||
| import FeedIcon from '@mui/icons-material/Feed' | ||||
| import PhotoLibraryIcon from '@mui/icons-material/PhotoLibrary' | ||||
| import InfoIcon from '@mui/icons-material/Info' | ||||
| import PolicyIcon from '@mui/icons-material/Policy' | ||||
| import Link from 'next/link' | ||||
|  | ||||
| interface NavLink { | ||||
|   title: string | ||||
|   icon: JSX.Element | ||||
|   href: string | ||||
| } | ||||
|  | ||||
| export function CapDrawer({ width, open, onClose }: { width: number; open: boolean; onClose: () => void }) { | ||||
|   const functionLinks: NavLink[] = [ | ||||
|     { | ||||
|       title: 'Posts', | ||||
|       icon: <FeedIcon />, | ||||
|       href: '/posts', | ||||
|     }, | ||||
|     { | ||||
|       title: 'Gallery', | ||||
|       icon: <PhotoLibraryIcon />, | ||||
|       href: '/attachments', | ||||
|     }, | ||||
|   ] | ||||
|  | ||||
|   const additionalLinks: NavLink[] = [ | ||||
|     { | ||||
|       title: 'About', | ||||
|       icon: <InfoIcon />, | ||||
|       href: '/about', | ||||
|     }, | ||||
|     { | ||||
|       title: 'Term & Conditions', | ||||
|       icon: <PolicyIcon />, | ||||
|       href: '/terms', | ||||
|     }, | ||||
|   ] | ||||
|  | ||||
|   return ( | ||||
|     <Drawer open={open} onClose={onClose}> | ||||
|       <Box sx={{ width: width }} role="presentation" onClick={onClose}> | ||||
|         <Toolbar>Solsynth LLC</Toolbar> | ||||
|         <Divider /> | ||||
|  | ||||
|         <List> | ||||
|           {functionLinks.map((l) => ( | ||||
|             <Link passHref href={l.href} key={l.href}> | ||||
|               <ListItem disablePadding> | ||||
|                 <ListItemButton> | ||||
|                   <ListItemIcon>{l.icon}</ListItemIcon> | ||||
|                   <ListItemText primary={l.title} /> | ||||
|                 </ListItemButton> | ||||
|               </ListItem> | ||||
|             </Link> | ||||
|           ))} | ||||
|         </List> | ||||
|         <Divider /> | ||||
|         <List dense> | ||||
|           {additionalLinks.map((l) => ( | ||||
|             <Link passHref href={l.href} key={l.href}> | ||||
|               <ListItem disablePadding> | ||||
|                 <ListItemButton> | ||||
|                   <ListItemIcon>{l.icon}</ListItemIcon> | ||||
|                   <ListItemText primary={l.title} /> | ||||
|                 </ListItemButton> | ||||
|               </ListItem> | ||||
|             </Link> | ||||
|           ))} | ||||
|         </List> | ||||
|       </Box> | ||||
|     </Drawer> | ||||
|   ) | ||||
| } | ||||
| @@ -22,6 +22,7 @@ import Head from 'next/head' | ||||
| import Image from 'next/image' | ||||
| 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' | ||||
|  | ||||
| @@ -32,15 +33,12 @@ export const getServerSideProps = (async (context) => { | ||||
|   try { | ||||
|     const { data: post } = await sni.get<SnPost>('/cgi/co/posts/' + id.join(':')) | ||||
|     if (post.body.content) { | ||||
|       if (!post.body.description) { | ||||
|         post.body.description = post.body.content.replaceAll('\n', ' ').substring(0, 200) | ||||
|       let processor: any = unified().use(remarkParse) | ||||
|       if (post.type != 'article') { | ||||
|         processor = processor.use(remarkBreaks) | ||||
|       } | ||||
|       const out = await unified() | ||||
|         .use(remarkParse) | ||||
|         .use(remarkRehype) | ||||
|         .use(rehypeSanitize) | ||||
|         .use(rehypeStringify) | ||||
|         .process(post.body.content) | ||||
|       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) | ||||
|     } | ||||
|     let attachments: SnAttachment[] = [] | ||||
| @@ -49,6 +47,7 @@ export const getServerSideProps = (async (context) => { | ||||
|     } | ||||
|     return { props: { post, attachments } } | ||||
|   } catch (err) { | ||||
|     console.error(err) | ||||
|     return { | ||||
|       notFound: true, | ||||
|     } | ||||
| @@ -72,7 +71,11 @@ export default function Post({ post, attachments }: InferGetServerSidePropsType< | ||||
|         : `Post #${post.id} / @${post.publisher.name} / Solar Network`, | ||||
|     [post], | ||||
|   ) | ||||
|   const description = useMemo(() => post.body.description, [post]) | ||||
|   const description = useMemo( | ||||
|     () => | ||||
|       post.body.description ? post.body.description : post.body.rawContent.replaceAll('\n', ' ').substring(0, 200), | ||||
|     [post], | ||||
|   ) | ||||
|  | ||||
|   const image = useMemo(() => { | ||||
|     if (post.body.thumbnail) { | ||||
| @@ -80,21 +83,21 @@ export default function Post({ post, attachments }: InferGetServerSidePropsType< | ||||
|     } | ||||
|     if (attachments) { | ||||
|       const images = attachments.filter((a) => a.mimetype.startsWith('image')) | ||||
|       if (images) return getAttachmentUrl(images[0].rid) | ||||
|       if (images && images[0]) return getAttachmentUrl(images[0].rid) | ||||
|     } | ||||
|     return null | ||||
|   }, [post]) | ||||
|   const video = useMemo(() => { | ||||
|     if (attachments) { | ||||
|       const videos = attachments.filter((a) => a.mimetype.startsWith('video')) | ||||
|       if (videos) return getAttachmentUrl(videos[0].rid) | ||||
|       if (videos && videos[0]) return getAttachmentUrl(videos[0].rid) | ||||
|     } | ||||
|     return null | ||||
|   }, [post]) | ||||
|   const audio = useMemo(() => { | ||||
|     if (attachments) { | ||||
|       const audios = attachments.filter((a) => a.mimetype.startsWith('audio')) | ||||
|       if (audios) return getAttachmentUrl(audios[0].rid) | ||||
|       if (audios && audios[0]) return getAttachmentUrl(audios[0].rid) | ||||
|     } | ||||
|     return null | ||||
|   }, [post]) | ||||
| @@ -201,9 +204,21 @@ export default function Post({ post, attachments }: InferGetServerSidePropsType< | ||||
|         </Box> | ||||
|  | ||||
|         {attachments && ( | ||||
|           <Grid container spacing={2} sx={{ mt: 3 }} columns={{ md: Math.min(2, attachments.length) }}> | ||||
|           <Grid | ||||
|             container | ||||
|             spacing={2} | ||||
|             sx={{ mt: 3 }} | ||||
|             columns={{ | ||||
|               xs: 1, | ||||
|               sm: Math.min(2, attachments.length), | ||||
|               md: Math.min(3, attachments.length), | ||||
|               lg: Math.min(4, attachments.length), | ||||
|             }} | ||||
|           > | ||||
|             {attachments.map((a) => ( | ||||
|               <AttachmentItem item={a} /> | ||||
|               <Grid size="grow"> | ||||
|                 <AttachmentItem item={a} /> | ||||
|               </Grid> | ||||
|             ))} | ||||
|           </Grid> | ||||
|         )} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user