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:
@@ -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'/>
|
||||
|
||||
@@ -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
87
browser/lib/Mutable.js
Normal 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
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}),
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
63
browser/main/lib/dataApi/createFolder.js
Normal file
63
browser/main/lib/dataApi/createFolder.js
Normal 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
|
||||
84
browser/main/lib/dataApi/createNote.js
Normal file
84
browser/main/lib/dataApi/createNote.js
Normal 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
|
||||
75
browser/main/lib/dataApi/deleteFolder.js
Normal file
75
browser/main/lib/dataApi/deleteFolder.js
Normal 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
|
||||
34
browser/main/lib/dataApi/deleteNote.js
Normal file
34
browser/main/lib/dataApi/deleteNote.js
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
110
browser/main/lib/dataApi/migrateFromV5Storage.js
Normal file
110
browser/main/lib/dataApi/migrateFromV5Storage.js
Normal 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
|
||||
85
browser/main/lib/dataApi/migrateFromV6Storage.js
Normal file
85
browser/main/lib/dataApi/migrateFromV6Storage.js
Normal 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
|
||||
|
||||
90
browser/main/lib/dataApi/moveNote.js
Normal file
90
browser/main/lib/dataApi/moveNote.js
Normal 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
|
||||
30
browser/main/lib/dataApi/removeStorage.js
Normal file
30
browser/main/lib/dataApi/removeStorage.js
Normal 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
|
||||
29
browser/main/lib/dataApi/renameStorage.js
Normal file
29
browser/main/lib/dataApi/renameStorage.js
Normal 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
|
||||
39
browser/main/lib/dataApi/resolveStorageData.js
Normal file
39
browser/main/lib/dataApi/resolveStorageData.js
Normal 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
|
||||
30
browser/main/lib/dataApi/resolveStorageNotes.js
Normal file
30
browser/main/lib/dataApi/resolveStorageNotes.js
Normal 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
|
||||
56
browser/main/lib/dataApi/updateFolder.js
Normal file
56
browser/main/lib/dataApi/updateFolder.js
Normal 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
|
||||
122
browser/main/lib/dataApi/updateNote.js
Normal file
122
browser/main/lib/dataApi/updateNote.js
Normal 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
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
})
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
45
tests/dataApi/createFolder.js
Normal file
45
tests/dataApi/createFolder.js
Normal 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)
|
||||
})
|
||||
89
tests/dataApi/createNote.js
Normal file
89
tests/dataApi/createNote.js
Normal 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)
|
||||
})
|
||||
45
tests/dataApi/deleteFolder.js
Normal file
45
tests/dataApi/deleteFolder.js
Normal 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)
|
||||
})
|
||||
62
tests/dataApi/deleteNote.js
Normal file
62
tests/dataApi/deleteNote.js
Normal 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)
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
64
tests/dataApi/migrateFromV6Storage.js
Normal file
64
tests/dataApi/migrateFromV6Storage.js
Normal 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
66
tests/dataApi/moveNote.js
Normal 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)
|
||||
})
|
||||
36
tests/dataApi/removeStorage.js
Normal file
36
tests/dataApi/removeStorage.js
Normal 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)
|
||||
})
|
||||
38
tests/dataApi/renameStorage.js
Normal file
38
tests/dataApi/renameStorage.js
Normal 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)
|
||||
})
|
||||
46
tests/dataApi/updateFolder.js
Normal file
46
tests/dataApi/updateFolder.js
Normal 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
116
tests/dataApi/updateNote.js
Normal 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)
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"key": "fc6ba88e8ecf",
|
||||
"name": "test",
|
||||
"color": "#FF5555"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
179
tests/fixtures/TestDummy.js
vendored
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user