1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-15 10:46:32 +00:00

Merge pull request #1070 from voidsatisfaction/feature/add_multiselect_notes_delete

Feature multiselect notes delete and move to another folder
This commit is contained in:
Kazz Yokomizo
2017-11-28 12:39:12 +09:00
committed by GitHub
2 changed files with 254 additions and 88 deletions

View File

@@ -15,6 +15,7 @@ import { hashHistory } from 'react-router'
import markdown from 'browser/lib/markdown' import markdown from 'browser/lib/markdown'
import { findNoteTitle } from 'browser/lib/findNoteTitle' import { findNoteTitle } from 'browser/lib/findNoteTitle'
import store from 'browser/main/store' import store from 'browser/main/store'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
const { remote } = require('electron') const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote const { Menu, MenuItem, dialog } = remote
@@ -31,6 +32,18 @@ function sortByUpdatedAt (a, b) {
return new Date(b.updatedAt) - new Date(a.updatedAt) return new Date(b.updatedAt) - new Date(a.updatedAt)
} }
function findNoteByKey (notes, noteKey) {
return notes.find((note) => `${note.storage}-${note.key}` === noteKey)
}
function findNotesByKeys (notes, noteKeys) {
return notes.filter((note) => noteKeys.includes(getNoteKey(note)))
}
function getNoteKey (note) {
return `${note.storage}-${note.key}`
}
class NoteList extends React.Component { class NoteList extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -50,8 +63,16 @@ class NoteList extends React.Component {
} }
this.importFromFileHandler = this.importFromFile.bind(this) this.importFromFileHandler = this.importFromFile.bind(this)
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this) this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
this.deleteNote = this.deleteNote.bind(this)
this.focusNote = this.focusNote.bind(this)
this.pinToTop = this.pinToTop.bind(this)
// TODO: not Selected noteKeys but SelectedNote(for reusing)
this.state = { this.state = {
shiftKeyDown: false,
selectedNoteKeys: []
} }
this.contextNotes = [] this.contextNotes = []
@@ -124,12 +145,35 @@ class NoteList extends React.Component {
} }
} }
focusNote (selectedNoteKeys, noteKey) {
const { router } = this.context
const { location } = this.props
this.setState({
selectedNoteKeys
})
router.push({
pathname: location.pathname,
query: {
key: noteKey
}
})
}
getNoteKeyFromTargetIndex (targetIndex) {
const note = Object.assign({}, this.notes[targetIndex])
const noteKey = getNoteKey(note)
return noteKey
}
selectPriorNote () { selectPriorNote () {
if (this.notes == null || this.notes.length === 0) { if (this.notes == null || this.notes.length === 0) {
return return
} }
const { router } = this.context let { router } = this.context
const { location } = this.props let { location } = this.props
let { selectedNoteKeys, shiftKeyDown } = this.state
let targetIndex = this.getTargetIndex() let targetIndex = this.getTargetIndex()
@@ -137,39 +181,51 @@ class NoteList extends React.Component {
return return
} }
targetIndex-- targetIndex--
if (targetIndex < 0) targetIndex = 0
router.push({ if (!shiftKeyDown) { selectedNoteKeys = [] }
pathname: location.pathname, const priorNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
query: { if (selectedNoteKeys.includes(priorNoteKey)) {
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key selectedNoteKeys.pop()
} } else {
}) selectedNoteKeys.push(priorNoteKey)
}
this.focusNote(selectedNoteKeys, priorNoteKey)
ee.emit('list:moved')
} }
selectNextNote () { selectNextNote () {
if (this.notes == null || this.notes.length === 0) { if (this.notes == null || this.notes.length === 0) {
return return
} }
const { router } = this.context let { router } = this.context
const { location } = this.props let { location } = this.props
let { selectedNoteKeys, shiftKeyDown } = this.state
let targetIndex = this.getTargetIndex() let targetIndex = this.getTargetIndex()
const isTargetLastNote = targetIndex === this.notes.length - 1
if (targetIndex === this.notes.length - 1) { if (isTargetLastNote && shiftKeyDown) {
return
} else if (isTargetLastNote) {
targetIndex = 0 targetIndex = 0
} else { } else {
targetIndex++ targetIndex++
if (targetIndex < 0) targetIndex = 0 if (targetIndex < 0) targetIndex = 0
else if (targetIndex > this.notes.length - 1) targetIndex === this.notes.length - 1 else if (targetIndex > this.notes.length - 1) targetIndex = this.notes.length - 1
} }
router.push({ if (!shiftKeyDown) { selectedNoteKeys = [] }
pathname: location.pathname, const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
query: { if (selectedNoteKeys.includes(nextNoteKey)) {
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key selectedNoteKeys.pop()
} } else {
}) selectedNoteKeys.push(nextNoteKey)
}
this.focusNote(selectedNoteKeys, nextNoteKey)
ee.emit('list:moved') ee.emit('list:moved')
} }
@@ -186,17 +242,17 @@ class NoteList extends React.Component {
if (targetIndex < 0) targetIndex = 0 if (targetIndex < 0) targetIndex = 0
router.push({ const selectedNoteKeys = []
pathname: location.pathname, const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
query: { selectedNoteKeys.push(nextNoteKey)
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
} this.focusNote(selectedNoteKeys, nextNoteKey)
})
ee.emit('list:moved') ee.emit('list:moved')
} }
handleNoteListKeyDown (e) { handleNoteListKeyDown (e) {
const { shiftKeyDown } = this.state
if (e.metaKey || e.ctrlKey) return true if (e.metaKey || e.ctrlKey) return true
if (e.keyCode === 65 && !e.shiftKey) { if (e.keyCode === 65 && !e.shiftKey) {
@@ -206,7 +262,7 @@ class NoteList extends React.Component {
if (e.keyCode === 68) { if (e.keyCode === 68) {
e.preventDefault() e.preventDefault()
ee.emit('detail:delete') this.deleteNote()
} }
if (e.keyCode === 69) { if (e.keyCode === 69) {
@@ -223,6 +279,16 @@ class NoteList extends React.Component {
e.preventDefault() e.preventDefault()
this.selectNextNote() this.selectNextNote()
} }
if (e.shiftKey) {
this.setState({ shiftKeyDown: true })
}
}
handleNoteListKeyUp (e) {
if (!e.shiftKey) {
this.setState({ shiftKeyDown: false })
}
} }
getNotes () { getNotes () {
@@ -299,8 +365,24 @@ class NoteList extends React.Component {
} }
handleNoteClick (e, uniqueKey) { handleNoteClick (e, uniqueKey) {
const { router } = this.context let { router } = this.context
const { location } = this.props let { location } = this.props
let { shiftKeyDown, selectedNoteKeys } = this.state
if (shiftKeyDown && selectedNoteKeys.includes(uniqueKey)) {
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
this.setState({
selectedNoteKeys: newSelectedNoteKeys
})
return
}
if (!shiftKeyDown) {
selectedNoteKeys = []
}
selectedNoteKeys.push(uniqueKey)
this.setState({
selectedNoteKeys
})
router.push({ router.push({
pathname: location.pathname, pathname: location.pathname,
@@ -351,16 +433,23 @@ class NoteList extends React.Component {
} }
handleDragStart (e, note) { handleDragStart (e, note) {
const noteData = JSON.stringify(note) const { selectedNoteKeys } = this.state
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const noteData = JSON.stringify(selectedNotes)
e.dataTransfer.setData('note', noteData) e.dataTransfer.setData('note', noteData)
this.setState({ selectedNoteKeys: [] })
} }
handleNoteContextMenu (e, uniqueKey) { handleNoteContextMenu (e, uniqueKey) {
const { location } = this.props const { location } = this.props
const note = this.notes.find((note) => { const { selectedNoteKeys } = this.state
const noteKey = `${note.storage}-${note.key}` const note = findNoteByKey(this.notes, uniqueKey)
return noteKey === uniqueKey const noteKey = getNoteKey(note)
})
if (selectedNoteKeys.length === 0 || !selectedNoteKeys.includes(noteKey)) {
this.handleNoteClick(e, uniqueKey)
}
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top' const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
const deleteLabel = 'Delete Note' const deleteLabel = 'Delete Note'
@@ -369,35 +458,101 @@ class NoteList extends React.Component {
if (!location.pathname.match(/\/home|\/starred|\/trash/)) { if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
menu.append(new MenuItem({ menu.append(new MenuItem({
label: pinLabel, label: pinLabel,
click: (e) => this.pinToTop(e, uniqueKey) click: this.pinToTop
})) }))
} }
menu.append(new MenuItem({ menu.append(new MenuItem({
label: deleteLabel, label: deleteLabel,
click: (e) => this.deleteNote(e, uniqueKey) click: this.deleteNote
})) }))
menu.popup() menu.popup()
} }
pinToTop (e, uniqueKey) { pinToTop () {
this.handleNoteClick(e, uniqueKey) const { selectedNoteKeys } = this.state
const targetIndex = this.getTargetIndex() const { dispatch } = this.props
const note = this.notes[targetIndex] const notes = this.notes.map((note) => Object.assign({}, note))
note.isPinned = !note.isPinned const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
dataApi Promise.all(
.updateNote(note.storage, note.key, note) selectedNotes.map((note) => {
.then((note) => { note.isPinned = !note.isPinned
store.dispatch({ return dataApi
.updateNote(note.storage, note.key, note)
})
)
.then((updatedNotes) => {
updatedNotes.forEach((note) => {
dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
note: note note
}) })
}) })
})
this.setState({ selectedNoteKeys: [] })
} }
deleteNote (e, uniqueKey) { deleteNote () {
this.handleNoteClick(e, uniqueKey) const { dispatch } = this.props
ee.emit('detail:delete') const { selectedNoteKeys } = this.state
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
if (firstNote.isTrashed) {
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note'
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Confirm note deletion',
detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`,
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
Promise.all(
selectedNoteKeys.map((uniqueKey) => {
const storageKey = uniqueKey.split('-')[0]
const noteKey = uniqueKey.split('-')[1]
return dataApi
.deleteNote(storageKey, noteKey)
})
)
.then((data) => {
data.forEach((item) => {
dispatch({
type: 'DELETE_NOTE',
storageKey: item.storageKey,
noteKey: item.noteKey
})
})
})
.catch((err) => {
console.error('Cannot Delete note: ' + err)
})
console.log('Notes were all deleted')
} else {
Promise.all(
selectedNotes.map((note) => {
note.isTrashed = true
return dataApi
.updateNote(note.storage, note.key, note)
})
)
.then((newNotes) => {
newNotes.forEach((newNote) => {
dispatch({
type: 'UPDATE_NOTE',
note: newNote
})
})
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
console.log('Notes went to trash')
})
.catch((err) => {
console.error('Notes could not go to trash: ' + err)
})
}
this.setState({ selectedNoteKeys: [] })
} }
importFromFile () { importFromFile () {
@@ -444,7 +599,7 @@ class NoteList extends React.Component {
}) })
hashHistory.push({ hashHistory.push({
pathname: location.pathname, pathname: location.pathname,
query: {key: `${note.storage}-${note.key}`} query: {key: getNoteKey(note)}
}) })
}) })
}) })
@@ -454,7 +609,7 @@ class NoteList extends React.Component {
getTargetIndex () { getTargetIndex () {
const { location } = this.props const { location } = this.props
const targetIndex = _.findIndex(this.notes, (note) => { const targetIndex = _.findIndex(this.notes, (note) => {
return `${note.storage}-${note.key}` === location.query.key return getNoteKey(note) === location.query.key
}) })
return targetIndex return targetIndex
} }
@@ -490,9 +645,9 @@ class NoteList extends React.Component {
} }
render () { render () {
const { location, config } = this.props let { location, notes, config, dispatch } = this.props
let { notes } = this.props let { selectedNoteKeys } = this.state
const sortFunc = config.sortBy === 'CREATED_AT' let sortFunc = config.sortBy === 'CREATED_AT'
? sortByCreatedAt ? sortByCreatedAt
: config.sortBy === 'ALPHABETICAL' : config.sortBy === 'ALPHABETICAL'
? sortByAlphabetical ? sortByAlphabetical
@@ -531,7 +686,8 @@ class NoteList extends React.Component {
} }
const isDefault = config.listStyle === 'DEFAULT' const isDefault = config.listStyle === 'DEFAULT'
const isActive = location.query.key === note.storage + '-' + note.key const uniqueKey = getNoteKey(note)
const isActive = selectedNoteKeys.includes(uniqueKey)
const dateDisplay = moment( const dateDisplay = moment(
config.sortBy === 'CREATED_AT' config.sortBy === 'CREATED_AT'
? note.createdAt : note.updatedAt ? note.createdAt : note.updatedAt
@@ -544,7 +700,7 @@ class NoteList extends React.Component {
isActive={isActive} isActive={isActive}
note={note} note={note}
dateDisplay={dateDisplay} dateDisplay={dateDisplay}
key={key} key={uniqueKey}
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)} handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
handleNoteClick={this.handleNoteClick.bind(this)} handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)} handleDragStart={this.handleDragStart.bind(this)}
@@ -557,7 +713,7 @@ class NoteList extends React.Component {
<NoteItemSimple <NoteItemSimple
isActive={isActive} isActive={isActive}
note={note} note={note}
key={key} key={uniqueKey}
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)} handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
handleNoteClick={this.handleNoteClick.bind(this)} handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)} handleDragStart={this.handleDragStart.bind(this)}
@@ -606,6 +762,7 @@ class NoteList extends React.Component {
ref='list' ref='list'
tabIndex='-1' tabIndex='-1'
onKeyDown={(e) => this.handleNoteListKeyDown(e)} onKeyDown={(e) => this.handleNoteListKeyDown(e)}
onKeyUp={this.handleNoteListKeyUp}
> >
{noteList} {noteList}
</div> </div>

View File

@@ -143,40 +143,49 @@ class StorageItem extends React.Component {
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor') e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
} }
dropNote (storage, folder, dispatch, location, noteData) {
noteData = noteData.filter((note) => folder.key !== note.folder)
if (noteData.length === 0) return
const newNoteData = noteData.map((note) => Object.assign({}, note, {storage: storage, folder: folder.key}))
Promise.all(
newNoteData.map((note) => dataApi.createNote(storage.key, note))
)
.then((createdNoteData) => {
createdNoteData.forEach((note) => {
dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
})
.catch((err) => {
console.error(`error on create notes: ${err}`)
})
.then(() => {
return Promise.all(
noteData.map((note) => dataApi.deleteNote(note.storage, note.key))
)
})
.then((deletedNoteData) => {
deletedNoteData.forEach((note) => {
dispatch({
type: 'DELETE_NOTE',
storageKey: note.storageKey,
noteKey: note.noteKey
})
})
})
.catch((err) => {
console.error(`error on delete notes: ${err}`)
})
}
handleDrop (e, storage, folder, dispatch, location) { handleDrop (e, storage, folder, dispatch, location) {
e.target.style.opacity = '1' e.target.style.opacity = '1'
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor') e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
const noteData = JSON.parse(e.dataTransfer.getData('note')) const noteData = JSON.parse(e.dataTransfer.getData('note'))
const newNoteData = Object.assign({}, noteData, {storage: storage, folder: folder.key}) this.dropNote(storage, folder, dispatch, location, noteData)
if (folder.key === noteData.folder) return
dataApi
.createNote(storage.key, newNoteData)
.then((note) => {
dataApi
.deleteNote(noteData.storage, noteData.key)
.then((data) => {
const dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
storageKey: data.storageKey,
noteKey: data.noteKey
})
}
eventEmitter.once('list:moved', dispatchHandler)
eventEmitter.emit('list:next')
})
.catch((err) => {
console.error(err)
})
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: `${note.storage}-${note.key}`}
})
})
} }
render () { render () {
@@ -228,8 +237,8 @@ class StorageItem extends React.Component {
onMouseDown={(e) => this.handleToggleButtonClick(e)} onMouseDown={(e) => this.handleToggleButtonClick(e)}
> >
<img src={this.state.isOpen <img src={this.state.isOpen
? '../resources/icon/icon-down.svg' ? '../resources/icon/icon-down.svg'
: '../resources/icon/icon-right.svg' : '../resources/icon/icon-right.svg'
} }
/> />
</button> </button>