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

Merge pull request #110 from BoostIO/ui-improvement

Ui improvement for v0.6.5
This commit is contained in:
Dick Choi
2016-09-10 14:26:44 +09:00
committed by GitHub
16 changed files with 414 additions and 79 deletions

View File

@@ -140,7 +140,7 @@ export default class MarkdownPreview extends React.Component {
` `
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme) this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
this.refs.root.contentWindow.document.body.innerHTML = markdown(value) this.refs.root.contentWindow.document.body.innerHTML = markdown.render(value)
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => { Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
el.addEventListener('click', this.anchorClickHandler) el.addEventListener('click', this.anchorClickHandler)

View File

@@ -129,8 +129,40 @@ md.renderer.render = function render (tokens, options, env) {
} }
window.md = md window.md = md
export default function markdown (content) { function strip (input) {
if (!_.isString(content)) content = '' var output = input
try {
return md.render(content) output = output
.replace(/^([\s\t]*)([\*\-\+]|\d\.)\s+/gm, '$1')
.replace(/\n={2,}/g, '\n')
.replace(/~~/g, '')
.replace(/`{3}.*\n/g, '')
.replace(/<(.*?)>/g, '$1')
.replace(/^[=\-]{2,}\s*$/g, '')
.replace(/\[\^.+?\](\: .*?$)?/g, '')
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
.replace(/\!\[.*?\][\[\(].*?[\]\)]/g, '')
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
.replace(/>/g, '')
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
.replace(/^\#{1,6}\s*([^#]*)\s*(\#{1,6})?/gm, '$1')
.replace(/([\*_]{1,3})(\S.*?\S)\1/g, '$2')
.replace(/(`{3,})(.*?)\1/gm, '$2')
.replace(/^-{3,}\s*$/g, '')
.replace(/`(.+?)`/g, '$1')
.replace(/\n{2,}/g, '\n\n')
} catch (e) {
console.error(e)
return input
}
return output
} }
const markdown = {
render: function markdown (content) {
if (!_.isString(content)) content = ''
return md.render(content)
},
strip
}
export default markdown

View File

@@ -200,6 +200,11 @@ class FolderSelect extends React.Component {
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0] let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
if (this.state.search.trim().length > 0) {
let filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
options = options.filter((option) => filter.test(option.folder.name))
}
let optionList = options let optionList = options
.map((option, index) => { .map((option, index) => {
return ( return (

View File

@@ -57,12 +57,13 @@
.search-optionList .search-optionList
position fixed position fixed
max-height 450px
overflow auto
z-index 200 z-index 200
background-color white background-color white
border-radius 2px border-radius 2px
box-shadow 2px 2px 10px gray box-shadow 2px 2px 10px gray
.search-optionList-item .search-optionList-item
height 34px height 34px
width 250px width 250px

View File

@@ -8,6 +8,7 @@ import FolderSelect from './FolderSelect'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router' import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import markdown from 'browser/lib/markdown'
const electron = require('electron') const electron = require('electron')
const { remote } = electron const { remote } = electron
@@ -72,6 +73,8 @@ class MarkdownNoteDetail extends React.Component {
} }
} }
title = markdown.strip(title)
return title return title
} }

View File

@@ -247,25 +247,37 @@ class SnippetNoteDetail extends React.Component {
}) })
} }
handleTabButtonClick (index) { handleTabButtonClick (e, index) {
return (e) => { this.setState({
this.setState({ snippetIndex: index
snippetIndex: index })
}) }
handleTabDeleteButtonClick (e, index) {
if (this.state.note.snippets.length > 1) {
if (this.state.note.snippets[index].content.trim().length > 0) {
let dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a snippet',
detail: 'This work cannot be undone.',
buttons: ['Confirm', 'Cancel']
})
if (dialogIndex === 0) {
this.deleteSnippetByIndex(index)
}
} else {
this.deleteSnippetByIndex(index)
}
} }
} }
handleTabDeleteButtonClick (index) { deleteSnippetByIndex (index) {
return (e) => { let snippets = this.state.note.snippets.slice()
if (this.state.note.snippets.length > 1) { snippets.splice(index, 1)
let snippets = this.state.note.snippets.slice() this.state.note.snippets = snippets
snippets.splice(index, 1) this.setState({
this.state.note.snippets = snippets note: this.state.note
this.setState({ })
note: this.state.note
})
}
}
} }
handleNameInputChange (e, index) { handleNameInputChange (e, index) {
@@ -344,7 +356,7 @@ class SnippetNoteDetail extends React.Component {
key={index} key={index}
> >
<button styleName='tabList-item-button' <button styleName='tabList-item-button'
onClick={(e) => this.handleTabButtonClick(index)(e)} onClick={(e) => this.handleTabButtonClick(e, index)}
> >
{snippet.name.trim().length > 0 {snippet.name.trim().length > 0
? snippet.name ? snippet.name
@@ -355,7 +367,7 @@ class SnippetNoteDetail extends React.Component {
</button> </button>
{note.snippets.length > 1 && {note.snippets.length > 1 &&
<button styleName='tabList-item-deleteButton' <button styleName='tabList-item-deleteButton'
onClick={(e) => this.handleTabDeleteButtonClick(index)(e)} onClick={(e) => this.handleTabDeleteButtonClick(e, index)}
> >
<i className='fa fa-times'/> <i className='fa fa-times'/>
</button> </button>

View File

@@ -20,8 +20,10 @@ class Main extends React.Component {
let { config } = props let { config } = props
this.state = { this.state = {
isSliderFocused: false, isRightSliderFocused: false,
listWidth: config.listWidth listWidth: config.listWidth,
navWidth: config.listWidth,
isLeftSliderFocused: false
} }
} }
@@ -49,17 +51,24 @@ class Main extends React.Component {
}) })
} }
handleSlideMouseDown (e) { handleLeftSlideMouseDown (e) {
e.preventDefault() e.preventDefault()
this.setState({ this.setState({
isSliderFocused: true isLeftSliderFocused: true
})
}
handleRightSlideMouseDown (e) {
e.preventDefault()
this.setState({
isRightSliderFocused: true
}) })
} }
handleMouseUp (e) { handleMouseUp (e) {
if (this.state.isSliderFocused) { if (this.state.isRightSliderFocused) {
this.setState({ this.setState({
isSliderFocused: false isRightSliderFocused: false
}, () => { }, () => {
let { dispatch } = this.props let { dispatch } = this.props
let newListWidth = this.state.listWidth let newListWidth = this.state.listWidth
@@ -71,10 +80,24 @@ class Main extends React.Component {
}) })
}) })
} }
if (this.state.isLeftSliderFocused) {
this.setState({
isLeftSliderFocused: false
}, () => {
let { dispatch } = this.props
let navWidth = this.state.navWidth
// TODO: ConfigManager should dispatch itself.
ConfigManager.set({listWidth: navWidth})
dispatch({
type: 'SET_NAV_WIDTH',
listWidth: navWidth
})
})
}
} }
handleMouseMove (e) { handleMouseMove (e) {
if (this.state.isSliderFocused) { if (this.state.isRightSliderFocused) {
let offset = this.refs.body.getBoundingClientRect().left let offset = this.refs.body.getBoundingClientRect().left
let newListWidth = e.pageX - offset let newListWidth = e.pageX - offset
if (newListWidth < 10) { if (newListWidth < 10) {
@@ -86,6 +109,17 @@ class Main extends React.Component {
listWidth: newListWidth listWidth: newListWidth
}) })
} }
if (this.state.isLeftSliderFocused) {
let navWidth = e.pageX
if (navWidth < 80) {
navWidth = 80
} else if (navWidth > 600) {
navWidth = 600
}
this.setState({
navWidth: navWidth
})
}
} }
render () { render () {
@@ -105,9 +139,20 @@ class Main extends React.Component {
'config', 'config',
'location' 'location'
])} ])}
width={this.state.navWidth}
/> />
{!config.isSideNavFolded &&
<div styleName={this.state.isLeftSliderFocused ? 'slider--active' : 'slider'}
style={{left: this.state.navWidth - 1}}
onMouseDown={(e) => this.handleLeftSlideMouseDown(e)}
draggable='false'
>
<div styleName='slider-hitbox'/>
</div>
}
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'} <div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
ref='body' ref='body'
style={{left: config.isSideNavFolded ? 44 : this.state.navWidth}}
> >
<TopBar style={{width: this.state.listWidth}} <TopBar style={{width: this.state.listWidth}}
{..._.pick(this.props, [ {..._.pick(this.props, [
@@ -127,9 +172,9 @@ class Main extends React.Component {
'location' 'location'
])} ])}
/> />
<div styleName={this.state.isSliderFocused ? 'slider--active' : 'slider'} <div styleName={this.state.isRightSliderFocused ? 'slider--active' : 'slider'}
style={{left: this.state.listWidth}} style={{left: this.state.listWidth}}
onMouseDown={(e) => this.handleSlideMouseDown(e)} onMouseDown={(e) => this.handleRightSlideMouseDown(e)}
draggable='false' draggable='false'
> >
<div styleName='slider-hitbox'/> <div styleName='slider-hitbox'/>
@@ -143,7 +188,7 @@ class Main extends React.Component {
'params', 'params',
'location' 'location'
])} ])}
ignorePreviewPointerEvents={this.state.isSliderFocused} ignorePreviewPointerEvents={this.state.isRightSliderFocused}
/> />
</div> </div>
<StatusBar <StatusBar

View File

@@ -4,6 +4,10 @@ import styles from './NoteList.styl'
import moment from 'moment' import moment from 'moment'
import _ from 'lodash' import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import dataApi from 'browser/main/lib/dataApi'
const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote
class NoteList extends React.Component { class NoteList extends React.Component {
constructor (props) { constructor (props) {
@@ -19,6 +23,10 @@ class NoteList extends React.Component {
this.focusHandler = () => { this.focusHandler = () => {
this.refs.root.focus() this.refs.root.focus()
} }
this.state = {
range: 0
}
} }
componentDidMount () { componentDidMount () {
@@ -28,6 +36,29 @@ class NoteList extends React.Component {
ee.on('lost:focus', this.focusHandler) ee.on('lost:focus', this.focusHandler)
} }
componentWillReceiveProps (nextProps) {
if (nextProps.location.pathname !== this.props.location.pathname) {
this.resetScroll()
}
}
resetScroll () {
this.refs.root.scrollTop = 0
this.setState({
range: 0
})
}
handleScroll (e) {
let notes = this.notes
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 250 && notes.length > this.state.range * 10 + 10) {
this.setState({
range: this.state.range + 1
})
}
}
componentWillUnmount () { componentWillUnmount () {
clearInterval(this.refreshTimer) clearInterval(this.refreshTimer)
@@ -36,7 +67,7 @@ class NoteList extends React.Component {
ee.off('lost:focus', this.focusHandler) ee.off('lost:focus', this.focusHandler)
} }
componentDidUpdate () { componentDidUpdate (prevProps) {
let { location } = this.props let { location } = this.props
if (this.notes.length > 0 && location.query.key == null) { if (this.notes.length > 0 && location.query.key == null) {
let { router } = this.context let { router } = this.context
@@ -50,13 +81,14 @@ class NoteList extends React.Component {
} }
// Auto scroll // Auto scroll
if (_.isString(location.query.key)) { if (_.isString(location.query.key) && prevProps.location.query.key !== location.query.key) {
let targetIndex = _.findIndex(this.notes, (note) => { let targetIndex = _.findIndex(this.notes, (note) => {
return note != null && note.storage + '-' + note.key === location.query.key return note != null && note.storage + '-' + note.key === location.query.key
}) })
if (targetIndex > -1) { if (targetIndex > -1) {
let list = this.refs.root let list = this.refs.root
let item = list.childNodes[targetIndex] let item = list.childNodes[targetIndex]
if (item == null) return false
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0 let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
if (overflowBelow) { if (overflowBelow) {
@@ -185,17 +217,58 @@ class NoteList extends React.Component {
: [] : []
} }
handleNoteClick (uniqueKey) { handleNoteClick (e, uniqueKey) {
return (e) => { let { router } = this.context
let { router } = this.context let { location } = this.props
let { location } = this.props
router.push({ router.push({
pathname: location.pathname, pathname: location.pathname,
query: { query: {
key: uniqueKey key: uniqueKey
} }
}) })
}
handleNoteContextMenu (e, uniqueKey) {
let menu = new Menu()
menu.append(new MenuItem({
label: 'Delete Note',
click: (e) => this.handleDeleteNote(e, uniqueKey)
}))
menu.popup()
}
handleDeleteNote (e, uniqueKey) {
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Delete a note',
detail: 'This work cannot be undone.',
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
let { dispatch, location } = this.props
let splitted = uniqueKey.split('-')
let storageKey = splitted.shift()
let noteKey = splitted.shift()
dataApi
.deleteNote(storageKey, noteKey)
.then((data) => {
let dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
storageKey: data.storageKey,
noteKey: data.noteKey
})
}
if (location.query.key === uniqueKey) {
ee.once('list:moved', dispatchHandler)
ee.emit('list:next')
} else {
dispatchHandler()
}
})
} }
} }
@@ -204,7 +277,7 @@ class NoteList extends React.Component {
this.notes = notes = this.getNotes() this.notes = notes = this.getNotes()
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)) .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
let noteList = notes let noteList = notes.slice(0, 10 + 10 * this.state.range)
.map((note) => { .map((note) => {
if (note == null) return null if (note == null) return null
let storage = data.storageMap.get(note.storage) let storage = data.storageMap.get(note.storage)
@@ -226,7 +299,8 @@ class NoteList extends React.Component {
: 'item' : 'item'
} }
key={note.storage + '-' + note.key} key={note.storage + '-' + note.key}
onClick={(e) => this.handleNoteClick(note.storage + '-' + note.key)(e)} onClick={(e) => this.handleNoteClick(e, note.storage + '-' + note.key)}
onContextMenu={(e) => this.handleNoteContextMenu(e, note.storage + '-' + note.key)}
> >
<div styleName='item-border'/> <div styleName='item-border'/>
<div styleName='item-info'> <div styleName='item-info'>
@@ -277,6 +351,7 @@ class NoteList extends React.Component {
tabIndex='-1' tabIndex='-1'
onKeyDown={(e) => this.handleNoteListKeyDown(e)} onKeyDown={(e) => this.handleNoteListKeyDown(e)}
style={this.props.style} style={this.props.style}
onScroll={(e) => this.handleScroll(e)}
> >
{noteList} {noteList}
</div> </div>

View File

@@ -22,6 +22,7 @@
.top-menu-label .top-menu-label
margin-left 5px margin-left 5px
overflow ellipsis
.menu .menu
margin-top 15px margin-top 15px
@@ -33,6 +34,7 @@
font-size 14px font-size 14px
width 100% width 100%
text-align left text-align left
overflow ellipsis
.menu-button--active .menu-button--active
@extend .menu-button @extend .menu-button

View File

@@ -65,6 +65,7 @@
margin 2px 0 margin 2px 0
text-align left text-align left
border none border none
overflow ellipsis
font-size 14px font-size 14px
&:first-child &:first-child
margin-top 0 margin-top 0

View File

@@ -50,11 +50,13 @@ class SideNav extends React.Component {
isFolded={isFolded} isFolded={isFolded}
/> />
}) })
let style = {}
if (!isFolded) style.width = this.props.width
return ( return (
<div className='SideNav' <div className='SideNav'
styleName={isFolded ? 'root--folded' : 'root'} styleName={isFolded ? 'root--folded' : 'root'}
tabIndex='1' tabIndex='1'
style={style}
> >
<div styleName='top'> <div styleName='top'>
<button styleName='top-menu' <button styleName='top-menu'

View File

@@ -15,10 +15,13 @@ $control-height = 34px
border $ui-border border $ui-border
border-radius 20px border-radius 20px
overflow hidden overflow hidden
display flex
.control-search .control-search
absolute top left bottom height 32px
right 40px flex 1
background-color white background-color white
position relative
.control-search-icon .control-search-icon
absolute top bottom left absolute top bottom left
@@ -76,10 +79,24 @@ $control-height = 34px
color $ui-inactive-text-color color $ui-inactive-text-color
line-height 150px line-height 150px
text-align center text-align center
.control-contextButton
display block
width 20px
height $control-height - 2
navButtonColor()
border-left $ui-border
font-size 14px
line-height 28px
padding 0
&:active
border-color $ui-button--active-backgroundColor
&:hover .control-newPostButton-tooltip
opacity 1
.control-newPostButton .control-newPostButton
display block display block
absolute top right bottom width 36px
width 40px
height $control-height - 2 height $control-height - 2
navButtonColor() navButtonColor()
border-left $ui-border border-left $ui-border
@@ -143,10 +160,9 @@ body[data-theme="dark"]
color $ui-inactive-text-color color $ui-inactive-text-color
padding-right 3px padding-right 3px
.control-search-optionList-empty .control-search-optionList-empty
height 150px
color $ui-inactive-text-color color $ui-inactive-text-color
line-height 150px
text-align center .control-contextButton,
.control-newPostButton .control-newPostButton
colorDarkDefaultButton() colorDarkDefaultButton()
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
@@ -155,12 +171,3 @@ body[data-theme="dark"]
.control-newPostButton-tooltip .control-newPostButton-tooltip
darkTooltip() darkTooltip()
position fixed
pointer-events none
top 45px
left 385px
z-index 10
padding 5px
line-height normal
opacity 0
transition 0.1s

View File

@@ -6,8 +6,12 @@ import modal from 'browser/main/lib/modal'
import NewNoteModal from 'browser/main/modals/NewNoteModal' import NewNoteModal from 'browser/main/modals/NewNoteModal'
import { hashHistory } from 'react-router' import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import ConfigManager from 'browser/main/lib/ConfigManager'
import dataApi from 'browser/main/lib/dataApi'
const OSX = window.process.platform === 'darwin' const OSX = window.process.platform === 'darwin'
const { remote } = require('electron')
const { Menu, MenuItem } = remote
class TopBar extends React.Component { class TopBar extends React.Component {
constructor (props) { constructor (props) {
@@ -33,7 +37,30 @@ class TopBar extends React.Component {
} }
handleNewPostButtonClick (e) { handleNewPostButtonClick (e) {
let { data, params, dispatch, location } = this.props let { config } = this.props
switch (config.ui.defaultNote) {
case 'MARKDOWN_NOTE':
this.createNote('MARKDOWN_NOTE')
break
case 'SNIPPET_NOTE':
this.createNote('SNIPPET_NOTE')
break
case 'ALWAYS_ASK':
let { dispatch, location } = this.props
let { storage, folder } = this.resolveTargetFolder()
modal.open(NewNoteModal, {
storage: storage.key,
folder: folder.key,
dispatch,
location
})
}
}
resolveTargetFolder () {
let { data, params } = this.props
let storage = data.storageMap.get(params.storageKey) let storage = data.storageMap.get(params.storageKey)
// Find first storage // Find first storage
@@ -48,12 +75,10 @@ class TopBar extends React.Component {
if (folder == null) folder = storage.folders[0] if (folder == null) folder = storage.folders[0]
if (folder == null) throw new Error('No folder to craete a note') if (folder == null) throw new Error('No folder to craete a note')
modal.open(NewNoteModal, { return {
storage: storage.key, storage,
folder: folder.key, folder
dispatch, }
location
})
} }
handleSearchChange (e) { handleSearchChange (e) {
@@ -63,8 +88,9 @@ class TopBar extends React.Component {
} }
getOptions () { getOptions () {
let { notes } = this.props let { data } = this.props
let { search } = this.state let { search } = this.state
let notes = data.noteMap.map((note) => note)
if (search.trim().length === 0) return [] if (search.trim().length === 0) return []
let searchBlocks = search.split(' ') let searchBlocks = search.split(' ')
searchBlocks.forEach((block) => { searchBlocks.forEach((block) => {
@@ -134,15 +160,110 @@ class TopBar extends React.Component {
} }
} }
handleContextButtonClick (e) {
let { config } = this.props
let menu = new Menu()
menu.append(new MenuItem({
label: 'Create Markdown Note',
click: (e) => this.createNote('MARKDOWN_NOTE')
}))
menu.append(new MenuItem({
label: 'Create Snippet Note',
click: (e) => this.createNote('SNIPPET_NOTE')
}))
menu.append(new MenuItem({
type: 'separator'
}))
menu.append(new MenuItem({
label: 'Change Default Note',
submenu: [
{
type: 'radio',
label: 'Markdown Note',
checked: config .ui.defaultNote === 'MARKDOWN_NOTE',
click: (e) => this.setDefaultNote('MARKDOWN_NOTE')
},
{
type: 'radio',
label: 'Snippet Note',
checked: config.ui.defaultNote === 'SNIPPET_NOTE',
click: (e) => this.setDefaultNote('SNIPPET_NOTE')
},
{
type: 'radio',
label: 'Always Ask',
checked: config.ui.defaultNote === 'ALWAYS_ASK',
click: (e) => this.setDefaultNote('ALWAYS_ASK')
}
]
}))
menu.popup(remote.getCurrentWindow())
}
createNote (noteType) {
let { dispatch, location } = this.props
if (noteType !== 'MARKDOWN_NOTE' && noteType !== 'SNIPPET_NOTE') throw new Error('Invalid note type.')
let { storage, folder } = this.resolveTargetFolder()
let newNote = noteType === 'MARKDOWN_NOTE'
? {
type: 'MARKDOWN_NOTE',
folder: folder.key,
title: '',
content: ''
}
: {
type: 'SNIPPET_NOTE',
folder: folder.key,
title: '',
description: '',
snippets: [{
name: '',
mode: 'text',
content: ''
}]
}
dataApi
.createNote(storage.key, newNote)
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: note.storage + '-' + note.key}
})
ee.emit('detail:focus')
})
}
setDefaultNote (defaultNote) {
let { config, dispatch } = this.props
let ui = Object.assign(config.ui)
ui.defaultNote = defaultNote
ConfigManager.set({
ui
})
dispatch({
type: 'SET_UI',
config: ConfigManager.get()
})
}
render () { render () {
let { config, style, storages } = this.props let { config, style, data } = this.props
let searchOptionList = this.getOptions() let searchOptionList = this.getOptions()
.map((note) => { .map((note) => {
let storage = _.find(storages, {key: note.storage}) let storage = data.storageMap.get(note.storage)
let folder = _.find(storage.folders, {key: note.folder}) let folder = _.find(storage.folders, {key: note.folder})
return <div styleName='control-search-optionList-item' return <div styleName='control-search-optionList-item'
key={note.uniqueKey} key={note.storage + '-' + note.key}
onClick={(e) => this.handleOptionClick(note.uniqueKey)(e)} onClick={(e) => this.handleOptionClick(note.storage + '-' + note.key)(e)}
> >
<div styleName='control-search-optionList-item-folder' <div styleName='control-search-optionList-item-folder'
style={{borderColor: folder.color}}> style={{borderColor: folder.color}}>
@@ -203,6 +324,11 @@ class TopBar extends React.Component {
New Note {OSX ? '⌘' : '^'} + n New Note {OSX ? '⌘' : '^'} + n
</span> </span>
</button> </button>
<button styleName='control-contextButton'
onClick={(e) => this.handleContextButtonClick(e)}
>
<i className='fa fa-caret-down'/>
</button>
</div> </div>
</div> </div>
) )

View File

@@ -8,13 +8,15 @@ const defaultConfig = {
zoom: 1, zoom: 1,
isSideNavFolded: false, isSideNavFolded: false,
listWidth: 250, listWidth: 250,
navWidth: 200,
hotkey: { hotkey: {
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S', toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E' toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
}, },
ui: { ui: {
theme: 'default', theme: 'default',
disableDirectWrite: false disableDirectWrite: false,
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
}, },
editor: { editor: {
theme: 'xcode', theme: 'xcode',

View File

@@ -35,6 +35,10 @@ class UnstyledFolderItem extends React.Component {
} }
handleConfirmButtonClick (e) { handleConfirmButtonClick (e) {
this.confirm()
}
confirm () {
let { storage, folder } = this.props let { storage, folder } = this.props
dataApi dataApi
.updateFolder(storage.key, folder.key, { .updateFolder(storage.key, folder.key, {
@@ -87,6 +91,17 @@ class UnstyledFolderItem extends React.Component {
}) })
} }
handleFolderItemBlur (e) {
let el = e.relatedTarget
while (el != null) {
if (el === this.refs.root) {
return false
}
el = el.parentNode
}
this.confirm()
}
renderEdit (e) { renderEdit (e) {
const popover = { position: 'absolute', zIndex: 2 } const popover = { position: 'absolute', zIndex: 2 }
const cover = { const cover = {
@@ -97,7 +112,11 @@ class UnstyledFolderItem extends React.Component {
position: 'absolute' position: 'absolute'
}, this.state.folder.colorPickerPos) }, this.state.folder.colorPickerPos)
return ( return (
<div styleName='folderList-item'> <div styleName='folderList-item'
onBlur={(e) => this.handleFolderItemBlur(e)}
tabIndex='-1'
ref='root'
>
<div styleName='folderList-item-left'> <div styleName='folderList-item-left'>
<button styleName='folderList-item-left-colorButton' style={{color: this.state.folder.color}} <button styleName='folderList-item-left-colorButton' style={{color: this.state.folder.color}}
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)} onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}

View File

@@ -461,6 +461,9 @@ function config (state = defaultConfig, action) {
case 'SET_LIST_WIDTH': case 'SET_LIST_WIDTH':
state.listWidth = action.listWidth state.listWidth = action.listWidth
return Object.assign({}, state) return Object.assign({}, state)
case 'SET_NAV_WIDTH':
state.navWidth = action.navWidth
return Object.assign({}, state)
case 'SET_CONFIG': case 'SET_CONFIG':
return Object.assign({}, state, action.config) return Object.assign({}, state, action.config)
case 'SET_UI': case 'SET_UI':