From 8c37b61ff61067d70577df3d58625b72e1377626 Mon Sep 17 00:00:00 2001 From: davy-c Date: Mon, 11 Nov 2019 10:36:45 +0900 Subject: [PATCH 1/8] fix webpack config when null parsed --- webpack.config.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/webpack.config.ts b/webpack.config.ts index e443db2d7b..56bb01000e 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -6,7 +6,10 @@ import ErrorOverlayPlugin from 'error-overlay-webpack-plugin' import dotenv from 'dotenv' import CopyPlugin from 'copy-webpack-plugin' -const { parsed } = dotenv.config() +let { parsed } = dotenv.config() +if (parsed == null) { + parsed = {} +} module.exports = { entry: [ From d1a2df8d36493be9707aa222cf0d8e30533e955f Mon Sep 17 00:00:00 2001 From: davy-c Date: Mon, 11 Nov 2019 13:16:41 +0900 Subject: [PATCH 2/8] update tag Store on create/update note --- src/lib/db/store.spec.ts | 103 ++++++++++++++++++++++++++++++++++++++- src/lib/db/store.ts | 41 ++++++++++++++-- 2 files changed, 139 insertions(+), 5 deletions(-) diff --git a/src/lib/db/store.spec.ts b/src/lib/db/store.spec.ts index 1f1b9aa74e..7a8457cee4 100644 --- a/src/lib/db/store.spec.ts +++ b/src/lib/db/store.spec.ts @@ -1,7 +1,7 @@ import { createDbStoreCreator, getStorageDataList } from './store' import { MemoryLiteStorage } from 'ltstrg' import { renderHook, act } from '@testing-library/react-hooks' -import { NoteStorage } from './types' +import { NoteStorage, NoteDoc } from './types' import { getFolderId } from './utils' import { RouterProvider } from '../router' @@ -201,4 +201,105 @@ describe('DbStore', () => { // TODO: routing test when current folder or current folder's parent folder is deleted. }) + + describe('#createNote', () => { + it('creates a note and registers in the map', async () => { + // given + const { result } = prepareDbStore() + let storage: NoteStorage + let noteDoc: NoteDoc | undefined + await act(async () => { + await result.current.initialize() + storage = await result.current.createStorage('test') + await result.current.createFolder(storage.id, '/test') + + // When + noteDoc = await result.current.createNote(storage.id, { + title: 'testNote' + }) + }) + // Then + expect(result.current.storageMap[storage!.id]!.noteMap).toEqual({ + [noteDoc!._id]: expect.objectContaining({ title: 'testNote' }) + }) + }) + + it('creates a note and register its tag', async () => { + // given + const { result } = prepareDbStore() + let storage: NoteStorage + let noteDoc: NoteDoc | undefined + await act(async () => { + await result.current.initialize() + storage = await result.current.createStorage('test') + await result.current.createFolder(storage.id, '/test') + + // When + noteDoc = await result.current.createNote(storage.id, { + title: 'testNote', + tags: ['testTag'] + }) + }) + // Then + expect( + result.current.storageMap[storage!.id]!.tagMap[noteDoc!.tags[0]]! + .noteIdSet + ).toMatchObject(new Set([noteDoc!._id])) + }) + }) + + describe('#updateNote', () => { + it('update a Note and handle tag removal', async () => { + // given + const { result } = prepareDbStore() + let storage: NoteStorage + let noteDoc: NoteDoc | undefined + await act(async () => { + await result.current.initialize() + storage = await result.current.createStorage('test') + await result.current.createFolder(storage.id, '/test') + noteDoc = await result.current.createNote(storage.id, { + title: 'testNote', + tags: ['tag1', 'tag2'] + }) + + // When + noteDoc = await result.current.updateNote(storage.id, noteDoc!._id, { + tags: ['tag2'] + }) + }) + // Then + expect( + result.current.storageMap[storage!.id]!.tagMap['tag1']!.noteIdSet.size + ).toEqual(0) + }) + it('update a Note and handle tag addition', async () => { + // given + const { result } = prepareDbStore() + let storage: NoteStorage + let noteDoc: NoteDoc | undefined + await act(async () => { + await result.current.initialize() + storage = await result.current.createStorage('test') + await result.current.createFolder(storage.id, '/test') + noteDoc = await result.current.createNote(storage.id, { + title: 'testNote', + tags: ['tag1'] + }) + await result.current.createNote(storage.id, { + title: 'testNote', + tags: ['tag2'] + }) + + // When + noteDoc = await result.current.updateNote(storage.id, noteDoc!._id, { + tags: ['tag1', 'tag2'] + }) + }) + // Then + expect( + result.current.storageMap[storage!.id]!.tagMap['tag2']!.noteIdSet.size + ).toEqual(2) + }) + }) }) diff --git a/src/lib/db/store.ts b/src/lib/db/store.ts index bc50f4d95f..59e8560506 100644 --- a/src/lib/db/store.ts +++ b/src/lib/db/store.ts @@ -4,7 +4,8 @@ import { ObjectMap, NoteDoc, NoteDocEditibleProps, - PopulatedFolderDoc + PopulatedFolderDoc, + PopulatedTagDoc } from './types' import { useState, useCallback } from 'react' import { createStoreContext } from '../utils/context' @@ -213,6 +214,7 @@ export function createDbStoreCreator( parentFolderPathnamesToCheck ) : [] + const folder: PopulatedFolderDoc = storage.folderMap[noteDoc.folderPathname] == null ? ({ @@ -228,7 +230,20 @@ export function createDbStoreCreator( ]) } - // TODO: Reflect tags + const tagMap = storage.tagMap + await Promise.all( + noteDoc.tags.map(async tag => { + if (tagMap[tag] == null) { + tagMap[tag] = { + ...(await storage.db.getTag(tag)!), + noteIdSet: new Set([noteDoc._id]) + } as PopulatedTagDoc + } else { + tagMap[tag]!.noteIdSet.add(noteDoc._id) + } + }) + ) + setStorageMap( produce((draft: ObjectMap) => { draft[storageId]!.noteMap[noteDoc._id] = noteDoc @@ -241,9 +256,9 @@ export function createDbStoreCreator( } }) draft[storageId]!.folderMap[noteDoc.folderPathname] = folder + draft[storageId]!.tagMap = tagMap }) ) - return noteDoc }, [storageMap] @@ -288,7 +303,24 @@ export function createDbStoreCreator( ]) } - // TODO: Reflect tags + const tagMap = storage.tagMap! + const currentTags = Object.keys(tagMap) + currentTags.forEach((tag: string) => { + tagMap[tag]!.noteIdSet.delete(noteDoc._id) + }) + await Promise.all( + noteDoc.tags.map(async tag => { + if (tagMap[tag] == null) { + tagMap[tag] = { + ...(await storage.db.getTag(tag)!), + noteIdSet: new Set([noteDoc._id]) + } as PopulatedTagDoc + } else { + tagMap[tag]!.noteIdSet.add(noteDoc._id) + } + }) + ) + setStorageMap( produce((draft: ObjectMap) => { draft[storageId]!.noteMap[noteDoc._id] = noteDoc @@ -301,6 +333,7 @@ export function createDbStoreCreator( } }) draft[storageId]!.folderMap[noteDoc.folderPathname] = folder + draft[storageId]!.tagMap = tagMap }) ) From c0b6abdafeb8d7a595359857aab0a1f20517aa22 Mon Sep 17 00:00:00 2001 From: davy-c Date: Mon, 11 Nov 2019 14:46:03 +0900 Subject: [PATCH 3/8] Do not modify store directly - immutable change --- src/lib/db/store.ts | 72 +++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/src/lib/db/store.ts b/src/lib/db/store.ts index 59e8560506..6cbc33624e 100644 --- a/src/lib/db/store.ts +++ b/src/lib/db/store.ts @@ -24,6 +24,9 @@ import { produce } from 'immer' import { useRouter } from '../router' import { values } from '../db/utils' import { storageDataListKey } from '../localStorageKeys' +import { TAG_ID_PREFIX } from './consts' + +const storageDataListKey = 'note.boostio.co:storageDataList' export interface DbStore { initialized: boolean @@ -230,19 +233,27 @@ export function createDbStoreCreator( ]) } - const tagMap = storage.tagMap - await Promise.all( + const modifiedTags = ((await Promise.all( noteDoc.tags.map(async tag => { - if (tagMap[tag] == null) { - tagMap[tag] = { + if (storage.tagMap[tag] == null) { + return { ...(await storage.db.getTag(tag)!), noteIdSet: new Set([noteDoc._id]) } as PopulatedTagDoc } else { - tagMap[tag]!.noteIdSet.add(noteDoc._id) + return { + ...storage.tagMap[tag]!, + noteIdSet: new Set([ + ...storage.tagMap[tag]!.noteIdSet.values(), + noteDoc._id + ]) + } } }) - ) + )) as PopulatedTagDoc[]).reduce((acc, tag) => { + acc[tag._id.replace(TAG_ID_PREFIX, '')] = tag + return acc + }, {}) setStorageMap( produce((draft: ObjectMap) => { @@ -256,7 +267,10 @@ export function createDbStoreCreator( } }) draft[storageId]!.folderMap[noteDoc.folderPathname] = folder - draft[storageId]!.tagMap = tagMap + draft[storageId]!.tagMap = { + ...storage.tagMap, + ...modifiedTags + } }) ) return noteDoc @@ -303,23 +317,42 @@ export function createDbStoreCreator( ]) } - const tagMap = storage.tagMap! - const currentTags = Object.keys(tagMap) - currentTags.forEach((tag: string) => { - tagMap[tag]!.noteIdSet.delete(noteDoc._id) - }) - await Promise.all( + const currentTags = Object.keys(storage.tagMap) + const previousTags: ObjectMap = { + ...currentTags!.reduce((acc, tag) => { + acc[tag] = { + ...storage.tagMap[tag]!, + noteIdSet: new Set( + [...storage.tagMap[tag]!.noteIdSet].filter(noteId => { + return noteId !== noteDoc._id + }) + ) + } + return acc + }, {}) + } + + const modifiedTags: ObjectMap = ((await Promise.all( noteDoc.tags.map(async tag => { - if (tagMap[tag] == null) { - tagMap[tag] = { + if (storage.tagMap[tag] == null) { + return { ...(await storage.db.getTag(tag)!), noteIdSet: new Set([noteDoc._id]) } as PopulatedTagDoc } else { - tagMap[tag]!.noteIdSet.add(noteDoc._id) + return { + ...storage.tagMap[tag]!, + noteIdSet: new Set([ + ...storage.tagMap[tag]!.noteIdSet.values(), + noteDoc._id + ]) + } } }) - ) + )) as PopulatedTagDoc[]).reduce((acc, tag) => { + acc[tag._id.replace(TAG_ID_PREFIX, '')] = tag + return acc + }, {}) setStorageMap( produce((draft: ObjectMap) => { @@ -333,7 +366,10 @@ export function createDbStoreCreator( } }) draft[storageId]!.folderMap[noteDoc.folderPathname] = folder - draft[storageId]!.tagMap = tagMap + draft[storageId]!.tagMap = { + ...previousTags, + ...modifiedTags + } }) ) From c2e93a02d3d893b34b0c69620f43f0430146fcc3 Mon Sep 17 00:00:00 2001 From: davy-c Date: Mon, 11 Nov 2019 14:56:12 +0900 Subject: [PATCH 4/8] fix rebase --- src/lib/db/store.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib/db/store.ts b/src/lib/db/store.ts index 6cbc33624e..39630c0421 100644 --- a/src/lib/db/store.ts +++ b/src/lib/db/store.ts @@ -26,8 +26,6 @@ import { values } from '../db/utils' import { storageDataListKey } from '../localStorageKeys' import { TAG_ID_PREFIX } from './consts' -const storageDataListKey = 'note.boostio.co:storageDataList' - export interface DbStore { initialized: boolean storageMap: ObjectMap From 2ac93dafd0e3844e8f3a06fc57bf59571042fe4e Mon Sep 17 00:00:00 2001 From: davy-c Date: Mon, 11 Nov 2019 15:24:58 +0900 Subject: [PATCH 5/8] fix lines and typo for tests --- src/lib/db/store.spec.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lib/db/store.spec.ts b/src/lib/db/store.spec.ts index 7a8457cee4..8fc09d020c 100644 --- a/src/lib/db/store.spec.ts +++ b/src/lib/db/store.spec.ts @@ -204,7 +204,7 @@ describe('DbStore', () => { describe('#createNote', () => { it('creates a note and registers in the map', async () => { - // given + // Given const { result } = prepareDbStore() let storage: NoteStorage let noteDoc: NoteDoc | undefined @@ -218,6 +218,7 @@ describe('DbStore', () => { title: 'testNote' }) }) + // Then expect(result.current.storageMap[storage!.id]!.noteMap).toEqual({ [noteDoc!._id]: expect.objectContaining({ title: 'testNote' }) @@ -225,7 +226,7 @@ describe('DbStore', () => { }) it('creates a note and register its tag', async () => { - // given + // Given const { result } = prepareDbStore() let storage: NoteStorage let noteDoc: NoteDoc | undefined @@ -240,6 +241,7 @@ describe('DbStore', () => { tags: ['testTag'] }) }) + // Then expect( result.current.storageMap[storage!.id]!.tagMap[noteDoc!.tags[0]]! @@ -250,7 +252,7 @@ describe('DbStore', () => { describe('#updateNote', () => { it('update a Note and handle tag removal', async () => { - // given + // Given const { result } = prepareDbStore() let storage: NoteStorage let noteDoc: NoteDoc | undefined @@ -268,13 +270,15 @@ describe('DbStore', () => { tags: ['tag2'] }) }) + // Then expect( result.current.storageMap[storage!.id]!.tagMap['tag1']!.noteIdSet.size ).toEqual(0) }) + it('update a Note and handle tag addition', async () => { - // given + // Given const { result } = prepareDbStore() let storage: NoteStorage let noteDoc: NoteDoc | undefined @@ -296,6 +300,7 @@ describe('DbStore', () => { tags: ['tag1', 'tag2'] }) }) + // Then expect( result.current.storageMap[storage!.id]!.tagMap['tag2']!.noteIdSet.size From a8fe1a1a611d3b6cab7cd2be071780ea421edb5f Mon Sep 17 00:00:00 2001 From: davy-c Date: Mon, 11 Nov 2019 15:52:07 +0900 Subject: [PATCH 6/8] remove note in noteIdSet mutably --- src/lib/db/store.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/lib/db/store.ts b/src/lib/db/store.ts index 39630c0421..75c7e5cc43 100644 --- a/src/lib/db/store.ts +++ b/src/lib/db/store.ts @@ -318,13 +318,11 @@ export function createDbStoreCreator( const currentTags = Object.keys(storage.tagMap) const previousTags: ObjectMap = { ...currentTags!.reduce((acc, tag) => { + const newNoteIdSet = new Set(storage.tagMap[tag]!.noteIdSet) + newNoteIdSet.delete(noteDoc._id) acc[tag] = { ...storage.tagMap[tag]!, - noteIdSet: new Set( - [...storage.tagMap[tag]!.noteIdSet].filter(noteId => { - return noteId !== noteDoc._id - }) - ) + noteIdSet: newNoteIdSet } return acc }, {}) From f01c2b68a40d7d2ba6a46ee3e9cc7f5a1b36d858 Mon Sep 17 00:00:00 2001 From: davy-c Date: Mon, 11 Nov 2019 16:07:24 +0900 Subject: [PATCH 7/8] simplify removed tags map --- src/lib/db/store.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/lib/db/store.ts b/src/lib/db/store.ts index 75c7e5cc43..c1e67968c8 100644 --- a/src/lib/db/store.ts +++ b/src/lib/db/store.ts @@ -25,6 +25,7 @@ import { useRouter } from '../router' import { values } from '../db/utils' import { storageDataListKey } from '../localStorageKeys' import { TAG_ID_PREFIX } from './consts' +import R from 'ramda' export interface DbStore { initialized: boolean @@ -315,18 +316,18 @@ export function createDbStoreCreator( ]) } - const currentTags = Object.keys(storage.tagMap) - const previousTags: ObjectMap = { - ...currentTags!.reduce((acc, tag) => { - const newNoteIdSet = new Set(storage.tagMap[tag]!.noteIdSet) - newNoteIdSet.delete(noteDoc._id) - acc[tag] = { - ...storage.tagMap[tag]!, - noteIdSet: newNoteIdSet - } - return acc - }, {}) - } + const removedTags: ObjectMap = R.difference( + storage.noteMap[noteDoc._id]!.tags, + noteDoc.tags + ).reduce((acc, tag) => { + const newNoteIdSet = new Set(storage.tagMap[tag]!.noteIdSet) + newNoteIdSet.delete(noteDoc._id) + acc[tag] = { + ...storage.tagMap[tag]!, + noteIdSet: newNoteIdSet + } + return acc + }, {}) const modifiedTags: ObjectMap = ((await Promise.all( noteDoc.tags.map(async tag => { @@ -363,7 +364,8 @@ export function createDbStoreCreator( }) draft[storageId]!.folderMap[noteDoc.folderPathname] = folder draft[storageId]!.tagMap = { - ...previousTags, + ...storage.tagMap, + ...removedTags, ...modifiedTags } }) From c6e937a1a59bf8ff7a2a17e0515f2a63915a3449 Mon Sep 17 00:00:00 2001 From: davy-c Date: Mon, 11 Nov 2019 16:46:47 +0900 Subject: [PATCH 8/8] fix ramda import --- src/lib/db/store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/db/store.ts b/src/lib/db/store.ts index c1e67968c8..2a1a1fa0d5 100644 --- a/src/lib/db/store.ts +++ b/src/lib/db/store.ts @@ -25,7 +25,7 @@ import { useRouter } from '../router' import { values } from '../db/utils' import { storageDataListKey } from '../localStorageKeys' import { TAG_ID_PREFIX } from './consts' -import R from 'ramda' +import { difference } from 'ramda' export interface DbStore { initialized: boolean @@ -316,7 +316,7 @@ export function createDbStoreCreator( ]) } - const removedTags: ObjectMap = R.difference( + const removedTags: ObjectMap = difference( storage.noteMap[noteDoc._id]!.tags, noteDoc.tags ).reduce((acc, tag) => {