diff --git a/src/pages/notes.tsx b/src/pages/notes.tsx
index 30262ff..f0f59da 100644
--- a/src/pages/notes.tsx
+++ b/src/pages/notes.tsx
@@ -7,7 +7,7 @@ import generateId from '../utilities/Id'
import Link from 'next/link'
import { formatDate } from '../utilities/Date'
import { useRouter } from 'next/router'
-import { Trash2, FilePlus, FolderPlus } from 'react-feather'
+import { Trash2, FilePlus, FolderPlus, FileText, GitBranch, User } from 'react-feather'
const Navbar = styled('aside')({
width: 360,
@@ -22,6 +22,43 @@ const Navbar = styled('aside')({
},
})
+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',
+ '::before': {
+ content: "''",
+ display: 'block',
+ top: 0,
+ left: 0,
+ width: '100%',
+ height: '100%',
+ position: 'absolute',
+ zIndex: -1,
+ backgroundColor: 'white',
+ opacity: 0.5,
+ },
+ '@media (min-width: 1080px)': {
+ flex: 'auto',
+ },
+})
+
+const SecondaryNavItemsOverflow = styled('div')({
+ overflow: 'auto',
+ width: '100%',
+ height: '100%',
+})
+
const Main = styled('main')({
margin: '2rem 0',
'@media (min-width: 1080px)': {
@@ -40,11 +77,17 @@ const Container = styled('div')({
},
})
-const NavbarContainer = styled('span')({
+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',
- padding: '0 1rem',
boxSizing: 'border-box',
maxWidth: 360,
})
@@ -59,7 +102,6 @@ const TitleInput = styled('input')({
fontSize: '3rem',
fontWeight: 'bold',
outline: 0,
- marginBottom: '2rem',
})
const NoteLink = styled('a')({
@@ -127,6 +169,30 @@ const BinIcon = styled(Trash2)({
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 }) => {
@@ -145,7 +211,7 @@ const Notes = ({ id: idProp }) => {
timeoutRef.current = window.setTimeout(async () => {
const newNote = await Storage.saveNote({
...stateRef.current,
- updatedAt: new Date().toISOString(),
+ updatedAt: (stateRef.current.updatedAt = new Date().toISOString()),
})
if (router.query.id !== id) {
await router.push(`/notes/${id}`, undefined, { shallow: true })
@@ -174,6 +240,14 @@ const Notes = ({ id: idProp }) => {
autoSave()
}
+ const deleteNote = note => async () => {
+ setNotes(notes.filter(n => n.id !== note.id))
+ const result = await Storage.deleteNote(note)
+ if (!result) {
+ setNotes(notes)
+ }
+ }
+
React.useEffect(() => {
const loadNotes = async () => {
const theNotes = await Storage.loadNotes()
@@ -210,121 +284,168 @@ const Notes = ({ id: idProp }) => {
-
-
-
-
-
-
- Personal
-
-
-
-
-
-
-
-
-
-
-
- Create Folder
-
-
-
-
-
-
-
-
-
-
-
- Create Note
-
-
-
-
-
- {
- Array.isArray(notes!)
- && notes.map(n => (
-
- {
- n.id === id
- && (
-
- )
- }
-
-
-
-
- 0 ? 1 : 0.5, }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Personal
+
+
+
+
+
+
+
+
+
+
+
+ Create Folder
+
+
+
+
+
+
+
+
+
+
+
+ Create Note
+
+
+
+
+
+ {
+ Array.isArray(notes!)
+ && notes.map(n => (
+
+ {
+ n.id === id
+ && (
+
+ )
+ }
+
- {n.title.length > 0 ? n.title : '(untitled)'}
-
-
- {' '}
-
-
-
-
-
-
-
-
-
-
-
-
- ))
- }
-
-
-
-
-
-
- View Binned Notes
-
-
-
-
-
+
+
+
+ 0 ? 1 : 0.5, }}
+ >
+ {n.title.length > 0 ? n.title : '(untitled)'}
+
+
+ {' '}
+
+
+
+
+
+
+
+
+
+
+
+
+ ))
+ }
+
+
+
+
+
+
+ View Binned Notes
+
+
+
+
+
+
+
+
+
@@ -332,11 +453,26 @@ const Notes = ({ id: idProp }) => {
Array.isArray(notes!)
&& (
-
+
+
+
+ {
+ stateRef.current.updatedAt
+ && router.query.id
+ && (
+
+ )
+ }
+
+
async (req, res) => {
const methodHandlers = {
'GET': Service.getSingle(repository),
'PUT': Service.save(repository)(req.body),
+ 'DELETE': Service.remove(repository)
}
const { [req.method as keyof typeof methodHandlers]: handler = null } = methodHandlers
@@ -50,7 +51,11 @@ export const item = (Model, Service) => async (req, res) => {
try {
const { status, data, } = await handler(id)
res.statusCode = status
- res.json(data)
+ if (data) {
+ res.json(data)
+ return
+ }
+ res.end()
} catch (err) {
console.log('ERROR', err)
const { status, data, } = err
diff --git a/src/services/Folder.ts b/src/services/Folder.ts
index 4cec27c..1fcd502 100644
--- a/src/services/Folder.ts
+++ b/src/services/Folder.ts
@@ -1,7 +1,8 @@
-import Model from '../../models/Folder'
+import FolderModel from '../../models/Folder'
import Instance from '../utilities/Instance'
import * as Response from '../utilities/Response'
-type ModelInstance = Instance
+
+type Folder = Instance
export const getSingle = repository => async (id: string) => {
const instance = await repository.findByPk(id)
@@ -20,7 +21,7 @@ export const getMultiple = repository => async (query: Record)
})
}
-export const save = repository => (body: Partial) => async (id: string, idColumnName = 'id') => {
+export const save = repository => (body: Partial) => async (id: string, idColumnName = 'id') => {
const [newInstance, created] = await repository.findOrCreate({
where: { [idColumnName]: id },
defaults: {
@@ -44,3 +45,12 @@ export const save = repository => (body: Partial) => async (id: s
data: updatedInstance.toJSON()
})
}
+
+export const remove = repository => async (id: string) => {
+ const instanceDAO = repository.findByPk(id)
+ if (instanceDAO === null) {
+ throw new Response.NotFound({ message: 'Not found.' })
+ }
+ await instanceDAO.destroy()
+ return new Response.Destroyed()
+}
diff --git a/src/services/Note.ts b/src/services/Note.ts
index b5d95c0..6c45193 100644
--- a/src/services/Note.ts
+++ b/src/services/Note.ts
@@ -1,7 +1,8 @@
import Model from '../../models/Note'
-import Instance from '../utilities/Instance'
+import InferType from '../utilities/Instance'
import * as Response from '../utilities/Response'
-type ModelInstance = Instance
+
+type ModelInstance = InferType
export const getSingle = repository => async (id: string) => {
const instanceDAO = await repository.findByPk(id)
@@ -66,3 +67,12 @@ export const save = repository => (body: Partial) => async (id: s
}
})
}
+
+export const remove = repository => async (id: string) => {
+ const instanceDAO = await repository.findByPk(id)
+ if (instanceDAO === null) {
+ throw new Response.NotFound({ message: 'Not found.' })
+ }
+ await instanceDAO.destroy()
+ return new Response.Destroyed()
+}
diff --git a/src/services/Storage.ts b/src/services/Storage.ts
index d64b535..cf928f5 100644
--- a/src/services/Storage.ts
+++ b/src/services/Storage.ts
@@ -1,13 +1,13 @@
-import {addTime, TimeDivision} from '../utilities/Date'
+import { addTime, TimeDivision } from '../utilities/Date'
import * as Serialization from '../utilities/Serialization'
import LocalStorage from './LocalStorage'
-type LoadItemParams = {
+type StorageParams = {
id: string,
url: string,
}
-type LoadItems = >(params: LoadItemParams) => () => Promise
+type LoadItems = >(params: StorageParams) => () => Promise
const loadItems: LoadItems = >(params) => async (): Promise => {
const { id, url, } = params
@@ -39,7 +39,7 @@ const loadItems: LoadItems = >(params) => asyn
return localData.items
}
-type SaveItem = >(p: LoadItemParams) => (item: T) => Promise
+type SaveItem = >(p: StorageParams) => (item: T) => Promise
const saveItem: SaveItem = >(params) => async (item) => {
const { id: storageId, url } = params
@@ -50,10 +50,9 @@ const saveItem: SaveItem = >(params) => async
)
const localData = storage.getItem(storageId, {
expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(),
- items: []
+ items: [],
})
- console.log(localData)
- const localItems = localData.items
+ const { items: localItems } = localData
const { id: itemId, ...theBody } = item
const theItems: T[] = (
localItems.some(i => i.id === itemId)
@@ -76,7 +75,32 @@ const saveItem: SaveItem = >(params) => async
return responseBody as T
}
+type DeleteItem = >(p: StorageParams) => (item: T) => Promise
+
+const deleteItem: DeleteItem = >(params) => async (item) => {
+ const { id: storageId, url } = params
+ const storage = new LocalStorage(
+ window.localStorage,
+ Serialization.serialize,
+ Serialization.deserialize
+ )
+
+ const localData = storage.getItem(storageId, {
+ expiry: addTime(new Date(), 30)(TimeDivision.DAYS).getTime(),
+ items: [],
+ })
+ const { items: localItems } = localData
+ const { id: itemId } = item
+ const theItems: T[] = localItems.filter(i => i.id !== itemId)
+ storage.setItem(storageId, { ...localData, items: theItems })
+ const response = await window.fetch(`${url}/${itemId}`, {
+ method: 'delete',
+ })
+ return response.status === 204
+}
+
export const loadNotes = loadItems({ id: 'notes', url: '/api/notes' })
export const loadFolders = loadItems({ id: 'folders', url: '/api/folders' })
export const saveNote = saveItem({ id: 'notes', url: '/api/notes' })
export const saveFolder = saveItem({ id: 'folders', url: '/api/folders' })
+export const deleteNote = deleteItem({ id: 'notes', url: '/api/notes' })
diff --git a/src/utilities/Instance.ts b/src/utilities/Instance.ts
index d2e8848..c3dfeef 100644
--- a/src/utilities/Instance.ts
+++ b/src/utilities/Instance.ts
@@ -1,5 +1,35 @@
-type Instance = {
- [key in keyof T]: unknown
+import * as Sequelize from 'sequelize'
+
+type ModelAttribute = {
+ allowNull?: boolean,
+ primaryKey?: boolean,
+ type: Sequelize.DataType,
+}
+
+type Model = {
+ tableName?: string,
+ modelName?: string,
+ options?: {
+ timestamps?: boolean,
+ paranoid?: boolean,
+ createdAt?: string | boolean,
+ updatedAt?: string | boolean,
+ deletedAt?: string | boolean,
+ },
+ rawAttributes: Record,
+}
+
+type InferType = (
+ V extends typeof Sequelize.STRING ? string :
+ V extends typeof Sequelize.TEXT ? string :
+ V extends typeof Sequelize.DATE ? Date :
+ V extends typeof Sequelize.DATEONLY ? Date :
+ V extends typeof Sequelize.UUIDV4 ? string :
+ unknown
+)
+
+type InferProps = {
+ [K in keyof M['rawAttributes']]-?: InferType
}
-export default Instance
+export default InferProps
diff --git a/src/utilities/Response.ts b/src/utilities/Response.ts
index ea8876e..fd48394 100644
--- a/src/utilities/Response.ts
+++ b/src/utilities/Response.ts
@@ -43,3 +43,7 @@ export class Retrieved> implements Response {
this.data = params.data
}
}
+
+export class Destroyed implements Response {
+ public readonly status = 204
+}