OurBigBook logoOurBigBook Docs OurBigBook logoOurBigBook.comSite Source code
web/pages/[uid]/_file/[...path].tsx
import React from 'react'
import { useRouter } from 'next/router'

import { DeleteIcon, MyHead, SeeIcon, UnlistedIcon } from 'front'
import { webApi } from 'front/api'
import ArticlePageHoc, { ArticlePageProps } from 'front/ArticlePage'
import { cant } from 'front/cant'
import { FileDirectoryHeader } from 'front/DirPage'
import routes from 'front/routes'
import { UserType } from 'front/types/UserType'

export { getServerSidePropsFile as getServerSideProps } from 'back/FilePage'

const ArticlePage = ArticlePageHoc()

function FileDeleteButton({ username, path }: { username: string; path: string }) {
  const router = useRouter()
  const [deleting, setDeleting] = React.useState(false)
  const [error, setError] = React.useState('')
  const pending = React.useRef(false)
  async function handleDelete() {
    if (pending.current || !confirm(`Delete ${username}/${path}? This cannot be undone.`)) return
    pending.current = true
    setDeleting(true)
    setError('')
    try {
      await webApi.uploadDelete(`${username}/${path}`)
      // Deleting the last file may remove its parent directory too. The root always exists.
      await router.push(routes.dir(username))
    } catch (error) {
      setError(error.response?.status === 403 ? 'Only admins can delete files.' : 'Could not delete the file. Please try again.')
    } finally {
      pending.current = false
      setDeleting(false)
    }
  }
  return <span>
    <button type="button" onClick={handleDelete} disabled={deleting}>
      <DeleteIcon /> {deleting ? 'Deleting…' : 'Delete file'}
    </button>
    {error && <p role="alert">{error}</p>}
  </span>
}

interface FileUpload { username: string; path: string; list: boolean }

function FileActions({ upload, loggedInUser }: { upload: FileUpload; loggedInUser?: UserType }) {
  const [listed, setListed] = React.useState(upload.list)
  const [saving, setSaving] = React.useState(false)
  const [error, setError] = React.useState('')
  React.useEffect(() => { setListed(upload.list) }, [upload.list])
  async function toggleList() {
    if (saving || (listed && !confirm('Unlist this file? It will remain accessible by direct URL.'))) return
    setSaving(true)
    setError('')
    try {
      await webApi.uploadUpdate(`${upload.username}/${upload.path}`, { list: !listed })
      setListed(!listed)
    } catch (error) {
      setError('Could not change file visibility. Please try again.')
    } finally {
      setSaving(false)
    }
  }
  return <div className="file-actions content-not-ourbigbook">
    {!listed && <><span className="pill"><UnlistedIcon /> Unlisted</span>{' '}</>}
    {!cant.editUpload(loggedInUser, upload.username) && <>
      <button type="button" className="modal" onClick={toggleList} disabled={saving}>
        {listed ? <><UnlistedIcon /> Unlist</> : <><SeeIcon /> List</>}
      </button>{' '}
    </>}
    {!cant.deleteUpload(loggedInUser) && <FileDeleteButton username={upload.username} path={upload.path} />}
    {error && <p role="alert">{error}</p>}
  </div>
}

export default function FilePage(props: ArticlePageProps & { fileUpload?: FileUpload; filePreview?: { author: UserType; path: string; render: string } }) {
  const { filePreview } = props
  const actions = props.fileUpload && <FileActions key={`${props.fileUpload.username}/${props.fileUpload.path}`} upload={props.fileUpload} loggedInUser={props.loggedInUser} />
  if (!filePreview) return <>{actions}<ArticlePage {...props} /></>
  return <>
    <MyHead title={`${filePreview.author.username}/${filePreview.path}`} />
    <div className="dir-page file-page content-not-ourbigbook">
      <FileDirectoryHeader author={filePreview.author} path={filePreview.path} isFile={true} />
      {actions}
      <div className="file-content ourbigbook" dangerouslySetInnerHTML={{ __html: filePreview.render }} />
    </div>
  </>
}