mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-16 11:15:12 +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--active'
|
||||||
: 'root'
|
: 'root'
|
||||||
}
|
}
|
||||||
key={note.uniqueKey}
|
key={note.storage + '-' + note.key}
|
||||||
onClick={(e) => this.handleClick(e)}
|
onClick={(e) => this.handleClick(e)}
|
||||||
>
|
>
|
||||||
<div styleName='border'/>
|
<div styleName='border'/>
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class NoteList extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<NoteItem
|
<NoteItem
|
||||||
note={note}
|
note={note}
|
||||||
key={`${note.storage}-${note.folder}-${note.key}`}
|
key={`${note.storage}-${note.key}`}
|
||||||
storage={storage}
|
storage={storage}
|
||||||
folder={folder}
|
folder={folder}
|
||||||
isActive={index === _index}
|
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')
|
const _ = require('lodash')
|
||||||
|
|
||||||
module.exports = function (length) {
|
module.exports = function (length) {
|
||||||
if (!_.isFinite(length)) length = 6
|
if (!_.isFinite(length)) length = 10
|
||||||
return crypto.randomBytes(length).toString('hex')
|
return crypto.randomBytes(length).toString('hex')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,12 +184,12 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className, storages, value } = this.props
|
let { className, data, value } = this.props
|
||||||
let splitted = value.split('-')
|
let splitted = value.split('-')
|
||||||
let storageKey = splitted.shift()
|
let storageKey = splitted.shift()
|
||||||
let folderKey = splitted.shift()
|
let folderKey = splitted.shift()
|
||||||
let options = []
|
let options = []
|
||||||
storages.forEach((storage, index) => {
|
data.storageMap.forEach((storage, index) => {
|
||||||
storage.folders.forEach((folder) => {
|
storage.folders.forEach((folder) => {
|
||||||
options.push({
|
options.push({
|
||||||
storage: storage,
|
storage: storage,
|
||||||
|
|||||||
@@ -11,19 +11,17 @@ import ee from 'browser/main/lib/eventEmitter'
|
|||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const Menu = remote.Menu
|
const { Menu, MenuItem, dialog } = remote
|
||||||
const MenuItem = remote.MenuItem
|
|
||||||
|
|
||||||
class MarkdownNoteDetail extends React.Component {
|
class MarkdownNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isMovingNote: false,
|
||||||
note: Object.assign({
|
note: Object.assign({
|
||||||
title: '',
|
title: '',
|
||||||
content: '',
|
content: ''
|
||||||
isMovingNote: false,
|
|
||||||
isDeleting: false
|
|
||||||
}, props.note)
|
}, props.note)
|
||||||
}
|
}
|
||||||
this.dispatchTimer = null
|
this.dispatchTimer = null
|
||||||
@@ -35,9 +33,9 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
||||||
|
if (this.saveQueue != null) this.saveNow()
|
||||||
this.setState({
|
this.setState({
|
||||||
note: Object.assign({}, nextProps.note),
|
note: Object.assign({}, nextProps.note)
|
||||||
isDeleting: false
|
|
||||||
}, () => {
|
}, () => {
|
||||||
this.refs.content.reload()
|
this.refs.content.reload()
|
||||||
this.refs.tags.reset()
|
this.refs.tags.reset()
|
||||||
@@ -45,6 +43,10 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
if (this.saveQueue != null) this.saveNow()
|
||||||
|
}
|
||||||
|
|
||||||
findTitle (value) {
|
findTitle (value) {
|
||||||
let splitted = value.split('\n')
|
let splitted = value.split('\n')
|
||||||
let title = null
|
let title = null
|
||||||
@@ -91,15 +93,23 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
save () {
|
save () {
|
||||||
clearTimeout(this.saveQueue)
|
clearTimeout(this.saveQueue)
|
||||||
this.saveQueue = setTimeout(() => {
|
this.saveQueue = setTimeout(() => {
|
||||||
|
this.saveNow()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveNow () {
|
||||||
let { note, dispatch } = this.props
|
let { note, dispatch } = this.props
|
||||||
dispatch({
|
clearTimeout(this.saveQueue)
|
||||||
type: 'UPDATE_NOTE',
|
this.saveQueue = null
|
||||||
note: this.state.note
|
|
||||||
})
|
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.updateNote(note.storage, note.folder, note.key, this.state.note)
|
.updateNote(note.storage, note.key, this.state.note)
|
||||||
}, 1000)
|
.then((note) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderChange (e) {
|
handleFolderChange (e) {
|
||||||
@@ -110,7 +120,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
let newFolderKey = splitted.shift()
|
let newFolderKey = splitted.shift()
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.moveNote(note.storage, note.folder, note.key, newStorageKey, newFolderKey)
|
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||||
.then((newNote) => {
|
.then((newNote) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isMovingNote: true,
|
isMovingNote: true,
|
||||||
@@ -119,13 +129,13 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
let { dispatch, location } = this.props
|
let { dispatch, location } = this.props
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
note: note,
|
originNote: note,
|
||||||
newNote: newNote
|
note: newNote
|
||||||
})
|
})
|
||||||
hashHistory.replace({
|
hashHistory.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: newNote.uniqueKey
|
key: newNote.storage + '-' + newNote.key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -175,34 +185,28 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteMenuClick (e) {
|
handleDeleteMenuClick (e) {
|
||||||
this.setState({
|
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
isDeleting: true
|
type: 'warning',
|
||||||
}, () => {
|
message: 'Delete a note',
|
||||||
this.refs.deleteConfirmButton.focus()
|
detail: 'This work cannot be undone.',
|
||||||
|
buttons: ['Confirm', 'Cancel']
|
||||||
})
|
})
|
||||||
}
|
if (index === 0) {
|
||||||
|
|
||||||
handleDeleteConfirmButtonClick (e) {
|
|
||||||
let { note, dispatch } = this.props
|
let { note, dispatch } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.removeNote(note.storage, note.folder, note.key)
|
.deleteNote(note.storage, note.key)
|
||||||
.then(() => {
|
.then((data) => {
|
||||||
let dispatchHandler = () => {
|
let dispatchHandler = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'REMOVE_NOTE',
|
type: 'DELETE_NOTE',
|
||||||
note: note
|
storageKey: data.storageKey,
|
||||||
|
noteKey: data.noteKey
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ee.once('list:moved', dispatchHandler)
|
ee.once('list:moved', dispatchHandler)
|
||||||
ee.emit('list:next')
|
ee.emit('list:next')
|
||||||
ee.emit('list:focus')
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteCancelButtonClick (e) {
|
|
||||||
this.setState({
|
|
||||||
isDeleting: false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteKeyDown (e) {
|
handleDeleteKeyDown (e) {
|
||||||
@@ -210,7 +214,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { storages, config } = this.props
|
let { data, config } = this.props
|
||||||
let { note } = this.state
|
let { note } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -218,32 +222,13 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
styleName='root'
|
styleName='root'
|
||||||
>
|
>
|
||||||
{this.state.isDeleting
|
<div styleName='info'>
|
||||||
? <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'>
|
||||||
<div styleName='info-left-top'>
|
<div styleName='info-left-top'>
|
||||||
<FolderSelect styleName='info-left-top-folderSelect'
|
<FolderSelect styleName='info-left-top-folderSelect'
|
||||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
ref='folder'
|
ref='folder'
|
||||||
storages={storages}
|
data={data}
|
||||||
onChange={(e) => this.handleFolderChange(e)}
|
onChange={(e) => this.handleFolderChange(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -280,7 +265,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
<div styleName='body'>
|
<div styleName='body'>
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
ref='content'
|
ref='content'
|
||||||
|
|||||||
@@ -12,34 +12,6 @@ $info-height = 75px
|
|||||||
border-bottom $ui-border
|
border-bottom $ui-border
|
||||||
background-color $ui-backgroundColor
|
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
|
.info-left
|
||||||
float left
|
float left
|
||||||
padding 0 5px
|
padding 0 5px
|
||||||
|
|||||||
@@ -24,21 +24,20 @@ function detectModeByFilename (filename) {
|
|||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const Menu = remote.Menu
|
const { Menu, MenuItem, dialog } = remote
|
||||||
const MenuItem = remote.MenuItem
|
|
||||||
|
|
||||||
class SnippetNoteDetail extends React.Component {
|
class SnippetNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isMovingNote: false,
|
||||||
snippetIndex: 0,
|
snippetIndex: 0,
|
||||||
note: Object.assign({
|
note: Object.assign({
|
||||||
description: ''
|
description: ''
|
||||||
}, props.note, {
|
}, props.note, {
|
||||||
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
|
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
|
||||||
}),
|
})
|
||||||
isDeleting: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +47,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key) {
|
if (nextProps.note.key !== this.props.note.key) {
|
||||||
|
if (this.saveQueue != null) this.saveNow()
|
||||||
let nextNote = Object.assign({
|
let nextNote = Object.assign({
|
||||||
description: ''
|
description: ''
|
||||||
}, nextProps.note, {
|
}, nextProps.note, {
|
||||||
@@ -55,8 +55,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
snippetIndex: 0,
|
snippetIndex: 0,
|
||||||
note: nextNote,
|
note: nextNote
|
||||||
isDeleting: false
|
|
||||||
}, () => {
|
}, () => {
|
||||||
let { snippets } = this.state.note
|
let { snippets } = this.state.note
|
||||||
snippets.forEach((snippet, index) => {
|
snippets.forEach((snippet, index) => {
|
||||||
@@ -67,6 +66,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
if (this.saveQueue != null) this.saveNow()
|
||||||
|
}
|
||||||
|
|
||||||
findTitle (value) {
|
findTitle (value) {
|
||||||
let splitted = value.split('\n')
|
let splitted = value.split('\n')
|
||||||
let title = null
|
let title = null
|
||||||
@@ -113,15 +116,23 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
save () {
|
save () {
|
||||||
clearTimeout(this.saveQueue)
|
clearTimeout(this.saveQueue)
|
||||||
this.saveQueue = setTimeout(() => {
|
this.saveQueue = setTimeout(() => {
|
||||||
|
this.saveNow()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
saveNow () {
|
||||||
let { note, dispatch } = this.props
|
let { note, dispatch } = this.props
|
||||||
dispatch({
|
clearTimeout(this.saveQueue)
|
||||||
type: 'UPDATE_NOTE',
|
this.saveQueue = null
|
||||||
note: this.state.note
|
|
||||||
})
|
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.updateNote(note.storage, note.folder, note.key, this.state.note)
|
.updateNote(note.storage, note.key, this.state.note)
|
||||||
}, 1000)
|
.then((note) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderChange (e) {
|
handleFolderChange (e) {
|
||||||
@@ -132,7 +143,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
let newFolderKey = splitted.shift()
|
let newFolderKey = splitted.shift()
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.moveNote(note.storage, note.folder, note.key, newStorageKey, newFolderKey)
|
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||||
.then((newNote) => {
|
.then((newNote) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isMovingNote: true,
|
isMovingNote: true,
|
||||||
@@ -141,13 +152,13 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
let { dispatch, location } = this.props
|
let { dispatch, location } = this.props
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
note: note,
|
originNote: note,
|
||||||
newNote: newNote
|
note: newNote
|
||||||
})
|
})
|
||||||
hashHistory.replace({
|
hashHistory.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: newNote.uniqueKey
|
key: newNote.storage + '-' + newNote.key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -198,33 +209,28 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteMenuClick (e) {
|
handleDeleteMenuClick (e) {
|
||||||
this.setState({
|
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
isDeleting: true
|
type: 'warning',
|
||||||
}, () => {
|
message: 'Delete a note',
|
||||||
this.refs.deleteConfirmButton.focus()
|
detail: 'This work cannot be undone.',
|
||||||
|
buttons: ['Confirm', 'Cancel']
|
||||||
})
|
})
|
||||||
}
|
if (index === 0) {
|
||||||
|
|
||||||
handleDeleteConfirmButtonClick (e) {
|
|
||||||
let { note, dispatch } = this.props
|
let { note, dispatch } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.removeNote(note.storage, note.folder, note.key)
|
.deleteNote(note.storage, note.key)
|
||||||
.then(() => {
|
.then((data) => {
|
||||||
let dispatchHandler = () => {
|
let dispatchHandler = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'REMOVE_NOTE',
|
type: 'DELETE_NOTE',
|
||||||
note: note
|
storageKey: data.storageKey,
|
||||||
|
noteKey: data.noteKey
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ee.once('list:moved', dispatchHandler)
|
ee.once('list:moved', dispatchHandler)
|
||||||
ee.emit('list:next')
|
ee.emit('list:next')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteCancelButtonClick (e) {
|
|
||||||
this.setState({
|
|
||||||
isDeleting: false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTabPlusButtonClick (e) {
|
handleTabPlusButtonClick (e) {
|
||||||
@@ -321,7 +327,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { storages, config } = this.props
|
let { data, config } = this.props
|
||||||
let { note } = this.state
|
let { note } = this.state
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
@@ -410,31 +416,13 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
styleName='root'
|
styleName='root'
|
||||||
>
|
>
|
||||||
{this.state.isDeleting
|
<div styleName='info'>
|
||||||
? <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'>
|
||||||
<div styleName='info-left-top'>
|
<div styleName='info-left-top'>
|
||||||
<FolderSelect styleName='info-left-top-folderSelect'
|
<FolderSelect styleName='info-left-top-folderSelect'
|
||||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
ref='folder'
|
ref='folder'
|
||||||
storages={storages}
|
data={data}
|
||||||
onChange={(e) => this.handleFolderChange(e)}
|
onChange={(e) => this.handleFolderChange(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -471,7 +459,6 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
<div styleName='body'>
|
<div styleName='body'>
|
||||||
<div styleName='body-description'>
|
<div styleName='body-description'>
|
||||||
|
|||||||
@@ -12,34 +12,6 @@ $info-height = 75px
|
|||||||
border-bottom $ui-border
|
border-bottom $ui-border
|
||||||
background-color $ui-backgroundColor
|
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
|
.info-left
|
||||||
float left
|
float left
|
||||||
padding 0 5px
|
padding 0 5px
|
||||||
|
|||||||
@@ -31,19 +31,14 @@ class Detail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { location, notes, config } = this.props
|
let { location, data, config } = this.props
|
||||||
let note = null
|
let note = null
|
||||||
if (location.query.key != null) {
|
if (location.query.key != null) {
|
||||||
let splitted = location.query.key.split('-')
|
let splitted = location.query.key.split('-')
|
||||||
let storageKey = splitted.shift()
|
let storageKey = splitted.shift()
|
||||||
let folderKey = splitted.shift()
|
|
||||||
let noteKey = splitted.shift()
|
let noteKey = splitted.shift()
|
||||||
|
|
||||||
note = _.find(notes, {
|
note = data.noteMap.get(storageKey + '-' + noteKey)
|
||||||
storage: storageKey,
|
|
||||||
folder: folderKey,
|
|
||||||
key: noteKey
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
@@ -67,7 +62,7 @@ class Detail extends React.Component {
|
|||||||
ref='root'
|
ref='root'
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'storages',
|
'data',
|
||||||
'style',
|
'style',
|
||||||
'ignorePreviewPointerEvents',
|
'ignorePreviewPointerEvents',
|
||||||
'location'
|
'location'
|
||||||
@@ -83,7 +78,7 @@ class Detail extends React.Component {
|
|||||||
ref='root'
|
ref='root'
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'storages',
|
'data',
|
||||||
'style',
|
'style',
|
||||||
'ignorePreviewPointerEvents',
|
'ignorePreviewPointerEvents',
|
||||||
'location'
|
'location'
|
||||||
@@ -95,7 +90,6 @@ class Detail extends React.Component {
|
|||||||
|
|
||||||
Detail.propTypes = {
|
Detail.propTypes = {
|
||||||
dispatch: PropTypes.func,
|
dispatch: PropTypes.func,
|
||||||
storages: PropTypes.array,
|
|
||||||
style: PropTypes.shape({
|
style: PropTypes.shape({
|
||||||
left: PropTypes.number
|
left: PropTypes.number
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ class Main extends React.Component {
|
|||||||
<SideNav
|
<SideNav
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'storages',
|
'data',
|
||||||
'config',
|
'config',
|
||||||
'location'
|
'location'
|
||||||
])}
|
])}
|
||||||
@@ -112,9 +112,8 @@ class Main extends React.Component {
|
|||||||
<TopBar style={{width: this.state.listWidth}}
|
<TopBar style={{width: this.state.listWidth}}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'storages',
|
|
||||||
'config',
|
'config',
|
||||||
'notes',
|
'data',
|
||||||
'params',
|
'params',
|
||||||
'location'
|
'location'
|
||||||
])}
|
])}
|
||||||
@@ -122,8 +121,7 @@ class Main extends React.Component {
|
|||||||
<NoteList style={{width: this.state.listWidth}}
|
<NoteList style={{width: this.state.listWidth}}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'storages',
|
'data',
|
||||||
'notes',
|
|
||||||
'config',
|
'config',
|
||||||
'params',
|
'params',
|
||||||
'location'
|
'location'
|
||||||
@@ -140,8 +138,7 @@ class Main extends React.Component {
|
|||||||
style={{left: this.state.listWidth + 1}}
|
style={{left: this.state.listWidth + 1}}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'storages',
|
'data',
|
||||||
'notes',
|
|
||||||
'config',
|
'config',
|
||||||
'params',
|
'params',
|
||||||
'location'
|
'location'
|
||||||
@@ -159,7 +156,7 @@ class Main extends React.Component {
|
|||||||
|
|
||||||
Main.propTypes = {
|
Main.propTypes = {
|
||||||
dispatch: PropTypes.func,
|
dispatch: PropTypes.func,
|
||||||
repositories: PropTypes.array
|
data: PropTypes.shape({}).isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect((x) => x)(CSSModules(Main, styles))
|
export default connect((x) => x)(CSSModules(Main, styles))
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class NoteList extends React.Component {
|
|||||||
router.replace({
|
router.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: this.notes[0].uniqueKey
|
key: this.notes[0].storage + '-' + this.notes[0].key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -52,7 +52,7 @@ class NoteList extends React.Component {
|
|||||||
// Auto scroll
|
// Auto scroll
|
||||||
if (_.isString(location.query.key)) {
|
if (_.isString(location.query.key)) {
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
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) {
|
if (targetIndex > -1) {
|
||||||
let list = this.refs.root
|
let list = this.refs.root
|
||||||
@@ -78,7 +78,7 @@ class NoteList extends React.Component {
|
|||||||
let { location } = this.props
|
let { location } = this.props
|
||||||
|
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||||
return note.uniqueKey === location.query.key
|
return note.storage + '-' + note.key === location.query.key
|
||||||
})
|
})
|
||||||
|
|
||||||
if (targetIndex === 0) {
|
if (targetIndex === 0) {
|
||||||
@@ -90,7 +90,7 @@ class NoteList extends React.Component {
|
|||||||
router.push({
|
router.push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
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 { location } = this.props
|
||||||
|
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
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) {
|
if (targetIndex === this.notes.length - 1) {
|
||||||
@@ -117,7 +117,7 @@ class NoteList extends React.Component {
|
|||||||
router.push({
|
router.push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: this.notes[targetIndex].uniqueKey
|
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
ee.emit('list:moved')
|
ee.emit('list:moved')
|
||||||
@@ -153,30 +153,36 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getNotes () {
|
getNotes () {
|
||||||
let { storages, notes, params, location } = this.props
|
let { data, params, location } = this.props
|
||||||
|
|
||||||
if (location.pathname.match(/\/home/)) {
|
if (location.pathname.match(/\/home/)) {
|
||||||
return notes
|
return data.noteMap.map((note) => note)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.pathname.match(/\/starred/)) {
|
if (location.pathname.match(/\/starred/)) {
|
||||||
return notes
|
return data.starredSet.toJS()
|
||||||
.filter((note) => note.isStarred)
|
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
let storageKey = params.storageKey
|
let storageKey = params.storageKey
|
||||||
let folderKey = params.folderKey
|
let folderKey = params.folderKey
|
||||||
let storage = _.find(storages, {key: storageKey})
|
let storage = data.storageMap.get(storageKey)
|
||||||
if (storage == null) return []
|
if (storage == null) return []
|
||||||
|
|
||||||
let folder = _.find(storage.folders, {key: folderKey})
|
let folder = _.find(storage.folders, {key: folderKey})
|
||||||
if (folder == null) {
|
if (folder == null) {
|
||||||
return notes
|
return data.storageNoteMap
|
||||||
.filter((note) => note.storage === storageKey)
|
.get(storage.key)
|
||||||
|
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
return notes
|
let folderNoteKeyList = data.folderNoteMap
|
||||||
.filter((note) => note.folder === folderKey)
|
.get(storage.key + '-' + folder.key)
|
||||||
|
|
||||||
|
return folderNoteKeyList != null
|
||||||
|
? folderNoteKeyList
|
||||||
|
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
|
: []
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNoteClick (uniqueKey) {
|
handleNoteClick (uniqueKey) {
|
||||||
@@ -194,13 +200,14 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { location, storages, notes } = this.props
|
let { location, data, notes } = this.props
|
||||||
this.notes = notes = this.getNotes()
|
this.notes = notes = this.getNotes()
|
||||||
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
||||||
|
|
||||||
let noteList = notes
|
let noteList = notes
|
||||||
.map((note) => {
|
.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 folder = _.find(storage.folders, {key: note.folder})
|
||||||
let tagElements = _.isArray(note.tags)
|
let tagElements = _.isArray(note.tags)
|
||||||
? note.tags.map((tag) => {
|
? 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 (
|
return (
|
||||||
<div styleName={isActive
|
<div styleName={isActive
|
||||||
? 'item--active'
|
? 'item--active'
|
||||||
: 'item'
|
: 'item'
|
||||||
}
|
}
|
||||||
key={note.uniqueKey}
|
key={note.storage + '-' + note.key}
|
||||||
onClick={(e) => this.handleNoteClick(note.uniqueKey)(e)}
|
onClick={(e) => this.handleNoteClick(note.storage + '-' + note.key)(e)}
|
||||||
>
|
>
|
||||||
<div styleName='item-border'/>
|
<div styleName='item-border'/>
|
||||||
<div styleName='item-info'>
|
<div styleName='item-info'>
|
||||||
|
|||||||
@@ -36,12 +36,13 @@ class SideNav extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { storages, location, config } = this.props
|
let { data, location, config } = this.props
|
||||||
|
|
||||||
let isFolded = config.isSideNavFolded
|
let isFolded = config.isSideNavFolded
|
||||||
let isHomeActive = location.pathname.match(/^\/home$/)
|
let isHomeActive = location.pathname.match(/^\/home$/)
|
||||||
let isStarredActive = location.pathname.match(/^\/starred$/)
|
let isStarredActive = location.pathname.match(/^\/starred$/)
|
||||||
let storageList = storages.map((storage) => {
|
|
||||||
|
let storageList = data.storageMap.map((storage, key) => {
|
||||||
return <StorageItem
|
return <StorageItem
|
||||||
key={storage.key}
|
key={storage.key}
|
||||||
storage={storage}
|
storage={storage}
|
||||||
|
|||||||
@@ -33,9 +33,16 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleNewPostButtonClick (e) {
|
handleNewPostButtonClick (e) {
|
||||||
let { storages, params, dispatch, location } = this.props
|
let { data, params, dispatch, location } = this.props
|
||||||
let storage = _.find(storages, {key: params.storageKey})
|
let storage = data.storageMap.get(params.storageKey)
|
||||||
if (storage == null) storage = storages[0]
|
|
||||||
|
// 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')
|
if (storage == null) throw new Error('No storage to create a note')
|
||||||
let folder = _.find(storage.folders, {key: params.folderKey})
|
let folder = _.find(storage.folders, {key: params.folderKey})
|
||||||
if (folder == null) folder = storage.folders[0]
|
if (folder == null) folder = storage.folders[0]
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const keygen = require('browser/lib/keygen')
|
const keygen = require('browser/lib/keygen')
|
||||||
const sander = require('sander')
|
const resolveStorageData = require('./resolveStorageData')
|
||||||
const path = require('path')
|
const resolveStorageNotes = require('./resolveStorageNotes')
|
||||||
|
|
||||||
const defaultBoostnoteJSON = {
|
|
||||||
folders: []
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object}
|
* @param {Object}
|
||||||
@@ -42,31 +38,10 @@ function addStorage (input) {
|
|||||||
path: input.path
|
path: input.path
|
||||||
}
|
}
|
||||||
|
|
||||||
const boostnoteJSONPath = path.join(newStorage.path, 'boostnote.json')
|
|
||||||
|
|
||||||
return Promise.resolve(newStorage)
|
return Promise.resolve(newStorage)
|
||||||
.then(function resolveBoostnoteJSON () {
|
.then(resolveStorageData)
|
||||||
return sander.readFile(boostnoteJSONPath)
|
.then(function saveMetadataToLocalStorage (resolvedStorage) {
|
||||||
.then(function checkBoostnoteJSONExists (data) {
|
newStorage = resolvedStorage
|
||||||
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 () {
|
|
||||||
rawStorages.push({
|
rawStorages.push({
|
||||||
key: newStorage.key,
|
key: newStorage.key,
|
||||||
type: newStorage.type,
|
type: newStorage.type,
|
||||||
@@ -75,36 +50,9 @@ function addStorage (input) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
||||||
|
return newStorage
|
||||||
})
|
})
|
||||||
.then(function fetchNotes () {
|
.then(resolveStorageNotes)
|
||||||
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(function returnValue (notes) {
|
.then(function returnValue (notes) {
|
||||||
return {
|
return {
|
||||||
storage: newStorage,
|
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 dataApi = {
|
||||||
const CSON = require('season')
|
init: require('./init'),
|
||||||
const path = require('path')
|
addStorage: require('./addStorage'),
|
||||||
const _ = require('lodash')
|
renameStorage: require('./renameStorage'),
|
||||||
const sander = require('sander')
|
removeStorage: require('./removeStorage'),
|
||||||
const consts = require('browser/lib/consts')
|
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 = []
|
_migrateFromV6Storage: require('./migrateFromV6Storage'),
|
||||||
let notes = []
|
_resolveStorageData: require('./resolveStorageData'),
|
||||||
|
_resolveStorageNotes: require('./resolveStorageNotes')
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Storage {
|
module.exports = dataApi
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
const sander = require('sander')
|
const resolveStorageData = require('./resolveStorageData')
|
||||||
const path = require('path')
|
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 () {
|
function init () {
|
||||||
let fetchStorages = function () {
|
let fetchStorages = function () {
|
||||||
let rawStorages
|
let rawStorages
|
||||||
@@ -10,67 +23,38 @@ function init () {
|
|||||||
rawStorages = JSON.parse(window.localStorage.getItem('storages'))
|
rawStorages = JSON.parse(window.localStorage.getItem('storages'))
|
||||||
if (!_.isArray(rawStorages)) throw new Error('Cached data is not valid.')
|
if (!_.isArray(rawStorages)) throw new Error('Cached data is not valid.')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.warn('Failed to parse cached data from localStorage', e)
|
||||||
rawStorages = []
|
rawStorages = []
|
||||||
window.localStorage.setItem('storages', JSON.stringify(rawStorages))
|
window.localStorage.setItem('storages', JSON.stringify(rawStorages))
|
||||||
}
|
}
|
||||||
return Promise.all(rawStorages
|
return Promise.all(rawStorages
|
||||||
.map(function assignFoldersToStorage (rawStorage) {
|
.map(resolveStorageData))
|
||||||
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)
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let fetchNotes = function (storages) {
|
let fetchNotes = function (storages) {
|
||||||
let notes = []
|
let findNotesFromEachStorage = storages
|
||||||
|
.map(resolveStorageNotes)
|
||||||
storages
|
return Promise.all(findNotesFromEachStorage)
|
||||||
.forEach((storage) => {
|
.then(function concatNoteGroup (noteGroups) {
|
||||||
storage.folders.forEach((folder) => {
|
return noteGroups.reduce(function (sum, group) {
|
||||||
let dataPath = path.join(storage.path, folder.key, 'data.json')
|
return sum.concat(group)
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
})
|
.then(function returnData (notes) {
|
||||||
})
|
return {
|
||||||
return Promise.resolve({
|
|
||||||
storages,
|
storages,
|
||||||
notes
|
notes
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(fetchStorages())
|
return Promise.resolve(fetchStorages())
|
||||||
.then((storages) => {
|
.then((storages) => {
|
||||||
storages = storages.filter((storage) => {
|
return storages
|
||||||
|
.filter((storage) => {
|
||||||
if (!_.isObject(storage)) return false
|
if (!_.isObject(storage)) return false
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
return storages
|
|
||||||
})
|
})
|
||||||
.then(fetchNotes)
|
.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) {
|
handleMarkdownNoteButtonClick (e) {
|
||||||
let { storage, folder, dispatch, location } = this.props
|
let { storage, folder, dispatch, location } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.createMarkdownNote(storage, folder, {
|
.createNote(storage, {
|
||||||
|
type: 'MARKDOWN_NOTE',
|
||||||
|
folder: folder,
|
||||||
title: '',
|
title: '',
|
||||||
content: ''
|
content: ''
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'CREATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
hashHistory.push({
|
hashHistory.push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {key: note.uniqueKey}
|
query: {key: note.storage + '-' + note.key}
|
||||||
})
|
})
|
||||||
ee.emit('detail:focus')
|
ee.emit('detail:focus')
|
||||||
this.props.close()
|
this.props.close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMarkdownNoteButtonKeyDown (e) {
|
handleMarkdownNoteButtonKeyDown (e) {
|
||||||
if (e.keyCode === 9) {
|
if (e.keyCode === 9) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -50,8 +53,11 @@ class NewNoteModal extends React.Component {
|
|||||||
|
|
||||||
handleSnippetNoteButtonClick (e) {
|
handleSnippetNoteButtonClick (e) {
|
||||||
let { storage, folder, dispatch, location } = this.props
|
let { storage, folder, dispatch, location } = this.props
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.createSnippetNote(storage, folder, {
|
.createNote(storage, {
|
||||||
|
type: 'SNIPPET_NOTE',
|
||||||
|
folder: folder,
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
snippets: [{
|
snippets: [{
|
||||||
@@ -62,12 +68,12 @@ class NewNoteModal extends React.Component {
|
|||||||
})
|
})
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'CREATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
hashHistory.push({
|
hashHistory.push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {key: note.uniqueKey}
|
query: {key: note.storage + '-' + note.key}
|
||||||
})
|
})
|
||||||
ee.emit('detail:focus')
|
ee.emit('detail:focus')
|
||||||
this.props.close()
|
this.props.close()
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import dataApi from 'browser/main/lib/dataApi'
|
|||||||
import store from 'browser/main/store'
|
import store from 'browser/main/store'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { shell, remote } = electron
|
const { shell } = electron
|
||||||
const { Menu, MenuItem } = remote
|
|
||||||
import { SketchPicker } from 'react-color'
|
import { SketchPicker } from 'react-color'
|
||||||
|
|
||||||
class UnstyledFolderItem extends React.Component {
|
class UnstyledFolderItem extends React.Component {
|
||||||
@@ -42,10 +41,10 @@ class UnstyledFolderItem extends React.Component {
|
|||||||
color: this.state.folder.color,
|
color: this.state.folder.color,
|
||||||
name: this.state.folder.name
|
name: this.state.folder.name
|
||||||
})
|
})
|
||||||
.then((storage) => {
|
.then((data) => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'UPDATE_STORAGE',
|
type: 'UPDATE_FOLDER',
|
||||||
storage: storage
|
storage: data.storage
|
||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'IDLE'
|
status: 'IDLE'
|
||||||
@@ -55,10 +54,10 @@ class UnstyledFolderItem extends React.Component {
|
|||||||
|
|
||||||
handleColorButtonClick (e) {
|
handleColorButtonClick (e) {
|
||||||
const folder = Object.assign({}, this.state.folder, { showColumnPicker: true, colorPickerPos: { left: 0, top: 0 } })
|
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
|
// After the color picker has been painted, re-calculate its position
|
||||||
// by comparing its dimensions to the host dimensions.
|
// by comparing its dimensions to the host dimensions.
|
||||||
const { hostBoundingBox } = this.props;
|
const { hostBoundingBox } = this.props
|
||||||
const colorPickerNode = ReactDOM.findDOMNode(this.refs.colorPicker)
|
const colorPickerNode = ReactDOM.findDOMNode(this.refs.colorPicker)
|
||||||
const colorPickerBox = colorPickerNode.getBoundingClientRect()
|
const colorPickerBox = colorPickerNode.getBoundingClientRect()
|
||||||
const offsetTop = hostBoundingBox.bottom - colorPickerBox.bottom
|
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}}
|
<button styleName='folderList-item-left-colorButton' style={{color: this.state.folder.color}}
|
||||||
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}
|
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}
|
||||||
>
|
>
|
||||||
{ this.state.folder.showColumnPicker ?
|
{this.state.folder.showColumnPicker
|
||||||
<div style={ popover }>
|
? <div style={popover}>
|
||||||
<div style={ cover }
|
<div style={cover}
|
||||||
onClick={ () => this.handleColorPickerClose() } />
|
onClick={() => this.handleColorPickerClose()}
|
||||||
|
/>
|
||||||
<div style={pickerStyle}>
|
<div style={pickerStyle}>
|
||||||
<SketchPicker
|
<SketchPicker
|
||||||
ref="colorPicker"
|
ref='colorPicker'
|
||||||
color={this.state.folder.color}
|
color={this.state.folder.color}
|
||||||
onChange={ (color) => this.handleColorChange(color) }
|
onChange={(color) => this.handleColorChange(color)}
|
||||||
onChangeComplete={ (color) => this.handleColorChange(color) } />
|
onChangeComplete={(color) => this.handleColorChange(color)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: null }
|
: null
|
||||||
|
}
|
||||||
<i className='fa fa-square'/>
|
<i className='fa fa-square'/>
|
||||||
</button>
|
</button>
|
||||||
<input styleName='folderList-item-left-nameInput'
|
<input styleName='folderList-item-left-nameInput'
|
||||||
@@ -143,12 +145,12 @@ class UnstyledFolderItem extends React.Component {
|
|||||||
handleDeleteConfirmButtonClick (e) {
|
handleDeleteConfirmButtonClick (e) {
|
||||||
let { storage, folder } = this.props
|
let { storage, folder } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.removeFolder(storage.key, folder.key)
|
.deleteFolder(storage.key, folder.key)
|
||||||
.then((storage) => {
|
.then((data) => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'REMOVE_FOLDER',
|
type: 'DELETE_FOLDER',
|
||||||
key: folder.key,
|
storage: data.storage,
|
||||||
storage: storage
|
folderKey: data.folderKey
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -254,10 +256,10 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dataApi.createFolder(storage.key, input)
|
dataApi.createFolder(storage.key, input)
|
||||||
.then((storage) => {
|
.then((data) => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'ADD_FOLDER',
|
type: 'UPDATE_FOLDER',
|
||||||
storage: storage
|
storage: data.storage
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
@@ -276,11 +278,11 @@ class StorageItem extends React.Component {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'REMOVE_STORAGE',
|
type: 'REMOVE_STORAGE',
|
||||||
key: storage.key
|
storageKey: storage.key
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(err)
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,14 +51,13 @@ class StoragesTab extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderList () {
|
renderList () {
|
||||||
let { storages, boundingBox } = this.props
|
let { data, boundingBox } = this.props
|
||||||
|
|
||||||
if (!boundingBox) { return null }
|
if (!boundingBox) { return null }
|
||||||
let storageList = storages.map((storage) => {
|
let storageList = data.storageMap.map((storage) => {
|
||||||
return <StorageItem
|
return <StorageItem
|
||||||
key={storage.key}
|
key={storage.key}
|
||||||
storage={storage}
|
storage={storage}
|
||||||
test={true}
|
|
||||||
hostBoundingBox={boundingBox}
|
hostBoundingBox={boundingBox}
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ class Preferences extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderContent () {
|
renderContent () {
|
||||||
const { boundingBox } = this.state;
|
const { boundingBox } = this.state
|
||||||
let { dispatch, config, storages } = this.props
|
let { dispatch, config, data } = this.props
|
||||||
|
|
||||||
switch (this.state.currentTab) {
|
switch (this.state.currentTab) {
|
||||||
case 'INFO':
|
case 'INFO':
|
||||||
@@ -51,7 +51,7 @@ class Preferences extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<StoragesTab
|
<StoragesTab
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
storages={storages}
|
data={data}
|
||||||
boundingBox={boundingBox}
|
boundingBox={boundingBox}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -66,7 +66,7 @@ class Preferences extends React.Component {
|
|||||||
|
|
||||||
getContentBoundingBox () {
|
getContentBoundingBox () {
|
||||||
const node = ReactDOM.findDOMNode(this.refs.content)
|
const node = ReactDOM.findDOMNode(this.refs.content)
|
||||||
return node.getBoundingClientRect();
|
return node.getBoundingClientRect()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
|||||||
@@ -1,95 +1,449 @@
|
|||||||
import { combineReducers, createStore } from 'redux'
|
import { combineReducers, createStore } from 'redux'
|
||||||
import { routerReducer } from 'react-router-redux'
|
import { routerReducer } from 'react-router-redux'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
import { Map, Set } from 'browser/lib/Mutable'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
function storages (state = [], action) {
|
function defaultDataMap () {
|
||||||
console.info('REDUX >> ', action)
|
return {
|
||||||
switch (action.type) {
|
storageMap: new Map(),
|
||||||
case 'INIT_ALL':
|
noteMap: new Map(),
|
||||||
return action.storages
|
starredSet: new Set(),
|
||||||
case 'ADD_STORAGE':
|
storageNoteMap: new Map(),
|
||||||
{
|
folderNoteMap: new Map(),
|
||||||
let storages = state.slice()
|
tagNoteMap: new Map()
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function notes (state = [], action) {
|
function data (state = defaultDataMap(), action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'INIT_ALL':
|
case 'INIT_ALL':
|
||||||
return action.notes
|
state = defaultDataMap()
|
||||||
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)
|
|
||||||
|
|
||||||
return notes
|
action.storages.forEach((storage) => {
|
||||||
}
|
state.storageMap.set(storage.key, storage)
|
||||||
case 'REMOVE_FOLDER':
|
})
|
||||||
{
|
|
||||||
let notes = state.slice()
|
|
||||||
notes = notes
|
|
||||||
.filter((note) => note.storage !== action.storage.key || note.folder !== action.key)
|
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
case 'CREATE_NOTE':
|
|
||||||
{
|
let storageNoteList = state.storageNoteMap.get(note.storage)
|
||||||
let notes = state.slice()
|
if (storageNoteList == null) {
|
||||||
notes.push(action.note)
|
storageNoteList = new Set(storageNoteList)
|
||||||
return notes
|
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':
|
case 'UPDATE_NOTE':
|
||||||
{
|
{
|
||||||
let notes = state.slice()
|
let note = action.note
|
||||||
notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage)
|
let uniqueKey = note.storage + '-' + note.key
|
||||||
notes.push(action.note)
|
let folderKey = note.storage + '-' + note.folder
|
||||||
return notes
|
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':
|
case 'MOVE_NOTE':
|
||||||
{
|
{
|
||||||
let notes = state.slice()
|
let originNote = action.originNote
|
||||||
notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage)
|
let originKey = originNote.storage + '-' + originNote.key
|
||||||
notes.push(action.newNote)
|
let note = action.note
|
||||||
return notes
|
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)
|
||||||
}
|
}
|
||||||
case 'REMOVE_NOTE':
|
|
||||||
|
// 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 'DELETE_NOTE':
|
||||||
{
|
{
|
||||||
let notes = state.slice()
|
let uniqueKey = action.storageKey + '-' + action.noteKey
|
||||||
notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage)
|
let targetNote = state.noteMap.get(uniqueKey)
|
||||||
return notes
|
|
||||||
|
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
|
return state
|
||||||
}
|
}
|
||||||
@@ -116,8 +470,7 @@ function config (state = defaultConfig, action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let reducer = combineReducers({
|
let reducer = combineReducers({
|
||||||
storages,
|
data,
|
||||||
notes,
|
|
||||||
config,
|
config,
|
||||||
routing: routerReducer
|
routing: routerReducer
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"electron-gh-releases": "^2.0.2",
|
"electron-gh-releases": "^2.0.2",
|
||||||
"font-awesome": "^4.3.0",
|
"font-awesome": "^4.3.0",
|
||||||
"highlight.js": "^9.3.0",
|
"highlight.js": "^9.3.0",
|
||||||
|
"immutable": "^3.8.1",
|
||||||
"lodash": "^4.11.1",
|
"lodash": "^4.11.1",
|
||||||
"markdown-it": "^6.0.1",
|
"markdown-it": "^6.0.1",
|
||||||
"markdown-it-checkbox": "^1.1.0",
|
"markdown-it-checkbox": "^1.1.0",
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
"dom-storage": "^2.0.2",
|
"dom-storage": "^2.0.2",
|
||||||
"electron-packager": "^6.0.0",
|
"electron-packager": "^6.0.0",
|
||||||
"electron-prebuilt": "^1.2.8",
|
"electron-prebuilt": "^1.2.8",
|
||||||
|
"faker": "^3.1.0",
|
||||||
"grunt": "^0.4.5",
|
"grunt": "^0.4.5",
|
||||||
"grunt-electron-installer": "^1.2.0",
|
"grunt-electron-installer": "^1.2.0",
|
||||||
"history": "^1.17.0",
|
"history": "^1.17.0",
|
||||||
|
|||||||
@@ -8,86 +8,65 @@ global.navigator = window.navigator
|
|||||||
const Storage = require('dom-storage')
|
const Storage = require('dom-storage')
|
||||||
const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true })
|
const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true })
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const TestDummy = require('../fixtures/TestDummy')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
const os = require('os')
|
||||||
|
const CSON = require('season')
|
||||||
|
|
||||||
function copyFile (filePath, targetPath) {
|
const v1StoragePath = path.join(os.tmpdir(), 'test/addStorage-v1-storage')
|
||||||
return sander.readFile(filePath)
|
// const legacyStoragePath = path.join(os.tmpdir(), 'test/addStorage-legacy-storage')
|
||||||
.then(function writeFile (data) {
|
// const emptyDirPath = path.join(os.tmpdir(), 'test/addStorage-empty-storage')
|
||||||
return sander.writeFile(targetPath, data.toString())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
test('add a initialized storage', (t) => {
|
test.beforeEach((t) => {
|
||||||
const dummyStoragePath = path.join(__dirname, '../dummy/dummyStorage')
|
t.context.v1StorageData = TestDummy.dummyStorage(v1StoragePath)
|
||||||
const targetPath = path.join(__dirname, '../sandbox/test-add-storage1')
|
// t.context.legacyStorageData = TestDummy.dummyLegacyStorage(legacyStoragePath)
|
||||||
|
|
||||||
|
localStorage.setItem('storages', JSON.stringify([]))
|
||||||
|
})
|
||||||
|
|
||||||
|
test.serial('Add Storage', (t) => {
|
||||||
const input = {
|
const input = {
|
||||||
type: 'FILESYSTEM',
|
type: 'FILESYSTEM',
|
||||||
name: 'test-add-storage1',
|
name: 'add-storage1',
|
||||||
path: targetPath
|
path: v1StoragePath
|
||||||
}
|
}
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(function before () {
|
.then(function doTest () {
|
||||||
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) {
|
|
||||||
return addStorage(input)
|
return addStorage(input)
|
||||||
})
|
})
|
||||||
.then(function validateResult (data) {
|
.then(function validateResult (data) {
|
||||||
const { storage, notes } = data
|
let { storage, notes } = data
|
||||||
|
|
||||||
|
// Check data.storage
|
||||||
t.true(_.isString(storage.key))
|
t.true(_.isString(storage.key))
|
||||||
t.is(storage.name, 'test-add-storage1')
|
t.is(storage.name, input.name)
|
||||||
t.true(_.isArray(storage.folders))
|
t.is(storage.type, input.type)
|
||||||
t.is(storage.folders.length, 1)
|
t.is(storage.path, input.path)
|
||||||
t.true(_.isArray(notes))
|
t.is(storage.version, '1.0')
|
||||||
t.is(notes.length, 2)
|
t.is(storage.folders.length, t.context.v1StorageData.json.folders.length)
|
||||||
t.is(notes[0].folder, 'fc6ba88e8ecf')
|
|
||||||
t.is(notes[0].storage, storage.key)
|
// Check data.notes
|
||||||
|
t.is(notes.length, t.context.v1StorageData.notes.length)
|
||||||
|
notes.forEach(function validateNote (note) {
|
||||||
|
t.is(note.storage, storage.key)
|
||||||
})
|
})
|
||||||
.then(function after () {
|
|
||||||
localStorage.clear()
|
// Check localStorage
|
||||||
sander.rimrafSync(targetPath)
|
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) => {
|
test.after.always(() => {
|
||||||
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()
|
localStorage.clear()
|
||||||
sander.rimrafSync(targetPath)
|
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 Storage = require('dom-storage')
|
||||||
const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true })
|
const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true })
|
||||||
const path = require('path')
|
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 v1StoragePath = path.join(os.tmpdir(), 'test/init-v1-storage')
|
||||||
const dummyStoragePath = path.join(__dirname, '..', 'dummy/dummyStorage')
|
const legacyStoragePath = path.join(os.tmpdir(), 'test/init-legacy-storage')
|
||||||
const dummyRawStorage = {
|
const emptyDirPath = path.join(os.tmpdir(), 'test/init-empty-storage')
|
||||||
name: 'test1',
|
|
||||||
key: crypto.randomBytes(6).toString('hex'),
|
test.beforeEach((t) => {
|
||||||
path: dummyStoragePath
|
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()
|
return Promise.resolve()
|
||||||
.then(function before () {
|
|
||||||
localStorage.setItem('storages', JSON.stringify([dummyRawStorage]))
|
|
||||||
})
|
|
||||||
.then(function test () {
|
.then(function test () {
|
||||||
return init()
|
return init()
|
||||||
})
|
})
|
||||||
.then(function assert (data) {
|
.then(function assert (data) {
|
||||||
t.true(Array.isArray(data.storages))
|
t.true(Array.isArray(data.storages))
|
||||||
var targetStorage = data.storages.filter((storage) => storage.key === dummyRawStorage.key)[0]
|
t.is(data.notes.length, v1StorageData.notes.length + legacyStorageData.notes.length)
|
||||||
t.not(targetStorage, null)
|
t.is(data.storages.length, 3)
|
||||||
t.is(targetStorage.name, dummyRawStorage.name)
|
data.storages.forEach(function assertStorage (storage) {
|
||||||
t.is(targetStorage.key, dummyRawStorage.key)
|
t.true(_.isString(storage.key))
|
||||||
t.is(targetStorage.path, dummyRawStorage.path)
|
t.true(_.isString(storage.name))
|
||||||
t.is(data.notes.length, 2)
|
t.true(storage.type === 'FILESYSTEM')
|
||||||
data.notes.forEach((note) => {
|
t.true(_.isString(storage.path))
|
||||||
t.is(note.folder, dummyFolderKey)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.true(Array.isArray(data.notes))
|
|
||||||
})
|
})
|
||||||
.then(function after () {
|
.then(function after () {
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.serial('If storage path is a empty folder, return metadata with empty folder array and empty note array.', (t) => {
|
test.after.always(() => {
|
||||||
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()
|
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/'
|
publicPath: 'http://localhost:8080/assets/'
|
||||||
},
|
},
|
||||||
debug: true,
|
debug: true,
|
||||||
devtool: 'eval-source-map'
|
devtool: 'cheap-module-eval-source-map'
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = config
|
module.exports = config
|
||||||
|
|||||||
Reference in New Issue
Block a user