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

Add trash can

This commit is contained in:
asmsuechan
2017-07-12 15:34:40 +09:00
parent ec560ceab1
commit 2650cc2f1c
11 changed files with 294 additions and 141 deletions

View File

@@ -89,7 +89,8 @@ NoteItem.propTypes = {
type: PropTypes.string.isRequired,
title: PropTypes.string.isrequired,
tags: PropTypes.array,
isStarred: PropTypes.bool.isRequired
isStarred: PropTypes.bool.isRequired,
isTrashed: PropTypes.bool.isRequired
}),
handleNoteClick: PropTypes.func.isRequired,
handleDragStart: PropTypes.func.isRequired,

View File

@@ -15,7 +15,7 @@ import styles from './SideNavFilter.styl'
*/
const SideNavFilter = ({
isFolded, isHomeActive, handleAllNotesButtonClick,
isStarredActive, handleStarredButtonClick
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick
}) => (
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
@@ -30,6 +30,12 @@ const SideNavFilter = ({
<i className='fa fa-star fa-fw' />
<span styleName='menu-button-label'>Starred</span>
</button>
<button styleName={isTrashedActive ? 'menu-button--active' : 'menu-button'}
onClick={handleTrashedButtonClick}
>
<i className='fa fa-trash fa-fw' />
<span styleName='menu-button-label'>Trashed</span>
</button>
</div>
)
@@ -38,7 +44,9 @@ SideNavFilter.propTypes = {
isHomeActive: PropTypes.bool.isRequired,
handleAllNotesButtonClick: PropTypes.func.isRequired,
isStarredActive: PropTypes.bool.isRequired,
handleStarredButtonClick: PropTypes.func.isRequired
isTrashedActive: PropTypes.bool.isRequired,
handleStarredButtonClick: PropTypes.func.isRequired,
handleTrashdButtonClick: PropTypes.func.isRequired
}
export default CSSModules(SideNavFilter, styles)

View File

@@ -56,7 +56,7 @@ class MarkdownNoteDetail extends React.Component {
note: Object.assign({}, nextProps.note)
}, () => {
this.refs.content.reload()
this.refs.tags.reset()
if (this.refs.tags) this.refs.tags.reset()
})
}
}
@@ -176,13 +176,27 @@ class MarkdownNoteDetail extends React.Component {
}
handleTrashButtonClick (e) {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
let { note } = this.state
const { isTrashed } = note
const popupMessage = isTrashed ? 'This work cannot be undone.' : 'Throw it into trashbox.'
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a note',
detail: 'This work cannot be undone.',
detail: popupMessage,
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
if (dialogueButtonIndex === 0) {
if (!isTrashed) {
note.isTrashed = true
this.setState({
note
}, () => {
this.save()
})
} else {
let { note, dispatch } = this.props
dataApi
.deleteNote(note.storage, note.key)
@@ -195,9 +209,24 @@ class MarkdownNoteDetail extends React.Component {
})
}
ee.once('list:moved', dispatchHandler)
ee.emit('list:next')
})
}
ee.emit('list:next')
}
}
handleUndoButtonClick (e) {
let { note } = this.state
note.isTrashed = false
this.setState({
note
}, () => {
this.save()
this.refs.content.reload()
ee.emit('list:next')
})
}
handleFullScreenButton (e) {
@@ -254,12 +283,23 @@ class MarkdownNoteDetail extends React.Component {
})
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
return (
<div className='NoteDetail'
style={this.props.style}
styleName='root'
>
<div styleName='info'>
const trashTopBar = <div styleName='info'>
<div styleName='info-left'>
<div styleName='info-left-top'>
<div styleName='info-left-top-folderSelect'>
<i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
</div>
</div>
</div>
<div styleName='info-right'>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
</div>
</div>
const detailTopBar = <div styleName='info'>
<div styleName='info-left'>
<StarButton styleName='info-left-button'
onClick={(e) => this.handleStarButtonClick(e)}
@@ -293,34 +333,30 @@ class MarkdownNoteDetail extends React.Component {
>
<i className={faClassName} styleName='lock-button' />
<span styleName='control-lockButton-tooltip'>
{this.state.isLocked ? 'Lock' : 'Unlock'}
{this.state.isLocked ? 'Unlock' : 'Lock'}
</span>
</button>
return (
this.state.isLockButtonShown ? lockButtonComponent : ''
)
})()}
<TrashButton
onClick={(e) => this.handleTrashButtonClick(e)}
/>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<i className='fa fa-expand' styleName='fullScreen-button' />
<i className='fa fa-arrows-alt' styleName='fullScreen-button' />
</button>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteKey={location.query.key}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
/>
</div>
</div>
return (
<div className='NoteDetail'
style={this.props.style}
styleName='root'
>
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
<div styleName='body'>
<MarkdownEditor
ref='content'

View File

@@ -55,6 +55,18 @@ $info-margin-under-border = 27px
bottom 1px
padding-left 30px
.undo-button
position relative
border solid 1px transparent
line-height 34px
vertical-align middle
border-radius 2px
transition 0.15s
user-select none
cursor pointer
&:hover
background-color #D9D9D9
body[data-theme="dark"]
.info
border-color $ui-dark-borderColor

View File

@@ -72,7 +72,7 @@ class SnippetNoteDetail extends React.Component {
snippets.forEach((snippet, index) => {
this.refs['code-' + index].reload()
})
this.refs.tags.reset()
if (this.refs.tags) this.refs.tags.reset()
})
}
}
@@ -84,7 +84,7 @@ class SnippetNoteDetail extends React.Component {
handleChange (e) {
let { note } = this.state
note.tags = this.refs.tags.value
if (this.refs.tags) note.tags = this.refs.tags.value
note.description = this.refs.description.value
note.updatedAt = new Date()
note.title = findNoteTitle(note.description)
@@ -170,13 +170,27 @@ class SnippetNoteDetail extends React.Component {
}
handleTrashButtonClick (e) {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
let { note } = this.state
const { isTrashed } = note
const popupMessage = isTrashed ? 'This work cannot be undone.' : 'Throw it into trashbox.'
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a note',
detail: 'This work cannot be undone.',
detail: popupMessage,
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
if (dialogueButtonIndex === 0) {
if (!isTrashed) {
note.isTrashed = true
this.setState({
note
}, () => {
this.save()
})
} else {
let { note, dispatch } = this.props
dataApi
.deleteNote(note.storage, note.key)
@@ -189,9 +203,23 @@ class SnippetNoteDetail extends React.Component {
})
}
ee.once('list:moved', dispatchHandler)
ee.emit('list:next')
})
}
ee.emit('list:next')
}
}
handleUndoButtonClick (e) {
let { note } = this.state
note.isTrashed = false
this.setState({
note
}, () => {
this.save()
ee.emit('list:next')
})
}
handleFullScreenButton (e) {
@@ -513,13 +541,23 @@ class SnippetNoteDetail extends React.Component {
})
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
return (
<div className='NoteDetail'
style={this.props.style}
styleName='root'
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='info'>
const trashTopBar = <div styleName='info'>
<div styleName='info-left'>
<div styleName='info-left-top'>
<div styleName='info-left-top-folderSelect'>
<i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
</div>
</div>
</div>
<div styleName='info-right'>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
</div>
</div>
const detailTopBar = <div styleName='info'>
<div styleName='info-left'>
<StarButton styleName='info-left-button'
onClick={(e) => this.handleStarButtonClick(e)}
@@ -541,27 +579,23 @@ class SnippetNoteDetail extends React.Component {
/>
</div>
<div styleName='info-right'>
<TrashButton
onClick={(e) => this.handleTrashButtonClick(e)}
/>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<i className='fa fa-expand' styleName='fullScreen-button' />
<i className='fa fa-arrows-alt' styleName='fullScreen-button' />
</button>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteKey={location.query.key}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
/>
</div>
</div>
return (
<div className='NoteDetail'
style={this.props.style}
styleName='root'
onKeyDown={(e) => this.handleKeyDown(e)}
>
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
<div styleName='body'>
<div styleName='description'>
<textarea

View File

@@ -257,6 +257,11 @@ class NoteList extends React.Component {
return searchFromNotes(this.props.data, searchInputText)
}
if (location.pathname.match(/\/trashed/)) {
return data.trashedSet.toJS()
.map((uniqueKey) => data.noteMap.get(uniqueKey))
}
let storageKey = params.storageKey
let folderKey = params.folderKey
let storage = data.storageMap.get(storageKey)
@@ -411,6 +416,10 @@ class NoteList extends React.Component {
: sortByUpdatedAt
this.notes = notes = this.getNotes()
.sort(sortFunc)
.filter((note) => {
// this is for the trash box
if (note.isTrashed !== true || location.pathname === '/trashed') return true
})
let noteList = notes
.map(note => {

View File

@@ -33,12 +33,18 @@ class SideNav extends React.Component {
})
}
handleTrashedButtonClick (e) {
let { router } = this.context
router.push('/trashed')
}
render () {
let { data, location, config, dispatch } = this.props
let isFolded = config.isSideNavFolded
let isHomeActive = !!location.pathname.match(/^\/home$/)
let isStarredActive = !!location.pathname.match(/^\/starred$/)
let isTrashedActive = !!location.pathname.match(/^\/trashed$/)
let storageList = data.storageMap.map((storage, key) => {
return <StorageItem
@@ -72,7 +78,9 @@ class SideNav extends React.Component {
isHomeActive={isHomeActive}
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
isStarredActive={isStarredActive}
isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
/>
<div styleName='storageList'>

View File

@@ -60,6 +60,7 @@ ReactDOM.render((
<Route path='home' />
<Route path='starred' />
<Route path='searched' />
<Route path='trashed' />
<Route path='storages'>
<IndexRedirect to='/home' />
<Route path=':storageKey'>

View File

@@ -10,6 +10,7 @@ function validateInput (input) {
input.tags = input.tags.filter((tag) => _.isString(tag) && tag.trim().length > 0)
if (!_.isString(input.title)) input.title = ''
input.isStarred = !!input.isStarred
input.isTrashed = !!input.isTrashed
switch (input.type) {
case 'MARKDOWN_NOTE':

View File

@@ -21,6 +21,10 @@ function validateInput (input) {
validatedInput.isStarred = !!input.isStarred
}
if (input.isTrashed != null) {
validatedInput.isTrashed = !!input.isTrashed
}
validatedInput.type = input.type
switch (input.type) {
case 'MARKDOWN_NOTE':
@@ -101,6 +105,7 @@ function updateNote (storageKey, noteKey, input) {
noteData.createdAt = new Date()
noteData.updatedAt = new Date()
noteData.isStarred = false
noteData.isTrashed = false
noteData.tags = []
}

View File

@@ -11,7 +11,8 @@ function defaultDataMap () {
starredSet: new Set(),
storageNoteMap: new Map(),
folderNoteMap: new Map(),
tagNoteMap: new Map()
tagNoteMap: new Map(),
trashedSet: new Set()
}
}
@@ -34,6 +35,10 @@ function data (state = defaultDataMap(), action) {
state.starredSet.add(uniqueKey)
}
if (note.isTrashed) {
state.trashedSet.add(uniqueKey)
}
let storageNoteList = state.storageNoteMap.get(note.storage)
if (storageNoteList == null) {
storageNoteList = new Set(storageNoteList)
@@ -78,6 +83,15 @@ function data (state = defaultDataMap(), action) {
}
}
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
state.trashedSet = new Set(state.trashedSet)
if (note.isTrashed) {
state.trashedSet.add(uniqueKey)
} else {
state.trashedSet.delete(uniqueKey)
}
}
// Update storageNoteMap if oldNote doesn't exist
if (oldNote == null) {
state.storageNoteMap = new Map(state.storageNoteMap)
@@ -163,6 +177,11 @@ function data (state = defaultDataMap(), action) {
state.starredSet.delete(originKey)
}
if (originNote.isTrashed) {
state.trashedSet = new Set(state.trashedSet)
state.trashedSet.delete(originKey)
}
// From storageNoteMap
state.storageNoteMap = new Map(state.storageNoteMap)
let noteSet = state.storageNoteMap.get(originNote.storage)
@@ -199,6 +218,15 @@ function data (state = defaultDataMap(), action) {
}
}
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
state.trashedSet = new Set(state.trashedSet)
if (note.isTrashed) {
state.trashedSet.add(uniqueKey)
} else {
state.trashedSet.delete(uniqueKey)
}
}
// Update storageNoteMap if oldNote doesn't exist
if (oldNote == null) {
state.storageNoteMap = new Map(state.storageNoteMap)
@@ -283,6 +311,11 @@ function data (state = defaultDataMap(), action) {
state.starredSet.delete(uniqueKey)
}
if (targetNote.isTrashed) {
state.trashedSet = new Set(state.trashedSet)
state.trashedSet.delete(uniqueKey)
}
// From folderNoteMap
let folderKey = targetNote.storage + '-' + targetNote.folder
state.folderNoteMap = new Map(state.folderNoteMap)
@@ -348,6 +381,11 @@ function data (state = defaultDataMap(), action) {
state.starredSet.delete(noteKey)
}
if (note.isTrashed) {
state.trashedSet = new Set(state.trashedSet)
state.trashedSet.delete(noteKey)
}
// Delete key from tag map
state.tagNoteMap = new Map(state.tagNoteMap)
note.tags.forEach((tag) => {