|
- import * as React from 'react'
- import Head from 'next/head'
- import styled from 'styled-components'
- import { Trash2, FilePlus, FolderPlus, FileText, GitBranch, User } from 'react-feather'
- import { useRouter } from 'next/router'
- import Editor from '../components/Editor/Editor'
- import generateId from '../utilities/Id'
- import Link from 'next/link'
- import { formatDate } from '../utilities/Date'
- import * as Note from '../controllers/Note'
- import * as Folder from '../controllers/Folder'
-
- const Navbar = styled('aside')({
- width: 360,
- height: '100%',
- position: 'fixed',
- top: 0,
- left: -360,
- backgroundColor: 'var(--color-fg)',
- color: 'var(--color-bg)',
- '@media (min-width: 1080px)': {
- width: `${100 / 3}%`,
- left: 0,
- },
- })
-
- const PrimaryNavItems = styled('nav')({
- height: '100%',
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'space-between',
- alignItems: 'stretch',
- '@media (min-width: 1080px)': {
- width: '4rem',
- },
- })
-
- const SecondaryNavItems = styled('nav')({
- height: '100%',
- position: 'relative',
- backgroundColor: 'var(--color-bg)',
- '::before': {
- content: "''",
- display: 'block',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- position: 'absolute',
- backgroundColor: 'black',
- opacity: 0.03125,
- },
- '@media (min-width: 1080px)': {
- flex: 'auto',
- },
- })
-
- const SecondaryNavItemsOverflow = styled('div')({
- overflow: 'auto',
- width: '100%',
- height: '100%',
- position: 'relative',
- })
-
- const Main = styled('main')({
- margin: '2rem 0',
- '@media (min-width: 1080px)': {
- paddingLeft: `${100 / 3}%`,
- boxSizing: 'border-box',
- },
- })
-
- const Container = styled('div')({
- width: '100%',
- margin: '0 auto',
- padding: '0 1rem',
- boxSizing: 'border-box',
- '@media (min-width: 720px)': {
- maxWidth: 720,
- },
- })
-
- const NavbarItems = styled('div')({
- display: 'flex',
- width: '100%',
- height: '100%',
- })
-
- const NavbarContainer = styled('div')({
- display: 'block',
- width: '100%',
- height: '100%',
- margin: '0 0 0 auto',
- boxSizing: 'border-box',
- maxWidth: 360,
- })
-
- const TitleInput = styled('input')({
- border: 0,
- background: 'transparent',
- padding: 0,
- display: 'block',
- width: '100%',
- font: 'inherit',
- fontSize: '3rem',
- fontWeight: 'bold',
- color: 'inherit',
- outline: 0,
- })
-
- const NoteLink = styled('a')({
- display: 'flex',
- textDecoration: 'none',
- color: 'inherit',
- height: '4rem',
- alignItems: 'center',
- position: 'relative',
- })
-
- const NoteLinkPrimary = styled('div')({
- display: 'block',
- })
-
- const NoteLinkTitle = styled('strong')({
- verticalAlign: 'middle',
- })
-
- const LinkContainer = styled('div')({
- position: 'relative',
- color: 'var(--color-primary, blue)',
- })
-
- const NoteActions = styled('div')({
- display: 'flex',
- position: 'absolute',
- alignItems: 'stretch',
- top: 0,
- right: 0,
- height: '100%',
- })
-
- const NoteAction = styled('button')({
- height: '100%',
- width: '4rem',
- background: 'transparent',
- border: 0,
- color: 'inherit',
- cursor: 'pointer',
- outline: 0,
- })
-
- const NoteLinkBackground = styled('span')({
- '::before': {
- content: "''",
- position: 'absolute',
- top: 0,
- left: 0,
- width: '0.25rem',
- height: '100%',
- display: 'block',
- backgroundColor: 'currentColor',
- },
- '::after': {
- content: "''",
- opacity: 0.125,
- backgroundColor: 'currentColor',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- position: 'absolute',
- },
- })
-
- const NewIcon = styled(FilePlus)({
- verticalAlign: 'middle',
- marginRight: '0.5rem',
- })
-
- const NewFolderIcon = styled(FolderPlus)({
- verticalAlign: 'middle',
- marginRight: '0.5rem',
- })
-
- const BinIcon = styled(Trash2)({
- verticalAlign: 'middle',
- marginRight: '0.5rem',
- })
-
- const NavbarItemContent = styled('span')({
- padding: '0 1rem',
- boxSizing: 'border-box',
- })
-
- const PrimaryNavItem = styled('a')({
- width: '4rem',
- height: '4rem',
- display: 'grid',
- placeContent: 'center',
- color: 'inherit',
- })
-
- const PostMeta = styled('small')({
- opacity: 0.5,
- height: '1.25rem',
- display: 'block',
- lineHeight: 1.25,
- })
-
- const PostPrimary = styled('div')({
- marginBottom: '2rem',
- })
-
- type NoteInstance = { id: string, title: string, content?: object, updatedAt: string, }
-
- const Notes = ({ id: idProp }) => {
- // TODO remove extra state for ID
- const [id, setId, ] = React.useState(idProp)
- const [title, setTitle, ] = React.useState('')
- const [notes, setNotes, ] = React.useState(null)
- const [folders, setFolders, ] = React.useState(null)
- const stateRef = React.useRef<NoteInstance>({ id, title: '', updatedAt: new Date().toISOString(), })
- const timeoutRef = React.useRef<number>(null)
- const router = useRouter()
-
- React.useEffect(() => {
- Note.load({ setNotes })
- }, [])
-
- React.useEffect(() => {
- Folder.loadFolders({ setFolders })
- }, [])
-
- React.useEffect(() => {
- if (!Array.isArray(notes!)) {
- return
- }
- const theNote = notes.find(n => n.id === id)
- stateRef.current = theNote ? theNote : { id, title: '', updatedAt: new Date().toISOString(), }
- setTitle(stateRef.current.title)
- }, [id, notes])
-
- React.useEffect(() => {
- setId(idProp || generateId())
- }, [idProp])
-
- return (
- <React.Fragment>
- <Head>
- <title>{ idProp === undefined ? 'Notes | New Note' : `Notes | Edit Note - ${title.length > 0 ? title : '(untitled)'}`}</title>
- <link rel="icon" href="/favicon.ico" />
- </Head>
- <Navbar>
- <NavbarContainer>
- <NavbarItems>
- <PrimaryNavItems>
- <div>
- <Link
- href={{
- pathname: '/notes',
- }}
- passHref
- >
- <PrimaryNavItem>
- <FileText />
- </PrimaryNavItem>
- </Link>
- <Link
- href={{
- pathname: '/graph',
- }}
- passHref
- >
- <PrimaryNavItem>
- <GitBranch />
- </PrimaryNavItem>
- </Link>
- </div>
- <div>
- <Link
- href={{
- pathname: '/me',
- }}
- passHref
- >
- <PrimaryNavItem>
- <User />
- </PrimaryNavItem>
- </Link>
- </div>
- </PrimaryNavItems>
- <SecondaryNavItems>
- <SecondaryNavItemsOverflow>
- <LinkContainer>
- <Link
- href={{
- pathname: '/profile',
- }}
- passHref
- >
- <NoteLink>
- <NavbarItemContent>
- <NoteLinkPrimary>
- <NewFolderIcon />
- <NoteLinkTitle>
- Personal
- </NoteLinkTitle>
- </NoteLinkPrimary>
- </NavbarItemContent>
- </NoteLink>
- </Link>
- </LinkContainer>
- <LinkContainer>
- <Link
- href={{
- pathname: '/folders/new',
- }}
- passHref
- >
- <NoteLink>
- <NavbarItemContent>
- <NoteLinkPrimary>
- <NewFolderIcon />
- <NoteLinkTitle>
- Create Folder
- </NoteLinkTitle>
- </NoteLinkPrimary>
- </NavbarItemContent>
- </NoteLink>
- </Link>
- </LinkContainer>
- <LinkContainer>
- <Link
- href={{
- pathname: '/notes',
- }}
- passHref
- >
- <NoteLink>
- <NavbarItemContent>
- <NoteLinkPrimary>
- <NewIcon />
- <NoteLinkTitle>
- Create Note
- </NoteLinkTitle>
- </NoteLinkPrimary>
- </NavbarItemContent>
- </NoteLink>
- </Link>
- </LinkContainer>
- {
- Array.isArray(notes!)
- && notes.map(n => (
- <LinkContainer
- key={n.id}
- >
- {
- n.id === id
- && (
- <NoteLinkBackground />
- )
- }
- <Link
- href={{
- pathname: '/notes/[id]',
- query: { id: n.id },
- }}
- replace
- passHref
- >
- <NoteLink>
- <NavbarItemContent>
- <NoteLinkPrimary>
- <NoteLinkTitle
- style={{ opacity: n.title.length > 0 ? 1 : 0.5, }}
- >
- {n.title.length > 0 ? n.title : '(untitled)'}
- </NoteLinkTitle>
- </NoteLinkPrimary>
- {' '}
- <PostMeta>
- <time
- dateTime={new Date(n.updatedAt).toISOString()}
- >
- Last updated {formatDate(new Date(n.updatedAt))}
- </time>
- </PostMeta>
- </NavbarItemContent>
- </NoteLink>
- </Link>
- <NoteActions>
- <NoteAction
- onClick={Note.remove({ setNotes, notes, router, })(n)}
- >
- <Trash2 />
- </NoteAction>
- </NoteActions>
- </LinkContainer>
- ))
- }
- <LinkContainer>
- <Link
- href={{
- pathname: '/bin',
- }}
- passHref
- >
- <NoteLink>
- <NavbarItemContent>
- <NoteLinkPrimary>
- <BinIcon />
- <NoteLinkTitle>
- View Binned Notes
- </NoteLinkTitle>
- </NoteLinkPrimary>
- </NavbarItemContent>
- </NoteLink>
- </Link>
- </LinkContainer>
- </SecondaryNavItemsOverflow>
- </SecondaryNavItems>
- </NavbarItems>
- </NavbarContainer>
- </Navbar>
- <Main>
- <Container>
- {
- Array.isArray(notes!)
- && (
- <React.Fragment>
- <PostPrimary>
- <TitleInput
- placeholder="Title"
- value={title}
- onChange={Note.updateTitle({
- stateRef,
- timeoutRef,
- router,
- id,
- setNotes,
- setTitle,
- })}
- />
- <PostMeta>
- {
- stateRef.current.updatedAt
- && router.query.id
- && (
- <time
- dateTime={new Date(stateRef.current.updatedAt).toISOString()}
- >
- Last updated {formatDate(new Date(stateRef.current.updatedAt))}
- </time>
- )
- }
- </PostMeta>
- </PostPrimary>
- <Editor
- autoFocus={false}
- key={id}
- content={stateRef.current ? stateRef.current.content : undefined}
- onChange={Note.updateContent({
- stateRef,
- timeoutRef,
- router,
- id,
- setNotes,
- })}
- placeholder="Start typing here."
- />
- </React.Fragment>
- )
- }
- </Container>
- </Main>
- </React.Fragment>
- )
- }
-
- export const getServerSideProps = async ctx => {
- if (ctx.params) {
- return {
- props: {
- id: ctx.params?.id
- }
- }
- }
-
- return {
- props: {}
- }
- }
-
- export default Notes
|