From 2650cc2f1c7aeeebf159258fc5d010be5a81852e Mon Sep 17 00:00:00 2001 From: asmsuechan Date: Wed, 12 Jul 2017 15:34:40 +0900 Subject: [PATCH 1/5] Add trash can --- browser/components/NoteItem.js | 3 +- browser/components/SideNavFilter.js | 12 +- browser/main/Detail/MarkdownNoteDetail.js | 192 +++++++++++++--------- browser/main/Detail/NoteDetailInfo.styl | 12 ++ browser/main/Detail/SnippetNoteDetail.js | 154 ++++++++++------- browser/main/NoteList/index.js | 9 + browser/main/SideNav/index.js | 8 + browser/main/index.js | 1 + browser/main/lib/dataApi/createNote.js | 1 + browser/main/lib/dataApi/updateNote.js | 5 + browser/main/store.js | 40 ++++- 11 files changed, 295 insertions(+), 142 deletions(-) diff --git a/browser/components/NoteItem.js b/browser/components/NoteItem.js index 92aefad62..a4e9ca487 100644 --- a/browser/components/NoteItem.js +++ b/browser/components/NoteItem.js @@ -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, diff --git a/browser/components/SideNavFilter.js b/browser/components/SideNavFilter.js index 84cc97290..cce4d5d05 100644 --- a/browser/components/SideNavFilter.js +++ b/browser/components/SideNavFilter.js @@ -15,7 +15,7 @@ import styles from './SideNavFilter.styl' */ const SideNavFilter = ({ isFolded, isHomeActive, handleAllNotesButtonClick, - isStarredActive, handleStarredButtonClick + isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick }) => (
+
) @@ -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) diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index ba64c5905..11cbf77e7 100644 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -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,30 +176,59 @@ 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) { - let { note, dispatch } = this.props - dataApi - .deleteNote(note.storage, note.key) - .then((data) => { - let dispatchHandler = () => { - dispatch({ - type: 'DELETE_NOTE', - storageKey: data.storageKey, - noteKey: data.noteKey - }) - } - ee.once('list:moved', dispatchHandler) - ee.emit('list:next') + 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) + .then((data) => { + let dispatchHandler = () => { + dispatch({ + type: 'DELETE_NOTE', + storageKey: data.storageKey, + noteKey: data.noteKey + }) + } + ee.once('list:moved', dispatchHandler) + }) + } + 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) { ee.emit('editor:fullscreen') } @@ -254,72 +283,79 @@ class MarkdownNoteDetail extends React.Component { }) let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0] + const trashTopBar =
+
+
+
+ this.handleUndoButtonClick(e)} + /> +
+
+
+
+ this.handleTrashButtonClick(e)} /> +
+
+ + const detailTopBar =
+
+ this.handleStarButtonClick(e)} + isActive={note.isStarred} + /> +
+ this.handleFolderChange(e)} + /> +
+ + this.handleChange(e)} + /> + +
+
+ {(() => { + const faClassName = `fa ${this.getToggleLockButton()}` + const lockButtonComponent = + + return ( + this.state.isLockButtonShown ? lockButtonComponent : '' + ) + })()} + this.handleTrashButtonClick(e)} /> + +
+
+ return (
-
-
- this.handleStarButtonClick(e)} - isActive={note.isStarred} - /> -
- this.handleFolderChange(e)} - /> -
- - this.handleChange(e)} - /> - -
-
- {(() => { - const faClassName = `fa ${this.getToggleLockButton()}` - const lockButtonComponent = - - return ( - this.state.isLockButtonShown ? lockButtonComponent : '' - ) - })()} - this.handleTrashButtonClick(e)} - /> - - this.handleInfoButtonClick(e)} - /> - -
-
+ + {location.pathname === '/trashed' ? trashTopBar : detailTopBar}
{ 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,30 +170,58 @@ 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) { - let { note, dispatch } = this.props - dataApi - .deleteNote(note.storage, note.key) - .then((data) => { - let dispatchHandler = () => { - dispatch({ - type: 'DELETE_NOTE', - storageKey: data.storageKey, - noteKey: data.noteKey - }) - } - ee.once('list:moved', dispatchHandler) - ee.emit('list:next') + 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) + .then((data) => { + let dispatchHandler = () => { + dispatch({ + type: 'DELETE_NOTE', + storageKey: data.storageKey, + noteKey: data.noteKey + }) + } + ee.once('list:moved', dispatchHandler) + }) + } + ee.emit('list:next') } } + handleUndoButtonClick (e) { + let { note } = this.state + + note.isTrashed = false + + this.setState({ + note + }, () => { + this.save() + ee.emit('list:next') + }) + } + handleFullScreenButton (e) { ee.emit('editor:fullscreen') } @@ -513,54 +541,60 @@ class SnippetNoteDetail extends React.Component { }) let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0] + const trashTopBar =
+
+
+
+ this.handleUndoButtonClick(e)} + /> +
+
+
+
+ this.handleTrashButtonClick(e)} /> +
+
+ + const detailTopBar =
+
+ this.handleStarButtonClick(e)} + isActive={note.isStarred} + /> +
+ this.handleFolderChange(e)} + /> +
+ + this.handleChange(e)} + /> +
+
+ this.handleTrashButtonClick(e)} /> + +
+
+ return (
this.handleKeyDown(e)} > -
-
- this.handleStarButtonClick(e)} - isActive={note.isStarred} - /> -
- this.handleFolderChange(e)} - /> -
- - this.handleChange(e)} - /> -
-
- this.handleTrashButtonClick(e)} - /> - - this.handleInfoButtonClick(e)} - /> - -
-
+ {location.pathname === '/trashed' ? trashTopBar : detailTopBar}
diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index 5d0264860..399687c5c 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -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 => { diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index f31ae96c6..909c6777f 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -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 this.handleHomeButtonClick(e)} isStarredActive={isStarredActive} + isTrashedActive={isTrashedActive} handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)} + handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)} />
diff --git a/browser/main/index.js b/browser/main/index.js index 07774fa6e..d36a4c9ee 100644 --- a/browser/main/index.js +++ b/browser/main/index.js @@ -60,6 +60,7 @@ ReactDOM.render(( + diff --git a/browser/main/lib/dataApi/createNote.js b/browser/main/lib/dataApi/createNote.js index 83ea85e4f..e0acc74e9 100644 --- a/browser/main/lib/dataApi/createNote.js +++ b/browser/main/lib/dataApi/createNote.js @@ -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': diff --git a/browser/main/lib/dataApi/updateNote.js b/browser/main/lib/dataApi/updateNote.js index 7dc4c6c78..f1b1f2329 100644 --- a/browser/main/lib/dataApi/updateNote.js +++ b/browser/main/lib/dataApi/updateNote.js @@ -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 = [] } diff --git a/browser/main/store.js b/browser/main/store.js index 628e297ec..68c468768 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -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) => { From cbff5fb585f1f8fe8ec1ba25c6fd248060a1e579 Mon Sep 17 00:00:00 2001 From: asmsuechan Date: Fri, 7 Jul 2017 18:15:34 +0900 Subject: [PATCH 2/5] Fix indents pointed by lint --- browser/main/Detail/SnippetNoteDetail.js | 52 ++++++++++++------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index c7e5d4bb2..de88efba9 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -558,35 +558,35 @@ class SnippetNoteDetail extends React.Component {
const detailTopBar =
-
- this.handleStarButtonClick(e)} - isActive={note.isStarred} - /> -
- this.handleFolderChange(e)} - /> -
- - this.handleChange(e)} +
+ this.handleStarButtonClick(e)} + isActive={note.isStarred} + /> +
+ this.handleFolderChange(e)} />
-
- this.handleTrashButtonClick(e)} /> - -
+ + this.handleChange(e)} + />
+
+ this.handleTrashButtonClick(e)} /> + +
+
return (
Date: Fri, 7 Jul 2017 19:31:03 +0900 Subject: [PATCH 3/5] Escape from addition a note when the note is in the trashbox --- browser/main/TopBar/index.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/browser/main/TopBar/index.js b/browser/main/TopBar/index.js index 66478ecf0..5e56bedea 100644 --- a/browser/main/TopBar/index.js +++ b/browser/main/TopBar/index.js @@ -9,6 +9,9 @@ import ee from 'browser/main/lib/eventEmitter' import ConfigManager from 'browser/main/lib/ConfigManager' import dataApi from 'browser/main/lib/dataApi' +const { remote } = require('electron') +const { dialog } = remote + const OSX = window.process.platform === 'darwin' class TopBar extends React.Component { @@ -41,7 +44,17 @@ class TopBar extends React.Component { } handleNewPostButtonClick (e) { - let { config } = this.props + let { config, location } = this.props + + if (location.pathname === '/trashed') { + dialog.showMessageBox(remote.getCurrentWindow(), { + type: 'warning', + message: 'Cannot create new note', + detail: 'You cannot create new note in trash box.', + buttons: ['OK'] + }) + return + } switch (config.ui.defaultNote) { case 'MARKDOWN_NOTE': From 2d243abc12bb75f0a122d1bcf7e3a501aeb6f935 Mon Sep 17 00:00:00 2001 From: asmsuechan Date: Fri, 7 Jul 2017 20:52:40 +0900 Subject: [PATCH 4/5] Add condition --- browser/main/Detail/MarkdownNoteDetail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 11cbf77e7..699b9a9d9 100644 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -91,7 +91,7 @@ class MarkdownNoteDetail extends React.Component { let { note } = this.state note.content = this.refs.content.value - note.tags = this.refs.tags.value + if (this.refs.tags) note.tags = this.refs.tags.value note.title = markdown.strip(findNoteTitle(note.content)) note.updatedAt = new Date() From 2e628de9c6d68242c95a306492ba2f3636ddb03c Mon Sep 17 00:00:00 2001 From: asmsuechan Date: Sun, 9 Jul 2017 17:33:54 +0900 Subject: [PATCH 5/5] Restore missing components --- browser/main/Detail/MarkdownNoteDetail.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 699b9a9d9..aa9c0b295 100644 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -344,8 +344,18 @@ class MarkdownNoteDetail extends React.Component { + this.handleInfoButtonClick(e)} + /> +