|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- import * as React from 'react'
- import Head from 'next/head'
- import styled from 'styled-components'
- import Editor from '../components/Editor/Editor'
- import * as Storage from '../services/Storage'
- import generateId from '../utilities/Id'
- import Link from 'next/link'
- import { formatDate } from '../utilities/Date'
- import { useRouter } from 'next/router'
- import { XCircle } from 'react-feather'
-
- const Navbar = styled('aside')({
- width: 360,
- height: '100%',
- position: 'fixed',
- top: 0,
- left: -360,
- backgroundColor: 'yellow',
- '@media (min-width: 1080px)': {
- width: `${100 / 3}%`,
- left: 0,
- },
- })
-
- 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 NavbarContainer = styled('span')({
- display: 'block',
- width: '100%',
- margin: '0 0 0 auto',
- padding: '0 1rem',
- 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',
- outline: 0,
- marginBottom: '2rem',
- })
-
- const NoteLink = styled('a')({
- display: 'flex',
- textDecoration: 'none',
- color: 'inherit',
- height: '4rem',
- alignItems: 'center',
- position: 'relative',
- })
-
- const NoteLinkTitle = styled('strong')({
- display: 'block',
- })
-
- const LinkContainer = styled('div')({
- position: 'relative',
- })
-
- 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')({
- opacity: 0.125,
- backgroundColor: 'currentColor',
- top: 0,
- left: 0,
- width: '100%',
- height: '100%',
- position: 'absolute',
- })
-
- type NoteInstance = { id: string, title: string, content?: object, updatedAt: string, }
-
- const Notes = ({ id: idProp }) => {
- 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()
-
- const autoSave = () => {
- if (timeoutRef.current !== null) {
- window.clearTimeout(timeoutRef.current)
- }
- timeoutRef.current = window.setTimeout(async () => {
- const newNote = await Storage.saveNote({
- ...stateRef.current,
- title,
- updatedAt: new Date().toISOString(),
- })
- if (router.query.id !== id) {
- await router.push(`/notes/${id}`, undefined, { shallow: true })
- }
- setNotes(oldNotes => {
- let notes
- if (oldNotes.some((a) => a.id === id)) {
- notes = oldNotes.map(n => n.id === id ? newNote : n)
- } else {
- notes = [newNote, ...oldNotes]
- }
- return notes.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
- })
- timeoutRef.current = null
- }, 3000)
- }
-
- const handleEditorChange = e => {
- stateRef.current.content = e
- autoSave()
- }
-
- const handleTitleChange = e => {
- setTitle(e.target.value)
- autoSave()
- }
-
- React.useEffect(() => {
- const loadNotes = async () => {
- const theNotes = await Storage.loadNotes()
- setNotes(theNotes)
- }
- loadNotes()
- }, [])
-
- React.useEffect(() => {
- const loadFolders = async () => {
- const theFolders = await Storage.loadFolders()
- setFolders(theFolders)
- }
- loadFolders()
- }, [])
-
- 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])
-
- React.useEffect(() => {
- autoSave()
- }, [title])
-
- return (
- <React.Fragment>
- <Head>
- <title>{ idProp === undefined ? 'Notes | New Note' : `Notes | ${title.length > 0 ? title : '(untitled)'}`}</title>
- <link rel="icon" href="/favicon.ico" />
- </Head>
- <Navbar>
- <Link
- href={{
- pathname: '/notes',
- }}
- passHref
- >
- <NoteLink>
- <NavbarContainer>
- <NoteLinkTitle>
- New Note
- </NoteLinkTitle>
- </NavbarContainer>
- </NoteLink>
- </Link>
- {
- Array.isArray(notes!)
- && notes.map(n => (
- <LinkContainer
- key={n.id}
- >
- {
- n.id === id
- && (
- <NoteLinkBackground />
- )
- }
- <Link
- href={{
- pathname: '/notes/[id]',
- query: { id: n.id },
- }}
- passHref
- >
- <NoteLink>
- <NavbarContainer>
- <NoteLinkTitle
- style={{ opacity: n.title.length > 0 ? 1 : 0.5, }}
- >
- {n.title.length > 0 ? n.title : '(untitled)'}
- </NoteLinkTitle>
- {' '}
- <small>
- <time
- dateTime={new Date(n.updatedAt).toISOString()}
- >
- Last updated {formatDate(new Date(n.updatedAt))}
- </time>
- </small>
- </NavbarContainer>
- </NoteLink>
- </Link>
- <NoteActions>
- <NoteAction>
- <XCircle />
- </NoteAction>
- </NoteActions>
- </LinkContainer>
- ))
- }
- </Navbar>
- <Main>
- <Container>
- {
- Array.isArray(notes!)
- && (
- <React.Fragment>
- <TitleInput
- placeholder="Title"
- value={title}
- onChange={handleTitleChange}
- />
- <Editor
- autoFocus={false}
- key={id}
- content={stateRef.current ? stateRef.current.content : undefined}
- onChange={handleEditorChange}
- 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
|