diff --git a/browser/components/SideNavFilter.js b/browser/components/SideNavFilter.js index dba26f92..2f839a84 100644 --- a/browser/components/SideNavFilter.js +++ b/browser/components/SideNavFilter.js @@ -17,7 +17,7 @@ import styles from './SideNavFilter.styl' const SideNavFilter = ({ isFolded, isHomeActive, handleAllNotesButtonClick, isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote, - counterTotalNote, counterStarredNote + counterTotalNote, counterStarredNote, handleFilterButtonContextMenu }) => (
@@ -26,9 +26,9 @@ const SideNavFilter = ({ >
All Notes @@ -40,9 +40,9 @@ const SideNavFilter = ({ >
Starred @@ -54,12 +54,12 @@ const SideNavFilter = ({ >
- Trash + Trash {counterDelNote} diff --git a/browser/components/TagListItem.js b/browser/components/TagListItem.js index 2625412a..ebef7df4 100644 --- a/browser/components/TagListItem.js +++ b/browser/components/TagListItem.js @@ -12,10 +12,11 @@ import CSSModules from 'browser/lib/CSSModules' * @param {bool} isActive */ -const TagListItem = ({name, handleClickTagListItem, isActive}) => ( +const TagListItem = ({name, handleClickTagListItem, isActive, count}) => ( ) diff --git a/browser/components/TagListItem.styl b/browser/components/TagListItem.styl index cd3a5387..b35b30cf 100644 --- a/browser/components/TagListItem.styl +++ b/browser/components/TagListItem.styl @@ -48,6 +48,9 @@ overflow hidden text-overflow ellipsis +.tagList-item-count + padding 0 3px + body[data-theme="white"] .tagList-item color $ui-inactive-text-color @@ -63,6 +66,8 @@ body[data-theme="white"] color $ui-text-color &:hover background-color alpha($ui-button--active-backgroundColor, 60%) + .tagList-item-count + color $ui-text-color body[data-theme="dark"] .tagList-item @@ -81,4 +86,6 @@ body[data-theme="dark"] background-color alpha($ui-dark-button--active-backgroundColor, 50%) &:hover color $ui-dark-text-color - background-color alpha($ui-dark-button--active-backgroundColor, 50%) \ No newline at end of file + background-color alpha($ui-dark-button--active-backgroundColor, 50%) + .tagList-item-count + color $ui-dark-button--active-color diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 7694f1e7..a543a5aa 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -19,6 +19,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import ConfigManager from 'browser/main/lib/ConfigManager' import TrashButton from './TrashButton' import FullscreenButton from './FullscreenButton' +import RestoreButton from './RestoreButton' import PermanentDeleteButton from './PermanentDeleteButton' import InfoButton from './InfoButton' import ToggleModeButton from './ToggleModeButton' @@ -321,10 +322,7 @@ class MarkdownNoteDetail extends React.Component { const trashTopBar =
- this.handleUndoButtonClick(e)} - /> + this.handleUndoButtonClick(e)} />
this.handleTrashButtonClick(e)} /> @@ -359,12 +357,10 @@ class MarkdownNoteDetail extends React.Component { value={this.state.note.tags} onChange={this.handleUpdateTag.bind(this)} /> - - this.handleSwitchMode(e)} editorType={editorType} /> -
+ this.handleSwitchMode(e)} editorType={editorType} /> this.handleStarButtonClick(e)} isActive={note.isStarred} diff --git a/browser/main/Detail/MarkdownNoteDetail.styl b/browser/main/Detail/MarkdownNoteDetail.styl index 652532c7..ad20f0f2 100644 --- a/browser/main/Detail/MarkdownNoteDetail.styl +++ b/browser/main/Detail/MarkdownNoteDetail.styl @@ -7,6 +7,7 @@ background-color $ui-noteDetail-backgroundColor box-shadow none padding 20px 40px + overflow hidden .lock-button padding-bottom 3px @@ -44,7 +45,7 @@ margin 0 30px .body-noteEditor absolute top bottom left right - + body[data-theme="white"] .root box-shadow $note-detail-box-shadow diff --git a/browser/main/Detail/RestoreButton.js b/browser/main/Detail/RestoreButton.js new file mode 100644 index 00000000..0f9c992e --- /dev/null +++ b/browser/main/Detail/RestoreButton.js @@ -0,0 +1,21 @@ +import PropTypes from 'prop-types' +import React from 'react' +import CSSModules from 'browser/lib/CSSModules' +import styles from './RestoreButton.styl' + +const RestoreButton = ({ + onClick +}) => ( + +) + +RestoreButton.propTypes = { + onClick: PropTypes.func.isRequired +} + +export default CSSModules(RestoreButton, styles) diff --git a/browser/main/Detail/RestoreButton.styl b/browser/main/Detail/RestoreButton.styl new file mode 100644 index 00000000..58ce745d --- /dev/null +++ b/browser/main/Detail/RestoreButton.styl @@ -0,0 +1,22 @@ +.control-restoreButton + top 115px + topBarButtonRight() + &:hover .tooltip + opacity 1 + +.tooltip + tooltip() + position absolute + pointer-events none + top 50px + left 25px + z-index 200 + padding 5px + line-height normal + border-radius 2px + opacity 0 + transition 0.1s + +body[data-theme="dark"] + .control-restoreButton + topBarButtonDark() diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 791b490e..dbef37ca 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -20,6 +20,7 @@ import _ from 'lodash' import { findNoteTitle } from 'browser/lib/findNoteTitle' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import TrashButton from './TrashButton' +import RestoreButton from './RestoreButton' import PermanentDeleteButton from './PermanentDeleteButton' import InfoButton from './InfoButton' import InfoPanel from './InfoPanel' @@ -589,10 +590,7 @@ class SnippetNoteDetail extends React.Component { const trashTopBar =
- this.handleUndoButtonClick(e)} - /> + this.handleUndoButtonClick(e)} />
this.handleTrashButtonClick(e)} /> diff --git a/browser/main/Detail/ToggleModeButton.styl b/browser/main/Detail/ToggleModeButton.styl index c69401f8..185a780c 100644 --- a/browser/main/Detail/ToggleModeButton.styl +++ b/browser/main/Detail/ToggleModeButton.styl @@ -5,8 +5,8 @@ width 52px display flex align-items center - position absolute - right 165px + position: relative + top 2px .active background-color #1EC38B width 33px @@ -55,4 +55,4 @@ body[data-theme="solarized-dark"] background-color #002B36 .active background-color #1EC38B - box-shadow 2px 0px 7px #222222 \ No newline at end of file + box-shadow 2px 0px 7px #222222 diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index 6ba45faa..ff003479 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -69,6 +69,7 @@ class NoteList extends React.Component { this.getNoteStorage = this.getNoteStorage.bind(this) this.getNoteFolder = this.getNoteFolder.bind(this) this.getViewType = this.getViewType.bind(this) + this.restoreNote = this.restoreNote.bind(this) // TODO: not Selected noteKeys but SelectedNote(for reusing) this.state = { @@ -456,6 +457,7 @@ class NoteList extends React.Component { const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top' const deleteLabel = 'Delete Note' const cloneNote = 'Clone Note' + const restoreNote = 'Restore Note' const menu = new Menu() if (!location.pathname.match(/\/starred|\/trash/)) { @@ -464,6 +466,14 @@ class NoteList extends React.Component { click: this.pinToTop })) } + + if (location.pathname.match(/\/trash/)) { + menu.append(new MenuItem({ + label: restoreNote, + click: this.restoreNote + })) + } + menu.append(new MenuItem({ label: deleteLabel, click: this.deleteNote @@ -475,28 +485,50 @@ class NoteList extends React.Component { menu.popup() } - pinToTop () { + updateSelectedNotes (updateFunc, cleanSelection = true) { const { selectedNoteKeys } = this.state const { dispatch } = this.props const notes = this.notes.map((note) => Object.assign({}, note)) const selectedNotes = findNotesByKeys(notes, selectedNoteKeys) + if (!_.isFunction(updateFunc)) { + console.warn('Update function is not defined. No update will happen') + updateFunc = (note) => { return note } + } + Promise.all( - selectedNotes.map((note) => { - note.isPinned = !note.isPinned - return dataApi - .updateNote(note.storage, note.key, note) - }) - ) - .then((updatedNotes) => { - updatedNotes.forEach((note) => { - dispatch({ - type: 'UPDATE_NOTE', - note + selectedNotes.map((note) => { + note = updateFunc(note) + return dataApi + .updateNote(note.storage, note.key, note) }) - }) + ) + .then((updatedNotes) => { + updatedNotes.forEach((note) => { + dispatch({ + type: 'UPDATE_NOTE', + note + }) + }) + }) + + if (cleanSelection) { + this.selectNextNote() + } + } + + pinToTop () { + this.updateSelectedNotes((note) => { + note.isPinned = !note.isPinned + return note + }) + } + + restoreNote () { + this.updateSelectedNotes((note) => { + note.isTrashed = false + return note }) - this.setState({ selectedNoteKeys: [] }) } deleteNote () { diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index 9b95ae3e..6d05e37b 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -1,6 +1,9 @@ import PropTypes from 'prop-types' import React from 'react' import CSSModules from 'browser/lib/CSSModules' +const { remote } = require('electron') +const { Menu } = remote +import dataApi from 'browser/main/lib/dataApi' import styles from './SideNav.styl' import { openModal } from 'browser/main/lib/modal' import PreferencesModal from '../modals/PreferencesModal' @@ -89,6 +92,7 @@ class SideNav extends React.Component { counterTotalNote={data.noteMap._map.size - data.trashedSet._set.size} counterStarredNote={data.starredSet._set.size} counterDelNote={data.trashedSet._set.size} + handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)} /> @@ -113,18 +117,21 @@ class SideNav extends React.Component { tagListComponent () { const { data, location } = this.props - const tagList = data.tagNoteMap.map((tag, key) => { - return key + const tagList = data.tagNoteMap.map((tag, name) => { + return { name, size: tag.size } }) return ( - tagList.map(tag => ( - - )) + tagList.map(tag => { + return ( + + ) + }) ) } @@ -139,6 +146,34 @@ class SideNav extends React.Component { router.push(`/tags/${name}`) } + emptyTrash (entries) { + const { dispatch } = this.props + const deletionPromises = entries.map((storageAndNoteKey) => { + const storageKey = storageAndNoteKey.split('-')[0] + const noteKey = storageAndNoteKey.split('-')[1] + return dataApi.deleteNote(storageKey, noteKey) + }) + Promise.all(deletionPromises) + .then((arrayOfStorageAndNoteKeys) => { + arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => { + dispatch({ type: 'DELETE_NOTE', storageKey, noteKey }) + }) + }) + .catch((err) => { + console.error('Cannot Delete note: ' + err) + }) + console.log('Trash emptied') + } + + handleFilterButtonContextMenu (event) { + const { data } = this.props + const entries = data.trashedSet.toJS() + const menu = Menu.buildFromTemplate([ + { label: 'Empty Trash', click: () => this.emptyTrash(entries) } + ]) + menu.popup() + } + render () { const { data, location, config, dispatch } = this.props