From 9f3c030a33edcf57eb832c500253044d107f6e25 Mon Sep 17 00:00:00 2001 From: piotrruss Date: Thu, 19 Aug 2021 21:31:21 +0200 Subject: export multiple as zip --- apps/Notes/Notes.module.scss | 16 ++++++ apps/Notes/components/Actions.js | 9 ++- apps/Notes/components/Export.js | 83 +++++++++++++++++++++++++++ apps/Notes/components/Import.js | 2 +- apps/Notes/components/List.js | 9 ++- apps/Notes/components/ListItem.js | 1 - apps/Notes/helpers/fileName.js | 3 + apps/Notes/helpers/noteActions.js | 10 +--- apps/Notes/hooks/useNotes.js | 1 - helpers/saveFile.js | 12 ++++ package-lock.json | 118 ++++++++++++++++++++++++++++++++++++++ package.json | 1 + 12 files changed, 247 insertions(+), 18 deletions(-) create mode 100644 apps/Notes/components/Export.js create mode 100644 apps/Notes/helpers/fileName.js create mode 100644 helpers/saveFile.js diff --git a/apps/Notes/Notes.module.scss b/apps/Notes/Notes.module.scss index babffa2..2e3c68f 100644 --- a/apps/Notes/Notes.module.scss +++ b/apps/Notes/Notes.module.scss @@ -232,6 +232,22 @@ } } +.export { + padding: 1em; + + p { + padding-top: 2em; + padding-bottom: 1em; + } + + &__select { + display: inline-block; + border-bottom: 1px dashed #666; + padding-bottom: .25em; + margin-bottom: .5em; + } +} + .loader, .connection { display: flex; diff --git a/apps/Notes/components/Actions.js b/apps/Notes/components/Actions.js index 8f08103..1f5d680 100644 --- a/apps/Notes/components/Actions.js +++ b/apps/Notes/components/Actions.js @@ -1,6 +1,7 @@ import NoteView from './NoteView' import NoteEdit from './NoteEdit' import Import from './Import' +import Export from './Export' const Actions = ({ action, setAction, fetchedNote, setFetchedNote @@ -26,9 +27,13 @@ const Actions = ({ fetchedNote={fetchedNote} /> ) - case 'importNote': return ( + case 'importNotes': return ( + ) + case 'exportNotes': return ( + ) diff --git a/apps/Notes/components/Export.js b/apps/Notes/components/Export.js new file mode 100644 index 0000000..f281d1a --- /dev/null +++ b/apps/Notes/components/Export.js @@ -0,0 +1,83 @@ +import styles from '../Notes.module.scss' +import React, {useState} from 'react' +import useNotes from '../hooks/useNotes' +import fetchJson from 'lib/fetchJson' +import JSZip from 'jszip' +import saveFile from 'helpers/saveFile' +import filename from '../helpers/fileName' +// import {state, color, handleImport, handleChange} from '../helpers/import' + +const Export = ({setAction}) => { + const {notes} = useNotes() + const [ids, setIds] = useState(notes.map(n => n.noteId)) + // const [files, setFiles] = useState() + // const [done, setDone] = useState([]) + + const handleSelect = id => { + ids.includes(id) + ? setIds(ids.filter(i => i !== id)) + : setIds([...ids, id]) + } + + const handleSelectAll = e => { + if (e.target.checked) { + setIds(notes.map(n => n.noteId)) + } else { + setIds([]) + } + } + + const handleExport = e => { + e.preventDefault() + const zip = new JSZip() + + Promise.all(ids.map(async id => { + const title = notes.find(n => n.noteId === id).title + const {content} = await fetchJson(`/api/note/${id}`) + zip.folder('notes').file(filename(title), content, {binary: true}) + })).then(() => { + zip.generateAsync({type:"blob"}) + .then(c => saveFile(c, 'notes.zip', 'application/zip')) + }) + } + + if (!notes) return null + + return ( +
+
+
{setAction('')}}>Back
+
+
+

Click to export your notes:

+ +

Notes to export:

+
+ + +
+ {notes.map(note => ( +
+ handleSelect(note.noteId)} + /> +
+
+ ))} +
+
+ ) +} + +export default Export diff --git a/apps/Notes/components/Import.js b/apps/Notes/components/Import.js index 2e2e5c7..700acb4 100644 --- a/apps/Notes/components/Import.js +++ b/apps/Notes/components/Import.js @@ -4,7 +4,7 @@ import fetchJson from 'lib/fetchJson' import useNotes from '../hooks/useNotes' import {state, color, handleImport, handleChange} from '../helpers/import' -const Import = ({action, setAction}) => { +const Import = ({setAction}) => { const [files, setFiles] = useState() const [done, setDone] = useState([]) const {mutateNotes} = useNotes() diff --git a/apps/Notes/components/List.js b/apps/Notes/components/List.js index e4060d5..0f6221e 100644 --- a/apps/Notes/components/List.js +++ b/apps/Notes/components/List.js @@ -1,9 +1,8 @@ import styles from '../Notes.module.scss' -import React, {useState, useEffect, useRef} from 'react' +import React, {useState} from 'react' import useUser from 'lib/useUser' import useNotes from '../hooks/useNotes' import useSort from '../hooks/useSort' -import {Layout} from 'components' import ListItem from './ListItem' import Actions from './Actions' import Splash from './Splash' @@ -13,7 +12,7 @@ const List = () => { const [action, setAction] = useState('') const {notes, error} = useNotes() const [sortedBy, sortBy, sortFn] = useSort(2) - const {user, mutateUser} = useUser({ + const {user} = useUser({ redirectToLogin: true, redirectToVerify: true, }) @@ -31,8 +30,8 @@ const List = () => { <>
setAction('addNote')}>New note
-
setAction('importNote')}>Import
-
{}}>Export
+
setAction('importNotes')}>Import
+
setAction('exportNotes')}>Export
diff --git a/apps/Notes/components/ListItem.js b/apps/Notes/components/ListItem.js index 47dea3b..caa22e9 100644 --- a/apps/Notes/components/ListItem.js +++ b/apps/Notes/components/ListItem.js @@ -1,6 +1,5 @@ import styles from '../Notes.module.scss' import React, { useContext } from 'react' -import fetchJson from 'lib/fetchJson' import {getNote, exportNote, removeNote} from '../helpers/noteActions.js' import useNotes from '../hooks/useNotes' import Context from 'context'; diff --git a/apps/Notes/helpers/fileName.js b/apps/Notes/helpers/fileName.js new file mode 100644 index 0000000..f5d4a8e --- /dev/null +++ b/apps/Notes/helpers/fileName.js @@ -0,0 +1,3 @@ +const filename = (t) => t.toLowerCase().replaceAll(' ', '-')+'.txt' + +export default filename diff --git a/apps/Notes/helpers/noteActions.js b/apps/Notes/helpers/noteActions.js index f90e1c7..9588150 100644 --- a/apps/Notes/helpers/noteActions.js +++ b/apps/Notes/helpers/noteActions.js @@ -1,4 +1,5 @@ import fetchJson from 'lib/fetchJson' +import filename from '../helpers/fileName' export const getNote = async (note, setFetchedNote, setPopup, callback) => { try { @@ -108,12 +109,5 @@ export const exportNote = async note => { ? note : await fetchJson(`/api/note/${note.noteId}`) - const a = document.createElement('a'); - const file = new Blob([content], {type: 'text/plain'}); - - a.href= URL.createObjectURL(file); - a.download = title.toLowerCase().replaceAll(' ', '-')+'.txt'; - a.click(); - - URL.revokeObjectURL(a.href); + saveFile(content, filename(title), 'text/plain') } diff --git a/apps/Notes/hooks/useNotes.js b/apps/Notes/hooks/useNotes.js index 9d79034..586ceeb 100644 --- a/apps/Notes/hooks/useNotes.js +++ b/apps/Notes/hooks/useNotes.js @@ -1,5 +1,4 @@ import useSWR from 'swr' -import fetchJson from 'lib/fetchJson' export default function useNotes(){ const { data: notes, error, mutate: mutateNotes } = useSWR('/api/notes') diff --git a/helpers/saveFile.js b/helpers/saveFile.js new file mode 100644 index 0000000..83d46cb --- /dev/null +++ b/helpers/saveFile.js @@ -0,0 +1,12 @@ +const saveFile = (content, filename, type) => { + const a = document.createElement('a') + const file = new Blob([content], {type}) + + a.href= URL.createObjectURL(file) + a.download = filename + a.click() + + URL.revokeObjectURL(a.href) +} + +export default saveFile diff --git a/package-lock.json b/package-lock.json index c2a0e1b..1c8cbc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.1.15", "bcryptjs": "^2.4.3", + "jszip": "^3.7.1", "mongoose": "^5.12.13", "next": "latest", "next-iron-session": "4.1.7", @@ -1315,6 +1316,11 @@ } ] }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -1587,11 +1593,57 @@ "json5": "lib/cli.js" } }, + "node_modules/jszip": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/loader-utils": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", @@ -2593,6 +2645,14 @@ "semver": "bin/semver.js" } }, + "node_modules/set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -4104,6 +4164,11 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -4276,11 +4341,59 @@ "minimist": "^1.2.0" } }, + "jszip": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.7.1.tgz", + "integrity": "sha512-ghL0tz1XG9ZEmRMcEN2vt7xabrDdqHHeykgARpmZ0BiIctWxM47Vt63ZO2dnp4QYt/xJVLLy5Zv1l/xRdh2byg==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "kareem": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz", "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ==" }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, "loader-utils": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", @@ -5068,6 +5181,11 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", diff --git a/package.json b/package.json index dcb975e..dfdcbe5 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.1.15", "bcryptjs": "^2.4.3", + "jszip": "^3.7.1", "mongoose": "^5.12.13", "next": "latest", "next-iron-session": "4.1.7", -- cgit v1.2.3