Monorepo containing core modules of Zeichen.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

304 lignes
7.8 KiB

  1. import * as React from 'react'
  2. import Head from 'next/head'
  3. import styled from 'styled-components'
  4. import { useRouter } from 'next/router'
  5. import Editor from '../components/Editor/Editor'
  6. import Navbar from '../components/Navbar/Navbar'
  7. import generateId from '../utilities/Id'
  8. import { formatDate } from '../utilities/Date'
  9. import * as Note from '../controllers/Note'
  10. import * as Folder from '../controllers/Folder'
  11. const Main = styled('main')({
  12. margin: '2rem 0',
  13. '@media (min-width: 1080px)': {
  14. paddingLeft: `${100 / 3}%`,
  15. boxSizing: 'border-box',
  16. },
  17. })
  18. const Container = styled('div')({
  19. width: '100%',
  20. margin: '0 auto',
  21. padding: '0 1rem',
  22. boxSizing: 'border-box',
  23. '@media (min-width: 720px)': {
  24. maxWidth: 720,
  25. },
  26. })
  27. const TitleInput = styled('input')({
  28. border: 0,
  29. background: 'transparent',
  30. padding: 0,
  31. display: 'block',
  32. width: '100%',
  33. font: 'inherit',
  34. fontSize: '3rem',
  35. fontWeight: 'bold',
  36. color: 'inherit',
  37. outline: 0,
  38. })
  39. const PostMeta = styled('small')({
  40. opacity: 0.5,
  41. height: '1.25rem',
  42. display: 'block',
  43. lineHeight: 1.25,
  44. })
  45. const PostPrimary = styled('div')({
  46. marginBottom: '2rem',
  47. })
  48. type NoteInstance = { id: string, title: string, content?: object, updatedAt: string, }
  49. const Notes = ({ id: idProp }) => {
  50. // TODO remove extra state for ID
  51. const [id, setId, ] = React.useState(idProp)
  52. const [title, setTitle, ] = React.useState('')
  53. const [notes, setNotes, ] = React.useState(null)
  54. const [folders, setFolders, ] = React.useState(null)
  55. const stateRef = React.useRef<NoteInstance>({ id, title: '', updatedAt: new Date().toISOString(), })
  56. const timeoutRef = React.useRef<number>(null)
  57. const router = useRouter()
  58. React.useEffect(() => {
  59. Note.load({ setNotes })
  60. }, [])
  61. React.useEffect(() => {
  62. Folder.load({ setFolders })
  63. }, [])
  64. React.useEffect(() => {
  65. if (!Array.isArray(notes!)) {
  66. return
  67. }
  68. const theNote = notes.find(n => n.id === id)
  69. stateRef.current = theNote ? theNote : { id, title: '', updatedAt: new Date().toISOString(), }
  70. setTitle(stateRef.current.title)
  71. }, [id, notes])
  72. React.useEffect(() => {
  73. setId(idProp || generateId())
  74. }, [idProp])
  75. return (
  76. <React.Fragment>
  77. <Head>
  78. <title>{ idProp === undefined ? 'Notes | New Note' : `Notes | Edit Note - ${title.length > 0 ? title : '(untitled)'}`}</title>
  79. <link rel="icon" href="/favicon.ico" />
  80. </Head>
  81. <Navbar
  82. secondaryVisible={Boolean(router.query.navbar)}
  83. primaryItemsStart={[
  84. {
  85. id: 'sidebar',
  86. mobileOnly: true,
  87. href: {
  88. pathname: router.pathname,
  89. query: {
  90. ...router.query,
  91. navbar: 'true',
  92. },
  93. },
  94. iconName: 'menu',
  95. title: 'Notes',
  96. },
  97. {
  98. id: 'folders',
  99. active: router.pathname.startsWith('/folders'),
  100. href: {
  101. pathname: '/folders',
  102. },
  103. iconName: 'note',
  104. title: 'Notes',
  105. },
  106. {
  107. id: 'search',
  108. href: {
  109. pathname: '/notes',
  110. query: {
  111. action: 'search',
  112. },
  113. },
  114. iconName: 'search',
  115. title: 'Search',
  116. },
  117. {
  118. id: 'binned',
  119. href: {
  120. pathname: '/notes',
  121. query: {
  122. status: 'binned',
  123. },
  124. },
  125. iconName: 'bin',
  126. title: 'View Binned Notes',
  127. },
  128. ]}
  129. primaryItemsEnd={[
  130. {
  131. id: 'user',
  132. href: {
  133. pathname: '/me',
  134. },
  135. iconName: 'user',
  136. title: 'User',
  137. },
  138. ]}
  139. secondaryItemsHeader={[
  140. {
  141. id: 'parent',
  142. href: {
  143. pathname: '/notes',
  144. query: {
  145. folder: '00000000-0000-0000-000000000000',
  146. },
  147. },
  148. iconName: 'back',
  149. title: 'Folder Name',
  150. // todo use history back
  151. },
  152. {
  153. id: 'note',
  154. href: {
  155. pathname: '/notes',
  156. query: {
  157. action: 'new',
  158. parentFolderId: '00000000-0000-0000-000000000000',
  159. },
  160. },
  161. iconName: 'new-note',
  162. title: 'Create Note',
  163. },
  164. {
  165. id: 'folder',
  166. href: {
  167. pathname: '/folders',
  168. query: {
  169. action: 'new',
  170. parentFolderId: '00000000-0000-0000-000000000000',
  171. },
  172. },
  173. iconName: 'new-folder',
  174. title: 'Create Child Folder',
  175. },
  176. {
  177. id: 'map',
  178. href: {
  179. pathname: '/notes',
  180. query: {
  181. action: 'view-map',
  182. parentFolderId: '00000000-0000-0000-000000000000',
  183. },
  184. },
  185. iconName: 'mind-map',
  186. title: 'View Folder Mind Map',
  187. },
  188. ]}
  189. secondaryItems={
  190. Array.isArray(notes!)
  191. ? notes.map(n => ({
  192. id: n.id,
  193. active: n.id === id,
  194. href: {
  195. pathname: '/notes/[id]',
  196. query: { id: n.id },
  197. },
  198. iconName: 'note',
  199. replace: true,
  200. title: n.title.trim(),
  201. subtitle: (
  202. <React.Fragment>
  203. {'Last updated '}
  204. <time
  205. dateTime={new Date(n.updatedAt).toISOString()}
  206. >
  207. {formatDate(new Date(n.updatedAt))}
  208. </time>
  209. </React.Fragment>
  210. ),
  211. actions: [
  212. {
  213. id: 'bin',
  214. iconName: 'bin',
  215. onClick: Note.remove({ setNotes, notes, router, })(n),
  216. }
  217. ],
  218. }))
  219. : []
  220. }
  221. />
  222. <Main>
  223. <Container>
  224. {
  225. Array.isArray(notes!)
  226. && (
  227. <React.Fragment>
  228. <PostPrimary>
  229. <TitleInput
  230. placeholder="Title"
  231. value={title}
  232. onChange={Note.updateTitle({
  233. stateRef,
  234. timeoutRef,
  235. router,
  236. id,
  237. setNotes,
  238. setTitle,
  239. })}
  240. />
  241. <PostMeta>
  242. {
  243. stateRef.current.updatedAt
  244. && router.query.id
  245. && (
  246. <time
  247. dateTime={new Date(stateRef.current.updatedAt).toISOString()}
  248. >
  249. Last updated {formatDate(new Date(stateRef.current.updatedAt))}
  250. </time>
  251. )
  252. }
  253. </PostMeta>
  254. </PostPrimary>
  255. <Editor
  256. autoFocus={false}
  257. key={id}
  258. content={stateRef.current ? stateRef.current.content : undefined}
  259. onChange={Note.updateContent({
  260. stateRef,
  261. timeoutRef,
  262. router,
  263. id,
  264. setNotes,
  265. })}
  266. placeholder="Start typing here..."
  267. />
  268. </React.Fragment>
  269. )
  270. }
  271. </Container>
  272. </Main>
  273. </React.Fragment>
  274. )
  275. }
  276. export const getServerSideProps = async ctx => {
  277. if (ctx.params) {
  278. return {
  279. props: {
  280. id: ctx.params?.id
  281. }
  282. }
  283. }
  284. return {
  285. props: {}
  286. }
  287. }
  288. export default Notes