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

update design of Note List

add list style
remove folder info
enhance design
This commit is contained in:
Dick Choi
2016-10-12 20:02:50 +09:00
parent 823da07a5e
commit 6e0def310f
7 changed files with 348 additions and 172 deletions

View File

@@ -27,20 +27,24 @@
border-bottom $ui-border border-bottom $ui-border
display flex display flex
background-color $ui-backgroundColor background-color $ui-backgroundColor
.tabList-item .tabList-item
position relative position relative
flex 1 flex 1
border-right $ui-border border-right $ui-border
.tabList-item--active .tabList-item--active
@extend .tabList-item @extend .tabList-item
.tabList-item-button .tabList-item-button
border-color $brand-color border-color $brand-color
.tabList-item-button .tabList-item-button
width 100% width 100%
height 29px height 29px
navButtonColor() navButtonColor()
outline none outline none
border-left 4px solid transparent border-left 4px solid transparent
.tabList-item-deleteButton .tabList-item-deleteButton
position absolute position absolute
top 5px top 5px

View File

@@ -22,7 +22,7 @@ class NoteItem extends React.Component {
let tagList = _.isArray(note.tags) let tagList = _.isArray(note.tags)
? note.tags.map((tag) => { ? note.tags.map((tag) => {
return ( return (
<span styleName='item-tagList-item' <span styleName='bottom-tagList-item'
key={tag}> key={tag}>
{tag} {tag}
</span> </span>
@@ -37,9 +37,7 @@ class NoteItem extends React.Component {
key={note.storage + '-' + note.key} key={note.storage + '-' + note.key}
onClick={(e) => this.handleClick(e)} onClick={(e) => this.handleClick(e)}
> >
<div styleName='border'/>
<div styleName='info'> <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<span styleName='info-left-folder' <span styleName='info-left-folder'
style={{borderColor: folder.color}} style={{borderColor: folder.color}}
@@ -48,11 +46,6 @@ class NoteItem extends React.Component {
<span styleName='info-left-folder-surfix'>in {storage.name}</span> <span styleName='info-left-folder-surfix'>in {storage.name}</span>
</span> </span>
</div> </div>
<div styleName='info-right'>
{moment(note.updatedAt).fromNow()}
</div>
</div> </div>
<div styleName='title'> <div styleName='title'>
@@ -66,15 +59,21 @@ class NoteItem extends React.Component {
} }
</div> </div>
<div styleName='tagList'> <div styleName='bottom'>
<i styleName='tagList-icon' <i styleName='bottom-tagIcon'
className='fa fa-tags fa-fw' className='fa fa-tags fa-fw'
/> />
<div styleName='bottom-tagList'>
{tagList.length > 0 {tagList.length > 0
? tagList ? tagList
: <span styleName='tagList-empty'>Not tagged yet</span> : <span styleName='bottom-tagList-empty'>Not tagged yet</span>
} }
</div> </div>
<div styleName='bottom-time'>
{moment(note.updatedAt).fromNow()}
</div>
</div>
</div> </div>
) )
} }

View File

@@ -1,18 +1,33 @@
.root .root
position relative position relative
height 80px
border-bottom $ui-border border-bottom $ui-border
padding 0 5px padding 2px 5px
user-select none user-select none
cursor pointer cursor pointer
transition background-color 0.15s transition background-color 0.15s
&:hover &:hover
background-color alpha($ui-active-color, 10%) background-color alpha($ui-active-color, 20%)
.root--active .root--active
@extend .root @extend .root
.border background-color $ui-active-color
border-color $ui-active-color &:hover
background-color $ui-active-color
color white
.info-left-folder
.info-left-folder-surfix
.title
.title-icon
.title-empty
.bottom-tagIcon
.bottom-tagList-item
.bottom-tagList-empty
.bottom-time
color white
.bottom-tagList-item
color white
background-color transparent
.border .border
absolute top bottom left right absolute top bottom left right
@@ -22,11 +37,11 @@
transition 0.15s transition 0.15s
.info .info
height 30px height 20px
clearfix() clearfix()
font-size 12px font-size 12px
color $ui-inactive-text-color color $ui-inactive-text-color
line-height 30px line-height 20px
overflow-y hidden overflow-y hidden
.info-left .info-left
@@ -45,77 +60,120 @@
float right float right
.title .title
height 24px
box-sizing border-box
line-height 24px
height 20px height 20px
line-height 20px line-height 20px
padding 0 5px 0 0 padding 0 5px 0 0
font-weight bold
overflow ellipsis overflow ellipsis
color $ui-text-color color $ui-text-color
.title-icon .title-icon
font-size 12px font-size 12px
color $ui-inactive-text-color color $ui-inactive-text-color
padding-right 3px padding-right 3px
.title-empty .title-empty
font-weight normal font-weight normal
color $ui-inactive-text-color color $ui-inactive-text-color
.tagList .bottom
height 30px margin-top 2px
height 20px
font-size 12px font-size 12px
line-height 30px line-height 20px
overflow ellipsis overflow ellipsis
display flex
.tagList-icon .bottom-tagIcon
vertical-align middle vertical-align middle
color $ui-button-color color $ui-button-color
height 20px
line-height 20px
.tagList-item .bottom-tagList
flex 1
overflow ellipsis
line-height 20px
.bottom-tagList-item
margin 0 4px margin 0 4px
padding 0 4px padding 0 4px
height 20px height 20px
box-sizing border-box
border-radius 3px border-radius 3px
vertical-align middle vertical-align middle
border-style solid border-style solid
border-color $ui-button--focus-borderColor border-color $ui-button--focus-borderColor
border-width 0 0 0 3px border-width 0 0 0 3px
background-color $ui-backgroundColor background-color $ui-backgroundColor
color $ui-text-color
transition 0.15s
.tagList-empty .bottom-tagList-empty
color $ui-inactive-text-color color $ui-inactive-text-color
vertical-align middle vertical-align middle
font-size 10px
.bottom-time
color $ui-inactive-text-color
margin-left 5px
font-size 10px
body[data-theme="dark"] body[data-theme="dark"]
.root .root
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
&:hover
background-color alpha($ui-active-color, 20%)
.root--active .root--active
@extend .root @extend .root
.border border-color $ui-dark-borderColor
border-color $ui-active-color &:hover
background-color $ui-active-color
.info-left-folder
.info-left-folder-surfix
.title
.title-icon
.title-empty
.bottom-tagIcon
.bottom-tagList-item
.bottom-tagList-empty
.bottom-time
color white
.bottom-tagList-item
color white
background-color transparent
.info .info
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
.info-left-folder .info-left-folder
color $ui-dark-text-color color $ui-dark-text-color
.info-left-folder-surfix .info-left-folder-surfix
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
.title .title
color $ui-dark-text-color color $ui-dark-text-color
.title-icon .title-icon
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
.title-empty .title-empty
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
.tagList-icon
color $ui-dark-button-color
.tagList-item
border-color $ui-dark-button--focus-borderColor
background-color $ui-dark-backgroundColor
.tagList-empty .tagList-empty
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
.bottom-tagIcon
color $ui-dark-button-color
.bottom-tagList-item
color $ui-dark-text-color
background-color $ui-dark-backgroundColor
.bottom-tagList-empty
color $ui-dark-inactive-text-color
.bottom-time
color $ui-dark-inactive-text-color

View File

@@ -1,34 +1,6 @@
import { combineReducers, createStore } from 'redux' import { combineReducers, createStore } from 'redux'
import { routerReducer } from 'react-router-redux' import { routerReducer } from 'react-router-redux'
import { DEFAULT_CONFIG } from 'browser/main/lib/ConfigManager'
const OSX = global.process.platform === 'darwin'
const defaultConfig = {
zoom: 1,
isSideNavFolded: false,
listWidth: 250,
hotkey: {
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
},
ui: {
theme: 'default',
disableDirectWrite: false
},
editor: {
theme: 'xcode',
fontSize: '14',
fontFamily: 'Monaco, Consolas',
indentType: 'space',
indentSize: '4',
switchPreview: 'BLUR' // Available value: RIGHTCLICK, BLUR
},
preview: {
fontSize: '14',
fontFamily: 'Lato',
codeBlockTheme: 'xcode',
lineNumber: true
}
}
let defaultData = { let defaultData = {
storageMap: {}, storageMap: {},
@@ -48,7 +20,7 @@ function data (state = defaultData, action) {
return state return state
} }
function config (state = defaultConfig, action) { function config (state = DEFAULT_CONFIG, action) {
switch (action.type) { switch (action.type) {
case 'INIT_CONFIG': case 'INIT_CONFIG':
case 'SET_CONFIG': case 'SET_CONFIG':

View File

@@ -2,14 +2,64 @@
absolute left bottom absolute left bottom
border-top $ui-border border-top $ui-border
border-bottom $ui-border border-bottom $ui-border
overflow auto
top $topBar-height - 1 top $topBar-height - 1
.control
absolute top left right
user-select none
height 25px
font-size 10px
border-bottom $ui-border
line-height 25px
display flex
background-color $ui-backgroundColor
color $ui-inactive-text-color
.control-sortBy
flex 1
padding-left 5px
.control-sortBy-select
margin-left 5px
padding 0
border none
background-color transparent
font-size 10px
.control-button
width 25px
padding 0
background-color transparent
border none
color $ui-inactive-text-color
&:hover
.control-button-tooltip
opacity 1
.control-button--active
@extend .control-button
color $ui-active-color
.control-button-tooltip
tooltip()
position absolute
top 20px
right 5px
padding 5px
opacity 0
white-space nowrap
border-radius 2px
z-index 1
.list
absolute left right bottom
top 24px
overflow auto
.item .item
position relative position relative
height 80px
border-bottom $ui-border border-bottom $ui-border
padding 0 5px padding 2px 5px
user-select none user-select none
cursor pointer cursor pointer
transition background-color 0.15s transition background-color 0.15s
@@ -18,8 +68,23 @@
.item--active .item--active
@extend .item @extend .item
.item-border background-color alpha($ui-active-color, 100%)
border-color $ui-active-color color white
.item-title
color white
.item-title-icon
color white
.item-bottom-tagIcon
color white
.item-bottom-tagList-empty
color white
.item-bottom-time
color white
.item-bottom-tagList-item
background-color transparent
color white
&:hover
background-color alpha($ui-active-color, 100%)
.item-border .item-border
absolute top bottom left right absolute top bottom left right
@@ -28,68 +93,64 @@
border-color transparent border-color transparent
transition 0.15s transition 0.15s
.item-info
height 30px
clearfix()
font-size 12px
color $ui-inactive-text-color
line-height 30px
overflow-y hidden
.item-info-left
float left
overflow ellipsis
.item-info-left-folder
border-left 4px solid transparent
padding 2px 5px
color $ui-text-color
.item-info-left-folder-surfix
font-size 10px
margin-left 5px
color $ui-inactive-text-color
.item-info-right
float right
.item-title .item-title
height 20px height 24px
line-height 20px box-sizing border-box
padding 0 5px 0 0 line-height 24px
font-weight bold padding 0
overflow ellipsis overflow ellipsis
color $ui-text-color color $ui-text-color
.item-title-icon .item-title-icon
font-size 12px font-size 12px
color $ui-inactive-text-color color $ui-inactive-text-color
padding-right 3px padding-right 3px
.item-title-empty .item-title-empty
font-weight normal font-weight normal
color $ui-inactive-text-color color $ui-inactive-text-color
.item-tagList .item-bottom
height 30px margin-top 2px
height 20px
font-size 12px font-size 12px
line-height 30px line-height 20px
overflow ellipsis overflow ellipsis
display flex
.item-tagList-icon .item-bottom-tagIcon
vertical-align middle vertical-align middle
color $ui-button-color color $ui-button-color
height 20px
line-height 20px
.item-tagList-item .item-bottom-tagList
flex 1
overflow ellipsis
line-height 20px
.item-bottom-tagList-item
margin 0 4px margin 0 4px
padding 0 4px padding 0 4px
height 20px height 20px
box-sizing border-box
border-radius 3px border-radius 3px
vertical-align middle vertical-align middle
border-style solid border-style solid
border-color $ui-button--focus-borderColor border-color $ui-button--focus-borderColor
border-width 0 0 0 3px border-width 0 0 0 3px
background-color $ui-backgroundColor background-color $ui-backgroundColor
color $ui-text-color
.item-tagList-empty .item-bottom-tagList-empty
color $ui-inactive-text-color color $ui-inactive-text-color
vertical-align middle vertical-align middle
font-size 10px
.item-bottom-time
color $ui-inactive-text-color
margin-left 5px
font-size 10px
body[data-theme="dark"] body[data-theme="dark"]
.root .root
@@ -103,32 +164,40 @@ body[data-theme="dark"]
.item--active .item--active
@extend .item @extend .item
.item-border border-color $ui-dark-borderColor
border-color $ui-active-color .item-title
color white
.item-info .item-bottom-tagList-item
color $ui-dark-inactive-text-color background-color transparent
color white
.item-info-left-folder .item-bottom-tagList-empty
color $ui-dark-text-color color white
.item-info-left-folder-surfix &:hover
color $ui-dark-inactive-text-color background-color alpha($ui-active-color, 100%)
.item-title .item-title
color $ui-dark-text-color color $ui-dark-text-color
.item-title-icon .item-title-icon
color $ui-darkinactive-text-color color $ui-darkinactive-text-color
.item-title-empty .item-title-empty
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
.item-tagList-icon .item-bottom-tagIcon
color $ui-dark-button-color color $ui-dark-button-color
.item-tagList-item .item-bottom-tagList-item
border-color $ui-dark-button--focus-borderColor border-color $ui-dark-button--focus-borderColor
background-color $ui-dark-button--hover-backgroundColor background-color $ui-dark-button--hover-backgroundColor
color $ui-dark-text-color color $ui-dark-text-color
.item-tagList-empty .item-bottom-tagList-empty
color $ui-inactive-text-color color $ui-inactive-text-color
vertical-align middle vertical-align middle
.control
background-color $ui-dark-backgroundColor
border-color $ui-dark-borderColor
.control-sortBy-select
color $ui-dark-text-color

View File

@@ -5,10 +5,23 @@ 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' import dataApi from 'browser/main/lib/dataApi'
import ConfigManager from 'browser/main/lib/ConfigManager'
const { remote } = require('electron') const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote const { Menu, MenuItem, dialog } = remote
function sortByCreatedAt (a, b) {
return new Date(b.createdAt) - new Date(a.createdAt)
}
function sortByAlphabetical (a, b) {
return a.title.localeCompare(b.title)
}
function sortByUpdatedAt (a, b) {
return new Date(b.updatedAt) - new Date(a.updatedAt)
}
class NoteList extends React.Component { class NoteList extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -21,7 +34,7 @@ class NoteList extends React.Component {
this.selectPriorNote() this.selectPriorNote()
} }
this.focusHandler = () => { this.focusHandler = () => {
this.refs.root.focus() this.refs.list.focus()
} }
this.state = { this.state = {
@@ -43,7 +56,7 @@ class NoteList extends React.Component {
} }
resetScroll () { resetScroll () {
this.refs.root.scrollTop = 0 this.refs.list.scrollTop = 0
this.setState({ this.setState({
range: 0 range: 0
}) })
@@ -52,7 +65,7 @@ class NoteList extends React.Component {
handleScroll (e) { handleScroll (e) {
let notes = this.notes let notes = this.notes
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 250 && notes.length > this.state.range * 10 + 10) { if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 250 && notes.length > this.state.range * 20 + 20) {
this.setState({ this.setState({
range: this.state.range + 1 range: this.state.range + 1
}) })
@@ -86,7 +99,7 @@ class NoteList extends React.Component {
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.list
let item = list.childNodes[targetIndex] let item = list.childNodes[targetIndex]
if (item == null) return false if (item == null) return false
@@ -274,20 +287,51 @@ class NoteList extends React.Component {
} }
} }
render () { handleSortByChange (e) {
let { location, data, notes } = this.props let { dispatch } = this.props
this.notes = notes = this.getNotes()
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
let noteList = notes.slice(0, 10 + 10 * this.state.range) let config = {
sortBy: e.target.value
}
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
}
handleListStyleButtonClick (e, style) {
let { dispatch } = this.props
let config = {
listStyle: style
}
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
}
render () {
let { location, notes, config } = this.props
let sortFunc = config.sortBy === 'CREATED_AT'
? sortByCreatedAt
: config.sortBy === 'ALPHABETICAL'
? sortByAlphabetical
: sortByUpdatedAt
this.notes = notes = this.getNotes()
.sort(sortFunc)
let noteList = notes.slice(0, 20 + 20 * 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 folder = _.find(storage.folders, {key: note.folder})
let tagElements = _.isArray(note.tags) let tagElements = _.isArray(note.tags)
? note.tags.map((tag) => { ? note.tags.map((tag) => {
return ( return (
<span styleName='item-tagList-item' <span styleName='item-bottom-tagList-item'
key={tag}> key={tag}>
{tag} {tag}
</span> </span>
@@ -304,24 +348,6 @@ class NoteList extends React.Component {
onClick={(e) => this.handleNoteClick(e, note.storage + '-' + note.key)} onClick={(e) => this.handleNoteClick(e, note.storage + '-' + note.key)}
onContextMenu={(e) => this.handleNoteContextMenu(e, note.storage + '-' + note.key)} onContextMenu={(e) => this.handleNoteContextMenu(e, note.storage + '-' + note.key)}
> >
<div styleName='item-border'/>
<div styleName='item-info'>
<div styleName='item-info-left'>
<span styleName='item-info-left-folder'
style={{borderColor: folder.color}}
>
{folder.name}
<span styleName='item-info-left-folder-surfix'>in {storage.name}</span>
</span>
</div>
<div styleName='item-info-right'>
{moment(note.updatedAt).fromNow()}
</div>
</div>
<div styleName='item-title'> <div styleName='item-title'>
{note.type === 'SNIPPET_NOTE' {note.type === 'SNIPPET_NOTE'
? <i styleName='item-title-icon' className='fa fa-fw fa-code'/> ? <i styleName='item-title-icon' className='fa fa-fw fa-code'/>
@@ -333,15 +359,23 @@ class NoteList extends React.Component {
} }
</div> </div>
<div styleName='item-tagList'> {config.listStyle === 'DEFAULT' &&
<i styleName='item-tagList-icon' <div styleName='item-bottom'>
<i styleName='item-bottom-tagIcon'
className='fa fa-tags fa-fw' className='fa fa-tags fa-fw'
/> />
<div styleName='item-bottom-tagList'>
{tagElements.length > 0 {tagElements.length > 0
? tagElements ? tagElements
: <span styleName='item-tagList-empty'>Not tagged yet</span> : <span styleName='item-bottom-tagList-empty'>Not tagged yet</span>
} }
</div> </div>
<div styleName='item-bottom-time'>
{moment(config.sortBy === 'CREATED_AT' ? note.createdAt : note.updatedAt).fromNow()}
</div>
</div>
}
</div> </div>
) )
}) })
@@ -349,14 +383,52 @@ class NoteList extends React.Component {
return ( return (
<div className='NoteList' <div className='NoteList'
styleName='root' styleName='root'
ref='root' style={this.props.style}
>
<div styleName='control'>
<div styleName='control-sortBy'>
Sort by
<select styleName='control-sortBy-select'
value={config.sortBy}
onChange={(e) => this.handleSortByChange(e)}
>
<option value='UPDATED_AT'>Updated Time</option>
<option value='CREATED_AT'>Created Time</option>
<option value='ALPHABETICAL'>Alphabetical</option>
</select>
</div>
<button styleName={config.listStyle === 'DEFAULT'
? 'control-button--active'
: 'control-button'
}
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
>
<i className='fa fa-th-list'/>
<span styleName='control-button-tooltip'>
Default Size
</span>
</button>
<button styleName={config.listStyle === 'SMALL'
? 'control-button--active'
: 'control-button'
}
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
>
<i className='fa fa-list'/>
<span styleName='control-button-tooltip'>
Small Size
</span>
</button>
</div>
<div styleName='list'
ref='list'
tabIndex='-1' tabIndex='-1'
onKeyDown={(e) => this.handleNoteListKeyDown(e)} onKeyDown={(e) => this.handleNoteListKeyDown(e)}
style={this.props.style}
onScroll={(e) => this.handleScroll(e)} onScroll={(e) => this.handleScroll(e)}
> >
{noteList} {noteList}
</div> </div>
</div>
) )
} }
} }

View File

@@ -7,11 +7,13 @@ const consts = require('browser/lib/consts')
let isInitialized = false let isInitialized = false
const defaultConfig = { export const DEFAULT_CONFIG = {
zoom: 1, zoom: 1,
isSideNavFolded: false, isSideNavFolded: false,
listWidth: 250, listWidth: 250,
navWidth: 200, navWidth: 200,
sortBy: 'UPDATED_AT', // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL'
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
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'
@@ -55,15 +57,15 @@ function get () {
let config = window.localStorage.getItem('config') let config = window.localStorage.getItem('config')
try { try {
config = Object.assign({}, defaultConfig, JSON.parse(config)) config = Object.assign({}, DEFAULT_CONFIG, JSON.parse(config))
config.hotkey = Object.assign({}, defaultConfig.hotkey, config.hotkey) config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, config.hotkey)
config.ui = Object.assign({}, defaultConfig.ui, config.ui) config.ui = Object.assign({}, DEFAULT_CONFIG.ui, config.ui)
config.editor = Object.assign({}, defaultConfig.editor, config.editor) config.editor = Object.assign({}, DEFAULT_CONFIG.editor, config.editor)
config.preview = Object.assign({}, defaultConfig.preview, config.preview) config.preview = Object.assign({}, DEFAULT_CONFIG.preview, config.preview)
if (!validate(config)) throw new Error('INVALID CONFIG') if (!validate(config)) throw new Error('INVALID CONFIG')
} catch (err) { } catch (err) {
console.warn('Boostnote resets the malformed configuration.') console.warn('Boostnote resets the malformed configuration.')
config = defaultConfig config = DEFAULT_CONFIG
_save(config) _save(config)
} }
@@ -91,7 +93,7 @@ function get () {
function set (updates) { function set (updates) {
let currentConfig = get() let currentConfig = get()
let newConfig = Object.assign({}, defaultConfig, currentConfig, updates) let newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, updates)
if (!validate(newConfig)) throw new Error('INVALID CONFIG') if (!validate(newConfig)) throw new Error('INVALID CONFIG')
_save(newConfig) _save(newConfig)