1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

Merge pull request #100 from BoostIO/data-api-refactor

Data api refactoring
This commit is contained in:
Dick Choi
2016-09-08 16:50:43 +09:00
committed by GitHub
52 changed files with 2575 additions and 1249 deletions

View File

@@ -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)}
>
<div styleName='border'/>

View File

@@ -64,7 +64,7 @@ class NoteList extends React.Component {
return (
<NoteItem
note={note}
key={`${note.storage}-${note.folder}-${note.key}`}
key={`${note.storage}-${note.key}`}
storage={storage}
folder={folder}
isActive={index === _index}

87
browser/lib/Mutable.js Normal file
View File

@@ -0,0 +1,87 @@
class MutableMap {
constructor (iterable) {
this._map = new Map(iterable)
}
get (...args) {
return this._map.get(...args)
}
set (...args) {
return this._map.set(...args)
}
delete (...args) {
return this._map.delete(...args)
}
has (...args) {
return this._map.has(...args)
}
clear (...args) {
return this._map.clear(...args)
}
forEach (...args) {
return this._map.forEach(...args)
}
[Symbol.iterator] () {
return this._map[Symbol.iterator]()
}
map (cb) {
let result = []
for (let [key, value] of this._map) {
result.push(cb(value, key))
}
return result
}
}
class MutableSet {
constructor (iterable) {
if (iterable instanceof MutableSet) {
this._set = new Set(iterable._set)
} else {
this._set = new Set(iterable)
}
}
add (...args) {
return this._set.add(...args)
}
delete (...args) {
return this._set.delete(...args)
}
forEach (...args) {
return this._set.forEach(...args)
}
[Symbol.iterator] () {
return this._set[Symbol.iterator]()
}
map (cb) {
let result = []
this._set.forEach(function (value, key) {
result.push(cb(value, key))
})
return result
}
toJS () {
return Array.from(this._set)
}
}
const Mutable = {
Map: MutableMap,
Set: MutableSet
}
module.exports = Mutable

View File

@@ -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')
}

View File

@@ -184,12 +184,12 @@ class FolderSelect extends React.Component {
}
render () {
let { className, storages, value } = this.props
let { className, data, value } = this.props
let splitted = value.split('-')
let storageKey = splitted.shift()
let folderKey = splitted.shift()
let options = []
storages.forEach((storage, index) => {
data.storageMap.forEach((storage, index) => {
storage.folders.forEach((folder) => {
options.push({
storage: storage,

View File

@@ -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
@@ -35,9 +33,9 @@ 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
note: Object.assign({}, nextProps.note)
}, () => {
this.refs.content.reload()
this.refs.tags.reset()
@@ -45,6 +43,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 +93,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 +120,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 +129,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({
@@ -175,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) {
@@ -210,7 +214,7 @@ class MarkdownNoteDetail extends React.Component {
}
render () {
let { storages, config } = this.props
let { data, config } = this.props
let { note } = this.state
return (
@@ -218,69 +222,50 @@ class MarkdownNoteDetail extends React.Component {
style={this.props.style}
styleName='root'
>
{this.state.isDeleting
? <div styleName='info'>
<div styleName='info-delete'
tabIndex='-1'
onKeyDown={(e) => this.handleDeleteKeyDown(e)}
>
<span styleName='info-delete-message'>
Are you sure to delete this note?
</span>
<button styleName='info-delete-confirmButton'
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
ref='deleteConfirmButton'
>Confirm</button>
<button styleName='info-delete-cancelButton'
onClick={(e) => this.handleDeleteCancelButtonClick(e)}
>Cancel</button>
</div>
</div>
: <div styleName='info'>
<div styleName='info-left'>
<div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
storages={storages}
onChange={(e) => this.handleFolderChange(e)}
/>
</div>
<div styleName='info-left-bottom'>
<TagSelect
styleName='info-left-bottom-tagSelect'
ref='tags'
value={this.state.note.tags}
onChange={(e) => this.handleChange(e)}
/>
</div>
</div>
<div styleName='info-right'>
<StarButton styleName='info-right-button'
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
<div styleName='info'>
<div styleName='info-left'>
<div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
data={data}
onChange={(e) => this.handleFolderChange(e)}
/>
</div>
<div styleName='info-left-bottom'>
<TagSelect
styleName='info-left-bottom-tagSelect'
ref='tags'
value={this.state.note.tags}
onChange={(e) => this.handleChange(e)}
/>
<button styleName='info-right-button'
onClick={(e) => this.handleShareButtonClick(e)}
disabled
>
<i className='fa fa-share-alt fa-fw'/>
<span styleName='info-right-button-tooltip'
style={{right: 20}}
>Share Note</span>
</button>
<button styleName='info-right-button'
onClick={(e) => this.handleContextButtonClick(e)}
>
<i className='fa fa-ellipsis-v'/>
<span styleName='info-right-button-tooltip'
style={{right: 5}}
>More Options</span>
</button>
</div>
</div>
}
<div styleName='info-right'>
<StarButton styleName='info-right-button'
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
<button styleName='info-right-button'
onClick={(e) => this.handleShareButtonClick(e)}
disabled
>
<i className='fa fa-share-alt fa-fw'/>
<span styleName='info-right-button-tooltip'
style={{right: 20}}
>Share Note</span>
</button>
<button styleName='info-right-button'
onClick={(e) => this.handleContextButtonClick(e)}
>
<i className='fa fa-ellipsis-v'/>
<span styleName='info-right-button-tooltip'
style={{right: 5}}
>More Options</span>
</button>
</div>
</div>
<div styleName='body'>
<MarkdownEditor
ref='content'

View File

@@ -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

View File

@@ -24,21 +24,20 @@ function detectModeByFilename (filename) {
const electron = require('electron')
const { remote } = electron
const Menu = remote.Menu
const MenuItem = remote.MenuItem
const { Menu, MenuItem, dialog } = remote
class SnippetNoteDetail extends React.Component {
constructor (props) {
super(props)
this.state = {
isMovingNote: false,
snippetIndex: 0,
note: Object.assign({
description: ''
}, props.note, {
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
}),
isDeleting: false
})
}
}
@@ -48,6 +47,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, {
@@ -55,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) => {
@@ -67,6 +66,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 +116,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 +143,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 +152,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({
@@ -198,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) {
@@ -321,7 +327,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)
@@ -410,68 +416,49 @@ class SnippetNoteDetail extends React.Component {
style={this.props.style}
styleName='root'
>
{this.state.isDeleting
? <div styleName='info'>
<div styleName='info-delete'
tabIndex='-1'
onKeyDown={(e) => this.handleDeleteKeyDown(e)}
>
<span styleName='info-delete-message'>
Are you sure to delete this note?
</span>
<button styleName='info-delete-confirmButton'
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
ref='deleteConfirmButton'
>Confirm</button>
<button styleName='info-delete-cancelButton'
onClick={(e) => this.handleDeleteCancelButtonClick(e)}
>Cancel</button>
</div>
</div>
: <div styleName='info'>
<div styleName='info-left'>
<div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
storages={storages}
onChange={(e) => this.handleFolderChange(e)}
/>
</div>
<div styleName='info-left-bottom'>
<TagSelect
styleName='info-left-bottom-tagSelect'
ref='tags'
value={this.state.note.tags}
onChange={(e) => this.handleChange(e)}
/>
</div>
</div>
<div styleName='info-right'>
<StarButton styleName='info-right-button'
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
<div styleName='info'>
<div styleName='info-left'>
<div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
data={data}
onChange={(e) => this.handleFolderChange(e)}
/>
</div>
<div styleName='info-left-bottom'>
<TagSelect
styleName='info-left-bottom-tagSelect'
ref='tags'
value={this.state.note.tags}
onChange={(e) => this.handleChange(e)}
/>
<button styleName='info-right-button'
onClick={(e) => this.handleShareButtonClick(e)}
disabled
>
<i className='fa fa-share-alt fa-fw'/>
<span styleName='info-right-button-tooltip'
style={{right: 20}}
>Share Note</span>
</button>
<button styleName='info-right-button'
onClick={(e) => this.handleContextButtonClick(e)}
>
<i className='fa fa-ellipsis-v'/>
<span styleName='info-right-button-tooltip'
style={{right: 5}}
>More Options</span>
</button>
</div>
</div>
}
<div styleName='info-right'>
<StarButton styleName='info-right-button'
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
<button styleName='info-right-button'
onClick={(e) => this.handleShareButtonClick(e)}
disabled
>
<i className='fa fa-share-alt fa-fw'/>
<span styleName='info-right-button-tooltip'
style={{right: 20}}
>Share Note</span>
</button>
<button styleName='info-right-button'
onClick={(e) => this.handleContextButtonClick(e)}
>
<i className='fa fa-ellipsis-v'/>
<span styleName='info-right-button-tooltip'
style={{right: 5}}
>More Options</span>
</button>
</div>
</div>
<div styleName='body'>
<div styleName='body-description'>

View File

@@ -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

View File

@@ -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
}),

View File

@@ -101,7 +101,7 @@ class Main extends React.Component {
<SideNav
{..._.pick(this.props, [
'dispatch',
'storages',
'data',
'config',
'location'
])}
@@ -112,9 +112,8 @@ class Main extends React.Component {
<TopBar style={{width: this.state.listWidth}}
{..._.pick(this.props, [
'dispatch',
'storages',
'config',
'notes',
'data',
'params',
'location'
])}
@@ -122,8 +121,7 @@ class Main extends React.Component {
<NoteList style={{width: this.state.listWidth}}
{..._.pick(this.props, [
'dispatch',
'storages',
'notes',
'data',
'config',
'params',
'location'
@@ -140,8 +138,7 @@ class Main extends React.Component {
style={{left: this.state.listWidth + 1}}
{..._.pick(this.props, [
'dispatch',
'storages',
'notes',
'data',
'config',
'params',
'location'
@@ -159,7 +156,7 @@ class Main extends React.Component {
Main.propTypes = {
dispatch: PropTypes.func,
repositories: PropTypes.array
data: PropTypes.shape({}).isRequired
}
export default connect((x) => x)(CSSModules(Main, styles))

View File

@@ -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
@@ -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')
@@ -153,30 +153,36 @@ 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.storageNoteMap
.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 != null
? folderNoteKeyList
.map((uniqueKey) => data.noteMap.get(uniqueKey))
: []
}
handleNoteClick (uniqueKey) {
@@ -194,13 +200,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 +219,14 @@ class NoteList extends React.Component {
)
})
: []
let isActive = location.query.key === note.uniqueKey
let isActive = location.query.key === note.storage + '-' + note.key
return (
<div styleName={isActive
? 'item--active'
: 'item'
}
key={note.uniqueKey}
onClick={(e) => this.handleNoteClick(note.uniqueKey)(e)}
key={note.storage + '-' + note.key}
onClick={(e) => this.handleNoteClick(note.storage + '-' + note.key)(e)}
>
<div styleName='item-border'/>
<div styleName='item-info'>

View File

@@ -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 <StorageItem
key={storage.key}
storage={storage}

View File

@@ -33,9 +33,16 @@ class TopBar extends React.Component {
}
handleNewPostButtonClick (e) {
let { storages, params, dispatch, location } = this.props
let storage = _.find(storages, {key: params.storageKey})
if (storage == null) storage = storages[0]
let { data, params, dispatch, location } = this.props
let storage = data.storageMap.get(params.storageKey)
// Find first storage
if (storage == null) {
for (let kv of data.storageMap) {
storage = kv[1]
break
}
}
if (storage == null) throw new Error('No storage to create a note')
let folder = _.find(storage.folders, {key: params.folderKey})
if (folder == null) folder = storage.folders[0]

View File

@@ -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,31 +38,10 @@ 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(function saveMetadataToLocalStorage () {
.then(resolveStorageData)
.then(function saveMetadataToLocalStorage (resolvedStorage) {
newStorage = resolvedStorage
rawStorages.push({
key: newStorage.key,
type: newStorage.type,
@@ -75,36 +50,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,

View File

@@ -0,0 +1,63 @@
const _ = require('lodash')
const keygen = require('browser/lib/keygen')
const path = require('path')
const resolveStorageData = require('./resolveStorageData')
const CSON = require('season')
/**
* @param {String} storageKey
* @param {Object} input
* ```
* {
* color: String,
* name: String
* }
* ```
*
* @return {Object}
* ```
* {
* storage: Object
* }
* ```
*/
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)
}
return resolveStorageData(targetStorage)
.then(function createFolder (storage) {
let key = keygen()
while (storage.folders.some((folder) => folder.key === key)) {
key = keygen()
}
let newFolder = {
key,
color: input.color,
name: input.name
}
storage.folders.push(newFolder)
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
return {
storage
}
})
}
module.exports = createFolder

View File

@@ -0,0 +1,84 @@
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'))
key = keygen()
} 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

View File

@@ -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,
* folderKey: String
* }
* ```
*/
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,
folderKey
}
})
}
module.exports = deleteFolder

View File

@@ -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

View File

@@ -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

View File

@@ -1,8 +1,21 @@
'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
* ```
* {
* storages: [...],
* notes: [...]
* }
* ```
*
* This method deals with 3 patterns.
* 1. v1
* 2. legacy
* 3. empty directory
*/
function init () {
let fetchStorages = function () {
let rawStorages
@@ -10,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)
}

View File

@@ -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

View File

@@ -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 migrateFromV5Storage (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 = migrateFromV5Storage

View File

@@ -0,0 +1,90 @@
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
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 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

View File

@@ -0,0 +1,30 @@
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({
storageKey: key
})
}
module.exports = removeStorage

View File

@@ -0,0 +1,29 @@
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 cachedStorageList
try {
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')
targetStorage.name = name
localStorage.setItem('storages', JSON.stringify(cachedStorageList))
return Promise.resolve(targetStorage)
}
module.exports = renameStorage

View File

@@ -0,0 +1,39 @@
const _ = require('lodash')
const path = require('path')
const CSON = require('season')
const migrateFromV6Storage = require('./migrateFromV6Storage')
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 migrateFromV6Storage(storage.path)
.then(() => storage)
}
module.exports = resolveStorageData

View File

@@ -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

View File

@@ -0,0 +1,56 @@
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})
if (targetFolder == null) throw new Error('Target folder doesn\'t exist.')
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

View File

@@ -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

View File

@@ -24,23 +24,26 @@ class NewNoteModal extends React.Component {
handleMarkdownNoteButtonClick (e) {
let { storage, folder, dispatch, location } = this.props
dataApi
.createMarkdownNote(storage, folder, {
.createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
content: ''
})
.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()
})
}
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()

View File

@@ -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'
@@ -55,10 +54,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 +102,22 @@ class UnstyledFolderItem extends React.Component {
<button styleName='folderList-item-left-colorButton' style={{color: this.state.folder.color}}
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}
>
{ this.state.folder.showColumnPicker ?
<div style={ popover }>
<div style={ cover }
onClick={ () => this.handleColorPickerClose() } />
{this.state.folder.showColumnPicker
? <div style={popover}>
<div style={cover}
onClick={() => this.handleColorPickerClose()}
/>
<div style={pickerStyle}>
<SketchPicker
ref="colorPicker"
ref='colorPicker'
color={this.state.folder.color}
onChange={ (color) => this.handleColorChange(color) }
onChangeComplete={ (color) => this.handleColorChange(color) } />
onChange={(color) => this.handleColorChange(color)}
onChangeComplete={(color) => this.handleColorChange(color)}
/>
</div>
</div>
: null }
: null
}
<i className='fa fa-square'/>
</button>
<input styleName='folderList-item-left-nameInput'
@@ -143,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
})
})
}
@@ -254,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) => {
@@ -276,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
})
}

View File

@@ -51,14 +51,13 @@ 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 <StorageItem
key={storage.key}
storage={storage}
test={true}
hostBoundingBox={boundingBox}
/>
})

View File

@@ -33,8 +33,8 @@ class Preferences extends React.Component {
}
renderContent () {
const { boundingBox } = this.state;
let { dispatch, config, storages } = this.props
const { boundingBox } = this.state
let { dispatch, config, data } = this.props
switch (this.state.currentTab) {
case 'INFO':
@@ -51,7 +51,7 @@ class Preferences extends React.Component {
return (
<StoragesTab
dispatch={dispatch}
storages={storages}
data={data}
boundingBox={boundingBox}
/>
)
@@ -66,7 +66,7 @@ class Preferences extends React.Component {
getContentBoundingBox () {
const node = ReactDOM.findDOMNode(this.refs.content)
return node.getBoundingClientRect();
return node.getBoundingClientRect()
}
render () {

View File

@@ -1,95 +1,449 @@
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(),
storageNoteMap: 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.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 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.storageNoteMap = new Map(state.storageNoteMap)
let storageNoteSet = state.storageNoteMap.get(note.storage)
storageNoteSet = new Set(storageNoteSet)
storageNoteSet.add(uniqueKey)
state.storageNoteMap.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 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
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.storageNoteMap = new Map(state.storageNoteMap)
let noteSet = state.storageNoteMap.get(originNote.storage)
noteSet = new Set(noteSet)
noteSet.delete(originKey)
state.storageNoteMap.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.storageNoteMap = new Map(state.storageNoteMap)
let noteSet = state.storageNoteMap.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
let uniqueKey = action.storageKey + '-' + action.noteKey
let targetNote = state.noteMap.get(uniqueKey)
state = Object.assign({}, state)
// From storageNoteMap
state.storageNoteMap = new Map(state.storageNoteMap)
let noteSet = state.storageNoteMap.get(targetNote.storage)
noteSet = new Set(noteSet)
noteSet.delete(uniqueKey)
state.storageNoteMap.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
}
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)
state.storageNoteMap.set(action.storage.key, storageNoteSet)
noteSet.forEach(function handleNoteKey (noteKey) {
// Get note from noteMap
let note = state.noteMap.get(noteKey)
if (note != null) {
state.noteMap.delete(noteKey)
// From storageSet
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
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)
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
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
}
@@ -116,8 +470,7 @@ function config (state = defaultConfig, action) {
}
let reducer = combineReducers({
storages,
notes,
data,
config,
routing: routerReducer
})

View File

@@ -48,6 +48,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",
@@ -76,6 +77,7 @@
"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",

View File

@@ -8,86 +8,65 @@ 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')
const CSON = require('season')
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 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)
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)
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)
// 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)
})
})
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)
})

View File

@@ -0,0 +1,45 @@
const test = require('ava')
const createFolder = require('browser/main/lib/dataApi/createFolder')
global.document = require('jsdom').jsdom('<body></body>')
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/create-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 storageKey = t.context.storage.cache.key
const input = {
name: 'created',
color: '#ff5555'
}
return Promise.resolve()
.then(function doTest () {
return createFolder(storageKey, 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)
})

View File

@@ -0,0 +1,89 @@
const test = require('ava')
const createNote = require('browser/main/lib/dataApi/createNote')
global.document = require('jsdom').jsdom('<body></body>')
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/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 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()
return Promise.resolve()
.then(function doTest () {
return Promise.all([
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)
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)
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)
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)
})

View File

@@ -0,0 +1,45 @@
const test = require('ava')
const deleteFolder = require('browser/main/lib/dataApi/deleteFolder')
global.document = require('jsdom').jsdom('<body></body>')
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('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(storageKey, 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)
})

View File

@@ -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('<body></body>')
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('Delete 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)
})

View File

@@ -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')
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 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
}
}
const dummyFolderKey = 'fc6ba88e8ecf'
localStorage.setItem('storages', JSON.stringify([t.context.v1StorageData.cache, t.context.legacyStorageData.cache, t.context.emptyStorageData.cache]))
})
test.serial('Initialize All Storages', (t) => {
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)
})

View File

@@ -0,0 +1,64 @@
const test = require('ava')
const migrateFromV6Storage = require('browser/main/lib/dataApi/migrateFromV6Storage')
global.document = require('jsdom').jsdom('<body></body>')
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(), 'test/migrate-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]))
})
test.serial('Migrate legacy storage into v1 storage', (t) => {
return Promise.resolve()
.then(function test () {
return migrateFromV6Storage(dummyStoragePath)
})
.then(function assert (data) {
// Check the result. It must be true if succeed.
t.true(data)
// Check all notes migrated.
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))
})
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)
})

66
tests/dataApi/moveNote.js Normal file
View File

@@ -0,0 +1,66 @@
const test = require('ava')
const moveNote = require('browser/main/lib/dataApi/moveNote')
global.document = require('jsdom').jsdom('<body></body>')
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.key + '.cson'))
t.is(jsonData1.folder, folderKey1)
t.is(jsonData1.title, note1.title)
let jsonData2 = CSON.readFileSync(path.join(storagePath2, 'notes', data2.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)
})

View File

@@ -0,0 +1,36 @@
const test = require('ava')
const removeStorage = require('browser/main/lib/dataApi/removeStorage')
global.document = require('jsdom').jsdom('<body></body>')
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 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 storageKey = t.context.storage.cache.key
return Promise.resolve()
.then(function doTest () {
return removeStorage(storageKey)
})
.then(function assert (data) {
t.is(JSON.parse(localStorage.getItem('storages')).length, 0)
})
})
test.after(function after () {
localStorage.clear()
sander.rimrafSync(storagePath)
})

View File

@@ -0,0 +1,38 @@
const test = require('ava')
const renameStorage = require('browser/main/lib/dataApi/renameStorage')
global.document = require('jsdom').jsdom('<body></body>')
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 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 storageKey = t.context.storage.cache.key
return Promise.resolve()
.then(function doTest () {
return renameStorage(storageKey, 'changed')
})
.then(function assert (data) {
let cachedStorageList = JSON.parse(localStorage.getItem('storages'))
t.true(_.find(cachedStorageList, {key: storageKey}).name === 'changed')
})
})
test.after(function after () {
localStorage.clear()
sander.rimrafSync(storagePath)
})

View File

@@ -0,0 +1,46 @@
const test = require('ava')
const updateFolder = require('browser/main/lib/dataApi/updateFolder')
global.document = require('jsdom').jsdom('<body></body>')
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('Update a folder', (t) => {
const storageKey = 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(storageKey, 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)
})

116
tests/dataApi/updateNote.js Normal file
View File

@@ -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('<body></body>')
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('Update 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)
})

View File

@@ -1,9 +0,0 @@
{
"folders": [
{
"key": "fc6ba88e8ecf",
"name": "test",
"color": "#FF5555"
}
]
}

View File

@@ -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"
}
]
}

179
tests/fixtures/TestDummy.js vendored Normal file
View File

@@ -0,0 +1,179 @@
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: []
* version: String(enum:'1.0')
* },
* 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 = 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 = []
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 = 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
for (var i = 0; i < noteCount; i++) {
var key = keygen(6)
while (folderNotes.some((note) => note.key === key)) {
key = keygen(6)
}
var noteData = dummyNote({
key,
folder: jsonData.folders[j].key
})
folderNotes.push(noteData)
}
notesData = notesData.concat(folderNotes)
CSON.writeFileSync(path.join(storagePath, jsonData.folders[j].key, 'data.json'), {notes: folderNotes.map((note) => _.omit(note, ['folder']))})
}
return {
json: jsonData,
cache: cacheData,
notes: notesData
}
}
module.exports = {
dummyFolder,
dummyBoostnoteJSONData,
dummyStorage,
dummyLegacyStorage
}

View File

@@ -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