From c5414aadd122a69bfe1441a16de33c944327deda Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Fri, 26 Aug 2016 10:26:22 +0900 Subject: [PATCH 01/34] extract renameStorage, removeStorage methods and tests for each of them --- browser/main/lib/dataApi/init.js | 9 +++++ browser/main/lib/dataApi/removeStorage.js | 28 ++++++++++++++ browser/main/lib/dataApi/renameStorage.js | 33 ++++++++++++++++ .../modals/PreferencesModal/StoragesTab.js | 1 - tests/dataApi/removeStorage.js | 36 ++++++++++++++++++ tests/dataApi/renameStorage.js | 38 +++++++++++++++++++ 6 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 browser/main/lib/dataApi/removeStorage.js create mode 100644 browser/main/lib/dataApi/renameStorage.js create mode 100644 tests/dataApi/removeStorage.js create mode 100644 tests/dataApi/renameStorage.js diff --git a/browser/main/lib/dataApi/init.js b/browser/main/lib/dataApi/init.js index 316ea56d..c30b4a98 100644 --- a/browser/main/lib/dataApi/init.js +++ b/browser/main/lib/dataApi/init.js @@ -3,6 +3,15 @@ const _ = require('lodash') const sander = require('sander') const path = require('path') +/** + * @return {Object} all storages and notes + * ``` + * { + * storages: [...], + * notes: [...] + * } + * ``` + */ function init () { let fetchStorages = function () { let rawStorages diff --git a/browser/main/lib/dataApi/removeStorage.js b/browser/main/lib/dataApi/removeStorage.js new file mode 100644 index 00000000..3ada7227 --- /dev/null +++ b/browser/main/lib/dataApi/removeStorage.js @@ -0,0 +1,28 @@ +const _ = require('lodash') + +/** + * @param {String} key + * @return {key} + */ +function removeStorage (key) { + let rawStorages + + try { + rawStorages = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(rawStorages)) throw new Error('invalid storages') + } catch (e) { + console.warn(e) + rawStorages = [] + } + + rawStorages = rawStorages + .filter(function excludeTargetStorage (rawStorage) { + return rawStorage.key !== key + }) + + localStorage.setItem('storages', JSON.stringify(rawStorages)) + + return Promise.resolve(key) +} + +module.exports = removeStorage diff --git a/browser/main/lib/dataApi/renameStorage.js b/browser/main/lib/dataApi/renameStorage.js new file mode 100644 index 00000000..fde14ac6 --- /dev/null +++ b/browser/main/lib/dataApi/renameStorage.js @@ -0,0 +1,33 @@ +const _ = require('lodash') + +/** + * @param {String} key + * @param {String} name + * @return {Object} Storage meta data + */ +function renameStorage (key, name) { + if (!_.isString(name)) return Promise.reject(new Error('Name must be a string.')) + + let rawStorages + try { + rawStorages = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(rawStorages)) throw new Error('invalid storages') + } catch (e) { + console.warn(e) + rawStorages = [] + } + + let targetStorage + for (let i = 0; i < rawStorages.length; i++) { + if (rawStorages[i].key === key) { + rawStorages[i].name = name + targetStorage = rawStorages[i] + } + } + + localStorage.setItem('storages', JSON.stringify(rawStorages)) + + return Promise.resolve(targetStorage) +} + +module.exports = renameStorage diff --git a/browser/main/modals/PreferencesModal/StoragesTab.js b/browser/main/modals/PreferencesModal/StoragesTab.js index ead759ba..1f45c476 100644 --- a/browser/main/modals/PreferencesModal/StoragesTab.js +++ b/browser/main/modals/PreferencesModal/StoragesTab.js @@ -58,7 +58,6 @@ class StoragesTab extends React.Component { return }) diff --git a/tests/dataApi/removeStorage.js b/tests/dataApi/removeStorage.js new file mode 100644 index 00000000..09c8473a --- /dev/null +++ b/tests/dataApi/removeStorage.js @@ -0,0 +1,36 @@ +const test = require('ava') +const removeStorage = require('browser/main/lib/dataApi/removeStorage') + +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 crypto = require('crypto') + +test('Remove a storage', (t) => { + const dummyStoragePath = path.join(__dirname, '..', 'dummy/dummyStorage') + const dummyStorageKey = crypto.randomBytes(6).toString('hex') + const dummyRawStorage = { + name: 'test1', + key: dummyStorageKey, + path: dummyStoragePath + } + + return Promise.resolve() + .then(function before () { + localStorage.setItem('storages', JSON.stringify([dummyRawStorage])) + }) + .then(function test () { + return removeStorage(dummyStorageKey) + }) + .then(function assert (data) { + t.is(JSON.parse(localStorage.getItem('storages')).length, 0) + }) +}) + +test.after(function after () { + localStorage.clear() +}) diff --git a/tests/dataApi/renameStorage.js b/tests/dataApi/renameStorage.js new file mode 100644 index 00000000..d54f767c --- /dev/null +++ b/tests/dataApi/renameStorage.js @@ -0,0 +1,38 @@ +const test = require('ava') +const renameStorage = require('browser/main/lib/dataApi/renameStorage') + +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 crypto = require('crypto') +const _ = require('lodash') + +test('Rename a storage', (t) => { + const dummyStoragePath = path.join(__dirname, '..', 'dummy/dummyStorage') + const dummyStorageKey = crypto.randomBytes(6).toString('hex') + const dummyRawStorage = { + name: 'test1', + key: dummyStorageKey, + path: dummyStoragePath + } + + return Promise.resolve() + .then(function before () { + localStorage.setItem('storages', JSON.stringify([dummyRawStorage])) + }) + .then(function test () { + return renameStorage(dummyStorageKey, 'test2') + }) + .then(function assert (data) { + let rawStorages = JSON.parse(localStorage.getItem('storages')) + t.true(_.find(rawStorages, {key: dummyStorageKey}).name === 'test2') + }) +}) + +test.after(function after () { + localStorage.clear() +}) From 8f1ee30553a23412abb55e1329a5a318a157f3b9 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Fri, 26 Aug 2016 18:48:19 +0900 Subject: [PATCH 02/34] extract createFolder method --- browser/main/lib/dataApi/createFolder.js | 86 ++++++++++++++++++++++++ tests/dataApi/createFolder.js | 58 ++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 browser/main/lib/dataApi/createFolder.js create mode 100644 tests/dataApi/createFolder.js diff --git a/browser/main/lib/dataApi/createFolder.js b/browser/main/lib/dataApi/createFolder.js new file mode 100644 index 00000000..630d273f --- /dev/null +++ b/browser/main/lib/dataApi/createFolder.js @@ -0,0 +1,86 @@ +const _ = require('lodash') +const sander = require('sander') +const keygen = require('browser/lib/keygen') +const path = require('path') + +const defaultDataJSON = { + notes: [] +} + +/** + * @param {String} storageKey + * @param {Object} input + * ``` + * { + * color: String, + * name: String + * } + * ``` + * + * @return {key} + */ +function createFolder (storageKey, input) { + let rawStorages + let targetStorage + try { + if (input == null) throw new Error('No input found.') + if (!_.isString(input.name)) throw new Error('Name must be a string.') + if (!_.isString(input.color)) throw new Error('Color must be a string.') + + rawStorages = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(rawStorages)) throw new Error('Target storage doesn\'t exist.') + + targetStorage = _.find(rawStorages, {key: storageKey}) + if (targetStorage == null) throw new Error('Target storage doesn\'t exist.') + } catch (e) { + return Promise.reject(e) + } + + const storageData = Object.assign({}, targetStorage) + + const boostnoteJSONPath = path.join(targetStorage.path, 'boostnote.json') + return Promise.resolve() + .then(function fetchBoostnoteJSON () { + return sander.readFile(boostnoteJSONPath) + }) + .then(function updateBoostnoteJSON (data) { + let boostnoteJSON + // If `boostnote.json` is invalid, reset `boostnote.json`. + try { + boostnoteJSON = JSON.parse(data) + if (!_.isArray(boostnoteJSON.folders)) throw new Error('the value of `folders` must be array') + } catch (err) { + boostnoteJSON = { + folders: [] + } + } + + let key = keygen() + while (boostnoteJSON.folders.some((folder) => folder.key === key)) { + key = keygen() + } + + let newFolder = { + key, + color: input.color, + name: input.name + } + boostnoteJSON.folders.push(newFolder) + + storageData.folders = boostnoteJSON.folders + + return sander.writeFile(boostnoteJSONPath, JSON.stringify(boostnoteJSON)) + .then(() => newFolder) + }) + .then(function createDataJSON (newFolder) { + const folderDirPath = path.join(targetStorage.path, newFolder.key) + return sander.writeFile(folderDirPath, 'data.json', JSON.stringify(defaultDataJSON)) + }) + .then(function returnData () { + return { + storage: storageData + } + }) +} + +module.exports = createFolder diff --git a/tests/dataApi/createFolder.js b/tests/dataApi/createFolder.js new file mode 100644 index 00000000..6c6c19f6 --- /dev/null +++ b/tests/dataApi/createFolder.js @@ -0,0 +1,58 @@ +const test = require('ava') +const createFolder = require('browser/main/lib/dataApi/createFolder') +const sander = require('sander') + +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 crypto = require('crypto') +const _ = require('lodash') + +function copyFile (filePath, targetPath) { + return sander.readFile(filePath) + .then(function writeFile (data) { + return sander.writeFile(targetPath, data.toString()) + }) +} + +const dummyStoragePath = path.join(__dirname, '..', 'dummy/dummyStorage') +const targetPath = path.join(__dirname, '../sandbox/test-add-folder') +const dummyRawStorage = { + name: 'test1', + key: crypto.randomBytes(6).toString('hex'), + path: targetPath +} +test.before(function () { + localStorage.setItem('storages', JSON.stringify([dummyRawStorage])) + + return copyFile(path.join(dummyStoragePath, 'boostnote.json'), path.join(targetPath, 'boostnote.json')) +}) +const input = { + name: 'test folder', + color: '#FF5555' +} + +test('Add note to storage', (t) => { + return createFolder(dummyRawStorage.key, input) + .then(function assert (data) { + t.not(data.storage, null) + + let targetFolder = _.find(data.storage.folders, { + name: input.name, + color: input.color + }) + + t.not(targetFolder, null) + + t.true(sander.statSync(path.join(targetPath, targetFolder.key)).isDirectory()) + }) +}) + +test.after.always(function () { + localStorage.clear() + sander.rimrafSync(targetPath) +}) From 3a8bef26d35b0325e7eacea48c123d157528180b Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 12:23:58 +0900 Subject: [PATCH 03/34] add fixtures.TestDummy and transform method --- browser/lib/keygen.js | 2 +- browser/main/lib/dataApi/transform.js | 85 +++++++++++++ tests/dataApi/transform.js | 62 ++++++++++ tests/fixtures/TestDummy.js | 172 ++++++++++++++++++++++++++ 4 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 browser/main/lib/dataApi/transform.js create mode 100644 tests/dataApi/transform.js create mode 100644 tests/fixtures/TestDummy.js diff --git a/browser/lib/keygen.js b/browser/lib/keygen.js index 8b9fdf5b..f4937a83 100644 --- a/browser/lib/keygen.js +++ b/browser/lib/keygen.js @@ -2,6 +2,6 @@ const crypto = require('crypto') const _ = require('lodash') module.exports = function (length) { - if (!_.isFinite(length)) length = 6 + if (!_.isFinite(length)) length = 10 return crypto.randomBytes(length).toString('hex') } diff --git a/browser/main/lib/dataApi/transform.js b/browser/main/lib/dataApi/transform.js new file mode 100644 index 00000000..0a0f04cf --- /dev/null +++ b/browser/main/lib/dataApi/transform.js @@ -0,0 +1,85 @@ +const path = require('path') +const sander = require('sander') +const keygen = require('browser/lib/keygen') +const _ = require('lodash') +const CSON = require('season') + +function transform (storagePath) { + var boostnoteJSONPath = path.join(storagePath, 'boostnote.json') + return Promise.resolve() + .then(function readBoostnoteJSON () { + return sander.readFile(boostnoteJSONPath, { + encoding: 'utf-8' + }) + }) + .then(function verifyVersion (rawData) { + var boostnoteJSONData = JSON.parse(rawData) + if (boostnoteJSONData.version === '1.0') throw new Error('Target storage seems to be transformed already.') + if (!_.isArray(boostnoteJSONData.folders)) throw new Error('the value of folders is not an array.') + + return boostnoteJSONData + }) + .then(function setVersion (boostnoteJSONData) { + boostnoteJSONData.version = '1.0' + return sander.writeFile(boostnoteJSONPath, JSON.stringify(boostnoteJSONData)) + .then(() => boostnoteJSONData) + }) + .then(function fetchNotes (boostnoteJSONData) { + var fetchNotesFromEachFolder = boostnoteJSONData.folders + .map(function (folder) { + const folderDataJSONPath = path.join(storagePath, folder.key, 'data.json') + return sander + .readFile(folderDataJSONPath, { + encoding: 'utf-8' + }) + .then(function (rawData) { + var data = JSON.parse(rawData) + if (!_.isArray(data.notes)) throw new Error('value of notes is not an array.') + return data.notes + .map(function setFolderToNote (note) { + note.folder = folder.key + return note + }) + }) + .catch(function failedToReadDataJSON (err) { + console.warn('Failed to fetch notes from ', folderDataJSONPath, err) + return [] + }) + .then(function deleteFolderDir (data) { + sander.rimrafSync(path.join(storagePath, folder.key)) + return data + }) + }) + + return Promise.all(fetchNotesFromEachFolder) + .then(function flatten (folderNotes) { + return folderNotes + .reduce(function concatNotes (sum, notes) { + return sum.concat(notes) + }, []) + }) + }) + .then(function saveNotes (notes) { + notes.forEach(function renewKey (note) { + var newKey = keygen() + while (notes.some((_note) => _note.key === newKey)) { + newKey = keygen() + } + note.key = newKey + }) + + const noteDirPath = path.join(storagePath, 'notes') + notes + .map(function saveNote (note) { + CSON.writeFileSync(path.join(noteDirPath, note.key) + '.cson', note) + }) + return true + }) + .catch(function handleError (err) { + console.warn(err) + return false + }) +} + +module.exports = transform + diff --git a/tests/dataApi/transform.js b/tests/dataApi/transform.js new file mode 100644 index 00000000..fb54a582 --- /dev/null +++ b/tests/dataApi/transform.js @@ -0,0 +1,62 @@ +const test = require('ava') +const transform = require('browser/main/lib/dataApi/transform') + +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 sander = require('sander') +const CSON = require('season') +const _ = require('lodash') +const os = require('os') + +const dummyStoragePath = path.join(os.tmpdir(), 'sandbox/transform-test-storage') + +test.beforeEach((t) => { + let dummyData = t.context.dummyData = TestDummy.dummyLegacyStorage(dummyStoragePath) + localStorage.setItem('storages', JSON.stringify([dummyData.cache])) +}) + +test.serial('Transform legacy storage into v1 storage', (t) => { + return Promise.resolve() + .then(function test () { + return transform(dummyStoragePath) + }) + .then(function assert (data) { + // Check the result. It must be true if succeed. + t.true(data) + + // Check all notes transformed. + let dummyData = t.context.dummyData + let noteDirPath = path.join(dummyStoragePath, 'notes') + let fileList = sander.readdirSync(noteDirPath) + let noteMap = fileList + .map((filePath) => { + return CSON.readFileSync(path.join(noteDirPath, filePath)) + }) + dummyData.notes + .forEach(function (targetNote) { + t.true(_.find(noteMap, {title: targetNote.title, folder: targetNote.folder}) != null) + }) + + // Check legacy folder directory is removed + dummyData.json.folders + .forEach(function (folder) { + try { + sander.statSync(dummyStoragePath, folder.key) + t.fail('Folder still remains. ENOENT error must be occured.') + } catch (err) { + t.is(err.code, 'ENOENT') + } + }) + }) +}) + +test.after.always(function () { + localStorage.clear() + sander.rimrafSync(dummyStoragePath) +}) diff --git a/tests/fixtures/TestDummy.js b/tests/fixtures/TestDummy.js new file mode 100644 index 00000000..449315b7 --- /dev/null +++ b/tests/fixtures/TestDummy.js @@ -0,0 +1,172 @@ +const faker = require('faker') +const keygen = require('browser/lib/keygen') +const _ = require('lodash') +const sander = require('sander') +const CSON = require('season') +const path = require('path') + +function dummyFolder (override = {}) { + var data = { + name: faker.lorem.word(), + color: faker.internet.color() + } + if (override.key == null) data.key = keygen() + + Object.assign(data, override) + + return data +} + +function dummyBoostnoteJSONData (override = {}, isLegacy = false) { + var data = {} + if (override.folders == null) { + data.folders = [] + + var folderCount = Math.floor((Math.random() * 5)) + 1 + for (var i = 0; i < folderCount; i++) { + var key = keygen() + while (data.folders.some((folder) => folder.key === key)) { + key = keygen() + } + + data.folders.push(dummyFolder({ + key + })) + } + } + if (!isLegacy) data.version = '1.0' + + Object.assign(data, override) + + return data +} + +function dummyNote (override = {}) { + var data = Math.random() > 0.5 + ? { + type: 'MARKDOWN_NOTE', + content: faker.lorem.lines() + } + : { + type: 'SNIPPET_NOTE', + description: faker.lorem.lines(), + snippets: [{ + name: faker.system.fileName(), + mode: 'text', + content: faker.lorem.lines() + }] + } + data.title = data.type === 'MARKDOWN_NOTE' + ? data.content.split('\n').shift() + : data.description.split('\n').shift() + data.createdAt = faker.date.past() + data.updatedAt = faker.date.recent() + data.isStarred = false + data.tags = faker.lorem.words().split(' ') + + if (override.key == null) data.key = keygen() + if (override.folder == null) data.folder = keygen() + Object.assign(data, override) + + return data +} + +/** + * @param {String} + * @param {Object} + * ``` + * { + * json: {folders: []}, + * cache: { + * key: String, + * name: String, + * type: String(enum:FILESYSTEM), + * path: String + * }, + * notes: [] + * } + * ``` + * @return {[type]} + */ +function dummyStorage (storagePath, override = {}) { + var jsonData = override.json != null + ? override.json + : dummyBoostnoteJSONData() + var cacheData = override.cache != null + ? override.cache + : {} + if (cacheData.key == null) cacheData.key = key + if (cacheData.name == null) cacheData.name = faker.random.word() + + sander.writeFileSync(path.join(storagePath, 'boostnote.json'), JSON.stringify(jsonData)) + var notesData = [] + console.log(notesData) + var noteCount = Math.floor((Math.random() * 15)) + 1 + for (var i = 0; i < noteCount; i++) { + var key = keygen() + while (notesData.some((note) => note.key === key)) { + key = keygen() + } + + var noteData = dummyNote({ + key, + folder: jsonData.folders[Math.floor(Math.random() * jsonData.folders.length)].key + }) + + notesData.push(noteData) + } + notesData.forEach(function saveNoteCSON (note) { + CSON.writeFileSync(path.join(storagePath, 'notes', note.key + '.cson'), _.omit(note, ['key'])) + }) + + return { + json: jsonData, + cache: cacheData, + notes: notesData + } +} + +function dummyLegacyStorage (storagePath, override = {}) { + var jsonData = override.json != null + ? override.json + : dummyBoostnoteJSONData({}, true) + var cacheData = override.cache != null + ? override.cache + : {} + if (cacheData.key == null) cacheData.key = key + if (cacheData.name == null) cacheData.name = faker.random.word() + + sander.writeFileSync(path.join(storagePath, 'boostnote.json'), JSON.stringify(jsonData)) + + for (var j = 0; j < jsonData.folders.length; j++) { + var noteCount = Math.floor((Math.random() * 5)) + 1 + var notesData = [] + for (var i = 0; i < noteCount; i++) { + var key = keygen(6) + while (notesData.some((note) => note.key === key)) { + key = keygen(6) + } + + var noteData = dummyNote({ + key, + folder: jsonData.folders[j].key + }) + notesData.push(noteData) + } + + CSON.writeFileSync(path.join(storagePath, jsonData.folders[j].key, 'data.json'), {notes: notesData.map((note) => _.omit(note, ['folder']))}) + } + + return { + json: jsonData, + cache: cacheData, + notes: notesData + } +} + +module.exports = { + dummyFolder, + dummyBoostnoteJSONData, + dummyStorage, + dummyLegacyStorage +} From 87cfc8f1de4cc70120a9414fc89ecd6bc0f67644 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 14:00:25 +0900 Subject: [PATCH 04/34] add fixtures.TestDummy and transform method --- package.json | 3 +++ tests/dataApi/transform.js | 4 +++- tests/fixtures/TestDummy.js | 20 ++++++++++++-------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index ae31b5f0..0e2bb2d1 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "electron-gh-releases": "^2.0.2", "font-awesome": "^4.3.0", "highlight.js": "^9.3.0", + "immutable": "^3.8.1", "lodash": "^4.11.1", "markdown-it": "^6.0.1", "markdown-it-checkbox": "^1.1.0", @@ -75,11 +76,13 @@ "dom-storage": "^2.0.2", "electron-packager": "^6.0.0", "electron-prebuilt": "^1.2.8", + "faker": "^3.1.0", "grunt": "^0.4.5", "grunt-electron-installer": "^1.2.0", "history": "^1.17.0", "jsdom": "^9.4.2", "merge-stream": "^1.0.0", + "mock-fs": "^3.11.0", "nib": "^1.1.0", "oh-my-cdn": "^0.1.1", "react": "^15.3.0", diff --git a/tests/dataApi/transform.js b/tests/dataApi/transform.js index fb54a582..2c181b42 100644 --- a/tests/dataApi/transform.js +++ b/tests/dataApi/transform.js @@ -14,10 +14,11 @@ const CSON = require('season') const _ = require('lodash') const os = require('os') -const dummyStoragePath = path.join(os.tmpdir(), 'sandbox/transform-test-storage') +const dummyStoragePath = path.join(os.tmpdir(), 'test/transform-test-storage') test.beforeEach((t) => { let dummyData = t.context.dummyData = TestDummy.dummyLegacyStorage(dummyStoragePath) + console.log('init count', dummyData.notes.length) localStorage.setItem('storages', JSON.stringify([dummyData.cache])) }) @@ -34,6 +35,7 @@ test.serial('Transform legacy storage into v1 storage', (t) => { let dummyData = t.context.dummyData let noteDirPath = path.join(dummyStoragePath, 'notes') let fileList = sander.readdirSync(noteDirPath) + t.is(dummyData.notes.length, fileList.length) let noteMap = fileList .map((filePath) => { return CSON.readFileSync(path.join(noteDirPath, filePath)) diff --git a/tests/fixtures/TestDummy.js b/tests/fixtures/TestDummy.js index 449315b7..c51f5a11 100644 --- a/tests/fixtures/TestDummy.js +++ b/tests/fixtures/TestDummy.js @@ -95,12 +95,13 @@ function dummyStorage (storagePath, override = {}) { var cacheData = override.cache != null ? override.cache : {} - if (cacheData.key == null) cacheData.key = key + if (cacheData.key == null) cacheData.key = keygen() if (cacheData.name == null) cacheData.name = faker.random.word() + if (cacheData.type == null) cacheData.type = 'FILESYSTEM' + cacheData.path = storagePath sander.writeFileSync(path.join(storagePath, 'boostnote.json'), JSON.stringify(jsonData)) var notesData = [] - console.log(notesData) var noteCount = Math.floor((Math.random() * 15)) + 1 for (var i = 0; i < noteCount; i++) { var key = keygen() @@ -133,17 +134,20 @@ function dummyLegacyStorage (storagePath, override = {}) { var cacheData = override.cache != null ? override.cache : {} - if (cacheData.key == null) cacheData.key = key + if (cacheData.key == null) cacheData.key = keygen() if (cacheData.name == null) cacheData.name = faker.random.word() + if (cacheData.type == null) cacheData.type = 'FILESYSTEM' + cacheData.path = storagePath sander.writeFileSync(path.join(storagePath, 'boostnote.json'), JSON.stringify(jsonData)) + var notesData = [] for (var j = 0; j < jsonData.folders.length; j++) { + var folderNotes = [] var noteCount = Math.floor((Math.random() * 5)) + 1 - var notesData = [] for (var i = 0; i < noteCount; i++) { var key = keygen(6) - while (notesData.some((note) => note.key === key)) { + while (folderNotes.some((note) => note.key === key)) { key = keygen(6) } @@ -151,10 +155,10 @@ function dummyLegacyStorage (storagePath, override = {}) { key, folder: jsonData.folders[j].key }) - notesData.push(noteData) + folderNotes.push(noteData) } - - CSON.writeFileSync(path.join(storagePath, jsonData.folders[j].key, 'data.json'), {notes: notesData.map((note) => _.omit(note, ['folder']))}) + notesData = notesData.concat(folderNotes) + CSON.writeFileSync(path.join(storagePath, jsonData.folders[j].key, 'data.json'), {notes: folderNotes.map((note) => _.omit(note, ['folder']))}) } return { From db3a4d0f01b27c7b2cc0edff9d0847bfb37b2296 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 14:01:31 +0900 Subject: [PATCH 05/34] renew init method --- browser/main/lib/dataApi/init.js | 77 ++++++---------- .../main/lib/dataApi/resolveStorageData.js | 39 ++++++++ .../main/lib/dataApi/resolveStorageNotes.js | 30 +++++++ tests/dataApi/init.js | 88 ++++++++----------- 4 files changed, 134 insertions(+), 100 deletions(-) create mode 100644 browser/main/lib/dataApi/resolveStorageData.js create mode 100644 browser/main/lib/dataApi/resolveStorageNotes.js diff --git a/browser/main/lib/dataApi/init.js b/browser/main/lib/dataApi/init.js index c30b4a98..7488dce0 100644 --- a/browser/main/lib/dataApi/init.js +++ b/browser/main/lib/dataApi/init.js @@ -1,8 +1,7 @@ 'use strict' const _ = require('lodash') -const sander = require('sander') -const path = require('path') - +const resolveStorageData = require('./resolveStorageData') +const resolveStorageNotes = require('./resolveStorageNotes') /** * @return {Object} all storages and notes * ``` @@ -11,6 +10,11 @@ const path = require('path') * notes: [...] * } * ``` + * + * This method deals with 3 patterns. + * 1. v1 + * 2. legacy + * 3. empty directory */ function init () { let fetchStorages = function () { @@ -19,67 +23,38 @@ function init () { rawStorages = JSON.parse(window.localStorage.getItem('storages')) if (!_.isArray(rawStorages)) throw new Error('Cached data is not valid.') } catch (e) { - console.error(e) + console.warn('Failed to parse cached data from localStorage', e) rawStorages = [] window.localStorage.setItem('storages', JSON.stringify(rawStorages)) } return Promise.all(rawStorages - .map(function assignFoldersToStorage (rawStorage) { - let data - let boostnoteJSONPath = path.join(rawStorage.path, 'boostnote.json') - try { - data = JSON.parse(sander.readFileSync(boostnoteJSONPath)) - if (!_.isArray(data.folders)) throw new Error('folders should be an array.') - rawStorage.folders = data.folders - } catch (err) { - if (err.code === 'ENOENT') { - console.warn('boostnote.json file doesn\'t exist the given path') - } else { - console.error(err) - } - rawStorage.folders = [] - } - return Promise.resolve(rawStorage) - })) + .map(resolveStorageData)) } let fetchNotes = function (storages) { - let notes = [] - - storages - .forEach((storage) => { - storage.folders.forEach((folder) => { - let dataPath = path.join(storage.path, folder.key, 'data.json') - let data - try { - data = JSON.parse(sander.readFileSync(dataPath)) - if (!_.isArray(data.notes)) throw new Error('notes should be an array.') - } catch (e) { - // Remove folder if fetching failed. - console.error('Failed to load data: %s', dataPath) - storage.folders = storage.folders.filter((_folder) => _folder.key !== folder.key) - data = {notes: []} - } - data.notes.forEach((note) => { - note.storage = storage.key - note.folder = folder.key - notes.push(note) - }) - }) + let findNotesFromEachStorage = storages + .map(resolveStorageNotes) + return Promise.all(findNotesFromEachStorage) + .then(function concatNoteGroup (noteGroups) { + return noteGroups.reduce(function (sum, group) { + return sum.concat(group) + }, []) + }) + .then(function returnData (notes) { + return { + storages, + notes + } }) - return Promise.resolve({ - storages, - notes - }) } return Promise.resolve(fetchStorages()) .then((storages) => { - storages = storages.filter((storage) => { - if (!_.isObject(storage)) return false - return true - }) return storages + .filter((storage) => { + if (!_.isObject(storage)) return false + return true + }) }) .then(fetchNotes) } diff --git a/browser/main/lib/dataApi/resolveStorageData.js b/browser/main/lib/dataApi/resolveStorageData.js new file mode 100644 index 00000000..3a34e862 --- /dev/null +++ b/browser/main/lib/dataApi/resolveStorageData.js @@ -0,0 +1,39 @@ +const _ = require('lodash') +const path = require('path') +const CSON = require('season') +const transform = require('./transform') + +function resolveStorageData (storageCache) { + let storage = { + key: storageCache.key, + name: storageCache.name, + type: storageCache.type, + path: storageCache.path + } + + const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json') + try { + let jsonData = CSON.readFileSync(boostnoteJSONPath) + if (!_.isArray(jsonData.folders)) throw new Error('folders should be an array.') + storage.folders = jsonData.folders + storage.version = jsonData.version + } catch (err) { + if (err.code === 'ENOENT') { + console.warn('boostnote.json file doesn\'t exist the given path') + CSON.writeFileSync(boostnoteJSONPath, {folders: [], version: '1.0'}) + } else { + console.error(err) + } + storage.folders = [] + storage.version = '1.0' + } + + if (storage.version === '1.0') { + return Promise.resolve(storage) + } + console.log('Transform Legacy storage', storage.path) + return transform(storage.path) + .then(() => storage) +} + +module.exports = resolveStorageData diff --git a/browser/main/lib/dataApi/resolveStorageNotes.js b/browser/main/lib/dataApi/resolveStorageNotes.js new file mode 100644 index 00000000..68885fce --- /dev/null +++ b/browser/main/lib/dataApi/resolveStorageNotes.js @@ -0,0 +1,30 @@ +const sander = require('sander') +const path = require('path') +const CSON = require('season') + +function resolveStorageNotes (storage) { + const notesDirPath = path.join(storage.path, 'notes') + let notePathList + try { + notePathList = sander.readdirSync(notesDirPath) + } catch (err) { + if (err.code === 'ENOENT') { + console.log(notesDirPath, ' doesn\'t exist.') + sander.mkdirSync(notesDirPath) + } else { + console.warn('Failed to find note dir', notesDirPath, err) + } + notePathList = [] + } + let notes = notePathList + .map(function parseCSONFile (notePath) { + let data = CSON.readFileSync(path.join(notesDirPath, notePath)) + data.key = path.basename(notePath, '.cson') + data.storage = storage.key + return data + }) + + return Promise.resolve(notes) +} + +module.exports = resolveStorageNotes diff --git a/tests/dataApi/init.js b/tests/dataApi/init.js index 2d47a904..0f4a1ed4 100644 --- a/tests/dataApi/init.js +++ b/tests/dataApi/init.js @@ -8,68 +8,58 @@ 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 crypto = require('crypto') +const TestDummy = require('../fixtures/TestDummy') +const keygen = require('browser/lib/keygen') +const sander = require('sander') +const _ = require('lodash') +const os = require('os') + +const v1StoragePath = path.join(os.tmpdir(), 'test/init-v1-storage') +const legacyStoragePath = path.join(os.tmpdir(), 'test/init-legacy-storage') +const emptyDirPath = path.join(os.tmpdir(), 'test/init-empty-storage') + +test.beforeEach((t) => { + localStorage.clear() + // Prepare 3 types of dir + t.context.v1StorageData = TestDummy.dummyStorage(v1StoragePath, {cache: {name: 'v1'}}) + t.context.legacyStorageData = TestDummy.dummyLegacyStorage(legacyStoragePath, {cache: {name: 'legacy'}}) + t.context.emptyStorageData = { + cache: { + type: 'FILESYSTEM', + name: 'empty', + key: keygen(), + path: emptyDirPath + } + } + + localStorage.setItem('storages', JSON.stringify([t.context.v1StorageData.cache, t.context.legacyStorageData.cache, t.context.emptyStorageData.cache])) +}) test.serial('Fetch storages and notes', (t) => { - const dummyStoragePath = path.join(__dirname, '..', 'dummy/dummyStorage') - const dummyRawStorage = { - name: 'test1', - key: crypto.randomBytes(6).toString('hex'), - path: dummyStoragePath - } - const dummyFolderKey = 'fc6ba88e8ecf' - + const { v1StorageData, legacyStorageData, emptyStorageData } = t.context return Promise.resolve() - .then(function before () { - localStorage.setItem('storages', JSON.stringify([dummyRawStorage])) - }) .then(function test () { return init() }) .then(function assert (data) { t.true(Array.isArray(data.storages)) - var targetStorage = data.storages.filter((storage) => storage.key === dummyRawStorage.key)[0] - t.not(targetStorage, null) - t.is(targetStorage.name, dummyRawStorage.name) - t.is(targetStorage.key, dummyRawStorage.key) - t.is(targetStorage.path, dummyRawStorage.path) - t.is(data.notes.length, 2) - data.notes.forEach((note) => { - t.is(note.folder, dummyFolderKey) + t.is(data.notes.length, v1StorageData.notes.length + legacyStorageData.notes.length) + t.is(data.storages.length, 3) + data.storages.forEach(function assertStorage (storage) { + t.true(_.isString(storage.key)) + t.true(_.isString(storage.name)) + t.true(storage.type === 'FILESYSTEM') + t.true(_.isString(storage.path)) }) - - t.true(Array.isArray(data.notes)) }) .then(function after () { localStorage.clear() }) }) -test.serial('If storage path is a empty folder, return metadata with empty folder array and empty note array.', (t) => { - const emptyFolderPath = path.join(__dirname, '..', 'dummy/empty') - const dummyRawStorage = { - name: 'test2', - key: crypto.randomBytes(6).toString('hex'), - path: emptyFolderPath - } - return Promise.resolve() - .then(function before () { - localStorage.setItem('storages', JSON.stringify([dummyRawStorage])) - }) - .then(function test () { - return init() - }) - .then(function assert (data) { - t.true(Array.isArray(data.storages)) - var targetStorage = data.storages.filter((storage) => storage.key === dummyRawStorage.key)[0] - t.not(targetStorage, null) - t.is(targetStorage.name, dummyRawStorage.name) - t.is(targetStorage.key, dummyRawStorage.key) - t.is(targetStorage.path, dummyRawStorage.path) - - t.true(Array.isArray(data.notes)) - }) - .then(function after () { - localStorage.clear() - }) +test.after.always(() => { + localStorage.clear() + sander.rimrafSync(v1StoragePath) + sander.rimrafSync(legacyStoragePath) + sander.rimrafSync(emptyDirPath) }) From 55584033587dfa4b7e7bf43b391b0dfe053b1a9c Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 15:02:00 +0900 Subject: [PATCH 06/34] renew addStorage --- browser/main/lib/dataApi/addStorage.js | 63 ++------------- tests/dataApi/addStorage.js | 102 +++++++++---------------- 2 files changed, 41 insertions(+), 124 deletions(-) diff --git a/browser/main/lib/dataApi/addStorage.js b/browser/main/lib/dataApi/addStorage.js index 794c0916..8d760b12 100644 --- a/browser/main/lib/dataApi/addStorage.js +++ b/browser/main/lib/dataApi/addStorage.js @@ -1,11 +1,7 @@ const _ = require('lodash') const keygen = require('browser/lib/keygen') -const sander = require('sander') -const path = require('path') - -const defaultBoostnoteJSON = { - folders: [] -} +const resolveStorageData = require('./resolveStorageData') +const resolveStorageNotes = require('./resolveStorageNotes') /** * @param {Object} @@ -42,30 +38,8 @@ function addStorage (input) { path: input.path } - const boostnoteJSONPath = path.join(newStorage.path, 'boostnote.json') - return Promise.resolve(newStorage) - .then(function resolveBoostnoteJSON () { - return sander.readFile(boostnoteJSONPath) - .then(function checkBoostnoteJSONExists (data) { - let parsedData = JSON.parse(data.toString()) - if (!_.isArray(parsedData.folders)) throw new Error('`folders` must be array.') - - newStorage.folders = parsedData.folders - .filter(function takeOnlyValidKey (folder) { - return _.isString(folder.key) - }) - return newStorage - }) - .catch(function tryToRewriteNewBoostnoteJSON (err) { - return sander - .writeFile(boostnoteJSONPath, JSON.stringify(defaultBoostnoteJSON)) - .then(function () { - newStorage.folders = defaultBoostnoteJSON.folders - return newStorage - }) - }) - }) + .then(resolveStorageData) .then(function saveMetadataToLocalStorage () { rawStorages.push({ key: newStorage.key, @@ -75,36 +49,9 @@ function addStorage (input) { }) localStorage.setItem('storages', JSON.stringify(rawStorages)) + return newStorage }) - .then(function fetchNotes () { - var folderNotes = newStorage.folders - .map(function fetchNotesFromEachFolder (folder) { - var folderDataJSONPath = path.join(newStorage.path, folder.key, 'data.json') - return sander.readFile(folderDataJSONPath) - .then(function parseData (rawData) { - return JSON.parse(rawData) - }) - .then(function validateNotes (data) { - if (!_.isArray(data.notes)) throw new Error('Invalid data.json') - return data.notes - .map(function (note) { - note.folder = folder.key - note.storage = newStorage.key - return note - }) - }) - .catch(function rewriteNotes (err) { - console.error(err) - return [] - }) - }) - return Promise.all(folderNotes) - .then(function reduceFolderNotes (folderNotes) { - return folderNotes.reduce(function (sum, notes) { - return sum.concat(notes) - }, []) - }) - }) + .then(resolveStorageNotes) .then(function returnValue (notes) { return { storage: newStorage, diff --git a/tests/dataApi/addStorage.js b/tests/dataApi/addStorage.js index 26d24fe9..e834551b 100644 --- a/tests/dataApi/addStorage.js +++ b/tests/dataApi/addStorage.js @@ -8,86 +8,56 @@ 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 sander = require('sander') const _ = require('lodash') +const os = require('os') -function copyFile (filePath, targetPath) { - return sander.readFile(filePath) - .then(function writeFile (data) { - return sander.writeFile(targetPath, data.toString()) - }) -} +const v1StoragePath = path.join(os.tmpdir(), 'test/addStorage-v1-storage') +// const legacyStoragePath = path.join(os.tmpdir(), 'test/addStorage-legacy-storage') +// const emptyDirPath = path.join(os.tmpdir(), 'test/addStorage-empty-storage') -test('add a initialized storage', (t) => { - const dummyStoragePath = path.join(__dirname, '../dummy/dummyStorage') - const targetPath = path.join(__dirname, '../sandbox/test-add-storage1') +test.beforeEach((t) => { + t.context.v1StorageData = TestDummy.dummyStorage(v1StoragePath) + // t.context.legacyStorageData = TestDummy.dummyLegacyStorage(legacyStoragePath) + + localStorage.setItem('storages', JSON.stringify([])) +}) + +test.serial('add a initialized storage', (t) => { const input = { type: 'FILESYSTEM', - name: 'test-add-storage1', - path: targetPath + name: 'add-storage1', + path: v1StoragePath } return Promise.resolve() - .then(function before () { - localStorage.setItem('storages', JSON.stringify([])) - - sander.rimrafSync(targetPath) - return copyFile(path.join(dummyStoragePath, 'boostnote.json'), path.join(targetPath, 'boostnote.json')) - .then(() => { - return copyFile(path.join(dummyStoragePath, 'fc6ba88e8ecf/data.json'), path.join(targetPath, 'fc6ba88e8ecf/data.json')) - }) - }) - .then(function doTest (data) { + .then(function doTest () { return addStorage(input) }) .then(function validateResult (data) { - const { storage, notes } = data + let { storage, notes } = data + // Check data.storage t.true(_.isString(storage.key)) - t.is(storage.name, 'test-add-storage1') - t.true(_.isArray(storage.folders)) - t.is(storage.folders.length, 1) - t.true(_.isArray(notes)) - t.is(notes.length, 2) - t.is(notes[0].folder, 'fc6ba88e8ecf') - t.is(notes[0].storage, storage.key) - }) - .then(function after () { - localStorage.clear() - sander.rimrafSync(targetPath) + t.is(storage.name, input.name) + t.is(storage.type, input.type) + t.is(storage.path, input.path) + + // Check data.notes + t.is(notes.length, t.context.v1StorageData.notes.length) + notes.forEach(function validateNote (note) { + t.is(note.storage, storage.key) + }) + + // Check localStorage + let cacheData = _.find(JSON.parse(localStorage.getItem('storages')), {key: data.storage.key}) + t.is(cacheData.name, input.name) + t.is(cacheData.type, input.type) + t.is(cacheData.path, input.path) }) }) -test('add a fresh storage', (t) => { - const targetPath = path.join(__dirname, '../sandbox/test-add-storage2') - const input = { - type: 'FILESYSTEM', - name: 'test-add-storage2', - path: targetPath - } - return Promise.resolve() - .then(function before () { - localStorage.setItem('storages', JSON.stringify([])) - - sander.rimrafSync(targetPath) - }) - .then(function doTest (data) { - return addStorage(input) - }) - .then(function validateResult (data) { - const { storage, notes } = data - - t.true(_.isString(storage.key)) - t.is(storage.name, 'test-add-storage2') - t.true(_.isArray(storage.folders)) - t.is(storage.folders.length, 0) - - t.true(_.isArray(notes)) - t.is(notes.length, 0) - - t.true(sander.statSync(path.join(targetPath, 'boostnote.json')).isFile()) - }) - .then(function after () { - localStorage.clear() - sander.rimrafSync(targetPath) - }) +test.after.always(() => { + localStorage.clear() + sander.rimrafSync(v1StoragePath) }) From eb163ef03c25aef98ced9c3b83031d908b731172 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 15:18:28 +0900 Subject: [PATCH 07/34] renew renameStorage api --- browser/main/lib/dataApi/renameStorage.js | 26 +++++++++----------- tests/dataApi/renameStorage.js | 30 +++++++++++------------ 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/browser/main/lib/dataApi/renameStorage.js b/browser/main/lib/dataApi/renameStorage.js index fde14ac6..5ab86bcb 100644 --- a/browser/main/lib/dataApi/renameStorage.js +++ b/browser/main/lib/dataApi/renameStorage.js @@ -8,24 +8,20 @@ const _ = require('lodash') function renameStorage (key, name) { if (!_.isString(name)) return Promise.reject(new Error('Name must be a string.')) - let rawStorages + let cachedStorageList try { - rawStorages = JSON.parse(localStorage.getItem('storages')) - if (!_.isArray(rawStorages)) throw new Error('invalid storages') - } catch (e) { - console.warn(e) - rawStorages = [] + cachedStorageList = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(cachedStorageList)) throw new Error('invalid storages') + } catch (err) { + console.log('error got') + console.error(err) + return Promise.reject(err) } + let targetStorage = _.find(cachedStorageList, {key: key}) + if (targetStorage == null) return Promise.reject('Storage') - let targetStorage - for (let i = 0; i < rawStorages.length; i++) { - if (rawStorages[i].key === key) { - rawStorages[i].name = name - targetStorage = rawStorages[i] - } - } - - localStorage.setItem('storages', JSON.stringify(rawStorages)) + targetStorage.name = name + localStorage.setItem('storages', JSON.stringify(cachedStorageList)) return Promise.resolve(targetStorage) } diff --git a/tests/dataApi/renameStorage.js b/tests/dataApi/renameStorage.js index d54f767c..cbbed615 100644 --- a/tests/dataApi/renameStorage.js +++ b/tests/dataApi/renameStorage.js @@ -10,29 +10,29 @@ const localStorage = window.localStorage = global.localStorage = new Storage(nul const path = require('path') const crypto = require('crypto') const _ = require('lodash') +const TestDummy = require('../fixtures/TestDummy') +const sander = require('sander') +const os = require('os') -test('Rename a storage', (t) => { - const dummyStoragePath = path.join(__dirname, '..', 'dummy/dummyStorage') - const dummyStorageKey = crypto.randomBytes(6).toString('hex') - const dummyRawStorage = { - name: 'test1', - key: dummyStorageKey, - path: dummyStoragePath - } +const storagePath = path.join(os.tmpdir(), 'test/rename-storage') +test.beforeEach((t) => { + t.context.storage = TestDummy.dummyStorage(storagePath) + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) +}) +test.serial('Rename a storage', (t) => { + const stoargeKey = t.context.storage.cache.key return Promise.resolve() - .then(function before () { - localStorage.setItem('storages', JSON.stringify([dummyRawStorage])) - }) - .then(function test () { - return renameStorage(dummyStorageKey, 'test2') + .then(function doTest () { + return renameStorage(stoargeKey, 'changed') }) .then(function assert (data) { - let rawStorages = JSON.parse(localStorage.getItem('storages')) - t.true(_.find(rawStorages, {key: dummyStorageKey}).name === 'test2') + let cachedStorageList = JSON.parse(localStorage.getItem('storages')) + t.true(_.find(cachedStorageList, {key: stoargeKey}).name === 'changed') }) }) test.after(function after () { localStorage.clear() + sander.rimrafSync(storagePath) }) From 8d96368ea6753523e41a6ffd0200957ff8e0ca96 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 16:05:29 +0900 Subject: [PATCH 08/34] cleanup test remove crypto dependency from renameStorage refactor removeStorage remove dummy files --- tests/dataApi/removeStorage.js | 28 +++++++++---------- tests/dataApi/renameStorage.js | 2 +- tests/dummy/dummyStorage/boostnote.json | 9 ------ .../dummy/dummyStorage/fc6ba88e8ecf/data.json | 24 ---------------- 4 files changed, 15 insertions(+), 48 deletions(-) delete mode 100644 tests/dummy/dummyStorage/boostnote.json delete mode 100644 tests/dummy/dummyStorage/fc6ba88e8ecf/data.json diff --git a/tests/dataApi/removeStorage.js b/tests/dataApi/removeStorage.js index 09c8473a..041983b7 100644 --- a/tests/dataApi/removeStorage.js +++ b/tests/dataApi/removeStorage.js @@ -8,23 +8,22 @@ 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 crypto = require('crypto') +const TestDummy = require('../fixtures/TestDummy') +const sander = require('sander') +const os = require('os') + +const storagePath = path.join(os.tmpdir(), 'test/remove-storage') + +test.beforeEach((t) => { + t.context.storage = TestDummy.dummyStorage(storagePath) + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) +}) test('Remove a storage', (t) => { - const dummyStoragePath = path.join(__dirname, '..', 'dummy/dummyStorage') - const dummyStorageKey = crypto.randomBytes(6).toString('hex') - const dummyRawStorage = { - name: 'test1', - key: dummyStorageKey, - path: dummyStoragePath - } - + const stoargeKey = t.context.storage.cache.key return Promise.resolve() - .then(function before () { - localStorage.setItem('storages', JSON.stringify([dummyRawStorage])) - }) - .then(function test () { - return removeStorage(dummyStorageKey) + .then(function doTest () { + return removeStorage(stoargeKey) }) .then(function assert (data) { t.is(JSON.parse(localStorage.getItem('storages')).length, 0) @@ -33,4 +32,5 @@ test('Remove a storage', (t) => { test.after(function after () { localStorage.clear() + sander.rimrafSync(storagePath) }) diff --git a/tests/dataApi/renameStorage.js b/tests/dataApi/renameStorage.js index cbbed615..de4b9e79 100644 --- a/tests/dataApi/renameStorage.js +++ b/tests/dataApi/renameStorage.js @@ -8,13 +8,13 @@ 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 crypto = require('crypto') const _ = require('lodash') const TestDummy = require('../fixtures/TestDummy') const sander = require('sander') const os = require('os') const storagePath = path.join(os.tmpdir(), 'test/rename-storage') + test.beforeEach((t) => { t.context.storage = TestDummy.dummyStorage(storagePath) localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) diff --git a/tests/dummy/dummyStorage/boostnote.json b/tests/dummy/dummyStorage/boostnote.json deleted file mode 100644 index ae4e4df4..00000000 --- a/tests/dummy/dummyStorage/boostnote.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "folders": [ - { - "key": "fc6ba88e8ecf", - "name": "test", - "color": "#FF5555" - } - ] -} diff --git a/tests/dummy/dummyStorage/fc6ba88e8ecf/data.json b/tests/dummy/dummyStorage/fc6ba88e8ecf/data.json deleted file mode 100644 index 08e002c1..00000000 --- a/tests/dummy/dummyStorage/fc6ba88e8ecf/data.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "notes": [ - { - "tags": [], - "title": "Footnote test", - "content": "# Footnote test\n\ntest test", - "type": "MARKDOWN_NOTE", - "key": "93c6ac2a7953", - "isStarred": false, - "createdAt": "2016-07-25T16:19:55.620Z", - "updatedAt": "2016-07-26T08:00:11.326Z" - }, - { - "tags": [], - "title": "Checkbox test", - "content": "# Checkbox test\n\n- [x] Task1\n- [ ] Task2\n- [ ] Task3\n\n", - "type": "MARKDOWN_NOTE", - "key": "4568d84331d9", - "isStarred": false, - "createdAt": "2016-07-25T16:58:43.685Z", - "updatedAt": "2016-08-21T06:14:50.381Z" - } - ] -} From 67dd089e67973b4ef733333ee42292bf82c03a5b Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 16:31:45 +0900 Subject: [PATCH 09/34] createFolder --- browser/main/lib/dataApi/createFolder.js | 53 ++++++-------------- tests/dataApi/createFolder.js | 61 ++++++++++-------------- 2 files changed, 39 insertions(+), 75 deletions(-) diff --git a/browser/main/lib/dataApi/createFolder.js b/browser/main/lib/dataApi/createFolder.js index 630d273f..2fb21a73 100644 --- a/browser/main/lib/dataApi/createFolder.js +++ b/browser/main/lib/dataApi/createFolder.js @@ -1,11 +1,8 @@ const _ = require('lodash') -const sander = require('sander') const keygen = require('browser/lib/keygen') const path = require('path') - -const defaultDataJSON = { - notes: [] -} +const resolveStorageData = require('./resolveStorageData') +const CSON = require('season') /** * @param {String} storageKey @@ -17,7 +14,12 @@ const defaultDataJSON = { * } * ``` * - * @return {key} + * @return {Object} + * ``` + * { + * storage: Object + * } + * ``` */ function createFolder (storageKey, input) { let rawStorages @@ -36,49 +38,24 @@ function createFolder (storageKey, input) { return Promise.reject(e) } - const storageData = Object.assign({}, targetStorage) - - const boostnoteJSONPath = path.join(targetStorage.path, 'boostnote.json') - return Promise.resolve() - .then(function fetchBoostnoteJSON () { - return sander.readFile(boostnoteJSONPath) - }) - .then(function updateBoostnoteJSON (data) { - let boostnoteJSON - // If `boostnote.json` is invalid, reset `boostnote.json`. - try { - boostnoteJSON = JSON.parse(data) - if (!_.isArray(boostnoteJSON.folders)) throw new Error('the value of `folders` must be array') - } catch (err) { - boostnoteJSON = { - folders: [] - } - } - + return resolveStorageData(targetStorage) + .then(function createFolder (storage) { let key = keygen() - while (boostnoteJSON.folders.some((folder) => folder.key === key)) { + while (storage.folders.some((folder) => folder.key === key)) { key = keygen() } - let newFolder = { key, color: input.color, name: input.name } - boostnoteJSON.folders.push(newFolder) - storageData.folders = boostnoteJSON.folders + storage.folders.push(newFolder) + + CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version'])) - return sander.writeFile(boostnoteJSONPath, JSON.stringify(boostnoteJSON)) - .then(() => newFolder) - }) - .then(function createDataJSON (newFolder) { - const folderDirPath = path.join(targetStorage.path, newFolder.key) - return sander.writeFile(folderDirPath, 'data.json', JSON.stringify(defaultDataJSON)) - }) - .then(function returnData () { return { - storage: storageData + storage } }) } diff --git a/tests/dataApi/createFolder.js b/tests/dataApi/createFolder.js index 6c6c19f6..80def1bf 100644 --- a/tests/dataApi/createFolder.js +++ b/tests/dataApi/createFolder.js @@ -1,6 +1,5 @@ const test = require('ava') const createFolder = require('browser/main/lib/dataApi/createFolder') -const sander = require('sander') global.document = require('jsdom').jsdom('') global.window = document.defaultView @@ -9,50 +8,38 @@ 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 crypto = require('crypto') const _ = require('lodash') +const TestDummy = require('../fixtures/TestDummy') +const sander = require('sander') +const os = require('os') +const CSON = require('season') -function copyFile (filePath, targetPath) { - return sander.readFile(filePath) - .then(function writeFile (data) { - return sander.writeFile(targetPath, data.toString()) - }) -} +const storagePath = path.join(os.tmpdir(), 'test/rename-storage') -const dummyStoragePath = path.join(__dirname, '..', 'dummy/dummyStorage') -const targetPath = path.join(__dirname, '../sandbox/test-add-folder') -const dummyRawStorage = { - name: 'test1', - key: crypto.randomBytes(6).toString('hex'), - path: targetPath -} -test.before(function () { - localStorage.setItem('storages', JSON.stringify([dummyRawStorage])) - - return copyFile(path.join(dummyStoragePath, 'boostnote.json'), path.join(targetPath, 'boostnote.json')) +test.beforeEach((t) => { + t.context.storage = TestDummy.dummyStorage(storagePath) + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) }) -const input = { - name: 'test folder', - color: '#FF5555' -} -test('Add note to storage', (t) => { - return createFolder(dummyRawStorage.key, input) +test.serial('Create a folder', (t) => { + const stoargeKey = t.context.storage.cache.key + const input = { + name: 'created', + color: '#ff5555' + } + return Promise.resolve() + .then(function doTest () { + return createFolder(stoargeKey, input) + }) .then(function assert (data) { - t.not(data.storage, null) - - let targetFolder = _.find(data.storage.folders, { - name: input.name, - color: input.color - }) - - t.not(targetFolder, null) - - t.true(sander.statSync(path.join(targetPath, targetFolder.key)).isDirectory()) + t.true(_.find(data.storage.folders, input) != null) + let jsonData = CSON.readFileSync(path.join(data.storage.path, 'boostnote.json')) + console.log(path.join(data.storage.path, 'boostnote.json')) + t.true(_.find(jsonData.folders, input) != null) }) }) -test.after.always(function () { +test.after(function after () { localStorage.clear() - sander.rimrafSync(targetPath) + sander.rimrafSync(storagePath) }) From 8178ec5671a6ecf2a9e6895495837dfc82beb789 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 16:36:37 +0900 Subject: [PATCH 10/34] cleanup tests remove mock-fs update the comment of TestDummy.dummyStorage change dummy storage name of createFolder test --- package.json | 1 - tests/dataApi/createFolder.js | 2 +- tests/fixtures/TestDummy.js | 7 +++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 0e2bb2d1..50918fb6 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,6 @@ "history": "^1.17.0", "jsdom": "^9.4.2", "merge-stream": "^1.0.0", - "mock-fs": "^3.11.0", "nib": "^1.1.0", "oh-my-cdn": "^0.1.1", "react": "^15.3.0", diff --git a/tests/dataApi/createFolder.js b/tests/dataApi/createFolder.js index 80def1bf..1761eef7 100644 --- a/tests/dataApi/createFolder.js +++ b/tests/dataApi/createFolder.js @@ -14,7 +14,7 @@ const sander = require('sander') const os = require('os') const CSON = require('season') -const storagePath = path.join(os.tmpdir(), 'test/rename-storage') +const storagePath = path.join(os.tmpdir(), 'test/create-folder') test.beforeEach((t) => { t.context.storage = TestDummy.dummyStorage(storagePath) diff --git a/tests/fixtures/TestDummy.js b/tests/fixtures/TestDummy.js index c51f5a11..19cfa9c5 100644 --- a/tests/fixtures/TestDummy.js +++ b/tests/fixtures/TestDummy.js @@ -76,11 +76,14 @@ function dummyNote (override = {}) { * @param {Object} * ``` * { - * json: {folders: []}, + * json: { + * folders: [] + * version: String(enum:'1.0') + * }, * cache: { * key: String, * name: String, - * type: String(enum:FILESYSTEM), + * type: String(enum:'FILESYSTEM'), * path: String * }, * notes: [] From ff123be8952c3a2acd792773e4202cd24561bcdd Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 17:38:43 +0900 Subject: [PATCH 11/34] updateFolder --- browser/main/lib/dataApi/updateFolder.js | 55 ++++++++++++++++++++++++ tests/dataApi/updateFolder.js | 46 ++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 browser/main/lib/dataApi/updateFolder.js create mode 100644 tests/dataApi/updateFolder.js diff --git a/browser/main/lib/dataApi/updateFolder.js b/browser/main/lib/dataApi/updateFolder.js new file mode 100644 index 00000000..b6dd4485 --- /dev/null +++ b/browser/main/lib/dataApi/updateFolder.js @@ -0,0 +1,55 @@ +const _ = require('lodash') +const path = require('path') +const resolveStorageData = require('./resolveStorageData') +const CSON = require('season') + +/** + * @param {String} storageKey + * @param {String} folderKey + * @param {Object} input + * ``` + * { + * color: String, + * name: String + * } + * ``` + * + * @return {Object} + * ``` + * { + * storage: Object + * } + * ``` + */ +function updateFolder (storageKey, folderKey, input) { + let rawStorages + let targetStorage + try { + if (input == null) throw new Error('No input found.') + if (!_.isString(input.name)) throw new Error('Name must be a string.') + if (!_.isString(input.color)) throw new Error('Color must be a string.') + + rawStorages = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(rawStorages)) throw new Error('Target storage doesn\'t exist.') + + targetStorage = _.find(rawStorages, {key: storageKey}) + if (targetStorage == null) throw new Error('Target storage doesn\'t exist.') + } catch (e) { + return Promise.reject(e) + } + + return resolveStorageData(targetStorage) + .then(function updateFolder (storage) { + let targetFolder = _.find(storage.folders, {key: folderKey}) + targetFolder.name = input.name + targetFolder.color = input.color + + CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version'])) + + return { + storage + } + }) +} + +module.exports = updateFolder diff --git a/tests/dataApi/updateFolder.js b/tests/dataApi/updateFolder.js new file mode 100644 index 00000000..c8aacb60 --- /dev/null +++ b/tests/dataApi/updateFolder.js @@ -0,0 +1,46 @@ +const test = require('ava') +const updateFolder = require('browser/main/lib/dataApi/updateFolder') + +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 _ = require('lodash') +const TestDummy = require('../fixtures/TestDummy') +const sander = require('sander') +const os = require('os') +const CSON = require('season') + +const storagePath = path.join(os.tmpdir(), 'test/update-folder') + +test.beforeEach((t) => { + t.context.storage = TestDummy.dummyStorage(storagePath) + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) +}) + +test.serial('Create a folder', (t) => { + const stoargeKey = t.context.storage.cache.key + const folderKey = t.context.storage.json.folders[0].key + const input = { + name: 'changed', + color: '#FF0000' + } + return Promise.resolve() + .then(function doTest () { + return updateFolder(stoargeKey, folderKey, input) + }) + .then(function assert (data) { + t.true(_.find(data.storage.folders, input) != null) + let jsonData = CSON.readFileSync(path.join(data.storage.path, 'boostnote.json')) + console.log(path.join(data.storage.path, 'boostnote.json')) + t.true(_.find(jsonData.folders, input) != null) + }) +}) + +test.after(function after () { + localStorage.clear() + sander.rimrafSync(storagePath) +}) From ffe3b689c41756dd0978c516fff45c954e51c584 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 20:50:34 +0900 Subject: [PATCH 12/34] deleteFolder --- browser/main/lib/dataApi/deleteFolder.js | 75 ++++++++++++++++++++++++ tests/dataApi/deleteFolder.js | 45 ++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 browser/main/lib/dataApi/deleteFolder.js create mode 100644 tests/dataApi/deleteFolder.js diff --git a/browser/main/lib/dataApi/deleteFolder.js b/browser/main/lib/dataApi/deleteFolder.js new file mode 100644 index 00000000..0cc20dcc --- /dev/null +++ b/browser/main/lib/dataApi/deleteFolder.js @@ -0,0 +1,75 @@ +const _ = require('lodash') +const path = require('path') +const resolveStorageData = require('./resolveStorageData') +const resolveStorageNotes = require('./resolveStorageNotes') +const CSON = require('season') +const sander = require('sander') + +/** + * @param {String} storageKey + * @param {String} folderKey + * + * @return {Object} + * ``` + * { + * storage: Object, + * folder: folderKey + * } + * ``` + */ +function deleteFolder (storageKey, folderKey) { + let rawStorages + let targetStorage + try { + rawStorages = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(rawStorages)) throw new Error('Target storage doesn\'t exist.') + + targetStorage = _.find(rawStorages, {key: storageKey}) + if (targetStorage == null) throw new Error('Target storage doesn\'t exist.') + } catch (e) { + return Promise.reject(e) + } + + return resolveStorageData(targetStorage) + .then(function assignNotes (storage) { + return resolveStorageNotes(storage) + .then((notes) => { + return { + storage, + notes + } + }) + }) + .then(function deleteFolderAndNotes (data) { + let { storage, notes } = data + storage.folders = storage.folders + .filter(function excludeTargetFolder (folder) { + return folder.key !== folderKey + }) + + let targetNotes = notes.filter(function filterTargetNotes (note) { + return note.folder === folderKey + }) + + let deleteAllNotes = targetNotes + .map(function deleteNote (note) { + const notePath = path.join(storage.path, 'notes', note.key + '.cson') + return sander.unlink(notePath) + .catch(function (err) { + console.warn('Failed to delete', notePath, err) + }) + }) + return Promise.all(deleteAllNotes) + .then(() => storage) + }) + .then(function (storage) { + CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version'])) + + return { + storage, + folder: folderKey + } + }) +} + +module.exports = deleteFolder diff --git a/tests/dataApi/deleteFolder.js b/tests/dataApi/deleteFolder.js new file mode 100644 index 00000000..459ff8bb --- /dev/null +++ b/tests/dataApi/deleteFolder.js @@ -0,0 +1,45 @@ +const test = require('ava') +const deleteFolder = require('browser/main/lib/dataApi/deleteFolder') + +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 _ = require('lodash') +const TestDummy = require('../fixtures/TestDummy') +const sander = require('sander') +const os = require('os') +const CSON = require('season') + +const storagePath = path.join(os.tmpdir(), 'test/delete-folder') + +test.beforeEach((t) => { + t.context.storage = TestDummy.dummyStorage(storagePath) + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) +}) + +test.serial('Create a folder', (t) => { + const stoargeKey = t.context.storage.cache.key + const folderKey = t.context.storage.json.folders[0].key + + return Promise.resolve() + .then(function doTest () { + return deleteFolder(stoargeKey, folderKey) + }) + .then(function assert (data) { + t.true(_.find(data.storage.folders, {key: folderKey}) == null) + let jsonData = CSON.readFileSync(path.join(data.storage.path, 'boostnote.json')) + + t.true(_.find(jsonData.folders, {key: folderKey}) == null) + let notePaths = sander.readdirSync(data.storage.path, 'notes') + t.is(notePaths.length, t.context.storage.notes.filter((note) => note.folder !== folderKey).length) + }) +}) + +test.after.always(function after () { + localStorage.clear() + sander.rimrafSync(storagePath) +}) From 0127d5143afdeea20044026a0f6cfff06f2bfa7c Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 22:13:32 +0900 Subject: [PATCH 13/34] createNote --- browser/main/lib/dataApi/createNote.js | 83 ++++++++++++++++++++++++ tests/dataApi/createNote.js | 88 ++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 browser/main/lib/dataApi/createNote.js create mode 100644 tests/dataApi/createNote.js diff --git a/browser/main/lib/dataApi/createNote.js b/browser/main/lib/dataApi/createNote.js new file mode 100644 index 00000000..5d984c49 --- /dev/null +++ b/browser/main/lib/dataApi/createNote.js @@ -0,0 +1,83 @@ +const sander = require('sander') +const resolveStorageData = require('./resolveStorageData') +const _ = require('lodash') +const keygen = require('browser/lib/keygen') +const path = require('path') +const CSON = require('season') + +function validateInput (input) { + if (!_.isArray(input.tags)) input.tags = [] + input.tags = input.tags.filter((tag) => _.isString(tag) && tag.trim().length > 0) + if (!_.isString(input.title)) input.title = '' + input.isStarred = !!input.isStarred + + switch (input.type) { + case 'MARKDOWN_NOTE': + if (!_.isString(input.content)) input.content = '' + break + case 'SNIPPET_NOTE': + if (!_.isString(input.description)) input.description = '' + if (!_.isArray(input.snippets)) { + input.snippets = [{ + name: '', + mode: 'text', + content: '' + }] + } + break + default: + throw new Error('Invalid type: only MARKDOWN_NOTE and SNIPPET_NOTE are available.') + } +} + +function createNote (storageKey, input) { + let targetStorage + try { + if (input == null) throw new Error('No input found.') + input = Object.assign({}, input) + validateInput(input) + + let cachedStorageList = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.') + + targetStorage = _.find(cachedStorageList, {key: storageKey}) + if (targetStorage == null) throw new Error('Target storage doesn\'t exist.') + } catch (e) { + return Promise.reject(e) + } + + return resolveStorageData(targetStorage) + .then(function checkFolderExists (storage) { + if (_.find(storage.folders, {key: input.folder}) == null) { + throw new Error('Target folder doesn\'t exist.') + } + return storage + }) + .then(function saveNote (storage) { + let key = keygen() + let isUnique = false + while (!isUnique) { + try { + sander.statSync(path.join(storage.path, 'notes', key + '.cson')) + } catch (err) { + if (err.code === 'ENOENT') { + isUnique = true + } else { + throw err + } + } + } + let noteData = Object.assign({}, input, { + key, + createdAt: new Date(), + updatedAt: new Date(), + storage: storageKey + }) + + CSON.writeFileSync(path.join(storage.path, 'notes', key + '.cson'), _.omit(noteData, ['key', 'storage'])) + + return noteData + }) +} + +module.exports = createNote diff --git a/tests/dataApi/createNote.js b/tests/dataApi/createNote.js new file mode 100644 index 00000000..74eb2940 --- /dev/null +++ b/tests/dataApi/createNote.js @@ -0,0 +1,88 @@ +const test = require('ava') +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 _ = require('lodash') +const TestDummy = require('../fixtures/TestDummy') +const sander = require('sander') +const os = require('os') +const CSON = require('season') +const faker = require('faker') + +const storagePath = path.join(os.tmpdir(), 'test/create-note') + +test.beforeEach((t) => { + t.context.storage = TestDummy.dummyStorage(storagePath) + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) +}) + +test.serial('Create a note', (t) => { + const stoargeKey = t.context.storage.cache.key + const folderKey = t.context.storage.json.folders[0].key + + const input1 = { + type: 'SNIPPET_NOTE', + description: faker.lorem.lines(), + snippets: [{ + name: faker.system.fileName(), + mode: 'text', + content: faker.lorem.lines() + }], + tags: faker.lorem.words().split(' '), + folder: folderKey + } + input1.title = input1.description.split('\n').shift() + + const input2 = { + type: 'MARKDOWN_NOTE', + content: faker.lorem.lines(), + tags: faker.lorem.words().split(' '), + folder: folderKey + } + input2.title = input2.content.split('\n').shift() + + return Promise.resolve() + .then(function doTest () { + return Promise.all([ + createNote(stoargeKey, input1), + createNote(stoargeKey, input2) + ]) + }) + .then(function assert (data) { + let data1 = data[0] + let data2 = data[1] + + let jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.key + '.cson')) + t.is(input1.title, data1.title) + t.is(input1.title, jsonData1.title) + t.is(input1.description, data1.description) + t.is(input1.description, jsonData1.description) + t.is(input1.tags.length, data1.tags.length) + t.is(input1.tags.length, jsonData1.tags.length) + t.is(input1.snippets.length, data1.snippets.length) + t.is(input1.snippets.length, jsonData1.snippets.length) + t.is(input1.snippets[0].content, data1.snippets[0].content) + t.is(input1.snippets[0].content, jsonData1.snippets[0].content) + t.is(input1.snippets[0].name, data1.snippets[0].name) + t.is(input1.snippets[0].name, jsonData1.snippets[0].name) + + let jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson')) + t.is(input2.title, data2.title) + t.is(input2.title, jsonData2.title) + t.is(input2.content, data2.content) + t.is(input2.content, jsonData2.content) + t.is(input2.tags.length, data2.tags.length) + t.is(input2.tags.length, jsonData2.tags.length) + }) +}) + +test.after(function after () { + localStorage.clear() + sander.rimrafSync(storagePath) +}) From 25f50b8cdf5cad1d944dc56896220669ba2f14d8 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 23:46:08 +0900 Subject: [PATCH 14/34] fix type stoargeKey -> storageKey --- tests/dataApi/addStorage.js | 2 +- tests/dataApi/createFolder.js | 4 ++-- tests/dataApi/createNote.js | 9 +++++---- tests/dataApi/deleteFolder.js | 6 +++--- tests/dataApi/init.js | 2 +- tests/dataApi/removeStorage.js | 4 ++-- tests/dataApi/renameStorage.js | 6 +++--- tests/dataApi/updateFolder.js | 6 +++--- 8 files changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/dataApi/addStorage.js b/tests/dataApi/addStorage.js index e834551b..001595b9 100644 --- a/tests/dataApi/addStorage.js +++ b/tests/dataApi/addStorage.js @@ -24,7 +24,7 @@ test.beforeEach((t) => { localStorage.setItem('storages', JSON.stringify([])) }) -test.serial('add a initialized storage', (t) => { +test.serial('Add Storage', (t) => { const input = { type: 'FILESYSTEM', name: 'add-storage1', diff --git a/tests/dataApi/createFolder.js b/tests/dataApi/createFolder.js index 1761eef7..986123b0 100644 --- a/tests/dataApi/createFolder.js +++ b/tests/dataApi/createFolder.js @@ -22,14 +22,14 @@ test.beforeEach((t) => { }) test.serial('Create a folder', (t) => { - const stoargeKey = t.context.storage.cache.key + const storageKey = t.context.storage.cache.key const input = { name: 'created', color: '#ff5555' } return Promise.resolve() .then(function doTest () { - return createFolder(stoargeKey, input) + return createFolder(storageKey, input) }) .then(function assert (data) { t.true(_.find(data.storage.folders, input) != null) diff --git a/tests/dataApi/createNote.js b/tests/dataApi/createNote.js index 74eb2940..7a983fef 100644 --- a/tests/dataApi/createNote.js +++ b/tests/dataApi/createNote.js @@ -8,7 +8,6 @@ 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 _ = require('lodash') const TestDummy = require('../fixtures/TestDummy') const sander = require('sander') const os = require('os') @@ -23,7 +22,7 @@ test.beforeEach((t) => { }) test.serial('Create a note', (t) => { - const stoargeKey = t.context.storage.cache.key + const storageKey = t.context.storage.cache.key const folderKey = t.context.storage.json.folders[0].key const input1 = { @@ -50,14 +49,15 @@ test.serial('Create a note', (t) => { return Promise.resolve() .then(function doTest () { return Promise.all([ - createNote(stoargeKey, input1), - createNote(stoargeKey, input2) + createNote(storageKey, input1), + createNote(storageKey, input2) ]) }) .then(function assert (data) { let data1 = data[0] let data2 = data[1] + t.is(storageKey, data1.storage) let jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.key + '.cson')) t.is(input1.title, data1.title) t.is(input1.title, jsonData1.title) @@ -72,6 +72,7 @@ test.serial('Create a note', (t) => { t.is(input1.snippets[0].name, data1.snippets[0].name) t.is(input1.snippets[0].name, jsonData1.snippets[0].name) + t.is(storageKey, data2.storage) let jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson')) t.is(input2.title, data2.title) t.is(input2.title, jsonData2.title) diff --git a/tests/dataApi/deleteFolder.js b/tests/dataApi/deleteFolder.js index 459ff8bb..f760c819 100644 --- a/tests/dataApi/deleteFolder.js +++ b/tests/dataApi/deleteFolder.js @@ -21,13 +21,13 @@ test.beforeEach((t) => { localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) }) -test.serial('Create a folder', (t) => { - const stoargeKey = t.context.storage.cache.key +test.serial('Delete a folder', (t) => { + const storageKey = t.context.storage.cache.key const folderKey = t.context.storage.json.folders[0].key return Promise.resolve() .then(function doTest () { - return deleteFolder(stoargeKey, folderKey) + return deleteFolder(storageKey, folderKey) }) .then(function assert (data) { t.true(_.find(data.storage.folders, {key: folderKey}) == null) diff --git a/tests/dataApi/init.js b/tests/dataApi/init.js index 0f4a1ed4..81ef38a4 100644 --- a/tests/dataApi/init.js +++ b/tests/dataApi/init.js @@ -35,7 +35,7 @@ test.beforeEach((t) => { localStorage.setItem('storages', JSON.stringify([t.context.v1StorageData.cache, t.context.legacyStorageData.cache, t.context.emptyStorageData.cache])) }) -test.serial('Fetch storages and notes', (t) => { +test.serial('Initialize All Storages', (t) => { const { v1StorageData, legacyStorageData, emptyStorageData } = t.context return Promise.resolve() .then(function test () { diff --git a/tests/dataApi/removeStorage.js b/tests/dataApi/removeStorage.js index 041983b7..33541df1 100644 --- a/tests/dataApi/removeStorage.js +++ b/tests/dataApi/removeStorage.js @@ -20,10 +20,10 @@ test.beforeEach((t) => { }) test('Remove a storage', (t) => { - const stoargeKey = t.context.storage.cache.key + const storageKey = t.context.storage.cache.key return Promise.resolve() .then(function doTest () { - return removeStorage(stoargeKey) + return removeStorage(storageKey) }) .then(function assert (data) { t.is(JSON.parse(localStorage.getItem('storages')).length, 0) diff --git a/tests/dataApi/renameStorage.js b/tests/dataApi/renameStorage.js index de4b9e79..b898d6f1 100644 --- a/tests/dataApi/renameStorage.js +++ b/tests/dataApi/renameStorage.js @@ -21,14 +21,14 @@ test.beforeEach((t) => { }) test.serial('Rename a storage', (t) => { - const stoargeKey = t.context.storage.cache.key + const storageKey = t.context.storage.cache.key return Promise.resolve() .then(function doTest () { - return renameStorage(stoargeKey, 'changed') + return renameStorage(storageKey, 'changed') }) .then(function assert (data) { let cachedStorageList = JSON.parse(localStorage.getItem('storages')) - t.true(_.find(cachedStorageList, {key: stoargeKey}).name === 'changed') + t.true(_.find(cachedStorageList, {key: storageKey}).name === 'changed') }) }) diff --git a/tests/dataApi/updateFolder.js b/tests/dataApi/updateFolder.js index c8aacb60..9ebbdc9f 100644 --- a/tests/dataApi/updateFolder.js +++ b/tests/dataApi/updateFolder.js @@ -21,8 +21,8 @@ test.beforeEach((t) => { localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) }) -test.serial('Create a folder', (t) => { - const stoargeKey = t.context.storage.cache.key +test.serial('Update a folder', (t) => { + const storageKey = t.context.storage.cache.key const folderKey = t.context.storage.json.folders[0].key const input = { name: 'changed', @@ -30,7 +30,7 @@ test.serial('Create a folder', (t) => { } return Promise.resolve() .then(function doTest () { - return updateFolder(stoargeKey, folderKey, input) + return updateFolder(storageKey, folderKey, input) }) .then(function assert (data) { t.true(_.find(data.storage.folders, input) != null) From ec96021b004dbd644c71ccc5904bdfbf91080a82 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sat, 27 Aug 2016 23:46:12 +0900 Subject: [PATCH 15/34] updateNote --- browser/main/lib/dataApi/updateNote.js | 122 +++++++++++++++++++++++++ tests/dataApi/updateNote.js | 116 +++++++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 browser/main/lib/dataApi/updateNote.js create mode 100644 tests/dataApi/updateNote.js diff --git a/browser/main/lib/dataApi/updateNote.js b/browser/main/lib/dataApi/updateNote.js new file mode 100644 index 00000000..6d132e99 --- /dev/null +++ b/browser/main/lib/dataApi/updateNote.js @@ -0,0 +1,122 @@ +const resolveStorageData = require('./resolveStorageData') +const _ = require('lodash') +const path = require('path') +const CSON = require('season') + +function validateInput (input) { + let validatedInput = {} + + if (input.tags != null) { + if (!_.isArray(input.tags)) validatedInput.tags = [] + validatedInput.tags = input.tags + .filter((tag) => _.isString(tag) && tag.trim().length > 0) + } + + if (input.title != null) { + if (!_.isString(input.title)) validatedInput.title = '' + else validatedInput.title = input.title + } + + if (input.isStarred != null) { + validatedInput.isStarred = !!input.isStarred + } + + validatedInput.type = input.type + switch (input.type) { + case 'MARKDOWN_NOTE': + if (input.content != null) { + if (!_.isString(input.content)) validatedInput.content = '' + else validatedInput.content = input.content + } + return input + case 'SNIPPET_NOTE': + if (input.description != null) { + if (!_.isString(input.description)) validatedInput.description = '' + else validatedInput.description = input.description + } + if (input.snippets != null) { + if (!_.isArray(input.snippets)) { + validatedInput.snippets = [{ + name: '', + mode: 'text', + content: '' + }] + } else { + validatedInput.snippets = input.snippets + } + validatedInput.snippets.filter((snippet) => { + if (!_.isString(snippet.name)) return false + if (!_.isString(snippet.mode)) return false + if (!_.isString(snippet.content)) return false + return true + }) + } + return validatedInput + default: + throw new Error('Invalid type: only MARKDOWN_NOTE and SNIPPET_NOTE are available.') + } +} + +function updateNote (storageKey, noteKey, input) { + let targetStorage + try { + if (input == null) throw new Error('No input found.') + input = validateInput(input) + + let cachedStorageList = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.') + + targetStorage = _.find(cachedStorageList, {key: storageKey}) + if (targetStorage == null) throw new Error('Target storage doesn\'t exist.') + } catch (e) { + return Promise.reject(e) + } + + return resolveStorageData(targetStorage) + .then(function saveNote (storage) { + let noteData + let notePath = path.join(storage.path, 'notes', noteKey + '.cson') + try { + noteData = CSON.readFileSync(notePath) + } catch (err) { + console.warn('Failed to find note cson', err) + noteData = input.type === 'SNIPPET_NOTE' + ? { + type: 'SNIPPET_NOTE', + description: [], + snippets: [{ + name: '', + mode: 'text', + content: '' + }] + } + : { + type: 'MARKDOWN_NOTE', + content: '' + } + noteData.title = '' + if (storage.folders.length === 0) throw new Error('Failed to restore note: No folder exists.') + noteData.folder = storage.folders[0].key + noteData.createdAt = new Date() + noteData.updatedAt = new Date() + noteData.isStarred = false + noteData.tags = [] + } + + if (noteData.type === 'SNIPPET_NOTE') { + noteData.title + } + + Object.assign(noteData, input, { + key: noteKey, + updatedAt: new Date(), + storage: storageKey + }) + + CSON.writeFileSync(path.join(storage.path, 'notes', noteKey + '.cson'), _.omit(noteData, ['key', 'storage'])) + + return noteData + }) +} + +module.exports = updateNote diff --git a/tests/dataApi/updateNote.js b/tests/dataApi/updateNote.js new file mode 100644 index 00000000..5f90fb10 --- /dev/null +++ b/tests/dataApi/updateNote.js @@ -0,0 +1,116 @@ +const test = require('ava') +const createNote = require('browser/main/lib/dataApi/createNote') +const updateNote = require('browser/main/lib/dataApi/updateNote') + +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 sander = require('sander') +const os = require('os') +const CSON = require('season') +const faker = require('faker') + +const storagePath = path.join(os.tmpdir(), 'test/update-note') + +test.beforeEach((t) => { + t.context.storage = TestDummy.dummyStorage(storagePath) + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) +}) + +test.serial('Create a note', (t) => { + const storageKey = t.context.storage.cache.key + const folderKey = t.context.storage.json.folders[0].key + + const input1 = { + type: 'SNIPPET_NOTE', + description: faker.lorem.lines(), + snippets: [{ + name: faker.system.fileName(), + mode: 'text', + content: faker.lorem.lines() + }], + tags: faker.lorem.words().split(' '), + folder: folderKey + } + input1.title = input1.description.split('\n').shift() + + const input2 = { + type: 'MARKDOWN_NOTE', + content: faker.lorem.lines(), + tags: faker.lorem.words().split(' '), + folder: folderKey + } + input2.title = input2.content.split('\n').shift() + + const input3 = { + type: 'SNIPPET_NOTE', + description: faker.lorem.lines(), + snippets: [{ + name: faker.system.fileName(), + mode: 'text', + content: faker.lorem.lines() + }], + tags: faker.lorem.words().split(' ') + } + input3.title = input3.description.split('\n').shift() + + const input4 = { + type: 'MARKDOWN_NOTE', + content: faker.lorem.lines(), + tags: faker.lorem.words().split(' ') + } + input4.title = input4.content.split('\n').shift() + + return Promise.resolve() + .then(function doTest () { + return Promise + .all([ + createNote(storageKey, input1), + createNote(storageKey, input2) + ]) + .then(function updateNotes (data) { + let data1 = data[0] + let data2 = data[1] + return Promise.all([ + updateNote(data1.storage, data1.key, input3), + updateNote(data1.storage, data2.key, input4) + ]) + }) + }) + .then(function assert (data) { + let data1 = data[0] + let data2 = data[1] + + let jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.key + '.cson')) + t.is(input3.title, data1.title) + t.is(input3.title, jsonData1.title) + t.is(input3.description, data1.description) + t.is(input3.description, jsonData1.description) + t.is(input3.tags.length, data1.tags.length) + t.is(input3.tags.length, jsonData1.tags.length) + t.is(input3.snippets.length, data1.snippets.length) + t.is(input3.snippets.length, jsonData1.snippets.length) + t.is(input3.snippets[0].content, data1.snippets[0].content) + t.is(input3.snippets[0].content, jsonData1.snippets[0].content) + t.is(input3.snippets[0].name, data1.snippets[0].name) + t.is(input3.snippets[0].name, jsonData1.snippets[0].name) + + let jsonData2 = CSON.readFileSync(path.join(storagePath, 'notes', data2.key + '.cson')) + t.is(input4.title, data2.title) + t.is(input4.title, jsonData2.title) + t.is(input4.content, data2.content) + t.is(input4.content, jsonData2.content) + t.is(input4.tags.length, data2.tags.length) + t.is(input4.tags.length, jsonData2.tags.length) + }) +}) + +test.after(function after () { + localStorage.clear() + sander.rimrafSync(storagePath) +}) From 8d624459d46dd5aa674ecc446059178156aa404c Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sun, 28 Aug 2016 00:20:08 +0900 Subject: [PATCH 16/34] add assertions to addStorage test check version and folders attribute from resolved data and json --- browser/main/lib/dataApi/addStorage.js | 3 ++- tests/dataApi/addStorage.js | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/browser/main/lib/dataApi/addStorage.js b/browser/main/lib/dataApi/addStorage.js index 8d760b12..f7fbf6ef 100644 --- a/browser/main/lib/dataApi/addStorage.js +++ b/browser/main/lib/dataApi/addStorage.js @@ -40,7 +40,8 @@ function addStorage (input) { return Promise.resolve(newStorage) .then(resolveStorageData) - .then(function saveMetadataToLocalStorage () { + .then(function saveMetadataToLocalStorage (resolvedStorage) { + newStorage = resolvedStorage rawStorages.push({ key: newStorage.key, type: newStorage.type, diff --git a/tests/dataApi/addStorage.js b/tests/dataApi/addStorage.js index 001595b9..1ca10c54 100644 --- a/tests/dataApi/addStorage.js +++ b/tests/dataApi/addStorage.js @@ -12,6 +12,7 @@ const TestDummy = require('../fixtures/TestDummy') const sander = require('sander') const _ = require('lodash') const os = require('os') +const CSON = require('season') const v1StoragePath = path.join(os.tmpdir(), 'test/addStorage-v1-storage') // const legacyStoragePath = path.join(os.tmpdir(), 'test/addStorage-legacy-storage') @@ -42,6 +43,8 @@ test.serial('Add Storage', (t) => { t.is(storage.name, input.name) t.is(storage.type, input.type) t.is(storage.path, input.path) + t.is(storage.version, '1.0') + t.is(storage.folders.length, t.context.v1StorageData.json.folders.length) // Check data.notes t.is(notes.length, t.context.v1StorageData.notes.length) @@ -54,6 +57,12 @@ test.serial('Add Storage', (t) => { t.is(cacheData.name, input.name) t.is(cacheData.type, input.type) t.is(cacheData.path, input.path) + + // Check boostnote.json + let jsonData = CSON.readFileSync(path.join(storage.path, 'boostnote.json')) + t.true(_.isArray(jsonData.folders)) + t.is(jsonData.version, '1.0') + t.is(jsonData.folders.length, t.context.v1StorageData.json.folders.length) }) }) From 8921db89abf47c9fe658a37554e5db512e5bd565 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sun, 28 Aug 2016 01:33:05 +0900 Subject: [PATCH 17/34] key should be labeled ~Key --- browser/main/lib/dataApi/deleteFolder.js | 4 ++-- browser/main/lib/dataApi/removeStorage.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/browser/main/lib/dataApi/deleteFolder.js b/browser/main/lib/dataApi/deleteFolder.js index 0cc20dcc..f001ab59 100644 --- a/browser/main/lib/dataApi/deleteFolder.js +++ b/browser/main/lib/dataApi/deleteFolder.js @@ -13,7 +13,7 @@ const sander = require('sander') * ``` * { * storage: Object, - * folder: folderKey + * folderKey: String * } * ``` */ @@ -67,7 +67,7 @@ function deleteFolder (storageKey, folderKey) { return { storage, - folder: folderKey + folderKey } }) } diff --git a/browser/main/lib/dataApi/removeStorage.js b/browser/main/lib/dataApi/removeStorage.js index 3ada7227..c50bbd12 100644 --- a/browser/main/lib/dataApi/removeStorage.js +++ b/browser/main/lib/dataApi/removeStorage.js @@ -22,7 +22,9 @@ function removeStorage (key) { localStorage.setItem('storages', JSON.stringify(rawStorages)) - return Promise.resolve(key) + return Promise.resolve({ + storageKey: key + }) } module.exports = removeStorage From 73dd0db5294dcc37bd8423c1bc68d363fc71ef68 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sun, 28 Aug 2016 01:33:21 +0900 Subject: [PATCH 18/34] deleteNote --- browser/main/lib/dataApi/deleteNote.js | 34 ++++++++++++++ tests/dataApi/deleteNote.js | 62 ++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 browser/main/lib/dataApi/deleteNote.js create mode 100644 tests/dataApi/deleteNote.js diff --git a/browser/main/lib/dataApi/deleteNote.js b/browser/main/lib/dataApi/deleteNote.js new file mode 100644 index 00000000..119e1952 --- /dev/null +++ b/browser/main/lib/dataApi/deleteNote.js @@ -0,0 +1,34 @@ +const resolveStorageData = require('./resolveStorageData') +const _ = require('lodash') +const path = require('path') +const sander = require('sander') + +function deleteNote (storageKey, noteKey) { + let targetStorage + try { + let cachedStorageList = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.') + + targetStorage = _.find(cachedStorageList, {key: storageKey}) + if (targetStorage == null) throw new Error('Target storage doesn\'t exist.') + } catch (e) { + return Promise.reject(e) + } + + return resolveStorageData(targetStorage) + .then(function deleteNoteFile (storage) { + let notePath = path.join(storage.path, 'notes', noteKey + '.cson') + + try { + sander.unlinkSync(notePath) + } catch (err) { + console.warn('Failed to delete note cson', err) + } + return { + noteKey, + storageKey + } + }) +} + +module.exports = deleteNote diff --git a/tests/dataApi/deleteNote.js b/tests/dataApi/deleteNote.js new file mode 100644 index 00000000..6ced22df --- /dev/null +++ b/tests/dataApi/deleteNote.js @@ -0,0 +1,62 @@ +const test = require('ava') +const createNote = require('browser/main/lib/dataApi/createNote') +const deleteNote = require('browser/main/lib/dataApi/deleteNote') + +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 sander = require('sander') +const os = require('os') +const CSON = require('season') +const faker = require('faker') + +const storagePath = path.join(os.tmpdir(), 'test/delete-note') + +test.beforeEach((t) => { + t.context.storage = TestDummy.dummyStorage(storagePath) + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) +}) + +test.serial('Create a note', (t) => { + const storageKey = t.context.storage.cache.key + const folderKey = t.context.storage.json.folders[0].key + + const input1 = { + type: 'SNIPPET_NOTE', + description: faker.lorem.lines(), + snippets: [{ + name: faker.system.fileName(), + mode: 'text', + content: faker.lorem.lines() + }], + tags: faker.lorem.words().split(' '), + folder: folderKey + } + input1.title = input1.description.split('\n').shift() + + return Promise.resolve() + .then(function doTest () { + return createNote(storageKey, input1) + .then(function (data) { + return deleteNote(storageKey, data.key) + }) + }) + .then(function assert (data) { + try { + CSON.readFileSync(path.join(storagePath, 'notes', data.noteKey + '.cson')) + t.fail('note cson must be deleted.') + } catch (err) { + t.is(err.code, 'ENOENT') + } + }) +}) + +test.after(function after () { + localStorage.clear() + sander.rimrafSync(storagePath) +}) From 5163ab134edd7e86b2f92e737dfd46bc77075885 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sun, 28 Aug 2016 21:54:33 +0900 Subject: [PATCH 19/34] fix text --- browser/main/lib/dataApi/createNote.js | 1 + browser/main/lib/dataApi/updateFolder.js | 1 + tests/dataApi/deleteNote.js | 2 +- tests/dataApi/updateNote.js | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/browser/main/lib/dataApi/createNote.js b/browser/main/lib/dataApi/createNote.js index 5d984c49..b98f6832 100644 --- a/browser/main/lib/dataApi/createNote.js +++ b/browser/main/lib/dataApi/createNote.js @@ -59,6 +59,7 @@ function createNote (storageKey, input) { while (!isUnique) { try { sander.statSync(path.join(storage.path, 'notes', key + '.cson')) + key = keygen() } catch (err) { if (err.code === 'ENOENT') { isUnique = true diff --git a/browser/main/lib/dataApi/updateFolder.js b/browser/main/lib/dataApi/updateFolder.js index b6dd4485..44250b2e 100644 --- a/browser/main/lib/dataApi/updateFolder.js +++ b/browser/main/lib/dataApi/updateFolder.js @@ -41,6 +41,7 @@ function updateFolder (storageKey, folderKey, input) { return resolveStorageData(targetStorage) .then(function updateFolder (storage) { let targetFolder = _.find(storage.folders, {key: folderKey}) + if (targetFolder == null) throw new Error('Target folder doesn\'t exist.') targetFolder.name = input.name targetFolder.color = input.color diff --git a/tests/dataApi/deleteNote.js b/tests/dataApi/deleteNote.js index 6ced22df..b22e5de2 100644 --- a/tests/dataApi/deleteNote.js +++ b/tests/dataApi/deleteNote.js @@ -22,7 +22,7 @@ test.beforeEach((t) => { localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) }) -test.serial('Create a note', (t) => { +test.serial('Delete a note', (t) => { const storageKey = t.context.storage.cache.key const folderKey = t.context.storage.json.folders[0].key diff --git a/tests/dataApi/updateNote.js b/tests/dataApi/updateNote.js index 5f90fb10..63b74dbb 100644 --- a/tests/dataApi/updateNote.js +++ b/tests/dataApi/updateNote.js @@ -22,7 +22,7 @@ test.beforeEach((t) => { localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) }) -test.serial('Create a note', (t) => { +test.serial('Update a note', (t) => { const storageKey = t.context.storage.cache.key const folderKey = t.context.storage.json.folders[0].key From 5c312c193941446e8e52540d5d3906f2677e9a0a Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Sun, 28 Aug 2016 21:54:38 +0900 Subject: [PATCH 20/34] moveNote --- browser/main/lib/dataApi/moveNote.js | 92 ++++++++++++++++++++++++++++ tests/dataApi/moveNote.js | 66 ++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 browser/main/lib/dataApi/moveNote.js create mode 100644 tests/dataApi/moveNote.js diff --git a/browser/main/lib/dataApi/moveNote.js b/browser/main/lib/dataApi/moveNote.js new file mode 100644 index 00000000..2c938aad --- /dev/null +++ b/browser/main/lib/dataApi/moveNote.js @@ -0,0 +1,92 @@ +const resolveStorageData = require('./resolveStorageData') +const _ = require('lodash') +const path = require('path') +const CSON = require('season') +const keygen = require('browser/lib/keygen') +const sander = require('sander') + +function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) { + let oldStorage, newStorage + try { + let cachedStorageList = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(cachedStorageList)) throw new Error('Storage doesn\'t exist.') + + oldStorage = _.find(cachedStorageList, {key: storageKey}) + if (oldStorage == null) throw new Error('Storage doesn\'t exist.') + + newStorage = _.find(cachedStorageList, {key: newStorageKey}) + if (newStorage == null) throw new Error('Target storage doesn\'t exist.') + } catch (e) { + return Promise.reject(e) + } + + return resolveStorageData(oldStorage) + .then(function saveNote (_oldStorage) { + oldStorage = _oldStorage + let noteData + let notePath = path.join(oldStorage.path, 'notes', noteKey + '.cson') + try { + noteData = CSON.readFileSync(notePath) + } catch (err) { + console.warn('Failed to find note cson', err) + throw err + } + let newNoteKey + return Promise.resolve() + .then(function resolveNewStorage () { + if (storageKey === newStorageKey) { + newNoteKey = noteKey + return oldStorage + } + return resolveStorageData(newStorage) + .then(function findNewNoteKey (_newStorage) { + newStorage = _newStorage + newNoteKey = keygen() + let isUnique = false + while (!isUnique) { + try { + sander.statSync(path.join(newStorage.path, 'notes', newNoteKey + '.cson')) + newNoteKey = keygen() + } catch (err) { + if (err.code === 'ENOENT') { + isUnique = true + } else { + throw err + } + } + } + + return newStorage + }) + }) + .then(function checkFolderExistsAndPrepareNoteData (newStorage) { + if (_.find(newStorage.folders, {key: newFolderKey}) == null) throw new Error('Target folder doesn\'t exist.') + + noteData.folder = newFolderKey + noteData.key = newNoteKey + noteData.storage = newStorageKey + return noteData + }) + .then(function writeAndReturn (noteData) { + CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage'])) + return { + oldStorageKey: storageKey, + oldNoteKey: noteKey, + note: noteData + } + }) + .then(function deleteOldNote (data) { + if (storageKey !== newStorageKey) { + try { + sander.unlinkSync(path.join(oldStorage.path, 'notes', noteKey + '.cson')) + } catch (err) { + console.warn(err) + } + } + + return data + }) + }) +} + +module.exports = moveNote diff --git a/tests/dataApi/moveNote.js b/tests/dataApi/moveNote.js new file mode 100644 index 00000000..2f9fccd1 --- /dev/null +++ b/tests/dataApi/moveNote.js @@ -0,0 +1,66 @@ +const test = require('ava') +const moveNote = require('browser/main/lib/dataApi/moveNote') + +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 sander = require('sander') +const os = require('os') +const CSON = require('season') +const faker = require('faker') + +const storagePath = path.join(os.tmpdir(), 'test/move-note') +const storagePath2 = path.join(os.tmpdir(), 'test/move-note2') + +test.beforeEach((t) => { + t.context.storage1 = TestDummy.dummyStorage(storagePath) + t.context.storage2 = TestDummy.dummyStorage(storagePath2) + localStorage.setItem('storages', JSON.stringify([t.context.storage1.cache, t.context.storage2.cache])) +}) + +test.serial('Move a note', (t) => { + const storageKey1 = t.context.storage1.cache.key + const folderKey1 = t.context.storage1.json.folders[0].key + const note1 = t.context.storage1.notes[0] + const note2 = t.context.storage1.notes[1] + const storageKey2 = t.context.storage2.cache.key + const folderKey2 = t.context.storage2.json.folders[0].key + + return Promise.resolve() + .then(function doTest () { + return Promise.all([ + moveNote(storageKey1, note1.key, storageKey1, folderKey1), + moveNote(storageKey1, note2.key, storageKey2, folderKey2) + ]) + }) + .then(function assert (data) { + let data1 = data[0] + let data2 = data[1] + + let jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.note.key + '.cson')) + + t.is(jsonData1.folder, folderKey1) + t.is(jsonData1.title, note1.title) + + let jsonData2 = CSON.readFileSync(path.join(storagePath2, 'notes', data2.note.key + '.cson')) + t.is(jsonData2.folder, folderKey2) + t.is(jsonData2.title, note2.title) + try { + CSON.readFileSync(path.join(storagePath, 'notes', note2.key + '.cson')) + t.fail('The old note should be deleted.') + } catch (err) { + t.is(err.code, 'ENOENT') + } + }) +}) + +test.after(function after () { + localStorage.clear() + sander.rimrafSync(storagePath) + sander.rimrafSync(storagePath2) +}) From d7e8f26acee1fe3257aaf7c0fdb0cdb683b2bde8 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Mon, 29 Aug 2016 10:05:21 +0900 Subject: [PATCH 21/34] rename `transform` method `migrateFromV6Storage` --- .../dataApi/{transform.js => migrateFromV6Storage.js} | 4 ++-- browser/main/lib/dataApi/resolveStorageData.js | 4 ++-- .../dataApi/{transform.js => migrateFromV6Storage.js} | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) rename browser/main/lib/dataApi/{transform.js => migrateFromV6Storage.js} (97%) rename tests/dataApi/{transform.js => migrateFromV6Storage.js} (85%) diff --git a/browser/main/lib/dataApi/transform.js b/browser/main/lib/dataApi/migrateFromV6Storage.js similarity index 97% rename from browser/main/lib/dataApi/transform.js rename to browser/main/lib/dataApi/migrateFromV6Storage.js index 0a0f04cf..1af49cf5 100644 --- a/browser/main/lib/dataApi/transform.js +++ b/browser/main/lib/dataApi/migrateFromV6Storage.js @@ -4,7 +4,7 @@ const keygen = require('browser/lib/keygen') const _ = require('lodash') const CSON = require('season') -function transform (storagePath) { +function migrateFromV5Storage (storagePath) { var boostnoteJSONPath = path.join(storagePath, 'boostnote.json') return Promise.resolve() .then(function readBoostnoteJSON () { @@ -81,5 +81,5 @@ function transform (storagePath) { }) } -module.exports = transform +module.exports = migrateFromV5Storage diff --git a/browser/main/lib/dataApi/resolveStorageData.js b/browser/main/lib/dataApi/resolveStorageData.js index 3a34e862..1cf409a1 100644 --- a/browser/main/lib/dataApi/resolveStorageData.js +++ b/browser/main/lib/dataApi/resolveStorageData.js @@ -1,7 +1,7 @@ const _ = require('lodash') const path = require('path') const CSON = require('season') -const transform = require('./transform') +const migrateFromV6Storage = require('./migrateFromV6Storage') function resolveStorageData (storageCache) { let storage = { @@ -32,7 +32,7 @@ function resolveStorageData (storageCache) { return Promise.resolve(storage) } console.log('Transform Legacy storage', storage.path) - return transform(storage.path) + return migrateFromV6Storage(storage.path) .then(() => storage) } diff --git a/tests/dataApi/transform.js b/tests/dataApi/migrateFromV6Storage.js similarity index 85% rename from tests/dataApi/transform.js rename to tests/dataApi/migrateFromV6Storage.js index 2c181b42..f10e3ff6 100644 --- a/tests/dataApi/transform.js +++ b/tests/dataApi/migrateFromV6Storage.js @@ -1,5 +1,5 @@ const test = require('ava') -const transform = require('browser/main/lib/dataApi/transform') +const migrateFromV6Storage = require('browser/main/lib/dataApi/migrateFromV6Storage') global.document = require('jsdom').jsdom('') global.window = document.defaultView @@ -14,7 +14,7 @@ const CSON = require('season') const _ = require('lodash') const os = require('os') -const dummyStoragePath = path.join(os.tmpdir(), 'test/transform-test-storage') +const dummyStoragePath = path.join(os.tmpdir(), 'test/migrate-test-storage') test.beforeEach((t) => { let dummyData = t.context.dummyData = TestDummy.dummyLegacyStorage(dummyStoragePath) @@ -22,16 +22,16 @@ test.beforeEach((t) => { localStorage.setItem('storages', JSON.stringify([dummyData.cache])) }) -test.serial('Transform legacy storage into v1 storage', (t) => { +test.serial('Migrate legacy storage into v1 storage', (t) => { return Promise.resolve() .then(function test () { - return transform(dummyStoragePath) + return migrateFromV6Storage(dummyStoragePath) }) .then(function assert (data) { // Check the result. It must be true if succeed. t.true(data) - // Check all notes transformed. + // Check all notes migrated. let dummyData = t.context.dummyData let noteDirPath = path.join(dummyStoragePath, 'notes') let fileList = sander.readdirSync(noteDirPath) From 357c4a382d868f84bb46ca7af69586de73069b98 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Mon, 29 Aug 2016 10:05:56 +0900 Subject: [PATCH 22/34] migrateFromV5Storage --- .../main/lib/dataApi/migrateFromV5Storage.js | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 browser/main/lib/dataApi/migrateFromV5Storage.js diff --git a/browser/main/lib/dataApi/migrateFromV5Storage.js b/browser/main/lib/dataApi/migrateFromV5Storage.js new file mode 100644 index 00000000..d13a5e28 --- /dev/null +++ b/browser/main/lib/dataApi/migrateFromV5Storage.js @@ -0,0 +1,110 @@ +const _ = require('lodash') +const keygen = require('browser/lib/keygen') +const resolveStorageData = require('./resolveStorageData') +const consts = require('browser/lib/consts') +const CSON = require('season') +const path = require('path') +const sander = require('sander') + +function migrateFromV5Storage (storageKey, data) { + let targetStorage + try { + let cachedStorageList = JSON.parse(localStorage.getItem('storages')) + if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.') + + targetStorage = _.find(cachedStorageList, {key: storageKey}) + if (targetStorage == null) throw new Error('Target storage doesn\'t exist.') + } catch (e) { + return Promise.reject(e) + } + return resolveStorageData(targetStorage) + .then(function (storage) { + return importAll(storage, data) + }) +} + +function importAll (storage, data) { + let oldArticles = data.articles + let notes = [] + data.folders + .forEach(function (oldFolder) { + let folderKey = keygen() + while (storage.folders.some((folder) => folder.key === folderKey)) { + folderKey = keygen() + } + let newFolder = { + key: folderKey, + name: oldFolder.name, + color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7] + } + + storage.folders.push(newFolder) + + let articles = oldArticles.filter((article) => article.FolderKey === oldFolder.key) + articles.forEach((article) => { + let noteKey = keygen() + let isUnique = false + while (!isUnique) { + try { + sander.statSync(path.join(storage.path, 'notes', noteKey + '.cson')) + noteKey = keygen() + } catch (err) { + if (err.code === 'ENOENT') { + isUnique = true + } else { + console.error('Failed to read `notes` directory.') + throw err + } + } + } + + if (article.mode === 'markdown') { + let newNote = { + tags: article.tags, + createdAt: article.createdAt, + updatedAt: article.updatedAt, + folder: folderKey, + storage: storage.key, + type: 'MARKDOWN_NOTE', + isStarred: false, + title: article.title, + content: '# ' + article.title + '\n\n' + article.content, + key: noteKey + } + notes.push(newNote) + } else { + let newNote = { + tags: article.tags, + createdAt: article.createdAt, + updatedAt: article.updatedAt, + folder: folderKey, + storage: storage.key, + type: 'SNIPPET_NOTE', + isStarred: false, + title: article.title, + description: article.title, + key: noteKey, + snippets: [{ + name: article.mode, + mode: article.mode, + content: article.content + }] + } + notes.push(newNote) + } + }) + }) + + notes.forEach(function (note) { + CSON.writeFileSync(path.join(storage.path, 'notes', note.key + '.cson'), _.omit(note, ['storage', 'key'])) + }) + + CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['version', 'folders'])) + + return { + storage, + notes + } +} + +module.exports = migrateFromV5Storage From 33a11ac2e5584ab47df98bd86c6bf24d67a61fe3 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Mon, 29 Aug 2016 10:12:14 +0900 Subject: [PATCH 23/34] integrate all methods --- browser/main/lib/dataApi/index.js | 580 +----------------------------- 1 file changed, 17 insertions(+), 563 deletions(-) diff --git a/browser/main/lib/dataApi/index.js b/browser/main/lib/dataApi/index.js index 464d05d2..bad6f527 100644 --- a/browser/main/lib/dataApi/index.js +++ b/browser/main/lib/dataApi/index.js @@ -1,566 +1,20 @@ -const keygen = require('browser/lib/keygen') -const CSON = require('season') -const path = require('path') -const _ = require('lodash') -const sander = require('sander') -const consts = require('browser/lib/consts') +const dataApi = { + init: require('./init'), + addStorage: require('./addStorage'), + renameStorage: require('./renameStorage'), + removeStorage: require('./removeStorage'), + createFolder: require('./createFolder'), + updateFolder: require('./updateFolder'), + deleteFolder: require('./deleteFolder'), + createNote: require('./createNote'), + updateNote: require('./updateNote'), + deleteNote: require('./deleteNote'), + moveNote: require('./moveNote'), + migrateFromV5Storage: require('./migrateFromV5Storage'), -let storages = [] -let notes = [] - -let queuedTasks = [] - -function queueSaveFolder (storageKey, folderKey) { - let storage = _.find(storages, {key: storageKey}) - if (storage == null) throw new Error('Failed to queue: Storage doesn\'t exist.') - - let targetTasks = queuedTasks.filter((task) => task.storage === storageKey && task.folder === folderKey) - targetTasks.forEach((task) => { - clearTimeout(task.timer) - }) - queuedTasks = queuedTasks.filter((task) => task.storage !== storageKey || task.folder !== folderKey) - let newTimer = setTimeout(() => { - let folderNotes = notes.filter((note) => note.storage === storageKey && note.folder === folderKey) - sander - .writeFile(path.join(storage.cache.path, folderKey, 'data.json'), JSON.stringify({ - notes: folderNotes.map((note) => { - let json = note.toJSON() - delete json.storage - return json - }) - })) - }, 1500) - - queuedTasks.push({ - storage: storageKey, - folder: folderKey, - timer: newTimer - }) + _migrateFromV6Storage: require('./migrateFromV6Storage'), + _resolveStorageData: require('./resolveStorageData'), + _resolveStorageNotes: require('./resolveStorageNotes') } -class Storage { - constructor (cache) { - this.key = cache.key - this.cache = cache - } - - loadJSONData () { - return new Promise((resolve, reject) => { - try { - let data = CSON.readFileSync(path.join(this.cache.path, 'boostnote.json')) - this.data = data - resolve(this) - } catch (err) { - reject(err) - } - }) - } - - toJSON () { - return Object.assign({}, this.cache, this.data) - } - - initStorage () { - return this.loadJSONData() - .catch((err) => { - console.error(err.code) - if (err.code === 'ENOENT') { - let initialStorage = { - folders: [] - } - - return sander.writeFile(path.join(this.cache.path, 'boostnote.json'), JSON.stringify(initialStorage)) - } else throw err - }) - .then(() => this.loadJSONData()) - } - - saveData () { - return sander - .writeFile(path.join(this.cache.path, 'boostnote.json'), JSON.stringify(this.data)) - .then(() => this) - } - - saveCache () { - _saveCaches() - } - - static forge (cache) { - let instance = new this(cache) - return instance - } -} - -class Note { - constructor (note) { - this.storage = note.storage - this.folder = note.folder - this.key = note.key - this.uniqueKey = `${note.storage}-${note.folder}-${note.key}` - this.data = note - } - - toJSON () { - return Object.assign({}, this.data, { - uniqueKey: this.uniqueKey - }) - } - - save () { - let storage = _.find(storages, {key: this.storage}) - if (storage == null) return Promise.reject(new Error('Storage doesn\'t exist.')) - let folder = _.find(storage.data.folders, {key: this.folder}) - if (folder == null) return Promise.reject(new Error('Storage doesn\'t exist.')) - - // FS MUST BE MANIPULATED BY ASYNC METHOD - queueSaveFolder(storage.key, folder.key) - return Promise.resolve(this) - } - - static forge (note) { - let instance = new this(note) - - return Promise.resolve(instance) - } -} - -function init () { - let fetchStorages = function () { - let caches - try { - caches = JSON.parse(localStorage.getItem('storages')) - if (!_.isArray(caches)) throw new Error('Cached data is not valid.') - } catch (e) { - throw e - console.error(e) - caches = [] - localStorage.setItem('storages', JSON.stringify(caches)) - } - - return caches.map((cache) => { - return Storage - .forge(cache) - .loadJSONData() - .catch((err) => { - console.error(err) - console.error('Failed to load a storage JSON File: %s', cache) - return null - }) - }) - } - - let fetchNotes = function (storages) { - let notes = [] - let modifiedStorages = [] - storages - .forEach((storage) => { - storage.data.folders.forEach((folder) => { - let dataPath = path.join(storage.cache.path, folder.key, 'data.json') - let data - try { - data = CSON.readFileSync(dataPath) - } catch (e) { - // Remove folder if fetching failed. - console.error('Failed to load data: %s', dataPath) - storage.data.folders = storage.data.folders.filter((_folder) => _folder.key !== folder.key) - if (modifiedStorages.some((modified) => modified.key === storage.key)) modifiedStorages.push(storage) - return - } - data.notes.forEach((note) => { - note.storage = storage.key - note.folder = folder.key - notes.push(Note.forge(note)) - }) - }) - }, []) - return Promise - .all(modifiedStorages.map((storage) => storage.saveData())) - .then(() => Promise.all(notes)) - } - - return Promise.all(fetchStorages()) - .then((_storages) => { - storages = _storages.filter((storage) => { - if (!_.isObject(storage)) return false - return true - }) - _saveCaches() - - return storages - }) - .then(fetchNotes) - .then((_notes) => { - notes = _notes - return { - storages: storages.map((storage) => storage.toJSON()), - notes: notes.map((note) => note.toJSON()) - } - }) -} - -function _saveCaches () { - localStorage.setItem('storages', JSON.stringify(storages.map((storage) => storage.cache))) -} - -function addStorage (input) { - if (!_.isString(input.path)) { - return Promise.reject(new Error('Path must be a string.')) - } - - let key = keygen() - while (storages.some((storage) => storage.key === key)) { - key = keygen() - } - - return Storage - .forge({ - name: input.name, - key: key, - type: input.type, - path: input.path - }) - .initStorage() - .then((storage) => { - let _notes = [] - let isFolderRemoved = false - storage.data.folders.forEach((folder) => { - let dataPath = path.join(storage.cache.path, folder.key, 'data.json') - let data - try { - data = CSON.readFileSync(dataPath) - } catch (e) { - // Remove folder if fetching failed. - console.error('Failed to load data: %s', dataPath) - storage.data.folders = storage.data.folders.filter((_folder) => _folder.key !== folder.key) - isFolderRemoved = true - return true - } - data.notes.forEach((note) => { - note.storage = storage.key - note.folder = folder.key - _notes.push(Note.forge(note)) - }) - }) - - return Promise.all(_notes) - .then((_notes) => { - notes = notes.concat(_notes) - let data = { - storage: storage, - notes: _notes - } - return isFolderRemoved - ? storage.saveData().then(() => data) - : data - }) - }) - .then((data) => { - storages = storages.filter((storage) => storage.key !== data.storage.key) - storages.push(data.storage) - _saveCaches() - - if (data.storage.data.folders.length < 1) { - return createFolder(data.storage.key, { - name: 'Default', - color: consts.FOLDER_COLORS[0] - }).then(() => data) - } - - return data - }) - .then((data) => { - return { - storage: data.storage.toJSON(), - notes: data.notes.map((note) => note.toJSON()) - } - }) -} - -function removeStorage (key) { - storages = storages.filter((storage) => storage.key !== key) - _saveCaches() - notes = notes.filter((note) => note.storage !== key) - return Promise.resolve(true) -} - -function renameStorage (key, name) { - let storage = _.find(storages, {key: key}) - if (storage == null) throw new Error('Storage doesn\'t exist.') - storage.cache.name = name - storage.saveCache() - - return Promise.resolve(storage.toJSON()) -} - -function migrateFromV5 (key, data) { - let oldFolders = data.folders - let oldArticles = data.articles - let storage = _.find(storages, {key: key}) - if (storage == null) throw new Error('Storage doesn\'t exist.') - - let migrateFolders = oldFolders.map((oldFolder) => { - let folderKey = keygen() - while (storage.data.folders.some((folder) => folder.key === folderKey)) { - folderKey = keygen() - } - let newFolder = { - key: folderKey, - name: oldFolder.name, - color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7] - } - storage.data.folders.push(newFolder) - let articles = oldArticles.filter((article) => article.FolderKey === oldFolder.key) - let folderNotes = [] - articles.forEach((article) => { - let noteKey = keygen() - while (notes.some((note) => note.storage === key && note.folder === folderKey && note.key === noteKey)) { - key = keygen() - } - if (article.mode === 'markdown') { - let newNote = new Note({ - tags: article.tags, - createdAt: article.createdAt, - updatedAt: article.updatedAt, - folder: folderKey, - storage: key, - type: 'MARKDOWN_NOTE', - isStarred: false, - title: article.title, - content: '# ' + article.title + '\n\n' + article.content, - key: noteKey - }) - notes.push(newNote) - folderNotes.push(newNote) - } else { - let newNote = new Note({ - tags: article.tags, - createdAt: article.createdAt, - updatedAt: article.updatedAt, - folder: folderKey, - storage: key, - type: 'SNIPPET_NOTE', - isStarred: false, - title: article.title, - description: article.title, - key: noteKey, - snippets: [{ - name: article.mode, - mode: article.mode, - content: article.content - }] - }) - notes.push(newNote) - folderNotes.push(newNote) - } - }) - - return sander - .writeFile(path.join(storage.cache.path, folderKey, 'data.json'), JSON.stringify({ - notes: folderNotes.map((note) => { - let json = note.toJSON() - delete json.storage - return json - }) - })) - }) - return Promise.all(migrateFolders) - .then(() => storage.saveData()) - .then(() => { - return { - storage: storage.toJSON(), - notes: notes.filter((note) => note.storage === key) - .map((note) => note.toJSON()) - } - }) -} - -function createFolder (key, input) { - let storage = _.find(storages, {key: key}) - if (storage == null) throw new Error('Storage doesn\'t exist.') - - let folderKey = keygen() - while (storage.data.folders.some((folder) => folder.key === folderKey)) { - folderKey = keygen() - } - - let newFolder = { - key: folderKey, - name: input.name, - color: input.color - } - - const defaultData = {notes: []} - // FS MUST BE MANIPULATED BY ASYNC METHOD - return sander - .writeFile(path.join(storage.cache.path, folderKey, 'data.json'), JSON.stringify(defaultData)) - .then(() => { - storage.data.folders.push(newFolder) - return storage - .saveData() - .then((storage) => storage.toJSON()) - }) -} - -function updateFolder (storageKey, folderKey, input) { - let storage = _.find(storages, {key: storageKey}) - if (storage == null) throw new Error('Storage doesn\'t exist.') - let folder = _.find(storage.data.folders, {key: folderKey}) - folder.color = input.color - folder.name = input.name - - return storage - .saveData() - .then((storage) => storage.toJSON()) -} - -function removeFolder (storageKey, folderKey) { - let storage = _.find(storages, {key: storageKey}) - if (storage == null) throw new Error('Storage doesn\'t exist.') - storage.data.folders = storage.data.folders.filter((folder) => folder.key !== folderKey) - notes = notes.filter((note) => note.storage !== storageKey || note.folder !== folderKey) - - // FS MUST BE MANIPULATED BY ASYNC METHOD - return sander - .rimraf(path.join(storage.cache.path, folderKey)) - .catch((err) => { - if (err.code === 'ENOENT') return true - else throw err - }) - .then(() => storage.saveData()) - .then((storage) => storage.toJSON()) -} - -function createMarkdownNote (storageKey, folderKey, input) { - let key = keygen() - while (notes.some((note) => note.storage === storageKey && note.folder === folderKey && note.key === key)) { - key = keygen() - } - - let newNote = new Note(Object.assign({ - tags: [], - title: '', - content: '' - }, input, { - type: 'MARKDOWN_NOTE', - storage: storageKey, - folder: folderKey, - key: key, - isStarred: false, - createdAt: new Date(), - updatedAt: new Date() - })) - notes.push(newNote) - - return newNote - .save() - .then(() => newNote.toJSON()) -} - -function createSnippetNote (storageKey, folderKey, input) { - let key = keygen() - while (notes.some((note) => note.storage === storageKey && note.folder === folderKey && note.key === key)) { - key = keygen() - } - - let newNote = new Note(Object.assign({ - tags: [], - title: '', - description: '', - snippets: [{ - name: '', - mode: 'text', - content: '' - }] - }, input, { - type: 'SNIPPET_NOTE', - storage: storageKey, - folder: folderKey, - key: key, - isStarred: false, - createdAt: new Date(), - updatedAt: new Date() - })) - notes.push(newNote) - - return newNote - .save() - .then(() => newNote.toJSON()) -} - -function updateNote (storageKey, folderKey, noteKey, input) { - let note = _.find(notes, { - key: noteKey, - storage: storageKey, - folder: folderKey - }) - - switch (note.data.type) { - case 'MARKDOWN_NOTE': - note.data.title = input.title - note.data.tags = input.tags - note.data.content = input.content - note.data.updatedAt = input.updatedAt - break - case 'SNIPPET_NOTE': - note.data.title = input.title - note.data.tags = input.tags - note.data.description = input.description - note.data.snippets = input.snippets - note.data.updatedAt = input.updatedAt - } - - return note.save() - .then(() => note.toJSON()) -} - -function removeNote (storageKey, folderKey, noteKey) { - notes = notes.filter((note) => note.storage !== storageKey || note.folder !== folderKey || note.key !== noteKey) - queueSaveFolder(storageKey, folderKey) - - return Promise.resolve(null) -} - -function moveNote (storageKey, folderKey, noteKey, newStorageKey, newFolderKey) { - let note = _.find(notes, { - key: noteKey, - storage: storageKey, - folder: folderKey - }) - if (note == null) throw new Error('Note doesn\'t exist.') - - let storage = _.find(storages, {key: newStorageKey}) - if (storage == null) throw new Error('Storage doesn\'t exist.') - let folder = _.find(storage.data.folders, {key: newFolderKey}) - if (folder == null) throw new Error('Folder doesn\'t exist.') - note.storage = storage.key - note.data.storage = storage.key - note.folder = folder.key - note.data.folder = folder.key - let key = note.key - while (notes.some((note) => note.storage === storage.key && note.folder === folder.key && note.key === key)) { - key = keygen() - } - note.key = key - note.data.key = key - note.uniqueKey = `${note.storage}-${note.folder}-${note.key}` - console.log(note.uniqueKey) - queueSaveFolder(storageKey, folderKey) - return note.save() - .then(() => note.toJSON()) -} - -module.exports = { - init, - addStorage, - removeStorage, - renameStorage, - createFolder, - updateFolder, - removeFolder, - createMarkdownNote, - createSnippetNote, - updateNote, - removeNote, - moveNote, - migrateFromV5 -} +module.exports = dataApi From ba374e08ff080e2b6b3dcefb6c9a89beb053c033 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Tue, 30 Aug 2016 02:33:00 +0900 Subject: [PATCH 24/34] fix moveNote Api --- browser/main/lib/dataApi/moveNote.js | 8 +++----- tests/dataApi/moveNote.js | 4 ++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/browser/main/lib/dataApi/moveNote.js b/browser/main/lib/dataApi/moveNote.js index 2c938aad..1564d80c 100644 --- a/browser/main/lib/dataApi/moveNote.js +++ b/browser/main/lib/dataApi/moveNote.js @@ -65,15 +65,13 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) { noteData.folder = newFolderKey noteData.key = newNoteKey noteData.storage = newStorageKey + noteData.updatedAt = new Date() + return noteData }) .then(function writeAndReturn (noteData) { CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage'])) - return { - oldStorageKey: storageKey, - oldNoteKey: noteKey, - note: noteData - } + return noteData }) .then(function deleteOldNote (data) { if (storageKey !== newStorageKey) { diff --git a/tests/dataApi/moveNote.js b/tests/dataApi/moveNote.js index 2f9fccd1..595b46ed 100644 --- a/tests/dataApi/moveNote.js +++ b/tests/dataApi/moveNote.js @@ -42,12 +42,12 @@ test.serial('Move a note', (t) => { let data1 = data[0] let data2 = data[1] - let jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.note.key + '.cson')) + let jsonData1 = CSON.readFileSync(path.join(storagePath, 'notes', data1.key + '.cson')) t.is(jsonData1.folder, folderKey1) t.is(jsonData1.title, note1.title) - let jsonData2 = CSON.readFileSync(path.join(storagePath2, 'notes', data2.note.key + '.cson')) + let jsonData2 = CSON.readFileSync(path.join(storagePath2, 'notes', data2.key + '.cson')) t.is(jsonData2.folder, folderKey2) t.is(jsonData2.title, note2.title) try { From aefb84df3b030bb12e187b140c9bcea88dabefaa Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Tue, 30 Aug 2016 02:33:28 +0900 Subject: [PATCH 25/34] Mutable INIT_ALL, NOTE_MOVE, NOTE_UPDATE(create/update) done --- browser/finder/NoteItem.js | 2 +- browser/finder/NoteList.js | 2 +- browser/lib/Mutable.js | 87 ++++++ browser/main/Detail/FolderSelect.js | 4 +- browser/main/Detail/MarkdownNoteDetail.js | 41 ++- browser/main/Detail/SnippetNoteDetail.js | 41 ++- browser/main/Detail/index.js | 14 +- browser/main/Main.js | 13 +- browser/main/NoteList/index.js | 36 +-- browser/main/SideNav/index.js | 5 +- browser/main/TopBar/index.js | 13 +- browser/main/modals/NewNoteModal.js | 18 +- browser/main/store.js | 325 +++++++++++++++++----- 13 files changed, 449 insertions(+), 152 deletions(-) create mode 100644 browser/lib/Mutable.js diff --git a/browser/finder/NoteItem.js b/browser/finder/NoteItem.js index 9dd183ee..bc3ae253 100644 --- a/browser/finder/NoteItem.js +++ b/browser/finder/NoteItem.js @@ -34,7 +34,7 @@ class NoteItem extends React.Component { ? 'root--active' : 'root' } - key={note.uniqueKey} + key={note.storage + '-' + note.key} onClick={(e) => this.handleClick(e)} >
diff --git a/browser/finder/NoteList.js b/browser/finder/NoteList.js index 986efcf7..7087eb53 100644 --- a/browser/finder/NoteList.js +++ b/browser/finder/NoteList.js @@ -64,7 +64,7 @@ class NoteList extends React.Component { return ( { + data.storageMap.forEach((storage, index) => { storage.folders.forEach((folder) => { options.push({ storage: storage, diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 5c37074d..b156f0fa 100644 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -35,6 +35,7 @@ class MarkdownNoteDetail extends React.Component { componentWillReceiveProps (nextProps) { if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) { + if (this.saveQueue != null) this.saveNow() this.setState({ note: Object.assign({}, nextProps.note), isDeleting: false @@ -45,6 +46,10 @@ class MarkdownNoteDetail extends React.Component { } } + componentWillUnmount () { + if (this.saveQueue != null) this.saveNow() + } + findTitle (value) { let splitted = value.split('\n') let title = null @@ -91,17 +96,25 @@ class MarkdownNoteDetail extends React.Component { save () { clearTimeout(this.saveQueue) this.saveQueue = setTimeout(() => { - let { note, dispatch } = this.props - dispatch({ - type: 'UPDATE_NOTE', - note: this.state.note - }) - - dataApi - .updateNote(note.storage, note.folder, note.key, this.state.note) + this.saveNow() }, 1000) } + saveNow () { + let { note, dispatch } = this.props + clearTimeout(this.saveQueue) + this.saveQueue = null + + dataApi + .updateNote(note.storage, note.key, this.state.note) + .then((note) => { + dispatch({ + type: 'UPDATE_NOTE', + note: note + }) + }) + } + handleFolderChange (e) { let { note } = this.state let value = this.refs.folder.value @@ -110,7 +123,7 @@ class MarkdownNoteDetail extends React.Component { let newFolderKey = splitted.shift() dataApi - .moveNote(note.storage, note.folder, note.key, newStorageKey, newFolderKey) + .moveNote(note.storage, note.key, newStorageKey, newFolderKey) .then((newNote) => { this.setState({ isMovingNote: true, @@ -119,13 +132,13 @@ class MarkdownNoteDetail extends React.Component { let { dispatch, location } = this.props dispatch({ type: 'MOVE_NOTE', - note: note, - newNote: newNote + originNote: note, + note: newNote }) hashHistory.replace({ pathname: location.pathname, query: { - key: newNote.uniqueKey + key: newNote.storage + '-' + newNote.key } }) this.setState({ @@ -210,7 +223,7 @@ class MarkdownNoteDetail extends React.Component { } render () { - let { storages, config } = this.props + let { data, config } = this.props let { note } = this.state return ( @@ -243,7 +256,7 @@ class MarkdownNoteDetail extends React.Component { this.handleFolderChange(e)} />
diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 2bfc5714..6cb80bb7 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -48,6 +48,7 @@ class SnippetNoteDetail extends React.Component { componentWillReceiveProps (nextProps) { if (nextProps.note.key !== this.props.note.key) { + if (this.saveQueue != null) this.saveNow() let nextNote = Object.assign({ description: '' }, nextProps.note, { @@ -67,6 +68,10 @@ class SnippetNoteDetail extends React.Component { } } + componentWillUnmount () { + if (this.saveQueue != null) this.saveNow() + } + findTitle (value) { let splitted = value.split('\n') let title = null @@ -113,17 +118,25 @@ class SnippetNoteDetail extends React.Component { save () { clearTimeout(this.saveQueue) this.saveQueue = setTimeout(() => { - let { note, dispatch } = this.props - dispatch({ - type: 'UPDATE_NOTE', - note: this.state.note - }) - - dataApi - .updateNote(note.storage, note.folder, note.key, this.state.note) + this.saveNow() }, 1000) } + saveNow () { + let { note, dispatch } = this.props + clearTimeout(this.saveQueue) + this.saveQueue = null + + dataApi + .updateNote(note.storage, note.key, this.state.note) + .then((note) => { + dispatch({ + type: 'UPDATE_NOTE', + note: note + }) + }) + } + handleFolderChange (e) { let { note } = this.state let value = this.refs.folder.value @@ -132,7 +145,7 @@ class SnippetNoteDetail extends React.Component { let newFolderKey = splitted.shift() dataApi - .moveNote(note.storage, note.folder, note.key, newStorageKey, newFolderKey) + .moveNote(note.storage, note.key, newStorageKey, newFolderKey) .then((newNote) => { this.setState({ isMovingNote: true, @@ -141,13 +154,13 @@ class SnippetNoteDetail extends React.Component { let { dispatch, location } = this.props dispatch({ type: 'MOVE_NOTE', - note: note, - newNote: newNote + originNote: note, + note: newNote }) hashHistory.replace({ pathname: location.pathname, query: { - key: newNote.uniqueKey + key: newNote.storage + '-' + newNote.key } }) this.setState({ @@ -321,7 +334,7 @@ class SnippetNoteDetail extends React.Component { } render () { - let { storages, config } = this.props + let { data, config } = this.props let { note } = this.state let editorFontSize = parseInt(config.editor.fontSize, 10) @@ -434,7 +447,7 @@ class SnippetNoteDetail extends React.Component { this.handleFolderChange(e)} /> diff --git a/browser/main/Detail/index.js b/browser/main/Detail/index.js index d2c96726..89d1dd56 100644 --- a/browser/main/Detail/index.js +++ b/browser/main/Detail/index.js @@ -31,19 +31,14 @@ class Detail extends React.Component { } render () { - let { location, notes, config } = this.props + let { location, data, config } = this.props let note = null if (location.query.key != null) { let splitted = location.query.key.split('-') let storageKey = splitted.shift() - let folderKey = splitted.shift() let noteKey = splitted.shift() - note = _.find(notes, { - storage: storageKey, - folder: folderKey, - key: noteKey - }) + note = data.noteMap.get(storageKey + '-' + noteKey) } if (note == null) { @@ -67,7 +62,7 @@ class Detail extends React.Component { ref='root' {..._.pick(this.props, [ 'dispatch', - 'storages', + 'data', 'style', 'ignorePreviewPointerEvents', 'location' @@ -83,7 +78,7 @@ class Detail extends React.Component { ref='root' {..._.pick(this.props, [ 'dispatch', - 'storages', + 'data', 'style', 'ignorePreviewPointerEvents', 'location' @@ -95,7 +90,6 @@ class Detail extends React.Component { Detail.propTypes = { dispatch: PropTypes.func, - storages: PropTypes.array, style: PropTypes.shape({ left: PropTypes.number }), diff --git a/browser/main/Main.js b/browser/main/Main.js index 45295a4d..9c3138e1 100644 --- a/browser/main/Main.js +++ b/browser/main/Main.js @@ -101,7 +101,7 @@ class Main extends React.Component { x)(CSSModules(Main, styles)) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index c44a5bdd..f23eafb0 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -43,7 +43,7 @@ class NoteList extends React.Component { router.replace({ pathname: location.pathname, query: { - key: this.notes[0].uniqueKey + key: this.notes[0].storage + '-' + this.notes[0].key } }) return @@ -52,7 +52,7 @@ class NoteList extends React.Component { // Auto scroll if (_.isString(location.query.key)) { let targetIndex = _.findIndex(this.notes, (note) => { - return note.uniqueKey === location.query.key + return note != null && note.storage + '-' + note.key === location.query.key }) if (targetIndex > -1) { let list = this.refs.root @@ -153,30 +153,33 @@ class NoteList extends React.Component { } getNotes () { - let { storages, notes, params, location } = this.props + let { data, params, location } = this.props if (location.pathname.match(/\/home/)) { - return notes + return data.noteMap.map((note) => note) } if (location.pathname.match(/\/starred/)) { - return notes - .filter((note) => note.isStarred) + return data.starredSet.toJS() + .map((uniqueKey) => data.noteMap.get(uniqueKey)) } let storageKey = params.storageKey let folderKey = params.folderKey - let storage = _.find(storages, {key: storageKey}) + let storage = data.storageMap.get(storageKey) if (storage == null) return [] let folder = _.find(storage.folders, {key: folderKey}) if (folder == null) { - return notes - .filter((note) => note.storage === storageKey) + return data.storeageNoteMap + .get(storage.key) + .map((uniqueKey) => data.noteMap.get(uniqueKey)) } - return notes - .filter((note) => note.folder === folderKey) + let folderNoteKeyList = data.folderNoteMap + .get(storage.key + '-' + folder.key) + return folderNoteKeyList + .map((uniqueKey) => data.noteMap.get(uniqueKey)) } handleNoteClick (uniqueKey) { @@ -194,13 +197,14 @@ class NoteList extends React.Component { } render () { - let { location, storages, notes } = this.props + let { location, data, notes } = this.props this.notes = notes = this.getNotes() .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)) let noteList = notes .map((note) => { - let storage = _.find(storages, {key: note.storage}) + if (note == null) return null + let storage = data.storageMap.get(note.storage) let folder = _.find(storage.folders, {key: note.folder}) let tagElements = _.isArray(note.tags) ? note.tags.map((tag) => { @@ -212,14 +216,14 @@ class NoteList extends React.Component { ) }) : [] - let isActive = location.query.key === note.uniqueKey + let isActive = location.query.key === note.storage + '-' + note.key return (
this.handleNoteClick(note.uniqueKey)(e)} + key={note.storage + '-' + note.key} + onClick={(e) => this.handleNoteClick(note.storage + '-' + note.key)(e)} >
diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index 37bf994a..3e541413 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -36,12 +36,13 @@ class SideNav extends React.Component { } render () { - let { storages, location, config } = this.props + let { data, location, config } = this.props let isFolded = config.isSideNavFolded let isHomeActive = location.pathname.match(/^\/home$/) let isStarredActive = location.pathname.match(/^\/starred$/) - let storageList = storages.map((storage) => { + + let storageList = data.storageMap.map((storage, key) => { return { dispatch({ - type: 'CREATE_NOTE', + type: 'UPDATE_NOTE', note: note }) hashHistory.push({ pathname: location.pathname, - query: {key: note.uniqueKey} + query: {key: note.storage + '-' + note.key} }) ee.emit('detail:focus') this.props.close() }) } + handleMarkdownNoteButtonKeyDown (e) { if (e.keyCode === 9) { e.preventDefault() @@ -50,8 +53,11 @@ class NewNoteModal extends React.Component { handleSnippetNoteButtonClick (e) { let { storage, folder, dispatch, location } = this.props + dataApi - .createSnippetNote(storage, folder, { + .createNote(storage, { + type: 'SNIPPET_NOTE', + folder: folder, title: '', description: '', snippets: [{ @@ -62,12 +68,12 @@ class NewNoteModal extends React.Component { }) .then((note) => { dispatch({ - type: 'CREATE_NOTE', + type: 'UPDATE_NOTE', note: note }) hashHistory.push({ pathname: location.pathname, - query: {key: note.uniqueKey} + query: {key: note.storage + '-' + note.key} }) ee.emit('detail:focus') this.props.close() diff --git a/browser/main/store.js b/browser/main/store.js index b1c2e68b..b93a64d8 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -1,94 +1,270 @@ import { combineReducers, createStore } from 'redux' import { routerReducer } from 'react-router-redux' import ConfigManager from 'browser/main/lib/ConfigManager' +import { Map, Set } from 'browser/lib/Mutable' +import _ from 'lodash' -function storages (state = [], action) { - console.info('REDUX >> ', action) - switch (action.type) { - case 'INIT_ALL': - return action.storages - case 'ADD_STORAGE': - { - let storages = state.slice() - - storages.push(action.storage) - - return storages - } - case 'ADD_FOLDER': - case 'REMOVE_FOLDER': - case 'UPDATE_STORAGE': - case 'RENAME_STORAGE': - { - let storages = state.slice() - storages = storages - .filter((storage) => storage.key !== action.storage.key) - storages.push(action.storage) - - return storages - } - case 'REMOVE_STORAGE': - { - let storages = state.slice() - storages = storages - .filter((storage) => storage.key !== action.key) - - return storages - } +function defaultDataMap () { + return { + storageMap: new Map(), + noteMap: new Map(), + starredSet: new Set(), + storeageNoteMap: new Map(), + folderNoteMap: new Map(), + tagNoteMap: new Map() } - return state } -function notes (state = [], action) { +function data (state = defaultDataMap(), action) { switch (action.type) { case 'INIT_ALL': - return action.notes - case 'ADD_STORAGE': - { - let notes = state.concat(action.notes) - return notes - } - case 'REMOVE_STORAGE': - { - let notes = state.slice() - notes = notes - .filter((note) => note.storage !== action.key) + state = defaultDataMap() - return notes - } - case 'REMOVE_FOLDER': - { - let notes = state.slice() - notes = notes - .filter((note) => note.storage !== action.storage.key || note.folder !== action.key) + action.storages.forEach((storage) => { + state.storageMap.set(storage.key, storage) + }) - return notes - } - case 'CREATE_NOTE': - { - let notes = state.slice() - notes.push(action.note) - return notes - } + action.notes.forEach((note) => { + let uniqueKey = note.storage + '-' + note.key + let folderKey = note.storage + '-' + note.folder + state.noteMap.set(uniqueKey, note) + + if (note.isStarred) { + state.starredSet.add(uniqueKey) + } + + let storageNoteList = state.storeageNoteMap.get(note.storage) + if (storageNoteList == null) { + storageNoteList = new Set(storageNoteList) + state.storeageNoteMap.set(note.storage, storageNoteList) + } + storageNoteList.add(uniqueKey) + + let folderNoteList = state.folderNoteMap.get(folderKey) + if (folderNoteList == null) { + folderNoteList = new Set(folderNoteList) + state.folderNoteMap.set(folderKey, folderNoteList) + } + folderNoteList.add(uniqueKey) + + note.tags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList == null) { + tagNoteList = new Set(tagNoteList) + state.tagNoteMap.set(tag, tagNoteList) + } + tagNoteList.add(uniqueKey) + }) + }) + return state case 'UPDATE_NOTE': { - let notes = state.slice() - notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage) - notes.push(action.note) - return notes + let note = action.note + let uniqueKey = note.storage + '-' + note.key + let folderKey = note.storage + '-' + note.folder + let oldNote = state.noteMap.get(uniqueKey) + + state = Object.assign({}, state) + state.noteMap = new Map(state.noteMap) + state.noteMap.set(uniqueKey, note) + + if (oldNote == null || oldNote.isStarred !== note.isStarred) { + state.starredSet = new Set(state.starredSet) + if (note.isStarred) { + state.starredSet.add(uniqueKey) + } else { + state.starredSet.delete(uniqueKey) + } + } + + // Update storageNoteMap if oldNote doesn't exist + if (oldNote == null) { + state.storeageNoteMap = new Map(state.storeageNoteMap) + let noteSet = state.storeageNoteMap.get(note.storage) + noteSet = new Set(noteSet) + noteSet.add(uniqueKey) + state.folderNoteMap.set(folderKey, noteSet) + } + + // Update foldermap if folder changed or post created + if (oldNote == null || oldNote.folder !== note.folder) { + state.folderNoteMap = new Map(state.folderNoteMap) + let folderNoteList = state.folderNoteMap.get(folderKey) + folderNoteList = new Set(folderNoteList) + folderNoteList.add(uniqueKey) + state.folderNoteMap.set(folderKey, folderNoteList) + + if (oldNote != null) { + let oldFolderKey = oldNote.storage + '-' + oldNote.folder + let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey) + oldFolderNoteList = new Set(oldFolderNoteList) + oldFolderNoteList.delete(uniqueKey) + state.folderNoteMap.set(oldFolderKey, oldFolderNoteList) + } + } + + if (oldNote != null) { + let discardedTags = _.difference(oldNote.tags, note.tags) + let addedTags = _.difference(note.tags, oldNote.tags) + if (discardedTags.length + addedTags.length > 0) { + state.tagNoteMap = new Map(state.tagNoteMap) + + discardedTags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList != null) { + tagNoteList = new Set(tagNoteList) + tagNoteList.delete(uniqueKey) + state.tagNoteMap.set(tag, tagNoteList) + } + }) + addedTags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + tagNoteList = new Set(tagNoteList) + tagNoteList.add(uniqueKey) + + state.tagNoteMap.set(tag, tagNoteList) + }) + } + } else { + state.tagNoteMap = new Map(state.tagNoteMap) + note.tags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList == null) { + tagNoteList = new Set(tagNoteList) + state.tagNoteMap.set(tag, tagNoteList) + } + tagNoteList.add(uniqueKey) + }) + } + + return state } case 'MOVE_NOTE': { - let notes = state.slice() - notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage) - notes.push(action.newNote) - return notes + let originNote = action.originNote + let originKey = originNote.storage + '-' + originNote.key + let note = action.note + let uniqueKey = note.storage + '-' + note.key + let folderKey = note.storage + '-' + note.folder + let oldNote = state.noteMap.get(uniqueKey) + + state = Object.assign({}, state) + state.noteMap = new Map(state.noteMap) + state.noteMap.delete(originKey) + state.noteMap.set(uniqueKey, note) + + // If storage chanced, origin key must be discarded + if (originKey !== uniqueKey) { + console.log('diffrent storage') + // From isStarred + if (originNote.isStarred) { + state.starredSet = new Set(state.starredSet) + state.starredSet.delete(originKey) + } + + // From storageNoteMap + state.storeageNoteMap = new Map(state.storeageNoteMap) + let noteSet = state.storeageNoteMap.get(originNote.storage) + noteSet = new Set(noteSet) + noteSet.delete(originKey) + state.storeageNoteMap.set(originNote.storage, noteSet) + + // From folderNoteMap + state.folderNoteMap = new Map(state.folderNoteMap) + let originFolderKey = originNote.storage + '-' + originNote.folder + let originFolderList = state.folderNoteMap.get(originFolderKey) + originFolderList = new Set(originFolderList) + originFolderList.delete(originKey) + state.folderNoteMap.set(originFolderKey, originFolderList) + + // From tagMap + if (originNote.tags.length > 0) { + state.tagNoteMap = new Map(state.tagNoteMap) + originNote.tags.forEach((tag) => { + let noteSet = state.tagNoteMap.get(tag) + noteSet = new Set(noteSet) + noteSet.delete(originKey) + state.tagNoteMap.set(tag, noteSet) + }) + } + } + + if (oldNote == null || oldNote.isStarred !== note.isStarred) { + state.starredSet = new Set(state.starredSet) + if (note.isStarred) { + state.starredSet.add(uniqueKey) + } else { + state.starredSet.delete(uniqueKey) + } + } + + // Update storageNoteMap if oldNote doesn't exist + if (oldNote == null) { + state.storeageNoteMap = new Map(state.storeageNoteMap) + let noteSet = state.storeageNoteMap.get(note.storage) + noteSet = new Set(noteSet) + noteSet.add(uniqueKey) + state.folderNoteMap.set(folderKey, noteSet) + } + + // Update foldermap if folder changed or post created + if (oldNote == null || oldNote.folder !== note.folder) { + state.folderNoteMap = new Map(state.folderNoteMap) + let folderNoteList = state.folderNoteMap.get(folderKey) + folderNoteList = new Set(folderNoteList) + folderNoteList.add(uniqueKey) + state.folderNoteMap.set(folderKey, folderNoteList) + + if (oldNote != null) { + let oldFolderKey = oldNote.storage + '-' + oldNote.folder + let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey) + oldFolderNoteList = new Set(oldFolderNoteList) + oldFolderNoteList.delete(uniqueKey) + state.folderNoteMap.set(oldFolderKey, oldFolderNoteList) + } + } + + // Remove from old folder map + if (oldNote != null) { + let discardedTags = _.difference(oldNote.tags, note.tags) + let addedTags = _.difference(note.tags, oldNote.tags) + if (discardedTags.length + addedTags.length > 0) { + state.tagNoteMap = new Map(state.tagNoteMap) + + discardedTags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList != null) { + tagNoteList = new Set(tagNoteList) + tagNoteList.delete(uniqueKey) + state.tagNoteMap.set(tag, tagNoteList) + } + }) + addedTags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + tagNoteList = new Set(tagNoteList) + tagNoteList.add(uniqueKey) + + state.tagNoteMap.set(tag, tagNoteList) + }) + } + } else { + state.tagNoteMap = new Map(state.tagNoteMap) + note.tags.forEach((tag) => { + let tagNoteList = state.tagNoteMap.get(tag) + if (tagNoteList == null) { + tagNoteList = new Set(tagNoteList) + state.tagNoteMap.set(tag, tagNoteList) + } + tagNoteList.add(uniqueKey) + }) + } + + return state } - case 'REMOVE_NOTE': + case 'DELETE_NOTE': { - let notes = state.slice() - notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage) - return notes + + return state } } return state @@ -116,8 +292,7 @@ function config (state = defaultConfig, action) { } let reducer = combineReducers({ - storages, - notes, + data, config, routing: routerReducer }) From 52efc2398429eb33c46196794cd7ab5672d0dd31 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Thu, 1 Sep 2016 00:02:09 +0900 Subject: [PATCH 26/34] fix navigate methods of note list --- browser/main/NoteList/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index f23eafb0..78f1d17d 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -78,7 +78,7 @@ class NoteList extends React.Component { let { location } = this.props let targetIndex = _.findIndex(this.notes, (note) => { - return note.uniqueKey === location.query.key + return note.storage + '-' + note.key === location.query.key }) if (targetIndex === 0) { @@ -90,7 +90,7 @@ class NoteList extends React.Component { router.push({ pathname: location.pathname, query: { - key: this.notes[targetIndex].uniqueKey + key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key } }) } @@ -103,7 +103,7 @@ class NoteList extends React.Component { let { location } = this.props let targetIndex = _.findIndex(this.notes, (note) => { - return note.uniqueKey === location.query.key + return note.storage + '-' + note.key === location.query.key }) if (targetIndex === this.notes.length - 1) { @@ -117,7 +117,7 @@ class NoteList extends React.Component { router.push({ pathname: location.pathname, query: { - key: this.notes[targetIndex].uniqueKey + key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key } }) ee.emit('list:moved') From 0d573651a37baf780442cb58f86ce184237ce2a8 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Thu, 1 Sep 2016 00:02:16 +0900 Subject: [PATCH 27/34] DELETE_NOTE --- browser/main/Detail/MarkdownNoteDetail.js | 160 ++++++++------------ browser/main/Detail/MarkdownNoteDetail.styl | 28 ---- browser/main/Detail/SnippetNoteDetail.js | 156 ++++++++----------- browser/main/Detail/SnippetNoteDetail.styl | 28 ---- browser/main/store.js | 39 +++++ 5 files changed, 170 insertions(+), 241 deletions(-) diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index b156f0fa..93b3a40a 100644 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -11,19 +11,17 @@ import ee from 'browser/main/lib/eventEmitter' const electron = require('electron') const { remote } = electron -const Menu = remote.Menu -const MenuItem = remote.MenuItem +const { Menu, MenuItem, dialog } = remote class MarkdownNoteDetail extends React.Component { constructor (props) { super(props) this.state = { + isMovingNote: false, note: Object.assign({ title: '', - content: '', - isMovingNote: false, - isDeleting: false + content: '' }, props.note) } this.dispatchTimer = null @@ -37,8 +35,7 @@ class MarkdownNoteDetail extends React.Component { if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) { if (this.saveQueue != null) this.saveNow() this.setState({ - note: Object.assign({}, nextProps.note), - isDeleting: false + note: Object.assign({}, nextProps.note) }, () => { this.refs.content.reload() this.refs.tags.reset() @@ -188,34 +185,28 @@ class MarkdownNoteDetail extends React.Component { } handleDeleteMenuClick (e) { - this.setState({ - isDeleting: true - }, () => { - this.refs.deleteConfirmButton.focus() - }) - } - - handleDeleteConfirmButtonClick (e) { - let { note, dispatch } = this.props - dataApi - .removeNote(note.storage, note.folder, note.key) - .then(() => { - let dispatchHandler = () => { - dispatch({ - type: 'REMOVE_NOTE', - note: note - }) - } - ee.once('list:moved', dispatchHandler) - ee.emit('list:next') - ee.emit('list:focus') - }) - } - - handleDeleteCancelButtonClick (e) { - this.setState({ - isDeleting: false + let index = dialog.showMessageBox(remote.getCurrentWindow(), { + type: 'warning', + message: 'Delete a note', + detail: 'This work cannot be undone.', + buttons: ['Confirm', 'Cancel'] }) + if (index === 0) { + let { note, dispatch } = this.props + dataApi + .deleteNote(note.storage, note.key) + .then((data) => { + let dispatchHandler = () => { + dispatch({ + type: 'DELETE_NOTE', + storageKey: data.storageKey, + noteKey: data.noteKey + }) + } + ee.once('list:moved', dispatchHandler) + ee.emit('list:next') + }) + } } handleDeleteKeyDown (e) { @@ -231,69 +222,50 @@ class MarkdownNoteDetail extends React.Component { style={this.props.style} styleName='root' > - {this.state.isDeleting - ?
-
this.handleDeleteKeyDown(e)} - > - - - Are you sure to delete this note? - - - -
-
- :
-
-
- this.handleFolderChange(e)} - /> -
-
- this.handleChange(e)} - /> -
-
-
- this.handleStarButtonClick(e)} - isActive={note.isStarred} +
+
+
+ this.handleFolderChange(e)} + /> +
+
+ this.handleChange(e)} /> - -
- } +
+ this.handleStarButtonClick(e)} + isActive={note.isStarred} + /> + + +
+
+
Object.assign({}, snippet)) - }), - isDeleting: false + }) } } @@ -56,8 +55,7 @@ class SnippetNoteDetail extends React.Component { }) this.setState({ snippetIndex: 0, - note: nextNote, - isDeleting: false + note: nextNote }, () => { let { snippets } = this.state.note snippets.forEach((snippet, index) => { @@ -211,33 +209,28 @@ class SnippetNoteDetail extends React.Component { } handleDeleteMenuClick (e) { - this.setState({ - isDeleting: true - }, () => { - this.refs.deleteConfirmButton.focus() - }) - } - - handleDeleteConfirmButtonClick (e) { - let { note, dispatch } = this.props - dataApi - .removeNote(note.storage, note.folder, note.key) - .then(() => { - let dispatchHandler = () => { - dispatch({ - type: 'REMOVE_NOTE', - note: note - }) - } - ee.once('list:moved', dispatchHandler) - ee.emit('list:next') - }) - } - - handleDeleteCancelButtonClick (e) { - this.setState({ - isDeleting: false + let index = dialog.showMessageBox(remote.getCurrentWindow(), { + type: 'warning', + message: 'Delete a note', + detail: 'This work cannot be undone.', + buttons: ['Confirm', 'Cancel'] }) + if (index === 0) { + let { note, dispatch } = this.props + dataApi + .deleteNote(note.storage, note.key) + .then((data) => { + let dispatchHandler = () => { + dispatch({ + type: 'DELETE_NOTE', + storageKey: data.storageKey, + noteKey: data.noteKey + }) + } + ee.once('list:moved', dispatchHandler) + ee.emit('list:next') + }) + } } handleTabPlusButtonClick (e) { @@ -423,68 +416,49 @@ class SnippetNoteDetail extends React.Component { style={this.props.style} styleName='root' > - {this.state.isDeleting - ?
-
this.handleDeleteKeyDown(e)} - > - - Are you sure to delete this note? - - - -
-
- :
-
-
- this.handleFolderChange(e)} - /> -
-
- this.handleChange(e)} - /> -
-
-
- this.handleStarButtonClick(e)} - isActive={note.isStarred} +
+
+
+ this.handleFolderChange(e)} + /> +
+
+ this.handleChange(e)} /> - -
- } +
+ this.handleStarButtonClick(e)} + isActive={note.isStarred} + /> + + +
+
diff --git a/browser/main/Detail/SnippetNoteDetail.styl b/browser/main/Detail/SnippetNoteDetail.styl index 5a875180..cd1351db 100644 --- a/browser/main/Detail/SnippetNoteDetail.styl +++ b/browser/main/Detail/SnippetNoteDetail.styl @@ -12,34 +12,6 @@ $info-height = 75px border-bottom $ui-border background-color $ui-backgroundColor -.info-delete - height 80px - display flex - -.info-delete-message - height 80px - line-height 80px - padding 0 25px - overflow ellipsis - flex 1 - -.info-delete-confirmButton - margin 25px 5px 0 - width 80px - height 30px - border-radius 2px - border none - colorDangerButton() - -.info-delete-cancelButton - width 80px - height 30px - margin 25px 5px 0 - border $ui-border - border-radius 2px - color $ui-text-color - colorDefaultButton() - .info-left float left padding 0 5px diff --git a/browser/main/store.js b/browser/main/store.js index b93a64d8..8534c6be 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -263,7 +263,46 @@ function data (state = defaultDataMap(), action) { } case 'DELETE_NOTE': { + let uniqueKey = action.storageKey + '-' + action.noteKey + let targetNote = state.noteMap.get(uniqueKey) + state = Object.assign({}, state) + + // From storageNoteMap + state.storeageNoteMap = new Map(state.storeageNoteMap) + let noteSet = state.storeageNoteMap.get(targetNote.storage) + noteSet = new Set(noteSet) + noteSet.delete(uniqueKey) + state.storeageNoteMap.set(targetNote.storage, noteSet) + + if (targetNote != null) { + // From isStarred + if (targetNote.isStarred) { + state.starredSet = new Set(state.starredSet) + state.starredSet.delete(uniqueKey) + } + + // From folderNoteMap + let folderKey = targetNote.storage + '-' + targetNote.folder + state.folderNoteMap = new Map(state.folderNoteMap) + let folderSet = state.folderNoteMap.get(folderKey) + folderSet = new Set(folderSet) + folderSet.delete(uniqueKey) + state.folderNoteMap.set(folderKey, folderSet) + + // From tagMap + if (targetNote.tags.length > 0) { + state.tagNoteMap = new Map(state.tagNoteMap) + targetNote.tags.forEach((tag) => { + let noteSet = state.tagNoteMap.get(tag) + noteSet = new Set(noteSet) + noteSet.delete(uniqueKey) + state.tagNoteMap.set(tag, noteSet) + }) + } + } + state.noteMap = new Map(state.noteMap) + state.noteMap.delete(uniqueKey) return state } } From 4ee49d5991a629f5de8ec2d36215af03cc51ce3c Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Thu, 1 Sep 2016 00:49:25 +0900 Subject: [PATCH 28/34] lint colorpicker code --- .../modals/PreferencesModal/StorageItem.js | 23 +++++++++++-------- browser/main/modals/PreferencesModal/index.js | 8 +++---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/browser/main/modals/PreferencesModal/StorageItem.js b/browser/main/modals/PreferencesModal/StorageItem.js index 68cfb1fe..f84e2c79 100644 --- a/browser/main/modals/PreferencesModal/StorageItem.js +++ b/browser/main/modals/PreferencesModal/StorageItem.js @@ -55,10 +55,10 @@ class UnstyledFolderItem extends React.Component { handleColorButtonClick (e) { const folder = Object.assign({}, this.state.folder, { showColumnPicker: true, colorPickerPos: { left: 0, top: 0 } }) - this.setState({ folder }, function() { + this.setState({ folder }, function () { // After the color picker has been painted, re-calculate its position // by comparing its dimensions to the host dimensions. - const { hostBoundingBox } = this.props; + const { hostBoundingBox } = this.props const colorPickerNode = ReactDOM.findDOMNode(this.refs.colorPicker) const colorPickerBox = colorPickerNode.getBoundingClientRect() const offsetTop = hostBoundingBox.bottom - colorPickerBox.bottom @@ -103,19 +103,22 @@ class UnstyledFolderItem extends React.Component { ) @@ -66,7 +66,7 @@ class Preferences extends React.Component { getContentBoundingBox () { const node = ReactDOM.findDOMNode(this.refs.content) - return node.getBoundingClientRect(); + return node.getBoundingClientRect() } render () { From a391ac682d78735f95e3b6b029755e0757f64eeb Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Fri, 2 Sep 2016 09:33:01 +0900 Subject: [PATCH 29/34] UPDATE_FOLDER & DELETE_FOLDER fix store bug when creating note --- browser/lib/Mutable.js | 2 +- browser/main/NoteList/index.js | 7 +- .../modals/PreferencesModal/StorageItem.js | 25 +++--- .../modals/PreferencesModal/StoragesTab.js | 4 +- browser/main/store.js | 81 ++++++++++++++++--- 5 files changed, 88 insertions(+), 31 deletions(-) diff --git a/browser/lib/Mutable.js b/browser/lib/Mutable.js index 5e5a0b23..7a307139 100644 --- a/browser/lib/Mutable.js +++ b/browser/lib/Mutable.js @@ -58,7 +58,7 @@ class MutableSet { } forEach (...args) { - return this._map.forEach(...args) + return this._set.forEach(...args) } [Symbol.iterator] () { diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index 78f1d17d..3e87f4d3 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -178,8 +178,11 @@ class NoteList extends React.Component { let folderNoteKeyList = data.folderNoteMap .get(storage.key + '-' + folder.key) - return folderNoteKeyList - .map((uniqueKey) => data.noteMap.get(uniqueKey)) + + return folderNoteKeyList != null + ? folderNoteKeyList + .map((uniqueKey) => data.noteMap.get(uniqueKey)) + : [] } handleNoteClick (uniqueKey) { diff --git a/browser/main/modals/PreferencesModal/StorageItem.js b/browser/main/modals/PreferencesModal/StorageItem.js index f84e2c79..b67e9235 100644 --- a/browser/main/modals/PreferencesModal/StorageItem.js +++ b/browser/main/modals/PreferencesModal/StorageItem.js @@ -7,8 +7,7 @@ import dataApi from 'browser/main/lib/dataApi' import store from 'browser/main/store' const electron = require('electron') -const { shell, remote } = electron -const { Menu, MenuItem } = remote +const { shell } = electron import { SketchPicker } from 'react-color' class UnstyledFolderItem extends React.Component { @@ -42,10 +41,10 @@ class UnstyledFolderItem extends React.Component { color: this.state.folder.color, name: this.state.folder.name }) - .then((storage) => { + .then((data) => { store.dispatch({ - type: 'UPDATE_STORAGE', - storage: storage + type: 'UPDATE_FOLDER', + storage: data.storage }) this.setState({ status: 'IDLE' @@ -146,12 +145,12 @@ class UnstyledFolderItem extends React.Component { handleDeleteConfirmButtonClick (e) { let { storage, folder } = this.props dataApi - .removeFolder(storage.key, folder.key) - .then((storage) => { + .deleteFolder(storage.key, folder.key) + .then((data) => { store.dispatch({ - type: 'REMOVE_FOLDER', - key: folder.key, - storage: storage + type: 'DELETE_FOLDER', + storage: data.storage, + folderKey: data.folderKey }) }) } @@ -257,10 +256,10 @@ class StorageItem extends React.Component { } dataApi.createFolder(storage.key, input) - .then((storage) => { + .then((data) => { store.dispatch({ - type: 'ADD_FOLDER', - storage: storage + type: 'UPDATE_FOLDER', + storage: data.storage }) }) .catch((err) => { diff --git a/browser/main/modals/PreferencesModal/StoragesTab.js b/browser/main/modals/PreferencesModal/StoragesTab.js index 1f45c476..f0609e6e 100644 --- a/browser/main/modals/PreferencesModal/StoragesTab.js +++ b/browser/main/modals/PreferencesModal/StoragesTab.js @@ -51,10 +51,10 @@ class StoragesTab extends React.Component { } renderList () { - let { storages, boundingBox } = this.props + let { data, boundingBox } = this.props if (!boundingBox) { return null } - let storageList = storages.map((storage) => { + let storageList = data.storageMap.map((storage) => { return { let tagNoteList = state.tagNoteMap.get(tag) @@ -80,19 +80,19 @@ function data (state = defaultDataMap(), action) { // Update storageNoteMap if oldNote doesn't exist if (oldNote == null) { state.storeageNoteMap = new Map(state.storeageNoteMap) - let noteSet = state.storeageNoteMap.get(note.storage) - noteSet = new Set(noteSet) - noteSet.add(uniqueKey) - state.folderNoteMap.set(folderKey, noteSet) + let storageNoteSet = state.storeageNoteMap.get(note.storage) + storageNoteSet = new Set(storageNoteSet) + storageNoteSet.add(uniqueKey) + state.storeageNoteMap.set(note.storage, storageNoteSet) } // Update foldermap if folder changed or post created if (oldNote == null || oldNote.folder !== note.folder) { state.folderNoteMap = new Map(state.folderNoteMap) - let folderNoteList = state.folderNoteMap.get(folderKey) - folderNoteList = new Set(folderNoteList) - folderNoteList.add(uniqueKey) - state.folderNoteMap.set(folderKey, folderNoteList) + let folderNoteSet = state.folderNoteMap.get(folderKey) + folderNoteSet = new Set(folderNoteSet) + folderNoteSet.add(uniqueKey) + state.folderNoteMap.set(folderKey, folderNoteSet) if (oldNote != null) { let oldFolderKey = oldNote.storage + '-' + oldNote.folder @@ -305,6 +305,61 @@ function data (state = defaultDataMap(), action) { state.noteMap.delete(uniqueKey) return state } + case 'UPDATE_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) + state.storageMap = new Map(state.storageMap) + state.storageMap.set(action.storage.key, action.storage) + + // Get note list from folder-note map + // and delete note set from folder-note map + let folderKey = action.storage.key + '-' + action.folderKey + let noteSet = state.folderNoteMap.get(folderKey) + state.folderNoteMap = new Map(state.folderNoteMap) + state.folderNoteMap.delete(folderKey) + + state.noteMap = new Map(state.noteMap) + state.storageNoteMap = new Map(state.storageNoteMap) + let storageNoteSet = state.storageNoteMap.get(action.storage.key) + storageNoteSet = new Set(storageNoteSet) + storageNoteSet.delete() + noteSet.forEach(function handleNoteKey (noteKey) { + // Get note from noteMap + let note = state.noteMap.get(noteKey) + if (note != null) { + state.noteMap.delete(noteKey) + + // From storageSet + let storageNoteSet = state.storageNoteMap.get(note.storage) + storageNoteSet = new Set(storageNoteSet) + state.storageNoteMap.set(note.storage, storageNoteSet) + storageNoteSet.delete(noteKey) + + // From starredSet + if (note.isStarred) { + state.starredSet = new Set(state.starredSet) + state.starredSet.delete(noteKey) + } + + // Delete key from tag map + state.tagNoteMap = new Map(state.tagNoteMap) + note.tags.forEach((tag) => { + let tagNoteSet = state.tagNoteMap.get(tag) + tagNoteSet = new Set(tagNoteSet) + state.tagNoteMap.set(tag, tagNoteSet) + tagNoteSet.delete(noteKey) + }) + } + }) + } + return state } return state } From fba972c98e4408824182981c7d5ec1243635f3fa Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Fri, 2 Sep 2016 09:41:01 +0900 Subject: [PATCH 30/34] remove deuplicate code --- browser/main/store.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/browser/main/store.js b/browser/main/store.js index b539d56c..46da68f2 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -329,7 +329,7 @@ function data (state = defaultDataMap(), action) { state.storageNoteMap = new Map(state.storageNoteMap) let storageNoteSet = state.storageNoteMap.get(action.storage.key) storageNoteSet = new Set(storageNoteSet) - storageNoteSet.delete() + state.storageNoteMap.set(action.storage.key, storageNoteSet) noteSet.forEach(function handleNoteKey (noteKey) { // Get note from noteMap let note = state.noteMap.get(noteKey) @@ -337,9 +337,6 @@ function data (state = defaultDataMap(), action) { state.noteMap.delete(noteKey) // From storageSet - let storageNoteSet = state.storageNoteMap.get(note.storage) - storageNoteSet = new Set(storageNoteSet) - state.storageNoteMap.set(note.storage, storageNoteSet) storageNoteSet.delete(noteKey) // From starredSet From 34ae3cd7045cf4ca43e6d87333f3768a2d80fb5d Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Thu, 8 Sep 2016 16:32:17 +0900 Subject: [PATCH 31/34] use cheap-module-eval-source-map on developing --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 7a1768f1..dfde7e85 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -24,7 +24,7 @@ var config = Object.assign({}, skeleton, { publicPath: 'http://localhost:8080/assets/' }, debug: true, - devtool: 'eval-source-map' + devtool: 'cheap-module-eval-source-map' }) module.exports = config From 7132e9ff24efaefe33b1084c90d3b9966d1bf1a7 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Thu, 8 Sep 2016 16:33:16 +0900 Subject: [PATCH 32/34] REMOVE_STORAGE redux action fix typo storageNoteMap --- browser/main/NoteList/index.js | 2 +- .../modals/PreferencesModal/StorageItem.js | 4 +- browser/main/store.js | 67 +++++++++++++++---- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index 3e87f4d3..df33ed98 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -171,7 +171,7 @@ class NoteList extends React.Component { let folder = _.find(storage.folders, {key: folderKey}) if (folder == null) { - return data.storeageNoteMap + return data.storageNoteMap .get(storage.key) .map((uniqueKey) => data.noteMap.get(uniqueKey)) } diff --git a/browser/main/modals/PreferencesModal/StorageItem.js b/browser/main/modals/PreferencesModal/StorageItem.js index b67e9235..314e5cba 100644 --- a/browser/main/modals/PreferencesModal/StorageItem.js +++ b/browser/main/modals/PreferencesModal/StorageItem.js @@ -278,11 +278,11 @@ class StorageItem extends React.Component { .then(() => { store.dispatch({ type: 'REMOVE_STORAGE', - key: storage.key + storageKey: storage.key }) }) .catch((err) => { - console.error(err) + throw err }) } diff --git a/browser/main/store.js b/browser/main/store.js index 46da68f2..dd4518c3 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -9,7 +9,7 @@ function defaultDataMap () { storageMap: new Map(), noteMap: new Map(), starredSet: new Set(), - storeageNoteMap: new Map(), + storageNoteMap: new Map(), folderNoteMap: new Map(), tagNoteMap: new Map() } @@ -33,10 +33,10 @@ function data (state = defaultDataMap(), action) { state.starredSet.add(uniqueKey) } - let storageNoteList = state.storeageNoteMap.get(note.storage) + let storageNoteList = state.storageNoteMap.get(note.storage) if (storageNoteList == null) { storageNoteList = new Set(storageNoteList) - state.storeageNoteMap.set(note.storage, storageNoteList) + state.storageNoteMap.set(note.storage, storageNoteList) } storageNoteList.add(uniqueKey) @@ -79,11 +79,11 @@ function data (state = defaultDataMap(), action) { // Update storageNoteMap if oldNote doesn't exist if (oldNote == null) { - state.storeageNoteMap = new Map(state.storeageNoteMap) - let storageNoteSet = state.storeageNoteMap.get(note.storage) + state.storageNoteMap = new Map(state.storageNoteMap) + let storageNoteSet = state.storageNoteMap.get(note.storage) storageNoteSet = new Set(storageNoteSet) storageNoteSet.add(uniqueKey) - state.storeageNoteMap.set(note.storage, storageNoteSet) + state.storageNoteMap.set(note.storage, storageNoteSet) } // Update foldermap if folder changed or post created @@ -163,11 +163,11 @@ function data (state = defaultDataMap(), action) { } // From storageNoteMap - state.storeageNoteMap = new Map(state.storeageNoteMap) - let noteSet = state.storeageNoteMap.get(originNote.storage) + state.storageNoteMap = new Map(state.storageNoteMap) + let noteSet = state.storageNoteMap.get(originNote.storage) noteSet = new Set(noteSet) noteSet.delete(originKey) - state.storeageNoteMap.set(originNote.storage, noteSet) + state.storageNoteMap.set(originNote.storage, noteSet) // From folderNoteMap state.folderNoteMap = new Map(state.folderNoteMap) @@ -200,8 +200,8 @@ function data (state = defaultDataMap(), action) { // Update storageNoteMap if oldNote doesn't exist if (oldNote == null) { - state.storeageNoteMap = new Map(state.storeageNoteMap) - let noteSet = state.storeageNoteMap.get(note.storage) + state.storageNoteMap = new Map(state.storageNoteMap) + let noteSet = state.storageNoteMap.get(note.storage) noteSet = new Set(noteSet) noteSet.add(uniqueKey) state.folderNoteMap.set(folderKey, noteSet) @@ -269,11 +269,11 @@ function data (state = defaultDataMap(), action) { state = Object.assign({}, state) // From storageNoteMap - state.storeageNoteMap = new Map(state.storeageNoteMap) - let noteSet = state.storeageNoteMap.get(targetNote.storage) + state.storageNoteMap = new Map(state.storageNoteMap) + let noteSet = state.storageNoteMap.get(targetNote.storage) noteSet = new Set(noteSet) noteSet.delete(uniqueKey) - state.storeageNoteMap.set(targetNote.storage, noteSet) + state.storageNoteMap.set(targetNote.storage, noteSet) if (targetNote != null) { // From isStarred @@ -357,6 +357,45 @@ function data (state = defaultDataMap(), action) { }) } return state + case 'REMOVE_STORAGE': + state = Object.assign({}, state) + let storage = state.storageMap.get(action.storageKey) + state.storageMap = new Map(state.storageMap) + state.storageMap.delete(action.storageKey) + + // Remove folders from folderMap + if (storage != null) { + state.folderMap = new Map(state.folderMap) + storage.folders.forEach((folder) => { + let folderKey = storage.key + '-' + folder.key + state.folderMap.delete(folderKey) + }) + } + + // Remove notes from noteMap and tagNoteMap + let storageNoteSet = state.storageNoteMap.get(action.storageKey) + state.storageNoteMap = new Map(state.storageNoteMap) + state.storageNoteMap.delete(action.storageKey) + if (storageNoteSet != null) { + let notes = storageNoteSet + .map((noteKey) => state.noteMap.get(noteKey)) + .filter((note) => note != null) + + state.noteMap = new Map(state.noteMap) + state.tagNoteMap = new Map(state.tagNoteMap) + state.starredSet = new Set(state.starredSet) + notes.forEach((note) => { + let noteKey = storage.key + '-' + note.key + state.noteMap.delete(noteKey) + state.starredSet.delete(noteKey) + note.tags.forEach((tag) => { + let tagNoteSet = state.tagNoteMap.get(tag) + tagNoteSet = new Set(tagNoteSet) + tagNoteSet.delete(noteKey) + }) + }) + } + return state } return state } From f07f309393969a6c40a01bbeab21e5f2cd483a36 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Thu, 8 Sep 2016 16:46:18 +0900 Subject: [PATCH 33/34] ADD_STORAGE redux action --- browser/main/store.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/browser/main/store.js b/browser/main/store.js index dd4518c3..469eb4af 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -357,6 +357,49 @@ function data (state = defaultDataMap(), action) { }) } return state + case 'ADD_STORAGE': + state = Object.assign({}, state) + state.storageMap = new Map(state.storageMap) + state.storageMap.set(action.storage.key, action.storage) + + state.noteMap = new Map(state.noteMap) + state.storageNoteMap = new Map(state.storageNoteMap) + state.storageNoteMap.set(action.storage.key, new Set()) + state.folderNoteMap = new Map(state.folderNoteMap) + state.tagNoteMap = new Map(state.tagNoteMap) + action.notes.forEach((note) => { + let uniqueKey = note.storage + '-' + note.key + let folderKey = note.storage + '-' + note.folder + state.noteMap.set(uniqueKey, note) + + if (note.isStarred) { + state.starredSet.add(uniqueKey) + } + + let storageNoteList = state.storageNoteMap.get(note.storage) + if (storageNoteList == null) { + storageNoteList = new Set(storageNoteList) + state.storageNoteMap.set(note.storage, storageNoteList) + } + storageNoteList.add(uniqueKey) + + let folderNoteSet = state.folderNoteMap.get(folderKey) + if (folderNoteSet == null) { + folderNoteSet = new Set(folderNoteSet) + state.folderNoteMap.set(folderKey, folderNoteSet) + } + folderNoteSet.add(uniqueKey) + + note.tags.forEach((tag) => { + let tagNoteSet = state.tagNoteMap.get(tag) + if (tagNoteSet == null) { + tagNoteSet = new Set(tagNoteSet) + state.tagNoteMap.set(tag, tagNoteSet) + } + tagNoteSet.add(uniqueKey) + }) + }) + return state case 'REMOVE_STORAGE': state = Object.assign({}, state) let storage = state.storageMap.get(action.storageKey) From 519ea1a33f154a2083c13796065b77bdba67ef11 Mon Sep 17 00:00:00 2001 From: Dick Choi Date: Thu, 8 Sep 2016 16:50:05 +0900 Subject: [PATCH 34/34] RENAME_STORAGE redux action --- browser/main/store.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/browser/main/store.js b/browser/main/store.js index 469eb4af..8b778939 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -439,6 +439,11 @@ function data (state = defaultDataMap(), action) { }) } return state + case 'RENAME_STORAGE': + state = Object.assign({}, state) + state.storageMap = new Map(state.storageMap) + state.storageMap.set(action.storage.key, action.storage) + return state } return state }