aboutsummaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/Notes/Notes.module.scss231
-rw-r--r--apps/Notes/components/Import.js88
-rw-r--r--apps/Notes/components/List.js45
-rw-r--r--apps/Notes/components/ListItem.js115
-rw-r--r--apps/Notes/components/NoteEdit.js83
-rw-r--r--apps/Notes/components/NoteView.js31
-rw-r--r--apps/Notes/helpers/import.js41
7 files changed, 315 insertions, 319 deletions
diff --git a/apps/Notes/Notes.module.scss b/apps/Notes/Notes.module.scss
new file mode 100644
index 0000000..e91dab9
--- /dev/null
+++ b/apps/Notes/Notes.module.scss
@@ -0,0 +1,231 @@
+.notesList {
+ @keyframes fade-in {
+ from {opacity: 0;}
+ to {opacity: 1;}
+ }
+
+ display: block;
+ overflow: auto;
+ width: 100%;
+ table-layout: fixed;
+ word-wrap: break-word;
+ height: 100%;
+ margin-top: -1em;
+ padding-top: 1em;
+
+ & > tbody,
+ & > thead {
+ display: block;
+ }
+
+ & tr {
+ display: flex;
+ padding: .5em;
+ }
+
+ & th {
+ font-weight: 600;
+ text-align: left;
+ min-width: 10em;
+ white-space: nowrap;
+ padding-bottom: .5em;
+ cursor: pointer;
+ line-height: 1.1em;
+
+ &:first-of-type {
+ width: 99%;
+ }
+
+ svg {
+ animation: fade-in .3s;
+ }
+ }
+}
+
+.listItem {
+ td {
+ min-width: 10em;
+ white-space: nowrap;
+
+ &:first-of-type {
+ width: 99%;
+ display: flex;
+ padding-right: 1em;
+
+ & > span:nth-child(1) {
+ text-overflow: ellipsis;
+ flex-grow: 1;
+ }
+
+ & > span:nth-child(2),
+ & > span:nth-child(3) {
+ margin-left: .5em;
+ padding: .15em .5em;
+ line-height: 1em;
+ border-radius: 50%;
+ visibility: hidden;
+ opacity: 0;
+ font-size: 80%;
+ transition: .3s opacity linear .3s;
+ }
+ }
+ }
+
+ &:hover {
+ background: #eee;
+ border-radius: .5em;
+ cursor: pointer;
+
+ & > td:first-of-type > span:nth-child(2),
+ & > td:first-of-type > span:nth-child(3) {
+ color: #666;
+ visibility: visible;
+ opacity: 1;
+ }
+ }
+
+
+ & > td:first-of-type > span:nth-child(2):hover {
+ color: #333;
+ background-color: #deffde;
+ }
+
+ & > td:first-of-type > span:nth-child(3):hover {
+ background-color: #ffdede;
+ color: #333;
+ }
+}
+
+.noteView {
+ @keyframes fade-in {
+ from {opacity: 0;}
+ to {opacity: 1;}
+ }
+
+ background: white;
+ padding: 1rem;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ animation: fade-in .3s;
+
+ h2 {
+ font-size: 1.25em;
+ font-weight: 600;
+ padding: 1rem;
+ user-select: text;
+ }
+
+ p {
+ padding: 0 1rem 1rem;
+ white-space: pre-line;
+ user-select: text;
+ }
+}
+
+.noteEdit {
+ @keyframes fade-in {
+ from {opacity: 0;}
+ to {opacity: 1;}
+ }
+
+ display: flex;
+ flex-direction: column;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background: white;
+ padding: 1em 1em 2em;
+ animation: fade-in .3s;
+
+ h2 {
+ font-size: 1.2em;
+ margin-bottom: .5em;
+ }
+
+ form {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+ flex-grow: 1;
+
+ & > div {
+ text-align: right;
+ }
+ }
+
+ input[type=text] {
+ margin-bottom: .5rem;
+ height: 3rem;
+ border: none;
+ padding: 0.5rem;
+ font-size: 1rem;
+ // font-weight: 600;
+ border: 1px dashed #666;
+
+ &:placeholder {
+ font: inherit;
+ }
+ }
+
+ textarea {
+ font-size: 1rem;
+ flex-grow: 1;
+ resize: none;
+ height: 100%;
+ width: 100%;
+ border: none;
+ border: 1px dashed #666;
+ padding: 0.5rem;
+
+ &:placeholder {
+ font: inherit;
+ }
+ }
+}
+
+.import {
+ @keyframes fade-in {
+ from {opacity: 0;}
+ to {opacity: 1;}
+ }
+
+ background: white;
+ padding: 1rem;
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ animation: fade-in .3s;
+
+ form {
+ padding: 1em;
+ }
+
+ input[type=file] {
+ display: none;
+ }
+
+ label {
+ display: inline-block;
+ cursor: pointer;
+ }
+
+ li {
+ padding: .25em;
+ }
+
+ p {
+ padding: 1em 0;
+ }
+
+ .fa-check ~ span {
+ color: green;
+ }
+}
+
diff --git a/apps/Notes/components/Import.js b/apps/Notes/components/Import.js
index 71c08d6..2e2e5c7 100644
--- a/apps/Notes/components/Import.js
+++ b/apps/Notes/components/Import.js
@@ -1,58 +1,21 @@
+import styles from '../Notes.module.scss'
import React, {useState} from 'react'
import fetchJson from 'lib/fetchJson'
import useNotes from '../hooks/useNotes'
-import {faCheck, faTimes} from '@fortawesome/free-solid-svg-icons'
-import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
+import {state, color, handleImport, handleChange} from '../helpers/import'
const Import = ({action, setAction}) => {
const [files, setFiles] = useState()
const [done, setDone] = useState([])
const {mutateNotes} = useNotes()
- const state = i => done[i] && <span><FontAwesomeIcon icon={done[i] === 1 ? faCheck : faTimes} /></span>
-
- const readFileAsText = (file) => new Promise((resolve,reject) => {
- let fr = new FileReader()
-
- fr.onload = () => resolve(fr.result)
- fr.onerror = () => reject(fr)
-
- fr.readAsText(file)
- })
-
- const handleImport = async e => {
- e.preventDefault();
-
- Array.from(files).forEach(async (file, i) => {
- const title = file.name.replace(/\.[^/.]+$/, "")
- const content = await readFileAsText(file);
-
- try {
- const notes = await fetchJson('/api/notes', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({title, content}),
- })
- if (i === files.length - 1) await mutateNotes(notes)
- setDone((prev) => ({...prev, [i]: 1}))
- } catch (e) {
- setDone((prev) => ({...prev, [i]: 0}))
- }
- })
- }
-
- const handleChange = e => {
- setFiles(e.currentTarget.files)
- setDone([])
- }
-
return (
- <section>
+ <section className={styles.import}>
<div className='window__submenu'>
- <div onClick={() => { setAction('') }}>Back</div>
+ <div onClick={() => {setAction('')}}>Back</div>
</div>
<div className='window__scroll'>
- <form onSubmit={handleImport}>
+ <form onSubmit={e => handleImport(e, files, mutateNotes, setDone)}>
Import new notes:
<div>
<label className="window__button">
@@ -62,7 +25,7 @@ const Import = ({action, setAction}) => {
type="file"
multiple="multiple"
accept="text/plain"
- onChange={handleChange}
+ onChange={e => handleChange(e, setFiles, setDone)}
/>
</label>
</div>
@@ -70,7 +33,7 @@ const Import = ({action, setAction}) => {
<>
<p>Notes to import:</p>
<ul>
- {[...files].map((f, i) => <li key={f.name}>{f.name} {state(i)}</li>)}
+ {[...files].map((f, i) => <li style={color(done[i])} key={f.name}>{f.name} {state(done[i])}</li>)}
</ul>
{
done.length === 0
@@ -81,43 +44,6 @@ const Import = ({action, setAction}) => {
)}
</form>
</div>
- <style jsx>{`
- section {
- background: white;
- padding: 1rem;
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- animation: fade-in .3s;
- }
-
- form {
- padding: 1em;
- }
-
- input[type=file] {
- display: none;
- }
-
- label {
- display: inline-block;
- cursor: pointer;
- }
-
- ul {
- list-style: disc inside none;
- }
-
- li {
- padding: .25em;
- }
-
- p {
- padding: 1em 0;
- }
- `}</style>
</section>
)
}
diff --git a/apps/Notes/components/List.js b/apps/Notes/components/List.js
index ec19639..b9fde02 100644
--- a/apps/Notes/components/List.js
+++ b/apps/Notes/components/List.js
@@ -1,8 +1,8 @@
+import styles from '../Notes.module.scss'
import React, {useState, useEffect, useRef} from 'react'
import useUser from 'lib/useUser'
import useNotes from '../hooks/useNotes'
import useSort from '../hooks/useSort'
-import fetchJson from 'lib/fetchJson'
import {Layout} from 'components'
import ListItem from './ListItem'
import Actions from './Actions'
@@ -33,12 +33,12 @@ const List = () => {
<div onClick={() => setAction('import')}>Import</div>
<div onClick={() => {}}>Export</div>
</div>
- <table className="list">
+ <table className={styles.notesList}>
<thead>
<tr>
- <th className='list__title' onClick={() => sortBy(1)}>Title {sortedBy(1)}</th>
- <th className='list__date' onClick={() => sortBy(2)}>Created {sortedBy(2)}</th>
- <th className='list__date' onClick={() => sortBy(3)}>Modified {sortedBy(3)}</th>
+ <th onClick={() => sortBy(1)}>Title {sortedBy(1)}</th>
+ <th onClick={() => sortBy(2)}>Created {sortedBy(2)}</th>
+ <th onClick={() => sortBy(3)}>Modified {sortedBy(3)}</th>
</tr>
</thead>
<tbody>
@@ -68,41 +68,6 @@ const List = () => {
/>
)
}
- <style jsx>{`
- table {
- display: block;
- overflow: auto;
- width: 100%;
- table-layout: fixed;
- word-wrap: break-word;
- height: 100%;
- margin-top: -1em;
- padding-top: 1em;
- }
-
- tbody, thead {
- display: block;
- }
-
- tr {
- display: flex;
- padding: 0 .5em;
- }
-
- th {
- font-weight: 600;
- text-align: left;
- min-width: 10em;
- white-space: nowrap;
- padding-bottom: .5em;
- cursor: pointer;
- line-height: 1.1em;
- }
-
- th:first-of-type {
- width: 99%;
- }
- `}</style>
</>
)
}
diff --git a/apps/Notes/components/ListItem.js b/apps/Notes/components/ListItem.js
index 42d67c0..5d3b91f 100644
--- a/apps/Notes/components/ListItem.js
+++ b/apps/Notes/components/ListItem.js
@@ -1,3 +1,4 @@
+import styles from '../Notes.module.scss'
import React, { useContext } from 'react'
import fetchJson from 'lib/fetchJson'
import {getNote, removeNote} from '../helpers/noteActions.js'
@@ -22,97 +23,33 @@ const ListItem = ({note, setAction, setFetchedNote}) => {
}
return (
- <>
- <tr key={note._id}>
- <td
- onClick={() => handleNoteAction('showNote', note)}
+ <tr className={styles.listItem} key={note._id}>
+ <td
+ onClick={() => handleNoteAction('showNote', note)}
+ >
+ <span>{`${note.title}`}</span>
+ <span
+ onClick={e => handleNoteAction('editNote', note, e)}
>
- <span>{`${note.title}`}</span>
- <span
- onClick={e => handleNoteAction('editNote', note, e)}
- >
- <FontAwesomeIcon icon={faEdit} />
- </span>
- <span
- onClick={(e) => removeNote(e, note._id, mutateNotes, setPopup, setAction)}
- >
- <FontAwesomeIcon icon={faTrash} />
- </span>
- </td>
- <td
- onClick={() => handleNoteAction('showNote', note)}
+ <FontAwesomeIcon icon={faEdit} />
+ </span>
+ <span
+ onClick={(e) => removeNote(e, note._id, mutateNotes, setPopup, setAction)}
>
- {datestring(note.created_at)}
- </td>
- <td
- onClick={() => handleNoteAction('showNote', note)}
- >
- {datestring(note.updated_at)}
- </td>
- </tr>
- <style jsx>{`
- tr {
- display: flex;
- padding: .5em;
- }
-
- td {
- min-width: 10em;
- white-space: nowrap;
- }
-
- td:first-of-type {
- width: 99%;
- display: flex;
- padding-right: 1em;
- }
-
- td:first-of-type > span:nth-child(2),
- td:first-of-type > span:nth-child(3) {
- margin-left: .5em;
- padding: .15em .5em;
- line-height: 1em;
- border-radius: 50%;
- visibility: hidden;
- opacity: 0;
- font-size: 80%;
- transition: .3s opacity linear .3s;
- }
-
- td:first-of-type > span:nth-child(1) {
- text-overflow: ellipsis;
- flex-grow: 1;
- }
-
- // td:first-of-type > span:nth-child(2) {
- // margin-left: 1.25em;
- // }
-
- tr:hover {
- background: #eee;
- border-radius: .5em;
- cursor: pointer;
- }
-
- tr:hover > td:first-of-type > span:nth-child(2),
- tr:hover > td:first-of-type > span:nth-child(3) {
- color: #666;
- visibility: visible;
- opacity: 1;
- }
-
- tr > td:first-of-type > span:nth-child(2):hover {
- color: #333;
- background-color: #deffde;
- }
-
- tr > td:first-of-type > span:nth-child(3):hover {
- background-color: #ffdede;
- color: #333;
- }
-
- `}</style>
- </>
+ <FontAwesomeIcon icon={faTrash} />
+ </span>
+ </td>
+ <td
+ onClick={() => handleNoteAction('showNote', note)}
+ >
+ {datestring(note.created_at)}
+ </td>
+ <td
+ onClick={() => handleNoteAction('showNote', note)}
+ >
+ {datestring(note.updated_at)}
+ </td>
+ </tr>
)
}
diff --git a/apps/Notes/components/NoteEdit.js b/apps/Notes/components/NoteEdit.js
index b568aa9..42eee46 100644
--- a/apps/Notes/components/NoteEdit.js
+++ b/apps/Notes/components/NoteEdit.js
@@ -1,3 +1,4 @@
+import styles from '../Notes.module.scss'
import React, {useState, useContext} from 'react'
import Context from 'context';
import fetchJson from 'lib/fetchJson'
@@ -24,11 +25,10 @@ const NoteEdit = ({action, setAction, fetchedNote}) => {
// }
return (
- <div className='note'>
+ <div className={styles.noteEdit}>
<h2>{fetchedNote ? 'Edit note:' : 'Add new note:'}</h2>
- <form onSubmit={handleSubmit} className='note__form'>
+ <form onSubmit={handleSubmit}>
<input
- className='note__title'
name='title'
type='text'
placeholder='Title'
@@ -36,7 +36,6 @@ const NoteEdit = ({action, setAction, fetchedNote}) => {
/>
<textarea
required
- className='note__text'
placeholder='Note'
name='content'
defaultValue={fetchedNote ? fetchedNote.content : ''}
@@ -56,82 +55,6 @@ const NoteEdit = ({action, setAction, fetchedNote}) => {
</div>
</form>
<style jsx>{`
- .note {
- display: flex;
- flex-direction: column;
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- background: white;
- padding: 1em 1em 2em;
- animation: fade-in .3s;
- }
-
- @keyframes fade-in {
- from {opacity: 0;}
- to {opacity: 1;}
- }
-
- h2 {
- font-size: 1.2em;
- margin-bottom: .5em;
- }
-
- .note__form {
- display: flex;
- flex-direction: column;
- width: 100%;
- flex-grow: 1;
- }
-
- .note__title {
- margin-bottom: .5rem;
- height: 3rem;
- border: none;
- padding: 0.5rem;
- font-size: 1rem;
- // font-weight: 600;
- border: 1px dashed #666;
- }
-
- .note__title:placeholder {
- font: inherit;
- }
-
- .note__text {
- font-size: 1rem;
- flex-grow: 1;
- resize: none;
- height: 100%;
- width: 100%;
- border: none;
- border: 1px dashed #666;
- padding: 0.5rem;
- }
-
- .note__text:placeholder {
- font: inherit;
- }
-
- .note__close {
- position: absolute;
- top: 8px;
- right: 15px;
- transform: rotate(45deg);
- font-size: 26px;
- cursor: pointer;
- transition: .3s transform;
- }
-
- .note__close:hover {
- transform: rotate(135deg);
- }
-
- .note__buttons {
- text-align: right;
- }
`}</style>
</div>
)
diff --git a/apps/Notes/components/NoteView.js b/apps/Notes/components/NoteView.js
index 1735579..7d93b16 100644
--- a/apps/Notes/components/NoteView.js
+++ b/apps/Notes/components/NoteView.js
@@ -1,3 +1,4 @@
+import styles from '../Notes.module.scss'
import React, {useContext} from 'react';
import Context from 'context';
import useNotes from '../hooks/useNotes'
@@ -17,7 +18,7 @@ const NoteView = ({fetchedNote, setFetchedNote, setAction}) => {
const {_id, content, title} = fetchedNote
return (
- <section>
+ <section className={styles.noteView}>
<div className='window__submenu'>
<div onClick={() => { setFetchedNote(); setAction('') }}>Back</div>
<div onClick={() => copyToClipboard(content, setPopup)}>Copy</div>
@@ -30,34 +31,6 @@ const NoteView = ({fetchedNote, setFetchedNote, setAction}) => {
<p>{content}</p>
</div>
<style jsx>{`
- section {
- background: white;
- padding: 1rem;
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- animation: fade-in .3s;
- }
-
- @keyframes fade-in {
- from {opacity: 0;}
- to {opacity: 1;}
- }
-
- h2 {
- font-size: 1.25em;
- font-weight: 600;
- padding: 1rem;
- user-select: text;
- }
-
- p {
- padding: 0 1rem 1rem;
- white-space: pre-line;
- user-select: text;
- }
`}</style>
</section>
)
diff --git a/apps/Notes/helpers/import.js b/apps/Notes/helpers/import.js
new file mode 100644
index 0000000..b05a458
--- /dev/null
+++ b/apps/Notes/helpers/import.js
@@ -0,0 +1,41 @@
+import fetchJson from 'lib/fetchJson'
+import {faCheck, faTimes} from '@fortawesome/free-solid-svg-icons'
+import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
+
+export const state = s => s && <span><FontAwesomeIcon icon={s === 1 ? faCheck : faTimes} /></span>
+export const color = s => s && {color: s === 1 ? 'green' : 'brown'}
+
+const readFileAsText = (file) => new Promise((resolve,reject) => {
+ let fr = new FileReader()
+
+ fr.onload = () => resolve(fr.result)
+ fr.onerror = () => reject(fr)
+
+ fr.readAsText(file)
+})
+
+export const handleImport = async (e, files, mutateNotes, setDone) => {
+ e.preventDefault();
+
+ Array.from(files).forEach(async (file, i) => {
+ const title = file.name.replace(/\.[^/.]+$/, "")
+ const content = await readFileAsText(file);
+
+ try {
+ const notes = await fetchJson('/api/notes', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({title, content}),
+ })
+ if (i === files.length - 1) await mutateNotes(notes)
+ setDone((prev) => ({...prev, [i]: 1}))
+ } catch (e) {
+ setDone((prev) => ({...prev, [i]: 0}))
+ }
+ })
+}
+
+export const handleChange = (e, setFiles, setDone) => {
+ setFiles(e.currentTarget.files)
+ setDone([])
+}