mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 09:46:22 +00:00
Merge branch 'master' into filter-tags-and-folders
# Conflicts: # browser/main/SideNav/index.js
This commit is contained in:
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class FolderSelect extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -16,24 +16,27 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
componentDidUpdate() {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
handleClick (e) {
|
||||
this.setState({
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
}, () => {
|
||||
this.refs.search.focus()
|
||||
})
|
||||
handleClick(e) {
|
||||
this.setState(
|
||||
{
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
},
|
||||
() => {
|
||||
this.refs.search.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleFocus (e) {
|
||||
handleFocus(e) {
|
||||
if (this.state.status === 'IDLE') {
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
@@ -41,7 +44,7 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur (e) {
|
||||
handleBlur(e) {
|
||||
if (this.state.status === 'FOCUS') {
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
@@ -49,40 +52,49 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 13:
|
||||
if (this.state.status === 'FOCUS') {
|
||||
this.setState({
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
}, () => {
|
||||
this.refs.search.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
},
|
||||
() => {
|
||||
this.refs.search.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
break
|
||||
case 40:
|
||||
case 38:
|
||||
if (this.state.status === 'FOCUS') {
|
||||
this.setState({
|
||||
status: 'SEARCH',
|
||||
optionIndex: 0
|
||||
}, () => {
|
||||
this.refs.search.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'SEARCH',
|
||||
optionIndex: 0
|
||||
},
|
||||
() => {
|
||||
this.refs.search.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
break
|
||||
case 9:
|
||||
if (e.shiftKey) {
|
||||
e.preventDefault()
|
||||
const tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
|
||||
const previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
||||
const tabbable = document.querySelectorAll(
|
||||
'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'
|
||||
)
|
||||
const previousEl =
|
||||
tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
||||
if (previousEl != null) previousEl.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchInputBlur (e) {
|
||||
handleSearchInputBlur(e) {
|
||||
if (e.relatedTarget !== this.refs.root) {
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
@@ -90,14 +102,17 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchInputChange (e) {
|
||||
handleSearchInputChange(e) {
|
||||
const { folders } = this.props
|
||||
const search = this.refs.search.value
|
||||
const optionIndex = search.length > 0
|
||||
? _.findIndex(folders, (folder) => {
|
||||
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
|
||||
})
|
||||
: -1
|
||||
const optionIndex =
|
||||
search.length > 0
|
||||
? _.findIndex(folders, folder => {
|
||||
return folder.name.match(
|
||||
new RegExp('^' + _.escapeRegExp(search), 'i')
|
||||
)
|
||||
})
|
||||
: -1
|
||||
|
||||
this.setState({
|
||||
search: this.refs.search.value,
|
||||
@@ -105,7 +120,7 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchInputKeyDown (e) {
|
||||
handleSearchInputKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 40:
|
||||
e.stopPropagation()
|
||||
@@ -121,15 +136,18 @@ class FolderSelect extends React.Component {
|
||||
break
|
||||
case 27:
|
||||
e.stopPropagation()
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
}, () => {
|
||||
this.refs.root.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'FOCUS'
|
||||
},
|
||||
() => {
|
||||
this.refs.root.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
nextOption () {
|
||||
nextOption() {
|
||||
let { optionIndex } = this.state
|
||||
const { folders } = this.props
|
||||
|
||||
@@ -141,7 +159,7 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
previousOption () {
|
||||
previousOption() {
|
||||
const { folders } = this.props
|
||||
let { optionIndex } = this.state
|
||||
|
||||
@@ -153,46 +171,52 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
selectOption () {
|
||||
selectOption() {
|
||||
const { folders } = this.props
|
||||
const optionIndex = this.state.optionIndex
|
||||
|
||||
const folder = folders[optionIndex]
|
||||
if (folder != null) {
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
}, () => {
|
||||
this.setValue(folder.key)
|
||||
this.refs.root.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'FOCUS'
|
||||
},
|
||||
() => {
|
||||
this.setValue(folder.key)
|
||||
this.refs.root.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
handleOptionClick (storageKey, folderKey) {
|
||||
return (e) => {
|
||||
handleOptionClick(storageKey, folderKey) {
|
||||
return e => {
|
||||
e.stopPropagation()
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
}, () => {
|
||||
this.setValue(storageKey + '-' + folderKey)
|
||||
this.refs.root.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'FOCUS'
|
||||
},
|
||||
() => {
|
||||
this.setValue(storageKey + '-' + folderKey)
|
||||
this.refs.root.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setValue (value) {
|
||||
setValue(value) {
|
||||
this.value = value
|
||||
this.props.onChange()
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { className, data, value } = this.props
|
||||
const splitted = value.split('-')
|
||||
const storageKey = splitted.shift()
|
||||
const folderKey = splitted.shift()
|
||||
let options = []
|
||||
data.storageMap.forEach((storage, index) => {
|
||||
storage.folders.forEach((folder) => {
|
||||
storage.folders.forEach(folder => {
|
||||
options.push({
|
||||
storage: storage,
|
||||
folder: folder
|
||||
@@ -200,68 +224,78 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
})
|
||||
|
||||
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||
const currentOption = options.filter(
|
||||
option =>
|
||||
option.storage.key === storageKey && option.folder.key === folderKey
|
||||
)[0]
|
||||
|
||||
if (this.state.search.trim().length > 0) {
|
||||
const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
||||
options = options.filter((option) => filter.test(option.folder.name))
|
||||
options = options.filter(option => filter.test(option.folder.name))
|
||||
}
|
||||
|
||||
const optionList = options
|
||||
.map((option, index) => {
|
||||
return (
|
||||
<div styleName={index === this.state.optionIndex
|
||||
const optionList = options.map((option, index) => {
|
||||
return (
|
||||
<div
|
||||
styleName={
|
||||
index === this.state.optionIndex
|
||||
? 'search-optionList-item--active'
|
||||
: 'search-optionList-item'
|
||||
}
|
||||
key={option.storage.key + '-' + option.folder.key}
|
||||
onClick={(e) => this.handleOptionClick(option.storage.key, option.folder.key)(e)}
|
||||
}
|
||||
key={option.storage.key + '-' + option.folder.key}
|
||||
onClick={e =>
|
||||
this.handleOptionClick(option.storage.key, option.folder.key)(e)
|
||||
}
|
||||
>
|
||||
<span
|
||||
styleName='search-optionList-item-name'
|
||||
style={{ borderColor: option.folder.color }}
|
||||
>
|
||||
<span styleName='search-optionList-item-name'
|
||||
style={{borderColor: option.folder.color}}
|
||||
>
|
||||
{option.folder.name}
|
||||
<span styleName='search-optionList-item-name-surfix'>in {option.storage.name}</span>
|
||||
{option.folder.name}
|
||||
<span styleName='search-optionList-item-name-surfix'>
|
||||
in {option.storage.name}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={_.isString(className)
|
||||
? 'FolderSelect ' + className
|
||||
: 'FolderSelect'
|
||||
<div
|
||||
className={
|
||||
_.isString(className) ? 'FolderSelect ' + className : 'FolderSelect'
|
||||
}
|
||||
styleName={this.state.status === 'SEARCH'
|
||||
? 'root--search'
|
||||
: this.state.status === 'FOCUS'
|
||||
? 'root--focus'
|
||||
: 'root'
|
||||
styleName={
|
||||
this.state.status === 'SEARCH'
|
||||
? 'root--search'
|
||||
: this.state.status === 'FOCUS'
|
||||
? 'root--focus'
|
||||
: 'root'
|
||||
}
|
||||
ref='root'
|
||||
tabIndex='0'
|
||||
onClick={(e) => this.handleClick(e)}
|
||||
onFocus={(e) => this.handleFocus(e)}
|
||||
onBlur={(e) => this.handleBlur(e)}
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onClick={e => this.handleClick(e)}
|
||||
onFocus={e => this.handleFocus(e)}
|
||||
onBlur={e => this.handleBlur(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
{this.state.status === 'SEARCH'
|
||||
? <div styleName='search'>
|
||||
<input styleName='search-input'
|
||||
{this.state.status === 'SEARCH' ? (
|
||||
<div styleName='search'>
|
||||
<input
|
||||
styleName='search-input'
|
||||
ref='search'
|
||||
value={this.state.search}
|
||||
placeholder={i18n.__('Folder...')}
|
||||
onChange={(e) => this.handleSearchInputChange(e)}
|
||||
onBlur={(e) => this.handleSearchInputBlur(e)}
|
||||
onKeyDown={(e) => this.handleSearchInputKeyDown(e)}
|
||||
onChange={e => this.handleSearchInputChange(e)}
|
||||
onBlur={e => this.handleSearchInputBlur(e)}
|
||||
onKeyDown={e => this.handleSearchInputKeyDown(e)}
|
||||
/>
|
||||
<div styleName='search-optionList'
|
||||
ref='optionList'
|
||||
>
|
||||
<div styleName='search-optionList' ref='optionList'>
|
||||
{optionList}
|
||||
</div>
|
||||
</div>
|
||||
: <div styleName='idle' style={{color: currentOption.folder.color}}>
|
||||
) : (
|
||||
<div styleName='idle' style={{ color: currentOption.folder.color }}>
|
||||
<div styleName='idle-label'>
|
||||
<i className='fa fa-folder' />
|
||||
<span styleName='idle-label-name'>
|
||||
@@ -269,8 +303,7 @@ class FolderSelect extends React.Component {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -280,11 +313,13 @@ FolderSelect.propTypes = {
|
||||
className: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
folders: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
color: PropTypes.string
|
||||
}))
|
||||
folders: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
color: PropTypes.string
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export default CSSModules(FolderSelect, styles)
|
||||
|
||||
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class FromUrlButton extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -14,44 +14,46 @@ class FromUrlButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDown (e) {
|
||||
handleMouseDown(e) {
|
||||
this.setState({
|
||||
isActive: true
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
handleMouseUp(e) {
|
||||
this.setState({
|
||||
isActive: false
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseLeave (e) {
|
||||
handleMouseLeave(e) {
|
||||
this.setState({
|
||||
isActive: false
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { className } = this.props
|
||||
|
||||
return (
|
||||
<button className={_.isString(className)
|
||||
? 'FromUrlButton ' + className
|
||||
: 'FromUrlButton'
|
||||
<button
|
||||
className={
|
||||
_.isString(className) ? 'FromUrlButton ' + className : 'FromUrlButton'
|
||||
}
|
||||
styleName={this.state.isActive || this.props.isActive
|
||||
? 'root--active'
|
||||
: 'root'
|
||||
styleName={
|
||||
this.state.isActive || this.props.isActive ? 'root--active' : 'root'
|
||||
}
|
||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
||||
onClick={this.props.onClick}>
|
||||
<img styleName='icon'
|
||||
src={this.state.isActive || this.props.isActive
|
||||
? '../resources/icon/icon-external.svg'
|
||||
: '../resources/icon/icon-external.svg'
|
||||
onMouseDown={e => this.handleMouseDown(e)}
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
onMouseLeave={e => this.handleMouseLeave(e)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<img
|
||||
styleName='icon'
|
||||
src={
|
||||
this.state.isActive || this.props.isActive
|
||||
? '../resources/icon/icon-external.svg'
|
||||
: '../resources/icon/icon-external.svg'
|
||||
}
|
||||
/>
|
||||
<span styleName='tooltip'>{i18n.__('Convert URL to Markdown')}</span>
|
||||
|
||||
@@ -5,14 +5,18 @@ import styles from './FullscreenButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
const FullscreenButton = ({
|
||||
onClick
|
||||
}) => {
|
||||
const FullscreenButton = ({ onClick }) => {
|
||||
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||
return (
|
||||
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
||||
<button
|
||||
styleName='control-fullScreenButton'
|
||||
title={i18n.__('Fullscreen')}
|
||||
onMouseDown={e => onClick(e)}
|
||||
>
|
||||
<img src='../resources/icon/icon-full.svg' />
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Fullscreen')}({hotkey})
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const InfoButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-infoButton'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
const InfoButton = ({ onClick }) => (
|
||||
<button styleName='control-infoButton' onClick={e => onClick(e)}>
|
||||
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Info')}</span>
|
||||
</button>
|
||||
|
||||
@@ -6,28 +6,47 @@ import copy from 'copy-to-clipboard'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class InfoPanel extends React.Component {
|
||||
copyNoteLink () {
|
||||
const {noteLink} = this.props
|
||||
copyNoteLink() {
|
||||
const { noteLink } = this.props
|
||||
this.refs.noteLink.select()
|
||||
copy(noteLink)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const {
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf, wordCount, letterCount, type, print
|
||||
storageName,
|
||||
folderName,
|
||||
noteLink,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
exportAsMd,
|
||||
exportAsTxt,
|
||||
exportAsHtml,
|
||||
exportAsPdf,
|
||||
wordCount,
|
||||
letterCount,
|
||||
type,
|
||||
print
|
||||
} = this.props
|
||||
return (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||
<div
|
||||
className='infoPanel'
|
||||
styleName='control-infoButton-panel'
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<div>
|
||||
<p styleName='modification-date'>{updatedAt}</p>
|
||||
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
||||
<p styleName='modification-date-desc'>
|
||||
{i18n.__('MODIFICATION DATE')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <div styleName='count-wrap'>
|
||||
{type === 'SNIPPET_NOTE' ? (
|
||||
''
|
||||
) : (
|
||||
<div styleName='count-wrap'>
|
||||
<div styleName='count-number'>
|
||||
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
||||
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p>
|
||||
@@ -37,12 +56,9 @@ class InfoPanel extends React.Component {
|
||||
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <hr />
|
||||
}
|
||||
{type === 'SNIPPET_NOTE' ? '' : <hr />}
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{storageName}</p>
|
||||
@@ -60,8 +76,18 @@ class InfoPanel extends React.Component {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input styleName='infoPanel-noteLink' ref='noteLink' defaultValue={noteLink} onClick={(e) => { e.target.select() }} />
|
||||
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
|
||||
<input
|
||||
styleName='infoPanel-noteLink'
|
||||
ref='noteLink'
|
||||
defaultValue={noteLink}
|
||||
onClick={e => {
|
||||
e.target.select()
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={() => this.copyNoteLink()}
|
||||
styleName='infoPanel-copyButton'
|
||||
>
|
||||
<i className='fa fa-clipboard' />
|
||||
</button>
|
||||
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
|
||||
@@ -70,27 +96,39 @@ class InfoPanel extends React.Component {
|
||||
<hr />
|
||||
|
||||
<div id='export-wrap'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsMd(e, 'export-md')}
|
||||
>
|
||||
<i className='fa fa-file-code-o' />
|
||||
<p>{i18n.__('.md')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsTxt(e, 'export-txt')}
|
||||
>
|
||||
<i className='fa fa-file-text-o' />
|
||||
<p>{i18n.__('.txt')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsHtml(e, 'export-html')}
|
||||
>
|
||||
<i className='fa fa-html5' />
|
||||
<p>{i18n.__('.html')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||
>
|
||||
<i className='fa fa-file-pdf-o' />
|
||||
<p>{i18n.__('.pdf')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => print(e, 'print')}>
|
||||
<button styleName='export--enable' onClick={e => print(e, 'print')}>
|
||||
<i className='fa fa-print' />
|
||||
<p>{i18n.__('Print')}</p>
|
||||
</button>
|
||||
|
||||
@@ -5,9 +5,20 @@ import styles from './InfoPanel.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const InfoPanelTrashed = ({
|
||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf
|
||||
storageName,
|
||||
folderName,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
exportAsMd,
|
||||
exportAsTxt,
|
||||
exportAsHtml,
|
||||
exportAsPdf
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||
<div
|
||||
className='infoPanel'
|
||||
styleName='control-infoButton-panel-trash'
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<div>
|
||||
<p styleName='modification-date'>{updatedAt}</p>
|
||||
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
||||
@@ -21,7 +32,10 @@ const InfoPanelTrashed = ({
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
|
||||
<p styleName='infoPanel-default'>
|
||||
<text styleName='infoPanel-trash'>Trash</text>
|
||||
{folderName}
|
||||
</p>
|
||||
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
|
||||
</div>
|
||||
|
||||
@@ -31,22 +45,34 @@ const InfoPanelTrashed = ({
|
||||
</div>
|
||||
|
||||
<div id='export-wrap'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsMd(e, 'export-md')}
|
||||
>
|
||||
<i className='fa fa-file-code-o' />
|
||||
<p>.md</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsTxt(e, 'export-txt')}
|
||||
>
|
||||
<i className='fa fa-file-text-o' />
|
||||
<p>.txt</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsHtml(e, 'export-html')}
|
||||
>
|
||||
<i className='fa fa-html5' />
|
||||
<p>.html</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||
>
|
||||
<i className='fa fa-file-pdf-o' />
|
||||
<p>.pdf</p>
|
||||
</button>
|
||||
|
||||
@@ -34,16 +34,19 @@ import { replace } from 'connected-react-router'
|
||||
import ToggleDirectionButton from 'browser/main/Detail/ToggleDirectionButton'
|
||||
|
||||
class MarkdownNoteDetail extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isMovingNote: false,
|
||||
note: Object.assign({
|
||||
title: '',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}, props.note),
|
||||
note: Object.assign(
|
||||
{
|
||||
title: '',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
},
|
||||
props.note
|
||||
),
|
||||
isLockButtonShown: props.config.editor.type !== 'SPLIT',
|
||||
isLocked: false,
|
||||
editorType: props.config.editor.type,
|
||||
@@ -57,37 +60,42 @@ class MarkdownNoteDetail extends React.Component {
|
||||
this.generateToc = () => this.handleGenerateToc()
|
||||
}
|
||||
|
||||
focus () {
|
||||
focus() {
|
||||
this.refs.content.focus()
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
|
||||
ee.on('topbar:togglemodebutton', () => {
|
||||
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||
const reversedType =
|
||||
this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||
this.handleSwitchMode(reversedType)
|
||||
})
|
||||
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
|
||||
ee.on('code:generate-toc', this.generateToc)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const isNewNote = nextProps.note.key !== this.props.note.key
|
||||
const hasDeletedTags = nextProps.note.tags.length < this.props.note.tags.length
|
||||
const hasDeletedTags =
|
||||
nextProps.note.tags.length < this.props.note.tags.length
|
||||
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
this.setState({
|
||||
note: Object.assign({linesHighlighted: []}, nextProps.note)
|
||||
}, () => {
|
||||
this.refs.content.reload()
|
||||
if (this.refs.tags) this.refs.tags.reset()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note: Object.assign({ linesHighlighted: [] }, nextProps.note)
|
||||
},
|
||||
() => {
|
||||
this.refs.content.reload()
|
||||
if (this.refs.tags) this.refs.tags.reset()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Focus content if using blur or double click
|
||||
// --> Moved here from componentDidMount so a re-render during search won't set focus to the editor
|
||||
const {switchPreview} = nextProps.config.editor
|
||||
const { switchPreview } = nextProps.config.editor
|
||||
|
||||
if (this.state.switchPreview !== switchPreview) {
|
||||
this.setState({
|
||||
@@ -100,24 +108,28 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||
ee.on('topbar:toggledirectionbutton', this.handleSwitchDirection)
|
||||
ee.off('code:generate-toc', this.generateToc)
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
}
|
||||
|
||||
handleUpdateTag () {
|
||||
handleUpdateTag() {
|
||||
const { note } = this.state
|
||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
handleUpdateContent () {
|
||||
handleUpdateContent() {
|
||||
const { note } = this.state
|
||||
note.content = this.refs.content.value
|
||||
|
||||
let title = findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)
|
||||
let title = findNoteTitle(
|
||||
note.content,
|
||||
this.props.config.editor.enableFrontMatterTitle,
|
||||
this.props.config.editor.frontMatterTitleField
|
||||
)
|
||||
title = striptags(title)
|
||||
title = markdown.strip(title)
|
||||
note.title = title
|
||||
@@ -125,37 +137,35 @@ class MarkdownNoteDetail extends React.Component {
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
updateNote (note) {
|
||||
updateNote(note) {
|
||||
note.updatedAt = new Date()
|
||||
this.setState({note}, () => {
|
||||
this.setState({ note }, () => {
|
||||
this.save()
|
||||
})
|
||||
}
|
||||
|
||||
save () {
|
||||
save() {
|
||||
clearTimeout(this.saveQueue)
|
||||
this.saveQueue = setTimeout(() => {
|
||||
this.saveNow()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
saveNow () {
|
||||
saveNow() {
|
||||
const { note, dispatch } = this.props
|
||||
clearTimeout(this.saveQueue)
|
||||
this.saveQueue = null
|
||||
|
||||
dataApi
|
||||
.updateNote(note.storage, note.key, this.state.note)
|
||||
.then((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||
dataApi.updateNote(note.storage, note.key, this.state.note).then(note => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||
})
|
||||
}
|
||||
|
||||
handleFolderChange (e) {
|
||||
handleFolderChange(e) {
|
||||
const { note } = this.state
|
||||
const value = this.refs.folder.value
|
||||
const splitted = value.split('-')
|
||||
@@ -164,64 +174,71 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
dataApi
|
||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||
.then((newNote) => {
|
||||
this.setState({
|
||||
isMovingNote: true,
|
||||
note: Object.assign({}, newNote)
|
||||
}, () => {
|
||||
const { dispatch, location } = this.props
|
||||
dispatch({
|
||||
type: 'MOVE_NOTE',
|
||||
originNote: note,
|
||||
note: newNote
|
||||
})
|
||||
dispatch(replace({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({
|
||||
key: newNote.key
|
||||
.then(newNote => {
|
||||
this.setState(
|
||||
{
|
||||
isMovingNote: true,
|
||||
note: Object.assign({}, newNote)
|
||||
},
|
||||
() => {
|
||||
const { dispatch, location } = this.props
|
||||
dispatch({
|
||||
type: 'MOVE_NOTE',
|
||||
originNote: note,
|
||||
note: newNote
|
||||
})
|
||||
}))
|
||||
this.setState({
|
||||
isMovingNote: false
|
||||
})
|
||||
})
|
||||
dispatch(
|
||||
replace({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({
|
||||
key: newNote.key
|
||||
})
|
||||
})
|
||||
)
|
||||
this.setState({
|
||||
isMovingNote: false
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
handleStarButtonClick (e) {
|
||||
handleStarButtonClick(e) {
|
||||
const { note } = this.state
|
||||
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||
if (!note.isStarred)
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||
|
||||
note.isStarred = !note.isStarred
|
||||
|
||||
this.setState({
|
||||
note
|
||||
}, () => {
|
||||
this.save()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note
|
||||
},
|
||||
() => {
|
||||
this.save()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
exportAsFile () {
|
||||
exportAsFile() {}
|
||||
|
||||
}
|
||||
|
||||
exportAsMd () {
|
||||
exportAsMd() {
|
||||
ee.emit('export:save-md')
|
||||
}
|
||||
|
||||
exportAsTxt () {
|
||||
exportAsTxt() {
|
||||
ee.emit('export:save-text')
|
||||
}
|
||||
|
||||
exportAsHtml () {
|
||||
exportAsHtml() {
|
||||
ee.emit('export:save-html')
|
||||
}
|
||||
|
||||
exportAsPdf () {
|
||||
exportAsPdf() {
|
||||
ee.emit('export:save-pdf')
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
// tab key
|
||||
case 9:
|
||||
@@ -231,7 +248,11 @@ class MarkdownNoteDetail extends React.Component {
|
||||
} else if (e.ctrlKey && e.shiftKey) {
|
||||
e.preventDefault()
|
||||
this.jumpPrevTab()
|
||||
} else if (!e.ctrlKey && !e.shiftKey && e.target === this.refs.description) {
|
||||
} else if (
|
||||
!e.ctrlKey &&
|
||||
!e.shiftKey &&
|
||||
e.target === this.refs.description
|
||||
) {
|
||||
e.preventDefault()
|
||||
this.focusEditor()
|
||||
}
|
||||
@@ -239,9 +260,8 @@ class MarkdownNoteDetail extends React.Component {
|
||||
// I key
|
||||
case 73:
|
||||
{
|
||||
const isSuper = global.process.platform === 'darwin'
|
||||
? e.metaKey
|
||||
: e.ctrlKey
|
||||
const isSuper =
|
||||
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey
|
||||
if (isSuper) {
|
||||
e.preventDefault()
|
||||
this.handleInfoButtonClick(e)
|
||||
@@ -251,17 +271,17 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleTrashButtonClick (e) {
|
||||
handleTrashButtonClick(e) {
|
||||
const { note } = this.state
|
||||
const { isTrashed } = note
|
||||
const { confirmDeletion } = this.props.config.ui
|
||||
|
||||
if (isTrashed) {
|
||||
if (confirmDeleteNote(confirmDeletion, true)) {
|
||||
const {note, dispatch} = this.props
|
||||
const { note, dispatch } = this.props
|
||||
dataApi
|
||||
.deleteNote(note.storage, note.key)
|
||||
.then((data) => {
|
||||
.then(data => {
|
||||
const dispatchHandler = () => {
|
||||
dispatch({
|
||||
type: 'DELETE_NOTE',
|
||||
@@ -277,109 +297,124 @@ class MarkdownNoteDetail extends React.Component {
|
||||
if (confirmDeleteNote(confirmDeletion, false)) {
|
||||
note.isTrashed = true
|
||||
|
||||
this.setState({
|
||||
note
|
||||
}, () => {
|
||||
this.save()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note
|
||||
},
|
||||
() => {
|
||||
this.save()
|
||||
}
|
||||
)
|
||||
|
||||
ee.emit('list:next')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleUndoButtonClick (e) {
|
||||
handleUndoButtonClick(e) {
|
||||
const { note } = this.state
|
||||
|
||||
note.isTrashed = false
|
||||
|
||||
this.setState({
|
||||
note
|
||||
}, () => {
|
||||
this.save()
|
||||
this.refs.content.reload()
|
||||
ee.emit('list:next')
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note
|
||||
},
|
||||
() => {
|
||||
this.save()
|
||||
this.refs.content.reload()
|
||||
ee.emit('list:next')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleFullScreenButton (e) {
|
||||
handleFullScreenButton(e) {
|
||||
ee.emit('editor:fullscreen')
|
||||
}
|
||||
|
||||
handleLockButtonMouseDown (e) {
|
||||
handleLockButtonMouseDown(e) {
|
||||
e.preventDefault()
|
||||
ee.emit('editor:lock')
|
||||
this.setState({ isLocked: !this.state.isLocked })
|
||||
if (this.state.isLocked) this.focus()
|
||||
}
|
||||
|
||||
getToggleLockButton () {
|
||||
return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg'
|
||||
getToggleLockButton() {
|
||||
return this.state.isLocked
|
||||
? '../resources/icon/icon-lock.svg'
|
||||
: '../resources/icon/icon-unlock.svg'
|
||||
}
|
||||
|
||||
handleDeleteKeyDown (e) {
|
||||
handleDeleteKeyDown(e) {
|
||||
if (e.keyCode === 27) this.handleDeleteCancelButtonClick(e)
|
||||
}
|
||||
|
||||
handleToggleLockButton (event, noteStatus) {
|
||||
handleToggleLockButton(event, noteStatus) {
|
||||
// first argument event is not used
|
||||
if (noteStatus === 'CODE') {
|
||||
this.setState({isLockButtonShown: true})
|
||||
this.setState({ isLockButtonShown: true })
|
||||
} else {
|
||||
this.setState({isLockButtonShown: false})
|
||||
this.setState({ isLockButtonShown: false })
|
||||
}
|
||||
}
|
||||
|
||||
handleGenerateToc () {
|
||||
handleGenerateToc() {
|
||||
const editor = this.refs.content.refs.code.editor
|
||||
markdownToc.generateInEditor(editor)
|
||||
}
|
||||
|
||||
handleFocus (e) {
|
||||
handleFocus(e) {
|
||||
this.focus()
|
||||
}
|
||||
|
||||
handleInfoButtonClick (e) {
|
||||
handleInfoButtonClick(e) {
|
||||
const infoPanel = document.querySelector('.infoPanel')
|
||||
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||
if (infoPanel.style)
|
||||
infoPanel.style.display =
|
||||
infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||
}
|
||||
|
||||
print (e) {
|
||||
print(e) {
|
||||
ee.emit('print')
|
||||
}
|
||||
|
||||
handleSwitchMode (type) {
|
||||
handleSwitchMode(type) {
|
||||
// If in split mode, hide the lock button
|
||||
this.setState({ editorType: type, isLockButtonShown: !(type === 'SPLIT') }, () => {
|
||||
this.focus()
|
||||
const newConfig = Object.assign({}, this.props.config)
|
||||
newConfig.editor.type = type
|
||||
ConfigManager.set(newConfig)
|
||||
})
|
||||
this.setState(
|
||||
{ editorType: type, isLockButtonShown: !(type === 'SPLIT') },
|
||||
() => {
|
||||
this.focus()
|
||||
const newConfig = Object.assign({}, this.props.config)
|
||||
newConfig.editor.type = type
|
||||
ConfigManager.set(newConfig)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleSwitchDirection () {
|
||||
handleSwitchDirection() {
|
||||
// If in split mode, hide the lock button
|
||||
const direction = this.state.RTL
|
||||
this.setState({ RTL: !direction })
|
||||
}
|
||||
|
||||
handleDeleteNote () {
|
||||
handleDeleteNote() {
|
||||
this.handleTrashButtonClick()
|
||||
}
|
||||
|
||||
handleClearTodo () {
|
||||
handleClearTodo() {
|
||||
const { note } = this.state
|
||||
const splitted = note.content.split('\n')
|
||||
|
||||
const clearTodoContent = splitted.map((line) => {
|
||||
const trimmedLine = line.trim()
|
||||
if (trimmedLine.match(/\[x\]/i)) {
|
||||
return line.replace(/\[x\]/i, '[ ]')
|
||||
} else {
|
||||
return line
|
||||
}
|
||||
}).join('\n')
|
||||
const clearTodoContent = splitted
|
||||
.map(line => {
|
||||
const trimmedLine = line.trim()
|
||||
if (trimmedLine.match(/\[x\]/i)) {
|
||||
return line.replace(/\[x\]/i, '[ ]')
|
||||
} else {
|
||||
return line
|
||||
}
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
note.content = clearTodoContent
|
||||
this.refs.content.setValue(note.content)
|
||||
@@ -387,40 +422,44 @@ class MarkdownNoteDetail extends React.Component {
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
renderEditor () {
|
||||
renderEditor() {
|
||||
const { config, ignorePreviewPointerEvents } = this.props
|
||||
const { note } = this.state
|
||||
|
||||
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
||||
return <MarkdownEditor
|
||||
ref='content'
|
||||
styleName='body-noteEditor'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
isLocked={this.state.isLocked}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
RTL={this.state.RTL}
|
||||
/>
|
||||
return (
|
||||
<MarkdownEditor
|
||||
ref='content'
|
||||
styleName='body-noteEditor'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
isLocked={this.state.isLocked}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
RTL={this.state.RTL}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return <MarkdownSplitEditor
|
||||
ref='content'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
RTL={this.state.RTL}
|
||||
/>
|
||||
return (
|
||||
<MarkdownSplitEditor
|
||||
ref='content'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
RTL={this.state.RTL}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { data, dispatch, location, config } = this.props
|
||||
const { note, editorType } = this.state
|
||||
const storageKey = note.storage
|
||||
@@ -428,123 +467,141 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
const options = []
|
||||
data.storageMap.forEach((storage, index) => {
|
||||
storage.folders.forEach((folder) => {
|
||||
storage.folders.forEach(folder => {
|
||||
options.push({
|
||||
storage: storage,
|
||||
folder: folder
|
||||
})
|
||||
})
|
||||
})
|
||||
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||
const currentOption = options.filter(
|
||||
option =>
|
||||
option.storage.key === storageKey && option.folder.key === folderKey
|
||||
)[0]
|
||||
|
||||
const trashTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
<InfoPanelTrashed
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
const detailTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<div>
|
||||
<FolderSelect styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
ref='folder'
|
||||
data={data}
|
||||
onChange={(e) => this.handleFolderChange(e)}
|
||||
const trashTopBar = (
|
||||
<div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<RestoreButton onClick={e => this.handleUndoButtonClick(e)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<PermanentDeleteButton
|
||||
onClick={e => this.handleTrashButtonClick(e)}
|
||||
/>
|
||||
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||
<InfoPanelTrashed
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TagSelect
|
||||
ref='tags'
|
||||
value={this.state.note.tags}
|
||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
data={data}
|
||||
dispatch={dispatch}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
coloredTags={config.coloredTags}
|
||||
/>
|
||||
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||
<ToggleDirectionButton onClick={(e) => this.handleSwitchDirection(e)} isRTL={this.state.RTL} />
|
||||
<StarButton
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
)
|
||||
|
||||
{(() => {
|
||||
const imgSrc = `${this.getToggleLockButton()}`
|
||||
const lockButtonComponent =
|
||||
<button styleName='control-lockButton'
|
||||
onFocus={(e) => this.handleFocus(e)}
|
||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
||||
>
|
||||
<img src={imgSrc} />
|
||||
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
|
||||
</button>
|
||||
const detailTopBar = (
|
||||
<div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<div>
|
||||
<FolderSelect
|
||||
styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
ref='folder'
|
||||
data={data}
|
||||
onChange={e => this.handleFolderChange(e)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
return (
|
||||
this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||
)
|
||||
})()}
|
||||
<TagSelect
|
||||
ref='tags'
|
||||
value={this.state.note.tags}
|
||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
data={data}
|
||||
dispatch={dispatch}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
coloredTags={config.coloredTags}
|
||||
/>
|
||||
<TodoListPercentage
|
||||
onClearCheckboxClick={e => this.handleClearTodo(e)}
|
||||
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
||||
/>
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<ToggleModeButton
|
||||
onClick={e => this.handleSwitchMode(e)}
|
||||
editorType={editorType}
|
||||
/>
|
||||
<ToggleDirectionButton
|
||||
onClick={e => this.handleSwitchDirection(e)}
|
||||
isRTL={this.state.RTL}
|
||||
/>
|
||||
<StarButton
|
||||
onClick={e => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
|
||||
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
|
||||
{(() => {
|
||||
const imgSrc = `${this.getToggleLockButton()}`
|
||||
const lockButtonComponent = (
|
||||
<button
|
||||
styleName='control-lockButton'
|
||||
onFocus={e => this.handleFocus(e)}
|
||||
onMouseDown={e => this.handleLockButtonMouseDown(e)}
|
||||
>
|
||||
<img src={imgSrc} />
|
||||
{this.state.isLocked ? (
|
||||
<span styleName='tooltip'>Unlock</span>
|
||||
) : (
|
||||
<span styleName='tooltip'>Lock</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
return this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||
})()}
|
||||
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
<FullscreenButton onClick={e => this.handleFullScreenButton(e)} />
|
||||
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
wordCount={note.content.trim().split(/\s+/g).length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
print={this.print}
|
||||
/>
|
||||
<TrashButton onClick={e => this.handleTrashButtonClick(e)} />
|
||||
|
||||
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
noteLink={`[${note.title}](:note:${
|
||||
queryString.parse(location.search).key
|
||||
})`}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
wordCount={note.content.trim().split(/\s+/g).length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
print={this.print}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='NoteDetail'
|
||||
<div
|
||||
className='NoteDetail'
|
||||
style={this.props.style}
|
||||
styleName='root'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
|
||||
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||
|
||||
<div styleName='body'>
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
<div styleName='body'>{this.renderEditor()}</div>
|
||||
|
||||
<StatusBar
|
||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||
@@ -558,9 +615,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
MarkdownNoteDetail.propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
repositories: PropTypes.array,
|
||||
note: PropTypes.shape({
|
||||
|
||||
}),
|
||||
note: PropTypes.shape({}),
|
||||
style: PropTypes.shape({
|
||||
left: PropTypes.number
|
||||
}),
|
||||
|
||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TrashButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const PermanentDeleteButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-trashButton--in-trash'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
const PermanentDeleteButton = ({ onClick }) => (
|
||||
<button styleName='control-trashButton--in-trash' onClick={e => onClick(e)}>
|
||||
<img src='../resources/icon/icon-trash.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
|
||||
</button>
|
||||
|
||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './RestoreButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const RestoreButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-restoreButton'
|
||||
onClick={onClick}
|
||||
>
|
||||
const RestoreButton = ({ onClick }) => (
|
||||
<button styleName='control-restoreButton' onClick={onClick}>
|
||||
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
|
||||
<span styleName='tooltip'>{i18n.__('Restore')}</span>
|
||||
</button>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class StarButton extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -14,47 +14,51 @@ class StarButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDown (e) {
|
||||
handleMouseDown(e) {
|
||||
this.setState({
|
||||
isActive: true
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
handleMouseUp(e) {
|
||||
this.setState({
|
||||
isActive: false
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseLeave (e) {
|
||||
handleMouseLeave(e) {
|
||||
this.setState({
|
||||
isActive: false
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { className } = this.props
|
||||
|
||||
return (
|
||||
<button className={_.isString(className)
|
||||
? 'StarButton ' + className
|
||||
: 'StarButton'
|
||||
<button
|
||||
className={
|
||||
_.isString(className) ? 'StarButton ' + className : 'StarButton'
|
||||
}
|
||||
styleName={this.state.isActive || this.props.isActive
|
||||
? 'root--active'
|
||||
: 'root'
|
||||
styleName={
|
||||
this.state.isActive || this.props.isActive ? 'root--active' : 'root'
|
||||
}
|
||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
||||
onClick={this.props.onClick}>
|
||||
<img styleName='icon'
|
||||
src={this.state.isActive || this.props.isActive
|
||||
? '../resources/icon/icon-starred.svg'
|
||||
: '../resources/icon/icon-star.svg'
|
||||
onMouseDown={e => this.handleMouseDown(e)}
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
onMouseLeave={e => this.handleMouseLeave(e)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<img
|
||||
styleName='icon'
|
||||
src={
|
||||
this.state.isActive || this.props.isActive
|
||||
? '../resources/icon/icon-starred.svg'
|
||||
: '../resources/icon/icon-star.svg'
|
||||
}
|
||||
/>
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Star')}</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Star')}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import Autosuggest from 'react-autosuggest'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
class TagSelect extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -23,12 +23,16 @@ class TagSelect extends React.Component {
|
||||
this.onInputBlur = this.onInputBlur.bind(this)
|
||||
this.onInputChange = this.onInputChange.bind(this)
|
||||
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
||||
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
|
||||
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
|
||||
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(
|
||||
this
|
||||
)
|
||||
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(
|
||||
this
|
||||
)
|
||||
this.onSuggestionSelected = this.onSuggestionSelected.bind(this)
|
||||
}
|
||||
|
||||
addNewTag (newTag) {
|
||||
addNewTag(newTag) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
||||
|
||||
newTag = newTag.trim().replace(/ +/g, '_')
|
||||
@@ -44,9 +48,7 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
|
||||
let { value } = this.props
|
||||
value = _.isArray(value)
|
||||
? value.slice()
|
||||
: []
|
||||
value = _.isArray(value) ? value.slice() : []
|
||||
|
||||
if (!_.includes(value, newTag)) {
|
||||
value.push(newTag)
|
||||
@@ -56,27 +58,31 @@ class TagSelect extends React.Component {
|
||||
value = _.sortBy(value)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
newTag: ''
|
||||
}, () => {
|
||||
this.value = value
|
||||
this.props.onChange()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
newTag: ''
|
||||
},
|
||||
() => {
|
||||
this.value = value
|
||||
this.props.onChange()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
buildSuggestions () {
|
||||
this.suggestions = _.sortBy(this.props.data.tagNoteMap.map(
|
||||
(tag, name) => ({
|
||||
name,
|
||||
nameLC: name.toLowerCase(),
|
||||
size: tag.size
|
||||
})
|
||||
).filter(
|
||||
tag => tag.size > 0
|
||||
), ['name'])
|
||||
buildSuggestions() {
|
||||
this.suggestions = _.sortBy(
|
||||
this.props.data.tagNoteMap
|
||||
.map((tag, name) => ({
|
||||
name,
|
||||
nameLC: name.toLowerCase(),
|
||||
size: tag.size
|
||||
}))
|
||||
.filter(tag => tag.size > 0),
|
||||
['name']
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.value = this.props.value
|
||||
|
||||
this.buildSuggestions()
|
||||
@@ -84,19 +90,19 @@ class TagSelect extends React.Component {
|
||||
ee.on('editor:add-tag', this.handleAddTag)
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
componentDidUpdate() {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ee.off('editor:add-tag', this.handleAddTag)
|
||||
}
|
||||
|
||||
handleAddTag () {
|
||||
handleAddTag() {
|
||||
this.refs.newTag.input.focus()
|
||||
}
|
||||
|
||||
handleTagLabelClick (tag) {
|
||||
handleTagLabelClick(tag) {
|
||||
const { dispatch } = this.props
|
||||
|
||||
// Note: `tag` requires encoding later.
|
||||
@@ -104,23 +110,23 @@ class TagSelect extends React.Component {
|
||||
dispatch(push(`/tags/${tag}`))
|
||||
}
|
||||
|
||||
handleTagRemoveButtonClick (tag) {
|
||||
handleTagRemoveButtonClick(tag) {
|
||||
this.removeTagByCallback((value, tag) => {
|
||||
value.splice(value.indexOf(tag), 1)
|
||||
}, tag)
|
||||
}
|
||||
|
||||
onInputBlur (e) {
|
||||
onInputBlur(e) {
|
||||
this.submitNewTag()
|
||||
}
|
||||
|
||||
onInputChange (e, { newValue, method }) {
|
||||
onInputChange(e, { newValue, method }) {
|
||||
this.setState({
|
||||
newTag: newValue
|
||||
})
|
||||
}
|
||||
|
||||
onInputKeyDown (e) {
|
||||
onInputKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 9:
|
||||
e.preventDefault()
|
||||
@@ -136,17 +142,18 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onSuggestionsClearRequested () {
|
||||
onSuggestionsClearRequested() {
|
||||
this.setState({
|
||||
suggestions: []
|
||||
})
|
||||
}
|
||||
|
||||
onSuggestionsFetchRequested ({ value }) {
|
||||
onSuggestionsFetchRequested({ value }) {
|
||||
const valueLC = value.toLowerCase()
|
||||
const suggestions = _.filter(
|
||||
this.suggestions,
|
||||
tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
|
||||
tag =>
|
||||
!_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
|
||||
)
|
||||
|
||||
this.setState({
|
||||
@@ -154,22 +161,20 @@ class TagSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
onSuggestionSelected (event, { suggestion, suggestionValue }) {
|
||||
onSuggestionSelected(event, { suggestion, suggestionValue }) {
|
||||
this.addNewTag(suggestionValue)
|
||||
}
|
||||
|
||||
removeLastTag () {
|
||||
this.removeTagByCallback((value) => {
|
||||
removeLastTag() {
|
||||
this.removeTagByCallback(value => {
|
||||
value.pop()
|
||||
})
|
||||
}
|
||||
|
||||
removeTagByCallback (callback, tag = null) {
|
||||
removeTagByCallback(callback, tag = null) {
|
||||
let { value } = this.props
|
||||
|
||||
value = _.isArray(value)
|
||||
? value.slice()
|
||||
: []
|
||||
value = _.isArray(value) ? value.slice() : []
|
||||
callback(value, tag)
|
||||
value = _.uniq(value)
|
||||
|
||||
@@ -177,7 +182,7 @@ class TagSelect extends React.Component {
|
||||
this.props.onChange()
|
||||
}
|
||||
|
||||
reset () {
|
||||
reset() {
|
||||
this.buildSuggestions()
|
||||
|
||||
this.setState({
|
||||
@@ -185,51 +190,60 @@ class TagSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
submitNewTag () {
|
||||
submitNewTag() {
|
||||
this.addNewTag(this.refs.newTag.input.value)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { value, className, showTagsAlphabetically, coloredTags } = this.props
|
||||
|
||||
const tagList = _.isArray(value)
|
||||
? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
|
||||
const wrapperStyle = {}
|
||||
const textStyle = {}
|
||||
const BLACK = '#333333'
|
||||
const WHITE = '#f1f1f1'
|
||||
const color = coloredTags[tag]
|
||||
const invertedColor = color && invertColor(color, { black: BLACK, white: WHITE })
|
||||
let iconRemove = '../resources/icon/icon-x.svg'
|
||||
if (color) {
|
||||
wrapperStyle.backgroundColor = color
|
||||
textStyle.color = invertedColor
|
||||
}
|
||||
if (invertedColor === WHITE) {
|
||||
iconRemove = '../resources/icon/icon-x-light.svg'
|
||||
}
|
||||
return (
|
||||
<span styleName='tag'
|
||||
key={tag}
|
||||
style={wrapperStyle}
|
||||
>
|
||||
<span styleName='tag-label' style={textStyle} onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
|
||||
<button styleName='tag-removeButton'
|
||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
||||
>
|
||||
<img className='tag-removeButton-icon' src={iconRemove} width='8px' />
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
? (showTagsAlphabetically ? _.sortBy(value) : value).map(tag => {
|
||||
const wrapperStyle = {}
|
||||
const textStyle = {}
|
||||
const BLACK = '#333333'
|
||||
const WHITE = '#f1f1f1'
|
||||
const color = coloredTags[tag]
|
||||
const invertedColor =
|
||||
color && invertColor(color, { black: BLACK, white: WHITE })
|
||||
let iconRemove = '../resources/icon/icon-x.svg'
|
||||
if (color) {
|
||||
wrapperStyle.backgroundColor = color
|
||||
textStyle.color = invertedColor
|
||||
}
|
||||
if (invertedColor === WHITE) {
|
||||
iconRemove = '../resources/icon/icon-x-light.svg'
|
||||
}
|
||||
return (
|
||||
<span styleName='tag' key={tag} style={wrapperStyle}>
|
||||
<span
|
||||
styleName='tag-label'
|
||||
style={textStyle}
|
||||
onClick={e => this.handleTagLabelClick(tag)}
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
<button
|
||||
styleName='tag-removeButton'
|
||||
onClick={e => this.handleTagRemoveButtonClick(tag)}
|
||||
>
|
||||
<img
|
||||
className='tag-removeButton-icon'
|
||||
src={iconRemove}
|
||||
width='8px'
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
: []
|
||||
|
||||
const { newTag, suggestions } = this.state
|
||||
|
||||
return (
|
||||
<div className={_.isString(className)
|
||||
? 'TagSelect ' + className
|
||||
: 'TagSelect'
|
||||
<div
|
||||
className={
|
||||
_.isString(className) ? 'TagSelect ' + className : 'TagSelect'
|
||||
}
|
||||
styleName='root'
|
||||
>
|
||||
@@ -241,11 +255,7 @@ class TagSelect extends React.Component {
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
getSuggestionValue={suggestion => suggestion.name}
|
||||
renderSuggestion={suggestion => (
|
||||
<div>
|
||||
{suggestion.name}
|
||||
</div>
|
||||
)}
|
||||
renderSuggestion={suggestion => <div>{suggestion.name}</div>}
|
||||
inputProps={{
|
||||
placeholder: i18n.__('Add tag...'),
|
||||
value: newTag,
|
||||
|
||||
@@ -4,9 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ToggleDirectionButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const ToggleDirectionButton = ({
|
||||
onClick, isRTL
|
||||
}) => (
|
||||
const ToggleDirectionButton = ({ onClick, isRTL }) => (
|
||||
<div styleName='control-toggleModeButton'>
|
||||
<div styleName={isRTL ? 'active' : undefined} onClick={() => onClick()}>
|
||||
<img src={!isRTL ? '../resources/icon/icon-left-to-right.svg' : ''} />
|
||||
@@ -14,7 +12,9 @@ const ToggleDirectionButton = ({
|
||||
<div styleName={!isRTL ? 'active' : undefined} onClick={() => onClick()}>
|
||||
<img src={!isRTL ? '' : '../resources/icon/icon-right-to-left.svg'} />
|
||||
</div>
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Direction')}</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Toggle Direction')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -4,17 +4,35 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ToggleModeButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const ToggleModeButton = ({
|
||||
onClick, editorType
|
||||
}) => (
|
||||
const ToggleModeButton = ({ onClick, editorType }) => (
|
||||
<div styleName='control-toggleModeButton'>
|
||||
<div styleName={editorType === 'SPLIT' ? 'active' : undefined} onClick={() => onClick('SPLIT')}>
|
||||
<img src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
|
||||
<div
|
||||
styleName={editorType === 'SPLIT' ? 'active' : undefined}
|
||||
onClick={() => onClick('SPLIT')}
|
||||
>
|
||||
<img
|
||||
src={
|
||||
editorType === 'EDITOR_PREVIEW'
|
||||
? '../resources/icon/icon-mode-markdown-off-active.svg'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined} onClick={() => onClick('EDITOR_PREVIEW')}>
|
||||
<img src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
||||
<div
|
||||
styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined}
|
||||
onClick={() => onClick('EDITOR_PREVIEW')}
|
||||
>
|
||||
<img
|
||||
src={
|
||||
editorType === 'EDITOR_PREVIEW'
|
||||
? ''
|
||||
: '../resources/icon/icon-mode-split-on-active.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Toggle Mode')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -4,14 +4,12 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TrashButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const TrashButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-trashButton'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
const TrashButton = ({ onClick }) => (
|
||||
<button styleName='control-trashButton' onClick={e => onClick(e)}>
|
||||
<img src='../resources/icon/icon-trash.svg' />
|
||||
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Trash')}</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Trash')}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import queryString from 'query-string'
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
class Detail extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.focusHandler = () => {
|
||||
@@ -26,41 +26,55 @@ class Detail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
ee.on('detail:focus', this.focusHandler)
|
||||
ee.on('detail:delete', this.deleteHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ee.off('detail:focus', this.focusHandler)
|
||||
ee.off('detail:delete', this.deleteHandler)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, data, match: { params }, config } = this.props
|
||||
const noteKey = location.search !== '' && queryString.parse(location.search).key
|
||||
render() {
|
||||
const {
|
||||
location,
|
||||
data,
|
||||
match: { params },
|
||||
config
|
||||
} = this.props
|
||||
const noteKey =
|
||||
location.search !== '' && queryString.parse(location.search).key
|
||||
let note = null
|
||||
|
||||
if (location.search !== '') {
|
||||
const allNotes = data.noteMap.map(note => note)
|
||||
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||
const trashedNotes = data.trashedSet
|
||||
.toJS()
|
||||
.map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||
let displayedNotes = allNotes
|
||||
|
||||
if (location.pathname.match(/\/searched/)) {
|
||||
const searchStr = params.searchword
|
||||
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
|
||||
: searchFromNotes(allNotes, searchStr)
|
||||
displayedNotes =
|
||||
searchStr === undefined || searchStr === ''
|
||||
? allNotes
|
||||
: searchFromNotes(allNotes, searchStr)
|
||||
} else if (location.pathname.match(/^\/tags/)) {
|
||||
const listOfTags = params.tagname.split(' ')
|
||||
displayedNotes = data.noteMap.map(note => note).filter(note =>
|
||||
listOfTags.every(tag => note.tags.includes(tag))
|
||||
)
|
||||
displayedNotes = data.noteMap
|
||||
.map(note => note)
|
||||
.filter(note => listOfTags.every(tag => note.tags.includes(tag)))
|
||||
}
|
||||
|
||||
if (location.pathname.match(/^\/trashed/)) {
|
||||
displayedNotes = trashedNotes
|
||||
} else {
|
||||
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
|
||||
displayedNotes = _.differenceWith(
|
||||
displayedNotes,
|
||||
trashedNotes,
|
||||
(note, trashed) => note.key === trashed.key
|
||||
)
|
||||
}
|
||||
|
||||
const noteKeys = displayedNotes.map(note => note.key)
|
||||
@@ -71,12 +85,12 @@ class Detail extends React.Component {
|
||||
|
||||
if (note == null) {
|
||||
return (
|
||||
<div styleName='root'
|
||||
style={this.props.style}
|
||||
tabIndex='0'
|
||||
>
|
||||
<div styleName='root' style={this.props.style} tabIndex='0'>
|
||||
<div styleName='empty'>
|
||||
<div styleName='empty-message'>{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />{i18n.__('to create a new note')}</div>
|
||||
<div styleName='empty-message'>
|
||||
{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />
|
||||
{i18n.__('to create a new note')}
|
||||
</div>
|
||||
</div>
|
||||
<StatusBar
|
||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||
|
||||
@@ -24,7 +24,7 @@ const electron = require('electron')
|
||||
const { remote } = electron
|
||||
|
||||
class Main extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
@@ -46,7 +46,7 @@ class Main extends React.Component {
|
||||
this.toggleFullScreen = () => this.handleFullScreenButton()
|
||||
}
|
||||
|
||||
getChildContext () {
|
||||
getChildContext() {
|
||||
const { status, config } = this.props
|
||||
|
||||
return {
|
||||
@@ -55,7 +55,7 @@ class Main extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
init() {
|
||||
dataApi
|
||||
.addStorage({
|
||||
name: 'My Storage Location',
|
||||
@@ -93,18 +93,21 @@ class Main extends React.Component {
|
||||
type: 'SNIPPET_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
title: 'Snippet note example',
|
||||
description: 'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
|
||||
description:
|
||||
'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
|
||||
snippets: [
|
||||
{
|
||||
name: 'example.html',
|
||||
mode: 'html',
|
||||
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
|
||||
content:
|
||||
"<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
|
||||
linesHighlighted: []
|
||||
},
|
||||
{
|
||||
name: 'example.js',
|
||||
mode: 'javascript',
|
||||
content: "var boostnote = document.getElementById('hello').innerHTML\n\nconsole.log(boostnote)",
|
||||
content:
|
||||
"var boostnote = document.getElementById('hello').innerHTML\n\nconsole.log(boostnote)",
|
||||
linesHighlighted: []
|
||||
}
|
||||
]
|
||||
@@ -120,7 +123,8 @@ class Main extends React.Component {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
title: 'Welcome to Boostnote!',
|
||||
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
||||
content:
|
||||
'# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
||||
})
|
||||
.then(note => {
|
||||
store.dispatch({
|
||||
@@ -141,7 +145,7 @@ class Main extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
const { dispatch, config } = this.props
|
||||
|
||||
if (uiThemes.some(theme => theme.name === config.ui.theme)) {
|
||||
@@ -173,38 +177,44 @@ class Main extends React.Component {
|
||||
delete CodeMirror.keyMap.emacs['Ctrl-V']
|
||||
|
||||
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
||||
eventEmitter.on('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
|
||||
eventEmitter.on(
|
||||
'menubar:togglemenubar',
|
||||
this.toggleMenuBarVisible.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
|
||||
eventEmitter.off('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
|
||||
eventEmitter.off(
|
||||
'menubar:togglemenubar',
|
||||
this.toggleMenuBarVisible.bind(this)
|
||||
)
|
||||
}
|
||||
|
||||
toggleMenuBarVisible () {
|
||||
toggleMenuBarVisible() {
|
||||
const { config } = this.props
|
||||
const { ui } = config
|
||||
|
||||
const newUI = Object.assign(ui, {showMenuBar: !ui.showMenuBar})
|
||||
const newUI = Object.assign(ui, { showMenuBar: !ui.showMenuBar })
|
||||
const newConfig = Object.assign(config, newUI)
|
||||
ConfigManager.set(newConfig)
|
||||
}
|
||||
|
||||
handleLeftSlideMouseDown (e) {
|
||||
handleLeftSlideMouseDown(e) {
|
||||
e.preventDefault()
|
||||
this.setState({
|
||||
isLeftSliderFocused: true
|
||||
})
|
||||
}
|
||||
|
||||
handleRightSlideMouseDown (e) {
|
||||
handleRightSlideMouseDown(e) {
|
||||
e.preventDefault()
|
||||
this.setState({
|
||||
isRightSliderFocused: true
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
handleMouseUp(e) {
|
||||
// Change width of NoteList component.
|
||||
if (this.state.isRightSliderFocused) {
|
||||
this.setState(
|
||||
@@ -244,7 +254,7 @@ class Main extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseMove (e) {
|
||||
handleMouseMove(e) {
|
||||
if (this.state.isRightSliderFocused) {
|
||||
const offset = this.refs.body.getBoundingClientRect().left
|
||||
let newListWidth = e.pageX - offset
|
||||
@@ -270,7 +280,7 @@ class Main extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleFullScreenButton (e) {
|
||||
handleFullScreenButton(e) {
|
||||
this.setState({ fullScreen: !this.state.fullScreen }, () => {
|
||||
const noteDetail = document.querySelector('.NoteDetail')
|
||||
const noteList = document.querySelector('.NoteList')
|
||||
@@ -284,7 +294,7 @@ class Main extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
hideLeftLists (noteDetail, noteList, mainBody) {
|
||||
hideLeftLists(noteDetail, noteList, mainBody) {
|
||||
this.setState({ noteDetailWidth: noteDetail.style.left })
|
||||
this.setState({ mainBodyWidth: mainBody.style.left })
|
||||
noteDetail.style.left = '0px'
|
||||
@@ -292,13 +302,13 @@ class Main extends React.Component {
|
||||
noteList.style.display = 'none'
|
||||
}
|
||||
|
||||
showLeftLists (noteDetail, noteList, mainBody) {
|
||||
showLeftLists(noteDetail, noteList, mainBody) {
|
||||
noteDetail.style.left = this.state.noteDetailWidth
|
||||
mainBody.style.left = this.state.mainBodyWidth
|
||||
noteList.style.display = 'inline'
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { config } = this.props
|
||||
|
||||
// the width of the navigation bar when it is folded/collapsed
|
||||
@@ -312,10 +322,16 @@ class Main extends React.Component {
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
>
|
||||
<SideNav
|
||||
{..._.pick(this.props, ['dispatch', 'data', 'config', 'match', 'location'])}
|
||||
{..._.pick(this.props, [
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'match',
|
||||
'location'
|
||||
])}
|
||||
width={this.state.navWidth}
|
||||
/>
|
||||
{!config.isSideNavFolded &&
|
||||
{!config.isSideNavFolded && (
|
||||
<div
|
||||
styleName={
|
||||
this.state.isLeftSliderFocused ? 'slider--active' : 'slider'
|
||||
@@ -325,7 +341,8 @@ class Main extends React.Component {
|
||||
draggable='false'
|
||||
>
|
||||
<div styleName='slider-hitbox' />
|
||||
</div>}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
||||
id='main-body'
|
||||
|
||||
@@ -15,30 +15,48 @@ const { dialog } = remote
|
||||
const OSX = window.process.platform === 'darwin'
|
||||
|
||||
class NewNoteButton extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
}
|
||||
this.state = {}
|
||||
|
||||
this.handleNewNoteButtonClick = this.handleNewNoteButtonClick.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
eventEmitter.on('top:new-note', this.handleNewNoteButtonClick)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
eventEmitter.off('top:new-note', this.handleNewNoteButtonClick)
|
||||
}
|
||||
|
||||
handleNewNoteButtonClick (e) {
|
||||
const { location, dispatch, match: { params }, config } = this.props
|
||||
handleNewNoteButtonClick(e) {
|
||||
const {
|
||||
location,
|
||||
dispatch,
|
||||
match: { params },
|
||||
config
|
||||
} = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
|
||||
createMarkdownNote(storage.key, folder.key, dispatch, location, params, config)
|
||||
createMarkdownNote(
|
||||
storage.key,
|
||||
folder.key,
|
||||
dispatch,
|
||||
location,
|
||||
params,
|
||||
config
|
||||
)
|
||||
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
|
||||
createSnippetNote(storage.key, folder.key, dispatch, location, params, config)
|
||||
createSnippetNote(
|
||||
storage.key,
|
||||
folder.key,
|
||||
dispatch,
|
||||
location,
|
||||
params,
|
||||
config
|
||||
)
|
||||
} else {
|
||||
modal.open(NewNoteModal, {
|
||||
storage: storage.key,
|
||||
@@ -51,8 +69,11 @@ class NewNoteButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
const { data, match: { params } } = this.props
|
||||
resolveTargetFolder() {
|
||||
const {
|
||||
data,
|
||||
match: { params }
|
||||
} = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
// Find first storage
|
||||
if (storage == null) {
|
||||
@@ -62,9 +83,12 @@ class NewNoteButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
if (storage == null) this.showMessageBox(i18n.__('No storage to create a note'))
|
||||
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
|
||||
if (folder == null) this.showMessageBox(i18n.__('No folder to create a note'))
|
||||
if (storage == null)
|
||||
this.showMessageBox(i18n.__('No storage to create a note'))
|
||||
const folder =
|
||||
_.find(storage.folders, { key: params.folderKey }) || storage.folders[0]
|
||||
if (folder == null)
|
||||
this.showMessageBox(i18n.__('No folder to create a note'))
|
||||
|
||||
return {
|
||||
storage,
|
||||
@@ -72,7 +96,7 @@ class NewNoteButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
showMessageBox (message) {
|
||||
showMessageBox(message) {
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: message,
|
||||
@@ -80,16 +104,19 @@ class NewNoteButton extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { config, style } = this.props
|
||||
return (
|
||||
<div className='NewNoteButton'
|
||||
<div
|
||||
className='NewNoteButton'
|
||||
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
||||
style={style}
|
||||
>
|
||||
<div styleName='control'>
|
||||
<button styleName='control-newNoteButton'
|
||||
onClick={this.handleNewNoteButtonClick}>
|
||||
<button
|
||||
styleName='control-newNoteButton'
|
||||
onClick={this.handleNewNoteButtonClick}
|
||||
>
|
||||
<img src='../resources/icon/icon-newnote.svg' />
|
||||
<span styleName='control-newNoteButton-tooltip'>
|
||||
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,17 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './SwitchButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const ListButton = ({
|
||||
onClick, isTagActive
|
||||
}) => (
|
||||
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={onClick}>
|
||||
<img src={isTagActive
|
||||
? '../resources/icon/icon-list.svg'
|
||||
: '../resources/icon/icon-list-active.svg'
|
||||
}
|
||||
const ListButton = ({ onClick, isTagActive }) => (
|
||||
<button
|
||||
styleName={isTagActive ? 'non-active-button' : 'active-button'}
|
||||
onClick={onClick}
|
||||
>
|
||||
<img
|
||||
src={
|
||||
isTagActive
|
||||
? '../resources/icon/icon-list.svg'
|
||||
: '../resources/icon/icon-list-active.svg'
|
||||
}
|
||||
/>
|
||||
<span styleName='tooltip'>{i18n.__('Notes')}</span>
|
||||
</button>
|
||||
|
||||
@@ -4,10 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './PreferenceButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const PreferenceButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
|
||||
const PreferenceButton = ({ onClick }) => (
|
||||
<button styleName='top-menu-preference' onClick={e => onClick(e)}>
|
||||
<img src='../resources/icon/icon-setting.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Preferences')}</span>
|
||||
</button>
|
||||
|
||||
@@ -5,10 +5,14 @@ import styles from './SearchButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const SearchButton = ({ onClick, isActive }) => (
|
||||
<button styleName='top-menu-search' onClick={(e) => onClick(e)}>
|
||||
<button styleName='top-menu-search' onClick={e => onClick(e)}>
|
||||
<img
|
||||
styleName='icon-search'
|
||||
src={isActive ? '../resources/icon/icon-search-active.svg' : '../resources/icon/icon-search.svg'}
|
||||
src={
|
||||
isActive
|
||||
? '../resources/icon/icon-search-active.svg'
|
||||
: '../resources/icon/icon-search.svg'
|
||||
}
|
||||
/>
|
||||
<span styleName='tooltip'>{i18n.__('Search')}</span>
|
||||
</button>
|
||||
|
||||
@@ -19,7 +19,7 @@ const escapeStringRegexp = require('escape-string-regexp')
|
||||
const path = require('path')
|
||||
|
||||
class StorageItem extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
const { storage } = this.props
|
||||
@@ -30,11 +30,11 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleHeaderContextMenu (e) {
|
||||
handleHeaderContextMenu(e) {
|
||||
context.popup([
|
||||
{
|
||||
label: i18n.__('Add Folder'),
|
||||
click: (e) => this.handleAddFolderButtonClick(e)
|
||||
click: e => this.handleAddFolderButtonClick(e)
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
@@ -44,11 +44,11 @@ class StorageItem extends React.Component {
|
||||
submenu: [
|
||||
{
|
||||
label: i18n.__('Export as txt'),
|
||||
click: (e) => this.handleExportStorageClick(e, 'txt')
|
||||
click: e => this.handleExportStorageClick(e, 'txt')
|
||||
},
|
||||
{
|
||||
label: i18n.__('Export as md'),
|
||||
click: (e) => this.handleExportStorageClick(e, 'md')
|
||||
click: e => this.handleExportStorageClick(e, 'md')
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -57,75 +57,74 @@ class StorageItem extends React.Component {
|
||||
},
|
||||
{
|
||||
label: i18n.__('Unlink Storage'),
|
||||
click: (e) => this.handleUnlinkStorageClick(e)
|
||||
click: e => this.handleUnlinkStorageClick(e)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
handleUnlinkStorageClick (e) {
|
||||
handleUnlinkStorageClick(e) {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: i18n.__('Unlink Storage'),
|
||||
detail: i18n.__('This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)'),
|
||||
detail: i18n.__(
|
||||
"This work will just detatches a storage from Boostnote. (Any data won't be deleted.)"
|
||||
),
|
||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||
})
|
||||
|
||||
if (index === 0) {
|
||||
const { storage, dispatch } = this.props
|
||||
dataApi.removeStorage(storage.key)
|
||||
dataApi
|
||||
.removeStorage(storage.key)
|
||||
.then(() => {
|
||||
dispatch({
|
||||
type: 'REMOVE_STORAGE',
|
||||
storageKey: storage.key
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleExportStorageClick (e, fileType) {
|
||||
handleExportStorageClick(e, fileType) {
|
||||
const options = {
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
buttonLabel: i18n.__('Select directory'),
|
||||
title: i18n.__('Select a folder to export the files to'),
|
||||
multiSelections: false
|
||||
}
|
||||
dialog.showOpenDialog(remote.getCurrentWindow(), options,
|
||||
(paths) => {
|
||||
if (paths && paths.length === 1) {
|
||||
const { storage, dispatch } = this.props
|
||||
dataApi
|
||||
.exportStorage(storage.key, fileType, paths[0])
|
||||
.then(data => {
|
||||
dispatch({
|
||||
type: 'EXPORT_STORAGE',
|
||||
storage: data.storage,
|
||||
fileType: data.fileType
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
||||
if (paths && paths.length === 1) {
|
||||
const { storage, dispatch } = this.props
|
||||
dataApi.exportStorage(storage.key, fileType, paths[0]).then(data => {
|
||||
dispatch({
|
||||
type: 'EXPORT_STORAGE',
|
||||
storage: data.storage,
|
||||
fileType: data.fileType
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleToggleButtonClick (e) {
|
||||
handleToggleButtonClick(e) {
|
||||
const { storage, dispatch } = this.props
|
||||
const isOpen = !this.state.isOpen
|
||||
dataApi.toggleStorage(storage.key, isOpen)
|
||||
.then((storage) => {
|
||||
dispatch({
|
||||
type: 'EXPAND_STORAGE',
|
||||
storage,
|
||||
isOpen
|
||||
})
|
||||
dataApi.toggleStorage(storage.key, isOpen).then(storage => {
|
||||
dispatch({
|
||||
type: 'EXPAND_STORAGE',
|
||||
storage,
|
||||
isOpen
|
||||
})
|
||||
})
|
||||
this.setState({
|
||||
isOpen: isOpen
|
||||
})
|
||||
}
|
||||
|
||||
handleAddFolderButtonClick (e) {
|
||||
handleAddFolderButtonClick(e) {
|
||||
const { storage } = this.props
|
||||
|
||||
modal.open(CreateFolderModal, {
|
||||
@@ -133,23 +132,23 @@ class StorageItem extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleHeaderInfoClick (e) {
|
||||
handleHeaderInfoClick(e) {
|
||||
const { storage, dispatch } = this.props
|
||||
dispatch(push('/storages/' + storage.key))
|
||||
}
|
||||
|
||||
handleFolderButtonClick (folderKey) {
|
||||
return (e) => {
|
||||
handleFolderButtonClick(folderKey) {
|
||||
return e => {
|
||||
const { storage, dispatch } = this.props
|
||||
dispatch(push('/storages/' + storage.key + '/folders/' + folderKey))
|
||||
}
|
||||
}
|
||||
|
||||
handleFolderButtonContextMenu (e, folder) {
|
||||
handleFolderButtonContextMenu(e, folder) {
|
||||
context.popup([
|
||||
{
|
||||
label: i18n.__('Rename Folder'),
|
||||
click: (e) => this.handleRenameFolderClick(e, folder)
|
||||
click: e => this.handleRenameFolderClick(e, folder)
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
@@ -159,11 +158,11 @@ class StorageItem extends React.Component {
|
||||
submenu: [
|
||||
{
|
||||
label: i18n.__('Export as txt'),
|
||||
click: (e) => this.handleExportFolderClick(e, folder, 'txt')
|
||||
click: e => this.handleExportFolderClick(e, folder, 'txt')
|
||||
},
|
||||
{
|
||||
label: i18n.__('Export as md'),
|
||||
click: (e) => this.handleExportFolderClick(e, folder, 'md')
|
||||
click: e => this.handleExportFolderClick(e, folder, 'md')
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -172,12 +171,12 @@ class StorageItem extends React.Component {
|
||||
},
|
||||
{
|
||||
label: i18n.__('Delete Folder'),
|
||||
click: (e) => this.handleFolderDeleteClick(e, folder)
|
||||
click: e => this.handleFolderDeleteClick(e, folder)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
handleRenameFolderClick (e, folder) {
|
||||
handleRenameFolderClick(e, folder) {
|
||||
const { storage } = this.props
|
||||
modal.open(RenameFolderModal, {
|
||||
storage,
|
||||
@@ -185,20 +184,19 @@ class StorageItem extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleExportFolderClick (e, folder, fileType) {
|
||||
handleExportFolderClick(e, folder, fileType) {
|
||||
const options = {
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
buttonLabel: i18n.__('Select directory'),
|
||||
title: i18n.__('Select a folder to export the files to'),
|
||||
multiSelections: false
|
||||
}
|
||||
dialog.showOpenDialog(remote.getCurrentWindow(), options,
|
||||
(paths) => {
|
||||
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
|
||||
if (paths && paths.length === 1) {
|
||||
const { storage, dispatch } = this.props
|
||||
dataApi
|
||||
.exportFolder(storage.key, folder.key, fileType, paths[0])
|
||||
.then((data) => {
|
||||
.then(data => {
|
||||
dispatch({
|
||||
type: 'EXPORT_FOLDER',
|
||||
storage: data.storage,
|
||||
@@ -224,66 +222,74 @@ class StorageItem extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleFolderDeleteClick (e, folder) {
|
||||
handleFolderDeleteClick(e, folder) {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: i18n.__('Delete Folder'),
|
||||
detail: i18n.__('This will delete all notes in the folder and can not be undone.'),
|
||||
detail: i18n.__(
|
||||
'This will delete all notes in the folder and can not be undone.'
|
||||
),
|
||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||
})
|
||||
|
||||
if (index === 0) {
|
||||
const { storage, dispatch } = this.props
|
||||
dataApi
|
||||
.deleteFolder(storage.key, folder.key)
|
||||
.then((data) => {
|
||||
dispatch({
|
||||
type: 'DELETE_FOLDER',
|
||||
storage: data.storage,
|
||||
folderKey: data.folderKey
|
||||
})
|
||||
dataApi.deleteFolder(storage.key, folder.key).then(data => {
|
||||
dispatch({
|
||||
type: 'DELETE_FOLDER',
|
||||
storage: data.storage,
|
||||
folderKey: data.folderKey
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleDragEnter (e, key) {
|
||||
handleDragEnter(e, key) {
|
||||
e.preventDefault()
|
||||
if (this.state.draggedOver === key) { return }
|
||||
if (this.state.draggedOver === key) {
|
||||
return
|
||||
}
|
||||
this.setState({
|
||||
draggedOver: key
|
||||
})
|
||||
}
|
||||
|
||||
handleDragLeave (e) {
|
||||
handleDragLeave(e) {
|
||||
e.preventDefault()
|
||||
if (this.state.draggedOver === null) { return }
|
||||
if (this.state.draggedOver === null) {
|
||||
return
|
||||
}
|
||||
this.setState({
|
||||
draggedOver: null
|
||||
})
|
||||
}
|
||||
|
||||
dropNote (storage, folder, dispatch, location, noteData) {
|
||||
noteData = noteData.filter((note) => folder.key !== note.folder)
|
||||
dropNote(storage, folder, dispatch, location, noteData) {
|
||||
noteData = noteData.filter(note => folder.key !== note.folder)
|
||||
if (noteData.length === 0) return
|
||||
|
||||
Promise.all(
|
||||
noteData.map((note) => dataApi.moveNote(note.storage, note.key, storage.key, folder.key))
|
||||
noteData.map(note =>
|
||||
dataApi.moveNote(note.storage, note.key, storage.key, folder.key)
|
||||
)
|
||||
)
|
||||
.then((createdNoteData) => {
|
||||
createdNoteData.forEach((newNote) => {
|
||||
dispatch({
|
||||
type: 'MOVE_NOTE',
|
||||
originNote: noteData.find((note) => note.content === newNote.oldContent),
|
||||
note: newNote
|
||||
.then(createdNoteData => {
|
||||
createdNoteData.forEach(newNote => {
|
||||
dispatch({
|
||||
type: 'MOVE_NOTE',
|
||||
originNote: noteData.find(
|
||||
note => note.content === newNote.oldContent
|
||||
),
|
||||
note: newNote
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`error on delete notes: ${err}`)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(`error on delete notes: ${err}`)
|
||||
})
|
||||
}
|
||||
|
||||
handleDrop (e, storage, folder, dispatch, location) {
|
||||
handleDrop(e, storage, folder, dispatch, location) {
|
||||
e.preventDefault()
|
||||
if (this.state.draggedOver !== null) {
|
||||
this.setState({
|
||||
@@ -294,21 +300,37 @@ class StorageItem extends React.Component {
|
||||
this.dropNote(storage, folder, dispatch, location, noteData)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { storage, location, isFolded, data, dispatch } = this.props
|
||||
const { folderNoteMap, trashedSet } = data
|
||||
const SortableStorageItemChild = SortableElement(StorageItemChild)
|
||||
const folderList = storage.folders.map((folder, index) => {
|
||||
const folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
|
||||
const isActive = !!(location.pathname.match(folderRegex))
|
||||
const folderRegex = new RegExp(
|
||||
escapeStringRegexp(path.sep) +
|
||||
'storages' +
|
||||
escapeStringRegexp(path.sep) +
|
||||
storage.key +
|
||||
escapeStringRegexp(path.sep) +
|
||||
'folders' +
|
||||
escapeStringRegexp(path.sep) +
|
||||
folder.key
|
||||
)
|
||||
const isActive = !!location.pathname.match(folderRegex)
|
||||
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||
|
||||
let noteCount = 0
|
||||
if (noteSet) {
|
||||
let trashedNoteCount = 0
|
||||
const noteKeys = noteSet.map(noteKey => { return noteKey })
|
||||
const noteKeys = noteSet.map(noteKey => {
|
||||
return noteKey
|
||||
})
|
||||
trashedSet.toJS().forEach(trashedKey => {
|
||||
if (noteKeys.some(noteKey => { return noteKey === trashedKey })) trashedNoteCount++
|
||||
if (
|
||||
noteKeys.some(noteKey => {
|
||||
return noteKey === trashedKey
|
||||
})
|
||||
)
|
||||
trashedNoteCount++
|
||||
})
|
||||
noteCount = noteSet.size - trashedNoteCount
|
||||
}
|
||||
@@ -317,73 +339,80 @@ class StorageItem extends React.Component {
|
||||
key={folder.key}
|
||||
index={index}
|
||||
isActive={isActive || folder.key === this.state.draggedOver}
|
||||
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
|
||||
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
|
||||
handleButtonClick={e => this.handleFolderButtonClick(folder.key)(e)}
|
||||
handleContextMenu={e => this.handleFolderButtonContextMenu(e, folder)}
|
||||
folderName={folder.name}
|
||||
folderColor={folder.color}
|
||||
isFolded={isFolded}
|
||||
noteCount={noteCount}
|
||||
handleDrop={(e) => {
|
||||
handleDrop={e => {
|
||||
this.handleDrop(e, storage, folder, dispatch, location)
|
||||
}}
|
||||
handleDragEnter={(e) => {
|
||||
handleDragEnter={e => {
|
||||
this.handleDragEnter(e, folder.key)
|
||||
}}
|
||||
handleDragLeave={(e) => {
|
||||
handleDragLeave={e => {
|
||||
this.handleDragLeave(e, folder)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const isActive = location.pathname.match(new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + '$'))
|
||||
const isActive = location.pathname.match(
|
||||
new RegExp(
|
||||
escapeStringRegexp(path.sep) +
|
||||
'storages' +
|
||||
escapeStringRegexp(path.sep) +
|
||||
storage.key +
|
||||
'$'
|
||||
)
|
||||
)
|
||||
|
||||
return (
|
||||
<div styleName={isFolded ? 'root--folded' : 'root'}
|
||||
key={storage.key}
|
||||
>
|
||||
<div styleName={isActive
|
||||
? 'header--active'
|
||||
: 'header'
|
||||
}
|
||||
onContextMenu={(e) => this.handleHeaderContextMenu(e)}
|
||||
<div styleName={isFolded ? 'root--folded' : 'root'} key={storage.key}>
|
||||
<div
|
||||
styleName={isActive ? 'header--active' : 'header'}
|
||||
onContextMenu={e => this.handleHeaderContextMenu(e)}
|
||||
>
|
||||
<button styleName='header-toggleButton'
|
||||
onMouseDown={(e) => this.handleToggleButtonClick(e)}
|
||||
<button
|
||||
styleName='header-toggleButton'
|
||||
onMouseDown={e => this.handleToggleButtonClick(e)}
|
||||
>
|
||||
<img src={this.state.isOpen
|
||||
? '../resources/icon/icon-down.svg'
|
||||
: '../resources/icon/icon-right.svg'
|
||||
}
|
||||
<img
|
||||
src={
|
||||
this.state.isOpen
|
||||
? '../resources/icon/icon-down.svg'
|
||||
: '../resources/icon/icon-right.svg'
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{!isFolded &&
|
||||
<button styleName='header-addFolderButton'
|
||||
onClick={(e) => this.handleAddFolderButtonClick(e)}
|
||||
{!isFolded && (
|
||||
<button
|
||||
styleName='header-addFolderButton'
|
||||
onClick={e => this.handleAddFolderButtonClick(e)}
|
||||
>
|
||||
<img src='../resources/icon/icon-plus.svg' />
|
||||
</button>
|
||||
}
|
||||
)}
|
||||
|
||||
<button styleName='header-info'
|
||||
onClick={(e) => this.handleHeaderInfoClick(e)}
|
||||
<button
|
||||
styleName='header-info'
|
||||
onClick={e => this.handleHeaderInfoClick(e)}
|
||||
>
|
||||
<span>
|
||||
{isFolded ? _.truncate(storage.name, {length: 1, omission: ''}) : storage.name}
|
||||
{isFolded
|
||||
? _.truncate(storage.name, { length: 1, omission: '' })
|
||||
: storage.name}
|
||||
</span>
|
||||
{isFolded &&
|
||||
{isFolded && (
|
||||
<span styleName='header-info--folded-tooltip'>
|
||||
{storage.name}
|
||||
</span>
|
||||
}
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
{this.state.isOpen &&
|
||||
<div>
|
||||
{folderList}
|
||||
</div>
|
||||
}
|
||||
{this.state.isOpen && <div>{folderList}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,14 +4,17 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './SwitchButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const TagButton = ({
|
||||
onClick, isTagActive
|
||||
}) => (
|
||||
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={onClick}>
|
||||
<img src={isTagActive
|
||||
? '../resources/icon/icon-tag-active.svg'
|
||||
: '../resources/icon/icon-tag.svg'
|
||||
}
|
||||
const TagButton = ({ onClick, isTagActive }) => (
|
||||
<button
|
||||
styleName={isTagActive ? 'active-button' : 'non-active-button'}
|
||||
onClick={onClick}
|
||||
>
|
||||
<img
|
||||
src={
|
||||
isTagActive
|
||||
? '../resources/icon/icon-tag-active.svg'
|
||||
: '../resources/icon/icon-tag.svg'
|
||||
}
|
||||
/>
|
||||
<span styleName='tooltip'>{i18n.__('Tags')}</span>
|
||||
</button>
|
||||
|
||||
@@ -17,7 +17,7 @@ import PreferenceButton from './PreferenceButton'
|
||||
import SearchButton from './SearchButton'
|
||||
import ListButton from './ListButton'
|
||||
import TagButton from './TagButton'
|
||||
import {SortableContainer} from 'react-sortable-hoc'
|
||||
import { SortableContainer } from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import context from 'browser/lib/context'
|
||||
import { remote } from 'electron'
|
||||
@@ -25,13 +25,13 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import ColorPicker from 'browser/components/ColorPicker'
|
||||
import { every, sortBy } from 'lodash'
|
||||
|
||||
function matchActiveTags (tags, activeTags) {
|
||||
function matchActiveTags(tags, activeTags) {
|
||||
return every(activeTags, v => tags.indexOf(v) >= 0)
|
||||
}
|
||||
|
||||
class SideNav extends React.Component {
|
||||
// TODO: should not use electron stuff v0.7
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -53,24 +53,32 @@ class SideNav extends React.Component {
|
||||
this.handleSearchInputClear = this.handleSearchInputClear.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
EventEmitter.on('side:preferences', this.handlePreferenceButtonClick)
|
||||
componentDidMount() {
|
||||
EventEmitter.on('side:preferences', this.handleMenuButtonClick)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
EventEmitter.off('side:preferences', this.handlePreferenceButtonClick)
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
|
||||
}
|
||||
|
||||
deleteTag (tag) {
|
||||
const selectedButton = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: i18n.__('Confirm tag deletion'),
|
||||
detail: i18n.__('This will permanently remove this tag.'),
|
||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||
})
|
||||
deleteTag(tag) {
|
||||
const selectedButton = remote.dialog.showMessageBox(
|
||||
remote.getCurrentWindow(),
|
||||
{
|
||||
type: 'warning',
|
||||
message: i18n.__('Confirm tag deletion'),
|
||||
detail: i18n.__('This will permanently remove this tag.'),
|
||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||
}
|
||||
)
|
||||
|
||||
if (selectedButton === 0) {
|
||||
const { data, dispatch, location, match: { params } } = this.props
|
||||
const {
|
||||
data,
|
||||
dispatch,
|
||||
location,
|
||||
match: { params }
|
||||
} = this.props
|
||||
|
||||
const notes = data.noteMap
|
||||
.map(note => note)
|
||||
@@ -84,34 +92,38 @@ class SideNav extends React.Component {
|
||||
return note
|
||||
})
|
||||
|
||||
Promise
|
||||
.all(notes.map(note => dataApi.updateNote(note.storage, note.key, note)))
|
||||
.then(updatedNotes => {
|
||||
updatedNotes.forEach(note => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note
|
||||
})
|
||||
Promise.all(
|
||||
notes.map(note => dataApi.updateNote(note.storage, note.key, note))
|
||||
).then(updatedNotes => {
|
||||
updatedNotes.forEach(note => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note
|
||||
})
|
||||
|
||||
if (location.pathname.match('/tags')) {
|
||||
const tags = params.tagname.split(' ')
|
||||
const index = tags.indexOf(tag)
|
||||
if (index !== -1) {
|
||||
tags.splice(index, 1)
|
||||
|
||||
dispatch(push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (location.pathname.match('/tags')) {
|
||||
const tags = params.tagname.split(' ')
|
||||
const index = tags.indexOf(tag)
|
||||
if (index !== -1) {
|
||||
tags.splice(index, 1)
|
||||
|
||||
dispatch(
|
||||
push(
|
||||
`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handlePreferenceButtonClick (e) {
|
||||
handleMenuButtonClick(e) {
|
||||
openModal(PreferencesModal)
|
||||
}
|
||||
|
||||
handleSearchButtonClick (e) {
|
||||
handleSearchButtonClick(e) {
|
||||
const { showSearch } = this.state
|
||||
this.setState({
|
||||
showSearch: !showSearch,
|
||||
@@ -119,29 +131,29 @@ class SideNav extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchInputClear (e) {
|
||||
handleSearchInputClear(e) {
|
||||
this.setState({
|
||||
searchText: ''
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchInputChange (e) {
|
||||
handleSearchInputChange(e) {
|
||||
this.setState({
|
||||
searchText: e.target.value
|
||||
})
|
||||
}
|
||||
|
||||
handleHomeButtonClick (e) {
|
||||
handleHomeButtonClick(e) {
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/home'))
|
||||
}
|
||||
|
||||
handleStarredButtonClick (e) {
|
||||
handleStarredButtonClick(e) {
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/starred'))
|
||||
}
|
||||
|
||||
handleTagContextMenu (e, tag) {
|
||||
handleTagContextMenu(e, tag) {
|
||||
const menu = []
|
||||
|
||||
menu.push({
|
||||
@@ -151,13 +163,17 @@ class SideNav extends React.Component {
|
||||
|
||||
menu.push({
|
||||
label: i18n.__('Customize Color'),
|
||||
click: this.displayColorPicker.bind(this, tag, e.target.getBoundingClientRect())
|
||||
click: this.displayColorPicker.bind(
|
||||
this,
|
||||
tag,
|
||||
e.target.getBoundingClientRect()
|
||||
)
|
||||
})
|
||||
|
||||
context.popup(menu)
|
||||
}
|
||||
|
||||
dismissColorPicker () {
|
||||
dismissColorPicker() {
|
||||
this.setState({
|
||||
colorPicker: {
|
||||
show: false
|
||||
@@ -165,7 +181,7 @@ class SideNav extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
displayColorPicker (tagName, rect) {
|
||||
displayColorPicker(tagName, rect) {
|
||||
const { config } = this.props
|
||||
this.setState({
|
||||
colorPicker: {
|
||||
@@ -177,10 +193,17 @@ class SideNav extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleColorPickerConfirm (color) {
|
||||
const { dispatch, config: {coloredTags} } = this.props
|
||||
const { colorPicker: { tagName } } = this.state
|
||||
const newColoredTags = Object.assign({}, coloredTags, {[tagName]: color.hex})
|
||||
handleColorPickerConfirm(color) {
|
||||
const {
|
||||
dispatch,
|
||||
config: { coloredTags }
|
||||
} = this.props
|
||||
const {
|
||||
colorPicker: { tagName }
|
||||
} = this.state
|
||||
const newColoredTags = Object.assign({}, coloredTags, {
|
||||
[tagName]: color.hex
|
||||
})
|
||||
|
||||
const config = { coloredTags: newColoredTags }
|
||||
ConfigManager.set(config)
|
||||
@@ -191,9 +214,14 @@ class SideNav extends React.Component {
|
||||
this.dismissColorPicker()
|
||||
}
|
||||
|
||||
handleColorPickerReset () {
|
||||
const { dispatch, config: {coloredTags} } = this.props
|
||||
const { colorPicker: { tagName } } = this.state
|
||||
handleColorPickerReset() {
|
||||
const {
|
||||
dispatch,
|
||||
config: { coloredTags }
|
||||
} = this.props
|
||||
const {
|
||||
colorPicker: { tagName }
|
||||
} = this.state
|
||||
const newColoredTags = Object.assign({}, coloredTags)
|
||||
|
||||
delete newColoredTags[tagName]
|
||||
@@ -207,11 +235,11 @@ class SideNav extends React.Component {
|
||||
this.dismissColorPicker()
|
||||
}
|
||||
|
||||
handleToggleButtonClick (e) {
|
||||
handleToggleButtonClick(e) {
|
||||
const { dispatch, config } = this.props
|
||||
const { showSearch, searchText } = this.state
|
||||
|
||||
ConfigManager.set({isSideNavFolded: !config.isSideNavFolded})
|
||||
ConfigManager.set({ isSideNavFolded: !config.isSideNavFolded })
|
||||
dispatch({
|
||||
type: 'SET_IS_SIDENAV_FOLDED',
|
||||
isFolded: !config.isSideNavFolded
|
||||
@@ -224,33 +252,31 @@ class SideNav extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleTrashedButtonClick (e) {
|
||||
handleTrashedButtonClick(e) {
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/trashed'))
|
||||
}
|
||||
|
||||
handleSwitchFoldersButtonClick () {
|
||||
handleSwitchFoldersButtonClick() {
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/home'))
|
||||
}
|
||||
|
||||
handleSwitchTagsButtonClick () {
|
||||
handleSwitchTagsButtonClick() {
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/alltags'))
|
||||
}
|
||||
|
||||
onSortEnd (storage) {
|
||||
return ({oldIndex, newIndex}) => {
|
||||
onSortEnd(storage) {
|
||||
return ({ oldIndex, newIndex }) => {
|
||||
const { dispatch } = this.props
|
||||
dataApi
|
||||
.reorderFolder(storage.key, oldIndex, newIndex)
|
||||
.then((data) => {
|
||||
dispatch({ type: 'REORDER_FOLDER', storage: data.storage })
|
||||
})
|
||||
dataApi.reorderFolder(storage.key, oldIndex, newIndex).then(data => {
|
||||
dispatch({ type: 'REORDER_FOLDER', storage: data.storage })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
SideNavComponent (isFolded) {
|
||||
SideNavComponent(isFolded) {
|
||||
const { location, data, config, dispatch } = this.props
|
||||
const { showSearch, searchText } = this.state
|
||||
|
||||
@@ -261,27 +287,35 @@ class SideNav extends React.Component {
|
||||
let component
|
||||
|
||||
// TagsMode is not selected
|
||||
if (!location.pathname.match('/tags') && !location.pathname.match('/alltags')) {
|
||||
if (
|
||||
!location.pathname.match('/tags') &&
|
||||
!location.pathname.match('/alltags')
|
||||
) {
|
||||
let storageMap = data.storageMap
|
||||
if (showSearch && searchText.length > 0) {
|
||||
storageMap = storageMap.map((storage) => {
|
||||
const folders = storage.folders.filter(folder => folder.name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1)
|
||||
storageMap = storageMap.map(storage => {
|
||||
const folders = storage.folders.filter(
|
||||
folder =>
|
||||
folder.name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1
|
||||
)
|
||||
return Object.assign({}, storage, { folders })
|
||||
})
|
||||
}
|
||||
|
||||
const storageList = storageMap.map((storage, key) => {
|
||||
const SortableStorageItem = SortableContainer(StorageItem)
|
||||
return <SortableStorageItem
|
||||
key={storage.key}
|
||||
storage={storage}
|
||||
data={data}
|
||||
location={location}
|
||||
isFolded={isFolded}
|
||||
dispatch={dispatch}
|
||||
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
||||
useDragHandle
|
||||
/>
|
||||
return (
|
||||
<SortableStorageItem
|
||||
key={storage.key}
|
||||
storage={storage}
|
||||
data={data}
|
||||
location={location}
|
||||
isFolded={isFolded}
|
||||
dispatch={dispatch}
|
||||
onSortEnd={this.onSortEnd.bind(this)(storage)}
|
||||
useDragHandle
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
component = (
|
||||
@@ -289,19 +323,26 @@ class SideNav extends React.Component {
|
||||
<SideNavFilter
|
||||
isFolded={isFolded}
|
||||
isHomeActive={isHomeActive}
|
||||
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
|
||||
handleAllNotesButtonClick={e => this.handleHomeButtonClick(e)}
|
||||
isStarredActive={isStarredActive}
|
||||
isTrashedActive={isTrashedActive}
|
||||
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
|
||||
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
|
||||
counterTotalNote={data.noteMap._map.size - data.trashedSet._set.size}
|
||||
handleStarredButtonClick={e => this.handleStarredButtonClick(e)}
|
||||
handleTrashedButtonClick={e => this.handleTrashedButtonClick(e)}
|
||||
counterTotalNote={
|
||||
data.noteMap._map.size - data.trashedSet._set.size
|
||||
}
|
||||
counterStarredNote={data.starredSet._set.size}
|
||||
counterDelNote={data.trashedSet._set.size}
|
||||
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)}
|
||||
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(
|
||||
this
|
||||
)}
|
||||
/>
|
||||
|
||||
<StorageList storageList={storageList} isFolded={isFolded} />
|
||||
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
|
||||
<NavToggleButton
|
||||
isFolded={isFolded}
|
||||
handleToggleButtonClick={this.handleToggleButtonClick.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
@@ -313,22 +354,26 @@ class SideNav extends React.Component {
|
||||
</div>
|
||||
<div styleName='tag-control-sortTagsBy'>
|
||||
<i className='fa fa-angle-down' />
|
||||
<select styleName='tag-control-sortTagsBy-select'
|
||||
<select
|
||||
styleName='tag-control-sortTagsBy-select'
|
||||
title={i18n.__('Select filter mode')}
|
||||
value={config.sortTagsBy}
|
||||
onChange={(e) => this.handleSortTagsByChange(e)}
|
||||
onChange={e => this.handleSortTagsByChange(e)}
|
||||
>
|
||||
<option title='Sort alphabetically'
|
||||
value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option>
|
||||
<option title='Sort by update time'
|
||||
value='COUNTER'>{i18n.__('Counter')}</option>
|
||||
<option title='Sort alphabetically' value='ALPHABETICAL'>
|
||||
{i18n.__('Alphabetically')}
|
||||
</option>
|
||||
<option title='Sort by update time' value='COUNTER'>
|
||||
{i18n.__('Counter')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='tagList'>
|
||||
{this.tagListComponent(data)}
|
||||
</div>
|
||||
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
|
||||
<div styleName='tagList'>{this.tagListComponent(data)}</div>
|
||||
<NavToggleButton
|
||||
isFolded={isFolded}
|
||||
handleToggleButtonClick={this.handleToggleButtonClick.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -336,85 +381,89 @@ class SideNav extends React.Component {
|
||||
return component
|
||||
}
|
||||
|
||||
tagListComponent () {
|
||||
tagListComponent() {
|
||||
const { data, location, config } = this.props
|
||||
const { colorPicker, showSearch, searchText } = this.state
|
||||
const activeTags = this.getActiveTags(location.pathname)
|
||||
const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
|
||||
let tagList = sortBy(data.tagNoteMap.map(
|
||||
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
|
||||
).filter(
|
||||
tag => tag.size > 0
|
||||
), ['name'])
|
||||
let tagList = sortBy(
|
||||
data.tagNoteMap
|
||||
.map((tag, name) => ({
|
||||
name,
|
||||
size: tag.size,
|
||||
related: relatedTags.has(name)
|
||||
}))
|
||||
.filter(tag => tag.size > 0),
|
||||
['name']
|
||||
)
|
||||
if (showSearch && searchText.length > 0) {
|
||||
tagList = tagList.filter(tag => tag.name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1)
|
||||
tagList = tagList.filter(
|
||||
tag => tag.name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1
|
||||
)
|
||||
}
|
||||
if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) {
|
||||
const notesTags = data.noteMap.map(note => note.tags)
|
||||
tagList = tagList.map(tag => {
|
||||
tag.size = notesTags.filter(tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)).length
|
||||
tag.size = notesTags.filter(
|
||||
tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)
|
||||
).length
|
||||
return tag
|
||||
})
|
||||
}
|
||||
if (config.sortTagsBy === 'COUNTER') {
|
||||
tagList = sortBy(tagList, item => (0 - item.size))
|
||||
tagList = sortBy(tagList, item => 0 - item.size)
|
||||
}
|
||||
if (config.ui.showOnlyRelatedTags && (relatedTags.size > 0)) {
|
||||
tagList = tagList.filter(
|
||||
tag => tag.related
|
||||
if (config.ui.showOnlyRelatedTags && relatedTags.size > 0) {
|
||||
tagList = tagList.filter(tag => tag.related)
|
||||
}
|
||||
return tagList.map(tag => {
|
||||
return (
|
||||
<TagListItem
|
||||
name={tag.name}
|
||||
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
||||
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
|
||||
handleContextMenu={this.handleTagContextMenu.bind(this)}
|
||||
isActive={
|
||||
this.getTagActive(location.pathname, tag.name) ||
|
||||
colorPicker.tagName === tag.name
|
||||
}
|
||||
isRelated={tag.related}
|
||||
key={tag.name}
|
||||
count={tag.size}
|
||||
color={config.coloredTags[tag.name]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
tagList.map(tag => {
|
||||
return (
|
||||
<TagListItem
|
||||
name={tag.name}
|
||||
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
||||
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
|
||||
handleContextMenu={this.handleTagContextMenu.bind(this)}
|
||||
isActive={this.getTagActive(location.pathname, tag.name) || (colorPicker.tagName === tag.name)}
|
||||
isRelated={tag.related}
|
||||
key={tag.name}
|
||||
count={tag.size}
|
||||
color={config.coloredTags[tag.name]}
|
||||
/>
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
getRelatedTags (activeTags, noteMap) {
|
||||
getRelatedTags(activeTags, noteMap) {
|
||||
if (activeTags.length === 0) {
|
||||
return new Set()
|
||||
}
|
||||
const relatedNotes = noteMap.map(
|
||||
note => ({key: note.key, tags: note.tags})
|
||||
).filter(
|
||||
note => activeTags.every(tag => note.tags.includes(tag))
|
||||
)
|
||||
const relatedNotes = noteMap
|
||||
.map(note => ({ key: note.key, tags: note.tags }))
|
||||
.filter(note => activeTags.every(tag => note.tags.includes(tag)))
|
||||
const relatedTags = new Set()
|
||||
relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag)))
|
||||
return relatedTags
|
||||
}
|
||||
|
||||
getTagActive (path, tag) {
|
||||
getTagActive(path, tag) {
|
||||
return this.getActiveTags(path).includes(tag)
|
||||
}
|
||||
|
||||
getActiveTags (path) {
|
||||
getActiveTags(path) {
|
||||
const pathSegments = path.split('/')
|
||||
const tags = pathSegments[pathSegments.length - 1]
|
||||
return (tags === 'alltags')
|
||||
? []
|
||||
: decodeURIComponent(tags).split(' ')
|
||||
return tags === 'alltags' ? [] : decodeURIComponent(tags).split(' ')
|
||||
}
|
||||
|
||||
handleClickTagListItem (name) {
|
||||
handleClickTagListItem(name) {
|
||||
const { dispatch } = this.props
|
||||
dispatch(push(`/tags/${encodeURIComponent(name)}`))
|
||||
}
|
||||
|
||||
handleSortTagsByChange (e) {
|
||||
handleSortTagsByChange(e) {
|
||||
const { dispatch } = this.props
|
||||
|
||||
const config = {
|
||||
@@ -428,7 +477,7 @@ class SideNav extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleClickNarrowToTag (tag) {
|
||||
handleClickNarrowToTag(tag) {
|
||||
const { dispatch, location } = this.props
|
||||
const listOfTags = this.getActiveTags(location.pathname)
|
||||
const indexOfTag = listOfTags.indexOf(tag)
|
||||
@@ -440,33 +489,38 @@ class SideNav extends React.Component {
|
||||
dispatch(push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`))
|
||||
}
|
||||
|
||||
emptyTrash (entries) {
|
||||
emptyTrash(entries) {
|
||||
const { dispatch } = this.props
|
||||
const deletionPromises = entries.map((note) => {
|
||||
const deletionPromises = entries.map(note => {
|
||||
return dataApi.deleteNote(note.storage, note.key)
|
||||
})
|
||||
const { confirmDeletion } = this.props.config.ui
|
||||
if (!confirmDeleteNote(confirmDeletion, true)) return
|
||||
Promise.all(deletionPromises)
|
||||
.then((arrayOfStorageAndNoteKeys) => {
|
||||
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
|
||||
dispatch({ type: 'DELETE_NOTE', storageKey, noteKey })
|
||||
.then(arrayOfStorageAndNoteKeys => {
|
||||
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
|
||||
dispatch({ type: 'DELETE_NOTE', storageKey, noteKey })
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Cannot Delete note: ' + err)
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Cannot Delete note: ' + err)
|
||||
})
|
||||
}
|
||||
|
||||
handleFilterButtonContextMenu (event) {
|
||||
handleFilterButtonContextMenu(event) {
|
||||
const { data } = this.props
|
||||
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
const trashedNotes = data.trashedSet
|
||||
.toJS()
|
||||
.map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||
context.popup([
|
||||
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
|
||||
{
|
||||
label: i18n.__('Empty Trash'),
|
||||
click: () => this.emptyTrash(trashedNotes)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { location, config } = this.props
|
||||
const { showSearch, searchText, colorPicker: colorPickerState } = this.state
|
||||
|
||||
@@ -489,7 +543,7 @@ class SideNav extends React.Component {
|
||||
const isTagActive = /tag/.test(location.pathname)
|
||||
|
||||
const navSearch = (
|
||||
<div styleName='search' style={{maxHeight: showSearch ? '3em' : '0'}}>
|
||||
<div styleName='search' style={{ maxHeight: showSearch ? '3em' : '0' }}>
|
||||
<input
|
||||
styleName='search-input'
|
||||
type='text'
|
||||
@@ -497,21 +551,38 @@ class SideNav extends React.Component {
|
||||
value={searchText}
|
||||
placeholder={i18n.__('Filter tags/folders...')}
|
||||
/>
|
||||
<img styleName='search-clear' src='../resources/icon/icon-x.svg' onClick={this.handleSearchInputClear} />
|
||||
{isFolded && <img styleName='search-folded' src='../resources/icon/icon-search-active.svg' onClick={this.handleSearchButtonClick} />}
|
||||
<img
|
||||
styleName='search-clear'
|
||||
src='../resources/icon/icon-x.svg'
|
||||
onClick={this.handleSearchInputClear}
|
||||
/>
|
||||
{isFolded && (
|
||||
<img
|
||||
styleName='search-folded'
|
||||
src='../resources/icon/icon-search-active.svg'
|
||||
onClick={this.handleSearchButtonClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='SideNav'
|
||||
<div
|
||||
className='SideNav'
|
||||
styleName={isFolded ? 'root--folded' : 'root'}
|
||||
tabIndex='1'
|
||||
style={style}
|
||||
>
|
||||
<div styleName='top'>
|
||||
<div styleName='switch-buttons'>
|
||||
<ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||
<TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||
<ListButton
|
||||
onClick={this.handleSwitchFoldersButtonClick.bind(this)}
|
||||
isTagActive={isTagActive}
|
||||
/>
|
||||
<TagButton
|
||||
onClick={this.handleSwitchTagsButtonClick.bind(this)}
|
||||
isTagActive={isTagActive}
|
||||
/>
|
||||
</div>
|
||||
<div styleName='extra-buttons'>
|
||||
<SearchButton
|
||||
|
||||
@@ -11,30 +11,43 @@ const electron = require('electron')
|
||||
const { remote, ipcRenderer } = electron
|
||||
const { dialog } = remote
|
||||
|
||||
const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
|
||||
const zoomOptions = [
|
||||
0.8,
|
||||
0.9,
|
||||
1,
|
||||
1.1,
|
||||
1.2,
|
||||
1.3,
|
||||
1.4,
|
||||
1.5,
|
||||
1.6,
|
||||
1.7,
|
||||
1.8,
|
||||
1.9,
|
||||
2.0
|
||||
]
|
||||
|
||||
class StatusBar extends React.Component {
|
||||
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.handleZoomInMenuItem = this.handleZoomInMenuItem.bind(this)
|
||||
this.handleZoomOutMenuItem = this.handleZoomOutMenuItem.bind(this)
|
||||
this.handleZoomResetMenuItem = this.handleZoomResetMenuItem.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
EventEmitter.on('status:zoomin', this.handleZoomInMenuItem)
|
||||
EventEmitter.on('status:zoomout', this.handleZoomOutMenuItem)
|
||||
EventEmitter.on('status:zoomreset', this.handleZoomResetMenuItem)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
EventEmitter.off('status:zoomin', this.handleZoomInMenuItem)
|
||||
EventEmitter.off('status:zoomout', this.handleZoomOutMenuItem)
|
||||
EventEmitter.off('status:zoomreset', this.handleZoomResetMenuItem)
|
||||
}
|
||||
|
||||
updateApp () {
|
||||
updateApp() {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: i18n.__('Update Boostnote'),
|
||||
@@ -47,10 +60,10 @@ class StatusBar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleZoomButtonClick (e) {
|
||||
handleZoomButtonClick(e) {
|
||||
const templates = []
|
||||
|
||||
zoomOptions.forEach((zoom) => {
|
||||
zoomOptions.forEach(zoom => {
|
||||
templates.push({
|
||||
label: Math.floor(zoom * 100) + '%',
|
||||
click: () => this.handleZoomMenuItemClick(zoom)
|
||||
@@ -60,7 +73,7 @@ class StatusBar extends React.Component {
|
||||
context.popup(templates)
|
||||
}
|
||||
|
||||
handleZoomMenuItemClick (zoomFactor) {
|
||||
handleZoomMenuItemClick(zoomFactor) {
|
||||
const { dispatch } = this.props
|
||||
ZoomManager.setZoom(zoomFactor)
|
||||
dispatch({
|
||||
@@ -69,40 +82,36 @@ class StatusBar extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleZoomInMenuItem () {
|
||||
handleZoomInMenuItem() {
|
||||
const zoomFactor = ZoomManager.getZoom() + 0.1
|
||||
this.handleZoomMenuItemClick(zoomFactor)
|
||||
}
|
||||
|
||||
handleZoomOutMenuItem () {
|
||||
handleZoomOutMenuItem() {
|
||||
const zoomFactor = ZoomManager.getZoom() - 0.1
|
||||
this.handleZoomMenuItemClick(zoomFactor)
|
||||
}
|
||||
|
||||
handleZoomResetMenuItem () {
|
||||
handleZoomResetMenuItem() {
|
||||
this.handleZoomMenuItemClick(1.0)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { config, status } = this.context
|
||||
|
||||
return (
|
||||
<div className='StatusBar'
|
||||
styleName='root'
|
||||
>
|
||||
<button styleName='zoom'
|
||||
onClick={(e) => this.handleZoomButtonClick(e)}
|
||||
>
|
||||
<div className='StatusBar' styleName='root'>
|
||||
<button styleName='zoom' onClick={e => this.handleZoomButtonClick(e)}>
|
||||
<img src='../resources/icon/icon-zoom.svg' />
|
||||
<span>{Math.floor(config.zoom * 100)}%</span>
|
||||
</button>
|
||||
|
||||
{status.updateReady
|
||||
? <button onClick={this.updateApp} styleName='update'>
|
||||
<i styleName='update-icon' className='fa fa-cloud-download' /> {i18n.__('Ready to Update!')}
|
||||
{status.updateReady ? (
|
||||
<button onClick={this.updateApp} styleName='update'>
|
||||
<i styleName='update-icon' className='fa fa-cloud-download' />{' '}
|
||||
{i18n.__('Ready to Update!')}
|
||||
</button>
|
||||
: null
|
||||
}
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import CInput from 'react-composition-input'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
class TopBar extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -33,19 +33,25 @@ class TopBar extends React.Component {
|
||||
this.handleSearchChange = this.handleSearchChange.bind(this)
|
||||
this.handleSearchClearButton = this.handleSearchClearButton.bind(this)
|
||||
|
||||
this.debouncedUpdateKeyword = debounce((keyword) => {
|
||||
dispatch(push(`/searched/${encodeURIComponent(keyword)}`))
|
||||
this.setState({
|
||||
search: keyword
|
||||
})
|
||||
ee.emit('top:search', keyword)
|
||||
}, 1000 / 60, {
|
||||
maxWait: 1000 / 8
|
||||
})
|
||||
this.debouncedUpdateKeyword = debounce(
|
||||
keyword => {
|
||||
dispatch(push(`/searched/${encodeURIComponent(keyword)}`))
|
||||
this.setState({
|
||||
search: keyword
|
||||
})
|
||||
ee.emit('top:search', keyword)
|
||||
},
|
||||
1000 / 60,
|
||||
{
|
||||
maxWait: 1000 / 8
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { match: { params } } = this.props
|
||||
componentDidMount() {
|
||||
const {
|
||||
match: { params }
|
||||
} = this.props
|
||||
const searchWord = params && params.searchword
|
||||
if (searchWord !== undefined) {
|
||||
this.setState({
|
||||
@@ -57,12 +63,12 @@ class TopBar extends React.Component {
|
||||
ee.on('code:init', this.codeInitHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ee.off('top:focus-search', this.focusSearchHandler)
|
||||
ee.off('code:init', this.codeInitHandler)
|
||||
}
|
||||
|
||||
handleSearchClearButton (e) {
|
||||
handleSearchClearButton(e) {
|
||||
const { dispatch } = this.props
|
||||
this.setState({
|
||||
search: '',
|
||||
@@ -74,7 +80,7 @@ class TopBar extends React.Component {
|
||||
this.debouncedUpdateKeyword('')
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
// Re-apply search field on ENTER key
|
||||
if (e.keyCode === 13) {
|
||||
this.debouncedUpdateKeyword(e.target.value)
|
||||
@@ -98,18 +104,18 @@ class TopBar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchChange (e) {
|
||||
handleSearchChange(e) {
|
||||
const keyword = e.target.value
|
||||
this.debouncedUpdateKeyword(keyword)
|
||||
}
|
||||
|
||||
handleSearchFocus (e) {
|
||||
handleSearchFocus(e) {
|
||||
this.setState({
|
||||
isSearching: true
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchBlur (e) {
|
||||
handleSearchBlur(e) {
|
||||
e.stopPropagation()
|
||||
|
||||
let el = e.relatedTarget
|
||||
@@ -128,7 +134,7 @@ class TopBar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleOnSearchFocus () {
|
||||
handleOnSearchFocus() {
|
||||
const el = this.refs.search.childNodes[0]
|
||||
if (this.state.isSearching) {
|
||||
el.blur()
|
||||
@@ -137,20 +143,22 @@ class TopBar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleCodeInit () {
|
||||
handleCodeInit() {
|
||||
ee.emit('top:search', this.refs.searchInput.value || '')
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { config, style, location } = this.props
|
||||
return (
|
||||
<div className='TopBar'
|
||||
<div
|
||||
className='TopBar'
|
||||
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
||||
style={style}
|
||||
>
|
||||
<div styleName='control'>
|
||||
<div styleName='control-search'>
|
||||
<div styleName='control-search-input'
|
||||
<div
|
||||
styleName='control-search-input'
|
||||
onFocus={this.handleSearchFocus}
|
||||
onBlur={this.handleSearchBlur}
|
||||
tabIndex='-1'
|
||||
@@ -165,27 +173,33 @@ class TopBar extends React.Component {
|
||||
type='text'
|
||||
className='searchInput'
|
||||
/>
|
||||
{this.state.search !== '' &&
|
||||
<button styleName='control-search-input-clear'
|
||||
{this.state.search !== '' && (
|
||||
<button
|
||||
styleName='control-search-input-clear'
|
||||
onClick={this.handleSearchClearButton}
|
||||
>
|
||||
<i className='fa fa-fw fa-times' />
|
||||
<span styleName='control-search-input-clear-tooltip'>{i18n.__('Clear Search')}</span>
|
||||
<span styleName='control-search-input-clear-tooltip'>
|
||||
{i18n.__('Clear Search')}
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{location.pathname === '/trashed' ? ''
|
||||
: <NewNoteButton
|
||||
{..._.pick(this.props, [
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'location',
|
||||
'match'
|
||||
])}
|
||||
/>}
|
||||
{location.pathname === '/trashed' ? (
|
||||
''
|
||||
) : (
|
||||
<NewNoteButton
|
||||
{..._.pick(this.props, [
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'location',
|
||||
'match'
|
||||
])}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ const electron = require('electron')
|
||||
const { remote, ipcRenderer } = electron
|
||||
const { dialog } = remote
|
||||
|
||||
document.addEventListener('drop', function (e) {
|
||||
document.addEventListener('drop', function(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
})
|
||||
document.addEventListener('dragover', function (e) {
|
||||
document.addEventListener('dragover', function(e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
})
|
||||
@@ -33,7 +33,7 @@ let isAltWithMouse = false
|
||||
let isAltWithOtherKey = false
|
||||
let isOtherKey = false
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Alt') {
|
||||
isAltPressing = true
|
||||
if (isOtherKey) {
|
||||
@@ -47,13 +47,13 @@ document.addEventListener('keydown', function (e) {
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('mousedown', function (e) {
|
||||
document.addEventListener('mousedown', function(e) {
|
||||
if (isAltPressing) {
|
||||
isAltWithMouse = true
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('keyup', function (e) {
|
||||
document.addEventListener('keyup', function(e) {
|
||||
if (e.key === 'Alt') {
|
||||
if (isAltWithMouse || isAltWithOtherKey) {
|
||||
e.preventDefault()
|
||||
@@ -65,14 +65,13 @@ document.addEventListener('keyup', function (e) {
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
document.addEventListener('click', function(e) {
|
||||
const className = e.target.className
|
||||
if (!className && typeof (className) !== 'string') return
|
||||
if (!className && typeof className !== 'string') return
|
||||
const isInfoButton = className.includes('infoButton')
|
||||
const offsetParent = e.target.offsetParent
|
||||
const isInfoPanel = offsetParent !== null
|
||||
? offsetParent.className.includes('infoPanel')
|
||||
: false
|
||||
const isInfoPanel =
|
||||
offsetParent !== null ? offsetParent.className.includes('infoPanel') : false
|
||||
if (isInfoButton || isInfoPanel) return
|
||||
const infoPanel = document.querySelector('.infoPanel')
|
||||
if (infoPanel) infoPanel.style.display = 'none'
|
||||
@@ -80,11 +79,11 @@ document.addEventListener('click', function (e) {
|
||||
|
||||
const el = document.getElementById('content')
|
||||
|
||||
function notify (...args) {
|
||||
function notify(...args) {
|
||||
return new window.Notification(...args)
|
||||
}
|
||||
|
||||
function updateApp () {
|
||||
function updateApp() {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: i18n.__('Update Boostnote'),
|
||||
@@ -97,7 +96,7 @@ function updateApp () {
|
||||
}
|
||||
}
|
||||
|
||||
ReactDOM.render((
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<Fragment>
|
||||
@@ -112,36 +111,41 @@ ReactDOM.render((
|
||||
{/* storages */}
|
||||
<Redirect path='/storages' to='/home' exact />
|
||||
<Route path='/storages/:storageKey' component={Main} exact />
|
||||
<Route path='/storages/:storageKey/folders/:folderKey' component={Main} />
|
||||
<Route
|
||||
path='/storages/:storageKey/folders/:folderKey'
|
||||
component={Main}
|
||||
/>
|
||||
</Switch>
|
||||
<DevTools />
|
||||
</Fragment>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
), el, function () {
|
||||
const loadingCover = document.getElementById('loadingCover')
|
||||
loadingCover.parentNode.removeChild(loadingCover)
|
||||
</Provider>,
|
||||
el,
|
||||
function() {
|
||||
const loadingCover = document.getElementById('loadingCover')
|
||||
loadingCover.parentNode.removeChild(loadingCover)
|
||||
|
||||
ipcRenderer.on('update-ready', function () {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_AVAILABLE'
|
||||
ipcRenderer.on('update-ready', function() {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_AVAILABLE'
|
||||
})
|
||||
notify('Update ready!', {
|
||||
body: 'New Boostnote is ready to be installed.'
|
||||
})
|
||||
updateApp()
|
||||
})
|
||||
notify('Update ready!', {
|
||||
body: 'New Boostnote is ready to be installed.'
|
||||
})
|
||||
updateApp()
|
||||
})
|
||||
|
||||
ipcRenderer.on('update-found', function () {
|
||||
notify('Update found!', {
|
||||
body: 'Preparing to update...'
|
||||
ipcRenderer.on('update-found', function() {
|
||||
notify('Update found!', {
|
||||
body: 'Preparing to update...'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
ipcRenderer.send('update-check', 'check-update')
|
||||
window.addEventListener('online', function () {
|
||||
if (!store.getState().status.updateReady) {
|
||||
ipcRenderer.send('update-check', 'check-update')
|
||||
}
|
||||
})
|
||||
})
|
||||
ipcRenderer.send('update-check', 'check-update')
|
||||
window.addEventListener('online', function() {
|
||||
if (!store.getState().status.updateReady) {
|
||||
ipcRenderer.send('update-check', 'check-update')
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ if (!getSendEventCond()) {
|
||||
})
|
||||
}
|
||||
|
||||
function convertPlatformName (platformName) {
|
||||
function convertPlatformName(platformName) {
|
||||
if (platformName === 'darwin') {
|
||||
return 'MacOS'
|
||||
} else if (platformName === 'win32') {
|
||||
@@ -34,16 +34,16 @@ function convertPlatformName (platformName) {
|
||||
}
|
||||
}
|
||||
|
||||
function getSendEventCond () {
|
||||
function getSendEventCond() {
|
||||
const isDev = process.env.NODE_ENV !== 'production'
|
||||
const isDisable = !ConfigManager.default.get().amaEnabled
|
||||
const isOffline = !window.navigator.onLine
|
||||
return isDev || isDisable || isOffline
|
||||
}
|
||||
|
||||
function initAwsMobileAnalytics () {
|
||||
function initAwsMobileAnalytics() {
|
||||
if (getSendEventCond()) return
|
||||
AWS.config.credentials.get((err) => {
|
||||
AWS.config.credentials.get(err => {
|
||||
if (!err) {
|
||||
recordDynamicCustomEvent('APP_STARTED')
|
||||
recordStaticCustomEvent()
|
||||
@@ -51,7 +51,7 @@ function initAwsMobileAnalytics () {
|
||||
})
|
||||
}
|
||||
|
||||
function recordDynamicCustomEvent (type, options = {}) {
|
||||
function recordDynamicCustomEvent(type, options = {}) {
|
||||
if (getSendEventCond()) return
|
||||
try {
|
||||
mobileAnalyticsClient.recordEvent(type, options)
|
||||
@@ -62,7 +62,7 @@ function recordDynamicCustomEvent (type, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function recordStaticCustomEvent () {
|
||||
function recordStaticCustomEvent() {
|
||||
if (getSendEventCond()) return
|
||||
try {
|
||||
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
let callees = []
|
||||
|
||||
function bind (name, el) {
|
||||
function bind(name, el) {
|
||||
callees.push({
|
||||
name: name,
|
||||
element: el
|
||||
})
|
||||
}
|
||||
|
||||
function release (el) {
|
||||
callees = callees.filter((callee) => callee.element !== el)
|
||||
function release(el) {
|
||||
callees = callees.filter(callee => callee.element !== el)
|
||||
}
|
||||
|
||||
function fire (command) {
|
||||
function fire(command) {
|
||||
console.info('COMMAND >>', command)
|
||||
const splitted = command.split(':')
|
||||
const target = splitted[0]
|
||||
const targetCommand = splitted[1]
|
||||
const targetCallees = callees
|
||||
.filter((callee) => callee.name === target)
|
||||
const targetCallees = callees.filter(callee => callee.name === target)
|
||||
|
||||
targetCallees.forEach((callee) => {
|
||||
targetCallees.forEach(callee => {
|
||||
callee.element.fire(targetCommand)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,7 +33,9 @@ export const DEFAULT_CONFIG = {
|
||||
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
|
||||
toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M',
|
||||
toggleDirection: OSX ? 'Command + Alt + Right' : 'Ctrl + Right',
|
||||
deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace',
|
||||
deleteNote: OSX
|
||||
? 'Command + Shift + Backspace'
|
||||
: 'Ctrl + Shift + Backspace',
|
||||
pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V',
|
||||
prettifyMarkdown: OSX ? 'Command + Shift + F' : 'Ctrl + Shift + F',
|
||||
sortLines: OSX ? 'Command + Shift + S' : 'Ctrl + Shift + S',
|
||||
@@ -116,7 +118,7 @@ export const DEFAULT_CONFIG = {
|
||||
coloredTags: {}
|
||||
}
|
||||
|
||||
function validate (config) {
|
||||
function validate(config) {
|
||||
if (!_.isObject(config)) return false
|
||||
if (!_.isNumber(config.zoom) || config.zoom < 0) return false
|
||||
if (!_.isBoolean(config.isSideNavFolded)) return false
|
||||
@@ -125,13 +127,17 @@ function validate (config) {
|
||||
return true
|
||||
}
|
||||
|
||||
function _save (config) {
|
||||
function _save(config) {
|
||||
window.localStorage.setItem('config', JSON.stringify(config))
|
||||
}
|
||||
|
||||
function get () {
|
||||
function get() {
|
||||
const rawStoredConfig = window.localStorage.getItem('config')
|
||||
const storedConfig = Object.assign({}, DEFAULT_CONFIG, JSON.parse(rawStoredConfig))
|
||||
const storedConfig = Object.assign(
|
||||
{},
|
||||
DEFAULT_CONFIG,
|
||||
JSON.parse(rawStoredConfig)
|
||||
)
|
||||
let config = storedConfig
|
||||
|
||||
try {
|
||||
@@ -145,7 +151,10 @@ function get () {
|
||||
_save(config)
|
||||
}
|
||||
|
||||
config.autoUpdateEnabled = electronConfig.get('autoUpdateEnabled', config.autoUpdateEnabled)
|
||||
config.autoUpdateEnabled = electronConfig.get(
|
||||
'autoUpdateEnabled',
|
||||
config.autoUpdateEnabled
|
||||
)
|
||||
|
||||
if (!isInitialized) {
|
||||
isInitialized = true
|
||||
@@ -157,7 +166,9 @@ function get () {
|
||||
document.head.appendChild(editorTheme)
|
||||
}
|
||||
|
||||
const theme = consts.THEMES.find(theme => theme.name === config.editor.theme)
|
||||
const theme = consts.THEMES.find(
|
||||
theme => theme.name === config.editor.theme
|
||||
)
|
||||
|
||||
if (theme) {
|
||||
editorTheme.setAttribute('href', theme.path)
|
||||
@@ -169,7 +180,7 @@ function get () {
|
||||
return config
|
||||
}
|
||||
|
||||
function set (updates) {
|
||||
function set(updates) {
|
||||
const currentConfig = get()
|
||||
|
||||
const arrangedUpdates = updates
|
||||
@@ -177,7 +188,12 @@ function set (updates) {
|
||||
arrangedUpdates.preview.customCSS = DEFAULT_CONFIG.preview.customCSS
|
||||
}
|
||||
|
||||
const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, arrangedUpdates)
|
||||
const newConfig = Object.assign(
|
||||
{},
|
||||
DEFAULT_CONFIG,
|
||||
currentConfig,
|
||||
arrangedUpdates
|
||||
)
|
||||
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
|
||||
_save(newConfig)
|
||||
|
||||
@@ -197,7 +213,9 @@ function set (updates) {
|
||||
document.head.appendChild(editorTheme)
|
||||
}
|
||||
|
||||
const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme)
|
||||
const newTheme = consts.THEMES.find(
|
||||
theme => theme.name === newConfig.editor.theme
|
||||
)
|
||||
|
||||
if (newTheme) {
|
||||
editorTheme.setAttribute('href', newTheme.path)
|
||||
@@ -211,20 +229,45 @@ function set (updates) {
|
||||
ee.emit('config-renew')
|
||||
}
|
||||
|
||||
function assignConfigValues (originalConfig, rcConfig) {
|
||||
function assignConfigValues(originalConfig, rcConfig) {
|
||||
const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
|
||||
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey)
|
||||
config.blog = Object.assign({}, DEFAULT_CONFIG.blog, originalConfig.blog, rcConfig.blog)
|
||||
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
|
||||
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
|
||||
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
|
||||
config.hotkey = Object.assign(
|
||||
{},
|
||||
DEFAULT_CONFIG.hotkey,
|
||||
originalConfig.hotkey,
|
||||
rcConfig.hotkey
|
||||
)
|
||||
config.blog = Object.assign(
|
||||
{},
|
||||
DEFAULT_CONFIG.blog,
|
||||
originalConfig.blog,
|
||||
rcConfig.blog
|
||||
)
|
||||
config.ui = Object.assign(
|
||||
{},
|
||||
DEFAULT_CONFIG.ui,
|
||||
originalConfig.ui,
|
||||
rcConfig.ui
|
||||
)
|
||||
config.editor = Object.assign(
|
||||
{},
|
||||
DEFAULT_CONFIG.editor,
|
||||
originalConfig.editor,
|
||||
rcConfig.editor
|
||||
)
|
||||
config.preview = Object.assign(
|
||||
{},
|
||||
DEFAULT_CONFIG.preview,
|
||||
originalConfig.preview,
|
||||
rcConfig.preview
|
||||
)
|
||||
|
||||
rewriteHotkey(config)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
function rewriteHotkey (config) {
|
||||
function rewriteHotkey(config) {
|
||||
const keys = [...Object.keys(config.hotkey)]
|
||||
keys.forEach(key => {
|
||||
config.hotkey[key] = config.hotkey[key].replace(/Cmd\s/g, 'Command ')
|
||||
|
||||
@@ -5,20 +5,20 @@ const { remote } = electron
|
||||
|
||||
_init()
|
||||
|
||||
function _init () {
|
||||
function _init() {
|
||||
setZoom(getZoom(), true)
|
||||
}
|
||||
|
||||
function _saveZoom (zoomFactor) {
|
||||
ConfigManager.set({zoom: zoomFactor})
|
||||
function _saveZoom(zoomFactor) {
|
||||
ConfigManager.set({ zoom: zoomFactor })
|
||||
}
|
||||
|
||||
function setZoom (zoomFactor, noSave = false) {
|
||||
function setZoom(zoomFactor, noSave = false) {
|
||||
if (!noSave) _saveZoom(zoomFactor)
|
||||
remote.getCurrentWebContents().setZoomFactor(zoomFactor)
|
||||
}
|
||||
|
||||
function getZoom () {
|
||||
function getZoom() {
|
||||
const config = ConfigManager.get()
|
||||
|
||||
return config.zoom
|
||||
|
||||
@@ -16,7 +16,7 @@ const CSON = require('@rokt33r/season')
|
||||
* 3. fetch notes & folders
|
||||
* 4. return `{storage: {...} folders: [folder]}`
|
||||
*/
|
||||
function addStorage (input) {
|
||||
function addStorage(input) {
|
||||
if (!_.isString(input.path)) {
|
||||
return Promise.reject(new Error('Path must be a string.'))
|
||||
}
|
||||
@@ -29,7 +29,7 @@ function addStorage (input) {
|
||||
rawStorages = []
|
||||
}
|
||||
let key = keygen()
|
||||
while (rawStorages.some((storage) => storage.key === key)) {
|
||||
while (rawStorages.some(storage => storage.key === key)) {
|
||||
key = keygen()
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ function addStorage (input) {
|
||||
|
||||
return Promise.resolve(newStorage)
|
||||
.then(resolveStorageData)
|
||||
.then(function saveMetadataToLocalStorage (resolvedStorage) {
|
||||
.then(function saveMetadataToLocalStorage(resolvedStorage) {
|
||||
newStorage = resolvedStorage
|
||||
rawStorages.push({
|
||||
key: newStorage.key,
|
||||
@@ -56,27 +56,29 @@ function addStorage (input) {
|
||||
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
||||
return newStorage
|
||||
})
|
||||
.then(function (storage) {
|
||||
return resolveStorageNotes(storage)
|
||||
.then((notes) => {
|
||||
let unknownCount = 0
|
||||
notes.forEach((note) => {
|
||||
if (!storage.folders.some((folder) => note.folder === folder.key)) {
|
||||
unknownCount++
|
||||
storage.folders.push({
|
||||
key: note.folder,
|
||||
color: consts.FOLDER_COLORS[(unknownCount - 1) % 7],
|
||||
name: 'Unknown ' + unknownCount
|
||||
})
|
||||
}
|
||||
})
|
||||
if (unknownCount > 0) {
|
||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
||||
.then(function(storage) {
|
||||
return resolveStorageNotes(storage).then(notes => {
|
||||
let unknownCount = 0
|
||||
notes.forEach(note => {
|
||||
if (!storage.folders.some(folder => note.folder === folder.key)) {
|
||||
unknownCount++
|
||||
storage.folders.push({
|
||||
key: note.folder,
|
||||
color: consts.FOLDER_COLORS[(unknownCount - 1) % 7],
|
||||
name: 'Unknown ' + unknownCount
|
||||
})
|
||||
}
|
||||
return notes
|
||||
})
|
||||
if (unknownCount > 0) {
|
||||
CSON.writeFileSync(
|
||||
path.join(storage.path, 'boostnote.json'),
|
||||
_.pick(storage, ['folders', 'version'])
|
||||
)
|
||||
}
|
||||
return notes
|
||||
})
|
||||
})
|
||||
.then(function returnValue (notes) {
|
||||
.then(function returnValue(notes) {
|
||||
return {
|
||||
storage: newStorage,
|
||||
notes
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ const path = require('path')
|
||||
* @param {String} dstPath
|
||||
* @return {Promise} an image path
|
||||
*/
|
||||
function copyFile (srcPath, dstPath) {
|
||||
function copyFile(srcPath, dstPath) {
|
||||
if (!path.extname(dstPath)) {
|
||||
dstPath = path.join(dstPath, path.basename(srcPath))
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const { findStorage } = require('browser/lib/findStorage')
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function createFolder (storageKey, input) {
|
||||
function createFolder(storageKey, input) {
|
||||
let targetStorage
|
||||
try {
|
||||
if (input == null) throw new Error('No input found.')
|
||||
@@ -34,26 +34,28 @@ function createFolder (storageKey, input) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function createFolder (storage) {
|
||||
let key = keygen()
|
||||
while (storage.folders.some((folder) => folder.key === key)) {
|
||||
key = keygen()
|
||||
}
|
||||
const newFolder = {
|
||||
key,
|
||||
color: input.color,
|
||||
name: input.name
|
||||
}
|
||||
return resolveStorageData(targetStorage).then(function createFolder(storage) {
|
||||
let key = keygen()
|
||||
while (storage.folders.some(folder => folder.key === key)) {
|
||||
key = keygen()
|
||||
}
|
||||
const newFolder = {
|
||||
key,
|
||||
color: input.color,
|
||||
name: input.name
|
||||
}
|
||||
|
||||
storage.folders.push(newFolder)
|
||||
storage.folders.push(newFolder)
|
||||
|
||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
||||
CSON.writeFileSync(
|
||||
path.join(storage.path, 'boostnote.json'),
|
||||
_.pick(storage, ['folders', 'version'])
|
||||
)
|
||||
|
||||
return {
|
||||
storage
|
||||
}
|
||||
})
|
||||
return {
|
||||
storage
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = createFolder
|
||||
|
||||
@@ -6,9 +6,11 @@ const path = require('path')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
function validateInput (input) {
|
||||
function validateInput(input) {
|
||||
if (!_.isArray(input.tags)) input.tags = []
|
||||
input.tags = input.tags.filter((tag) => _.isString(tag) && tag.trim().length > 0)
|
||||
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
|
||||
@@ -21,20 +23,24 @@ function validateInput (input) {
|
||||
case 'SNIPPET_NOTE':
|
||||
if (!_.isString(input.description)) input.description = ''
|
||||
if (!_.isArray(input.snippets)) {
|
||||
input.snippets = [{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}]
|
||||
input.snippets = [
|
||||
{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}
|
||||
]
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw new Error('Invalid type: only MARKDOWN_NOTE and SNIPPET_NOTE are available.')
|
||||
throw new Error(
|
||||
'Invalid type: only MARKDOWN_NOTE and SNIPPET_NOTE are available.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function createNote (storageKey, input) {
|
||||
function createNote(storageKey, input) {
|
||||
let targetStorage
|
||||
try {
|
||||
if (input == null) throw new Error('No input found.')
|
||||
@@ -47,13 +53,13 @@ function createNote (storageKey, input) {
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function checkFolderExists (storage) {
|
||||
if (_.find(storage.folders, {key: input.folder}) == null) {
|
||||
throw new Error('Target folder doesn\'t exist.')
|
||||
.then(function checkFolderExists(storage) {
|
||||
if (_.find(storage.folders, { key: input.folder }) == null) {
|
||||
throw new Error("Target folder doesn't exist.")
|
||||
}
|
||||
return storage
|
||||
})
|
||||
.then(function saveNote (storage) {
|
||||
.then(function saveNote(storage) {
|
||||
let key = keygen(true)
|
||||
let isUnique = false
|
||||
while (!isUnique) {
|
||||
@@ -68,7 +74,8 @@ function createNote (storageKey, input) {
|
||||
}
|
||||
}
|
||||
}
|
||||
const noteData = Object.assign({},
|
||||
const noteData = Object.assign(
|
||||
{},
|
||||
{
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
@@ -77,9 +84,13 @@ function createNote (storageKey, input) {
|
||||
{
|
||||
key,
|
||||
storage: storageKey
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
CSON.writeFileSync(path.join(storage.path, 'notes', key + '.cson'), _.omit(noteData, ['key', 'storage']))
|
||||
CSON.writeFileSync(
|
||||
path.join(storage.path, 'notes', key + '.cson'),
|
||||
_.omit(noteData, ['key', 'storage'])
|
||||
)
|
||||
|
||||
return noteData
|
||||
})
|
||||
|
||||
@@ -6,8 +6,12 @@ const createNote = require('./createNote')
|
||||
import { push } from 'connected-react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
|
||||
function validateUrl (str) {
|
||||
if (/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(str)) {
|
||||
function validateUrl(str) {
|
||||
if (
|
||||
/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(
|
||||
str
|
||||
)
|
||||
) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@@ -15,25 +19,33 @@ function validateUrl (str) {
|
||||
}
|
||||
|
||||
const ERROR_MESSAGES = {
|
||||
ENOTFOUND: 'URL not found. Please check the URL, or your internet connection and try again.',
|
||||
VALIDATION_ERROR: 'Please check if the URL follows this format: https://www.google.com',
|
||||
ENOTFOUND:
|
||||
'URL not found. Please check the URL, or your internet connection and try again.',
|
||||
VALIDATION_ERROR:
|
||||
'Please check if the URL follows this format: https://www.google.com',
|
||||
UNEXPECTED: 'Unexpected error! Please check console for details!'
|
||||
}
|
||||
|
||||
function createNoteFromUrl (url, storage, folder, dispatch = null, location = null) {
|
||||
function createNoteFromUrl(
|
||||
url,
|
||||
storage,
|
||||
folder,
|
||||
dispatch = null,
|
||||
location = null
|
||||
) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const td = createTurndownService()
|
||||
|
||||
if (!validateUrl(url)) {
|
||||
reject({result: false, error: ERROR_MESSAGES.VALIDATION_ERROR})
|
||||
reject({ result: false, error: ERROR_MESSAGES.VALIDATION_ERROR })
|
||||
}
|
||||
|
||||
const request = url.startsWith('https') ? https : http
|
||||
|
||||
const req = request.request(url, (res) => {
|
||||
const req = request.request(url, res => {
|
||||
let data = ''
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
res.on('data', chunk => {
|
||||
data += chunk
|
||||
})
|
||||
|
||||
@@ -46,20 +58,21 @@ function createNoteFromUrl (url, storage, folder, dispatch = null, location = nu
|
||||
folder: folder,
|
||||
title: '',
|
||||
content: markdownHTML
|
||||
})
|
||||
.then((note) => {
|
||||
}).then(note => {
|
||||
const noteHash = note.key
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
query: {key: noteHash}
|
||||
}))
|
||||
dispatch(
|
||||
push({
|
||||
pathname: location.pathname,
|
||||
query: { key: noteHash }
|
||||
})
|
||||
)
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
resolve({result: true, error: null})
|
||||
resolve({ result: true, error: null })
|
||||
})
|
||||
} else {
|
||||
createNote(storage, {
|
||||
@@ -67,16 +80,19 @@ function createNoteFromUrl (url, storage, folder, dispatch = null, location = nu
|
||||
folder: folder,
|
||||
title: '',
|
||||
content: markdownHTML
|
||||
}).then((note) => {
|
||||
resolve({result: true, note, error: null})
|
||||
}).then(note => {
|
||||
resolve({ result: true, note, error: null })
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
req.on('error', (e) => {
|
||||
req.on('error', e => {
|
||||
console.error('error in parsing URL', e)
|
||||
reject({result: false, error: ERROR_MESSAGES[e.code] || ERROR_MESSAGES.UNEXPECTED})
|
||||
reject({
|
||||
result: false,
|
||||
error: ERROR_MESSAGES[e.code] || ERROR_MESSAGES.UNEXPECTED
|
||||
})
|
||||
})
|
||||
|
||||
req.end()
|
||||
|
||||
@@ -3,7 +3,7 @@ import crypto from 'crypto'
|
||||
import consts from 'browser/lib/consts'
|
||||
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
|
||||
|
||||
function createSnippet (snippetFile) {
|
||||
function createSnippet(snippetFile) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const newSnippet = {
|
||||
id: crypto.randomBytes(16).toString('hex'),
|
||||
@@ -12,15 +12,21 @@ function createSnippet (snippetFile) {
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}
|
||||
fetchSnippet(null, snippetFile).then((snippets) => {
|
||||
snippets.push(newSnippet)
|
||||
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
||||
if (err) reject(err)
|
||||
resolve(newSnippet)
|
||||
fetchSnippet(null, snippetFile)
|
||||
.then(snippets => {
|
||||
snippets.push(newSnippet)
|
||||
fs.writeFile(
|
||||
snippetFile || consts.SNIPPET_FILE,
|
||||
JSON.stringify(snippets, null, 4),
|
||||
err => {
|
||||
if (err) reject(err)
|
||||
resolve(newSnippet)
|
||||
}
|
||||
)
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
}).catch((err) => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const deleteSingleNote = require('./deleteNote')
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function deleteFolder (storageKey, folderKey) {
|
||||
function deleteFolder(storageKey, folderKey) {
|
||||
let targetStorage
|
||||
try {
|
||||
targetStorage = findStorage(storageKey)
|
||||
@@ -27,35 +27,36 @@ function deleteFolder (storageKey, folderKey) {
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function assignNotes (storage) {
|
||||
return resolveStorageNotes(storage)
|
||||
.then((notes) => {
|
||||
return {
|
||||
storage,
|
||||
notes
|
||||
}
|
||||
})
|
||||
.then(function assignNotes(storage) {
|
||||
return resolveStorageNotes(storage).then(notes => {
|
||||
return {
|
||||
storage,
|
||||
notes
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(function deleteFolderAndNotes (data) {
|
||||
.then(function deleteFolderAndNotes(data) {
|
||||
const { storage, notes } = data
|
||||
storage.folders = storage.folders
|
||||
.filter(function excludeTargetFolder (folder) {
|
||||
return folder.key !== folderKey
|
||||
})
|
||||
storage.folders = storage.folders.filter(function excludeTargetFolder(
|
||||
folder
|
||||
) {
|
||||
return folder.key !== folderKey
|
||||
})
|
||||
|
||||
const targetNotes = notes.filter(function filterTargetNotes (note) {
|
||||
const targetNotes = notes.filter(function filterTargetNotes(note) {
|
||||
return note.folder === folderKey
|
||||
})
|
||||
|
||||
const deleteAllNotes = targetNotes
|
||||
.map(function deleteNote (note) {
|
||||
return deleteSingleNote(storageKey, note.key)
|
||||
})
|
||||
return Promise.all(deleteAllNotes)
|
||||
.then(() => storage)
|
||||
const deleteAllNotes = targetNotes.map(function deleteNote(note) {
|
||||
return deleteSingleNote(storageKey, note.key)
|
||||
})
|
||||
return Promise.all(deleteAllNotes).then(() => storage)
|
||||
})
|
||||
.then(function (storage) {
|
||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
||||
.then(function(storage) {
|
||||
CSON.writeFileSync(
|
||||
path.join(storage.path, 'boostnote.json'),
|
||||
_.pick(storage, ['folders', 'version'])
|
||||
)
|
||||
|
||||
return {
|
||||
storage,
|
||||
|
||||
@@ -4,7 +4,7 @@ const sander = require('sander')
|
||||
const attachmentManagement = require('./attachmentManagement')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
function deleteNote (storageKey, noteKey) {
|
||||
function deleteNote(storageKey, noteKey) {
|
||||
let targetStorage
|
||||
try {
|
||||
targetStorage = findStorage(storageKey)
|
||||
@@ -13,7 +13,7 @@ function deleteNote (storageKey, noteKey) {
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function deleteNoteFile (storage) {
|
||||
.then(function deleteNoteFile(storage) {
|
||||
const notePath = path.join(storage.path, 'notes', noteKey + '.cson')
|
||||
|
||||
try {
|
||||
@@ -26,8 +26,11 @@ function deleteNote (storageKey, noteKey) {
|
||||
storageKey
|
||||
}
|
||||
})
|
||||
.then(function deleteAttachments (storageInfo) {
|
||||
attachmentManagement.deleteAttachmentFolder(storageInfo.storageKey, storageInfo.noteKey)
|
||||
.then(function deleteAttachments(storageInfo) {
|
||||
attachmentManagement.deleteAttachmentFolder(
|
||||
storageInfo.storageKey,
|
||||
storageInfo.noteKey
|
||||
)
|
||||
return storageInfo
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,14 +2,20 @@ import fs from 'fs'
|
||||
import consts from 'browser/lib/consts'
|
||||
import fetchSnippet from 'browser/main/lib/dataApi/fetchSnippet'
|
||||
|
||||
function deleteSnippet (snippet, snippetFile) {
|
||||
function deleteSnippet(snippet, snippetFile) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fetchSnippet(null, snippetFile).then((snippets) => {
|
||||
snippets = snippets.filter(currentSnippet => currentSnippet.id !== snippet.id)
|
||||
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
||||
if (err) reject(err)
|
||||
resolve(snippet)
|
||||
})
|
||||
fetchSnippet(null, snippetFile).then(snippets => {
|
||||
snippets = snippets.filter(
|
||||
currentSnippet => currentSnippet.id !== snippet.id
|
||||
)
|
||||
fs.writeFile(
|
||||
snippetFile || consts.SNIPPET_FILE,
|
||||
JSON.stringify(snippets, null, 4),
|
||||
err => {
|
||||
if (err) reject(err)
|
||||
resolve(snippet)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import * as path from 'path'
|
||||
* ```
|
||||
*/
|
||||
|
||||
function exportFolder (storageKey, folderKey, fileType, exportDir) {
|
||||
function exportFolder(storageKey, folderKey, fileType, exportDir) {
|
||||
let targetStorage
|
||||
try {
|
||||
targetStorage = findStorage(storageKey)
|
||||
@@ -31,24 +31,38 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function assignNotes (storage) {
|
||||
return resolveStorageNotes(storage)
|
||||
.then((notes) => {
|
||||
return {
|
||||
storage,
|
||||
notes
|
||||
}
|
||||
})
|
||||
.then(function assignNotes(storage) {
|
||||
return resolveStorageNotes(storage).then(notes => {
|
||||
return {
|
||||
storage,
|
||||
notes
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(function exportNotes (data) {
|
||||
.then(function exportNotes(data) {
|
||||
const { storage, notes } = data
|
||||
|
||||
return Promise.all(notes
|
||||
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
|
||||
.map(note => {
|
||||
const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`)
|
||||
return exportNote(note.key, storage.path, note.content, notePath, null)
|
||||
})
|
||||
return Promise.all(
|
||||
notes
|
||||
.filter(
|
||||
note =>
|
||||
note.folder === folderKey &&
|
||||
note.isTrashed === false &&
|
||||
note.type === 'MARKDOWN_NOTE'
|
||||
)
|
||||
.map(note => {
|
||||
const notePath = path.join(
|
||||
exportDir,
|
||||
`${filenamify(note.title, { replacement: '_' })}.${fileType}`
|
||||
)
|
||||
return exportNote(
|
||||
note.key,
|
||||
storage.path,
|
||||
note.content,
|
||||
notePath,
|
||||
null
|
||||
)
|
||||
})
|
||||
).then(() => ({
|
||||
storage,
|
||||
folderKey,
|
||||
|
||||
@@ -19,8 +19,16 @@ const attachmentManagement = require('./attachmentManagement')
|
||||
* @param {function} outputFormatter
|
||||
* @return {Promise.<*[]>}
|
||||
*/
|
||||
function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatter) {
|
||||
const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path
|
||||
function exportNote(
|
||||
nodeKey,
|
||||
storageKey,
|
||||
noteContent,
|
||||
targetPath,
|
||||
outputFormatter
|
||||
) {
|
||||
const storagePath = path.isAbsolute(storageKey)
|
||||
? storageKey
|
||||
: findStorage(storageKey).path
|
||||
const exportTasks = []
|
||||
|
||||
if (!storagePath) {
|
||||
@@ -50,18 +58,19 @@ function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatt
|
||||
|
||||
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
|
||||
|
||||
return Promise.all(tasks.map((task) => copyFile(task.src, task.dst)))
|
||||
.then(() => exportedData)
|
||||
.then(data => {
|
||||
return saveToFile(data, targetPath)
|
||||
}).catch((err) => {
|
||||
rollbackExport(tasks)
|
||||
throw err
|
||||
})
|
||||
return Promise.all(tasks.map(task => copyFile(task.src, task.dst)))
|
||||
.then(() => exportedData)
|
||||
.then(data => {
|
||||
return saveToFile(data, targetPath)
|
||||
})
|
||||
.catch(err => {
|
||||
rollbackExport(tasks)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
function prepareTasks (tasks, storagePath, targetPath) {
|
||||
return tasks.map((task) => {
|
||||
function prepareTasks(tasks, storagePath, targetPath) {
|
||||
return tasks.map(task => {
|
||||
if (!path.isAbsolute(task.src)) {
|
||||
task.src = path.join(storagePath, task.src)
|
||||
}
|
||||
@@ -74,9 +83,9 @@ function prepareTasks (tasks, storagePath, targetPath) {
|
||||
})
|
||||
}
|
||||
|
||||
function saveToFile (data, filename) {
|
||||
function saveToFile(data, filename) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(filename, data, (err) => {
|
||||
fs.writeFile(filename, data, err => {
|
||||
if (err) return reject(err)
|
||||
|
||||
resolve(filename)
|
||||
@@ -88,9 +97,9 @@ function saveToFile (data, filename) {
|
||||
* Remove exported files
|
||||
* @param tasks Array of copy task objects. Object consists of two mandatory fields – `src` and `dst`
|
||||
*/
|
||||
function rollbackExport (tasks) {
|
||||
function rollbackExport(tasks) {
|
||||
const folders = new Set()
|
||||
tasks.forEach((task) => {
|
||||
tasks.forEach(task => {
|
||||
let fullpath = task.dst
|
||||
|
||||
if (!path.extname(task.dst)) {
|
||||
@@ -103,7 +112,7 @@ function rollbackExport (tasks) {
|
||||
}
|
||||
})
|
||||
|
||||
folders.forEach((folder) => {
|
||||
folders.forEach(folder => {
|
||||
if (fs.readdirSync(folder).length === 0) {
|
||||
fs.rmdir(folder)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import * as fs from 'fs'
|
||||
* ```
|
||||
*/
|
||||
|
||||
function exportStorage (storageKey, fileType, exportDir) {
|
||||
function exportStorage(storageKey, fileType, exportDir) {
|
||||
let targetStorage
|
||||
try {
|
||||
targetStorage = findStorage(storageKey)
|
||||
@@ -29,14 +29,17 @@ function exportStorage (storageKey, fileType, exportDir) {
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(storage => (
|
||||
resolveStorageNotes(storage).then(notes => ({storage, notes}))
|
||||
))
|
||||
.then(function exportNotes (data) {
|
||||
.then(storage =>
|
||||
resolveStorageNotes(storage).then(notes => ({ storage, notes }))
|
||||
)
|
||||
.then(function exportNotes(data) {
|
||||
const { storage, notes } = data
|
||||
const folderNamesMapping = {}
|
||||
storage.folders.forEach(folder => {
|
||||
const folderExportedDir = path.join(exportDir, filenamify(folder.name, {replacement: '_'}))
|
||||
const folderExportedDir = path.join(
|
||||
exportDir,
|
||||
filenamify(folder.name, { replacement: '_' })
|
||||
)
|
||||
folderNamesMapping[folder.key] = folderExportedDir
|
||||
// make sure directory exists
|
||||
try {
|
||||
@@ -47,7 +50,9 @@ function exportStorage (storageKey, fileType, exportDir) {
|
||||
.filter(note => !note.isTrashed && note.type === 'MARKDOWN_NOTE')
|
||||
.forEach(markdownNote => {
|
||||
const folderExportedDir = folderNamesMapping[markdownNote.folder]
|
||||
const snippetName = `${filenamify(markdownNote.title, {replacement: '_'})}.${fileType}`
|
||||
const snippetName = `${filenamify(markdownNote.title, {
|
||||
replacement: '_'
|
||||
})}.${fileType}`
|
||||
const notePath = path.join(folderExportedDir, snippetName)
|
||||
fs.writeFileSync(notePath, markdownNote.content)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from 'fs'
|
||||
import consts from 'browser/lib/consts'
|
||||
|
||||
function fetchSnippet (id, snippetFile) {
|
||||
function fetchSnippet(id, snippetFile) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(snippetFile || consts.SNIPPET_FILE, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
@@ -9,7 +9,9 @@ function fetchSnippet (id, snippetFile) {
|
||||
}
|
||||
const snippets = JSON.parse(data)
|
||||
if (id) {
|
||||
const snippet = snippets.find(snippet => { return snippet.id === id })
|
||||
const snippet = snippets.find(snippet => {
|
||||
return snippet.id === id
|
||||
})
|
||||
resolve(snippet)
|
||||
}
|
||||
resolve(snippets)
|
||||
|
||||
@@ -21,8 +21,8 @@ const CSON = require('@rokt33r/season')
|
||||
* 3. empty directory
|
||||
*/
|
||||
|
||||
function init () {
|
||||
const fetchStorages = function () {
|
||||
function init() {
|
||||
const fetchStorages = function() {
|
||||
let rawStorages
|
||||
try {
|
||||
rawStorages = JSON.parse(window.localStorage.getItem('storages'))
|
||||
@@ -34,44 +34,50 @@ function init () {
|
||||
rawStorages = []
|
||||
window.localStorage.setItem('storages', JSON.stringify(rawStorages))
|
||||
}
|
||||
return Promise.all(rawStorages
|
||||
.map(resolveStorageData))
|
||||
return Promise.all(rawStorages.map(resolveStorageData))
|
||||
}
|
||||
|
||||
const fetchNotes = function (storages) {
|
||||
const fetchNotes = function(storages) {
|
||||
const findNotesFromEachStorage = storages
|
||||
.filter(storage => fs.existsSync(storage.path))
|
||||
.map((storage) => {
|
||||
return resolveStorageNotes(storage)
|
||||
.then((notes) => {
|
||||
let unknownCount = 0
|
||||
notes.forEach((note) => {
|
||||
if (note && !storage.folders.some((folder) => note.folder === folder.key)) {
|
||||
unknownCount++
|
||||
storage.folders.push({
|
||||
key: note.folder,
|
||||
color: consts.FOLDER_COLORS[(unknownCount - 1) % 7],
|
||||
name: 'Unknown ' + unknownCount
|
||||
})
|
||||
}
|
||||
})
|
||||
if (unknownCount > 0) {
|
||||
try {
|
||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
||||
} catch (e) {
|
||||
console.log('Error writting boostnote.json: ' + e + ' from init.js')
|
||||
}
|
||||
.filter(storage => fs.existsSync(storage.path))
|
||||
.map(storage => {
|
||||
return resolveStorageNotes(storage).then(notes => {
|
||||
let unknownCount = 0
|
||||
notes.forEach(note => {
|
||||
if (
|
||||
note &&
|
||||
!storage.folders.some(folder => note.folder === folder.key)
|
||||
) {
|
||||
unknownCount++
|
||||
storage.folders.push({
|
||||
key: note.folder,
|
||||
color: consts.FOLDER_COLORS[(unknownCount - 1) % 7],
|
||||
name: 'Unknown ' + unknownCount
|
||||
})
|
||||
}
|
||||
return notes
|
||||
})
|
||||
if (unknownCount > 0) {
|
||||
try {
|
||||
CSON.writeFileSync(
|
||||
path.join(storage.path, 'boostnote.json'),
|
||||
_.pick(storage, ['folders', 'version'])
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(
|
||||
'Error writting boostnote.json: ' + e + ' from init.js'
|
||||
)
|
||||
}
|
||||
}
|
||||
return notes
|
||||
})
|
||||
})
|
||||
return Promise.all(findNotesFromEachStorage)
|
||||
.then(function concatNoteGroup (noteGroups) {
|
||||
return noteGroups.reduce(function (sum, group) {
|
||||
.then(function concatNoteGroup(noteGroups) {
|
||||
return noteGroups.reduce(function(sum, group) {
|
||||
return sum.concat(group)
|
||||
}, [])
|
||||
})
|
||||
.then(function returnData (notes) {
|
||||
.then(function returnData(notes) {
|
||||
return {
|
||||
storages,
|
||||
notes
|
||||
@@ -80,12 +86,11 @@ function init () {
|
||||
}
|
||||
|
||||
return Promise.resolve(fetchStorages())
|
||||
.then((storages) => {
|
||||
return storages
|
||||
.filter((storage) => {
|
||||
if (!_.isObject(storage)) return false
|
||||
return true
|
||||
})
|
||||
.then(storages => {
|
||||
return storages.filter(storage => {
|
||||
if (!_.isObject(storage)) return false
|
||||
return true
|
||||
})
|
||||
})
|
||||
.then(fetchNotes)
|
||||
}
|
||||
|
||||
@@ -6,102 +6,111 @@ const CSON = require('@rokt33r/season')
|
||||
const path = require('path')
|
||||
const sander = require('sander')
|
||||
|
||||
function migrateFromV5Storage (storageKey, data) {
|
||||
function migrateFromV5Storage(storageKey, data) {
|
||||
let targetStorage
|
||||
try {
|
||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
if (!_.isArray(cachedStorageList))
|
||||
throw new Error("Target storage doesn't exist.")
|
||||
|
||||
targetStorage = _.find(cachedStorageList, {key: storageKey})
|
||||
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
|
||||
targetStorage = _.find(cachedStorageList, { key: storageKey })
|
||||
if (targetStorage == null) throw new Error("Target storage doesn't exist.")
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function (storage) {
|
||||
return importAll(storage, data)
|
||||
})
|
||||
return resolveStorageData(targetStorage).then(function(storage) {
|
||||
return importAll(storage, data)
|
||||
})
|
||||
}
|
||||
|
||||
function importAll (storage, data) {
|
||||
function importAll(storage, data) {
|
||||
const oldArticles = data.articles
|
||||
const notes = []
|
||||
data.folders
|
||||
.forEach(function (oldFolder) {
|
||||
let folderKey = keygen()
|
||||
while (storage.folders.some((folder) => folder.key === folderKey)) {
|
||||
folderKey = keygen()
|
||||
}
|
||||
const newFolder = {
|
||||
key: folderKey,
|
||||
name: oldFolder.name,
|
||||
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
||||
}
|
||||
data.folders.forEach(function(oldFolder) {
|
||||
let folderKey = keygen()
|
||||
while (storage.folders.some(folder => folder.key === folderKey)) {
|
||||
folderKey = keygen()
|
||||
}
|
||||
const newFolder = {
|
||||
key: folderKey,
|
||||
name: oldFolder.name,
|
||||
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
||||
}
|
||||
|
||||
storage.folders.push(newFolder)
|
||||
storage.folders.push(newFolder)
|
||||
|
||||
const articles = oldArticles.filter((article) => article.FolderKey === oldFolder.key)
|
||||
articles.forEach((article) => {
|
||||
let noteKey = keygen()
|
||||
let isUnique = false
|
||||
while (!isUnique) {
|
||||
try {
|
||||
sander.statSync(path.join(storage.path, 'notes', noteKey + '.cson'))
|
||||
noteKey = keygen()
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
isUnique = true
|
||||
} else {
|
||||
console.error('Failed to read `notes` directory.')
|
||||
throw err
|
||||
}
|
||||
const articles = oldArticles.filter(
|
||||
article => article.FolderKey === oldFolder.key
|
||||
)
|
||||
articles.forEach(article => {
|
||||
let noteKey = keygen()
|
||||
let isUnique = false
|
||||
while (!isUnique) {
|
||||
try {
|
||||
sander.statSync(path.join(storage.path, 'notes', noteKey + '.cson'))
|
||||
noteKey = keygen()
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
isUnique = true
|
||||
} else {
|
||||
console.error('Failed to read `notes` directory.')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (article.mode === 'markdown') {
|
||||
const newNote = {
|
||||
tags: article.tags,
|
||||
createdAt: article.createdAt,
|
||||
updatedAt: article.updatedAt,
|
||||
folder: folderKey,
|
||||
storage: storage.key,
|
||||
type: 'MARKDOWN_NOTE',
|
||||
isStarred: false,
|
||||
title: article.title,
|
||||
content: '# ' + article.title + '\n\n' + article.content,
|
||||
key: noteKey,
|
||||
linesHighlighted: article.linesHighlighted
|
||||
}
|
||||
notes.push(newNote)
|
||||
} else {
|
||||
const newNote = {
|
||||
tags: article.tags,
|
||||
createdAt: article.createdAt,
|
||||
updatedAt: article.updatedAt,
|
||||
folder: folderKey,
|
||||
storage: storage.key,
|
||||
type: 'SNIPPET_NOTE',
|
||||
isStarred: false,
|
||||
title: article.title,
|
||||
description: article.title,
|
||||
key: noteKey,
|
||||
snippets: [{
|
||||
if (article.mode === 'markdown') {
|
||||
const newNote = {
|
||||
tags: article.tags,
|
||||
createdAt: article.createdAt,
|
||||
updatedAt: article.updatedAt,
|
||||
folder: folderKey,
|
||||
storage: storage.key,
|
||||
type: 'MARKDOWN_NOTE',
|
||||
isStarred: false,
|
||||
title: article.title,
|
||||
content: '# ' + article.title + '\n\n' + article.content,
|
||||
key: noteKey,
|
||||
linesHighlighted: article.linesHighlighted
|
||||
}
|
||||
notes.push(newNote)
|
||||
} else {
|
||||
const newNote = {
|
||||
tags: article.tags,
|
||||
createdAt: article.createdAt,
|
||||
updatedAt: article.updatedAt,
|
||||
folder: folderKey,
|
||||
storage: storage.key,
|
||||
type: 'SNIPPET_NOTE',
|
||||
isStarred: false,
|
||||
title: article.title,
|
||||
description: article.title,
|
||||
key: noteKey,
|
||||
snippets: [
|
||||
{
|
||||
name: article.mode,
|
||||
mode: article.mode,
|
||||
content: article.content,
|
||||
linesHighlighted: article.linesHighlighted
|
||||
}]
|
||||
}
|
||||
notes.push(newNote)
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
notes.push(newNote)
|
||||
}
|
||||
})
|
||||
|
||||
notes.forEach(function (note) {
|
||||
CSON.writeFileSync(path.join(storage.path, 'notes', note.key + '.cson'), _.omit(note, ['storage', 'key']))
|
||||
})
|
||||
|
||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['version', 'folders']))
|
||||
notes.forEach(function(note) {
|
||||
CSON.writeFileSync(
|
||||
path.join(storage.path, 'notes', note.key + '.cson'),
|
||||
_.omit(note, ['storage', 'key'])
|
||||
)
|
||||
})
|
||||
|
||||
CSON.writeFileSync(
|
||||
path.join(storage.path, 'boostnote.json'),
|
||||
_.pick(storage, ['version', 'folders'])
|
||||
)
|
||||
|
||||
return {
|
||||
storage,
|
||||
|
||||
@@ -4,86 +4,91 @@ const keygen = require('browser/lib/keygen')
|
||||
const _ = require('lodash')
|
||||
const CSON = require('@rokt33r/season')
|
||||
|
||||
function migrateFromV5Storage (storagePath) {
|
||||
function migrateFromV5Storage(storagePath) {
|
||||
var boostnoteJSONPath = path.join(storagePath, 'boostnote.json')
|
||||
return Promise.resolve()
|
||||
.then(function readBoostnoteJSON () {
|
||||
.then(function readBoostnoteJSON() {
|
||||
return sander.readFile(boostnoteJSONPath, {
|
||||
encoding: 'utf-8'
|
||||
})
|
||||
})
|
||||
.then(function verifyVersion (rawData) {
|
||||
.then(function verifyVersion(rawData) {
|
||||
var boostnoteJSONData = JSON.parse(rawData)
|
||||
if (boostnoteJSONData.version === '1.0') throw new Error('Target storage seems to be transformed already.')
|
||||
if (!_.isArray(boostnoteJSONData.folders)) throw new Error('the value of folders is not an array.')
|
||||
if (boostnoteJSONData.version === '1.0')
|
||||
throw new Error('Target storage seems to be transformed already.')
|
||||
if (!_.isArray(boostnoteJSONData.folders))
|
||||
throw new Error('the value of folders is not an array.')
|
||||
|
||||
return boostnoteJSONData
|
||||
})
|
||||
.then(function setVersion (boostnoteJSONData) {
|
||||
.then(function setVersion(boostnoteJSONData) {
|
||||
boostnoteJSONData.version = '1.0'
|
||||
return sander.writeFile(boostnoteJSONPath, JSON.stringify(boostnoteJSONData))
|
||||
return sander
|
||||
.writeFile(boostnoteJSONPath, JSON.stringify(boostnoteJSONData))
|
||||
.then(() => boostnoteJSONData)
|
||||
})
|
||||
.then(function fetchNotes (boostnoteJSONData) {
|
||||
var fetchNotesFromEachFolder = boostnoteJSONData.folders
|
||||
.map(function (folder) {
|
||||
const folderDataJSONPath = path.join(storagePath, folder.key, 'data.json')
|
||||
return sander
|
||||
.readFile(folderDataJSONPath, {
|
||||
encoding: 'utf-8'
|
||||
.then(function fetchNotes(boostnoteJSONData) {
|
||||
var fetchNotesFromEachFolder = boostnoteJSONData.folders.map(function(
|
||||
folder
|
||||
) {
|
||||
const folderDataJSONPath = path.join(
|
||||
storagePath,
|
||||
folder.key,
|
||||
'data.json'
|
||||
)
|
||||
return sander
|
||||
.readFile(folderDataJSONPath, {
|
||||
encoding: 'utf-8'
|
||||
})
|
||||
.then(function(rawData) {
|
||||
var data = JSON.parse(rawData)
|
||||
if (!_.isArray(data.notes))
|
||||
throw new Error('value of notes is not an array.')
|
||||
return data.notes.map(function setFolderToNote(note) {
|
||||
note.folder = folder.key
|
||||
return note
|
||||
})
|
||||
.then(function (rawData) {
|
||||
var data = JSON.parse(rawData)
|
||||
if (!_.isArray(data.notes)) throw new Error('value of notes is not an array.')
|
||||
return data.notes
|
||||
.map(function setFolderToNote (note) {
|
||||
note.folder = folder.key
|
||||
return note
|
||||
})
|
||||
})
|
||||
.catch(function failedToReadDataJSON (err) {
|
||||
console.warn('Failed to fetch notes from ', folderDataJSONPath, err)
|
||||
return []
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function failedToReadDataJSON(err) {
|
||||
console.warn('Failed to fetch notes from ', folderDataJSONPath, err)
|
||||
return []
|
||||
})
|
||||
})
|
||||
|
||||
return Promise.all(fetchNotesFromEachFolder)
|
||||
.then(function flatten (folderNotes) {
|
||||
return folderNotes
|
||||
.reduce(function concatNotes (sum, notes) {
|
||||
return sum.concat(notes)
|
||||
}, [])
|
||||
.then(function flatten(folderNotes) {
|
||||
return folderNotes.reduce(function concatNotes(sum, notes) {
|
||||
return sum.concat(notes)
|
||||
}, [])
|
||||
})
|
||||
.then(function saveNotes (notes) {
|
||||
notes.forEach(function renewKey (note) {
|
||||
.then(function saveNotes(notes) {
|
||||
notes.forEach(function renewKey(note) {
|
||||
var newKey = keygen()
|
||||
while (notes.some((_note) => _note.key === newKey)) {
|
||||
while (notes.some(_note => _note.key === newKey)) {
|
||||
newKey = keygen()
|
||||
}
|
||||
note.key = newKey
|
||||
})
|
||||
|
||||
const noteDirPath = path.join(storagePath, 'notes')
|
||||
notes
|
||||
.map(function saveNote (note) {
|
||||
CSON.writeFileSync(path.join(noteDirPath, note.key) + '.cson', note)
|
||||
})
|
||||
notes.map(function saveNote(note) {
|
||||
CSON.writeFileSync(path.join(noteDirPath, note.key) + '.cson', note)
|
||||
})
|
||||
return true
|
||||
})
|
||||
.then(function deleteFolderDir (check) {
|
||||
.then(function deleteFolderDir(check) {
|
||||
if (check) {
|
||||
boostnoteJSONData.folders.forEach((folder) => {
|
||||
boostnoteJSONData.folders.forEach(folder => {
|
||||
sander.rimrafSync(path.join(storagePath, folder.key))
|
||||
})
|
||||
}
|
||||
return check
|
||||
})
|
||||
})
|
||||
.catch(function handleError (err) {
|
||||
.catch(function handleError(err) {
|
||||
console.warn(err)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = migrateFromV5Storage
|
||||
|
||||
|
||||
@@ -7,90 +7,104 @@ const sander = require('sander')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
const attachmentManagement = require('./attachmentManagement')
|
||||
|
||||
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
||||
function moveNote(storageKey, noteKey, newStorageKey, newFolderKey) {
|
||||
let oldStorage, newStorage
|
||||
try {
|
||||
oldStorage = findStorage(storageKey)
|
||||
newStorage = findStorage(newStorageKey)
|
||||
if (newStorage == null) throw new Error('Target storage doesn\'t exist.')
|
||||
if (newStorage == null) throw new Error("Target storage doesn't exist.")
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
return resolveStorageData(oldStorage)
|
||||
.then(function saveNote (_oldStorage) {
|
||||
oldStorage = _oldStorage
|
||||
let noteData
|
||||
const notePath = path.join(oldStorage.path, 'notes', noteKey + '.cson')
|
||||
try {
|
||||
noteData = CSON.readFileSync(notePath)
|
||||
} catch (err) {
|
||||
console.warn('Failed to find note cson', err)
|
||||
throw err
|
||||
}
|
||||
let newNoteKey
|
||||
return Promise.resolve()
|
||||
.then(function resolveNewStorage () {
|
||||
if (storageKey === newStorageKey) {
|
||||
newNoteKey = noteKey
|
||||
return oldStorage
|
||||
}
|
||||
return resolveStorageData(newStorage)
|
||||
.then(function findNewNoteKey (_newStorage) {
|
||||
newStorage = _newStorage
|
||||
newNoteKey = keygen(true)
|
||||
let isUnique = false
|
||||
while (!isUnique) {
|
||||
try {
|
||||
sander.statSync(path.join(newStorage.path, 'notes', newNoteKey + '.cson'))
|
||||
newNoteKey = keygen(true)
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
isUnique = true
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newStorage
|
||||
})
|
||||
})
|
||||
.then(function checkFolderExistsAndPrepareNoteData (newStorage) {
|
||||
if (_.find(newStorage.folders, {key: newFolderKey}) == null) throw new Error('Target folder doesn\'t exist.')
|
||||
|
||||
noteData.folder = newFolderKey
|
||||
noteData.key = newNoteKey
|
||||
noteData.storage = newStorageKey
|
||||
noteData.updatedAt = new Date()
|
||||
noteData.oldContent = noteData.content
|
||||
|
||||
return noteData
|
||||
})
|
||||
.then(function moveAttachments (noteData) {
|
||||
if (oldStorage.path === newStorage.path) {
|
||||
return noteData
|
||||
}
|
||||
|
||||
noteData.content = attachmentManagement.moveAttachments(oldStorage.path, newStorage.path, noteKey, newNoteKey, noteData.content)
|
||||
return noteData
|
||||
})
|
||||
.then(function writeAndReturn (noteData) {
|
||||
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage', 'oldContent']))
|
||||
return noteData
|
||||
})
|
||||
.then(function deleteOldNote (data) {
|
||||
if (storageKey !== newStorageKey) {
|
||||
return resolveStorageData(oldStorage).then(function saveNote(_oldStorage) {
|
||||
oldStorage = _oldStorage
|
||||
let noteData
|
||||
const notePath = path.join(oldStorage.path, 'notes', noteKey + '.cson')
|
||||
try {
|
||||
noteData = CSON.readFileSync(notePath)
|
||||
} catch (err) {
|
||||
console.warn('Failed to find note cson', err)
|
||||
throw err
|
||||
}
|
||||
let newNoteKey
|
||||
return Promise.resolve()
|
||||
.then(function resolveNewStorage() {
|
||||
if (storageKey === newStorageKey) {
|
||||
newNoteKey = noteKey
|
||||
return oldStorage
|
||||
}
|
||||
return resolveStorageData(newStorage).then(function findNewNoteKey(
|
||||
_newStorage
|
||||
) {
|
||||
newStorage = _newStorage
|
||||
newNoteKey = keygen(true)
|
||||
let isUnique = false
|
||||
while (!isUnique) {
|
||||
try {
|
||||
sander.unlinkSync(path.join(oldStorage.path, 'notes', noteKey + '.cson'))
|
||||
sander.statSync(
|
||||
path.join(newStorage.path, 'notes', newNoteKey + '.cson')
|
||||
)
|
||||
newNoteKey = keygen(true)
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
if (err.code === 'ENOENT') {
|
||||
isUnique = true
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
return newStorage
|
||||
})
|
||||
})
|
||||
})
|
||||
.then(function checkFolderExistsAndPrepareNoteData(newStorage) {
|
||||
if (_.find(newStorage.folders, { key: newFolderKey }) == null)
|
||||
throw new Error("Target folder doesn't exist.")
|
||||
|
||||
noteData.folder = newFolderKey
|
||||
noteData.key = newNoteKey
|
||||
noteData.storage = newStorageKey
|
||||
noteData.updatedAt = new Date()
|
||||
noteData.oldContent = noteData.content
|
||||
|
||||
return noteData
|
||||
})
|
||||
.then(function moveAttachments(noteData) {
|
||||
if (oldStorage.path === newStorage.path) {
|
||||
return noteData
|
||||
}
|
||||
|
||||
noteData.content = attachmentManagement.moveAttachments(
|
||||
oldStorage.path,
|
||||
newStorage.path,
|
||||
noteKey,
|
||||
newNoteKey,
|
||||
noteData.content
|
||||
)
|
||||
return noteData
|
||||
})
|
||||
.then(function writeAndReturn(noteData) {
|
||||
CSON.writeFileSync(
|
||||
path.join(newStorage.path, 'notes', noteData.key + '.cson'),
|
||||
_.omit(noteData, ['key', 'storage', 'oldContent'])
|
||||
)
|
||||
return noteData
|
||||
})
|
||||
.then(function deleteOldNote(data) {
|
||||
if (storageKey !== newStorageKey) {
|
||||
try {
|
||||
sander.unlinkSync(
|
||||
path.join(oldStorage.path, 'notes', noteKey + '.cson')
|
||||
)
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = moveNote
|
||||
|
||||
@@ -4,7 +4,7 @@ const _ = require('lodash')
|
||||
* @param {String} key
|
||||
* @return {key}
|
||||
*/
|
||||
function removeStorage (key) {
|
||||
function removeStorage(key) {
|
||||
let rawStorages
|
||||
|
||||
try {
|
||||
@@ -15,10 +15,9 @@ function removeStorage (key) {
|
||||
rawStorages = []
|
||||
}
|
||||
|
||||
rawStorages = rawStorages
|
||||
.filter(function excludeTargetStorage (rawStorage) {
|
||||
return rawStorage.key !== key
|
||||
})
|
||||
rawStorages = rawStorages.filter(function excludeTargetStorage(rawStorage) {
|
||||
return rawStorage.key !== key
|
||||
})
|
||||
|
||||
localStorage.setItem('storages', JSON.stringify(rawStorages))
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@ const resolveStorageData = require('./resolveStorageData')
|
||||
* @param {String} name
|
||||
* @return {Object} Storage meta data
|
||||
*/
|
||||
function renameStorage (key, name) {
|
||||
if (!_.isString(name)) return Promise.reject(new Error('Name must be a string.'))
|
||||
function renameStorage(key, name) {
|
||||
if (!_.isString(name))
|
||||
return Promise.reject(new Error('Name must be a string.'))
|
||||
|
||||
let cachedStorageList
|
||||
try {
|
||||
@@ -17,7 +18,7 @@ function renameStorage (key, name) {
|
||||
console.error(err)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
const targetStorage = _.find(cachedStorageList, {key: key})
|
||||
const targetStorage = _.find(cachedStorageList, { key: key })
|
||||
if (targetStorage == null) return Promise.reject('Storage')
|
||||
|
||||
targetStorage.name = name
|
||||
|
||||
@@ -17,7 +17,7 @@ const { findStorage } = require('browser/lib/findStorage')
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function reorderFolder (storageKey, oldIndex, newIndex) {
|
||||
function reorderFolder(storageKey, oldIndex, newIndex) {
|
||||
let targetStorage
|
||||
try {
|
||||
if (!_.isNumber(oldIndex)) throw new Error('oldIndex must be a number.')
|
||||
@@ -28,15 +28,19 @@ function reorderFolder (storageKey, oldIndex, newIndex) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function reorderFolder (storage) {
|
||||
storage.folders = _.move(storage.folders, oldIndex, newIndex)
|
||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
||||
return resolveStorageData(targetStorage).then(function reorderFolder(
|
||||
storage
|
||||
) {
|
||||
storage.folders = _.move(storage.folders, oldIndex, newIndex)
|
||||
CSON.writeFileSync(
|
||||
path.join(storage.path, 'boostnote.json'),
|
||||
_.pick(storage, ['folders', 'version'])
|
||||
)
|
||||
|
||||
return {
|
||||
storage
|
||||
}
|
||||
})
|
||||
return {
|
||||
storage
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = reorderFolder
|
||||
|
||||
@@ -3,7 +3,7 @@ const path = require('path')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const migrateFromV6Storage = require('./migrateFromV6Storage')
|
||||
|
||||
function resolveStorageData (storageCache) {
|
||||
function resolveStorageData(storageCache) {
|
||||
const storage = {
|
||||
key: storageCache.key,
|
||||
name: storageCache.name,
|
||||
@@ -15,13 +15,14 @@ function resolveStorageData (storageCache) {
|
||||
const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json')
|
||||
try {
|
||||
const jsonData = CSON.readFileSync(boostnoteJSONPath)
|
||||
if (!_.isArray(jsonData.folders)) throw new Error('folders should be an array.')
|
||||
if (!_.isArray(jsonData.folders))
|
||||
throw new Error('folders should be an array.')
|
||||
storage.folders = jsonData.folders
|
||||
storage.version = jsonData.version
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.warn('boostnote.json file doesn\'t exist the given path')
|
||||
CSON.writeFileSync(boostnoteJSONPath, {folders: [], version: '1.0'})
|
||||
console.warn("boostnote.json file doesn't exist the given path")
|
||||
CSON.writeFileSync(boostnoteJSONPath, { folders: [], version: '1.0' })
|
||||
} else {
|
||||
console.error(err)
|
||||
}
|
||||
@@ -34,8 +35,7 @@ function resolveStorageData (storageCache) {
|
||||
return Promise.resolve(storage)
|
||||
}
|
||||
|
||||
return migrateFromV6Storage(storage.path)
|
||||
.then(() => storage)
|
||||
return migrateFromV6Storage(storage.path).then(() => storage)
|
||||
}
|
||||
|
||||
module.exports = resolveStorageData
|
||||
|
||||
@@ -2,14 +2,14 @@ const sander = require('sander')
|
||||
const path = require('path')
|
||||
const CSON = require('@rokt33r/season')
|
||||
|
||||
function resolveStorageNotes (storage) {
|
||||
function resolveStorageNotes(storage) {
|
||||
const notesDirPath = path.join(storage.path, 'notes')
|
||||
let notePathList
|
||||
try {
|
||||
notePathList = sander.readdirSync(notesDirPath)
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.error(notesDirPath, ' doesn\'t exist.')
|
||||
console.error(notesDirPath, " doesn't exist.")
|
||||
sander.mkdirSync(notesDirPath)
|
||||
} else {
|
||||
console.warn('Failed to find note dir', notesDirPath, err)
|
||||
@@ -17,10 +17,10 @@ function resolveStorageNotes (storage) {
|
||||
notePathList = []
|
||||
}
|
||||
const notes = notePathList
|
||||
.filter(function filterOnlyCSONFile (notePath) {
|
||||
.filter(function filterOnlyCSONFile(notePath) {
|
||||
return /\.cson$/.test(notePath)
|
||||
})
|
||||
.map(function parseCSONFile (notePath) {
|
||||
.map(function parseCSONFile(notePath) {
|
||||
try {
|
||||
const data = CSON.readFileSync(path.join(notesDirPath, notePath))
|
||||
data.key = path.basename(notePath, '.cson')
|
||||
@@ -30,7 +30,7 @@ function resolveStorageNotes (storage) {
|
||||
console.error(`error on note path: ${notePath}, error: ${err}`)
|
||||
}
|
||||
})
|
||||
.filter(function filterOnlyNoteObject (noteObj) {
|
||||
.filter(function filterOnlyNoteObject(noteObj) {
|
||||
return typeof noteObj === 'object'
|
||||
})
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const resolveStorageData = require('./resolveStorageData')
|
||||
* @param {Boolean} isOpen
|
||||
* @return {Object} Storage meta data
|
||||
*/
|
||||
function toggleStorage (key, isOpen) {
|
||||
function toggleStorage(key, isOpen) {
|
||||
let cachedStorageList
|
||||
try {
|
||||
cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
@@ -15,7 +15,7 @@ function toggleStorage (key, isOpen) {
|
||||
console.error(err)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
const targetStorage = _.find(cachedStorageList, {key: key})
|
||||
const targetStorage = _.find(cachedStorageList, { key: key })
|
||||
if (targetStorage == null) return Promise.reject('Storage')
|
||||
|
||||
targetStorage.isOpen = isOpen
|
||||
|
||||
@@ -22,7 +22,7 @@ const { findStorage } = require('browser/lib/findStorage')
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function updateFolder (storageKey, folderKey, input) {
|
||||
function updateFolder(storageKey, folderKey, input) {
|
||||
let targetStorage
|
||||
try {
|
||||
if (input == null) throw new Error('No input found.')
|
||||
@@ -34,19 +34,21 @@ function updateFolder (storageKey, folderKey, input) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function updateFolder (storage) {
|
||||
const targetFolder = _.find(storage.folders, {key: folderKey})
|
||||
if (targetFolder == null) throw new Error('Target folder doesn\'t exist.')
|
||||
targetFolder.name = input.name
|
||||
targetFolder.color = input.color
|
||||
return resolveStorageData(targetStorage).then(function updateFolder(storage) {
|
||||
const targetFolder = _.find(storage.folders, { key: folderKey })
|
||||
if (targetFolder == null) throw new Error("Target folder doesn't exist.")
|
||||
targetFolder.name = input.name
|
||||
targetFolder.color = input.color
|
||||
|
||||
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
||||
CSON.writeFileSync(
|
||||
path.join(storage.path, 'boostnote.json'),
|
||||
_.pick(storage, ['folders', 'version'])
|
||||
)
|
||||
|
||||
return {
|
||||
storage
|
||||
}
|
||||
})
|
||||
return {
|
||||
storage
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = updateFolder
|
||||
|
||||
@@ -4,13 +4,14 @@ const path = require('path')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
function validateInput (input) {
|
||||
function validateInput(input) {
|
||||
const validatedInput = {}
|
||||
|
||||
if (input.tags != null) {
|
||||
if (!_.isArray(input.tags)) validatedInput.tags = []
|
||||
validatedInput.tags = input.tags
|
||||
.filter((tag) => _.isString(tag) && tag.trim().length > 0)
|
||||
validatedInput.tags = input.tags.filter(
|
||||
tag => _.isString(tag) && tag.trim().length > 0
|
||||
)
|
||||
}
|
||||
|
||||
if (input.title != null) {
|
||||
@@ -40,7 +41,8 @@ function validateInput (input) {
|
||||
if (!_.isString(input.content)) validatedInput.content = ''
|
||||
else validatedInput.content = input.content
|
||||
|
||||
if (!_.isArray(input.linesHighlighted)) validatedInput.linesHighlighted = []
|
||||
if (!_.isArray(input.linesHighlighted))
|
||||
validatedInput.linesHighlighted = []
|
||||
else validatedInput.linesHighlighted = input.linesHighlighted
|
||||
}
|
||||
return validatedInput
|
||||
@@ -51,30 +53,33 @@ function validateInput (input) {
|
||||
}
|
||||
if (input.snippets != null) {
|
||||
if (!_.isArray(input.snippets)) {
|
||||
validatedInput.snippets = [{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}]
|
||||
validatedInput.snippets = [
|
||||
{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}
|
||||
]
|
||||
} else {
|
||||
validatedInput.snippets = input.snippets
|
||||
}
|
||||
validatedInput.snippets
|
||||
.filter((snippet) => {
|
||||
if (!_.isString(snippet.name)) return false
|
||||
if (!_.isString(snippet.mode)) return false
|
||||
if (!_.isString(snippet.content)) return false
|
||||
return true
|
||||
})
|
||||
validatedInput.snippets.filter(snippet => {
|
||||
if (!_.isString(snippet.name)) return false
|
||||
if (!_.isString(snippet.mode)) return false
|
||||
if (!_.isString(snippet.content)) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
return validatedInput
|
||||
default:
|
||||
throw new Error('Invalid type: only MARKDOWN_NOTE and SNIPPET_NOTE are available.')
|
||||
throw new Error(
|
||||
'Invalid type: only MARKDOWN_NOTE and SNIPPET_NOTE are available.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function updateNote (storageKey, noteKey, input) {
|
||||
function updateNote(storageKey, noteKey, input) {
|
||||
let targetStorage
|
||||
try {
|
||||
if (input == null) throw new Error('No input found.')
|
||||
@@ -85,55 +90,61 @@ function updateNote (storageKey, noteKey, input) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function saveNote (storage) {
|
||||
let noteData
|
||||
const notePath = path.join(storage.path, 'notes', noteKey + '.cson')
|
||||
try {
|
||||
noteData = CSON.readFileSync(notePath)
|
||||
} catch (err) {
|
||||
console.warn('Failed to find note cson', err)
|
||||
noteData = input.type === 'SNIPPET_NOTE'
|
||||
return resolveStorageData(targetStorage).then(function saveNote(storage) {
|
||||
let noteData
|
||||
const notePath = path.join(storage.path, 'notes', noteKey + '.cson')
|
||||
try {
|
||||
noteData = CSON.readFileSync(notePath)
|
||||
} catch (err) {
|
||||
console.warn('Failed to find note cson', err)
|
||||
noteData =
|
||||
input.type === 'SNIPPET_NOTE'
|
||||
? {
|
||||
type: 'SNIPPET_NOTE',
|
||||
description: [],
|
||||
snippets: [{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
type: 'SNIPPET_NOTE',
|
||||
description: [],
|
||||
snippets: [
|
||||
{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}
|
||||
]
|
||||
}
|
||||
: {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}]
|
||||
}
|
||||
: {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}
|
||||
noteData.title = ''
|
||||
if (storage.folders.length === 0) throw new Error('Failed to restore note: No folder exists.')
|
||||
noteData.folder = storage.folders[0].key
|
||||
noteData.createdAt = new Date()
|
||||
noteData.updatedAt = new Date()
|
||||
noteData.isStarred = false
|
||||
noteData.isTrashed = false
|
||||
noteData.tags = []
|
||||
noteData.isPinned = false
|
||||
}
|
||||
}
|
||||
noteData.title = ''
|
||||
if (storage.folders.length === 0)
|
||||
throw new Error('Failed to restore note: No folder exists.')
|
||||
noteData.folder = storage.folders[0].key
|
||||
noteData.createdAt = new Date()
|
||||
noteData.updatedAt = new Date()
|
||||
noteData.isStarred = false
|
||||
noteData.isTrashed = false
|
||||
noteData.tags = []
|
||||
noteData.isPinned = false
|
||||
}
|
||||
|
||||
if (noteData.type === 'SNIPPET_NOTE') {
|
||||
noteData.title
|
||||
}
|
||||
if (noteData.type === 'SNIPPET_NOTE') {
|
||||
noteData.title
|
||||
}
|
||||
|
||||
Object.assign(noteData, input, {
|
||||
key: noteKey,
|
||||
updatedAt: new Date(),
|
||||
storage: storageKey
|
||||
})
|
||||
|
||||
CSON.writeFileSync(path.join(storage.path, 'notes', noteKey + '.cson'), _.omit(noteData, ['key', 'storage']))
|
||||
|
||||
return noteData
|
||||
Object.assign(noteData, input, {
|
||||
key: noteKey,
|
||||
updatedAt: new Date(),
|
||||
storage: storageKey
|
||||
})
|
||||
|
||||
CSON.writeFileSync(
|
||||
path.join(storage.path, 'notes', noteKey + '.cson'),
|
||||
_.omit(noteData, ['key', 'storage'])
|
||||
)
|
||||
|
||||
return noteData
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = updateNote
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import fs from 'fs'
|
||||
import consts from 'browser/lib/consts'
|
||||
|
||||
function updateSnippet (snippet, snippetFile) {
|
||||
function updateSnippet(snippet, snippetFile) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const snippets = JSON.parse(fs.readFileSync(snippetFile || consts.SNIPPET_FILE, 'utf-8'))
|
||||
const snippets = JSON.parse(
|
||||
fs.readFileSync(snippetFile || consts.SNIPPET_FILE, 'utf-8')
|
||||
)
|
||||
|
||||
for (let i = 0; i < snippets.length; i++) {
|
||||
const currentSnippet = snippets[i]
|
||||
@@ -21,11 +23,15 @@ function updateSnippet (snippet, snippetFile) {
|
||||
currentSnippet.name = snippet.name
|
||||
currentSnippet.prefix = snippet.prefix
|
||||
currentSnippet.content = snippet.content
|
||||
currentSnippet.linesHighlighted = (snippet.linesHighlighted)
|
||||
fs.writeFile(snippetFile || consts.SNIPPET_FILE, JSON.stringify(snippets, null, 4), (err) => {
|
||||
if (err) reject(err)
|
||||
resolve(snippets)
|
||||
})
|
||||
currentSnippet.linesHighlighted = snippet.linesHighlighted
|
||||
fs.writeFile(
|
||||
snippetFile || consts.SNIPPET_FILE,
|
||||
JSON.stringify(snippets, null, 4),
|
||||
err => {
|
||||
if (err) reject(err)
|
||||
resolve(snippets)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
const electron = require('electron')
|
||||
const { ipcRenderer, remote } = electron
|
||||
|
||||
function on (name, listener) {
|
||||
function on(name, listener) {
|
||||
ipcRenderer.on(name, listener)
|
||||
}
|
||||
|
||||
function off (name, listener) {
|
||||
function off(name, listener) {
|
||||
ipcRenderer.removeListener(name, listener)
|
||||
}
|
||||
|
||||
function once (name, listener) {
|
||||
function once(name, listener) {
|
||||
ipcRenderer.once(name, listener)
|
||||
}
|
||||
|
||||
function emit (name, ...args) {
|
||||
function emit(name, ...args) {
|
||||
remote.getCurrentWindow().webContents.send(name, ...args)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ nodeIpc.config.silent = true
|
||||
nodeIpc.connectTo(
|
||||
'node',
|
||||
path.join(app.getPath('userData'), 'boostnote.service'),
|
||||
function () {
|
||||
nodeIpc.of.node.on('error', function (err) {
|
||||
function() {
|
||||
nodeIpc.of.node.on('error', function(err) {
|
||||
console.error(err)
|
||||
})
|
||||
nodeIpc.of.node.on('connect', function () {
|
||||
ipcRenderer.send('config-renew', {config: ConfigManager.get()})
|
||||
nodeIpc.of.node.on('connect', function() {
|
||||
ipcRenderer.send('config-renew', { config: ConfigManager.get() })
|
||||
})
|
||||
nodeIpc.of.node.on('disconnect', function () {
|
||||
nodeIpc.of.node.on('disconnect', function() {
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import ReactDOM from 'react-dom'
|
||||
import { store } from '../store'
|
||||
|
||||
class ModalBase extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
component: null,
|
||||
@@ -13,20 +13,30 @@ class ModalBase extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
close () {
|
||||
if (modalBase != null) modalBase.setState({component: null, componentProps: null, isHidden: true})
|
||||
close() {
|
||||
if (modalBase != null)
|
||||
modalBase.setState({
|
||||
component: null,
|
||||
componentProps: null,
|
||||
isHidden: true
|
||||
})
|
||||
// Toggle overflow style on NoteList
|
||||
const list = document.querySelector('.NoteList__list___browser-main-NoteList-')
|
||||
const list = document.querySelector(
|
||||
'.NoteList__list___browser-main-NoteList-'
|
||||
)
|
||||
list.style.overflow = 'auto'
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
<div className={'ModalBase' + (this.state.isHidden ? ' hide' : '')}>
|
||||
<div onClick={(e) => this.close(e)} className='modalBack' />
|
||||
<div onClick={e => this.close(e)} className='modalBack' />
|
||||
{this.state.component == null ? null : (
|
||||
<Provider store={store}>
|
||||
<this.state.component {...this.state.componentProps} close={this.close} />
|
||||
<this.state.component
|
||||
{...this.state.componentProps}
|
||||
close={this.close}
|
||||
/>
|
||||
</Provider>
|
||||
)}
|
||||
</div>
|
||||
@@ -38,21 +48,31 @@ const el = document.createElement('div')
|
||||
document.body.appendChild(el)
|
||||
const modalBase = ReactDOM.render(<ModalBase />, el)
|
||||
|
||||
export function openModal (component, props) {
|
||||
if (modalBase == null) { return }
|
||||
export function openModal(component, props) {
|
||||
if (modalBase == null) {
|
||||
return
|
||||
}
|
||||
// Hide scrollbar by removing overflow when modal opens
|
||||
const list = document.querySelector('.NoteList__list___browser-main-NoteList-')
|
||||
const list = document.querySelector(
|
||||
'.NoteList__list___browser-main-NoteList-'
|
||||
)
|
||||
list.style.overflow = 'hidden'
|
||||
document.body.setAttribute('data-modal', 'open')
|
||||
modalBase.setState({component: component, componentProps: props, isHidden: false})
|
||||
modalBase.setState({
|
||||
component: component,
|
||||
componentProps: props,
|
||||
isHidden: false
|
||||
})
|
||||
}
|
||||
|
||||
export function closeModal () {
|
||||
if (modalBase == null) { return }
|
||||
export function closeModal() {
|
||||
if (modalBase == null) {
|
||||
return
|
||||
}
|
||||
modalBase.close()
|
||||
}
|
||||
|
||||
export function isModalOpen () {
|
||||
export function isModalOpen() {
|
||||
return !modalBase.state.isHidden
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
const path = require('path')
|
||||
|
||||
function notify (title, options) {
|
||||
function notify(title, options) {
|
||||
if (process.platform === 'win32') {
|
||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
||||
options.icon = path.join(
|
||||
'file://',
|
||||
global.__dirname,
|
||||
'../../resources/app.png'
|
||||
)
|
||||
options.silent = false
|
||||
}
|
||||
return new window.Notification(title, options)
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
|
||||
module.exports = {
|
||||
'toggleMode': () => {
|
||||
toggleMode: () => {
|
||||
ee.emit('topbar:togglemodebutton')
|
||||
},
|
||||
'toggleDirection': () => {
|
||||
toggleDirection: () => {
|
||||
ee.emit('topbar:toggledirectionbutton')
|
||||
},
|
||||
'deleteNote': () => {
|
||||
deleteNote: () => {
|
||||
ee.emit('hotkey:deletenote')
|
||||
},
|
||||
'toggleMenuBar': () => {
|
||||
toggleMenuBar: () => {
|
||||
ee.emit('menubar:togglemenubar')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import functions from './shortcut'
|
||||
|
||||
let shortcuts = CM.get().hotkey
|
||||
|
||||
ee.on('config-renew', function () {
|
||||
ee.on('config-renew', function() {
|
||||
// only update if hotkey changed !
|
||||
const newHotkey = CM.get().hotkey
|
||||
if (!isObjectEqual(newHotkey, shortcuts)) {
|
||||
@@ -15,17 +15,17 @@ ee.on('config-renew', function () {
|
||||
}
|
||||
})
|
||||
|
||||
function updateShortcut (newHotkey) {
|
||||
function updateShortcut(newHotkey) {
|
||||
Mousetrap.reset()
|
||||
shortcuts = newHotkey
|
||||
applyShortcuts(newHotkey)
|
||||
}
|
||||
|
||||
function formatShortcut (shortcut) {
|
||||
function formatShortcut(shortcut) {
|
||||
return shortcut.toLowerCase().replace(/ /g, '')
|
||||
}
|
||||
|
||||
function applyShortcuts (shortcuts) {
|
||||
function applyShortcuts(shortcuts) {
|
||||
for (const shortcut in shortcuts) {
|
||||
const toggler = formatShortcut(shortcuts[shortcut])
|
||||
// only bind if the function for that shortcut exists
|
||||
|
||||
@@ -10,7 +10,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class CreateFolderModal extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -18,39 +18,39 @@ class CreateFolderModal extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.refs.name.focus()
|
||||
this.refs.name.select()
|
||||
}
|
||||
|
||||
handleCloseButtonClick (e) {
|
||||
handleCloseButtonClick(e) {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
handleChange(e) {
|
||||
this.setState({
|
||||
name: this.refs.name.value
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
if (e.keyCode === 27) {
|
||||
this.props.close()
|
||||
}
|
||||
}
|
||||
|
||||
handleInputKeyDown (e) {
|
||||
handleInputKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 13:
|
||||
this.confirm()
|
||||
}
|
||||
}
|
||||
|
||||
handleConfirmButtonClick (e) {
|
||||
handleConfirmButtonClick(e) {
|
||||
this.confirm()
|
||||
}
|
||||
|
||||
confirm () {
|
||||
confirm() {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_FOLDER')
|
||||
if (this.state.name.trim().length > 0) {
|
||||
const { storage } = this.props
|
||||
@@ -59,42 +59,48 @@ class CreateFolderModal extends React.Component {
|
||||
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
||||
}
|
||||
|
||||
dataApi.createFolder(storage.key, input)
|
||||
.then((data) => {
|
||||
dataApi
|
||||
.createFolder(storage.key, input)
|
||||
.then(data => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_FOLDER',
|
||||
storage: data.storage
|
||||
})
|
||||
this.props.close()
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
<div styleName='root'
|
||||
<div
|
||||
styleName='root'
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
<div styleName='header'>
|
||||
<div styleName='title'>{i18n.__('Create new folder')}</div>
|
||||
</div>
|
||||
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
|
||||
<ModalEscButton
|
||||
handleEscButtonClick={e => this.handleCloseButtonClick(e)}
|
||||
/>
|
||||
<div styleName='control'>
|
||||
<div styleName='control-folder'>
|
||||
<div styleName='control-folder-label'>{i18n.__('Folder name')}</div>
|
||||
<input styleName='control-folder-input'
|
||||
<input
|
||||
styleName='control-folder-input'
|
||||
ref='name'
|
||||
value={this.state.name}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onKeyDown={(e) => this.handleInputKeyDown(e)}
|
||||
onChange={e => this.handleChange(e)}
|
||||
onKeyDown={e => this.handleInputKeyDown(e)}
|
||||
/>
|
||||
</div>
|
||||
<button styleName='control-confirmButton'
|
||||
onClick={(e) => this.handleConfirmButtonClick(e)}
|
||||
<button
|
||||
styleName='control-confirmButton'
|
||||
onClick={e => this.handleConfirmButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Create')}
|
||||
</button>
|
||||
|
||||
@@ -7,7 +7,7 @@ import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class CreateMarkdownFromURLModal extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -17,89 +17,101 @@ class CreateMarkdownFromURLModal extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.refs.name.focus()
|
||||
this.refs.name.select()
|
||||
}
|
||||
|
||||
handleCloseButtonClick (e) {
|
||||
handleCloseButtonClick(e) {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
handleChange(e) {
|
||||
this.setState({
|
||||
name: this.refs.name.value
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
if (e.keyCode === 27) {
|
||||
this.props.close()
|
||||
}
|
||||
}
|
||||
|
||||
handleInputKeyDown (e) {
|
||||
handleInputKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 13:
|
||||
this.confirm()
|
||||
}
|
||||
}
|
||||
|
||||
handleConfirmButtonClick (e) {
|
||||
handleConfirmButtonClick(e) {
|
||||
this.confirm()
|
||||
}
|
||||
|
||||
showError (message) {
|
||||
showError(message) {
|
||||
this.setState({
|
||||
showerror: true,
|
||||
errormessage: message
|
||||
})
|
||||
}
|
||||
|
||||
hideError () {
|
||||
hideError() {
|
||||
this.setState({
|
||||
showerror: false,
|
||||
errormessage: ''
|
||||
})
|
||||
}
|
||||
|
||||
confirm () {
|
||||
confirm() {
|
||||
this.hideError()
|
||||
const { storage, folder, dispatch, location } = this.props
|
||||
|
||||
dataApi.createNoteFromUrl(this.state.name, storage, folder, dispatch, location).then((result) => {
|
||||
this.props.close()
|
||||
}).catch((result) => {
|
||||
this.showError(result.error)
|
||||
})
|
||||
dataApi
|
||||
.createNoteFromUrl(this.state.name, storage, folder, dispatch, location)
|
||||
.then(result => {
|
||||
this.props.close()
|
||||
})
|
||||
.catch(result => {
|
||||
this.showError(result.error)
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
<div styleName='root'
|
||||
<div
|
||||
styleName='root'
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
<div styleName='header'>
|
||||
<div styleName='title'>{i18n.__('Import Markdown From URL')}</div>
|
||||
</div>
|
||||
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
|
||||
<ModalEscButton
|
||||
handleEscButtonClick={e => this.handleCloseButtonClick(e)}
|
||||
/>
|
||||
<div styleName='control'>
|
||||
<div styleName='control-folder'>
|
||||
<div styleName='control-folder-label'>{i18n.__('Insert URL Here')}</div>
|
||||
<input styleName='control-folder-input'
|
||||
<div styleName='control-folder-label'>
|
||||
{i18n.__('Insert URL Here')}
|
||||
</div>
|
||||
<input
|
||||
styleName='control-folder-input'
|
||||
ref='name'
|
||||
value={this.state.name}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onKeyDown={(e) => this.handleInputKeyDown(e)}
|
||||
onChange={e => this.handleChange(e)}
|
||||
onKeyDown={e => this.handleInputKeyDown(e)}
|
||||
/>
|
||||
</div>
|
||||
<button styleName='control-confirmButton'
|
||||
onClick={(e) => this.handleConfirmButtonClick(e)}
|
||||
<button
|
||||
styleName='control-confirmButton'
|
||||
onClick={e => this.handleConfirmButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Import')}
|
||||
</button>
|
||||
<div className='error' styleName='error'>{this.state.errormessage}</div>
|
||||
<div className='error' styleName='error'>
|
||||
{this.state.errormessage}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -9,21 +9,21 @@ import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
|
||||
import queryString from 'query-string'
|
||||
|
||||
class NewNoteModal extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.lock = false
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.refs.markdownButton.focus()
|
||||
}
|
||||
|
||||
handleCloseButtonClick (e) {
|
||||
handleCloseButtonClick(e) {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
handleCreateMarkdownFromUrlClick (e) {
|
||||
handleCreateMarkdownFromUrlClick(e) {
|
||||
this.props.close()
|
||||
|
||||
const { storage, folder, dispatch, location } = this.props
|
||||
@@ -35,49 +35,63 @@ class NewNoteModal extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleMarkdownNoteButtonClick (e) {
|
||||
handleMarkdownNoteButtonClick(e) {
|
||||
const { storage, folder, dispatch, location, config } = this.props
|
||||
const params = location.search !== '' && queryString.parse(location.search)
|
||||
if (!this.lock) {
|
||||
this.lock = true
|
||||
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||
createMarkdownNote(
|
||||
storage,
|
||||
folder,
|
||||
dispatch,
|
||||
location,
|
||||
params,
|
||||
config
|
||||
).then(() => {
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleMarkdownNoteButtonKeyDown (e) {
|
||||
handleMarkdownNoteButtonKeyDown(e) {
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault()
|
||||
this.refs.snippetButton.focus()
|
||||
}
|
||||
}
|
||||
|
||||
handleSnippetNoteButtonClick (e) {
|
||||
handleSnippetNoteButtonClick(e) {
|
||||
const { storage, folder, dispatch, location, config } = this.props
|
||||
const params = location.search !== '' && queryString.parse(location.search)
|
||||
if (!this.lock) {
|
||||
this.lock = true
|
||||
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||
createSnippetNote(
|
||||
storage,
|
||||
folder,
|
||||
dispatch,
|
||||
location,
|
||||
params,
|
||||
config
|
||||
).then(() => {
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleSnippetNoteButtonKeyDown (e) {
|
||||
handleSnippetNoteButtonKeyDown(e) {
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault()
|
||||
this.refs.markdownButton.focus()
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
if (e.keyCode === 27) {
|
||||
this.props.close()
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
styleName='root'
|
||||
@@ -116,7 +130,8 @@ class NewNoteModal extends React.Component {
|
||||
onKeyDown={e => this.handleSnippetNoteButtonKeyDown(e)}
|
||||
ref='snippetButton'
|
||||
>
|
||||
<i styleName='control-button-icon' className='fa fa-code' /><br />
|
||||
<i styleName='control-button-icon' className='fa fa-code' />
|
||||
<br />
|
||||
<span styleName='control-button-label'>
|
||||
{i18n.__('Snippet Note')}
|
||||
</span>
|
||||
@@ -127,10 +142,17 @@ class NewNoteModal extends React.Component {
|
||||
)}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div styleName='description'><i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}</div>
|
||||
<div styleName='from-url' onClick={(e) => this.handleCreateMarkdownFromUrlClick(e)}>Or, create a new markdown note from a URL</div>
|
||||
<div styleName='description'>
|
||||
<i className='fa fa-arrows-h' />
|
||||
{i18n.__('Tab to switch format')}
|
||||
</div>
|
||||
<div
|
||||
styleName='from-url'
|
||||
onClick={e => this.handleCreateMarkdownFromUrlClick(e)}
|
||||
>
|
||||
Or, create a new markdown note from a URL
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const electron = require('electron')
|
||||
const { shell } = electron
|
||||
const ipc = electron.ipcRenderer
|
||||
class Blog extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -20,12 +20,12 @@ class Blog extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
handleLinkClick(e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
clearMessage () {
|
||||
clearMessage() {
|
||||
_.debounce(() => {
|
||||
this.setState({
|
||||
BlogAlert: null
|
||||
@@ -33,30 +33,41 @@ class Blog extends React.Component {
|
||||
}, 2000)()
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.handleSettingDone = () => {
|
||||
this.setState({BlogAlert: {
|
||||
type: 'success',
|
||||
message: i18n.__('Successfully applied!')
|
||||
}})
|
||||
this.setState({
|
||||
BlogAlert: {
|
||||
type: 'success',
|
||||
message: i18n.__('Successfully applied!')
|
||||
}
|
||||
})
|
||||
}
|
||||
this.handleSettingError = (err) => {
|
||||
this.setState({BlogAlert: {
|
||||
type: 'error',
|
||||
message: err.message != null ? err.message : i18n.__('An error occurred!')
|
||||
}})
|
||||
this.handleSettingError = err => {
|
||||
this.setState({
|
||||
BlogAlert: {
|
||||
type: 'error',
|
||||
message:
|
||||
err.message != null ? err.message : i18n.__('An error occurred!')
|
||||
}
|
||||
})
|
||||
}
|
||||
this.oldBlog = this.state.config.blog
|
||||
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||
}
|
||||
|
||||
handleBlogChange (e) {
|
||||
handleBlogChange(e) {
|
||||
const { config } = this.state
|
||||
config.blog = {
|
||||
password: !_.isNil(this.refs.passwordInput) ? this.refs.passwordInput.value : config.blog.password,
|
||||
username: !_.isNil(this.refs.usernameInput) ? this.refs.usernameInput.value : config.blog.username,
|
||||
token: !_.isNil(this.refs.tokenInput) ? this.refs.tokenInput.value : config.blog.token,
|
||||
password: !_.isNil(this.refs.passwordInput)
|
||||
? this.refs.passwordInput.value
|
||||
: config.blog.password,
|
||||
username: !_.isNil(this.refs.usernameInput)
|
||||
? this.refs.usernameInput.value
|
||||
: config.blog.username,
|
||||
token: !_.isNil(this.refs.tokenInput)
|
||||
? this.refs.tokenInput.value
|
||||
: config.blog.token,
|
||||
authMethod: this.refs.authMethodDropdown.value,
|
||||
address: this.refs.addressInput.value,
|
||||
type: this.refs.typeDropdown.value
|
||||
@@ -75,7 +86,7 @@ class Blog extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleSaveButtonClick (e) {
|
||||
handleSaveButtonClick(e) {
|
||||
const newConfig = {
|
||||
blog: this.state.config.blog
|
||||
}
|
||||
@@ -90,36 +101,36 @@ class Blog extends React.Component {
|
||||
this.props.haveToSave()
|
||||
}
|
||||
|
||||
render () {
|
||||
const {config, BlogAlert} = this.state
|
||||
const blogAlertElement = BlogAlert != null
|
||||
? <p className={`alert ${BlogAlert.type}`}>
|
||||
{BlogAlert.message}
|
||||
</p>
|
||||
: null
|
||||
render() {
|
||||
const { config, BlogAlert } = this.state
|
||||
const blogAlertElement =
|
||||
BlogAlert != null ? (
|
||||
<p className={`alert ${BlogAlert.type}`}>{BlogAlert.message}</p>
|
||||
) : null
|
||||
return (
|
||||
<div styleName='root'>
|
||||
<div styleName='group'>
|
||||
<div styleName='group-header'>{i18n.__('Blog')}</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Blog Type')}
|
||||
</div>
|
||||
<div styleName='group-section-label'>{i18n.__('Blog Type')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<select
|
||||
value={config.blog.type}
|
||||
ref='typeDropdown'
|
||||
onChange={(e) => this.handleBlogChange(e)}
|
||||
onChange={e => this.handleBlogChange(e)}
|
||||
>
|
||||
<option value='wordpress' key='wordpress'>{i18n.__('wordpress')}</option>
|
||||
<option value='wordpress' key='wordpress'>
|
||||
{i18n.__('wordpress')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Blog Address')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleBlogChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleBlogChange(e)}
|
||||
ref='addressInput'
|
||||
value={config.blog.address}
|
||||
type='text'
|
||||
@@ -127,8 +138,11 @@ class Blog extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-control'>
|
||||
<button styleName='group-control-rightButton'
|
||||
onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}
|
||||
<button
|
||||
styleName='group-control-rightButton'
|
||||
onClick={e => this.handleSaveButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Save')}
|
||||
</button>
|
||||
{blogAlertElement}
|
||||
</div>
|
||||
@@ -143,49 +157,59 @@ class Blog extends React.Component {
|
||||
<select
|
||||
value={config.blog.authMethod}
|
||||
ref='authMethodDropdown'
|
||||
onChange={(e) => this.handleBlogChange(e)}
|
||||
onChange={e => this.handleBlogChange(e)}
|
||||
>
|
||||
<option value='JWT' key='JWT'>{i18n.__('JWT')}</option>
|
||||
<option value='USER' key='USER'>{i18n.__('USER')}</option>
|
||||
<option value='JWT' key='JWT'>
|
||||
{i18n.__('JWT')}
|
||||
</option>
|
||||
<option value='USER' key='USER'>
|
||||
{i18n.__('USER')}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{ config.blog.authMethod === 'JWT' &&
|
||||
{config.blog.authMethod === 'JWT' && (
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Token')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleBlogChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleBlogChange(e)}
|
||||
ref='tokenInput'
|
||||
value={config.blog.token}
|
||||
type='text' />
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
{ config.blog.authMethod === 'USER' &&
|
||||
)}
|
||||
{config.blog.authMethod === 'USER' && (
|
||||
<div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('UserName')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleBlogChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleBlogChange(e)}
|
||||
ref='usernameInput'
|
||||
value={config.blog.username}
|
||||
type='text' />
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Password')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleBlogChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleBlogChange(e)}
|
||||
ref='passwordInput'
|
||||
value={config.blog.password}
|
||||
type='password' />
|
||||
type='password'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,50 +7,93 @@ const electron = require('electron')
|
||||
const { shell } = electron
|
||||
|
||||
class Crowdfunding extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
}
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
handleLinkClick(e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
<div styleName='root'>
|
||||
<div styleName='group-header'>{i18n.__('Crowdfunding')}</div>
|
||||
<p>{i18n.__('Thank you for using Boostnote!')}</p>
|
||||
<br />
|
||||
<p>{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}</p>
|
||||
<p>{i18n.__('Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.')}</p>
|
||||
<div styleName='group-header2--sub'>{i18n.__('Sustainable Open Source Ecosystem')}</div>
|
||||
<p>{i18n.__('We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.')}</p>
|
||||
<p>{i18n.__('The original reason why we made IssueHunt was to reward our contributors of Boostnote project. We’ve got tons of Github stars and hundred of contributors in two years.')}</p>
|
||||
<p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p>
|
||||
<div styleName='group-header2--sub'>{i18n.__('We believe Meritocracy')}</div>
|
||||
<p>{i18n.__('We think developers who have skills and do great things must be rewarded properly.')}</p>
|
||||
<p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p>
|
||||
<p>
|
||||
{i18n.__(
|
||||
'We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{i18n.__(
|
||||
'Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.'
|
||||
)}
|
||||
</p>
|
||||
<div styleName='group-header2--sub'>
|
||||
{i18n.__('Sustainable Open Source Ecosystem')}
|
||||
</div>
|
||||
<p>
|
||||
{i18n.__(
|
||||
'We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{i18n.__(
|
||||
'The original reason why we made IssueHunt was to reward our contributors of Boostnote project. We’ve got tons of Github stars and hundred of contributors in two years.'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{i18n.__(
|
||||
'We thought that it will be nice if we can pay reward for our contributors.'
|
||||
)}
|
||||
</p>
|
||||
<div styleName='group-header2--sub'>
|
||||
{i18n.__('We believe Meritocracy')}
|
||||
</div>
|
||||
<p>
|
||||
{i18n.__(
|
||||
'We think developers who have skills and do great things must be rewarded properly.'
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{i18n.__(
|
||||
'OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.'
|
||||
)}
|
||||
</p>
|
||||
<p>{i18n.__('It sometimes looks like exploitation.')}</p>
|
||||
<p>{i18n.__('We’ve realized IssueHunt could enhance sustainability of open-source ecosystem.')}</p>
|
||||
<p>
|
||||
{i18n.__(
|
||||
'We’ve realized IssueHunt could enhance sustainability of open-source ecosystem.'
|
||||
)}
|
||||
</p>
|
||||
<br />
|
||||
<p>{i18n.__('As same as issues of Boostnote are already funded on IssueHunt, your open-source projects can be also started funding from now.')}</p>
|
||||
<p>
|
||||
{i18n.__(
|
||||
'As same as issues of Boostnote are already funded on IssueHunt, your open-source projects can be also started funding from now.'
|
||||
)}
|
||||
</p>
|
||||
<br />
|
||||
<p>{i18n.__('Thank you,')}</p>
|
||||
<p>{i18n.__('The Boostnote Team')}</p>
|
||||
<br />
|
||||
<button styleName='cf-link'>
|
||||
<a href='http://bit.ly/issuehunt-from-boostnote-app' onClick={(e) => this.handleLinkClick(e)}>{i18n.__('See IssueHunt')}</a>
|
||||
<a
|
||||
href='http://bit.ly/issuehunt-from-boostnote-app'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
{i18n.__('See IssueHunt')}
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Crowdfunding.propTypes = {
|
||||
}
|
||||
Crowdfunding.propTypes = {}
|
||||
|
||||
export default CSSModules(Crowdfunding, styles)
|
||||
|
||||
@@ -10,7 +10,7 @@ import { SortableElement, SortableHandle } from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class FolderItem extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -24,7 +24,7 @@ class FolderItem extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleEditChange (e) {
|
||||
handleEditChange(e) {
|
||||
const { folder } = this.state
|
||||
|
||||
folder.name = this.refs.nameInput.value
|
||||
@@ -33,18 +33,18 @@ class FolderItem extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleConfirmButtonClick (e) {
|
||||
handleConfirmButtonClick(e) {
|
||||
this.confirm()
|
||||
}
|
||||
|
||||
confirm () {
|
||||
confirm() {
|
||||
const { storage, folder } = this.props
|
||||
dataApi
|
||||
.updateFolder(storage.key, folder.key, {
|
||||
color: this.state.folder.color,
|
||||
name: this.state.folder.name
|
||||
})
|
||||
.then((data) => {
|
||||
.then(data => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_FOLDER',
|
||||
storage: data.storage
|
||||
@@ -55,9 +55,12 @@ class FolderItem extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleColorButtonClick (e) {
|
||||
const folder = Object.assign({}, this.state.folder, { showColumnPicker: true, colorPickerPos: { left: 0, top: 0 } })
|
||||
this.setState({ folder }, function () {
|
||||
handleColorButtonClick(e) {
|
||||
const folder = Object.assign({}, this.state.folder, {
|
||||
showColumnPicker: true,
|
||||
colorPickerPos: { left: 0, top: 0 }
|
||||
})
|
||||
this.setState({ folder }, function() {
|
||||
// After the color picker has been painted, re-calculate its position
|
||||
// by comparing its dimensions to the host dimensions.
|
||||
const { hostBoundingBox } = this.props
|
||||
@@ -67,30 +70,32 @@ class FolderItem extends React.Component {
|
||||
const folder = Object.assign({}, this.state.folder, {
|
||||
colorPickerPos: {
|
||||
left: 25,
|
||||
top: offsetTop < 0 ? offsetTop - 5 : 0 // subtract 5px for aestetics
|
||||
top: offsetTop < 0 ? offsetTop - 5 : 0 // subtract 5px for aestetics
|
||||
}
|
||||
})
|
||||
this.setState({ folder })
|
||||
})
|
||||
}
|
||||
|
||||
handleColorChange (color) {
|
||||
handleColorChange(color) {
|
||||
const folder = Object.assign({}, this.state.folder, { color: color.hex })
|
||||
this.setState({ folder })
|
||||
}
|
||||
|
||||
handleColorPickerClose (event) {
|
||||
const folder = Object.assign({}, this.state.folder, { showColumnPicker: false })
|
||||
handleColorPickerClose(event) {
|
||||
const folder = Object.assign({}, this.state.folder, {
|
||||
showColumnPicker: false
|
||||
})
|
||||
this.setState({ folder })
|
||||
}
|
||||
|
||||
handleCancelButtonClick (e) {
|
||||
handleCancelButtonClick(e) {
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
})
|
||||
}
|
||||
|
||||
handleFolderItemBlur (e) {
|
||||
handleFolderItemBlur(e) {
|
||||
let el = e.relatedTarget
|
||||
while (el != null) {
|
||||
if (el === this.refs.root) {
|
||||
@@ -101,7 +106,7 @@ class FolderItem extends React.Component {
|
||||
this.confirm()
|
||||
}
|
||||
|
||||
renderEdit (e) {
|
||||
renderEdit(e) {
|
||||
const popover = { position: 'absolute', zIndex: 2 }
|
||||
const cover = {
|
||||
position: 'fixed',
|
||||
@@ -110,51 +115,64 @@ class FolderItem extends React.Component {
|
||||
bottom: 0,
|
||||
left: 0
|
||||
}
|
||||
const pickerStyle = Object.assign({}, {
|
||||
position: 'absolute'
|
||||
}, this.state.folder.colorPickerPos)
|
||||
const pickerStyle = Object.assign(
|
||||
{},
|
||||
{
|
||||
position: 'absolute'
|
||||
},
|
||||
this.state.folder.colorPickerPos
|
||||
)
|
||||
return (
|
||||
<div styleName='folderItem'
|
||||
onBlur={(e) => this.handleFolderItemBlur(e)}
|
||||
<div
|
||||
styleName='folderItem'
|
||||
onBlur={e => this.handleFolderItemBlur(e)}
|
||||
tabIndex='-1'
|
||||
ref='root'
|
||||
>
|
||||
<div styleName='folderItem-left'>
|
||||
<button styleName='folderItem-left-colorButton' style={{color: this.state.folder.color}}
|
||||
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}
|
||||
<button
|
||||
styleName='folderItem-left-colorButton'
|
||||
style={{ color: this.state.folder.color }}
|
||||
onClick={e =>
|
||||
!this.state.folder.showColumnPicker &&
|
||||
this.handleColorButtonClick(e)
|
||||
}
|
||||
>
|
||||
{this.state.folder.showColumnPicker
|
||||
? <div style={popover}>
|
||||
<div style={cover}
|
||||
{this.state.folder.showColumnPicker ? (
|
||||
<div style={popover}>
|
||||
<div
|
||||
style={cover}
|
||||
onClick={() => this.handleColorPickerClose()}
|
||||
/>
|
||||
<div style={pickerStyle}>
|
||||
<SketchPicker
|
||||
ref='colorPicker'
|
||||
color={this.state.folder.color}
|
||||
onChange={(color) => this.handleColorChange(color)}
|
||||
onChangeComplete={(color) => this.handleColorChange(color)}
|
||||
onChange={color => this.handleColorChange(color)}
|
||||
onChangeComplete={color => this.handleColorChange(color)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
) : null}
|
||||
<i className='fa fa-square' />
|
||||
</button>
|
||||
<input styleName='folderItem-left-nameInput'
|
||||
<input
|
||||
styleName='folderItem-left-nameInput'
|
||||
value={this.state.folder.name}
|
||||
ref='nameInput'
|
||||
onChange={(e) => this.handleEditChange(e)}
|
||||
onChange={e => this.handleEditChange(e)}
|
||||
/>
|
||||
</div>
|
||||
<div styleName='folderItem-right'>
|
||||
<button styleName='folderItem-right-confirmButton'
|
||||
onClick={(e) => this.handleConfirmButtonClick(e)}
|
||||
<button
|
||||
styleName='folderItem-right-confirmButton'
|
||||
onClick={e => this.handleConfirmButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Confirm')}
|
||||
</button>
|
||||
<button styleName='folderItem-right-button'
|
||||
onClick={(e) => this.handleCancelButtonClick(e)}
|
||||
<button
|
||||
styleName='folderItem-right-button'
|
||||
onClick={e => this.handleCancelButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Cancel')}
|
||||
</button>
|
||||
@@ -163,79 +181,85 @@ class FolderItem extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
handleDeleteConfirmButtonClick (e) {
|
||||
handleDeleteConfirmButtonClick(e) {
|
||||
const { storage, folder } = this.props
|
||||
dataApi
|
||||
.deleteFolder(storage.key, folder.key)
|
||||
.then((data) => {
|
||||
store.dispatch({
|
||||
type: 'DELETE_FOLDER',
|
||||
storage: data.storage,
|
||||
folderKey: data.folderKey
|
||||
})
|
||||
dataApi.deleteFolder(storage.key, folder.key).then(data => {
|
||||
store.dispatch({
|
||||
type: 'DELETE_FOLDER',
|
||||
storage: data.storage,
|
||||
folderKey: data.folderKey
|
||||
})
|
||||
}
|
||||
|
||||
renderDelete () {
|
||||
return (
|
||||
<div styleName='folderItem'>
|
||||
<div styleName='folderItem-left'>
|
||||
{i18n.__('Are you sure to ')} <span styleName='folderItem-left-danger'>{i18n.__(' delete')}</span> {i18n.__('this folder?')}
|
||||
</div>
|
||||
<div styleName='folderItem-right'>
|
||||
<button styleName='folderItem-right-dangerButton'
|
||||
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Confirm')}
|
||||
</button>
|
||||
<button styleName='folderItem-right-button'
|
||||
onClick={(e) => this.handleCancelButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
handleEditButtonClick (e) {
|
||||
const { folder: propsFolder } = this.props
|
||||
const { folder: stateFolder } = this.state
|
||||
const folder = Object.assign({}, stateFolder, propsFolder)
|
||||
this.setState({
|
||||
status: 'EDIT',
|
||||
folder
|
||||
}, () => {
|
||||
this.refs.nameInput.select()
|
||||
})
|
||||
}
|
||||
|
||||
handleDeleteButtonClick (e) {
|
||||
renderDelete() {
|
||||
return (
|
||||
<div styleName='folderItem'>
|
||||
<div styleName='folderItem-left'>
|
||||
{i18n.__('Are you sure to ')}{' '}
|
||||
<span styleName='folderItem-left-danger'>{i18n.__(' delete')}</span>{' '}
|
||||
{i18n.__('this folder?')}
|
||||
</div>
|
||||
<div styleName='folderItem-right'>
|
||||
<button
|
||||
styleName='folderItem-right-dangerButton'
|
||||
onClick={e => this.handleDeleteConfirmButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Confirm')}
|
||||
</button>
|
||||
<button
|
||||
styleName='folderItem-right-button'
|
||||
onClick={e => this.handleCancelButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
handleEditButtonClick(e) {
|
||||
const { folder: propsFolder } = this.props
|
||||
const { folder: stateFolder } = this.state
|
||||
const folder = Object.assign({}, stateFolder, propsFolder)
|
||||
this.setState(
|
||||
{
|
||||
status: 'EDIT',
|
||||
folder
|
||||
},
|
||||
() => {
|
||||
this.refs.nameInput.select()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleDeleteButtonClick(e) {
|
||||
this.setState({
|
||||
status: 'DELETE'
|
||||
})
|
||||
}
|
||||
|
||||
renderIdle () {
|
||||
renderIdle() {
|
||||
const { folder } = this.props
|
||||
return (
|
||||
<div styleName='folderItem'
|
||||
onDoubleClick={(e) => this.handleEditButtonClick(e)}
|
||||
<div
|
||||
styleName='folderItem'
|
||||
onDoubleClick={e => this.handleEditButtonClick(e)}
|
||||
>
|
||||
<div styleName='folderItem-left'
|
||||
style={{borderColor: folder.color}}
|
||||
>
|
||||
<div styleName='folderItem-left' style={{ borderColor: folder.color }}>
|
||||
<span>{folder.name}</span>
|
||||
<span styleName='folderItem-left-key'>({folder.key})</span>
|
||||
</div>
|
||||
<div styleName='folderItem-right'>
|
||||
<button styleName='folderItem-right-button'
|
||||
onClick={(e) => this.handleEditButtonClick(e)}
|
||||
<button
|
||||
styleName='folderItem-right-button'
|
||||
onClick={e => this.handleEditButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Edit')}
|
||||
</button>
|
||||
<button styleName='folderItem-right-button'
|
||||
onClick={(e) => this.handleDeleteButtonClick(e)}
|
||||
<button
|
||||
styleName='folderItem-right-button'
|
||||
onClick={e => this.handleDeleteButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Delete')}
|
||||
</button>
|
||||
@@ -244,7 +268,7 @@ class FolderItem extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
switch (this.state.status) {
|
||||
case 'DELETE':
|
||||
return this.renderDelete()
|
||||
@@ -277,7 +301,7 @@ FolderItem.propTypes = {
|
||||
}
|
||||
|
||||
class Handle extends React.Component {
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
<div styleName='folderItem-drag-handle'>
|
||||
<i className='fa fa-reorder' />
|
||||
@@ -287,7 +311,7 @@ class Handle extends React.Component {
|
||||
}
|
||||
|
||||
class SortableFolderItemComponent extends React.Component {
|
||||
render () {
|
||||
render() {
|
||||
const StyledHandle = CSSModules(Handle, styles)
|
||||
const DragHandle = SortableHandle(StyledHandle)
|
||||
|
||||
|
||||
@@ -9,24 +9,28 @@ import { SortableContainer } from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class FolderList extends React.Component {
|
||||
render () {
|
||||
render() {
|
||||
const { storage, hostBoundingBox } = this.props
|
||||
|
||||
const folderList = storage.folders.map((folder, index) => {
|
||||
return <FolderItem key={folder.key}
|
||||
folder={folder}
|
||||
storage={storage}
|
||||
index={index}
|
||||
hostBoundingBox={hostBoundingBox}
|
||||
/>
|
||||
return (
|
||||
<FolderItem
|
||||
key={folder.key}
|
||||
folder={folder}
|
||||
storage={storage}
|
||||
index={index}
|
||||
hostBoundingBox={hostBoundingBox}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{folderList.length > 0
|
||||
? folderList
|
||||
: <div styleName='folderList-empty'>{i18n.__('No Folders')}</div>
|
||||
}
|
||||
{folderList.length > 0 ? (
|
||||
folderList
|
||||
) : (
|
||||
<div styleName='folderList-empty'>{i18n.__('No Folders')}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -52,23 +56,21 @@ FolderList.propTypes = {
|
||||
}
|
||||
|
||||
class SortableFolderListComponent extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.onSortEnd = ({oldIndex, newIndex}) => {
|
||||
this.onSortEnd = ({ oldIndex, newIndex }) => {
|
||||
const { storage } = this.props
|
||||
dataApi
|
||||
.reorderFolder(storage.key, oldIndex, newIndex)
|
||||
.then((data) => {
|
||||
store.dispatch({
|
||||
type: 'REORDER_FOLDER',
|
||||
storage: data.storage
|
||||
})
|
||||
this.setState()
|
||||
dataApi.reorderFolder(storage.key, oldIndex, newIndex).then(data => {
|
||||
store.dispatch({
|
||||
type: 'REORDER_FOLDER',
|
||||
storage: data.storage
|
||||
})
|
||||
this.setState()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const StyledFolderList = CSSModules(FolderList, this.props.styles)
|
||||
const SortableFolderList = SortableContainer(StyledFolderList)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
|
||||
class HotkeyTab extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -20,28 +20,35 @@ class HotkeyTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.handleSettingDone = () => {
|
||||
this.setState({keymapAlert: {
|
||||
type: 'success',
|
||||
message: i18n.__('Successfully applied!')
|
||||
}})
|
||||
this.setState({
|
||||
keymapAlert: {
|
||||
type: 'success',
|
||||
message: i18n.__('Successfully applied!')
|
||||
}
|
||||
})
|
||||
}
|
||||
this.handleSettingError = (err) => {
|
||||
this.handleSettingError = err => {
|
||||
if (
|
||||
this.state.config.hotkey.toggleMain === '' ||
|
||||
this.state.config.hotkey.toggleMode === '' ||
|
||||
this.state.config.hotkey.toggleDirection === ''
|
||||
) {
|
||||
this.setState({keymapAlert: {
|
||||
type: 'success',
|
||||
message: i18n.__('Successfully applied!')
|
||||
}})
|
||||
this.setState({
|
||||
keymapAlert: {
|
||||
type: 'success',
|
||||
message: i18n.__('Successfully applied!')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.setState({keymapAlert: {
|
||||
type: 'error',
|
||||
message: err.message != null ? err.message : i18n.__('An error occurred!')
|
||||
}})
|
||||
this.setState({
|
||||
keymapAlert: {
|
||||
type: 'error',
|
||||
message:
|
||||
err.message != null ? err.message : i18n.__('An error occurred!')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
this.oldHotkey = this.state.config.hotkey
|
||||
@@ -49,12 +56,12 @@ class HotkeyTab extends React.Component {
|
||||
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||
ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||
}
|
||||
|
||||
handleSaveButtonClick (e) {
|
||||
handleSaveButtonClick(e) {
|
||||
const newConfig = {
|
||||
hotkey: this.state.config.hotkey
|
||||
}
|
||||
@@ -69,13 +76,13 @@ class HotkeyTab extends React.Component {
|
||||
this.props.haveToSave()
|
||||
}
|
||||
|
||||
handleHintToggleButtonClick (e) {
|
||||
handleHintToggleButtonClick(e) {
|
||||
this.setState({
|
||||
isHotkeyHintOpen: !this.state.isHotkeyHintOpen
|
||||
})
|
||||
}
|
||||
|
||||
handleHotkeyChange (e) {
|
||||
handleHotkeyChange(e) {
|
||||
const { config } = this.state
|
||||
config.hotkey = Object.assign({}, config.hotkey, {
|
||||
toggleMain: this.refs.toggleMain.value,
|
||||
@@ -102,7 +109,7 @@ class HotkeyTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
clearMessage () {
|
||||
clearMessage() {
|
||||
_.debounce(() => {
|
||||
this.setState({
|
||||
keymapAlert: null
|
||||
@@ -110,13 +117,12 @@ class HotkeyTab extends React.Component {
|
||||
}, 2000)()
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const keymapAlert = this.state.keymapAlert
|
||||
const keymapAlertElement = keymapAlert != null
|
||||
? <p className={`alert ${keymapAlert.type}`}>
|
||||
{keymapAlert.message}
|
||||
</p>
|
||||
: null
|
||||
const keymapAlertElement =
|
||||
keymapAlert != null ? (
|
||||
<p className={`alert ${keymapAlert.type}`}>{keymapAlert.message}</p>
|
||||
) : null
|
||||
const { config } = this.state
|
||||
|
||||
return (
|
||||
@@ -124,10 +130,13 @@ class HotkeyTab extends React.Component {
|
||||
<div styleName='group'>
|
||||
<div styleName='group-header'>{i18n.__('Hotkeys')}</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Show/Hide Boostnote')}</div>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Show/Hide Boostnote')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleHotkeyChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleHotkeyChange(e)}
|
||||
ref='toggleMain'
|
||||
value={config.hotkey.toggleMain}
|
||||
type='text'
|
||||
@@ -135,10 +144,13 @@ class HotkeyTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Show/Hide Menu Bar')}</div>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Show/Hide Menu Bar')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleHotkeyChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleHotkeyChange(e)}
|
||||
ref='toggleMenuBar'
|
||||
value={config.hotkey.toggleMenuBar}
|
||||
type='text'
|
||||
@@ -146,10 +158,13 @@ class HotkeyTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Toggle Editor Mode')}</div>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Toggle Editor Mode')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleHotkeyChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleHotkeyChange(e)}
|
||||
ref='toggleMode'
|
||||
value={config.hotkey.toggleMode}
|
||||
type='text'
|
||||
@@ -157,10 +172,13 @@ class HotkeyTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Toggle Direction')}</div>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Toggle Direction')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleHotkeyChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleHotkeyChange(e)}
|
||||
ref='toggleDirection'
|
||||
value={config.hotkey.toggleDirection}
|
||||
type='text'
|
||||
@@ -170,8 +188,9 @@ class HotkeyTab extends React.Component {
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Delete Note')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleHotkeyChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleHotkeyChange(e)}
|
||||
ref='deleteNote'
|
||||
value={config.hotkey.deleteNote}
|
||||
type='text'
|
||||
@@ -181,8 +200,9 @@ class HotkeyTab extends React.Component {
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Paste HTML')}</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleHotkeyChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleHotkeyChange(e)}
|
||||
ref='pasteSmartly'
|
||||
value={config.hotkey.pasteSmartly}
|
||||
type='text'
|
||||
@@ -190,19 +210,26 @@ class HotkeyTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Prettify Markdown')}</div>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Prettify Markdown')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleHotkeyChange(e)}
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
onChange={e => this.handleHotkeyChange(e)}
|
||||
ref='prettifyMarkdown'
|
||||
value={config.hotkey.prettifyMarkdown}
|
||||
type='text' />
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Insert Current Date')}</div>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Insert Current Date')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
ref='insertDate'
|
||||
value={config.hotkey.insertDate}
|
||||
type='text'
|
||||
@@ -211,9 +238,12 @@ class HotkeyTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Insert Current Date and Time')}</div>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Insert Current Date and Time')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
ref='insertDateTime'
|
||||
value={config.hotkey.insertDateTime}
|
||||
type='text'
|
||||
@@ -222,44 +252,87 @@ class HotkeyTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-control'>
|
||||
<button styleName='group-control-leftButton'
|
||||
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
||||
<button
|
||||
styleName='group-control-leftButton'
|
||||
onClick={e => this.handleHintToggleButtonClick(e)}
|
||||
>
|
||||
{this.state.isHotkeyHintOpen
|
||||
? i18n.__('Hide Help')
|
||||
: i18n.__('Help')
|
||||
}
|
||||
: i18n.__('Help')}
|
||||
</button>
|
||||
<button styleName='group-control-rightButton'
|
||||
onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}
|
||||
<button
|
||||
styleName='group-control-rightButton'
|
||||
onClick={e => this.handleSaveButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Save')}
|
||||
</button>
|
||||
{keymapAlertElement}
|
||||
</div>
|
||||
{this.state.isHotkeyHintOpen &&
|
||||
{this.state.isHotkeyHintOpen && (
|
||||
<div styleName='group-hint'>
|
||||
<p>{i18n.__('Available Keys')}</p>
|
||||
<ul>
|
||||
<li><code>0</code> to <code>9</code></li>
|
||||
<li><code>A</code> to <code>Z</code></li>
|
||||
<li><code>F1</code> to <code>F24</code></li>
|
||||
<li>Punctuations like <code>~</code>, <code>!</code>, <code>@</code>, <code>#</code>, <code>$</code>, etc.</li>
|
||||
<li><code>Plus</code></li>
|
||||
<li><code>Space</code></li>
|
||||
<li><code>Backspace</code></li>
|
||||
<li><code>Delete</code></li>
|
||||
<li><code>Insert</code></li>
|
||||
<li><code>Return</code> (or <code>Enter</code> as alias)</li>
|
||||
<li><code>Up</code>, <code>Down</code>, <code>Left</code> and <code>Right</code></li>
|
||||
<li><code>Home</code> and <code>End</code></li>
|
||||
<li><code>PageUp</code> and <code>PageDown</code></li>
|
||||
<li><code>Escape</code> (or <code>Esc</code> for short)</li>
|
||||
<li><code>VolumeUp</code>, <code>VolumeDown</code> and <code>VolumeMute</code></li>
|
||||
<li><code>MediaNextTrack</code>, <code>MediaPreviousTrack</code>, <code>MediaStop</code> and <code>MediaPlayPause</code></li>
|
||||
<li><code>Control</code> (or <code>Ctrl</code> for short)</li>
|
||||
<li><code>Shift</code></li>
|
||||
<li>
|
||||
<code>0</code> to <code>9</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>A</code> to <code>Z</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>F1</code> to <code>F24</code>
|
||||
</li>
|
||||
<li>
|
||||
Punctuations like <code>~</code>, <code>!</code>,{' '}
|
||||
<code>@</code>, <code>#</code>, <code>$</code>, etc.
|
||||
</li>
|
||||
<li>
|
||||
<code>Plus</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>Space</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>Backspace</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>Delete</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>Insert</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>Return</code> (or <code>Enter</code> as alias)
|
||||
</li>
|
||||
<li>
|
||||
<code>Up</code>, <code>Down</code>, <code>Left</code> and{' '}
|
||||
<code>Right</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>Home</code> and <code>End</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>PageUp</code> and <code>PageDown</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>Escape</code> (or <code>Esc</code> for short)
|
||||
</li>
|
||||
<li>
|
||||
<code>VolumeUp</code>, <code>VolumeDown</code> and{' '}
|
||||
<code>VolumeMute</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>MediaNextTrack</code>, <code>MediaPreviousTrack</code>,{' '}
|
||||
<code>MediaStop</code> and <code>MediaPlayPause</code>
|
||||
</li>
|
||||
<li>
|
||||
<code>Control</code> (or <code>Ctrl</code> for short)
|
||||
</li>
|
||||
<li>
|
||||
<code>Shift</code>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ const { shell, remote } = electron
|
||||
const appVersion = remote.app.getVersion()
|
||||
|
||||
class InfoTab extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -20,18 +20,18 @@ class InfoTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
handleLinkClick(e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
handleConfigChange (e) {
|
||||
handleConfigChange(e) {
|
||||
const newConfig = { amaEnabled: this.refs.amaEnabled.checked }
|
||||
|
||||
this.setState({ config: newConfig })
|
||||
}
|
||||
|
||||
handleSaveButtonClick (e) {
|
||||
handleSaveButtonClick(e) {
|
||||
const newConfig = {
|
||||
amaEnabled: this.state.config.amaEnabled
|
||||
}
|
||||
@@ -43,7 +43,7 @@ class InfoTab extends React.Component {
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
amaMessage: i18n.__('Thank\'s for trusting us')
|
||||
amaMessage: i18n.__("Thank's for trusting us")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class InfoTab extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
toggleAutoUpdate () {
|
||||
toggleAutoUpdate() {
|
||||
const newConfig = {
|
||||
autoUpdateEnabled: !this.state.config.autoUpdateEnabled
|
||||
}
|
||||
@@ -70,46 +70,64 @@ class InfoTab extends React.Component {
|
||||
ConfigManager.set(newConfig)
|
||||
}
|
||||
|
||||
infoMessage () {
|
||||
infoMessage() {
|
||||
const { amaMessage } = this.state
|
||||
return amaMessage ? <p styleName='policy-confirm'>{amaMessage}</p> : null
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
<div styleName='root'>
|
||||
<div styleName='group-header'>{i18n.__('Community')}</div>
|
||||
<div styleName='top'>
|
||||
<ul styleName='list'>
|
||||
<li>
|
||||
<a href='https://issuehunt.io/repos/53266139'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('Bounty on IssueHunt')}</a>
|
||||
<a
|
||||
href='https://issuehunt.io/repos/53266139'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
{i18n.__('Bounty on IssueHunt')}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://boostnote.io/#subscribe'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('Subscribe to Newsletter')}</a>
|
||||
<a
|
||||
href='https://boostnote.io/#subscribe'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
{i18n.__('Subscribe to Newsletter')}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://github.com/BoostIO/Boostnote/issues'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('GitHub')}</a>
|
||||
<a
|
||||
href='https://github.com/BoostIO/Boostnote/issues'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
{i18n.__('GitHub')}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://medium.com/boostnote'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('Blog')}</a>
|
||||
<a
|
||||
href='https://medium.com/boostnote'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
{i18n.__('Blog')}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://www.facebook.com/groups/boostnote'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('Facebook Group')}</a>
|
||||
<a
|
||||
href='https://www.facebook.com/groups/boostnote'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
{i18n.__('Facebook Group')}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://twitter.com/boostnoteapp'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('Twitter')}</a>
|
||||
<a
|
||||
href='https://twitter.com/boostnoteapp'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
{i18n.__('Twitter')}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -120,11 +138,20 @@ class InfoTab extends React.Component {
|
||||
|
||||
<div styleName='top'>
|
||||
<div styleName='icon-space'>
|
||||
<img styleName='icon' src='../resources/app.png' width='92' height='92' />
|
||||
<img
|
||||
styleName='icon'
|
||||
src='../resources/app.png'
|
||||
width='92'
|
||||
height='92'
|
||||
/>
|
||||
<div styleName='icon-right'>
|
||||
<div styleName='appId'>{i18n.__('Boostnote')} {appVersion}</div>
|
||||
<div styleName='appId'>
|
||||
{i18n.__('Boostnote')} {appVersion}
|
||||
</div>
|
||||
<div styleName='description'>
|
||||
{i18n.__('An open source note-taking app made for programmers just like you.')}
|
||||
{i18n.__(
|
||||
'An open source note-taking app made for programmers just like you.'
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,39 +159,71 @@ class InfoTab extends React.Component {
|
||||
|
||||
<ul styleName='list'>
|
||||
<li>
|
||||
<a href='https://boostnote.io'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('Website')}</a>
|
||||
<a
|
||||
href='https://boostnote.io'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
{i18n.__('Website')}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://github.com/BoostIO/Boostnote/blob/master/docs/build.md'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('Development')}</a>{i18n.__(' : Development configurations for Boostnote.')}
|
||||
</li>
|
||||
<li styleName='cc'>
|
||||
{i18n.__('Copyright (C) 2017 - 2020 BoostIO')}
|
||||
</li>
|
||||
<li styleName='cc'>
|
||||
{i18n.__('License: GPL v3')}
|
||||
<a
|
||||
href='https://github.com/BoostIO/Boostnote/blob/master/docs/build.md'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
{i18n.__('Development')}
|
||||
</a>
|
||||
{i18n.__(' : Development configurations for Boostnote.')}
|
||||
</li>
|
||||
<li styleName='cc'>{i18n.__('Copyright (C) 2017 - 2020 BoostIO')}</li>
|
||||
<li styleName='cc'>{i18n.__('License: GPL v3')}</li>
|
||||
</ul>
|
||||
|
||||
<div><label><input type='checkbox' onChange={this.toggleAutoUpdate.bind(this)} checked={this.state.config.autoUpdateEnabled} />{i18n.__('Enable Auto Update')}</label></div>
|
||||
<div>
|
||||
<label>
|
||||
<input
|
||||
type='checkbox'
|
||||
onChange={this.toggleAutoUpdate.bind(this)}
|
||||
checked={this.state.config.autoUpdateEnabled}
|
||||
/>
|
||||
{i18n.__('Enable Auto Update')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<hr styleName='separate-line' />
|
||||
|
||||
<div styleName='group-header2--sub'>{i18n.__('Analytics')}</div>
|
||||
<div>{i18n.__('Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.')}</div>
|
||||
<div>{i18n.__('You can see how it works on ')}<a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
|
||||
<div>
|
||||
{i18n.__(
|
||||
'Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.'
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
{i18n.__('You can see how it works on ')}
|
||||
<a
|
||||
href='https://github.com/BoostIO/Boostnote'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
GitHub
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
<br />
|
||||
<div>{i18n.__('You can choose to enable or disable this option.')}</div>
|
||||
<input onChange={(e) => this.handleConfigChange(e)}
|
||||
<input
|
||||
onChange={e => this.handleConfigChange(e)}
|
||||
checked={this.state.config.amaEnabled}
|
||||
ref='amaEnabled'
|
||||
type='checkbox'
|
||||
/>
|
||||
{i18n.__('Enable analytics to help improve Boostnote')}<br />
|
||||
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}</button>
|
||||
{i18n.__('Enable analytics to help improve Boostnote')}
|
||||
<br />
|
||||
<button
|
||||
styleName='policy-submit'
|
||||
onClick={e => this.handleSaveButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Save')}
|
||||
</button>
|
||||
<br />
|
||||
{this.infoMessage()}
|
||||
</div>
|
||||
@@ -172,7 +231,6 @@ class InfoTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
InfoTab.propTypes = {
|
||||
}
|
||||
InfoTab.propTypes = {}
|
||||
|
||||
export default CSSModules(InfoTab, styles)
|
||||
|
||||
@@ -6,13 +6,19 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import snippetManager from '../../../lib/SnippetManager'
|
||||
|
||||
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||
const defaultEditorFontFamily = [
|
||||
'Monaco',
|
||||
'Menlo',
|
||||
'Ubuntu Mono',
|
||||
'Consolas',
|
||||
'source-code-pro',
|
||||
'monospace'
|
||||
]
|
||||
const buildCMRulers = (rulers, enableRulers) =>
|
||||
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
|
||||
|
||||
class SnippetEditor extends React.Component {
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.props.onRef(this)
|
||||
const { rulers, enableRulers } = this.props
|
||||
this.cm = CodeMirror(this.refs.root, {
|
||||
@@ -49,38 +55,50 @@ class SnippetEditor extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
this.props.onRef(undefined)
|
||||
}
|
||||
|
||||
onSnippetChanged (newSnippet) {
|
||||
onSnippetChanged(newSnippet) {
|
||||
this.snippet = newSnippet
|
||||
this.cm.setValue(this.snippet.content)
|
||||
}
|
||||
|
||||
onSnippetNameOrPrefixChanged (newSnippet) {
|
||||
onSnippetNameOrPrefixChanged(newSnippet) {
|
||||
this.snippet.name = newSnippet.name
|
||||
this.snippet.prefix = newSnippet.prefix.toString().replace(/\s/g, '').split(',')
|
||||
this.snippet.prefix = newSnippet.prefix
|
||||
.toString()
|
||||
.replace(/\s/g, '')
|
||||
.split(',')
|
||||
this.saveSnippet()
|
||||
}
|
||||
|
||||
saveSnippet () {
|
||||
dataApi.updateSnippet(this.snippet)
|
||||
saveSnippet() {
|
||||
dataApi
|
||||
.updateSnippet(this.snippet)
|
||||
.then(snippets => snippetManager.assignSnippets(snippets))
|
||||
.catch((err) => { throw err })
|
||||
.catch(err => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { fontSize } = this.props
|
||||
let fontFamily = this.props.fontFamily
|
||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||
? [fontFamily].concat(defaultEditorFontFamily)
|
||||
: defaultEditorFontFamily
|
||||
fontFamily =
|
||||
_.isString(fontFamily) && fontFamily.length > 0
|
||||
? [fontFamily].concat(defaultEditorFontFamily)
|
||||
: defaultEditorFontFamily
|
||||
return (
|
||||
<div styleName='SnippetEditor' ref='root' tabIndex='-1' style={{
|
||||
fontFamily: fontFamily.join(', '),
|
||||
fontSize: fontSize
|
||||
}} />
|
||||
<div
|
||||
styleName='SnippetEditor'
|
||||
ref='root'
|
||||
tabIndex='-1'
|
||||
style={{
|
||||
fontFamily: fontFamily.join(', '),
|
||||
fontSize: fontSize
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,53 +7,65 @@ import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import context from 'browser/lib/context'
|
||||
|
||||
class SnippetList extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
snippets: []
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.reloadSnippetList()
|
||||
eventEmitter.on('snippetList:reload', this.reloadSnippetList.bind(this))
|
||||
}
|
||||
|
||||
reloadSnippetList () {
|
||||
reloadSnippetList() {
|
||||
dataApi.fetchSnippet().then(snippets => {
|
||||
this.setState({snippets})
|
||||
this.setState({ snippets })
|
||||
this.props.onSnippetSelect(this.props.currentSnippet)
|
||||
})
|
||||
}
|
||||
|
||||
handleSnippetContextMenu (snippet) {
|
||||
context.popup([{
|
||||
label: i18n.__('Delete snippet'),
|
||||
click: () => this.deleteSnippet(snippet)
|
||||
}])
|
||||
handleSnippetContextMenu(snippet) {
|
||||
context.popup([
|
||||
{
|
||||
label: i18n.__('Delete snippet'),
|
||||
click: () => this.deleteSnippet(snippet)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
deleteSnippet (snippet) {
|
||||
dataApi.deleteSnippet(snippet).then(() => {
|
||||
this.reloadSnippetList()
|
||||
this.props.onSnippetDeleted(snippet)
|
||||
}).catch(err => { throw err })
|
||||
deleteSnippet(snippet) {
|
||||
dataApi
|
||||
.deleteSnippet(snippet)
|
||||
.then(() => {
|
||||
this.reloadSnippetList()
|
||||
this.props.onSnippetDeleted(snippet)
|
||||
})
|
||||
.catch(err => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
handleSnippetClick (snippet) {
|
||||
handleSnippetClick(snippet) {
|
||||
this.props.onSnippetSelect(snippet)
|
||||
}
|
||||
|
||||
createSnippet () {
|
||||
dataApi.createSnippet().then(() => {
|
||||
this.reloadSnippetList()
|
||||
// scroll to end of list when added new snippet
|
||||
const snippetList = document.getElementById('snippets')
|
||||
snippetList.scrollTop = snippetList.scrollHeight
|
||||
}).catch(err => { throw err })
|
||||
createSnippet() {
|
||||
dataApi
|
||||
.createSnippet()
|
||||
.then(() => {
|
||||
this.reloadSnippetList()
|
||||
// scroll to end of list when added new snippet
|
||||
const snippetList = document.getElementById('snippets')
|
||||
snippetList.scrollTop = snippetList.scrollHeight
|
||||
})
|
||||
.catch(err => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
defineSnippetStyleName (snippet) {
|
||||
defineSnippetStyleName(snippet) {
|
||||
const { currentSnippet } = this.props
|
||||
|
||||
if (currentSnippet == null) {
|
||||
@@ -67,29 +79,31 @@ class SnippetList extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { snippets } = this.state
|
||||
return (
|
||||
<div styleName='snippet-list'>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-control'>
|
||||
<button styleName='group-control-button' onClick={() => this.createSnippet()}>
|
||||
<button
|
||||
styleName='group-control-button'
|
||||
onClick={() => this.createSnippet()}
|
||||
>
|
||||
<i className='fa fa-plus' /> {i18n.__('New Snippet')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul id='snippets' styleName='snippets'>
|
||||
{
|
||||
snippets.map((snippet) => (
|
||||
<li
|
||||
styleName={this.defineSnippetStyleName(snippet)}
|
||||
key={snippet.id}
|
||||
onContextMenu={() => this.handleSnippetContextMenu(snippet)}
|
||||
onClick={() => this.handleSnippetClick(snippet)}>
|
||||
{snippet.name}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
{snippets.map(snippet => (
|
||||
<li
|
||||
styleName={this.defineSnippetStyleName(snippet)}
|
||||
key={snippet.id}
|
||||
onContextMenu={() => this.handleSnippetContextMenu(snippet)}
|
||||
onClick={() => this.handleSnippetClick(snippet)}
|
||||
>
|
||||
{snippet.name}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ import copy from 'copy-to-clipboard'
|
||||
const path = require('path')
|
||||
|
||||
class SnippetTab extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
currentSnippet: null
|
||||
@@ -19,7 +19,7 @@ class SnippetTab extends React.Component {
|
||||
this.changeDelay = null
|
||||
}
|
||||
|
||||
notify (title, options) {
|
||||
notify(title, options) {
|
||||
if (global.process.platform === 'win32') {
|
||||
options.icon = path.join(
|
||||
'file://',
|
||||
@@ -30,7 +30,7 @@ class SnippetTab extends React.Component {
|
||||
return new window.Notification(title, options)
|
||||
}
|
||||
|
||||
handleSnippetNameOrPrefixChange () {
|
||||
handleSnippetNameOrPrefixChange() {
|
||||
clearTimeout(this.changeDelay)
|
||||
this.changeDelay = setTimeout(() => {
|
||||
// notify the snippet editor that the name or prefix of snippet has been changed
|
||||
@@ -39,20 +39,20 @@ class SnippetTab extends React.Component {
|
||||
}, 500)
|
||||
}
|
||||
|
||||
handleSnippetSelect (snippet) {
|
||||
handleSnippetSelect(snippet) {
|
||||
const { currentSnippet } = this.state
|
||||
if (snippet !== null) {
|
||||
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
|
||||
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
|
||||
// notify the snippet editor to load the content of the new snippet
|
||||
this.snippetEditor.onSnippetChanged(changedSnippet)
|
||||
this.setState({currentSnippet: changedSnippet})
|
||||
this.setState({ currentSnippet: changedSnippet })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSnippetNameOrPrefixChanged (e, type) {
|
||||
onSnippetNameOrPrefixChanged(e, type) {
|
||||
const newSnippet = Object.assign({}, this.state.currentSnippet)
|
||||
if (type === 'name') {
|
||||
newSnippet.name = e.target.value
|
||||
@@ -63,14 +63,14 @@ class SnippetTab extends React.Component {
|
||||
this.handleSnippetNameOrPrefixChange()
|
||||
}
|
||||
|
||||
handleDeleteSnippet (snippet) {
|
||||
handleDeleteSnippet(snippet) {
|
||||
// prevent old snippet still display when deleted
|
||||
if (snippet.id === this.state.currentSnippet.id) {
|
||||
this.setState({currentSnippet: null})
|
||||
this.setState({ currentSnippet: null })
|
||||
}
|
||||
}
|
||||
|
||||
handleCopySnippet (e) {
|
||||
handleCopySnippet(e) {
|
||||
const showCopyNotification = this.props.config.ui.showCopyNotification
|
||||
copy(this.state.currentSnippet.content)
|
||||
if (showCopyNotification) {
|
||||
@@ -81,7 +81,7 @@ class SnippetTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { config, storageKey } = this.props
|
||||
const { currentSnippet } = this.state
|
||||
|
||||
@@ -95,12 +95,19 @@ class SnippetTab extends React.Component {
|
||||
<SnippetList
|
||||
onSnippetSelect={this.handleSnippetSelect.bind(this)}
|
||||
onSnippetDeleted={this.handleDeleteSnippet.bind(this)}
|
||||
currentSnippet={currentSnippet} />
|
||||
<div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}>
|
||||
currentSnippet={currentSnippet}
|
||||
/>
|
||||
<div
|
||||
styleName='snippet-detail'
|
||||
style={{ visibility: currentSnippet ? 'visible' : 'hidden' }}
|
||||
>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-control'>
|
||||
<button styleName='group-control-rightButton'
|
||||
onClick={e => this.handleCopySnippet(e)}>{i18n.__('Copy')}
|
||||
<button
|
||||
styleName='group-control-rightButton'
|
||||
onClick={e => this.handleCopySnippet(e)}
|
||||
>
|
||||
{i18n.__('Copy')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -110,18 +117,26 @@ class SnippetTab extends React.Component {
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
value={currentSnippet ? currentSnippet.name : ''}
|
||||
onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'name') }}
|
||||
type='text' />
|
||||
onChange={e => {
|
||||
this.onSnippetNameOrPrefixChanged(e, 'name')
|
||||
}}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>{i18n.__('Snippet prefix')}</div>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Snippet prefix')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input
|
||||
styleName='group-section-control-input'
|
||||
value={currentSnippet ? currentSnippet.prefix : ''}
|
||||
onChange={e => { this.onSnippetNameOrPrefixChanged(e, 'prefix') }}
|
||||
type='text' />
|
||||
onChange={e => {
|
||||
this.onSnippetNameOrPrefixChanged(e, 'prefix')
|
||||
}}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='snippet-editor-section'>
|
||||
@@ -140,7 +155,10 @@ class SnippetTab extends React.Component {
|
||||
matchingTriples={config.editor.matchingTriples}
|
||||
explodingPairs={config.editor.explodingPairs}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
onRef={ref => { this.snippetEditor = ref }} />
|
||||
onRef={ref => {
|
||||
this.snippetEditor = ref
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -148,7 +166,6 @@ class SnippetTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
SnippetTab.PropTypes = {
|
||||
}
|
||||
SnippetTab.PropTypes = {}
|
||||
|
||||
export default CSSModules(SnippetTab, styles)
|
||||
|
||||
@@ -12,7 +12,7 @@ const { shell, remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
|
||||
class StorageItem extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -20,137 +20,156 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleNewFolderButtonClick (e) {
|
||||
handleNewFolderButtonClick(e) {
|
||||
const { storage } = this.props
|
||||
const input = {
|
||||
name: i18n.__('New Folder'),
|
||||
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
||||
}
|
||||
|
||||
dataApi.createFolder(storage.key, input)
|
||||
.then((data) => {
|
||||
dataApi
|
||||
.createFolder(storage.key, input)
|
||||
.then(data => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_FOLDER',
|
||||
storage: data.storage
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
handleExternalButtonClick () {
|
||||
handleExternalButtonClick() {
|
||||
const { storage } = this.props
|
||||
shell.showItemInFolder(storage.path)
|
||||
}
|
||||
|
||||
handleUnlinkButtonClick (e) {
|
||||
handleUnlinkButtonClick(e) {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: i18n.__('Unlink Storage'),
|
||||
detail: i18n.__('Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.'),
|
||||
detail: i18n.__(
|
||||
'Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.'
|
||||
),
|
||||
buttons: [i18n.__('Unlink'), i18n.__('Cancel')]
|
||||
})
|
||||
|
||||
if (index === 0) {
|
||||
const { storage } = this.props
|
||||
dataApi.removeStorage(storage.key)
|
||||
dataApi
|
||||
.removeStorage(storage.key)
|
||||
.then(() => {
|
||||
store.dispatch({
|
||||
type: 'REMOVE_STORAGE',
|
||||
storageKey: storage.key
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleLabelClick (e) {
|
||||
handleLabelClick(e) {
|
||||
const { storage } = this.props
|
||||
this.setState({
|
||||
isLabelEditing: true,
|
||||
name: storage.name
|
||||
}, () => {
|
||||
this.refs.label.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
isLabelEditing: true,
|
||||
name: storage.name
|
||||
},
|
||||
() => {
|
||||
this.refs.label.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
handleLabelChange (e) {
|
||||
handleLabelChange(e) {
|
||||
this.setState({
|
||||
name: this.refs.label.value
|
||||
})
|
||||
}
|
||||
|
||||
handleLabelBlur (e) {
|
||||
handleLabelBlur(e) {
|
||||
const { storage } = this.props
|
||||
dataApi
|
||||
.renameStorage(storage.key, this.state.name)
|
||||
.then((_storage) => {
|
||||
store.dispatch({
|
||||
type: 'RENAME_STORAGE',
|
||||
storage: _storage
|
||||
})
|
||||
this.setState({
|
||||
isLabelEditing: false
|
||||
})
|
||||
dataApi.renameStorage(storage.key, this.state.name).then(_storage => {
|
||||
store.dispatch({
|
||||
type: 'RENAME_STORAGE',
|
||||
storage: _storage
|
||||
})
|
||||
this.setState({
|
||||
isLabelEditing: false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { storage, hostBoundingBox } = this.props
|
||||
|
||||
return (
|
||||
<div styleName='root' key={storage.key}>
|
||||
<div styleName='header'>
|
||||
{this.state.isLabelEditing
|
||||
? <div styleName='header-label--edit'>
|
||||
<input styleName='header-label-input'
|
||||
{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)}
|
||||
onChange={e => this.handleLabelChange(e)}
|
||||
onBlur={e => this.handleLabelBlur(e)}
|
||||
/>
|
||||
</div>
|
||||
: <div styleName='header-label'
|
||||
onClick={(e) => this.handleLabelClick(e)}
|
||||
) : (
|
||||
<div
|
||||
styleName='header-label'
|
||||
onClick={e => this.handleLabelClick(e)}
|
||||
>
|
||||
<i className='fa fa-folder-open' />
|
||||
<i className='fa fa-folder-open' />
|
||||
|
||||
{storage.name}
|
||||
<span styleName='header-label-path'>({storage.path})</span>
|
||||
<i styleName='header-label-editButton' className='fa fa-pencil' />
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
<div styleName='header-control'>
|
||||
<button styleName='header-control-button'
|
||||
onClick={(e) => this.handleNewFolderButtonClick(e)}
|
||||
<button
|
||||
styleName='header-control-button'
|
||||
onClick={e => this.handleNewFolderButtonClick(e)}
|
||||
>
|
||||
<i className='fa fa-plus' />
|
||||
<span styleName='header-control-button-tooltip'
|
||||
style={{left: -20}}
|
||||
>{i18n.__('Add Folder')}</span>
|
||||
<span
|
||||
styleName='header-control-button-tooltip'
|
||||
style={{ left: -20 }}
|
||||
>
|
||||
{i18n.__('Add Folder')}
|
||||
</span>
|
||||
</button>
|
||||
<button styleName='header-control-button'
|
||||
onClick={(e) => this.handleExternalButtonClick(e)}
|
||||
<button
|
||||
styleName='header-control-button'
|
||||
onClick={e => this.handleExternalButtonClick(e)}
|
||||
>
|
||||
<i className='fa fa-external-link' />
|
||||
<span styleName='header-control-button-tooltip'
|
||||
style={{left: -50}}
|
||||
>{i18n.__('Open Storage folder')}</span>
|
||||
<span
|
||||
styleName='header-control-button-tooltip'
|
||||
style={{ left: -50 }}
|
||||
>
|
||||
{i18n.__('Open Storage folder')}
|
||||
</span>
|
||||
</button>
|
||||
<button styleName='header-control-button'
|
||||
onClick={(e) => this.handleUnlinkButtonClick(e)}
|
||||
<button
|
||||
styleName='header-control-button'
|
||||
onClick={e => this.handleUnlinkButtonClick(e)}
|
||||
>
|
||||
<i className='fa fa-unlink' />
|
||||
<span styleName='header-control-button-tooltip'
|
||||
style={{left: -10}}
|
||||
>{i18n.__('Unlink')}</span>
|
||||
<span
|
||||
styleName='header-control-button-tooltip'
|
||||
style={{ left: -10 }}
|
||||
>
|
||||
{i18n.__('Unlink')}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<FolderList storage={storage}
|
||||
hostBoundingBox={hostBoundingBox}
|
||||
/>
|
||||
<FolderList storage={storage} hostBoundingBox={hostBoundingBox} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,24 +12,27 @@ import fs from 'fs'
|
||||
const electron = require('electron')
|
||||
const { shell, remote } = electron
|
||||
|
||||
function browseFolder () {
|
||||
function browseFolder() {
|
||||
const dialog = remote.dialog
|
||||
|
||||
const defaultPath = remote.app.getPath('home')
|
||||
return new Promise((resolve, reject) => {
|
||||
dialog.showOpenDialog({
|
||||
title: i18n.__('Select Directory'),
|
||||
defaultPath,
|
||||
properties: ['openDirectory', 'createDirectory']
|
||||
}, function (targetPaths) {
|
||||
if (targetPaths == null) return resolve('')
|
||||
resolve(targetPaths[0])
|
||||
})
|
||||
dialog.showOpenDialog(
|
||||
{
|
||||
title: i18n.__('Select Directory'),
|
||||
defaultPath,
|
||||
properties: ['openDirectory', 'createDirectory']
|
||||
},
|
||||
function(targetPaths) {
|
||||
if (targetPaths == null) return resolve('')
|
||||
resolve(targetPaths[0])
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
class StoragesTab extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -44,7 +47,7 @@ class StoragesTab extends React.Component {
|
||||
this.loadAttachmentStorage()
|
||||
}
|
||||
|
||||
loadAttachmentStorage () {
|
||||
loadAttachmentStorage() {
|
||||
const promises = []
|
||||
this.props.data.noteMap.map(note => {
|
||||
const promise = attachmentManagement.getAttachmentsPathAndStatus(
|
||||
@@ -58,106 +61,128 @@ class StoragesTab extends React.Component {
|
||||
Promise.all(promises)
|
||||
.then(data => {
|
||||
const result = data.reduce((acc, curr) => acc.concat(curr), [])
|
||||
this.setState({attachments: result})
|
||||
this.setState({ attachments: result })
|
||||
})
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
handleAddStorageButton (e) {
|
||||
this.setState({
|
||||
page: 'ADD_STORAGE',
|
||||
newStorage: {
|
||||
name: 'Unnamed',
|
||||
type: 'FILESYSTEM',
|
||||
path: ''
|
||||
handleAddStorageButton(e) {
|
||||
this.setState(
|
||||
{
|
||||
page: 'ADD_STORAGE',
|
||||
newStorage: {
|
||||
name: 'Unnamed',
|
||||
type: 'FILESYSTEM',
|
||||
path: ''
|
||||
}
|
||||
},
|
||||
() => {
|
||||
this.refs.addStorageName.select()
|
||||
}
|
||||
}, () => {
|
||||
this.refs.addStorageName.select()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
handleLinkClick(e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
handleRemoveUnusedAttachments (attachments) {
|
||||
attachmentManagement.removeAttachmentsByPaths(attachments)
|
||||
handleRemoveUnusedAttachments(attachments) {
|
||||
attachmentManagement
|
||||
.removeAttachmentsByPaths(attachments)
|
||||
.then(() => this.loadAttachmentStorage())
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
renderList () {
|
||||
renderList() {
|
||||
const { data, boundingBox } = this.props
|
||||
const { attachments } = this.state
|
||||
|
||||
const unusedAttachments = attachments.filter(attachment => !attachment.isInUse)
|
||||
const inUseAttachments = attachments.filter(attachment => attachment.isInUse)
|
||||
const unusedAttachments = attachments.filter(
|
||||
attachment => !attachment.isInUse
|
||||
)
|
||||
const inUseAttachments = attachments.filter(
|
||||
attachment => attachment.isInUse
|
||||
)
|
||||
|
||||
const totalUnusedAttachments = unusedAttachments.length
|
||||
const totalInuseAttachments = inUseAttachments.length
|
||||
const totalAttachments = totalUnusedAttachments + totalInuseAttachments
|
||||
|
||||
const totalUnusedAttachmentsSize = unusedAttachments
|
||||
.reduce((acc, curr) => {
|
||||
const stats = fs.statSync(curr.path)
|
||||
const fileSizeInBytes = stats.size
|
||||
return acc + fileSizeInBytes
|
||||
}, 0)
|
||||
const totalInuseAttachmentsSize = inUseAttachments
|
||||
.reduce((acc, curr) => {
|
||||
const stats = fs.statSync(curr.path)
|
||||
const fileSizeInBytes = stats.size
|
||||
return acc + fileSizeInBytes
|
||||
}, 0)
|
||||
const totalAttachmentsSize = totalUnusedAttachmentsSize + totalInuseAttachmentsSize
|
||||
const totalUnusedAttachmentsSize = unusedAttachments.reduce((acc, curr) => {
|
||||
const stats = fs.statSync(curr.path)
|
||||
const fileSizeInBytes = stats.size
|
||||
return acc + fileSizeInBytes
|
||||
}, 0)
|
||||
const totalInuseAttachmentsSize = inUseAttachments.reduce((acc, curr) => {
|
||||
const stats = fs.statSync(curr.path)
|
||||
const fileSizeInBytes = stats.size
|
||||
return acc + fileSizeInBytes
|
||||
}, 0)
|
||||
const totalAttachmentsSize =
|
||||
totalUnusedAttachmentsSize + totalInuseAttachmentsSize
|
||||
|
||||
const unusedAttachmentPaths = unusedAttachments
|
||||
.reduce((acc, curr) => acc.concat(curr.path), [])
|
||||
const unusedAttachmentPaths = unusedAttachments.reduce(
|
||||
(acc, curr) => acc.concat(curr.path),
|
||||
[]
|
||||
)
|
||||
|
||||
if (!boundingBox) { return null }
|
||||
const storageList = data.storageMap.map((storage) => {
|
||||
return <StorageItem
|
||||
key={storage.key}
|
||||
storage={storage}
|
||||
hostBoundingBox={boundingBox}
|
||||
/>
|
||||
if (!boundingBox) {
|
||||
return null
|
||||
}
|
||||
const storageList = data.storageMap.map(storage => {
|
||||
return (
|
||||
<StorageItem
|
||||
key={storage.key}
|
||||
storage={storage}
|
||||
hostBoundingBox={boundingBox}
|
||||
/>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div styleName='list'>
|
||||
<div styleName='header'>{i18n.__('Storage Locations')}</div>
|
||||
{storageList.length > 0
|
||||
? storageList
|
||||
: <div styleName='list-empty'>{i18n.__('No storage found.')}</div>
|
||||
}
|
||||
{storageList.length > 0 ? (
|
||||
storageList
|
||||
) : (
|
||||
<div styleName='list-empty'>{i18n.__('No storage found.')}</div>
|
||||
)}
|
||||
<div styleName='list-control'>
|
||||
<button styleName='list-control-addStorageButton'
|
||||
onClick={(e) => this.handleAddStorageButton(e)}
|
||||
<button
|
||||
styleName='list-control-addStorageButton'
|
||||
onClick={e => this.handleAddStorageButton(e)}
|
||||
>
|
||||
<i className='fa fa-plus' /> {i18n.__('Add Storage Location')}
|
||||
</button>
|
||||
</div>
|
||||
<div styleName='header'>{i18n.__('Attachment storage')}</div>
|
||||
<p styleName='list-attachment-label'>
|
||||
Unused attachments size: {humanFileSize(totalUnusedAttachmentsSize)} ({totalUnusedAttachments} items)
|
||||
Unused attachments size: {humanFileSize(totalUnusedAttachmentsSize)} (
|
||||
{totalUnusedAttachments} items)
|
||||
</p>
|
||||
<p styleName='list-attachment-label'>
|
||||
In use attachments size: {humanFileSize(totalInuseAttachmentsSize)} ({totalInuseAttachments} items)
|
||||
In use attachments size: {humanFileSize(totalInuseAttachmentsSize)} (
|
||||
{totalInuseAttachments} items)
|
||||
</p>
|
||||
<p styleName='list-attachment-label'>
|
||||
Total attachments size: {humanFileSize(totalAttachmentsSize)} ({totalAttachments} items)
|
||||
Total attachments size: {humanFileSize(totalAttachmentsSize)} (
|
||||
{totalAttachments} items)
|
||||
</p>
|
||||
<button styleName='list-attachement-clear-button'
|
||||
onClick={() => this.handleRemoveUnusedAttachments(unusedAttachmentPaths)}>
|
||||
<button
|
||||
styleName='list-attachement-clear-button'
|
||||
onClick={() =>
|
||||
this.handleRemoveUnusedAttachments(unusedAttachmentPaths)
|
||||
}
|
||||
>
|
||||
{i18n.__('Clear unused attachments')}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
handleAddStorageBrowseButtonClick (e) {
|
||||
handleAddStorageBrowseButtonClick(e) {
|
||||
browseFolder()
|
||||
.then((targetPath) => {
|
||||
.then(targetPath => {
|
||||
if (targetPath.length > 0) {
|
||||
const { newStorage } = this.state
|
||||
newStorage.path = targetPath
|
||||
@@ -166,13 +191,13 @@ class StoragesTab extends React.Component {
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
console.error('BrowseFAILED')
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
handleAddStorageChange (e) {
|
||||
handleAddStorageChange(e) {
|
||||
const { newStorage } = this.state
|
||||
newStorage.name = this.refs.addStorageName.value
|
||||
newStorage.path = this.refs.addStoragePath.value
|
||||
@@ -181,13 +206,13 @@ class StoragesTab extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleAddStorageCreateButton (e) {
|
||||
handleAddStorageCreateButton(e) {
|
||||
dataApi
|
||||
.addStorage({
|
||||
name: this.state.newStorage.name,
|
||||
path: this.state.newStorage.path
|
||||
})
|
||||
.then((data) => {
|
||||
.then(data => {
|
||||
const { dispatch } = this.props
|
||||
dispatch({
|
||||
type: 'ADD_STORAGE',
|
||||
@@ -200,37 +225,39 @@ class StoragesTab extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleAddStorageCancelButton (e) {
|
||||
handleAddStorageCancelButton(e) {
|
||||
this.setState({
|
||||
page: 'LIST'
|
||||
})
|
||||
}
|
||||
|
||||
renderAddStorage () {
|
||||
renderAddStorage() {
|
||||
return (
|
||||
<div styleName='addStorage'>
|
||||
|
||||
<div styleName='addStorage-header'>{i18n.__('Add Storage')}</div>
|
||||
|
||||
<div styleName='addStorage-body'>
|
||||
|
||||
<div styleName='addStorage-body-section'>
|
||||
<div styleName='addStorage-body-section-label'>
|
||||
{i18n.__('Name')}
|
||||
</div>
|
||||
<div styleName='addStorage-body-section-name'>
|
||||
<input styleName='addStorage-body-section-name-input'
|
||||
<input
|
||||
styleName='addStorage-body-section-name-input'
|
||||
ref='addStorageName'
|
||||
value={this.state.newStorage.name}
|
||||
onChange={(e) => this.handleAddStorageChange(e)}
|
||||
onChange={e => this.handleAddStorageChange(e)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div styleName='addStorage-body-section'>
|
||||
<div styleName='addStorage-body-section-label'>{i18n.__('Type')}</div>
|
||||
<div styleName='addStorage-body-section-label'>
|
||||
{i18n.__('Type')}
|
||||
</div>
|
||||
<div styleName='addStorage-body-section-type'>
|
||||
<select styleName='addStorage-body-section-type-select'
|
||||
<select
|
||||
styleName='addStorage-body-section-type-select'
|
||||
value={this.state.newStorage.type}
|
||||
readOnly
|
||||
>
|
||||
@@ -238,25 +265,31 @@ class StoragesTab extends React.Component {
|
||||
</select>
|
||||
<div styleName='addStorage-body-section-type-description'>
|
||||
{i18n.__('Setting up 3rd-party cloud storage integration:')}{' '}
|
||||
<a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>{i18n.__('Cloud-Syncing-and-Backup')}</a>
|
||||
<a
|
||||
href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup'
|
||||
onClick={e => this.handleLinkClick(e)}
|
||||
>
|
||||
{i18n.__('Cloud-Syncing-and-Backup')}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div styleName='addStorage-body-section'>
|
||||
<div styleName='addStorage-body-section-label'>{i18n.__('Location')}
|
||||
<div styleName='addStorage-body-section-label'>
|
||||
{i18n.__('Location')}
|
||||
</div>
|
||||
<div styleName='addStorage-body-section-path'>
|
||||
<input styleName='addStorage-body-section-path-input'
|
||||
<input
|
||||
styleName='addStorage-body-section-path-input'
|
||||
ref='addStoragePath'
|
||||
placeholder={i18n.__('Select Folder')}
|
||||
value={this.state.newStorage.path}
|
||||
onChange={(e) => this.handleAddStorageChange(e)}
|
||||
onChange={e => this.handleAddStorageChange(e)}
|
||||
/>
|
||||
<button styleName='addStorage-body-section-path-button'
|
||||
onClick={(e) => this.handleAddStorageBrowseButtonClick(e)}
|
||||
<button
|
||||
styleName='addStorage-body-section-path-button'
|
||||
onClick={e => this.handleAddStorageBrowseButtonClick(e)}
|
||||
>
|
||||
...
|
||||
</button>
|
||||
@@ -264,21 +297,25 @@ class StoragesTab extends React.Component {
|
||||
</div>
|
||||
|
||||
<div styleName='addStorage-body-control'>
|
||||
<button styleName='addStorage-body-control-createButton'
|
||||
onClick={(e) => this.handleAddStorageCreateButton(e)}
|
||||
>{i18n.__('Add')}</button>
|
||||
<button styleName='addStorage-body-control-cancelButton'
|
||||
onClick={(e) => this.handleAddStorageCancelButton(e)}
|
||||
>{i18n.__('Cancel')}</button>
|
||||
<button
|
||||
styleName='addStorage-body-control-createButton'
|
||||
onClick={e => this.handleAddStorageCreateButton(e)}
|
||||
>
|
||||
{i18n.__('Add')}
|
||||
</button>
|
||||
<button
|
||||
styleName='addStorage-body-control-cancelButton'
|
||||
onClick={e => this.handleAddStorageCancelButton(e)}
|
||||
>
|
||||
{i18n.__('Cancel')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
renderContent() {
|
||||
switch (this.state.page) {
|
||||
case 'ADD_STORAGE':
|
||||
case 'ADD_FOLDER':
|
||||
@@ -289,12 +326,8 @@ class StoragesTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div styleName='root'>
|
||||
{this.renderContent()}
|
||||
</div>
|
||||
)
|
||||
render() {
|
||||
return <div styleName='root'>{this.renderContent()}</div>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class Preferences extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -27,44 +27,39 @@ class Preferences extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.refs.root.focus()
|
||||
const boundingBox = this.getContentBoundingBox()
|
||||
this.setState({ boundingBox })
|
||||
}
|
||||
|
||||
switchTeam (teamId) {
|
||||
this.setState({currentTeamId: teamId})
|
||||
switchTeam(teamId) {
|
||||
this.setState({ currentTeamId: teamId })
|
||||
}
|
||||
|
||||
handleNavButtonClick (tab) {
|
||||
return (e) => {
|
||||
this.setState({currentTab: tab})
|
||||
handleNavButtonClick(tab) {
|
||||
return e => {
|
||||
this.setState({ currentTab: tab })
|
||||
}
|
||||
}
|
||||
|
||||
handleEscButtonClick () {
|
||||
handleEscButtonClick() {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
renderContent () {
|
||||
renderContent() {
|
||||
const { boundingBox } = this.state
|
||||
const { dispatch, config, data } = this.props
|
||||
|
||||
switch (this.state.currentTab) {
|
||||
case 'INFO':
|
||||
return (
|
||||
<InfoTab
|
||||
dispatch={dispatch}
|
||||
config={config}
|
||||
/>
|
||||
)
|
||||
return <InfoTab dispatch={dispatch} config={config} />
|
||||
case 'HOTKEY':
|
||||
return (
|
||||
<HotkeyTab
|
||||
dispatch={dispatch}
|
||||
config={config}
|
||||
haveToSave={alert => this.setState({HotkeyAlert: alert})}
|
||||
haveToSave={alert => this.setState({ HotkeyAlert: alert })}
|
||||
/>
|
||||
)
|
||||
case 'UI':
|
||||
@@ -72,29 +67,21 @@ class Preferences extends React.Component {
|
||||
<UiTab
|
||||
dispatch={dispatch}
|
||||
config={config}
|
||||
haveToSave={alert => this.setState({UIAlert: alert})}
|
||||
haveToSave={alert => this.setState({ UIAlert: alert })}
|
||||
/>
|
||||
)
|
||||
case 'CROWDFUNDING':
|
||||
return (
|
||||
<Crowdfunding />
|
||||
)
|
||||
return <Crowdfunding />
|
||||
case 'BLOG':
|
||||
return (
|
||||
<Blog
|
||||
dispatch={dispatch}
|
||||
config={config}
|
||||
haveToSave={alert => this.setState({BlogAlert: alert})}
|
||||
haveToSave={alert => this.setState({ BlogAlert: alert })}
|
||||
/>
|
||||
)
|
||||
case 'SNIPPET':
|
||||
return (
|
||||
<SnippetTab
|
||||
dispatch={dispatch}
|
||||
config={config}
|
||||
data={data}
|
||||
/>
|
||||
)
|
||||
return <SnippetTab dispatch={dispatch} config={config} data={data} />
|
||||
case 'STORAGES':
|
||||
default:
|
||||
return (
|
||||
@@ -107,67 +94,69 @@ class Preferences extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
if (e.keyCode === 27) {
|
||||
this.props.close()
|
||||
}
|
||||
}
|
||||
|
||||
getContentBoundingBox () {
|
||||
getContentBoundingBox() {
|
||||
return this.refs.content.getBoundingClientRect()
|
||||
}
|
||||
|
||||
haveToSaveNotif (type, message) {
|
||||
return (
|
||||
<p styleName={`saving--${type}`}>{message}</p>
|
||||
)
|
||||
haveToSaveNotif(type, message) {
|
||||
return <p styleName={`saving--${type}`}>{message}</p>
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const content = this.renderContent()
|
||||
|
||||
const tabs = [
|
||||
{target: 'STORAGES', label: i18n.__('Storage')},
|
||||
{target: 'HOTKEY', label: i18n.__('Hotkeys'), Hotkey: this.state.HotkeyAlert},
|
||||
{target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert},
|
||||
{target: 'INFO', label: i18n.__('About')},
|
||||
{target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')},
|
||||
{target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert},
|
||||
{target: 'SNIPPET', label: i18n.__('Snippets')}
|
||||
{ target: 'STORAGES', label: i18n.__('Storage') },
|
||||
{
|
||||
target: 'HOTKEY',
|
||||
label: i18n.__('Hotkeys'),
|
||||
Hotkey: this.state.HotkeyAlert
|
||||
},
|
||||
{ target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert },
|
||||
{ target: 'INFO', label: i18n.__('About') },
|
||||
{ target: 'CROWDFUNDING', label: i18n.__('Crowdfunding') },
|
||||
{ target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert },
|
||||
{ target: 'SNIPPET', label: i18n.__('Snippets') }
|
||||
]
|
||||
|
||||
const navButtons = tabs.map((tab) => {
|
||||
const navButtons = tabs.map(tab => {
|
||||
const isActive = this.state.currentTab === tab.target
|
||||
const isUiHotkeyTab = _.isObject(tab[tab.label]) && tab.label === tab[tab.label].tab
|
||||
const isUiHotkeyTab =
|
||||
_.isObject(tab[tab.label]) && tab.label === tab[tab.label].tab
|
||||
return (
|
||||
<button styleName={isActive
|
||||
? 'nav-button--active'
|
||||
: 'nav-button'
|
||||
}
|
||||
<button
|
||||
styleName={isActive ? 'nav-button--active' : 'nav-button'}
|
||||
key={tab.target}
|
||||
onClick={(e) => this.handleNavButtonClick(tab.target)(e)}
|
||||
onClick={e => this.handleNavButtonClick(tab.target)(e)}
|
||||
>
|
||||
<span>
|
||||
{tab.label}
|
||||
</span>
|
||||
{isUiHotkeyTab ? this.haveToSaveNotif(tab[tab.label].type, tab[tab.label].message) : null}
|
||||
<span>{tab.label}</span>
|
||||
{isUiHotkeyTab
|
||||
? this.haveToSaveNotif(tab[tab.label].type, tab[tab.label].message)
|
||||
: null}
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div styleName='root'
|
||||
<div
|
||||
styleName='root'
|
||||
ref='root'
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
<div styleName='top-bar'>
|
||||
<p>{i18n.__('Your preferences for Boostnote')}</p>
|
||||
</div>
|
||||
<ModalEscButton handleEscButtonClick={(e) => this.handleEscButtonClick(e)} />
|
||||
<div styleName='nav'>
|
||||
{navButtons}
|
||||
</div>
|
||||
<ModalEscButton
|
||||
handleEscButtonClick={e => this.handleEscButtonClick(e)}
|
||||
/>
|
||||
<div styleName='nav'>{navButtons}</div>
|
||||
<div ref='content' styleName='content'>
|
||||
{content}
|
||||
</div>
|
||||
@@ -181,4 +170,4 @@ Preferences.propTypes = {
|
||||
dispatch: PropTypes.func
|
||||
}
|
||||
|
||||
export default connect((x) => x)(CSSModules(Preferences, styles))
|
||||
export default connect(x => x)(CSSModules(Preferences, styles))
|
||||
|
||||
@@ -8,7 +8,7 @@ import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class RenameFolderModal extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -16,39 +16,39 @@ class RenameFolderModal extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.refs.name.focus()
|
||||
this.refs.name.select()
|
||||
}
|
||||
|
||||
handleCloseButtonClick (e) {
|
||||
handleCloseButtonClick(e) {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
handleChange(e) {
|
||||
this.setState({
|
||||
name: this.refs.name.value
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
if (e.keyCode === 27) {
|
||||
this.props.close()
|
||||
}
|
||||
}
|
||||
|
||||
handleInputKeyDown (e) {
|
||||
handleInputKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 13:
|
||||
this.confirm()
|
||||
}
|
||||
}
|
||||
|
||||
handleConfirmButtonClick (e) {
|
||||
handleConfirmButtonClick(e) {
|
||||
this.confirm()
|
||||
}
|
||||
|
||||
confirm () {
|
||||
confirm() {
|
||||
if (this.state.name.trim().length > 0) {
|
||||
const { storage, folder } = this.props
|
||||
dataApi
|
||||
@@ -56,7 +56,7 @@ class RenameFolderModal extends React.Component {
|
||||
name: this.state.name,
|
||||
color: folder.color
|
||||
})
|
||||
.then((data) => {
|
||||
.then(data => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_FOLDER',
|
||||
storage: data.storage
|
||||
@@ -66,27 +66,32 @@ class RenameFolderModal extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
return (
|
||||
<div styleName='root'
|
||||
<div
|
||||
styleName='root'
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
<div styleName='header'>
|
||||
<div styleName='title'>{i18n.__('Rename Folder')}</div>
|
||||
</div>
|
||||
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
|
||||
<ModalEscButton
|
||||
handleEscButtonClick={e => this.handleCloseButtonClick(e)}
|
||||
/>
|
||||
|
||||
<div styleName='control'>
|
||||
<input styleName='control-input'
|
||||
<input
|
||||
styleName='control-input'
|
||||
placeholder={i18n.__('Folder Name')}
|
||||
ref='name'
|
||||
value={this.state.name}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onKeyDown={(e) => this.handleInputKeyDown(e)}
|
||||
onChange={e => this.handleChange(e)}
|
||||
onKeyDown={e => this.handleInputKeyDown(e)}
|
||||
/>
|
||||
<button styleName='control-confirmButton'
|
||||
onClick={(e) => this.handleConfirmButtonClick(e)}
|
||||
<button
|
||||
styleName='control-confirmButton'
|
||||
onClick={e => this.handleConfirmButtonClick(e)}
|
||||
>
|
||||
{i18n.__('Confirm')}
|
||||
</button>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Map, Set } from 'browser/lib/Mutable'
|
||||
import _ from 'lodash'
|
||||
import DevTools from './DevTools'
|
||||
|
||||
function defaultDataMap () {
|
||||
function defaultDataMap() {
|
||||
return {
|
||||
storageMap: new Map(),
|
||||
noteMap: new Map(),
|
||||
@@ -18,16 +18,16 @@ function defaultDataMap () {
|
||||
}
|
||||
}
|
||||
|
||||
function data (state = defaultDataMap(), action) {
|
||||
function data(state = defaultDataMap(), action) {
|
||||
switch (action.type) {
|
||||
case 'INIT_ALL':
|
||||
state = defaultDataMap()
|
||||
|
||||
action.storages.forEach((storage) => {
|
||||
action.storages.forEach(storage => {
|
||||
state.storageMap.set(storage.key, storage)
|
||||
})
|
||||
|
||||
action.notes.some((note) => {
|
||||
action.notes.some(note => {
|
||||
if (note === undefined) return true
|
||||
const uniqueKey = note.key
|
||||
const folderKey = note.storage + '-' + note.folder
|
||||
@@ -40,7 +40,10 @@ function data (state = defaultDataMap(), action) {
|
||||
if (note.isTrashed) {
|
||||
state.trashedSet.add(uniqueKey)
|
||||
}
|
||||
const storageNoteList = getOrInitItem(state.storageNoteMap, note.storage)
|
||||
const storageNoteList = getOrInitItem(
|
||||
state.storageNoteMap,
|
||||
note.storage
|
||||
)
|
||||
storageNoteList.add(uniqueKey)
|
||||
|
||||
const folderNoteSet = getOrInitItem(state.folderNoteMap, folderKey)
|
||||
@@ -51,173 +54,170 @@ function data (state = defaultDataMap(), action) {
|
||||
}
|
||||
})
|
||||
return state
|
||||
case 'UPDATE_NOTE':
|
||||
{
|
||||
const note = action.note
|
||||
const uniqueKey = note.key
|
||||
const folderKey = note.storage + '-' + note.folder
|
||||
const oldNote = state.noteMap.get(uniqueKey)
|
||||
case 'UPDATE_NOTE': {
|
||||
const note = action.note
|
||||
const uniqueKey = note.key
|
||||
const folderKey = note.storage + '-' + note.folder
|
||||
const oldNote = state.noteMap.get(uniqueKey)
|
||||
|
||||
state = Object.assign({}, state)
|
||||
state.noteMap = new Map(state.noteMap)
|
||||
state.noteMap.set(uniqueKey, note)
|
||||
state = Object.assign({}, state)
|
||||
state.noteMap = new Map(state.noteMap)
|
||||
state.noteMap.set(uniqueKey, note)
|
||||
|
||||
updateStarredChange(oldNote, note, state, uniqueKey)
|
||||
updateStarredChange(oldNote, note, state, uniqueKey)
|
||||
|
||||
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
||||
state.trashedSet = new Set(state.trashedSet)
|
||||
if (note.isTrashed) {
|
||||
state.trashedSet.add(uniqueKey)
|
||||
state.starredSet.delete(uniqueKey)
|
||||
removeFromTags(note.tags, state, uniqueKey)
|
||||
} else {
|
||||
state.trashedSet.delete(uniqueKey)
|
||||
|
||||
assignToTags(note.tags, state, uniqueKey)
|
||||
|
||||
if (note.isStarred) {
|
||||
state.starredSet.add(uniqueKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update storageNoteMap if oldNote doesn't exist
|
||||
if (oldNote == null) {
|
||||
state.storageNoteMap = new Map(state.storageNoteMap)
|
||||
let storageNoteSet = state.storageNoteMap.get(note.storage)
|
||||
storageNoteSet = new Set(storageNoteSet)
|
||||
storageNoteSet.add(uniqueKey)
|
||||
state.storageNoteMap.set(note.storage, storageNoteSet)
|
||||
}
|
||||
|
||||
// Update foldermap if folder changed or post created
|
||||
updateFolderChange(oldNote, note, state, folderKey, uniqueKey)
|
||||
|
||||
if (oldNote != null) {
|
||||
updateTagChanges(oldNote, note, state, uniqueKey)
|
||||
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
||||
state.trashedSet = new Set(state.trashedSet)
|
||||
if (note.isTrashed) {
|
||||
state.trashedSet.add(uniqueKey)
|
||||
state.starredSet.delete(uniqueKey)
|
||||
removeFromTags(note.tags, state, uniqueKey)
|
||||
} else {
|
||||
state.trashedSet.delete(uniqueKey)
|
||||
|
||||
assignToTags(note.tags, state, uniqueKey)
|
||||
}
|
||||
|
||||
return state
|
||||
if (note.isStarred) {
|
||||
state.starredSet.add(uniqueKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'MOVE_NOTE':
|
||||
{
|
||||
const originNote = action.originNote
|
||||
const originKey = originNote.key
|
||||
const note = action.note
|
||||
const uniqueKey = note.key
|
||||
const folderKey = note.storage + '-' + note.folder
|
||||
const oldNote = state.noteMap.get(uniqueKey)
|
||||
|
||||
state = Object.assign({}, state)
|
||||
state.noteMap = new Map(state.noteMap)
|
||||
state.noteMap.delete(originKey)
|
||||
state.noteMap.set(uniqueKey, note)
|
||||
// Update storageNoteMap if oldNote doesn't exist
|
||||
if (oldNote == null) {
|
||||
state.storageNoteMap = new Map(state.storageNoteMap)
|
||||
let storageNoteSet = state.storageNoteMap.get(note.storage)
|
||||
storageNoteSet = new Set(storageNoteSet)
|
||||
storageNoteSet.add(uniqueKey)
|
||||
state.storageNoteMap.set(note.storage, storageNoteSet)
|
||||
}
|
||||
|
||||
// If storage chanced, origin key must be discarded
|
||||
if (originKey !== uniqueKey) {
|
||||
// From isStarred
|
||||
if (originNote.isStarred) {
|
||||
state.starredSet = new Set(state.starredSet)
|
||||
state.starredSet.delete(originKey)
|
||||
}
|
||||
// Update foldermap if folder changed or post created
|
||||
updateFolderChange(oldNote, note, state, folderKey, uniqueKey)
|
||||
|
||||
if (originNote.isTrashed) {
|
||||
state.trashedSet = new Set(state.trashedSet)
|
||||
state.trashedSet.delete(originKey)
|
||||
}
|
||||
if (oldNote != null) {
|
||||
updateTagChanges(oldNote, note, state, uniqueKey)
|
||||
} else {
|
||||
assignToTags(note.tags, state, uniqueKey)
|
||||
}
|
||||
|
||||
// From storageNoteMap
|
||||
state.storageNoteMap = new Map(state.storageNoteMap)
|
||||
let noteSet = state.storageNoteMap.get(originNote.storage)
|
||||
noteSet = new Set(noteSet)
|
||||
noteSet.delete(originKey)
|
||||
state.storageNoteMap.set(originNote.storage, noteSet)
|
||||
return state
|
||||
}
|
||||
case 'MOVE_NOTE': {
|
||||
const originNote = action.originNote
|
||||
const originKey = originNote.key
|
||||
const note = action.note
|
||||
const uniqueKey = note.key
|
||||
const folderKey = note.storage + '-' + note.folder
|
||||
const oldNote = state.noteMap.get(uniqueKey)
|
||||
|
||||
// From folderNoteMap
|
||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||
const originFolderKey = originNote.storage + '-' + originNote.folder
|
||||
let originFolderList = state.folderNoteMap.get(originFolderKey)
|
||||
originFolderList = new Set(originFolderList)
|
||||
originFolderList.delete(originKey)
|
||||
state.folderNoteMap.set(originFolderKey, originFolderList)
|
||||
state = Object.assign({}, state)
|
||||
state.noteMap = new Map(state.noteMap)
|
||||
state.noteMap.delete(originKey)
|
||||
state.noteMap.set(uniqueKey, note)
|
||||
|
||||
removeFromTags(originNote.tags, state, originKey)
|
||||
// If storage chanced, origin key must be discarded
|
||||
if (originKey !== uniqueKey) {
|
||||
// From isStarred
|
||||
if (originNote.isStarred) {
|
||||
state.starredSet = new Set(state.starredSet)
|
||||
state.starredSet.delete(originKey)
|
||||
}
|
||||
|
||||
updateStarredChange(oldNote, note, state, uniqueKey)
|
||||
|
||||
if (oldNote == null || oldNote.isTrashed !== note.isTrashed) {
|
||||
if (originNote.isTrashed) {
|
||||
state.trashedSet = new Set(state.trashedSet)
|
||||
if (note.isTrashed) {
|
||||
state.trashedSet.add(uniqueKey)
|
||||
} else {
|
||||
state.trashedSet.delete(uniqueKey)
|
||||
}
|
||||
state.trashedSet.delete(originKey)
|
||||
}
|
||||
|
||||
// Update storageNoteMap if oldNote doesn't exist
|
||||
if (oldNote == null) {
|
||||
state.storageNoteMap = new Map(state.storageNoteMap)
|
||||
let noteSet = state.storageNoteMap.get(note.storage)
|
||||
noteSet = new Set(noteSet)
|
||||
noteSet.add(uniqueKey)
|
||||
state.storageNoteMap.set(folderKey, noteSet)
|
||||
}
|
||||
|
||||
// Update foldermap if folder changed or post created
|
||||
updateFolderChange(oldNote, note, state, folderKey, uniqueKey)
|
||||
|
||||
// Remove from old folder map
|
||||
if (oldNote != null) {
|
||||
updateTagChanges(oldNote, note, state, uniqueKey)
|
||||
} else {
|
||||
assignToTags(note.tags, state, uniqueKey)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
case 'DELETE_NOTE':
|
||||
{
|
||||
const uniqueKey = action.noteKey
|
||||
const targetNote = state.noteMap.get(uniqueKey)
|
||||
|
||||
state = Object.assign({}, state)
|
||||
|
||||
// From storageNoteMap
|
||||
state.storageNoteMap = new Map(state.storageNoteMap)
|
||||
let noteSet = state.storageNoteMap.get(targetNote.storage)
|
||||
let noteSet = state.storageNoteMap.get(originNote.storage)
|
||||
noteSet = new Set(noteSet)
|
||||
noteSet.delete(uniqueKey)
|
||||
state.storageNoteMap.set(targetNote.storage, noteSet)
|
||||
noteSet.delete(originKey)
|
||||
state.storageNoteMap.set(originNote.storage, noteSet)
|
||||
|
||||
if (targetNote != null) {
|
||||
// From isStarred
|
||||
if (targetNote.isStarred) {
|
||||
state.starredSet = new Set(state.starredSet)
|
||||
state.starredSet.delete(uniqueKey)
|
||||
}
|
||||
// From folderNoteMap
|
||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||
const originFolderKey = originNote.storage + '-' + originNote.folder
|
||||
let originFolderList = state.folderNoteMap.get(originFolderKey)
|
||||
originFolderList = new Set(originFolderList)
|
||||
originFolderList.delete(originKey)
|
||||
state.folderNoteMap.set(originFolderKey, originFolderList)
|
||||
|
||||
if (targetNote.isTrashed) {
|
||||
state.trashedSet = new Set(state.trashedSet)
|
||||
state.trashedSet.delete(uniqueKey)
|
||||
}
|
||||
|
||||
// From folderNoteMap
|
||||
const folderKey = targetNote.storage + '-' + targetNote.folder
|
||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||
let folderSet = state.folderNoteMap.get(folderKey)
|
||||
folderSet = new Set(folderSet)
|
||||
folderSet.delete(uniqueKey)
|
||||
state.folderNoteMap.set(folderKey, folderSet)
|
||||
|
||||
removeFromTags(targetNote.tags, state, uniqueKey)
|
||||
}
|
||||
state.noteMap = new Map(state.noteMap)
|
||||
state.noteMap.delete(uniqueKey)
|
||||
return state
|
||||
removeFromTags(originNote.tags, state, originKey)
|
||||
}
|
||||
|
||||
updateStarredChange(oldNote, note, state, uniqueKey)
|
||||
|
||||
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)
|
||||
let noteSet = state.storageNoteMap.get(note.storage)
|
||||
noteSet = new Set(noteSet)
|
||||
noteSet.add(uniqueKey)
|
||||
state.storageNoteMap.set(folderKey, noteSet)
|
||||
}
|
||||
|
||||
// Update foldermap if folder changed or post created
|
||||
updateFolderChange(oldNote, note, state, folderKey, uniqueKey)
|
||||
|
||||
// Remove from old folder map
|
||||
if (oldNote != null) {
|
||||
updateTagChanges(oldNote, note, state, uniqueKey)
|
||||
} else {
|
||||
assignToTags(note.tags, state, uniqueKey)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
case 'DELETE_NOTE': {
|
||||
const uniqueKey = action.noteKey
|
||||
const targetNote = state.noteMap.get(uniqueKey)
|
||||
|
||||
state = Object.assign({}, state)
|
||||
|
||||
// From storageNoteMap
|
||||
state.storageNoteMap = new Map(state.storageNoteMap)
|
||||
let noteSet = state.storageNoteMap.get(targetNote.storage)
|
||||
noteSet = new Set(noteSet)
|
||||
noteSet.delete(uniqueKey)
|
||||
state.storageNoteMap.set(targetNote.storage, noteSet)
|
||||
|
||||
if (targetNote != null) {
|
||||
// From isStarred
|
||||
if (targetNote.isStarred) {
|
||||
state.starredSet = new Set(state.starredSet)
|
||||
state.starredSet.delete(uniqueKey)
|
||||
}
|
||||
|
||||
if (targetNote.isTrashed) {
|
||||
state.trashedSet = new Set(state.trashedSet)
|
||||
state.trashedSet.delete(uniqueKey)
|
||||
}
|
||||
|
||||
// From folderNoteMap
|
||||
const folderKey = targetNote.storage + '-' + targetNote.folder
|
||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||
let folderSet = state.folderNoteMap.get(folderKey)
|
||||
folderSet = new Set(folderSet)
|
||||
folderSet.delete(uniqueKey)
|
||||
state.folderNoteMap.set(folderKey, folderSet)
|
||||
|
||||
removeFromTags(targetNote.tags, state, uniqueKey)
|
||||
}
|
||||
state.noteMap = new Map(state.noteMap)
|
||||
state.noteMap.delete(uniqueKey)
|
||||
return state
|
||||
}
|
||||
case 'UPDATE_FOLDER':
|
||||
case 'REORDER_FOLDER':
|
||||
case 'EXPORT_FOLDER':
|
||||
@@ -247,7 +247,7 @@ function data (state = defaultDataMap(), action) {
|
||||
state.storageNoteMap.set(action.storage.key, storageNoteSet)
|
||||
|
||||
if (noteSet != null) {
|
||||
noteSet.forEach(function handleNoteKey (noteKey) {
|
||||
noteSet.forEach(function handleNoteKey(noteKey) {
|
||||
// Get note from noteMap
|
||||
const note = state.noteMap.get(noteKey)
|
||||
if (note != null) {
|
||||
@@ -269,7 +269,7 @@ function data (state = defaultDataMap(), action) {
|
||||
|
||||
// Delete key from tag map
|
||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||
note.tags.forEach((tag) => {
|
||||
note.tags.forEach(tag => {
|
||||
const tagNoteSet = getOrInitItem(state.tagNoteMap, tag)
|
||||
tagNoteSet.delete(noteKey)
|
||||
})
|
||||
@@ -288,7 +288,7 @@ function data (state = defaultDataMap(), action) {
|
||||
state.storageNoteMap.set(action.storage.key, new Set())
|
||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||
action.notes.forEach((note) => {
|
||||
action.notes.forEach(note => {
|
||||
const uniqueKey = note.key
|
||||
const folderKey = note.storage + '-' + note.folder
|
||||
state.noteMap.set(uniqueKey, note)
|
||||
@@ -307,7 +307,7 @@ function data (state = defaultDataMap(), action) {
|
||||
}
|
||||
folderNoteSet.add(uniqueKey)
|
||||
|
||||
note.tags.forEach((tag) => {
|
||||
note.tags.forEach(tag => {
|
||||
const tagNoteSet = getOrInitItem(state.tagNoteMap, tag)
|
||||
tagNoteSet.add(uniqueKey)
|
||||
})
|
||||
@@ -322,7 +322,7 @@ function data (state = defaultDataMap(), action) {
|
||||
// Remove folders from folderMap
|
||||
if (storage != null) {
|
||||
state.folderMap = new Map(state.folderMap)
|
||||
storage.folders.forEach((folder) => {
|
||||
storage.folders.forEach(folder => {
|
||||
const folderKey = storage.key + '-' + folder.key
|
||||
state.folderMap.delete(folderKey)
|
||||
})
|
||||
@@ -334,17 +334,17 @@ function data (state = defaultDataMap(), action) {
|
||||
state.storageNoteMap.delete(action.storageKey)
|
||||
if (storageNoteSet != null) {
|
||||
const notes = storageNoteSet
|
||||
.map((noteKey) => state.noteMap.get(noteKey))
|
||||
.filter((note) => note != null)
|
||||
.map(noteKey => state.noteMap.get(noteKey))
|
||||
.filter(note => note != null)
|
||||
|
||||
state.noteMap = new Map(state.noteMap)
|
||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||
state.starredSet = new Set(state.starredSet)
|
||||
notes.forEach((note) => {
|
||||
notes.forEach(note => {
|
||||
const noteKey = note.key
|
||||
state.noteMap.delete(noteKey)
|
||||
state.starredSet.delete(noteKey)
|
||||
note.tags.forEach((tag) => {
|
||||
note.tags.forEach(tag => {
|
||||
let tagNoteSet = state.tagNoteMap.get(tag)
|
||||
tagNoteSet = new Set(tagNoteSet)
|
||||
tagNoteSet.delete(noteKey)
|
||||
@@ -364,7 +364,7 @@ function data (state = defaultDataMap(), action) {
|
||||
|
||||
const defaultConfig = ConfigManager.get()
|
||||
|
||||
function config (state = defaultConfig, action) {
|
||||
function config(state = defaultConfig, action) {
|
||||
switch (action.type) {
|
||||
case 'SET_IS_SIDENAV_FOLDED':
|
||||
state.isSideNavFolded = action.isFolded
|
||||
@@ -390,7 +390,7 @@ const defaultStatus = {
|
||||
updateReady: false
|
||||
}
|
||||
|
||||
function status (state = defaultStatus, action) {
|
||||
function status(state = defaultStatus, action) {
|
||||
switch (action.type) {
|
||||
case 'UPDATE_AVAILABLE':
|
||||
return Object.assign({}, defaultStatus, {
|
||||
@@ -400,7 +400,7 @@ function status (state = defaultStatus, action) {
|
||||
return state
|
||||
}
|
||||
|
||||
function updateStarredChange (oldNote, note, state, uniqueKey) {
|
||||
function updateStarredChange(oldNote, note, state, uniqueKey) {
|
||||
if (oldNote == null || oldNote.isStarred !== note.isStarred) {
|
||||
state.starredSet = new Set(state.starredSet)
|
||||
if (note.isStarred) {
|
||||
@@ -411,7 +411,7 @@ function updateStarredChange (oldNote, note, state, uniqueKey) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateFolderChange (oldNote, note, state, folderKey, uniqueKey) {
|
||||
function updateFolderChange(oldNote, note, state, folderKey, uniqueKey) {
|
||||
if (oldNote == null || oldNote.folder !== note.folder) {
|
||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||
let folderNoteList = state.folderNoteMap.get(folderKey)
|
||||
@@ -429,7 +429,7 @@ function updateFolderChange (oldNote, note, state, folderKey, uniqueKey) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateTagChanges (oldNote, note, state, uniqueKey) {
|
||||
function updateTagChanges(oldNote, note, state, uniqueKey) {
|
||||
const discardedTags = _.difference(oldNote.tags, note.tags)
|
||||
const addedTags = _.difference(note.tags, oldNote.tags)
|
||||
if (discardedTags.length + addedTags.length > 0) {
|
||||
@@ -438,15 +438,15 @@ function updateTagChanges (oldNote, note, state, uniqueKey) {
|
||||
}
|
||||
}
|
||||
|
||||
function assignToTags (tags, state, uniqueKey) {
|
||||
function assignToTags(tags, state, uniqueKey) {
|
||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||
tags.forEach((tag) => {
|
||||
tags.forEach(tag => {
|
||||
const tagNoteList = getOrInitItem(state.tagNoteMap, tag)
|
||||
tagNoteList.add(uniqueKey)
|
||||
})
|
||||
}
|
||||
|
||||
function removeFromTags (tags, state, uniqueKey) {
|
||||
function removeFromTags(tags, state, uniqueKey) {
|
||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||
tags.forEach(tag => {
|
||||
let tagNoteList = state.tagNoteMap.get(tag)
|
||||
@@ -458,7 +458,7 @@ function removeFromTags (tags, state, uniqueKey) {
|
||||
})
|
||||
}
|
||||
|
||||
function getOrInitItem (target, key) {
|
||||
function getOrInitItem(target, key) {
|
||||
let results = target.get(key)
|
||||
if (results == null) {
|
||||
results = new Set()
|
||||
@@ -476,8 +476,15 @@ const reducer = combineReducers({
|
||||
router: connectRouter(history)
|
||||
})
|
||||
|
||||
const store = createStore(reducer, undefined, process.env.NODE_ENV === 'development'
|
||||
? compose(applyMiddleware(routerMiddleware(history)), DevTools.instrument())
|
||||
: applyMiddleware(routerMiddleware(history)))
|
||||
const store = createStore(
|
||||
reducer,
|
||||
undefined,
|
||||
process.env.NODE_ENV === 'development'
|
||||
? compose(
|
||||
applyMiddleware(routerMiddleware(history)),
|
||||
DevTools.instrument()
|
||||
)
|
||||
: applyMiddleware(routerMiddleware(history))
|
||||
)
|
||||
|
||||
export { store, history }
|
||||
|
||||
Reference in New Issue
Block a user