diff --git a/.babelrc b/.babelrc index 24a13da0f0..47f1cb609d 100644 --- a/.babelrc +++ b/.babelrc @@ -43,9 +43,8 @@ "react" ], "plugins": [ - "react-hot-loader/babel", - "transform-class-properties", - "rewire" + "transform-es2015-modules-commonjs", + "transform-class-properties" ] } } diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..b16d7f9235 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - "6" \ No newline at end of file diff --git a/app/lib/main.html b/app/lib/main.html index 133a177e22..a943b8bf88 100644 --- a/app/lib/main.html +++ b/app/lib/main.html @@ -10,6 +10,9 @@ +
+ +

Loading...

+
diff --git a/app/lib/main.production.html b/app/lib/main.production.html index a0acb3ac25..d070d245c6 100644 --- a/app/lib/main.production.html +++ b/app/lib/main.production.html @@ -10,6 +10,9 @@ +
+ +

Loading...

+
diff --git a/app/lib/menuTemplate.js b/app/lib/menuTemplate.js index 39024414dd..e4e4032605 100644 --- a/app/lib/menuTemplate.js +++ b/app/lib/menuTemplate.js @@ -129,6 +129,24 @@ const template = [ }, { role: 'togglefullscreen' + }, + { + label: 'Single Layout', + accelerator: 'CmdOrCtrl + 1', + click: e => { + if (global.windows.main) { + global.windows.main.webContents.send('set-single-layout') + } + } + }, + { + label: 'Two Pane Layout', + accelerator: 'CmdOrCtrl + 2', + click: e => { + if (global.windows.main) { + global.windows.main.webContents.send('set-two-pane-layout') + } + } } ] }, diff --git a/docs/development/database.md b/docs/development/database.md index ae18c3ede7..45f20d37f1 100644 --- a/docs/development/database.md +++ b/docs/development/database.md @@ -1,6 +1,12 @@ # Database -Inpad uses pouchDB as a storage and the pouchDB uses WebSQL as a default. +Inpad uses pouchDB as a storage and the pouchDB uses IndexedDB as a default. + +## Apis + +- Create/Update apis can take partial data. (Default values will be applied to any missing attributes.) +- Create/Update apis must return whole data and its id. (Redux store will use it without any further processing.) +- Delete api should return id. ## Default Storage @@ -27,7 +33,7 @@ type | conventions -------|-------------------- Note | `note:$RANDOM_HASH$` Folder | `folder:$PATH_OF_FOLDER$` -Tag | `tag:$TAG$` +Tag | `tag:$TAG_NAME$` ### `$RANDOM_HASH$` @@ -37,11 +43,13 @@ This is a string of 10 Random bytes. It can be issued easily by `main/lib/util.r ### `$PATH_OF_FOLDERS$` -This string should be a valid path. When creating a folder, the app will convert it into a valid path if it is invalid. +This string should be a valid path for Unix. `/` character will be used for seperating directory components. + +When creating a folder, the app will convert it into a valid path if it is invalid. -### `$TAG$` +### `$TAG_NAME$` -Tag is a lowcased alphanumeric string. Spaces will be replaced with `_`. +Any character can be used for tag name. ## Schemes diff --git a/docs/development/keymap-and-commands.md b/docs/development/keymap-and-commands.md index b4e9e5dd7a..08b5eb23d7 100644 --- a/docs/development/keymap-and-commands.md +++ b/docs/development/keymap-and-commands.md @@ -22,6 +22,8 @@ macOS | Windows/Linux | Command via ipc or Action **Cmd - R** | **Ctrl - R, F5** | Refresh **Cmd - ,** | **Ctrl - ,** | `title:preferences` **Cmd - P** | **Ctrl - P** | `detail:print` (ipc `print`) +**Cmd - 1** | **Ctrl - 1** | `detail:set-single-layout` +**Cmd - 2** | **Ctrl - 2** | `detail:set-two-pane-layout` ## Main @@ -66,6 +68,7 @@ Right | Right | `list:focus` Up | Up | `nav:up` Down | Down | `nav:down` D | D | `nav:delete` +Tab | Tab | `nav:toggle-tab` ### Commands @@ -112,9 +115,12 @@ Cmd - ' | Ctrl - ' | `detail:focus-tag-select` ### Commands -Command | Action -------------------------|---------------------------- -detail:focus-tag-select | Focus tag select -detail:focus | Focus Detail -detail:find | Start finding -detail:print | Print +Command | Action +---------------------------|---------------------------- +detail:focus-tag-select | Focus tag select +detail:focus | Focus Detail +detail:find | Start finding +detail:print | Print +detail:set-single-layout | Set single layout +detail:set-two-pane-layout | Set two pane layout +detail:toggle-layout | Toggle layout diff --git a/gh-pages b/gh-pages index 765e1661cf..abd4b511f2 160000 --- a/gh-pages +++ b/gh-pages @@ -1 +1 @@ -Subproject commit 765e1661cf977576d1f6a55d9546eaaa2ad089ed +Subproject commit abd4b511f242893000b929892d9443d4a1585e1e diff --git a/package.json b/package.json index 222f310228..76d63f505e 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "inpad", "productName": "Inpad", - "version": "0.1.0", + "version": "0.2.0", "description": "A Polished Notes App with Github Flavored Markdown", "main": "app/index.js", "repository": { - "type" : "git", - "url" : "https://github.com/Sarah-Seo/Inpad.git" + "type": "git", + "url": "https://github.com/Sarah-Seo/Inpad.git" }, "scripts": { "start": "electron app/index.js", @@ -18,8 +18,9 @@ "shasum": "shasum -a 256 dist/mac/Inpad-$npm_package_version.dmg", "lint": "standard", "webpack": "NODE_ENV=development webpack-dev-server --config webpack.config.js", - "test:run": "NODE_ENV=test electron ./tools/webpack-test.js", - "test:serve": "NODE_ENV=test webpack-dev-server --config webpack.config.js", + "test": "NODE_ENV=test jest", + "test:watch": "NODE_ENV=test jest --watch", + "todo": "fixme -i 'compiled/**' -i 'dist/**' -i 'resources/**' -i 'node_modules/**'", "rebuild": "electron-rebuild" }, "keywords": [ @@ -33,9 +34,10 @@ "devDependencies": { "babel-core": "^6.18.0", "babel-eslint": "^7.1.1", + "babel-jest": "^18.0.0", "babel-loader": "^6.2.7", - "babel-plugin-rewire": "^1.0.0", "babel-plugin-transform-class-properties": "^6.19.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", "babel-preset-es2015": "^6.18.0", "babel-preset-react": "^6.16.0", "babel-preset-stage-2": "^6.18.0", @@ -46,10 +48,13 @@ "electron-devtools-installer": "^2.0.1", "electron-rebuild": "^1.3.0", "env-cmd": "^4.0.0", + "eslint-plugin-jest": "^1.0.2", "extract-text-webpack-plugin": "^2.0.0-beta.4", "file-loader": "^0.9.0", "filenamify": "^1.2.1", + "fixme": "^0.4.2", "immutable": "^3.8.1", + "jest": "^18.1.0", "json-loader": "^0.5.4", "lodash": "^4.16.6", "moment": "^2.17.0", @@ -88,7 +93,37 @@ "katex": "^0.6.0" }, "standard": { - "parser": "babel-eslint" + "parser": "babel-eslint", + "plugins": [ + "jest" + ], + "envs": [ + "jest" + ] + }, + "jest": { + "setupFiles": [ + "/specs/utils/setup.js" + ], + "testPathIgnorePatterns": [ + "/node_modules/" + ], + "moduleFileExtensions": [ + "js", + "jsx", + "json" + ], + "moduleDirectories": [ + "node_modules" + ], + "moduleNameMapper": { + "^main(.*)$": "/src/main$1", + "^lib(.*)$": "/src/lib$1", + "^components(.*)$": "/src/components$1", + "^specs(.*)$": "/specs$1", + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/specs/__mocks__/fileMock.js", + "\\.(css|less)$": "/specs/__mocks__/styleMock.js" + } }, "build": { "appId": "com.electron.Inpad", diff --git a/resources/dmg.png b/resources/dmg.png deleted file mode 100644 index 682118bb96..0000000000 Binary files a/resources/dmg.png and /dev/null differ diff --git a/resources/dmg@2x.png b/resources/dmg@2x.png deleted file mode 100644 index f8d17150ae..0000000000 Binary files a/resources/dmg@2x.png and /dev/null differ diff --git a/resources/install.gif b/resources/install.gif deleted file mode 100644 index b0520e64c9..0000000000 Binary files a/resources/install.gif and /dev/null differ diff --git a/specs/__functional__/lib/markdown.spec.js b/specs/__functional__/lib/markdown.spec.js new file mode 100644 index 0000000000..3cd2d76afa --- /dev/null +++ b/specs/__functional__/lib/markdown.spec.js @@ -0,0 +1,66 @@ +import markdown from 'lib/markdown' + +const contentWithEmojiTitle = ` +This is not a title + +# :smile: **This** is a title + +Preview line + +- [ ] task1 +- [x] task2 +- [ ] task3 + +` + +const contentWithOutHeading = ` +This should be title + +this should be a previewed content +` + +const contentWithImg = ` +# Awesome Electron [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) + +[](http://electron.atom.io) + +> Useful resources for creating apps with [Electron](http://electron.atom.io) +` + +// const mathString = ` +// L is lift force, +// $$\\rho$$ is air density, +// $$v$$ is true airspeed, +// $$A$$ is the wing area, and +// $$C_L$$ is the lift coefficient at the desired angle of attack + +// $$$ +// L = \\frac{1}{2} \\rho v^2 A C_L +// $$$ +// ` + +describe('markdown', () => { + it('should parse emoji', () => { + const parsed = markdown.parse(contentWithEmojiTitle) + expect(parsed.data.title).toEqual('😄 This is a title') + expect(parsed.data.preview).toEqual('Preview line') + }) + + it('should return first line as a title', () => { + const parsed = markdown.parse(contentWithOutHeading) + expect(parsed.data.title).toEqual('This should be title') + expect(parsed.data.preview).toEqual('this should be a previewed content') + }) + + it('should handle empty string', () => { + const parsed = markdown.parse('') + expect(parsed.data.title).toEqual('') + expect(parsed.data.preview).toEqual('') + }) + + it('skip img when finding preview', () => { + const parsed = markdown.parse(contentWithImg) + expect(parsed.data.title).toEqual('Awesome Electron Awesome') + expect(parsed.data.preview).toEqual('Useful resources for creating apps with Electron') + }) +}) diff --git a/specs/__functional__/main/lib/dataApi/createNote.spec.js b/specs/__functional__/main/lib/dataApi/createNote.spec.js new file mode 100644 index 0000000000..71a73df0c0 --- /dev/null +++ b/specs/__functional__/main/lib/dataApi/createNote.spec.js @@ -0,0 +1,62 @@ +import createNote from 'main/lib/dataAPI/createNote' +import DummyDB from 'specs/utils/DummyDB' +import { + NOTE_ID_PREFIX, + FOLDER_ID_PREFIX, + TAG_ID_PREFIX +} from 'main/lib/dataAPI/consts' + +const dbName = __filename +const db = new DummyDB(dbName) + +function fetchNote (noteId) { + return db.get(NOTE_ID_PREFIX + noteId) +} + +function fetchFolder () { + return db.get(FOLDER_ID_PREFIX + folderName) +} + +function fetchTag () { + return db.get(TAG_ID_PREFIX + tagName) +} + +const folderName = 'Test Folder' +const tagName = 'test_tag' +const note = { + meta: {}, + content: 'test', + tags: [tagName], + folder: folderName, + createdAt: new Date(), + updatedAt: new Date() +} + +describe('dataApi.createNote', () => { + it('should create a note', () => { + return createNote(dbName, note) + .then(res => { + expect(res.note.get('content')).toEqual(note.content) + return res.id + }) + .then(fetchNote) + .then(res => { + expect(res).not.toBeNull() + expect(res.content).toEqual(note.content) + expect(res.folder).toEqual(folderName) + expect(res.tags[0]).toEqual(tagName) + }) + .then(fetchFolder) + .then(doc => { + expect(doc._id).toEqual(FOLDER_ID_PREFIX + folderName) + }) + .then(fetchTag) + .then(doc => { + expect(doc._id).toEqual(TAG_ID_PREFIX + tagName) + }) + }) + + afterAll(() => { + return db.destory() + }) +}) diff --git a/specs/__functional__/main/lib/dataApi/deleteFolder.spec.js b/specs/__functional__/main/lib/dataApi/deleteFolder.spec.js new file mode 100644 index 0000000000..a030a04064 --- /dev/null +++ b/specs/__functional__/main/lib/dataApi/deleteFolder.spec.js @@ -0,0 +1,82 @@ +import deleteFolder from 'main/lib/dataAPI/deleteFolder' +import DummyDB from 'specs/utils/DummyDB' +import { + FOLDER_ID_PREFIX, + NOTE_ID_PREFIX +} from 'main/lib/dataAPI/consts' + +const dbName = __filename +const db = new DummyDB(dbName) + +const folderName = 'Testt Folder' +const noteId = 'testnote' +const note = { + title: 'test', + content: 'test\ncontent', + tags: ['test_tag'], + folder: folderName +} + +function createDummyFolder () { + return db.createFolder(folderName) +} + +function createDummyNote () { + return db.createNote(noteId, note) +} + +function fetchFolder () { + return db + .get(FOLDER_ID_PREFIX + folderName) +} + +function fetchNote () { + return db + .get(NOTE_ID_PREFIX + noteId) +} + +function createWrongView () { + return db.get('_design/notes') + .catch(err => { + if (err.name !== 'not_found') throw err + }) + .then(doc => { + return db.put(Object.assign({}, doc, { + _id: '_design/notes', + views: {} + })) + }) +} + +describe('dataAPI.deleteFolder', () => { + beforeAll(() => { + return createDummyFolder() + .then(createDummyNote) + .then(createWrongView) + }) + + it('should delete folder and its notes', () => { + return deleteFolder(dbName, folderName) + .then(folder => { + expect(folder.id).toEqual(folderName) + }) + .then(fetchFolder) + .then(res => { + throw new Error('should not fired') + }) + .catch(err => { + expect(err.message).toEqual('missing') + }) + .then(fetchNote) + .then(res => { + throw new Error('should not fired') + }) + .catch(err => { + expect(err.message).toEqual('missing') + }) + }) + + afterAll(() => { + return db.destory() + }) +}) diff --git a/specs/main/lib/data/deleteNote.spec.js b/specs/__functional__/main/lib/dataApi/deleteNote.spec.js similarity index 53% rename from specs/main/lib/data/deleteNote.spec.js rename to specs/__functional__/main/lib/dataApi/deleteNote.spec.js index e518479ff6..269139b05f 100644 --- a/specs/main/lib/data/deleteNote.spec.js +++ b/specs/__functional__/main/lib/dataApi/deleteNote.spec.js @@ -24,26 +24,26 @@ function fetchNote (noteId) { return db.get(NOTE_ID_PREFIX + noteId) } -export const before = t => { - return createdDummyNote() -} +describe('dataAPI.deleteNote', () => { + beforeAll(() => { + return createdDummyNote() + }) -export default t => { - return deleteNote(dbName, noteId) - .then(res => { - return res.id - }) - .then(fetchNote) - .then(res => { - t.fail('The note should not exist.') - }) - .catch(err => { - if (err.name !== 'not_found') { - throw err - } - }) -} + it('should delete a note', () => { + return deleteNote(dbName, noteId) + .then(res => { + return res.id + }) + .then(fetchNote) + .then(res => { + throw new Error('should not fired') + }) + .catch(err => { + expect(err.message).toEqual('missing') + }) + }) -export const after = t => { - return db.destory() -} + afterAll(() => { + return db.destory() + }) +}) diff --git a/specs/__functional__/main/lib/dataApi/deleteTag.spec.js b/specs/__functional__/main/lib/dataApi/deleteTag.spec.js new file mode 100644 index 0000000000..c3e03e2bed --- /dev/null +++ b/specs/__functional__/main/lib/dataApi/deleteTag.spec.js @@ -0,0 +1,77 @@ +import deleteTag from 'main/lib/dataAPI/deleteTag' +import DummyDB from 'specs/utils/DummyDB' +import { + TAG_ID_PREFIX, + NOTE_ID_PREFIX +} from 'main/lib/dataAPI/consts' + +const dbName = __filename +const db = new DummyDB(dbName) + +const tagName = 'Testt Tag' +const noteId = 'testnote' +const note = { + title: 'test', + content: 'test\ncontent', + tags: [tagName] +} + +function createDummyTag () { + return db.createTag(tagName) +} + +function createDummyNote () { + return db.createNote(noteId, note) +} + +function fetchNote () { + return db.get(NOTE_ID_PREFIX + noteId) +} + +function fetchTag () { + return db + .get(TAG_ID_PREFIX + tagName) +} + +function createWrongView () { + return db.get('_design/notes') + .catch(err => { + if (err.name !== 'not_found') throw err + }) + .then(doc => { + return db.put(Object.assign({}, doc, { + _id: '_design/notes', + views: {} + })) + }) +} + +describe('dataAPI.deleteTag', () => { + beforeAll(() => { + return createDummyTag() + .then(createDummyNote) + .then(createWrongView) + }) + + it('should delete a tag and untag its notes', () => { + return deleteTag(dbName, tagName) + .then(tag => { + expect(tag.id).toEqual(tagName) + }) + .then(fetchNote) + .then(res => { + expect(res.tags.length).toEqual(0) + }) + .then(fetchTag) + .then(res => { + throw new Error('should not fired') + }) + .catch(err => { + expect(err.message).toEqual('missing') + }) + }) + + afterAll(() => { + return db.destory() + }) +}) diff --git a/specs/__functional__/main/lib/dataApi/loadAllStorages.spec.js b/specs/__functional__/main/lib/dataApi/loadAllStorages.spec.js new file mode 100644 index 0000000000..b47a207ed7 --- /dev/null +++ b/specs/__functional__/main/lib/dataApi/loadAllStorages.spec.js @@ -0,0 +1,21 @@ +import loadAllStorages from 'main/lib/dataAPI/loadAllStorages' + +describe('dataAPI.loadAllStorages', () => { + beforeAll(() => { + window.localStorage.setItem('storages', JSON.stringify([{name: 'Test Storage'}])) + }) + + it('load all storages', () => { + return loadAllStorages() + .then(storageMap => { + const storages = ['Notebook', 'Test Storage'] + storages.forEach(storageName => { + expect(storageMap.hasIn([storageName])).toBeTruthy() + expect(storageMap.hasIn([storageName, 'noteMap'])).toBeTruthy() + expect(storageMap.hasIn([storageName, 'folderMap'])).toBeTruthy() + expect(storageMap.hasIn([storageName, 'folderMap', 'Notes'])).toBeTruthy() + expect(storageMap.hasIn([storageName, 'tagMap'])).toBeTruthy() + }) + }) + }) +}) diff --git a/specs/__functional__/main/lib/dataApi/loadStorage.spec.js b/specs/__functional__/main/lib/dataApi/loadStorage.spec.js new file mode 100644 index 0000000000..f6589dbbb3 --- /dev/null +++ b/specs/__functional__/main/lib/dataApi/loadStorage.spec.js @@ -0,0 +1,46 @@ +import loadStorage from 'main/lib/dataAPI/loadStorage' +import DummyDB from 'specs/utils/DummyDB' + +const dbName = __filename +const db = new DummyDB(dbName) + +const folderName = 'Test Folder' +const noteId = 'testnote' +const note = { + content: 'test\ncontent', + tags: ['test_tag'], + folder: folderName +} + +function createDummyFolder () { + return db.createFolder(folderName) +} + +function createDummyNote () { + return db.createNote(noteId, note) +} + +describe('dataAPI.loadStorage', () => { + beforeAll(() => { + return createDummyFolder(folderName) + .then(createDummyNote) + }) + + it('should load a storage', () => { + return loadStorage(dbName) + .then(storageMap => { + expect(storageMap.has('noteMap')).toBeTruthy() + expect(storageMap.has('folderMap')).toBeTruthy() + expect(storageMap.hasIn(['folderMap', folderName])).toBeTruthy() + expect(storageMap.hasIn(['folderMap', folderName, 'notes'])).toBeTruthy() + expect(storageMap.getIn(['folderMap', folderName, 'notes']).includes(noteId)).toBeTruthy() + expect(storageMap.hasIn(['tagMap'])).toBeTruthy() + expect(storageMap.hasIn(['tagMap', 'test_tag', 'notes'])).toBeTruthy() + expect(storageMap.getIn(['tagMap', 'test_tag', 'notes']).first()).toEqual(noteId) + }) + }) + + afterAll(() => { + return db.destory() + }) +}) diff --git a/specs/main/lib/data/renameFolder.spec.js b/specs/__functional__/main/lib/dataApi/renameFolder.spec.js similarity index 50% rename from specs/main/lib/data/renameFolder.spec.js rename to specs/__functional__/main/lib/dataApi/renameFolder.spec.js index 8956cb7484..60bf3a5dd1 100644 --- a/specs/main/lib/data/renameFolder.spec.js +++ b/specs/__functional__/main/lib/dataApi/renameFolder.spec.js @@ -41,35 +41,35 @@ function fetchNote () { .get(NOTE_ID_PREFIX + noteId) } -export const before = t => { - return createDummyFolder() - .then(createDummyNote) -} +describe('dataAPI.renameFolder', () => { + beforeAll(() => { + return createDummyFolder() + .then(createDummyNote) + }) -export default t => { - return renameFolder(dbName, folderName, newFolderName) - .then(folder => { - t.equal(folder.id, folderName) - }) - .then(fetchFolder) - .then(res => { - t.fail('The folder should not exist.') - }) - .catch(err => { - if (err.name !== 'not_found') { - throw err - } - }) - .then(fetchNote) - .then(res => { - t.equal(res.folder, newFolderName) - }) - .then(fetchRenamedFolder) - .then(res => { - t.equal(res._id, FOLDER_ID_PREFIX + newFolderName) - }) -} + it('should delete old folder and create a new folder and move all notes to the new folder', () => { + return renameFolder(dbName, folderName, newFolderName) + .then(folder => { + expect(folder.id).toEqual(folderName) + }) + .then(fetchFolder) + .then(res => { + throw new Error('should not fired') + }) + .catch(err => { + expect(err.message).toEqual('missing') + }) + .then(fetchNote) + .then(res => { + expect(res.folder).toEqual(newFolderName) + }) + .then(fetchRenamedFolder) + .then(res => { + expect(res._id).toEqual(FOLDER_ID_PREFIX + newFolderName) + }) + }) -export const after = t => { - return db.destory() -} + afterAll(() => { + return db.destory() + }) +}) diff --git a/specs/__functional__/main/lib/dataApi/renameTag.spec.js b/specs/__functional__/main/lib/dataApi/renameTag.spec.js new file mode 100644 index 0000000000..98e10c4d26 --- /dev/null +++ b/specs/__functional__/main/lib/dataApi/renameTag.spec.js @@ -0,0 +1,98 @@ +import renameTag from 'main/lib/dataAPI/renameTag' +import DummyDB from 'specs/utils/DummyDB' +import { + TAG_ID_PREFIX, + NOTE_ID_PREFIX +} from 'main/lib/dataAPI/consts' + +const dbName = __filename +const db = new DummyDB(dbName) + +const tagName = 'Test Tag' +const newTagName = 'Renamed Tag' +const noteId = 'testnote' +const note = { + title: 'test', + content: 'test\ncontent', + tags: [tagName], + folder: 'Notes' +} +const anotherNoteId = 'anothernote' +const anotherNote = { + title: 'tesasdfasdft', + content: 'tesasdfasdft\ncontent', + tags: [tagName], + folder: 'Notes' +} + +function createDummyTag () { + return db.createTag(tagName) +} + +function createDummyNote () { + return db.createNote(noteId, note) +} + +function createAnotherDummyNote () { + return db.createNote(anotherNoteId, anotherNote) +} + +function fetchTag () { + return db + .get(TAG_ID_PREFIX + tagName) +} + +function fetchRenamedTag () { + return db + .get(TAG_ID_PREFIX + newTagName) +} + +function fetchNote () { + return db + .get(NOTE_ID_PREFIX + noteId) +} + +function fetchAnotherNote () { + return db + .get(NOTE_ID_PREFIX + anotherNoteId) +} + +describe('dataAPI.renameFolder', () => { + beforeAll(() => { + return createDummyTag() + .then(createDummyNote) + .then(createAnotherDummyNote) + }) + + it('should delete old tag and create a new tag and update all notes', () => { + return renameTag(dbName, tagName, newTagName) + .then(tag => { + expect(tag.id).toEqual(tagName) + }) + .then(fetchTag) + .then(res => { + throw new Error('should not fired') + }) + .catch(err => { + expect(err.message).toEqual('missing') + }) + .then(fetchNote) + .then(res => { + expect(res.tags.indexOf(tagName) === -1).toBeTruthy() + expect(res.tags.indexOf(newTagName) > -1).toBeTruthy() + }) + .then(fetchAnotherNote) + .then(res => { + expect(res.tags.indexOf(tagName) === -1).toBeTruthy() + expect(res.tags.indexOf(newTagName) > -1).toBeTruthy() + }) + .then(fetchRenamedTag) + .then(res => { + expect(res._id).toEqual(TAG_ID_PREFIX + newTagName) + }) + }) + + afterAll(() => { + return db.destory() + }) +}) diff --git a/specs/__functional__/main/lib/dataApi/updateNote.spec.js b/specs/__functional__/main/lib/dataApi/updateNote.spec.js new file mode 100644 index 0000000000..526cfe1dea --- /dev/null +++ b/specs/__functional__/main/lib/dataApi/updateNote.spec.js @@ -0,0 +1,76 @@ +import updateNote from 'main/lib/dataAPI/updateNote' +import DummyDB from 'specs/utils/DummyDB' +import { + NOTE_ID_PREFIX, + FOLDER_ID_PREFIX, + TAG_ID_PREFIX +} from 'main/lib/dataAPI/consts' + +const dbName = __filename +const db = new DummyDB(dbName) + +const noteId = 'testNote' +const note = { + content: 'test', + tags: ['test_tag'], + folder: 'Notes', + createdAt: new Date(), + updatedAt: new Date() +} +const newNote = { + meta: { + title: 'changed' + }, + content: 'changed', + folder: 'Other Folder', + tags: ['changed'] +} + +function createdDummyNote () { + return db.createNote(noteId, note) +} + +function fetchNote (noteId) { + return db.get(NOTE_ID_PREFIX + noteId) +} + +function fetchNewTag () { + return db.get(TAG_ID_PREFIX + newNote.tags[0]) +} + +function fetchNewFolder () { + return db.get(FOLDER_ID_PREFIX + newNote.folder) +} + +describe('dataAPI.updateNote', () => { + beforeAll(() => { + return createdDummyNote() + }) + + it('should update note', () => { + return updateNote(dbName, noteId, newNote) + .then(res => { + expect(res.note.get('content')).toEqual('changed') + return res.id + }) + .then(fetchNote) + .then(res => { + expect(res).not.toBeNull() + expect(res.content).toEqual('changed') + expect(res.tags[0]).toEqual('changed') + }) + .then(fetchNewFolder) + .then(res => { + expect(res._id).toEqual(FOLDER_ID_PREFIX + newNote.folder) + }) + .then(fetchNewTag) + .then(res => { + expect(res).not.toBeNull() + expect(res._id).toEqual(TAG_ID_PREFIX + newNote.tags[0]) + }) + }) + + afterAll(() => { + return db.destory() + }) +}) diff --git a/specs/__functional__/main/lib/dataApi/upsertFolder.spec.js b/specs/__functional__/main/lib/dataApi/upsertFolder.spec.js new file mode 100644 index 0000000000..41a95bb715 --- /dev/null +++ b/specs/__functional__/main/lib/dataApi/upsertFolder.spec.js @@ -0,0 +1,30 @@ +import upsertFolder from 'main/lib/dataAPI/upsertFolder' +import DummyDB from 'specs/utils/DummyDB' +import { + FOLDER_ID_PREFIX +} from 'main/lib/dataAPI/consts' + +const dbName = __filename +const db = new DummyDB(dbName) + +function fetchFolder () { + return db + .get(FOLDER_ID_PREFIX + 'Test Folder') +} + +describe('dataAPI.upsertFolder', () => { + it('should create a folder', () => { + return upsertFolder(dbName, 'Test Folder') + .then(folder => { + expect(folder.id).toEqual('Test Folder') + }) + .then(fetchFolder) + .then(res => { + expect(res).not.toBeNull() + }) + }) + + afterAll(() => { + return db.destory() + }) +}) diff --git a/specs/__functional__/main/lib/dataApi/upsertTag.spec.js b/specs/__functional__/main/lib/dataApi/upsertTag.spec.js new file mode 100644 index 0000000000..9be0e69e7c --- /dev/null +++ b/specs/__functional__/main/lib/dataApi/upsertTag.spec.js @@ -0,0 +1,29 @@ +import upsertTag from 'main/lib/dataAPI/upsertTag' +import DummyDB from 'specs/utils/DummyDB' +import { + TAG_ID_PREFIX +} from 'main/lib/dataAPI/consts' + +const dbName = __filename +const db = new DummyDB(dbName) + +function fetchTag () { + return db + .get(TAG_ID_PREFIX + 'Test_tag') +} + +describe('dataAPI.upsertTag', () => { + it('should create a tag', () => { + return upsertTag(dbName, 'Test_tag') + .then(tag => { + expect(tag.id).toEqual('Test_tag') + }) + .then(fetchTag) + .then(res => { + expect(res != null).toBeTruthy() + }) + }) + afterAll(() => { + return db.destory() + }) +}) diff --git a/specs/__functional__/main/lib/redux/reducers/storageMap.spec.js b/specs/__functional__/main/lib/redux/reducers/storageMap.spec.js new file mode 100644 index 0000000000..43da5ba6cf --- /dev/null +++ b/specs/__functional__/main/lib/redux/reducers/storageMap.spec.js @@ -0,0 +1,121 @@ +import { Map, Set } from 'immutable' +import storageMap from 'main/lib/redux/reducers/storageMap' +import DummyStorageMap from 'specs/utils/DummyStorageMap' + +describe('storageMap', () => { + it('should create a note with tags and folder', () => { + const state = new DummyStorageMap().getState() + + const action = { + type: 'CREATE_NOTE', + payload: { + storageName: 'Notebook', + noteId: 'test', + note: new Map({ + folder: 'Test folder', + tags: new Set(['new', 'tag']) + }) + } + } + + const nextState = storageMap(state, action) + // it should create a note + expect(nextState.getIn(['Notebook', 'noteMap', action.payload.noteId, 'content'])).toEqual(action.payload.note.get('content')) + // it should create a folder + expect(nextState.hasIn(['Notebook', 'folderMap', action.payload.note.get('folder')])).toBeTruthy() + // it should create tags + action.payload.note.get('tags') + .forEach(tagName => { + expect(nextState.hasIn(['Notebook', 'tagMap', tagName])).toBeTruthy() + }) + }) + + it('should delete a folder and its notes', () => { + const state = new DummyStorageMap() + .createNote('Notebook', 'test', { + folder: 'Test folder' + }) + .createNote('Notebook', 'test2', { + folder: 'Test folder' + }) + .createNote('Notebook', 'test3', { + folder: 'Another folder' + }) + .getState() + + const action = { + type: 'DELETE_FOLDER', + payload: { + storageName: 'Notebook', + folderName: 'Test folder' + } + } + + const nextState = storageMap(state, action) + // The notes, test and test2, should be deleted + expect(nextState.hasIn(['Notebook', 'noteMap', 'test'])).toBeFalsy() + expect(nextState.hasIn(['Notebook', 'noteMap', 'test2'])).toBeFalsy() + // test3 should exist + expect(nextState.hasIn(['Notebook', 'noteMap', 'test3'])).toBeTruthy() + // it should delete tag + expect(nextState.hasIn(['Notebook', 'folderMap', action.payload.folderName])).toBeFalsy() + }) + + it('should delete a tag and untag its notes', () => { + const state = new DummyStorageMap() + .createNote('Notebook', 'test', { + tags: ['new', 'tag'] + }) + .createNote('Notebook', 'test2', { + tags: ['new', 'tag2'] + }) + .getState() + + const action = { + type: 'DELETE_TAG', + payload: { + storageName: 'Notebook', + tagName: 'new' + } + } + + const nextState = storageMap(state, action) + // it should untag all notes + expect(nextState.getIn(['Notebook', 'noteMap', 'test', 'tags']).has('new')).toBeFalsy() + expect(nextState.getIn(['Notebook', 'noteMap', 'test2', 'tags']).has('new')).toBeFalsy() + // it should delete tag + expect(nextState.hasIn(['Notebook', 'tagMap', action.payload.tagName])).toBeFalsy() + }) + + it('should move a tag and update tag attribute of its notes', () => { + const state = new DummyStorageMap() + .createNote('Notebook', 'test', { + tags: ['new', 'tag'] + }) + .createNote('Notebook', 'test2', { + tags: ['new', 'tag2'] + }) + .getState() + + const action = { + type: 'RENAME_TAG', + payload: { + storageName: 'Notebook', + tagName: 'new', + newTagName: 'newer' + } + } + + const nextState = storageMap(state, action) + // Notes should be untagged the target tag + expect(nextState.getIn(['Notebook', 'noteMap', 'test', 'tags']).has('new')).toBeFalsy() + expect(nextState.getIn(['Notebook', 'noteMap', 'test2', 'tags']).has('new')).toBeFalsy() + // Notes should have the renamed tag + expect(nextState.getIn(['Notebook', 'noteMap', 'test', 'tags']).has('newer')).toBeTruthy() + expect(nextState.getIn(['Notebook', 'noteMap', 'test2', 'tags']).has('newer')).toBeTruthy() + // Old tag should be deleted + expect(nextState.hasIn(['Notebook', 'tagMap', action.payload.tagName])).toBeFalsy() + // New Tag should be created + expect(nextState.hasIn(['Notebook', 'tagMap', action.payload.newTagName])).toBeTruthy() + }) +}) diff --git a/specs/mock/LocalStorage.js b/specs/__mocks__/LocalStorageMock.js similarity index 88% rename from specs/mock/LocalStorage.js rename to specs/__mocks__/LocalStorageMock.js index 6ba343c3e3..e95683ee45 100644 --- a/specs/mock/LocalStorage.js +++ b/specs/__mocks__/LocalStorageMock.js @@ -1,4 +1,4 @@ -class LocalStorage { +class LocalStorageMock { constructor () { this.data = {} @@ -28,4 +28,4 @@ class LocalStorage { } } -export default LocalStorage +export default LocalStorageMock diff --git a/specs/__mocks__/fileMock.js b/specs/__mocks__/fileMock.js new file mode 100644 index 0000000000..0e56c5b5f7 --- /dev/null +++ b/specs/__mocks__/fileMock.js @@ -0,0 +1 @@ +module.exports = 'test-file-stub' diff --git a/specs/__mocks__/styleMock.js b/specs/__mocks__/styleMock.js new file mode 100644 index 0000000000..4ba52ba2c8 --- /dev/null +++ b/specs/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/specs/lib/markdown.spec.js b/specs/lib/markdown.spec.js deleted file mode 100644 index 67a326ea88..0000000000 --- a/specs/lib/markdown.spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import markdown from 'lib/markdown' - -const rawString1 = ` -This is not a title - -# :smile: **This** is a title - -Preview line - -- [ ] task1 -- [x] task2 -- [ ] task3 - -` - -const rawString2 = ` -This should be title - -this should be a previewed content -` - -const rawString4 = ` -# Awesome Electron [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome) - -[](http://electron.atom.io) - -> Useful resources for creating apps with [Electron](http://electron.atom.io) -` - -const mathString = ` -L is lift force, -$$\\rho$$ is air density, -$$v$$ is true airspeed, -$$A$$ is the wing area, and -$$C_L$$ is the lift coefficient at the desired angle of attack - -$$$ -L = \\frac{1}{2} \\rho v^2 A C_L -$$$ -` - -export default t => { - let parsed1 = markdown.parse(rawString1) - t.equal(parsed1.data.title, '😄 This is a title\n') - t.equal(parsed1.data.preview, 'Preview line\n') - - let parsed2 = markdown.parse(rawString2) - t.equal(parsed2.data.title, 'This should be title\n') - t.equal(parsed2.data.preview, 'this should be a previewed content\n') - - let parsed3 = markdown.parse('') - t.equal(parsed3.data.title, '') - t.equal(parsed3.data.preview, '') - - let parsed4 = markdown.parse(rawString4) - t.equal(parsed4.data.title, 'Awesome Electron Awesome\n') - t.equal(parsed4.data.preview, 'Useful resources for creating apps with Electron\n') - - // let parsedMath = markdown.parse(mathString) - // console.log(parsedMath.toString()) -} diff --git a/specs/main/lib/data/createNote.spec.js b/specs/main/lib/data/createNote.spec.js deleted file mode 100644 index 0f0d0a9cfb..0000000000 --- a/specs/main/lib/data/createNote.spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import createNote from 'main/lib/dataAPI/createNote' -import DummyDB from 'specs/utils/DummyDB' -import { - NOTE_ID_PREFIX -} from 'main/lib/dataAPI/consts' - -const dbName = __filename -const db = new DummyDB(dbName) - -const note = { - meta: {}, - content: 'test', - tags: ['test_tag'], - folder: 'Notes', - createdAt: new Date(), - updatedAt: new Date() -} - -function fetchNote (noteId) { - return db.get(NOTE_ID_PREFIX + noteId) -} - -export default t => { - return createNote(dbName, note) - .then(res => { - t.equal(res.note.get('content'), 'test') - return res.id - }) - .then(fetchNote) - .then(res => { - t.ok(res != null) - t.equal(res.content, 'test') - t.equal(res.tags[0], 'test_tag') - }) -} - -export const after = t => { - return db.destory() -} diff --git a/specs/main/lib/data/deleteFolder.spec.js b/specs/main/lib/data/deleteFolder.spec.js deleted file mode 100644 index faf3a74b78..0000000000 --- a/specs/main/lib/data/deleteFolder.spec.js +++ /dev/null @@ -1,70 +0,0 @@ -import deleteFolder from 'main/lib/dataAPI/deleteFolder' -import DummyDB from 'specs/utils/DummyDB' -import { - FOLDER_ID_PREFIX, - NOTE_ID_PREFIX -} from 'main/lib/dataAPI/consts' - -const dbName = __filename -const db = new DummyDB(dbName) - -const folderName = 'Testt Folder' -const noteId = 'testnote' -const note = { - title: 'test', - content: 'test\ncontent', - tags: ['test_tag'], - folder: folderName -} - -function createDummyFolder () { - return db.createFolder(folderName) -} - -function createDummyNote () { - return db.createNote(noteId, note) -} - -function fetchFolder () { - return db - .get(FOLDER_ID_PREFIX + folderName) -} - -function fetchNote () { - return db - .get(NOTE_ID_PREFIX + noteId) -} - -export const before = t => { - return createDummyFolder() - .then(createDummyNote) -} - -export default t => { - return deleteFolder(dbName, folderName) - .then(folder => { - t.equal(folder.id, folderName) - }) - .then(fetchFolder) - .then(res => { - t.fail('The folder should not exist.') - }) - .catch(err => { - if (err.name !== 'not_found') { - throw err - } - }) - .then(fetchNote) - .then(res => { - t.fail('The note should not exist.') - }) - .catch(err => { - if (err.name !== 'not_found') { - throw err - } - }) -} - -export const after = t => { - return db.destory() -} diff --git a/specs/main/lib/data/loadAllStorages.spec.js b/specs/main/lib/data/loadAllStorages.spec.js deleted file mode 100644 index e3dd80cb02..0000000000 --- a/specs/main/lib/data/loadAllStorages.spec.js +++ /dev/null @@ -1,20 +0,0 @@ -import loadAllStorages from 'main/lib/dataAPI/loadAllStorages' -import LocalStorage from 'specs/mock/LocalStorage' - -const localStorage = new LocalStorage() -localStorage.setItem('storages', JSON.stringify([{name: 'Test Storage'}])) -loadAllStorages.__Rewire__('localStorage', localStorage) - -export default t => { - return loadAllStorages() - .then(storageMap => { - const storages = ['Notebook', 'Test Storage'] - storages.forEach(storageName => { - t.ok(storageMap.hasIn([storageName])) - t.ok(storageMap.hasIn([storageName, 'noteMap'])) - t.ok(storageMap.hasIn([storageName, 'folderMap'])) - t.ok(storageMap.hasIn([storageName, 'folderMap', 'Notes'])) - t.ok(storageMap.hasIn([storageName, 'tagMap'])) - }) - }) -} diff --git a/specs/main/lib/data/loadStorage.spec.js b/specs/main/lib/data/loadStorage.spec.js deleted file mode 100644 index c83fb4671e..0000000000 --- a/specs/main/lib/data/loadStorage.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -import loadStorage from 'main/lib/dataAPI/loadStorage' -import DummyDB from 'specs/utils/DummyDB' - -const dbName = __filename -const db = new DummyDB(dbName) - -const folderName = 'Test Folder' -const noteId = 'testnote' -const note = { - content: 'test\ncontent', - tags: ['test_tag'], - folder: folderName -} - -function createDummyFolder () { - return db.createFolder(folderName) -} - -function createDummyNote () { - return db.createNote(noteId, note) -} - -export const before = t => { - return createDummyFolder(folderName) - .then(createDummyNote) -} - -export default t => { - return loadStorage(dbName) - .then(storageMap => { - t.ok(storageMap.has('noteMap')) - t.ok(storageMap.has('folderMap')) - t.ok(storageMap.hasIn(['folderMap', folderName])) - t.ok(storageMap.hasIn(['folderMap', folderName, 'notes'])) - t.ok(storageMap.getIn(['folderMap', folderName, 'notes']).includes(noteId)) - t.ok(storageMap.hasIn(['tagMap'])) - t.ok(storageMap.hasIn(['tagMap', 'test_tag', 'notes'])) - t.equal(storageMap.getIn(['tagMap', 'test_tag', 'notes']).first(), noteId) - }) -} - -export const after = t => { - return db.destory() -} diff --git a/specs/main/lib/data/updateNote.spec.js b/specs/main/lib/data/updateNote.spec.js deleted file mode 100644 index a429266f6a..0000000000 --- a/specs/main/lib/data/updateNote.spec.js +++ /dev/null @@ -1,54 +0,0 @@ -import updateNote from 'main/lib/dataAPI/updateNote' -import DummyDB from 'specs/utils/DummyDB' -import { - NOTE_ID_PREFIX -} from 'main/lib/dataAPI/consts' - -const dbName = __filename -const db = new DummyDB(dbName) - -const noteId = 'testNote' -const note = { - content: 'test', - tags: ['test_tag'], - folder: 'Notes', - createdAt: new Date(), - updatedAt: new Date() -} -const newNote = { - meta: { - title: 'changed' - }, - content: 'changed', - tags: ['changed'] -} - -function createdDummyNote () { - return db.createNote(noteId, note) -} - -function fetchNote (noteId) { - return db.get(NOTE_ID_PREFIX + noteId) -} - -export const before = t => { - return createdDummyNote() -} - -export default t => { - return updateNote(dbName, noteId, newNote) - .then(res => { - t.equal(res.note.get('content'), 'changed') - return res.id - }) - .then(fetchNote) - .then(res => { - t.ok(res != null) - t.equal(res.content, 'changed') - t.equal(res.tags[0], 'changed') - }) -} - -export const after = t => { - return db.destory() -} diff --git a/specs/main/lib/data/upsertFolder.spec.js b/specs/main/lib/data/upsertFolder.spec.js deleted file mode 100644 index e02ccf9243..0000000000 --- a/specs/main/lib/data/upsertFolder.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -import upsertFolder from 'main/lib/dataAPI/upsertFolder' -import DummyDB from 'specs/utils/DummyDB' -import { - FOLDER_ID_PREFIX -} from 'main/lib/dataAPI/consts' - -const dbName = __filename -const db = new DummyDB(dbName) - -function fetchFolder () { - return db - .get(FOLDER_ID_PREFIX + 'Test Folder') -} - -export default t => { - return upsertFolder(dbName, 'Test Folder') - .then(folder => { - t.equal(folder.id, 'Test Folder') - }) - .then(fetchFolder) - .then(res => { - t.ok(res != null) - }) -} - -export const after = t => { - return db.destory() -} diff --git a/specs/utils/DummyDB.js b/specs/utils/DummyDB.js index aa49683d14..36dc1649b6 100644 --- a/specs/utils/DummyDB.js +++ b/specs/utils/DummyDB.js @@ -1,5 +1,6 @@ import { FOLDER_ID_PREFIX, + TAG_ID_PREFIX, NOTE_ID_PREFIX } from 'main/lib/dataAPI/consts' import PouchDB from 'lib/PouchDB' @@ -17,7 +18,7 @@ class DummyDB { createFolder (folderName, overrides) { return this.db .get(FOLDER_ID_PREFIX + folderName) - .catch((err) => { + .catch(err => { if (err.name === 'not_found') return {} throw err }) @@ -28,6 +29,20 @@ class DummyDB { }) } + createTag (tagName, overrides) { + return this.db + .get(TAG_ID_PREFIX + tagName) + .catch(err => { + if (err.name === 'not_found') return {} + throw err + }) + .then(doc => { + return this.db.put(Object.assign({}, doc, overrides, { + _id: TAG_ID_PREFIX + tagName + })) + }) + } + createNote (noteId, overrides) { overrides = Object.assign({ meta: {}, @@ -46,7 +61,7 @@ class DummyDB { return this.db .get(NOTE_ID_PREFIX + noteId) - .catch((err) => { + .catch(err => { if (err.name === 'not_found') return {} throw err }) @@ -61,6 +76,10 @@ class DummyDB { return this.db.get(...args) } + put (...args) { + return this.db.put(...args) + } + destroy (...args) { return this.db.destroy(...args) } diff --git a/specs/utils/DummyStorageMap.js b/specs/utils/DummyStorageMap.js new file mode 100644 index 0000000000..4a4d4340b4 --- /dev/null +++ b/specs/utils/DummyStorageMap.js @@ -0,0 +1,81 @@ +import Immutable, { OrderedMap, Set } from 'immutable' + +function reviver (k, v) { + var isIndexed = Immutable.Iterable.isIndexed(v) + return isIndexed ? v.toSet() : v.toMap() +} + +function fromJS (obj) { + return Immutable.fromJS(obj, reviver) +} + +const defaultNote = { + content: 'test', + folder: 'Notes', + tags: [], + meta: { + title: 'test', + preview: '' + } +} + +export default class DummyStorageMap { + constructor () { + this.state = OrderedMap(fromJS({ + Notebook: { + noteMap: {}, + folderMap: { + Note: { + notes: [] + } + }, + tagMap: {} + } + })) + } + + getState () { + return this.state + } + + createNote (storageName, noteId, note, preventSideEffect = false) { + // Add a note + const newNote = fromJS(Object.assign({}, defaultNote, note)) + this.state = this.state.setIn([storageName, 'noteMap', noteId], newNote) + + if (!preventSideEffect) { + // Update its folder + this.state = this.state.updateIn([storageName, 'folderMap', newNote.get('folder'), 'notes'], noteSet => { + if (noteSet == null) return new Set([noteId]) + return noteSet.add(noteId) + }) + + // Update its tags + newNote.get('tags').forEach(tagName => { + this.state = this.state.updateIn([storageName, 'tagMap', tagName, 'notes'], noteSet => { + if (noteSet == null) return new Set([noteId]) + return noteSet.add(noteId) + }) + }) + } + + return this + } + + createTag (storageName, tagName) { + this.state = this.state.updateIn([storageName, 'tagMap', tagName], tag => { + if (tag == null) return fromJS({notes: []}) + return tag + }) + + return this + } + + createFolder (storageName, folderName) { + this.state = this.state.updateIn([storageName, 'folderMap', folderName], folder => { + if (folder == null) return fromJS({notes: []}) + return folder + }) + return this + } +} diff --git a/specs/utils/setup.js b/specs/utils/setup.js new file mode 100644 index 0000000000..7f5b2004df --- /dev/null +++ b/specs/utils/setup.js @@ -0,0 +1,5 @@ +import LocalStorageMock from 'specs/__mocks__/LocalStorageMock' + +Object.defineProperty(window, 'localStorage', { + value: new LocalStorageMock() +}) diff --git a/src/components/CodeEditor.js b/src/components/CodeEditor.js index c8bd6cd5c2..7af14c5334 100644 --- a/src/components/CodeEditor.js +++ b/src/components/CodeEditor.js @@ -194,14 +194,14 @@ class CodeEditor extends React.Component { checkTaskItem (line) { const splitted = this.codemirror.getValue().split('\n') - const match = /- \[(x| )\]/.exec(splitted[line - 1]) + const match = /- \[(x| )]/.exec(splitted[line - 1]) if (!match) return if (match[1] === 'x') { - splitted[line - 1] = splitted[line - 1].replace(/- \[x\]/, '- [ ]') + splitted[line - 1] = splitted[line - 1].replace(/- \[x]/, '- [ ]') } else { - splitted[line - 1] = splitted[line - 1].replace(/- \[ \]/, '- [x]') + splitted[line - 1] = splitted[line - 1].replace(/- \[ ]/, '- [x]') } this.codemirror.setValue(splitted.join('\n')) diff --git a/src/components/MarkdownPreview.js b/src/components/MarkdownPreview.js index 47fc27fff1..06797a3ac3 100644 --- a/src/components/MarkdownPreview.js +++ b/src/components/MarkdownPreview.js @@ -56,6 +56,10 @@ class MarkdownPreview extends React.Component { text-align: center; } + div.katex { + margin-bottom: 16px; + } + .katex .frac-line { top: 0.9em; position: relative; diff --git a/src/components/Octicon.js b/src/components/Octicon.js index 839d71bf6e..d08b828077 100644 --- a/src/components/Octicon.js +++ b/src/components/Octicon.js @@ -21,6 +21,7 @@ const Root = styled.svg` width: 1em; height: 1em; vertical-align: middle; + fill: currentColor; ${p => p.pulse ? pulseStyle : ''} ` diff --git a/src/lib/consts.js b/src/lib/consts.js index de0f7aaf18..d50cda9adf 100644 --- a/src/lib/consts.js +++ b/src/lib/consts.js @@ -20,18 +20,21 @@ export const DEFAULT_CONFIG = new Map({ editorFontSize: 14, editorFontFamily: 'Consolas, "Liberation Mono", Menlo, Courier', - editorTheme: 'default', + editorTheme: 'dracula', // space, tab editorIndentStyle: 'space', editorIndentSize: 2, previewFontSize: 14, previewFontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial', - previewCodeBlockTheme: 'default', + previewCodeBlockTheme: 'dracula', previewCodeBlockFontFamily: 'Consolas, "Liberation Mono", Menlo, Courier' }) export const DEFAULT_STATUS = Map({ + navHidden: false, + // folders, tags + navTab: 'folders', navWidth: 150, noteListWidth: 200, // NORMAL, COMPACT @@ -54,7 +57,8 @@ export const DEFAULT_KEYMAP = Immutable.fromJS({ Right: 'list:focus', Up: 'nav:up', Down: 'nav:down', - D: 'nav:delete' + D: 'nav:delete', + Tab: 'nav:toggle-tab' }, list: { Enter: 'detail:focus', diff --git a/src/lib/metaMapper.js b/src/lib/metaMapper.js index 537c0801e4..9be19a51b8 100644 --- a/src/lib/metaMapper.js +++ b/src/lib/metaMapper.js @@ -9,7 +9,7 @@ function stripNode (node) { return '' } - return parser.process(parser.stringify(JSON.parse(JSON.stringify(node)))).toString() + return parser.process(parser.stringify(JSON.parse(JSON.stringify(node)))).toString().trim() } /** diff --git a/src/lib/themes.js b/src/lib/themes.js index 896e3af9d0..99abd9af9e 100644 --- a/src/lib/themes.js +++ b/src/lib/themes.js @@ -55,9 +55,6 @@ const defaultTheme = { opacity: 0.5; cursor: not-allowed; } - .Octicon { - fill: ${defaultUIColor}; - } ` } diff --git a/src/main/Main.js b/src/main/Main.js index fcd7c24baa..ec53aad3bb 100644 --- a/src/main/Main.js +++ b/src/main/Main.js @@ -9,6 +9,10 @@ import { NAV_MIN_WIDTH } from 'lib/consts' import ipc from './lib/ipc' import NoteList from './NoteList' +function hideLoadingScreen () { + document.body.removeChild(document.getElementById('loading')) +} + const { remote } = require('electron') const Root = styled.div` @@ -108,13 +112,16 @@ class Main extends React.Component { dataAPI.loadAllStorages() .then(storageMap => { - dispatch({ - type: 'LOAD_ALL_STORAGES', - payload: { - storageMap - } + return dispatch(disaptch => { + dispatch({ + type: 'LOAD_ALL_STORAGES', + payload: { + storageMap + } + }) }) }) + .then(hideLoadingScreen) } componentWillUnmount () { @@ -166,19 +173,23 @@ class Main extends React.Component { /> -