From 959b75bdddfcab3838641e503c40e9e9732d0a4a Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Mon, 20 Nov 2017 17:33:17 +0100 Subject: [PATCH 1/3] export folder as md or text --- .gitignore | 4 +- browser/main/SideNav/StorageItem.js | 94 ++++++++++++++++++------ browser/main/lib/dataApi/exportFolder.js | 64 ++++++++++++++++ browser/main/lib/dataApi/index.js | 1 + browser/main/store.js | 7 ++ tests/dataApi/exportFolder-test.js | 62 ++++++++++++++++ 6 files changed, 206 insertions(+), 26 deletions(-) create mode 100644 browser/main/lib/dataApi/exportFolder.js create mode 100644 tests/dataApi/exportFolder-test.js diff --git a/.gitignore b/.gitignore index 9f75dd1b..ace5316c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,5 @@ node_modules/* /compiled /secret *.log -.vscode -.idea \ No newline at end of file +.idea +.vscode \ No newline at end of file diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js index 4379a76c..c955b171 100644 --- a/browser/main/SideNav/StorageItem.js +++ b/browser/main/SideNav/StorageItem.js @@ -10,6 +10,7 @@ import dataApi from 'browser/main/lib/dataApi' import StorageItemChild from 'browser/components/StorageItem' import eventEmitter from 'browser/main/lib/eventEmitter' import _ from 'lodash' +const path = require('path') const { remote } = require('electron') const { Menu, MenuItem, dialog } = remote @@ -24,18 +25,20 @@ class StorageItem extends React.Component { } handleHeaderContextMenu (e) { - const menu = new Menu() - menu.append(new MenuItem({ - label: 'Add Folder', - click: (e) => this.handleAddFolderButtonClick(e) - })) - menu.append(new MenuItem({ - type: 'separator' - })) - menu.append(new MenuItem({ - label: 'Unlink Storage', - click: (e) => this.handleUnlinkStorageClick(e) - })) + let menu = Menu.buildFromTemplate([ + { + label: 'Add Folder', + click: (e) => this.handleAddFolderButtonClick(e) + }, + { + type: 'separator' + }, + { + label: 'Unlink Storage', + click: (e) => this.handleUnlinkStorageClick(e) + } + ]) + menu.popup() } @@ -89,18 +92,36 @@ class StorageItem extends React.Component { } handleFolderButtonContextMenu (e, folder) { - const menu = new Menu() - menu.append(new MenuItem({ - label: 'Rename Folder', - click: (e) => this.handleRenameFolderClick(e, folder) - })) - menu.append(new MenuItem({ - type: 'separator' - })) - menu.append(new MenuItem({ - label: 'Delete Folder', - click: (e) => this.handleFolderDeleteClick(e, folder) - })) + const menu = Menu.buildFromTemplate([ + { + label: 'Rename Folder', + click: (e) => this.handleRenameFolderClick(e, folder) + }, + { + type: 'separator' + }, + { + label: 'Export Folder', + submenu: [ + { + label: 'Export as txt', + click: (e) => this.handleExportFolderClick(e, folder, 'txt') + }, + { + label: 'Export as md', + click: (e) => this.handleExportFolderClick(e, folder, 'md') + } + ] + }, + { + type: 'separator' + }, + { + label: 'Delete Folder', + click: (e) => this.handleFolderDeleteClick(e, folder) + } + ]) + menu.popup() } @@ -112,6 +133,31 @@ class StorageItem extends React.Component { }) } + handleExportFolderClick (e, folder, fileType) { + const options = { + properties: ['openDirectory', 'createDirectory'], + buttonLabel: 'Select directory', + title: 'Select a folder to export the files to', + multiSelections: false + } + dialog.showOpenDialog(remote.getCurrentWindow(), options, + (paths) => { + if (paths && paths.length === 1) { + const { storage, dispatch } = this.props + dataApi + .exportFolder(storage.key, folder.key, fileType, paths[0]) + .then((data) => { + dispatch({ + type: 'EXPORT_FOLDER', + storage: data.storage, + folderKey: data.folderKey, + fileType: data.fileType + }) + }) + } + }) + } + handleFolderDeleteClick (e, folder) { const index = dialog.showMessageBox(remote.getCurrentWindow(), { type: 'warning', diff --git a/browser/main/lib/dataApi/exportFolder.js b/browser/main/lib/dataApi/exportFolder.js new file mode 100644 index 00000000..dead4880 --- /dev/null +++ b/browser/main/lib/dataApi/exportFolder.js @@ -0,0 +1,64 @@ +const { findStorage } = require('browser/lib/findStorage') +const resolveStorageData = require('./resolveStorageData') +const resolveStorageNotes = require('./resolveStorageNotes') +const path = require('path') +const fs = require('fs') + +/** + * @param {String} storageKey + * @param {String} folderKey + * @param {String} fileType + * @param {String} exportDir + * + * @return {Object} + * ``` + * { + * storage: Object, + * folderKey: String, + * fileType: String, + * exportDir: String + * } + * ``` + */ + +function exportFolder (storageKey, folderKey, fileType, exportDir) { + let targetStorage + try { + targetStorage = findStorage(storageKey) + } catch (e) { + return Promise.reject(e) + } + + return resolveStorageData(targetStorage) + .then(function assignNotes (storage) { + return resolveStorageNotes(storage) + .then((notes) => { + return { + storage, + notes + } + }) + }) + .then(function exportNotes (data) { + const { storage, notes } = data + + notes + .filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE') + .forEach(snippet => { + const notePath = path.join(exportDir, `${snippet.title}.${fileType}`) + console.log(notePath) + fs.writeFile(notePath, snippet.content, (err) => { + if (err) throw err + }) + }) + + return { + storage, + folderKey, + fileType, + exportDir + } + }) +} + +module.exports = exportFolder diff --git a/browser/main/lib/dataApi/index.js b/browser/main/lib/dataApi/index.js index 768dfe32..311ca2f3 100644 --- a/browser/main/lib/dataApi/index.js +++ b/browser/main/lib/dataApi/index.js @@ -7,6 +7,7 @@ const dataApi = { updateFolder: require('./updateFolder'), deleteFolder: require('./deleteFolder'), reorderFolder: require('./reorderFolder'), + exportFolder: require('./exportFolder'), createNote: require('./createNote'), updateNote: require('./updateNote'), deleteNote: require('./deleteNote'), diff --git a/browser/main/store.js b/browser/main/store.js index 647d0ac9..f63a67c7 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -349,6 +349,13 @@ function data (state = defaultDataMap(), action) { state.storageMap = new Map(state.storageMap) state.storageMap.set(action.storage.key, action.storage) return state + case 'EXPORT_FOLDER': + { + state = Object.assign({}, state) + state.storageMap = new Map(state.storageMap) + state.storageMap.set(action.storage.key, action.storage) + } + return state case 'DELETE_FOLDER': { state = Object.assign({}, state) diff --git a/tests/dataApi/exportFolder-test.js b/tests/dataApi/exportFolder-test.js new file mode 100644 index 00000000..ee6fb898 --- /dev/null +++ b/tests/dataApi/exportFolder-test.js @@ -0,0 +1,62 @@ +const test = require('ava') +const exportFolder = require('browser/main/lib/dataApi/exportFolder') +const createNote = require('browser/main/lib/dataApi/createNote') + +global.document = require('jsdom').jsdom('') +global.window = document.defaultView +global.navigator = window.navigator + +const Storage = require('dom-storage') +const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true }) +const path = require('path') +const TestDummy = require('../fixtures/TestDummy') +const os = require('os') +const faker = require('faker') +const fs = require('fs') + +const storagePath = path.join(os.tmpdir(), 'test/export-note') + +test.beforeEach((t) => { + t.context.storage = TestDummy.dummyStorage(storagePath) + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) +}) + +test.serial('Export a folder', (t) => { + const storageKey = t.context.storage.cache.key + const folderKey = t.context.storage.json.folders[0].key + + const input1 = { + type: 'MARKDOWN_NOTE', + description: '*Some* markdown text', + tags: faker.lorem.words().split(' '), + folder: folderKey + } + input1.title = 'input1' + + const input2 = { + type: 'SNIPPET_NOTE', + description: 'Some normal text', + snippets: [{ + name: faker.system.fileName(), + mode: 'text', + content: faker.lorem.lines() + }], + tags: faker.lorem.words().split(' '), + folder: folderKey + } + input2.title = 'input2' + + return createNote(storageKey, input1) + .then(function () { + return createNote(storageKey, input2) + }) + .then(function () { + return exportFolder(storageKey, folderKey, 'md', storagePath) + }) + .then(function assert () { + let filePath = path.join(storagePath, 'input1.md') + t.true(fs.existsSync(filePath)) + filePath = path.join(storagePath, 'input2.md') + t.false(fs.existsSync(filePath)) + }) +}) From 8c8a0ab46d0799648238d3884a79e855e982871d Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Mon, 20 Nov 2017 19:17:03 +0100 Subject: [PATCH 2/3] forgot to run lint --- browser/main/SideNav/StorageItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js index c955b171..62591598 100644 --- a/browser/main/SideNav/StorageItem.js +++ b/browser/main/SideNav/StorageItem.js @@ -95,7 +95,7 @@ class StorageItem extends React.Component { const menu = Menu.buildFromTemplate([ { label: 'Rename Folder', - click: (e) => this.handleRenameFolderClick(e, folder) + click: (e) => this.handleRenameFolderClick(e, folder) }, { type: 'separator' From 5d46adf8fd0fa620cd6a44e7bbe046e414e15880 Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Wed, 13 Dec 2017 17:11:43 +0100 Subject: [PATCH 3/3] fixed review comments --- browser/main/SideNav/StorageItem.js | 4 ++-- browser/main/lib/dataApi/exportFolder.js | 13 ++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js index 62591598..2bcbe050 100644 --- a/browser/main/SideNav/StorageItem.js +++ b/browser/main/SideNav/StorageItem.js @@ -10,7 +10,7 @@ import dataApi from 'browser/main/lib/dataApi' import StorageItemChild from 'browser/components/StorageItem' import eventEmitter from 'browser/main/lib/eventEmitter' import _ from 'lodash' -const path = require('path') +import * as path from 'path' const { remote } = require('electron') const { Menu, MenuItem, dialog } = remote @@ -25,7 +25,7 @@ class StorageItem extends React.Component { } handleHeaderContextMenu (e) { - let menu = Menu.buildFromTemplate([ + const menu = Menu.buildFromTemplate([ { label: 'Add Folder', click: (e) => this.handleAddFolderButtonClick(e) diff --git a/browser/main/lib/dataApi/exportFolder.js b/browser/main/lib/dataApi/exportFolder.js index dead4880..75dba959 100644 --- a/browser/main/lib/dataApi/exportFolder.js +++ b/browser/main/lib/dataApi/exportFolder.js @@ -1,8 +1,8 @@ -const { findStorage } = require('browser/lib/findStorage') -const resolveStorageData = require('./resolveStorageData') -const resolveStorageNotes = require('./resolveStorageNotes') -const path = require('path') -const fs = require('fs') +import { findStorage } from 'browser/lib/findStorage' +import resolveStorageData from './resolveStorageData' +import resolveStorageNotes from './resolveStorageNotes' +import * as path from 'path' +import * as fs from 'fs' /** * @param {String} storageKey @@ -46,8 +46,7 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) { .filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE') .forEach(snippet => { const notePath = path.join(exportDir, `${snippet.title}.${fileType}`) - console.log(notePath) - fs.writeFile(notePath, snippet.content, (err) => { + fs.writeFileSync(notePath, snippet.content, (err) => { if (err) throw err }) })