1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

delete note

This commit is contained in:
Dick Choi
2016-07-23 15:28:17 +09:00
parent b2d34ab95d
commit 45b1cd3942
17 changed files with 498 additions and 329 deletions

View File

@@ -23,7 +23,6 @@ export default class CodeEditor extends React.Component {
}
el = el.parentNode
}
console.log(isStillFocused)
if (!isStillFocused && this.props.onBlur != null) this.props.onBlur(e)
}
@@ -165,7 +164,7 @@ export default class CodeEditor extends React.Component {
let syntaxMode = mode != null
? mode.mode
: 'text'
session.setMode('ace/mode' + syntaxMode)
session.setMode('ace/mode/' + syntaxMode)
}
if (prevProps.theme !== this.props.theme) {
editor.setTheme('ace/theme/' + this.props.theme)

View File

@@ -1,144 +0,0 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import ModeIcon from 'browser/components/ModeIcon'
import moment from 'moment'
import FolderMark from 'browser/components/FolderMark'
import _ from 'lodash'
const electron = require('electron')
const remote = electron.remote
const ipc = electron.ipcRenderer
export default class ArticleList extends React.Component {
constructor (props) {
super(props)
this.focusHandler = (e) => this.focus()
}
componentDidMount () {
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
ipc.on('list-focus', this.focusHandler)
this.focus()
}
componentWillUnmount () {
clearInterval(this.refreshTimer)
ipc.removeListener('list-focus', this.focusHandler)
}
componentDidUpdate () {
return false
var index = articles.indexOf(null)
var el = ReactDOM.findDOMNode(this)
var li = el.querySelectorAll('.ArticleList>div')[index]
if (li == null) {
return
}
var overflowBelow = el.clientHeight + el.scrollTop < li.offsetTop + li.clientHeight
if (overflowBelow) {
el.scrollTop = li.offsetTop + li.clientHeight - el.clientHeight
}
var overflowAbove = el.scrollTop > li.offsetTop
if (overflowAbove) {
el.scrollTop = li.offsetTop
}
}
focus () {
ReactDOM.findDOMNode(this).focus()
}
// 移動ができなかったらfalseを返す:
selectPriorArticle () {
let { articles, activeArticle, dispatch } = this.props
let targetIndex = articles.indexOf(activeArticle) - 1
let targetArticle = articles[targetIndex]
if (targetArticle != null) {
dispatch(switchArticle(targetArticle.key))
return true
}
return false
}
selectNextArticle () {
let { articles, activeArticle, dispatch } = this.props
let targetIndex = articles.indexOf(activeArticle) + 1
let targetArticle = articles[targetIndex]
if (targetArticle != null) {
dispatch(switchArticle(targetArticle.key))
return true
}
return false
}
handleArticleClick (article) {
let { dispatch } = this.props
return function (e) {
dispatch(switchArticle(article.key))
}
}
handleArticleListKeyDown (e) {
if (e.metaKey || e.ctrlKey) return true
if (e.keyCode === 65 && !e.shiftKey) {
e.preventDefault()
remote.getCurrentWebContents().send('top-new-post')
}
if (e.keyCode === 65 && e.shiftKey) {
e.preventDefault()
remote.getCurrentWebContents().send('nav-new-folder')
}
if (e.keyCode === 68) {
e.preventDefault()
remote.getCurrentWebContents().send('detail-delete')
}
if (e.keyCode === 84) {
e.preventDefault()
remote.getCurrentWebContents().send('detail-title')
}
if (e.keyCode === 69) {
e.preventDefault()
remote.getCurrentWebContents().send('detail-edit')
}
if (e.keyCode === 83) {
e.preventDefault()
remote.getCurrentWebContents().send('detail-save')
}
if (e.keyCode === 38) {
e.preventDefault()
this.selectPriorArticle()
}
if (e.keyCode === 40) {
e.preventDefault()
this.selectNextArticle()
}
}
render () {
let articleElements = []
return (
<div tabIndex='3' onKeyDown={(e) => this.handleArticleListKeyDown(e)} className='ArticleList'>
{articleElements}
</div>
)
}
}
ArticleList.propTypes = {
dispatch: PropTypes.func,
repositories: PropTypes.array
}

View File

@@ -5,9 +5,9 @@ import MarkdownEditor from 'browser/components/MarkdownEditor'
import StarButton from './StarButton'
import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect'
import Commander from 'browser/main/lib/Commander'
import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
const electron = require('electron')
const { remote } = electron
@@ -22,31 +22,22 @@ class MarkdownNoteDetail extends React.Component {
note: Object.assign({
title: '',
content: '',
isMovingNote: false
isMovingNote: false,
isDeleting: false
}, props.note)
}
this.dispatchTimer = null
}
componentDidMount () {
Commander.bind('note-detail', this)
}
componentWillUnmount () {
Commander.release(this)
}
fire (command) {
switch (command) {
case 'focus':
focus () {
this.refs.content.focus()
}
}
componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
this.setState({
note: Object.assign({}, nextProps.note)
note: Object.assign({}, nextProps.note),
isDeleting: false
}, () => {
this.refs.content.reload()
this.refs.tags.reset()
@@ -176,11 +167,39 @@ class MarkdownNoteDetail extends React.Component {
let menu = new Menu()
menu.append(new MenuItem({
label: 'Delete',
click: (e) => this.handlePreferencesButtonClick(e)
click: (e) => this.handleDeleteMenuClick(e)
}))
menu.popup(remote.getCurrentWindow())
}
handleDeleteMenuClick (e) {
this.setState({
isDeleting: true
})
}
handleDeleteConfirmButtonClick (e) {
let { note, dispatch } = this.props
dataApi
.removeNote(note.storage, note.folder, note.key)
.then(() => {
let dispatchHandler = () => {
dispatch({
type: 'REMOVE_NOTE',
note: note
})
}
ee.once('list:moved', dispatchHandler)
ee.emit('list:next')
})
}
handleDeleteCancelButtonClick (e) {
this.setState({
isDeleting: false
})
}
render () {
let { storages, config } = this.props
let { note } = this.state
@@ -190,9 +209,23 @@ class MarkdownNoteDetail extends React.Component {
style={this.props.style}
styleName='root'
>
<div styleName='info'>
<div styleName='info-left'>
{this.state.isDeleting
? <div styleName='info'>
<div styleName='info-delete'>
<span styleName='info-delete-message'>
Are you sure to delete this note?
</span>
<button styleName='info-delete-cancelButton'
onClick={(e) => this.handleDeleteCancelButtonClick(e)}
>Cancel</button>
<button styleName='info-delete-confirmButton'
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
>Confirm</button>
</div>
</div>
: <div styleName='info'>
<div styleName='info-left'>
<div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
@@ -227,6 +260,7 @@ class MarkdownNoteDetail extends React.Component {
</button>
</div>
</div>
}
<div styleName='body'>
<MarkdownEditor
ref='content'

View File

@@ -12,6 +12,36 @@ $info-height = 75px
border-bottom $ui-border
background-color $ui-backgroundColor
.info-delete
height 80px
clearfix()
.info-delete-message
height 80px
line-height 80px
padding 0 25px
float left
.info-delete-confirmButton
float right
margin 25px 5px 0
height 30px
padding 0 25px
border-radius 2px
border none
color $ui-text-color
colorDangerButton()
.info-delete-cancelButton
float right
height 30px
margin 25px 5px 0
padding 0 25px
border $ui-border
border-radius 2px
color $ui-text-color
colorDefaultButton()
.info-left
float left
padding 0 5px

View File

@@ -5,10 +5,10 @@ import CodeEditor from 'browser/components/CodeEditor'
import StarButton from './StarButton'
import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect'
import Commander from 'browser/main/lib/Commander'
import dataApi from 'browser/main/lib/dataApi'
import modes from 'browser/lib/modes'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
const electron = require('electron')
const { remote } = electron
@@ -25,34 +25,26 @@ class SnippetNoteDetail extends React.Component {
description: ''
}, props.note, {
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
})
}),
isDeleting: false
}
}
componentDidMount () {
Commander.bind('note-detail', this)
}
componentWillUnmount () {
Commander.release(this)
}
fire (command) {
switch (command) {
case 'focus':
focus () {
this.refs.description.focus()
}
}
componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key) {
this.setState({
snippetIndex: 0,
note: Object.assign({
let nextNote = Object.assign({
description: ''
}, nextProps.note, {
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
})
this.setState({
snippetIndex: 0,
note: nextNote,
isDeleting: false
}, () => {
let { snippets } = this.state.note
snippets.forEach((snippet, index) => {
@@ -171,6 +163,7 @@ class SnippetNoteDetail extends React.Component {
let menu = new Menu()
menu.append(new MenuItem({
label: 'Export as a File',
disabled: true,
click: (e) => this.handlePreferencesButtonClick(e)
}))
menu.append(new MenuItem({
@@ -185,11 +178,39 @@ class SnippetNoteDetail extends React.Component {
let menu = new Menu()
menu.append(new MenuItem({
label: 'Delete',
click: (e) => this.handlePreferencesButtonClick(e)
click: (e) => this.handleDeleteMenuClick(e)
}))
menu.popup(remote.getCurrentWindow())
}
handleDeleteMenuClick (e) {
this.setState({
isDeleting: true
})
}
handleDeleteConfirmButtonClick (e) {
let { note, dispatch } = this.props
dataApi
.removeNote(note.storage, note.folder, note.key)
.then(() => {
let dispatchHandler = () => {
dispatch({
type: 'REMOVE_NOTE',
note: note
})
}
ee.once('list:moved', dispatchHandler)
ee.emit('list:next')
})
}
handleDeleteCancelButtonClick (e) {
this.setState({
isDeleting: false
})
}
handleTabPlusButtonClick (e) {
let { note } = this.state
@@ -360,9 +381,23 @@ class SnippetNoteDetail extends React.Component {
style={this.props.style}
styleName='root'
>
<div styleName='info'>
<div styleName='info-left'>
{this.state.isDeleting
? <div styleName='info'>
<div styleName='info-delete'>
<span styleName='info-delete-message'>
Are you sure to delete this note?
</span>
<button styleName='info-delete-cancelButton'
onClick={(e) => this.handleDeleteCancelButtonClick(e)}
>Cancel</button>
<button styleName='info-delete-confirmButton'
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
>Confirm</button>
</div>
</div>
: <div styleName='info'>
<div styleName='info-left'>
<div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
@@ -397,6 +432,8 @@ class SnippetNoteDetail extends React.Component {
</button>
</div>
</div>
}
<div styleName='body'>
<div styleName='body-description'>
<textarea styleName='body-description-textarea'

View File

@@ -12,6 +12,36 @@ $info-height = 75px
border-bottom $ui-border
background-color $ui-backgroundColor
.info-delete
height 80px
clearfix()
.info-delete-message
height 80px
line-height 80px
padding 0 25px
float left
.info-delete-confirmButton
float right
margin 25px 5px 0
height 30px
padding 0 25px
border-radius 2px
border none
color $ui-text-color
colorDangerButton()
.info-delete-cancelButton
float right
height 30px
margin 25px 5px 0
padding 0 25px
border $ui-border
border-radius 2px
color $ui-text-color
colorDefaultButton()
.info-left
float left
padding 0 5px
@@ -68,6 +98,7 @@ $info-height = 75px
resize none
border none
padding 10px
line-height 1.6
.tabList
absolute left right

View File

@@ -4,14 +4,25 @@ import styles from './Detail.styl'
import _ from 'lodash'
import MarkdownNoteDetail from './MarkdownNoteDetail'
import SnippetNoteDetail from './SnippetNoteDetail'
import dataApi from 'browser/main/lib/dataApi'
const electron = require('electron')
import ee from 'browser/main/lib/eventEmitter'
const OSX = global.process.platform === 'darwin'
class Detail extends React.Component {
componentDidUpdate (prevProps, prevState) {
constructor (props) {
super(props)
this.focusHandler = () => {
this.refs.root.focus()
}
}
componentDidMount () {
ee.on('detail:focus', this.focusHandler)
}
componentWillUnmount () {
ee.off('detail:focus', this.focusHandler)
}
render () {
@@ -35,6 +46,7 @@ class Detail extends React.Component {
<div styleName='root'
style={this.props.style}
tabIndex='0'
ref='root'
>
<div styleName='empty'>
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br/>to create a new post</div>
@@ -48,6 +60,7 @@ class Detail extends React.Component {
<SnippetNoteDetail
note={note}
config={config}
ref='root'
{..._.pick(this.props, [
'dispatch',
'storages',
@@ -63,6 +76,7 @@ class Detail extends React.Component {
<MarkdownNoteDetail
note={note}
config={config}
ref='root'
{..._.pick(this.props, [
'dispatch',
'storages',

View File

@@ -3,18 +3,32 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './NoteList.styl'
import moment from 'moment'
import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter'
class NoteList extends React.Component {
constructor (props) {
super(props)
this.selectNextNoteHandler = () => {
console.log('fired next')
this.selectNextNote()
}
this.selectPriorNoteHandler = () => {
this.selectPriorNote()
}
}
componentDidMount () {
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
ee.on('list:next', this.selectNextNoteHandler)
ee.on('list:prior', this.selectPriorNoteHandler)
}
componentWillUnmount () {
clearInterval(this.refreshTimer)
ee.off('list:next', this.selectNextNoteHandler)
ee.off('list:prior', this.selectPriorNoteHandler)
}
componentDidUpdate () {
@@ -51,10 +65,6 @@ class NoteList extends React.Component {
}
}
focus () {
// ReactDOM.findDOMNode(this).focus()
}
selectPriorNote () {
if (this.notes == null || this.notes.length === 0) {
return
@@ -104,6 +114,7 @@ class NoteList extends React.Component {
key: this.notes[targetIndex].uniqueKey
}
})
ee.emit('list:moved')
}
handleNoteListKeyDown (e) {

View File

@@ -65,7 +65,11 @@ textarea.block-input
modalZIndex= 1000
modalBackColor = transparentify(white, 65%)
.ace_focus
outline-color rgb(59, 153, 252)
outline-offset 0px
outline-style auto
outline-width 5px
.ModalBase
fixed top left bottom right
z-index modalZIndex

View File

@@ -282,6 +282,15 @@ function removeStorage (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
@@ -483,17 +492,31 @@ function updateNote (storageKey, folderKey, noteKey, input) {
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, input) {
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) {
@@ -529,6 +552,7 @@ export default {
init,
addStorage,
removeStorage,
renameStorage,
createFolder,
updateFolder,
removeFolder,

View File

@@ -0,0 +1,26 @@
const electron = require('electron')
const { ipcRenderer, remote } = electron
function on (name, listener) {
ipcRenderer.on(name, listener)
}
function off (name, listener) {
ipcRenderer.removeListener(name, listener)
}
function once (name, listener) {
ipcRenderer.once(name, listener)
}
function emit (name, ...args) {
console.log(name)
remote.getCurrentWindow().webContents.send(name, ...args)
}
export default {
emit,
on,
off,
once
}

View File

@@ -36,7 +36,8 @@ class InitModal extends React.Component {
migrationRequested: true,
isLoading: true,
data: null,
legacyStorageExists: false
legacyStorageExists: false,
isSending: false
}
}
@@ -86,6 +87,9 @@ class InitModal extends React.Component {
}
handleSubmitButtonClick (e) {
this.setState({
isSending: true
}, () => {
dataApi
.addStorage({
name: 'My Storage',
@@ -147,6 +151,13 @@ class InitModal extends React.Component {
hashHistory.push('/storages/' + storage.key)
this.props.close()
})
.catch((err) => {
this.setState({
isSending: false
})
throw err
})
})
}
handleMigrationRequestedChange (e) {
@@ -197,7 +208,15 @@ class InitModal extends React.Component {
<div styleName='body-control'>
<button styleName='body-control-createButton'
onClick={(e) => this.handleSubmitButtonClick(e)}
>Let's Go!</button>
disabled={this.state.isSending}
>
{this.state.isSending
? <span>
<i className='fa fa-spin fa-spinner'/> Loading...
</span>
: 'Let\'s Go!'
}
</button>
</div>
</div>

View File

@@ -27,7 +27,7 @@
top 10px
right 10px
height 30px
width 0 25px
padding 0 25px
border $ui-border
border-radius 2px
color $ui-text-color

View File

@@ -3,6 +3,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './NewNoteModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
class NewNoteModal extends React.Component {
constructor (props) {
@@ -36,6 +37,7 @@ class NewNoteModal extends React.Component {
pathname: location.pathname,
query: {key: note.uniqueKey}
})
ee.emit('detail:focus')
this.props.close()
})
}
@@ -67,6 +69,7 @@ class NewNoteModal extends React.Component {
pathname: location.pathname,
query: {key: note.uniqueKey}
})
ee.emit('detail:focus')
this.props.close()
})
}

View File

@@ -204,6 +204,14 @@ class UnstyledFolderItem extends React.Component {
const FolderItem = CSSModules(UnstyledFolderItem, styles)
class StorageItem extends React.Component {
constructor (props) {
super(props)
this.state = {
isLabelEditing: false
}
}
handleNewFolderButtonClick (e) {
let { storage } = this.props
let input = {
@@ -242,6 +250,35 @@ class StorageItem extends React.Component {
})
}
handleLabelClick (e) {
let { storage } = this.props
this.setState({
isLabelEditing: true,
name: storage.name
}, () => {
this.refs.label.focus()
})
}
handleLabelChange (e) {
this.setState({
name: this.refs.label.value
})
}
handleLabelBlur (e) {
let { storage } = this.props
dataApi
.renameStorage(storage.key, this.state.name)
.then((storage) => {
store.dispatch({
type: 'RENAME_STORAGE',
storage: storage
})
this.setState({
isLabelEditing: false
})
})
}
render () {
let { storage } = this.props
let folderList = storage.folders.map((folder) => {
@@ -253,9 +290,24 @@ class StorageItem extends React.Component {
return (
<div styleName='root' key={storage.key}>
<div styleName='header'>
{this.state.isLabelEditing
? <div styleName='header-label--edit'>
<input styleName='header-label-input'
value={this.state.name}
ref='label'
onChange={(e) => this.handleLabelChange(e)}
onBlur={(e) => this.handleLabelBlur(e)}
/>
</div>
: <div styleName='header-label'
onClick={(e) => this.handleLabelClick(e)}
>
<i className='fa fa-folder-open'/>&nbsp;
{storage.name}&nbsp;
<span styleName='header-path'>({storage.path})</span>
<span styleName='header-label-path'>({storage.path})</span>&nbsp;
<i styleName='header-label-editButton' className='fa fa-pencil'/>
</div>
}
<div styleName='header-control'>
<button styleName='header-control-button'
onClick={(e) => this.handleNewFolderButtonClick(e)}

View File

@@ -10,10 +10,32 @@
border-bottom $default-border
margin-bottom 5px
.header-path
.header-label
float left
cursor pointer
&:hover
.header-label-editButton
opacity 1
.header-label-path
color $ui-inactive-text-color
font-size 10px
margin 0 5px
.header-label-editButton
color $ui-text-color
opacity 0
transition 0.2s
.header-label--edit
@extend .header-label
.header-label-input
height 25px
box-sizing border-box
vertical-align middle
border $ui-border
border-radius 2px
padding 0 5px
.header-control
float right

View File

@@ -18,6 +18,7 @@ function storages (state = [], action) {
case 'ADD_FOLDER':
case 'REMOVE_FOLDER':
case 'UPDATE_STORAGE':
case 'RENAME_STORAGE':
{
let storages = state.slice()
storages = storages
@@ -83,6 +84,12 @@ function notes (state = [], action) {
notes.push(action.newNote)
return notes
}
case 'REMOVE_NOTE':
{
let notes = state.slice()
notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage)
return notes
}
}
return state
}