client: lint and minor scroll button/file explorer adjustments

This commit is contained in:
Max Leiter 2022-03-25 14:31:10 -07:00
parent 887ecfabbc
commit 0815d43ee8
No known key found for this signature in database
GPG key ID: A3512F2F2F17EBDA
10 changed files with 454 additions and 427 deletions

View file

@ -23,7 +23,7 @@
transition: var(--transition); transition: var(--transition);
border-radius: var(--radius); border-radius: var(--radius);
margin: 0; margin: 0;
padding: 2px var(--gap); padding: 0 0;
} }
.content li:hover, .content li:hover,
@ -32,9 +32,11 @@
} }
.content li a { .content li a {
display: block; display: flex;
height: 100%; align-items: center;
width: min-content; text-align: center;
padding: var(--gap-half) var(--gap);
color: var(--dark-gray);
} }
.button { .button {

View file

@ -1,8 +1,8 @@
import { Button, Link, Text, Popover } from '@geist-ui/core' import { Button, Link, Text, Popover } from '@geist-ui/core'
import FileIcon from '@geist-ui/icons/fileText' import FileIcon from '@geist-ui/icons/fileText'
import CodeIcon from '@geist-ui/icons/fileLambda' import CodeIcon from '@geist-ui/icons/fileFunction'
import styles from './dropdown.module.css' import styles from './dropdown.module.css'
import { useCallback, useEffect, useState } from "react" import { useCallback, useEffect, useRef, useState } from "react"
import { codeFileExtensions } from "@lib/constants" import { codeFileExtensions } from "@lib/constants"
import ChevronDown from '@geist-ui/icons/chevronDown' import ChevronDown from '@geist-ui/icons/chevronDown'
import ShiftBy from "@components/shift-by" import ShiftBy from "@components/shift-by"
@ -20,6 +20,16 @@ const FileDropdown = ({
}) => { }) => {
const [expanded, setExpanded] = useState(false) const [expanded, setExpanded] = useState(false)
const [items, setItems] = useState<Item[]>([]) const [items, setItems] = useState<Item[]>([])
const onOpen = useCallback(() => {
setExpanded(true)
}, [])
const onClose = useCallback(() => {
setExpanded(false)
// contentRef.current?.focus()
}, [])
useEffect(() => { useEffect(() => {
const newItems = files.map(file => { const newItems = files.map(file => {
const extension = file.title.split('.').pop() const extension = file.title.split('.').pop()
@ -40,24 +50,24 @@ const FileDropdown = ({
const content = useCallback(() => (<ul className={styles.content}> const content = useCallback(() => (<ul className={styles.content}>
{items.map(item => ( {items.map(item => (
<li key={item.id} onClick={() => setExpanded(false)}> <li key={item.id} onClick={onClose}>
<Link color={false} href={`#${item.title}`}> <a href={`#${item.title}`}>
<ShiftBy y={5}><span className={styles.fileIcon}> <ShiftBy y={5}><span className={styles.fileIcon}>
{item.icon}</span></ShiftBy> {item.icon}</span></ShiftBy>
<span className={styles.fileTitle}>{item.title ? item.title : 'Untitled'}</span> <span className={styles.fileTitle}>{item.title ? item.title : 'Untitled'}</span>
</Link> </a>
</li> </li>
))} ))}
</ul> </ul>
), [items]) ), [items, onClose])
// a list of files with an icon and a title // a list of files with an icon and a title
return ( return (
<Button auto onClick={() => setExpanded(!expanded)} className={styles.button} iconRight={<ChevronDown />}> <Button auto onClick={onOpen} className={styles.button} iconRight={<ChevronDown />}>
<Popover content={content} visible={expanded} trigger="click" hideArrow={true}> <Popover tabIndex={0} content={content} visible={expanded} trigger="click" hideArrow={true}>
{files.length} {files.length === 1 ? 'file' : 'files'} Jump to {files.length} {files.length === 1 ? 'file' : 'files'}
</Popover> </Popover>
</Button> </Button >
) )
} }

View file

@ -54,7 +54,7 @@ const PostPage = ({ post }: Props) => {
{/* {!isLoading && <PostFileExplorer files={post.files} />} */} {/* {!isLoading && <PostFileExplorer files={post.files} />} */}
<div className={styles.header}> <div className={styles.header}>
<span className={styles.title}> <span className={styles.title}>
<Text h2>{post.title}</Text> <Text h3>{post.title}</Text>
<div className={styles.badges}> <div className={styles.badges}>
<VisibilityBadge visibility={post.visibility} /> <VisibilityBadge visibility={post.visibility} />
<Badge type="secondary"><Tooltip text={formattedTime}>{time}</Tooltip></Badge> <Badge type="secondary"><Tooltip text={formattedTime}>{time}</Tooltip></Badge>

View file

@ -11,9 +11,13 @@
align-items: center; align-items: center;
} }
.header .title h3 {
margin: 0;
padding: 0;
}
.header .title .badges > * { .header .title .badges > * {
margin-left: var(--gap); margin-left: var(--gap);
margin-bottom: var(--gap-quarter);
} }
.header .buttons { .header .buttons {
@ -42,6 +46,6 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
width: 80%; width: 100%;
} }
} }

View file

@ -14,13 +14,15 @@ const ScrollToTop = () => {
return () => window.removeEventListener('scroll', handleScroll) return () => window.removeEventListener('scroll', handleScroll)
}, []) }, [])
const onClick = () => { const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {
// blur the button
e.currentTarget.blur()
window.scrollTo({ top: 0, behavior: 'smooth' }) window.scrollTo({ top: 0, behavior: 'smooth' })
} }
return ( return (
<div style={{ display: 'flex', flexDirection: 'row', width: '100%', height: 24, justifyContent: 'flex-end' }}> <div style={{ display: 'flex', flexDirection: 'row', width: '100%', height: 24, justifyContent: 'flex-end' }}>
<Tooltip text="Scroll to Top" className={`${styles['scroll-up']} ${shouldShow ? styles['scroll-up-shown'] : ''}`}> <Tooltip hideArrow text="Scroll to Top" className={`${styles['scroll-up']} ${shouldShow ? styles['scroll-up-shown'] : ''}`}>
<Button aria-label='Scroll to Top' onClick={onClick} style={{ background: 'var(--light-gray)' }} auto > <Button aria-label='Scroll to Top' onClick={onClick} style={{ background: 'var(--light-gray)' }} auto >
<Spacer height={2 / 3} inline width={0} /> <Spacer height={2 / 3} inline width={0} />
<ChevronUp /> <ChevronUp />

View file

@ -1,141 +1,141 @@
export const allowedFileTypes = [ export const allowedFileTypes = [
'application/json', "application/json",
'application/x-javascript', "application/x-javascript",
'application/xhtml+xml', "application/xhtml+xml",
'application/xml', "application/xml",
'text/xml', "text/xml",
'text/plain', "text/plain",
'text/html', "text/html",
'text/csv', "text/csv",
'text/tab-separated-values', "text/tab-separated-values",
'text/x-c', "text/x-c",
'text/x-c++', "text/x-c++",
'text/x-csharp', "text/x-csharp",
'text/x-java', "text/x-java",
'text/x-javascript', "text/x-javascript",
'text/x-php', "text/x-php",
'text/x-python', "text/x-python",
'text/x-ruby', "text/x-ruby",
'text/x-scala', "text/x-scala",
'text/x-swift', "text/x-swift",
'text/x-typescript', "text/x-typescript",
'text/x-vb', "text/x-vb",
'text/x-vbscript', "text/x-vbscript",
'text/x-yaml', "text/x-yaml",
'text/x-c++', "text/x-c++",
'text/x-c#', "text/x-c#",
'text/mathml', "text/mathml",
'text/x-markdown', "text/x-markdown",
'text/markdown', "text/markdown"
] ]
export const allowedFileNames = [ export const allowedFileNames = [
'Makefile', "Makefile",
'README', "README",
'Dockerfile', "Dockerfile",
'Jenkinsfile', "Jenkinsfile",
'LICENSE', "LICENSE",
'.env', ".env",
'.gitignore', ".gitignore",
'.gitattributes', ".gitattributes",
'.env.example', ".env.example",
'.env.development', ".env.development",
'.env.production', ".env.production",
'.env.test', ".env.test",
'.env.staging', ".env.staging",
'.env.development.local', ".env.development.local",
'yarn.lock', "yarn.lock",
'.bash', ".bash",
'.bashrc', ".bashrc",
'.bash_profile', ".bash_profile",
'.bash_logout', ".bash_logout",
'.profile', ".profile",
'.fish_prompt', ".fish_prompt",
'.zshrc', ".zshrc",
'.zsh', ".zsh",
'.zprofile', ".zprofile",
'go', "go"
] ]
export const allowedFileExtensions = [ export const allowedFileExtensions = [
'json', "json",
'js', "js",
'jsx', "jsx",
'ts', "ts",
'tsx', "tsx",
'c', "c",
'cpp', "cpp",
'c++', "c++",
'c#', "c#",
'java', "java",
'php', "php",
'py', "py",
'rb', "rb",
'scala', "scala",
'swift', "swift",
'vb', "vb",
'vbscript', "vbscript",
'yaml', "yaml",
'less', "less",
'stylus', "stylus",
'styl', "styl",
'sass', "sass",
'scss', "scss",
'lock', "lock",
'md', "md",
'markdown', "markdown",
'txt', "txt",
'html', "html",
'htm', "htm",
'css', "css",
'csv', "csv",
'log', "log",
'sql', "sql",
'xml', "xml",
'webmanifest', "webmanifest",
'vue', "vue",
'vuex', "vuex",
'rs', "rs",
'zig', "zig"
] ]
export const codeFileExtensions = [ export const codeFileExtensions = [
'json', "json",
'js', "js",
'jsx', "jsx",
'ts', "ts",
'tsx', "tsx",
'c', "c",
'cpp', "cpp",
'c++', "c++",
'c#', "c#",
'java', "java",
'php', "php",
'py', "py",
'rb', "rb",
'scala', "scala",
'swift', "swift",
'vb', "vb",
'vbscript', "vbscript",
'yaml', "yaml",
'less', "less",
'stylus', "stylus",
'styl', "styl",
'sass', "sass",
'scss', "scss",
'html', "html",
'htm', "htm",
'css', "css",
'asm', "asm",
's', "s",
'sh', "sh",
'bat', "bat",
'cmd', "cmd",
'sql', "sql",
'xml', "xml",
'rust', "rust",
'h', "h",
'zig', "zig",
'rs', "rs",
'go' "go"
] ]

View file

@ -11,8 +11,7 @@ const renderMarkdown: NextApiHandler = async (req, res) => {
Authorization: `Bearer ${req.cookies["drift-token"]}` Authorization: `Bearer ${req.cookies["drift-token"]}`
} }
}) })
if (file.status if (file.status !== 200) {
!== 200) {
return res.status(404).json({ error: "File not found" }) return res.status(404).json({ error: "File not found" })
} }

View file

@ -16,19 +16,19 @@ app.use("/posts", posts)
app.use("/users", users) app.use("/users", users)
app.use("/files", files) app.use("/files", files)
app.get('/welcome', secretKey, (req, res) => { app.get("/welcome", secretKey, (req, res) => {
const introContent = process.env.WELCOME_CONTENT; const introContent = process.env.WELCOME_CONTENT
const introTitle = process.env.WELCOME_TITLE; const introTitle = process.env.WELCOME_TITLE
if (!introContent || !introTitle) { if (!introContent || !introTitle) {
return res.status(500).json({ error: 'Missing welcome content' }); return res.status(500).json({ error: "Missing welcome content" })
} }
return res.json({ return res.json({
title: introTitle, title: introTitle,
content: introContent, content: introContent,
rendered: markdown(introContent) rendered: markdown(introContent)
}); })
}) })
app.use(errors()) app.use(errors())

View file

@ -12,8 +12,12 @@ import { Op } from "sequelize"
export const posts = Router() export const posts = Router()
const postVisibilitySchema = (value: string) => { const postVisibilitySchema = (value: string) => {
if (value === "public" || value === "private" || if (
value === "unlisted" || value === "protected") { value === "public" ||
value === "private" ||
value === "unlisted" ||
value === "protected"
) {
return value return value
} else { } else {
throw new Error("Invalid post visibility") throw new Error("Invalid post visibility")
@ -99,7 +103,9 @@ posts.get("/", secretKey, async (req, res, next) => {
} }
}) })
posts.get("/mine", jwt, posts.get(
"/mine",
jwt,
celebrate({ celebrate({
query: { query: {
page: Joi.number().integer().min(1).default(1).optional() page: Joi.number().integer().min(1).default(1).optional()
@ -124,7 +130,7 @@ posts.get("/mine", jwt,
model: File, model: File,
as: "files", as: "files",
attributes: ["id", "title", "createdAt"] attributes: ["id", "title", "createdAt"]
}, }
], ],
attributes: ["id", "title", "visibility", "createdAt"] attributes: ["id", "title", "visibility", "createdAt"]
} }
@ -134,14 +140,18 @@ posts.get("/mine", jwt,
return res.status(404).json({ error: "User not found" }) return res.status(404).json({ error: "User not found" })
} }
return res.json( return res.json(
user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()).slice((parsedPage - 1) * 10, parsedPage * 10) user.posts
?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
.slice((parsedPage - 1) * 10, parsedPage * 10)
) )
} catch (error) { } catch (error) {
next(error) next(error)
} }
}) }
)
posts.get("/search", posts.get(
"/search",
jwt, jwt,
celebrate({ celebrate({
query: { query: {