mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-16 03:06:27 +00:00
Merge branch 'master' into feature-editor-line-lines
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
.root
|
||||
absolute top bottom right
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
|
||||
.empty
|
||||
height 320px
|
||||
@@ -8,8 +11,9 @@
|
||||
|
||||
.empty-message
|
||||
width 100%
|
||||
font-size 42px
|
||||
line-height 72px
|
||||
font-size 36px
|
||||
font-weight 600
|
||||
line-height 56px
|
||||
text-align center
|
||||
color $ui-inactive-text-color
|
||||
|
||||
@@ -18,3 +22,9 @@ body[data-theme="dark"]
|
||||
background-color $ui-dark-backgroundColor
|
||||
.empty-message
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
.empty-message
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
*/
|
||||
|
||||
// Margin on the left side and the right side for NoteDetail component.
|
||||
$note-detail-left-margin = 25px
|
||||
$note-detail-right-margin = 25px
|
||||
$note-detail-left-margin = 100px
|
||||
$note-detail-right-margin = 120px
|
||||
$snippet-note-detail-left-margin = 60px
|
||||
$snippet-note-detail-right-margin = 80px
|
||||
|
||||
$note-detail-box-shadow = 2px 0 15px -8px #b1b1b1 inset
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './FolderSelect.styl'
|
||||
import _ from 'lodash'
|
||||
@@ -73,8 +74,8 @@ class FolderSelect extends React.Component {
|
||||
case 9:
|
||||
if (e.shiftKey) {
|
||||
e.preventDefault()
|
||||
let tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
|
||||
let 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()
|
||||
}
|
||||
}
|
||||
@@ -89,9 +90,9 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
|
||||
handleSearchInputChange (e) {
|
||||
let { folders } = this.props
|
||||
let search = this.refs.search.value
|
||||
let optionIndex = search.length > 0
|
||||
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'))
|
||||
})
|
||||
@@ -129,7 +130,7 @@ class FolderSelect extends React.Component {
|
||||
|
||||
nextOption () {
|
||||
let { optionIndex } = this.state
|
||||
let { folders } = this.props
|
||||
const { folders } = this.props
|
||||
|
||||
optionIndex++
|
||||
if (optionIndex >= folders.length) optionIndex = 0
|
||||
@@ -140,7 +141,7 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
|
||||
previousOption () {
|
||||
let { folders } = this.props
|
||||
const { folders } = this.props
|
||||
let { optionIndex } = this.state
|
||||
|
||||
optionIndex--
|
||||
@@ -152,10 +153,10 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
|
||||
selectOption () {
|
||||
let { folders } = this.props
|
||||
let optionIndex = this.state.optionIndex
|
||||
const { folders } = this.props
|
||||
const optionIndex = this.state.optionIndex
|
||||
|
||||
let folder = folders[optionIndex]
|
||||
const folder = folders[optionIndex]
|
||||
if (folder != null) {
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
@@ -184,10 +185,10 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { className, data, value } = this.props
|
||||
let splitted = value.split('-')
|
||||
let storageKey = splitted.shift()
|
||||
let folderKey = splitted.shift()
|
||||
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) => {
|
||||
@@ -198,14 +199,14 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
})
|
||||
|
||||
let 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) {
|
||||
let filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
||||
const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
||||
options = options.filter((option) => filter.test(option.folder.name))
|
||||
}
|
||||
|
||||
let optionList = options
|
||||
const optionList = options
|
||||
.map((option, index) => {
|
||||
return (
|
||||
<div styleName={index === this.state.optionIndex
|
||||
@@ -259,12 +260,11 @@ class FolderSelect extends React.Component {
|
||||
{optionList}
|
||||
</div>
|
||||
</div>
|
||||
: <div styleName='idle'>
|
||||
: <div styleName='idle' style={{color: currentOption.folder.color}}>
|
||||
<div styleName='idle-label'>
|
||||
<span styleName='idle-label-name'
|
||||
style={{color: currentOption.folder.color}}
|
||||
>
|
||||
{currentOption.folder.name} /
|
||||
<i className='fa fa-folder' />
|
||||
<span styleName='idle-label-name'>
|
||||
{currentOption.folder.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
.root
|
||||
position relative
|
||||
border solid 1px transparent
|
||||
line-height 26px
|
||||
vertical-align middle
|
||||
border-radius 2px
|
||||
transition 0.15s
|
||||
user-select none
|
||||
margin-right 10px
|
||||
&:hover
|
||||
background-color $ui-button--hover-backgroundColor
|
||||
|
||||
.root--search, .root--focus
|
||||
@extend .root
|
||||
background-color $ui-noteDetail-backgroundColor = #F4F4F4
|
||||
background-color $ui-noteDetail-backgroundColor = #fff
|
||||
border-color $ui-input--focus-borderColor
|
||||
width 100px
|
||||
width 154px
|
||||
height 30px
|
||||
&:hover
|
||||
border-color $ui-input--focus-borderColor
|
||||
border-color $ui-input--focus-borderColor = #fff
|
||||
|
||||
.idle
|
||||
position relative
|
||||
@@ -24,13 +25,16 @@
|
||||
.idle-label
|
||||
right 20px
|
||||
overflow ellipsis
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
.idle-label-name
|
||||
font-size 14px
|
||||
padding 2px
|
||||
font-size 13px
|
||||
font-weight 600
|
||||
margin-left 4px
|
||||
|
||||
.idle-label-name-surfix
|
||||
font-size 10px
|
||||
font-size 15px
|
||||
color $ui-inactive-text-color
|
||||
margin-left 5px
|
||||
.idle-caret
|
||||
@@ -38,40 +42,42 @@
|
||||
height 34px
|
||||
width 20px
|
||||
line-height 34px
|
||||
|
||||
.search
|
||||
absolute top left right bottom
|
||||
line-height 34px
|
||||
|
||||
|
||||
.search-input
|
||||
vertical-align middle
|
||||
position relative
|
||||
top -2px
|
||||
top 0
|
||||
font-size 14px
|
||||
outline none
|
||||
border none
|
||||
height 20px
|
||||
line-height 20px
|
||||
width 100%
|
||||
background-color transparent
|
||||
padding 0 10px
|
||||
|
||||
.search-optionList
|
||||
position fixed
|
||||
position absolute
|
||||
top 30px
|
||||
max-height 450px
|
||||
min-width 150px
|
||||
overflow auto
|
||||
z-index 200
|
||||
border $ui-border
|
||||
background-color white
|
||||
border-radius 2px
|
||||
padding 10px 6px
|
||||
|
||||
.search-optionList-item
|
||||
width 140px
|
||||
height 34px
|
||||
width 250px
|
||||
display flex
|
||||
align-items center
|
||||
box-sizing border-box
|
||||
padding 0 5px
|
||||
padding 0
|
||||
margin-bottom 10px
|
||||
overflow ellipsis
|
||||
cursor pointer
|
||||
&:hover
|
||||
background-color $ui-button--hover-backgroundColor
|
||||
background-color $ui-button--hover-backgroundColor
|
||||
|
||||
.search-optionList-item--active
|
||||
@extend .search-optionList-item
|
||||
@@ -81,13 +87,17 @@
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color $ui-button--active-color
|
||||
.search-optionList-item-name
|
||||
border-left solid 2px transparent
|
||||
padding 2px 5px
|
||||
border-left solid 3px transparent
|
||||
padding 6px
|
||||
.search-optionList-item-name-surfix
|
||||
font-size 10px
|
||||
color $ui-inactive-text-color
|
||||
margin-left 5px
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
color $ui-dark-text-color
|
||||
|
||||
19
browser/main/Detail/FullscreenButton.js
Normal file
19
browser/main/Detail/FullscreenButton.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './FullscreenButton.styl'
|
||||
|
||||
const FullscreenButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-fullScreenButton' title='Fullscreen' onMouseDown={(e) => onClick(e)}>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
||||
<span styleName='tooltip'>Fullscreen</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
FullscreenButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(FullscreenButton, styles)
|
||||
22
browser/main/Detail/FullscreenButton.styl
Normal file
22
browser/main/Detail/FullscreenButton.styl
Normal file
@@ -0,0 +1,22 @@
|
||||
.control-fullScreenButton
|
||||
top 80px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
right 0
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoButton.styl'
|
||||
|
||||
@@ -6,9 +7,10 @@ const InfoButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-infoButton'
|
||||
onClick={onClick}
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<i className='fa fa-info infoButton' styleName='info-button' />
|
||||
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||
<span styleName='tooltip'>Info</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
.control-infoButton
|
||||
float right
|
||||
topBarButtonLight()
|
||||
top 10px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.control-infoPanel
|
||||
position fixed
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 50px
|
||||
top 26px
|
||||
right 0
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
@@ -14,7 +19,6 @@
|
||||
|
||||
.infoButton
|
||||
padding 0px
|
||||
margin 15px 0
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-infoButton
|
||||
|
||||
@@ -1,73 +1,60 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoPanel.styl'
|
||||
|
||||
const InfoPanel = ({
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type, print
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Storage
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{storageName}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Folder
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{folderName}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Created
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{createdAt}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Updated
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{updatedAt}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Note Link
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input value={noteLink} onClick={(e) => { e.target.select() }} />
|
||||
</div>
|
||||
<div>
|
||||
<p styleName='modification-date'>{updatedAt}</p>
|
||||
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Words
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{wordCount}
|
||||
</div>
|
||||
: <div styleName='count-wrap'>
|
||||
<div styleName='count-number'>
|
||||
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
||||
<p styleName='infoPanel-sub-count'>Words</p>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Letters
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{letterCount}
|
||||
</div>
|
||||
<div styleName='count-number'>
|
||||
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
|
||||
<p styleName='infoPanel-sub-count'>Letters</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <hr />
|
||||
}
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{storageName}</p>
|
||||
<p styleName='infoPanel-sub'>STORAGE</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{folderName}</p>
|
||||
<p styleName='infoPanel-sub'>FOLDER</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{createdAt}</p>
|
||||
<p styleName='infoPanel-sub'>CREATION DATE</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input styleName='infoPanel-noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
|
||||
<p styleName='infoPanel-sub'>NOTE LINK</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div id='export-wrap'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||
<i className='fa fa-file-code-o fa-fw' />
|
||||
@@ -79,9 +66,9 @@ const InfoPanel = ({
|
||||
<p>.txt</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--unable'>
|
||||
<i className='fa fa-file-pdf-o fa-fw' />
|
||||
<p>.pdf</p>
|
||||
<button styleName='export--enable' onClick={(e) => print(e)}>
|
||||
<i className='fa fa-print fa-fw' />
|
||||
<p>Print</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,7 +84,8 @@ InfoPanel.propTypes = {
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
wordCount: PropTypes.number,
|
||||
letterCount: PropTypes.number,
|
||||
type: PropTypes.string.isRequired
|
||||
type: PropTypes.string.isRequired,
|
||||
print: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(InfoPanel, styles)
|
||||
|
||||
@@ -10,57 +10,86 @@
|
||||
|
||||
.control-infoButton-panel
|
||||
z-index 200
|
||||
margin-top 45px
|
||||
margin-left -210px
|
||||
margin-top 0px
|
||||
right 0
|
||||
position absolute
|
||||
padding 20px 20px 0 20px
|
||||
width 340px
|
||||
padding 20px 25px 0 25px
|
||||
width 300px
|
||||
height 350px
|
||||
overflow auto
|
||||
background-color $ui-noteList-backgroundColor
|
||||
border 1px solid $border-color
|
||||
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||
border-radius 2px
|
||||
|
||||
.modification-date
|
||||
font-size 18px
|
||||
line-height 30px
|
||||
color $ui-text-color
|
||||
|
||||
.modification-date-desc
|
||||
font-size 18px
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
z-index 200
|
||||
margin-top 45px
|
||||
margin-left -230px
|
||||
margin-top 0px
|
||||
right 0px
|
||||
position absolute
|
||||
padding 20px 20px 0 20px
|
||||
width 320px
|
||||
padding 20px 25px 0 25px
|
||||
width 300px
|
||||
background-color $ui-noteList-backgroundColor
|
||||
border 1px solid $border-color
|
||||
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||
border-radius 2px
|
||||
|
||||
.group-section
|
||||
display flex
|
||||
.count-wrap
|
||||
display flex
|
||||
position relative
|
||||
width 100%
|
||||
|
||||
.count-number
|
||||
position relative
|
||||
display block
|
||||
width 50%
|
||||
overflow hidden
|
||||
margin 0
|
||||
padding 0
|
||||
|
||||
.infoPanel-defaul-count
|
||||
font-size 16px
|
||||
line-height 30px
|
||||
font-size 13px
|
||||
|
||||
.group-section-label
|
||||
width 70px
|
||||
height 20px
|
||||
text-align left
|
||||
margin-right 50px
|
||||
font-size 13px
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.group-section-control
|
||||
flex 1
|
||||
font-size 13px
|
||||
color $ui-text-color
|
||||
|
||||
.group-section-control input
|
||||
width 160px
|
||||
height 25px
|
||||
.infoPanel-sub-count
|
||||
font-size 16px
|
||||
color $ui-inactive-text-color
|
||||
padding-bottom 8px
|
||||
|
||||
.group-section-control text
|
||||
color #EA4447
|
||||
font-weight 600
|
||||
.infoPanel-default
|
||||
font-size 14px
|
||||
line-height 30px
|
||||
color $ui-text-color
|
||||
|
||||
.infoPanel-sub
|
||||
font-size 14px
|
||||
color $ui-inactive-text-color
|
||||
padding-bottom 8px
|
||||
|
||||
.infoPanel-noteLink
|
||||
padding-right 5px
|
||||
width 200px
|
||||
height 25px
|
||||
margin-bottom 6px
|
||||
|
||||
.infoPanel-trash
|
||||
color #EA4447
|
||||
font-weight 600
|
||||
font-size 14px
|
||||
width 70px
|
||||
height 25px
|
||||
background-color rgba(226,33,113,0.1)
|
||||
border none
|
||||
border none
|
||||
outline none
|
||||
border-radius 2px
|
||||
margin-right 5px
|
||||
border-radius 2px
|
||||
margin-right 5px
|
||||
padding 2px 5px
|
||||
|
||||
[id=export-wrap]
|
||||
@@ -96,20 +125,30 @@
|
||||
body[data-theme="dark"]
|
||||
.control-infoButton-panel
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
border 1px solid $ui-dark-borderColor
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
border 1px solid $ui-dark-borderColor
|
||||
|
||||
.group-section-label
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.group-section-control
|
||||
.modification-date
|
||||
color $ui-dark-text-color
|
||||
|
||||
.group-section-control input
|
||||
background-color alpha($ui-dark-borderColor, 80%)
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-defaul-count
|
||||
color $ui-dark-text-color
|
||||
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-default
|
||||
color $ui-dark-text-color
|
||||
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-noteLink
|
||||
background-color alpha($ui-dark-borderColor, 60%)
|
||||
color $ui-dark-text-color
|
||||
|
||||
[id=export-wrap]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoPanel.styl'
|
||||
|
||||
@@ -6,37 +7,26 @@ const InfoPanelTrashed = ({
|
||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Storage
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{storageName}
|
||||
</div>
|
||||
<div>
|
||||
<p styleName='modification-date'>{updatedAt}</p>
|
||||
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Folder
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<text>Trash</text>{folderName}
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{storageName}</p>
|
||||
<p styleName='infoPanel-sub'>STORAGE</p>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Created
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{createdAt}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
|
||||
<p styleName='infoPanel-sub'>FOLDER</p>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Updated
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{updatedAt}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{createdAt}</p>
|
||||
<p styleName='infoPanel-sub'>CREATION DATE</p>
|
||||
</div>
|
||||
|
||||
<div id='export-wrap'>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './MarkdownNoteDetail.styl'
|
||||
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||
import MarkdownSplitEditor from 'browser/components/MarkdownSplitEditor'
|
||||
import TodoListPercentage from 'browser/components/TodoListPercentage'
|
||||
import StarButton from './StarButton'
|
||||
import TagSelect from './TagSelect'
|
||||
@@ -9,21 +11,26 @@ import FolderSelect from './FolderSelect'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import { hashHistory } from 'react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import markdown from 'browser/lib/markdown'
|
||||
import markdown from 'browser/lib/markdownTextHelper'
|
||||
import StatusBar from '../StatusBar'
|
||||
import _ from 'lodash'
|
||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import TrashButton from './TrashButton'
|
||||
import FullscreenButton from './FullscreenButton'
|
||||
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||
import InfoButton from './InfoButton'
|
||||
import ToggleModeButton from './ToggleModeButton'
|
||||
import InfoPanel from './InfoPanel'
|
||||
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||
import { formatDate } from 'browser/lib/date-formatter'
|
||||
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||
import striptags from 'striptags'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
const { Menu, MenuItem, dialog } = remote
|
||||
const { dialog } = remote
|
||||
|
||||
class MarkdownNoteDetail extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -36,7 +43,8 @@ class MarkdownNoteDetail extends React.Component {
|
||||
content: ''
|
||||
}, props.note),
|
||||
isLockButtonShown: false,
|
||||
isLocked: false
|
||||
isLocked: false,
|
||||
editorType: props.config.editor.type
|
||||
}
|
||||
this.dispatchTimer = null
|
||||
|
||||
@@ -72,11 +80,11 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
let { note } = this.state
|
||||
const { note } = this.state
|
||||
|
||||
note.content = this.refs.content.value
|
||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||
note.title = markdown.strip(findNoteTitle(note.content))
|
||||
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
|
||||
note.updatedAt = new Date()
|
||||
|
||||
this.setState({
|
||||
@@ -94,7 +102,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
saveNow () {
|
||||
let { note, dispatch } = this.props
|
||||
const { note, dispatch } = this.props
|
||||
clearTimeout(this.saveQueue)
|
||||
this.saveQueue = null
|
||||
|
||||
@@ -110,11 +118,11 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleFolderChange (e) {
|
||||
let { note } = this.state
|
||||
let value = this.refs.folder.value
|
||||
let splitted = value.split('-')
|
||||
let newStorageKey = splitted.shift()
|
||||
let newFolderKey = splitted.shift()
|
||||
const { note } = this.state
|
||||
const value = this.refs.folder.value
|
||||
const splitted = value.split('-')
|
||||
const newStorageKey = splitted.shift()
|
||||
const newFolderKey = splitted.shift()
|
||||
|
||||
dataApi
|
||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||
@@ -123,7 +131,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
isMovingNote: true,
|
||||
note: Object.assign({}, newNote)
|
||||
}, () => {
|
||||
let { dispatch, location } = this.props
|
||||
const { dispatch, location } = this.props
|
||||
dispatch({
|
||||
type: 'MOVE_NOTE',
|
||||
originNote: note,
|
||||
@@ -143,7 +151,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleStarButtonClick (e) {
|
||||
let { note } = this.state
|
||||
const { note } = this.state
|
||||
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||
|
||||
note.isStarred = !note.isStarred
|
||||
@@ -168,22 +176,22 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleTrashButtonClick (e) {
|
||||
let { note } = this.state
|
||||
const { note } = this.state
|
||||
const { isTrashed } = note
|
||||
|
||||
if (isTrashed) {
|
||||
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Confirm note deletion',
|
||||
detail: 'This will permanently remove this note.',
|
||||
buttons: ['Confirm', 'Cancel']
|
||||
})
|
||||
if (dialogueButtonIndex === 1) return
|
||||
let { note, dispatch } = this.props
|
||||
const { note, dispatch } = this.props
|
||||
dataApi
|
||||
.deleteNote(note.storage, note.key)
|
||||
.then((data) => {
|
||||
let dispatchHandler = () => {
|
||||
const dispatchHandler = () => {
|
||||
dispatch({
|
||||
type: 'DELETE_NOTE',
|
||||
storageKey: data.storageKey,
|
||||
@@ -205,7 +213,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleUndoButtonClick (e) {
|
||||
let { note } = this.state
|
||||
const { note } = this.state
|
||||
|
||||
note.isTrashed = false
|
||||
|
||||
@@ -230,7 +238,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
getToggleLockButton () {
|
||||
return this.state.isLocked ? 'fa-lock' : 'fa-unlock'
|
||||
return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
|
||||
}
|
||||
|
||||
handleDeleteKeyDown (e) {
|
||||
@@ -255,13 +263,50 @@ class MarkdownNoteDetail extends React.Component {
|
||||
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||
}
|
||||
|
||||
render () {
|
||||
let { data, config, location } = this.props
|
||||
let { note } = this.state
|
||||
let storageKey = note.storage
|
||||
let folderKey = note.folder
|
||||
print (e) {
|
||||
ee.emit('print')
|
||||
}
|
||||
|
||||
let options = []
|
||||
handleSwitchMode (type) {
|
||||
this.setState({ editorType: type }, () => {
|
||||
const newConfig = Object.assign({}, this.props.config)
|
||||
newConfig.editor.type = type
|
||||
ConfigManager.set(newConfig)
|
||||
})
|
||||
}
|
||||
|
||||
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}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
/>
|
||||
} else {
|
||||
return <MarkdownSplitEditor
|
||||
ref='content'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, location } = this.props
|
||||
const { note, editorType } = this.state
|
||||
const storageKey = note.storage
|
||||
const folderKey = note.folder
|
||||
|
||||
const options = []
|
||||
data.storageMap.forEach((storage, index) => {
|
||||
storage.folders.forEach((folder) => {
|
||||
options.push({
|
||||
@@ -270,7 +315,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
})
|
||||
})
|
||||
})
|
||||
let 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'>
|
||||
@@ -280,7 +325,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
@@ -297,10 +342,6 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
const detailTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<StarButton styleName='info-left-button'
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
<div styleName='info-left-top'>
|
||||
<FolderSelect styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
@@ -315,36 +356,40 @@ class MarkdownNoteDetail extends React.Component {
|
||||
value={this.state.note.tags}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
/>
|
||||
<TodoListPercentage
|
||||
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
||||
/>
|
||||
|
||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||
|
||||
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
|
||||
<StarButton
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
|
||||
{(() => {
|
||||
const faClassName = `fa ${this.getToggleLockButton()}`
|
||||
const imgSrc = `${this.getToggleLockButton()}`
|
||||
const lockButtonComponent =
|
||||
<button styleName='control-lockButton'
|
||||
onFocus={(e) => this.handleFocus(e)}
|
||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
||||
>
|
||||
<i className={faClassName} styleName='lock-button' />
|
||||
<span styleName='control-lockButton-tooltip'>
|
||||
{this.state.isLocked ? 'Unlock' : 'Lock'}
|
||||
</span>
|
||||
<img styleName='iconInfo' src={imgSrc} />
|
||||
</button>
|
||||
|
||||
return (
|
||||
this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||
)
|
||||
})()}
|
||||
|
||||
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
|
||||
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<button styleName='control-fullScreenButton'
|
||||
onMouseDown={(e) => this.handleFullScreenButton(e)}
|
||||
>
|
||||
<i className='fa fa-expand' styleName='fullScreen-button' />
|
||||
</button>
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
@@ -354,8 +399,9 @@ class MarkdownNoteDetail extends React.Component {
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
wordCount={note.content.split(' ').length}
|
||||
letterCount={note.content.length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
print={this.print}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -369,15 +415,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||
|
||||
<div styleName='body'>
|
||||
<MarkdownEditor
|
||||
ref='content'
|
||||
styleName='body-noteEditor'
|
||||
config={config}
|
||||
value={this.state.note.content}
|
||||
storageKey={this.state.note.storage}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
||||
/>
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
|
||||
<StatusBar
|
||||
|
||||
@@ -3,50 +3,42 @@
|
||||
|
||||
.root
|
||||
absolute top right bottom
|
||||
border-width 0 0 1px
|
||||
border-style solid
|
||||
border-color $ui-borderColor
|
||||
border-left 1px solid alpha(#DEDEDE, 60%)
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
box-shadow $note-detail-box-shadow
|
||||
box-shadow none
|
||||
padding 20px 40px
|
||||
|
||||
.lock-button
|
||||
padding-bottom 3px
|
||||
|
||||
.control-lockButton
|
||||
topBarButtonLight()
|
||||
top 150px
|
||||
topBarButtonRight()
|
||||
|
||||
.control-lockButton-tooltip
|
||||
tooltip()
|
||||
position fixed
|
||||
pointer-events none
|
||||
top 50px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.control-fullScreenButton
|
||||
float right
|
||||
padding 0 0 2px 0
|
||||
topBarButtonLight()
|
||||
.trashed-infopanel
|
||||
top 40px
|
||||
position relative
|
||||
|
||||
.body
|
||||
absolute left right
|
||||
left $note-detail-left-margin
|
||||
right $note-detail-right-margin
|
||||
left 0
|
||||
right 0
|
||||
top $info-height + $info-margin-under-border
|
||||
bottom $statusBar-height
|
||||
|
||||
margin 0 45px
|
||||
.body-noteEditor
|
||||
absolute top bottom left right
|
||||
|
||||
body[data-theme="white"]
|
||||
.root
|
||||
box-shadow $note-detail-box-shadow
|
||||
border none
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
border-color $ui-dark-borderColor
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
box-shadow none
|
||||
border-left 1px solid $ui-dark-borderColor
|
||||
|
||||
.control-lockButton
|
||||
topBarButtonDark()
|
||||
@@ -56,3 +48,9 @@ body[data-theme="dark"]
|
||||
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
border-left 1px solid $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
@import('DetailVars')
|
||||
|
||||
$info-height = 60px
|
||||
$info-margin-under-border = 27px
|
||||
$info-height = 50px
|
||||
$info-margin-under-border = 30px
|
||||
|
||||
.info
|
||||
absolute top left right
|
||||
left $note-detail-left-margin
|
||||
right $note-detail-right-margin
|
||||
left 0
|
||||
right 0
|
||||
height $info-height
|
||||
border-bottom $ui-border
|
||||
border-bottom 1px solid #eee
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
width 100%
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
.info-left
|
||||
float left
|
||||
padding 0 5px
|
||||
margin 0px 2px
|
||||
.info-left-top
|
||||
display inline-block
|
||||
height $info-height
|
||||
line-height $info-height
|
||||
padding 0 10px
|
||||
width 100%
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
|
||||
.info-left-top-folderSelect
|
||||
padding 0 3px
|
||||
height 34px
|
||||
line-height 26px
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
border-radius 3px
|
||||
|
||||
.info-left-button
|
||||
width 34px
|
||||
height 34px
|
||||
@@ -48,18 +46,18 @@ $info-margin-under-border = 27px
|
||||
|
||||
.info-right
|
||||
position absolute
|
||||
right 0
|
||||
top 0
|
||||
background $ui-noteDetail-backgroundColor
|
||||
right 40px
|
||||
top 60px
|
||||
bottom 1px
|
||||
padding-left 30px
|
||||
z-index 101
|
||||
|
||||
.undo-button
|
||||
width 34px
|
||||
height 34px
|
||||
border-radius 17px
|
||||
font-size 14px
|
||||
margin 15px 7px
|
||||
margin 5px 0px
|
||||
border none
|
||||
color $ui-button-color
|
||||
display flex
|
||||
@@ -72,6 +70,7 @@ $info-margin-under-border = 27px
|
||||
border-color $ui-button--active-backgroundColor
|
||||
&:hover
|
||||
background-color alpha($ui-button--hover-backgroundColor, 60%)
|
||||
transition 0.2s
|
||||
.control-lockButton-tooltip
|
||||
opacity 1
|
||||
|
||||
@@ -96,4 +95,10 @@ body[data-theme="dark"]
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
|
||||
.undo-button
|
||||
topBarButtonDark()
|
||||
topBarButtonDark()
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.info
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
|
||||
|
||||
21
browser/main/Detail/PermanentDeleteButton.js
Normal file
21
browser/main/Detail/PermanentDeleteButton.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TrashButton.styl'
|
||||
|
||||
const PermanentDeleteButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-trashButton--in-trash'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||
<span styleName='tooltip'>Permanent Delete</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
PermanentDeleteButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(PermanentDeleteButton, styles)
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './SnippetNoteDetail.styl'
|
||||
import CodeEditor from 'browser/components/CodeEditor'
|
||||
@@ -18,6 +19,7 @@ import _ from 'lodash'
|
||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import TrashButton from './TrashButton'
|
||||
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||
import InfoButton from './InfoButton'
|
||||
import InfoPanel from './InfoPanel'
|
||||
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||
@@ -60,7 +62,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
let nextNote = Object.assign({
|
||||
const nextNote = Object.assign({
|
||||
description: ''
|
||||
}, nextProps.note, {
|
||||
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
|
||||
@@ -69,7 +71,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
snippetIndex: 0,
|
||||
note: nextNote
|
||||
}, () => {
|
||||
let { snippets } = this.state.note
|
||||
const { snippets } = this.state.note
|
||||
snippets.forEach((snippet, index) => {
|
||||
this.refs['code-' + index].reload()
|
||||
})
|
||||
@@ -83,7 +85,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
let { note } = this.state
|
||||
const { note } = this.state
|
||||
|
||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||
note.description = this.refs.description.value
|
||||
@@ -105,7 +107,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
saveNow () {
|
||||
let { note, dispatch } = this.props
|
||||
const { note, dispatch } = this.props
|
||||
clearTimeout(this.saveQueue)
|
||||
this.saveQueue = null
|
||||
|
||||
@@ -121,11 +123,11 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleFolderChange (e) {
|
||||
let { note } = this.state
|
||||
let value = this.refs.folder.value
|
||||
let splitted = value.split('-')
|
||||
let newStorageKey = splitted.shift()
|
||||
let newFolderKey = splitted.shift()
|
||||
const { note } = this.state
|
||||
const value = this.refs.folder.value
|
||||
const splitted = value.split('-')
|
||||
const newStorageKey = splitted.shift()
|
||||
const newFolderKey = splitted.shift()
|
||||
|
||||
dataApi
|
||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||
@@ -134,7 +136,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
isMovingNote: true,
|
||||
note: Object.assign({}, newNote)
|
||||
}, () => {
|
||||
let { dispatch, location } = this.props
|
||||
const { dispatch, location } = this.props
|
||||
dispatch({
|
||||
type: 'MOVE_NOTE',
|
||||
originNote: note,
|
||||
@@ -154,7 +156,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleStarButtonClick (e) {
|
||||
let { note } = this.state
|
||||
const { note } = this.state
|
||||
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||
|
||||
note.isStarred = !note.isStarred
|
||||
@@ -171,22 +173,22 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleTrashButtonClick (e) {
|
||||
let { note } = this.state
|
||||
const { note } = this.state
|
||||
const { isTrashed } = note
|
||||
|
||||
if (isTrashed) {
|
||||
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Confirm note deletion',
|
||||
detail: 'This will permanently remove this note.',
|
||||
buttons: ['Confirm', 'Cancel']
|
||||
})
|
||||
if (dialogueButtonIndex === 1) return
|
||||
let { note, dispatch } = this.props
|
||||
const { note, dispatch } = this.props
|
||||
dataApi
|
||||
.deleteNote(note.storage, note.key)
|
||||
.then((data) => {
|
||||
let dispatchHandler = () => {
|
||||
const dispatchHandler = () => {
|
||||
dispatch({
|
||||
type: 'DELETE_NOTE',
|
||||
storageKey: data.storageKey,
|
||||
@@ -208,7 +210,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleUndoButtonClick (e) {
|
||||
let { note } = this.state
|
||||
const { note } = this.state
|
||||
|
||||
note.isTrashed = false
|
||||
|
||||
@@ -234,10 +236,31 @@ class SnippetNoteDetail extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleTabDragStart (e, index) {
|
||||
e.dataTransfer.setData('text/plain', index)
|
||||
}
|
||||
|
||||
handleTabDrop (e, index) {
|
||||
const oldIndex = parseInt(e.dataTransfer.getData('text'))
|
||||
|
||||
const snippets = this.state.note.snippets.slice()
|
||||
const draggedSnippet = snippets[oldIndex]
|
||||
snippets[oldIndex] = snippets[index]
|
||||
snippets[index] = draggedSnippet
|
||||
const snippetIndex = index
|
||||
|
||||
const note = Object.assign({}, this.state.note, {snippets})
|
||||
this.setState({ note, snippetIndex }, () => {
|
||||
this.save()
|
||||
this.refs['code-' + index].reload()
|
||||
this.refs['code-' + oldIndex].reload()
|
||||
})
|
||||
}
|
||||
|
||||
handleTabDeleteButtonClick (e, index) {
|
||||
if (this.state.note.snippets.length > 1) {
|
||||
if (this.state.note.snippets[index].content.trim().length > 0) {
|
||||
let dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Delete a snippet',
|
||||
detail: 'This work cannot be undone.',
|
||||
@@ -266,11 +289,16 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
renameSnippetByIndex (index, name) {
|
||||
let snippets = this.state.note.snippets.slice()
|
||||
const snippets = this.state.note.snippets.slice()
|
||||
snippets[index].name = name
|
||||
let syntax = CodeMirror.findModeByFileName(name.trim())
|
||||
let mode = syntax != null ? syntax.name : null
|
||||
if (mode != null) snippets[index].mode = mode
|
||||
const syntax = CodeMirror.findModeByFileName(name.trim())
|
||||
const mode = syntax != null ? syntax.name : null
|
||||
if (mode != null) {
|
||||
snippets[index].mode = mode
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SNIPPET_LANG', {
|
||||
name: mode
|
||||
})
|
||||
}
|
||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||
|
||||
this.setState({
|
||||
@@ -282,7 +310,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
|
||||
handleModeOptionClick (index, name) {
|
||||
return (e) => {
|
||||
let snippets = this.state.note.snippets.slice()
|
||||
const snippets = this.state.note.snippets.slice()
|
||||
snippets[index].mode = name
|
||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||
|
||||
@@ -291,12 +319,16 @@ class SnippetNoteDetail extends React.Component {
|
||||
}, () => {
|
||||
this.save()
|
||||
})
|
||||
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SELECT_LANG', {
|
||||
name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleCodeChange (index) {
|
||||
return (e) => {
|
||||
let snippets = this.state.note.snippets.slice()
|
||||
const snippets = this.state.note.snippets.slice()
|
||||
snippets[index].content = this.refs['code-' + index].value
|
||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||
this.setState({
|
||||
@@ -323,7 +355,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
break
|
||||
case 76:
|
||||
{
|
||||
let isSuper = global.process.platform === 'darwin'
|
||||
const isSuper = global.process.platform === 'darwin'
|
||||
? e.metaKey
|
||||
: e.ctrlKey
|
||||
if (isSuper) {
|
||||
@@ -334,7 +366,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
break
|
||||
case 84:
|
||||
{
|
||||
let isSuper = global.process.platform === 'darwin'
|
||||
const isSuper = global.process.platform === 'darwin'
|
||||
? e.metaKey
|
||||
: e.ctrlKey
|
||||
if (isSuper) {
|
||||
@@ -347,7 +379,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleModeButtonClick (e, index) {
|
||||
let menu = new Menu()
|
||||
const menu = new Menu()
|
||||
CodeMirror.modeInfo.forEach((mode) => {
|
||||
menu.append(new MenuItem({
|
||||
label: mode.name,
|
||||
@@ -388,8 +420,8 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleIndentSizeItemClick (e, indentSize) {
|
||||
let { config, dispatch } = this.props
|
||||
let editor = Object.assign({}, config.editor, {
|
||||
const { config, dispatch } = this.props
|
||||
const editor = Object.assign({}, config.editor, {
|
||||
indentSize
|
||||
})
|
||||
ConfigManager.set({
|
||||
@@ -404,8 +436,8 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleIndentTypeItemClick (e, indentType) {
|
||||
let { config, dispatch } = this.props
|
||||
let editor = Object.assign({}, config.editor, {
|
||||
const { config, dispatch } = this.props
|
||||
const editor = Object.assign({}, config.editor, {
|
||||
indentType
|
||||
})
|
||||
ConfigManager.set({
|
||||
@@ -424,14 +456,14 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
addSnippet () {
|
||||
let { note } = this.state
|
||||
const { note } = this.state
|
||||
|
||||
note.snippets = note.snippets.concat([{
|
||||
name: '',
|
||||
mode: 'Plain Text',
|
||||
content: ''
|
||||
}])
|
||||
let snippetIndex = note.snippets.length - 1
|
||||
const snippetIndex = note.snippets.length - 1
|
||||
|
||||
this.setState({
|
||||
note,
|
||||
@@ -477,19 +509,19 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { data, config, location } = this.props
|
||||
let { note } = this.state
|
||||
const { data, config, location } = this.props
|
||||
const { note } = this.state
|
||||
|
||||
let storageKey = note.storage
|
||||
let folderKey = note.folder
|
||||
const storageKey = note.storage
|
||||
const folderKey = note.folder
|
||||
|
||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||
|
||||
let tabList = note.snippets.map((snippet, index) => {
|
||||
let isActive = this.state.snippetIndex === index
|
||||
const tabList = note.snippets.map((snippet, index) => {
|
||||
const isActive = this.state.snippetIndex === index
|
||||
|
||||
return <SnippetTab
|
||||
key={index}
|
||||
@@ -500,11 +532,13 @@ class SnippetNoteDetail extends React.Component {
|
||||
onDelete={(e) => this.handleTabDeleteButtonClick(e, index)}
|
||||
onRename={(name) => this.renameSnippetByIndex(index, name)}
|
||||
isDeletable={note.snippets.length > 1}
|
||||
onDragStart={(e) => this.handleTabDragStart(e, index)}
|
||||
onDrop={(e) => this.handleTabDrop(e, index)}
|
||||
/>
|
||||
})
|
||||
|
||||
let viewList = note.snippets.map((snippet, index) => {
|
||||
let isActive = this.state.snippetIndex === index
|
||||
const viewList = note.snippets.map((snippet, index) => {
|
||||
const isActive = this.state.snippetIndex === index
|
||||
|
||||
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||
@@ -532,6 +566,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
indentSize={editorIndentSize}
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
keyMap={config.editor.keyMap}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||
ref={'code-' + index}
|
||||
/>
|
||||
@@ -539,7 +574,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
</div>
|
||||
})
|
||||
|
||||
let options = []
|
||||
const options = []
|
||||
data.storageMap.forEach((storage, index) => {
|
||||
storage.folders.forEach((folder) => {
|
||||
options.push({
|
||||
@@ -548,7 +583,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
})
|
||||
})
|
||||
})
|
||||
let 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'>
|
||||
@@ -558,7 +593,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
@@ -575,10 +610,6 @@ class SnippetNoteDetail extends React.Component {
|
||||
|
||||
const detailTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<StarButton styleName='info-left-button'
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
<div styleName='info-left-top'>
|
||||
<FolderSelect styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
@@ -595,15 +626,21 @@ class SnippetNoteDetail extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<button styleName='control-fullScreenButton'
|
||||
onMouseDown={(e) => this.handleFullScreenButton(e)}
|
||||
>
|
||||
<i className='fa fa-expand' styleName='fullScreen-button' />
|
||||
</button>
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
|
||||
<StarButton
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
|
||||
<button styleName='control-fullScreenButton' title='Fullscreen'
|
||||
onMouseDown={(e) => this.handleFullScreenButton(e)}>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
|
||||
</button>
|
||||
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
|
||||
@@ -3,23 +3,21 @@
|
||||
|
||||
.root
|
||||
absolute top bottom right
|
||||
border-width 0 0 1px
|
||||
border-style solid
|
||||
border-color $ui-borderColor
|
||||
border-left 1px solid alpha(#DEDEDE, 60%)
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
box-shadow $note-detail-box-shadow
|
||||
box-shadow none
|
||||
|
||||
.body
|
||||
absolute left right
|
||||
left $note-detail-left-margin
|
||||
right $note-detail-right-margin
|
||||
left $snippet-note-detail-left-margin
|
||||
right $snippet-note-detail-right-margin
|
||||
top $info-height + $info-margin-under-border
|
||||
bottom $statusBar-height
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
|
||||
.body .description
|
||||
absolute top left right
|
||||
height 80px
|
||||
height 50px
|
||||
|
||||
.body .description textarea
|
||||
outline none
|
||||
@@ -27,14 +25,14 @@
|
||||
height 100%
|
||||
width 100%
|
||||
resize none
|
||||
border none
|
||||
padding 10px
|
||||
border 1px solid $ui-borderColor
|
||||
padding 2px 5px
|
||||
line-height 1.6
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
|
||||
.tabList
|
||||
absolute left right
|
||||
top 80px
|
||||
top 55px
|
||||
height 30px
|
||||
display flex
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
@@ -45,35 +43,42 @@
|
||||
overflow hidden
|
||||
|
||||
.tabList .plusButton
|
||||
navButtonColor()
|
||||
navWhiteButtonColor()
|
||||
width 30px
|
||||
|
||||
.tabView
|
||||
absolute left right bottom
|
||||
top 130px
|
||||
top 100px
|
||||
|
||||
.tabView-content
|
||||
absolute top left right bottom
|
||||
|
||||
.override
|
||||
absolute bottom left
|
||||
bottom 1px
|
||||
left 60px
|
||||
height 23px
|
||||
z-index 1
|
||||
z-index 101
|
||||
button
|
||||
navButtonColor()
|
||||
height 24px
|
||||
padding 0 6px
|
||||
&:hover
|
||||
color $ui-active-color
|
||||
&:active .update-icon
|
||||
color white
|
||||
color $ui-active-color
|
||||
|
||||
.control-fullScreenButton
|
||||
float right
|
||||
padding 0 0 2px 0
|
||||
topBarButtonLight()
|
||||
top 80px
|
||||
margin-bottom 10px
|
||||
topBarButtonRight()
|
||||
|
||||
body[data-theme="white"]
|
||||
.root
|
||||
box-shadow $note-detail-box-shadow
|
||||
border none
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
border-color $ui-dark-borderColor
|
||||
border-left 1px solid $ui-dark-borderColor
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
box-shadow none
|
||||
|
||||
@@ -83,6 +88,7 @@ body[data-theme="dark"]
|
||||
.body .description textarea
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
border 1px solid $ui-dark-borderColor
|
||||
|
||||
.tabList
|
||||
background-color $ui-button--active-backgroundColor
|
||||
@@ -103,3 +109,20 @@ body[data-theme="dark"]
|
||||
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
border-left 1px solid $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
|
||||
.body
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
|
||||
.body .description textarea
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
border 1px solid $ui-solarized-dark-borderColor
|
||||
|
||||
.tabList
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StarButton.styl'
|
||||
import _ from 'lodash'
|
||||
@@ -31,7 +32,7 @@ class StarButton extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { className } = this.props
|
||||
const { className } = this.props
|
||||
|
||||
return (
|
||||
<button className={_.isString(className)
|
||||
@@ -45,14 +46,14 @@ class StarButton extends React.Component {
|
||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<i styleName='icon'
|
||||
className={this.state.isActive || this.props.isActive
|
||||
? 'fa fa-star'
|
||||
: 'fa fa-star-o'
|
||||
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 styleName='tooltip'>Star</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,47 +1,40 @@
|
||||
.root
|
||||
left 7px
|
||||
top 0
|
||||
padding 0
|
||||
color alpha($ui-favorite-star-button-color, 60%)
|
||||
top 45px
|
||||
topBarButtonRight()
|
||||
&:hover
|
||||
transition 0.15s
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-favorite-star-button-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-favorite-star-button-color
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
right 0
|
||||
width 100%
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.root--active
|
||||
@extend .root
|
||||
transition 0.15s
|
||||
color $ui-favorite-star-button-color
|
||||
&:hover
|
||||
transition 0.15s
|
||||
color $ui-favorite-star-button-color
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
&:active
|
||||
transition 0.15s
|
||||
color $ui-favorite-star-button-color
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
|
||||
.icon
|
||||
transition transform 0.15s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
topBarButtonDark()
|
||||
&:hover
|
||||
transition 0.15s
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
color $ui-favorite-star-button-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
color $ui-favorite-star-button-color
|
||||
|
||||
.root--active
|
||||
@extend .root
|
||||
color $ui-favorite-star-button-color
|
||||
&:hover
|
||||
transition 0.15s
|
||||
color $ui-favorite-star-button-color
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TagSelect.styl'
|
||||
import _ from 'lodash'
|
||||
@@ -37,6 +38,10 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleNewTagBlur (e) {
|
||||
this.submitTag()
|
||||
}
|
||||
|
||||
removeLastTag () {
|
||||
let { value } = this.props
|
||||
|
||||
@@ -59,7 +64,7 @@ class TagSelect extends React.Component {
|
||||
submitTag () {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
||||
let { value } = this.props
|
||||
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
|
||||
const newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
|
||||
|
||||
if (newTag.length <= 0) {
|
||||
this.setState({
|
||||
@@ -101,9 +106,9 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { value, className } = this.props
|
||||
const { value, className } = this.props
|
||||
|
||||
let tagList = _.isArray(value)
|
||||
const tagList = _.isArray(value)
|
||||
? value.map((tag) => {
|
||||
return (
|
||||
<span styleName='tag'
|
||||
@@ -113,7 +118,7 @@ class TagSelect extends React.Component {
|
||||
<button styleName='tag-removeButton'
|
||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)}
|
||||
>
|
||||
<i className='fa fa-times fa-fw tag-removeButton-icon' />
|
||||
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
@@ -134,6 +139,7 @@ class TagSelect extends React.Component {
|
||||
placeholder='Add tag...'
|
||||
onChange={(e) => this.handleNewTagInputChange(e)}
|
||||
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
|
||||
onBlur={(e) => this.handleNewTagBlur(e)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,61 +1,53 @@
|
||||
.root
|
||||
display inline-block
|
||||
display flex
|
||||
align-items center
|
||||
user-select none
|
||||
height 23px
|
||||
vertical-align middle
|
||||
width 300px
|
||||
width 100%
|
||||
overflow-x scroll
|
||||
white-space nowrap
|
||||
margin-right 10px
|
||||
|
||||
.root::-webkit-scrollbar
|
||||
display none
|
||||
|
||||
.tag
|
||||
display inline-block
|
||||
margin 1px 3px
|
||||
padding 0
|
||||
height 20px
|
||||
background-color alpha($ui-tag-backgroundColor, 10%)
|
||||
border-radius 3px
|
||||
overflow hidden
|
||||
display flex
|
||||
align-items center
|
||||
margin 0px 2px
|
||||
padding 2px 4px
|
||||
background-color alpha($ui-tag-backgroundColor, 3%)
|
||||
border-radius 4px
|
||||
position relative
|
||||
clearfix()
|
||||
|
||||
.tag-removeButton
|
||||
float right
|
||||
height 20px
|
||||
width 18px
|
||||
margin 0
|
||||
padding 0
|
||||
border-style solid
|
||||
border-width 0
|
||||
border-radius 20px
|
||||
line-height 18px
|
||||
background-color transparent
|
||||
color $ui-button-color
|
||||
position absolute
|
||||
right 6px
|
||||
|
||||
.tag-removeButton-icon
|
||||
width 5px
|
||||
padding-right 4px
|
||||
|
||||
.tag-label
|
||||
font-size 11px
|
||||
font-weight 600
|
||||
color: alpha($ui-text-color, 80%)
|
||||
float left
|
||||
height 20px
|
||||
line-height 20px
|
||||
padding 0 6px
|
||||
font-size 13px
|
||||
color: $ui-text-color
|
||||
padding 4px 16px 4px 8px
|
||||
|
||||
.newTag
|
||||
display inline-block
|
||||
margin 2px 0 15px 2px
|
||||
vertical-align middle
|
||||
height 18px
|
||||
box-sizing borde-box
|
||||
box-sizing border-box
|
||||
border none
|
||||
background-color transparent
|
||||
outline none
|
||||
padding 0 4px
|
||||
font-size 13px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.tag
|
||||
@@ -72,4 +64,20 @@ body[data-theme="dark"]
|
||||
.newTag
|
||||
border-color none
|
||||
background-color transparent
|
||||
color $ui-dark-text-color
|
||||
color $ui-dark-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.tag
|
||||
background-color $ui-solarized-dark-tag-backgroundColor
|
||||
|
||||
.tag-removeButton
|
||||
border-color $ui-button--focus-borderColor
|
||||
background-color transparent
|
||||
|
||||
.tag-label
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.newTag
|
||||
border-color none
|
||||
background-color transparent
|
||||
color $ui-solarized-dark-text-color
|
||||
25
browser/main/Detail/ToggleModeButton.js
Normal file
25
browser/main/Detail/ToggleModeButton.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ToggleModeButton.styl'
|
||||
|
||||
const ToggleModeButton = ({
|
||||
onClick, editorType
|
||||
}) => (
|
||||
<div styleName='control-toggleModeButton'>
|
||||
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
|
||||
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-split-on.svg' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
||||
</div>
|
||||
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
|
||||
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : '../resources/icon/icon-mode-markdown-off.svg'} />
|
||||
</div>
|
||||
<span styleName='tooltip'>Toggle Mode</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
ToggleModeButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
editorType: PropTypes.string.Required
|
||||
}
|
||||
|
||||
export default CSSModules(ToggleModeButton, styles)
|
||||
61
browser/main/Detail/ToggleModeButton.styl
Normal file
61
browser/main/Detail/ToggleModeButton.styl
Normal file
@@ -0,0 +1,61 @@
|
||||
.control-toggleModeButton
|
||||
border 1px solid #eee
|
||||
height 34px
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
div
|
||||
width 40px
|
||||
height 100%
|
||||
background-color #f9f9f9
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
cursor pointer
|
||||
|
||||
&:first-child
|
||||
border-right 1px solid #eee
|
||||
.active
|
||||
background-color #fff
|
||||
box-shadow 2px 0px 7px #eee
|
||||
z-index 1
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 47px
|
||||
right 11px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
.control-toggleModeButton
|
||||
border 1px solid #444444
|
||||
div
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
&:first-child
|
||||
border-right 1px solid #444444
|
||||
.active
|
||||
background-color #3A404C
|
||||
box-shadow 2px 0px 7px #444444
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.control-toggleModeButton
|
||||
border 1px solid #586E75
|
||||
div
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
&:first-child
|
||||
border-right 1px solid #586E75
|
||||
.active
|
||||
background-color #002B36
|
||||
box-shadow 2px 0px 7px #222222
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TrashButton.styl'
|
||||
|
||||
@@ -8,7 +9,8 @@ const TrashButton = ({
|
||||
<button styleName='control-trashButton'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<i className='fa fa-trash trashButton' styleName='info-button' />
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||
<span styleName='tooltip'>Trash</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
.control-trashButton
|
||||
float right
|
||||
topBarButtonLight()
|
||||
top 115px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
right 0
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.control-trashButton--in-trash
|
||||
top 60px
|
||||
topBarButtonRight()
|
||||
|
||||
.trashButton
|
||||
padding 0px
|
||||
margin 15px 0
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-trashButton
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './Detail.styl'
|
||||
import _ from 'lodash'
|
||||
@@ -32,12 +33,12 @@ class Detail extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { location, data, config } = this.props
|
||||
const { location, data, config } = this.props
|
||||
let note = null
|
||||
if (location.query.key != null) {
|
||||
let splitted = location.query.key.split('-')
|
||||
let storageKey = splitted.shift()
|
||||
let noteKey = splitted.shift()
|
||||
const splitted = location.query.key.split('-')
|
||||
const storageKey = splitted.shift()
|
||||
const noteKey = splitted.shift()
|
||||
|
||||
note = data.noteMap.get(storageKey + '-' + noteKey)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './Main.styl'
|
||||
import { connect } from 'react-redux'
|
||||
@@ -11,15 +12,11 @@ import _ from 'lodash'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import modal from 'browser/main/lib/modal'
|
||||
import InitModal from 'browser/main/modals/InitModal'
|
||||
import mixpanel from 'browser/main/lib/mixpanel'
|
||||
import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
|
||||
function focused () {
|
||||
mixpanel.track('MAIN_FOCUSED')
|
||||
}
|
||||
|
||||
class Main extends React.Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
@@ -27,7 +24,7 @@ class Main extends React.Component {
|
||||
mobileAnalytics.initAwsMobileAnalytics()
|
||||
}
|
||||
|
||||
let { config } = props
|
||||
const { config } = props
|
||||
|
||||
this.state = {
|
||||
isRightSliderFocused: false,
|
||||
@@ -43,7 +40,7 @@ class Main extends React.Component {
|
||||
}
|
||||
|
||||
getChildContext () {
|
||||
let { status, config } = this.props
|
||||
const { status, config } = this.props
|
||||
|
||||
return {
|
||||
status,
|
||||
@@ -52,10 +49,14 @@ class Main extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
let { dispatch, config } = this.props
|
||||
const { dispatch, config } = this.props
|
||||
|
||||
if (config.ui.theme === 'dark') {
|
||||
document.body.setAttribute('data-theme', 'dark')
|
||||
} else if (config.ui.theme === 'white') {
|
||||
document.body.setAttribute('data-theme', 'white')
|
||||
} else if (config.ui.theme === 'solarized-dark') {
|
||||
document.body.setAttribute('data-theme', 'solarized-dark')
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'default')
|
||||
}
|
||||
@@ -75,11 +76,9 @@ class Main extends React.Component {
|
||||
})
|
||||
|
||||
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
||||
window.addEventListener('focus', focused)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('focus', focused)
|
||||
eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
|
||||
}
|
||||
|
||||
@@ -103,8 +102,8 @@ class Main extends React.Component {
|
||||
this.setState({
|
||||
isRightSliderFocused: false
|
||||
}, () => {
|
||||
let { dispatch } = this.props
|
||||
let newListWidth = this.state.listWidth
|
||||
const { dispatch } = this.props
|
||||
const newListWidth = this.state.listWidth
|
||||
// TODO: ConfigManager should dispatch itself.
|
||||
ConfigManager.set({listWidth: newListWidth})
|
||||
dispatch({
|
||||
@@ -119,8 +118,8 @@ class Main extends React.Component {
|
||||
this.setState({
|
||||
isLeftSliderFocused: false
|
||||
}, () => {
|
||||
let { dispatch } = this.props
|
||||
let navWidth = this.state.navWidth
|
||||
const { dispatch } = this.props
|
||||
const navWidth = this.state.navWidth
|
||||
// TODO: ConfigManager should dispatch itself.
|
||||
ConfigManager.set({ navWidth })
|
||||
dispatch({
|
||||
@@ -133,7 +132,7 @@ class Main extends React.Component {
|
||||
|
||||
handleMouseMove (e) {
|
||||
if (this.state.isRightSliderFocused) {
|
||||
let offset = this.refs.body.getBoundingClientRect().left
|
||||
const offset = this.refs.body.getBoundingClientRect().left
|
||||
let newListWidth = e.pageX - offset
|
||||
if (newListWidth < 10) {
|
||||
newListWidth = 10
|
||||
@@ -186,7 +185,10 @@ class Main extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { config } = this.props
|
||||
const { config } = this.props
|
||||
|
||||
// the width of the navigation bar when it is folded/collapsed
|
||||
const foldedNavigationWidth = 44
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -216,7 +218,7 @@ class Main extends React.Component {
|
||||
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
||||
id='main-body'
|
||||
ref='body'
|
||||
style={{left: config.isSideNavFolded ? 44 : this.state.navWidth}}
|
||||
style={{left: config.isSideNavFolded ? foldedNavigationWidth : this.state.navWidth}}
|
||||
>
|
||||
<TopBar style={{width: this.state.listWidth}}
|
||||
{..._.pick(this.props, [
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
height $topBar-height - 1
|
||||
margin-left: auto;
|
||||
width: 64px;
|
||||
margin-right: -15px;
|
||||
|
||||
.root--expanded
|
||||
@extend .root
|
||||
@@ -14,10 +13,8 @@ $control-height = 34px
|
||||
.control
|
||||
position absolute
|
||||
top 13px
|
||||
left 8px
|
||||
right 8px
|
||||
right 7px
|
||||
height $control-height
|
||||
overflow hidden
|
||||
display flex
|
||||
|
||||
.control-newNoteButton
|
||||
@@ -35,10 +32,11 @@ $control-height = 34px
|
||||
|
||||
.control-newNoteButton-tooltip
|
||||
tooltip()
|
||||
position fixed
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 50px
|
||||
left 433px
|
||||
top 26px
|
||||
right -43px
|
||||
width 124px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
@@ -46,6 +44,13 @@ $control-height = 34px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="white"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
.control-newNoteButton
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
@@ -66,3 +71,7 @@ body[data-theme="dark"]
|
||||
|
||||
.control-newNoteButton-tooltip
|
||||
darkTooltip()
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
@@ -1,12 +1,11 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './NewNoteButton.styl'
|
||||
import _ from 'lodash'
|
||||
import modal from 'browser/main/lib/modal'
|
||||
import NewNoteModal from 'browser/main/modals/NewNoteModal'
|
||||
import { hashHistory } from 'react-router'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
@@ -34,7 +33,7 @@ class NewNoteButton extends React.Component {
|
||||
}
|
||||
|
||||
handleNewNoteButtonClick (e) {
|
||||
const { config, location, dispatch } = this.props
|
||||
const { location, dispatch } = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
modal.open(NewNoteModal, {
|
||||
@@ -51,7 +50,7 @@ class NewNoteButton extends React.Component {
|
||||
|
||||
// Find first storage
|
||||
if (storage == null) {
|
||||
for (let kv of data.storageMap) {
|
||||
for (const kv of data.storageMap) {
|
||||
storage = kv[1]
|
||||
break
|
||||
}
|
||||
@@ -85,7 +84,7 @@ class NewNoteButton extends React.Component {
|
||||
<div styleName='control'>
|
||||
<button styleName='control-newNoteButton'
|
||||
onClick={(e) => this.handleNewNoteButtonClick(e)}>
|
||||
<i className='fa fa-pencil-square-o' />
|
||||
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
|
||||
<span styleName='control-newNoteButton-tooltip'>
|
||||
Make a Note {OSX ? '⌘' : '^'} + n
|
||||
</span>
|
||||
|
||||
@@ -21,14 +21,14 @@ $control-height = 30px
|
||||
|
||||
.control-sortBy-select
|
||||
appearance: none;
|
||||
margin-left 3px
|
||||
margin-left 5px
|
||||
color $ui-inactive-text-color
|
||||
padding 0
|
||||
border none
|
||||
background-color transparent
|
||||
outline none
|
||||
cursor pointer
|
||||
font-size 11px
|
||||
font-size 12px
|
||||
&:hover
|
||||
transition 0.2s
|
||||
color $ui-text-color
|
||||
@@ -59,6 +59,13 @@ $control-height = 30px
|
||||
top $control-height
|
||||
overflow auto
|
||||
|
||||
body[data-theme="white"]
|
||||
.root
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
.control
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
border-color $ui-dark-borderColor
|
||||
@@ -81,4 +88,29 @@ body[data-theme="dark"]
|
||||
.control-button--active
|
||||
color $ui-dark-text-color
|
||||
&:active
|
||||
color $ui-dark-text-color
|
||||
color $ui-dark-text-color
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
|
||||
.control
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
|
||||
.control-sortBy-select
|
||||
&:hover
|
||||
transition 0.2s
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.control-button
|
||||
color $ui-solarized-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.control-button--active
|
||||
color $ui-solarized-dark-text-color
|
||||
&:active
|
||||
color $ui-solarized-dark-text-color
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './NoteList.styl'
|
||||
import moment from 'moment'
|
||||
@@ -11,8 +12,10 @@ import NoteItemSimple from 'browser/components/NoteItemSimple'
|
||||
import searchFromNotes from 'browser/lib/search'
|
||||
import fs from 'fs'
|
||||
import { hashHistory } from 'react-router'
|
||||
import markdown from 'browser/lib/markdown'
|
||||
import markdown from 'browser/lib/markdownTextHelper'
|
||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||
import store from 'browser/main/store'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { Menu, MenuItem, dialog } = remote
|
||||
@@ -29,6 +32,18 @@ function sortByUpdatedAt (a, b) {
|
||||
return new Date(b.updatedAt) - new Date(a.updatedAt)
|
||||
}
|
||||
|
||||
function findNoteByKey (notes, noteKey) {
|
||||
return notes.find((note) => `${note.storage}-${note.key}` === noteKey)
|
||||
}
|
||||
|
||||
function findNotesByKeys (notes, noteKeys) {
|
||||
return notes.filter((note) => noteKeys.includes(getNoteKey(note)))
|
||||
}
|
||||
|
||||
function getNoteKey (note) {
|
||||
return `${note.storage}-${note.key}`
|
||||
}
|
||||
|
||||
class NoteList extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
@@ -48,9 +63,19 @@ class NoteList extends React.Component {
|
||||
}
|
||||
this.importFromFileHandler = this.importFromFile.bind(this)
|
||||
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
|
||||
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
|
||||
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
|
||||
this.deleteNote = this.deleteNote.bind(this)
|
||||
this.focusNote = this.focusNote.bind(this)
|
||||
this.pinToTop = this.pinToTop.bind(this)
|
||||
|
||||
// TODO: not Selected noteKeys but SelectedNote(for reusing)
|
||||
this.state = {
|
||||
shiftKeyDown: false,
|
||||
selectedNoteKeys: []
|
||||
}
|
||||
|
||||
this.contextNotes = []
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -85,10 +110,11 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
let { location } = this.props
|
||||
const { location } = this.props
|
||||
|
||||
if (this.notes.length > 0 && location.query.key == null) {
|
||||
let { router } = this.context
|
||||
const { router } = this.context
|
||||
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
|
||||
router.replace({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
@@ -100,20 +126,18 @@ class NoteList extends React.Component {
|
||||
|
||||
// Auto scroll
|
||||
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
|
||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note != null && note.storage + '-' + note.key === location.query.key
|
||||
})
|
||||
const targetIndex = this.getTargetIndex()
|
||||
if (targetIndex > -1) {
|
||||
let list = this.refs.list
|
||||
let item = list.childNodes[targetIndex]
|
||||
const list = this.refs.list
|
||||
const item = list.childNodes[targetIndex]
|
||||
|
||||
if (item == null) return false
|
||||
|
||||
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
||||
const overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
||||
if (overflowBelow) {
|
||||
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
|
||||
}
|
||||
let overflowAbove = list.scrollTop > item.offsetTop
|
||||
const overflowAbove = list.scrollTop > item.offsetTop
|
||||
if (overflowAbove) {
|
||||
list.scrollTop = item.offsetTop
|
||||
}
|
||||
@@ -121,29 +145,54 @@ class NoteList extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
focusNote (selectedNoteKeys, noteKey) {
|
||||
const { router } = this.context
|
||||
const { location } = this.props
|
||||
|
||||
this.setState({
|
||||
selectedNoteKeys
|
||||
})
|
||||
|
||||
router.push({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
key: noteKey
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getNoteKeyFromTargetIndex (targetIndex) {
|
||||
const note = Object.assign({}, this.notes[targetIndex])
|
||||
const noteKey = getNoteKey(note)
|
||||
return noteKey
|
||||
}
|
||||
|
||||
selectPriorNote () {
|
||||
if (this.notes == null || this.notes.length === 0) {
|
||||
return
|
||||
}
|
||||
let { router } = this.context
|
||||
let { location } = this.props
|
||||
let { selectedNoteKeys, shiftKeyDown } = this.state
|
||||
|
||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note.storage + '-' + note.key === location.query.key
|
||||
})
|
||||
let targetIndex = this.getTargetIndex()
|
||||
|
||||
if (targetIndex === 0) {
|
||||
return
|
||||
}
|
||||
targetIndex--
|
||||
if (targetIndex < 0) targetIndex = 0
|
||||
|
||||
router.push({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
|
||||
}
|
||||
})
|
||||
if (!shiftKeyDown) { selectedNoteKeys = [] }
|
||||
const priorNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
|
||||
if (selectedNoteKeys.includes(priorNoteKey)) {
|
||||
selectedNoteKeys.pop()
|
||||
} else {
|
||||
selectedNoteKeys.push(priorNoteKey)
|
||||
}
|
||||
|
||||
this.focusNote(selectedNoteKeys, priorNoteKey)
|
||||
|
||||
ee.emit('list:moved')
|
||||
}
|
||||
|
||||
selectNextNote () {
|
||||
@@ -152,25 +201,31 @@ class NoteList extends React.Component {
|
||||
}
|
||||
let { router } = this.context
|
||||
let { location } = this.props
|
||||
let { selectedNoteKeys, shiftKeyDown } = this.state
|
||||
|
||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note.storage + '-' + note.key === location.query.key
|
||||
})
|
||||
let targetIndex = this.getTargetIndex()
|
||||
const isTargetLastNote = targetIndex === this.notes.length - 1
|
||||
|
||||
if (targetIndex === this.notes.length - 1) {
|
||||
if (isTargetLastNote && shiftKeyDown) {
|
||||
return
|
||||
} else if (isTargetLastNote) {
|
||||
targetIndex = 0
|
||||
} else {
|
||||
targetIndex++
|
||||
if (targetIndex < 0) targetIndex = 0
|
||||
else if (targetIndex > this.notes.length - 1) targetIndex === this.notes.length - 1
|
||||
else if (targetIndex > this.notes.length - 1) targetIndex = this.notes.length - 1
|
||||
}
|
||||
|
||||
router.push({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
|
||||
}
|
||||
})
|
||||
if (!shiftKeyDown) { selectedNoteKeys = [] }
|
||||
const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
|
||||
if (selectedNoteKeys.includes(nextNoteKey)) {
|
||||
selectedNoteKeys.pop()
|
||||
} else {
|
||||
selectedNoteKeys.push(nextNoteKey)
|
||||
}
|
||||
|
||||
this.focusNote(selectedNoteKeys, nextNoteKey)
|
||||
|
||||
ee.emit('list:moved')
|
||||
}
|
||||
|
||||
@@ -183,23 +238,21 @@ class NoteList extends React.Component {
|
||||
const { router } = this.context
|
||||
const { location } = this.props
|
||||
|
||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note.storage + '-' + note.key === noteHash
|
||||
})
|
||||
let targetIndex = this.getTargetIndex()
|
||||
|
||||
if (targetIndex < 0) targetIndex = 0
|
||||
|
||||
router.push({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
|
||||
}
|
||||
})
|
||||
const selectedNoteKeys = []
|
||||
const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
|
||||
selectedNoteKeys.push(nextNoteKey)
|
||||
|
||||
this.focusNote(selectedNoteKeys, nextNoteKey)
|
||||
|
||||
ee.emit('list:moved')
|
||||
}
|
||||
|
||||
handleNoteListKeyDown (e) {
|
||||
const { shiftKeyDown } = this.state
|
||||
if (e.metaKey || e.ctrlKey) return true
|
||||
|
||||
if (e.keyCode === 65 && !e.shiftKey) {
|
||||
@@ -209,7 +262,7 @@ class NoteList extends React.Component {
|
||||
|
||||
if (e.keyCode === 68) {
|
||||
e.preventDefault()
|
||||
ee.emit('detail:delete')
|
||||
this.deleteNote()
|
||||
}
|
||||
|
||||
if (e.keyCode === 69) {
|
||||
@@ -226,60 +279,110 @@ class NoteList extends React.Component {
|
||||
e.preventDefault()
|
||||
this.selectNextNote()
|
||||
}
|
||||
|
||||
if (e.shiftKey) {
|
||||
this.setState({ shiftKeyDown: true })
|
||||
}
|
||||
}
|
||||
|
||||
handleNoteListKeyUp (e) {
|
||||
if (!e.shiftKey) {
|
||||
this.setState({ shiftKeyDown: false })
|
||||
}
|
||||
}
|
||||
|
||||
getNotes () {
|
||||
let { data, params, location } = this.props
|
||||
let { router } = this.context
|
||||
const { data, params, location } = this.props
|
||||
|
||||
if (location.pathname.match(/\/home/)) {
|
||||
return data.noteMap.map((note) => note)
|
||||
if (location.pathname.match(/\/home/) || location.pathname.match(/\alltags/)) {
|
||||
const allNotes = data.noteMap.map((note) => note)
|
||||
this.contextNotes = allNotes
|
||||
return allNotes
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/starred/)) {
|
||||
return data.starredSet.toJS()
|
||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
const starredNotes = data.starredSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
this.contextNotes = starredNotes
|
||||
return starredNotes
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/searched/)) {
|
||||
const searchInputText = document.getElementsByClassName('searchInput')[0].value
|
||||
if (searchInputText === '') {
|
||||
router.push('/home')
|
||||
return this.sortByPin(this.contextNotes)
|
||||
}
|
||||
return searchFromNotes(this.notes, searchInputText)
|
||||
return searchFromNotes(this.contextNotes, searchInputText)
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/trashed/)) {
|
||||
return data.trashedSet.toJS()
|
||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
this.contextNotes = trashedNotes
|
||||
return trashedNotes
|
||||
}
|
||||
|
||||
let storageKey = params.storageKey
|
||||
let folderKey = params.folderKey
|
||||
let storage = data.storageMap.get(storageKey)
|
||||
if (storage == null) return []
|
||||
|
||||
let folder = _.find(storage.folders, {key: folderKey})
|
||||
if (folder == null) {
|
||||
let storageNoteSet = data.storageNoteMap
|
||||
.get(storage.key)
|
||||
if (storageNoteSet == null) storageNoteSet = []
|
||||
return storageNoteSet
|
||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
if (location.pathname.match(/\/tags/)) {
|
||||
return data.noteMap.map(note => {
|
||||
return note
|
||||
}).filter(note => {
|
||||
return note.tags.includes(params.tagname)
|
||||
})
|
||||
}
|
||||
|
||||
let folderNoteKeyList = data.folderNoteMap
|
||||
.get(storage.key + '-' + folder.key)
|
||||
return this.getContextNotes()
|
||||
}
|
||||
|
||||
return folderNoteKeyList != null
|
||||
? folderNoteKeyList
|
||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
: []
|
||||
// get notes in the current folder
|
||||
getContextNotes () {
|
||||
const { data, params } = this.props
|
||||
const storageKey = params.storageKey
|
||||
const folderKey = params.folderKey
|
||||
const storage = data.storageMap.get(storageKey)
|
||||
if (storage === undefined) return []
|
||||
|
||||
const folder = _.find(storage.folders, {key: folderKey})
|
||||
if (folder === undefined) {
|
||||
const storageNoteSet = data.storageNoteMap.get(storage.key) || []
|
||||
return storageNoteSet.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
}
|
||||
|
||||
const folderNoteKeyList = data.folderNoteMap.get(`${storage.key}-${folder.key}`) || []
|
||||
return folderNoteKeyList.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
}
|
||||
|
||||
sortByPin (unorderedNotes) {
|
||||
const pinnedNotes = []
|
||||
const unpinnedNotes = []
|
||||
|
||||
unorderedNotes.forEach((note) => {
|
||||
if (note.isPinned) {
|
||||
pinnedNotes.push(note)
|
||||
} else {
|
||||
unpinnedNotes.push(note)
|
||||
}
|
||||
})
|
||||
|
||||
return pinnedNotes.concat(unpinnedNotes)
|
||||
}
|
||||
|
||||
handleNoteClick (e, uniqueKey) {
|
||||
let { router } = this.context
|
||||
let { location } = this.props
|
||||
let { shiftKeyDown, selectedNoteKeys } = this.state
|
||||
|
||||
if (shiftKeyDown && selectedNoteKeys.includes(uniqueKey)) {
|
||||
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
|
||||
this.setState({
|
||||
selectedNoteKeys: newSelectedNoteKeys
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!shiftKeyDown) {
|
||||
selectedNoteKeys = []
|
||||
}
|
||||
selectedNoteKeys.push(uniqueKey)
|
||||
this.setState({
|
||||
selectedNoteKeys
|
||||
})
|
||||
|
||||
router.push({
|
||||
pathname: location.pathname,
|
||||
@@ -290,9 +393,9 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
handleSortByChange (e) {
|
||||
let { dispatch } = this.props
|
||||
const { dispatch } = this.props
|
||||
|
||||
let config = {
|
||||
const config = {
|
||||
sortBy: e.target.value
|
||||
}
|
||||
|
||||
@@ -304,9 +407,9 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
handleListStyleButtonClick (e, style) {
|
||||
let { dispatch } = this.props
|
||||
const { dispatch } = this.props
|
||||
|
||||
let config = {
|
||||
const config = {
|
||||
listStyle: style
|
||||
}
|
||||
|
||||
@@ -318,10 +421,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
alertIfSnippet () {
|
||||
let { location } = this.props
|
||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return `${note.storage}-${note.key}` === location.query.key
|
||||
})
|
||||
const targetIndex = this.getTargetIndex()
|
||||
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
@@ -333,13 +433,129 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
handleDragStart (e, note) {
|
||||
const noteData = JSON.stringify(note)
|
||||
const { selectedNoteKeys } = this.state
|
||||
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||
const noteData = JSON.stringify(selectedNotes)
|
||||
e.dataTransfer.setData('note', noteData)
|
||||
this.setState({ selectedNoteKeys: [] })
|
||||
}
|
||||
|
||||
handleNoteContextMenu (e, uniqueKey) {
|
||||
const { location } = this.props
|
||||
const { selectedNoteKeys } = this.state
|
||||
const note = findNoteByKey(this.notes, uniqueKey)
|
||||
const noteKey = getNoteKey(note)
|
||||
|
||||
if (selectedNoteKeys.length === 0 || !selectedNoteKeys.includes(noteKey)) {
|
||||
this.handleNoteClick(e, uniqueKey)
|
||||
}
|
||||
|
||||
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
|
||||
const deleteLabel = 'Delete Note'
|
||||
|
||||
const menu = new Menu()
|
||||
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
|
||||
menu.append(new MenuItem({
|
||||
label: pinLabel,
|
||||
click: this.pinToTop
|
||||
}))
|
||||
}
|
||||
menu.append(new MenuItem({
|
||||
label: deleteLabel,
|
||||
click: this.deleteNote
|
||||
}))
|
||||
menu.popup()
|
||||
}
|
||||
|
||||
pinToTop () {
|
||||
const { selectedNoteKeys } = this.state
|
||||
const { dispatch } = this.props
|
||||
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||
|
||||
Promise.all(
|
||||
selectedNotes.map((note) => {
|
||||
note.isPinned = !note.isPinned
|
||||
return dataApi
|
||||
.updateNote(note.storage, note.key, note)
|
||||
})
|
||||
)
|
||||
.then((updatedNotes) => {
|
||||
updatedNotes.forEach((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note
|
||||
})
|
||||
})
|
||||
})
|
||||
this.setState({ selectedNoteKeys: [] })
|
||||
}
|
||||
|
||||
deleteNote () {
|
||||
const { dispatch } = this.props
|
||||
const { selectedNoteKeys } = this.state
|
||||
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||
const firstNote = selectedNotes[0]
|
||||
|
||||
if (firstNote.isTrashed) {
|
||||
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note'
|
||||
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Confirm note deletion',
|
||||
detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`,
|
||||
buttons: ['Confirm', 'Cancel']
|
||||
})
|
||||
if (dialogueButtonIndex === 1) return
|
||||
Promise.all(
|
||||
selectedNoteKeys.map((uniqueKey) => {
|
||||
const storageKey = uniqueKey.split('-')[0]
|
||||
const noteKey = uniqueKey.split('-')[1]
|
||||
return dataApi
|
||||
.deleteNote(storageKey, noteKey)
|
||||
})
|
||||
)
|
||||
.then((data) => {
|
||||
data.forEach((item) => {
|
||||
dispatch({
|
||||
type: 'DELETE_NOTE',
|
||||
storageKey: item.storageKey,
|
||||
noteKey: item.noteKey
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Cannot Delete note: ' + err)
|
||||
})
|
||||
console.log('Notes were all deleted')
|
||||
} else {
|
||||
Promise.all(
|
||||
selectedNotes.map((note) => {
|
||||
note.isTrashed = true
|
||||
|
||||
return dataApi
|
||||
.updateNote(note.storage, note.key, note)
|
||||
})
|
||||
)
|
||||
.then((newNotes) => {
|
||||
newNotes.forEach((newNote) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: newNote
|
||||
})
|
||||
})
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||
console.log('Notes went to trash')
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Notes could not go to trash: ' + err)
|
||||
})
|
||||
}
|
||||
this.setState({ selectedNoteKeys: [] })
|
||||
}
|
||||
|
||||
importFromFile () {
|
||||
const { dispatch, location } = this.props
|
||||
|
||||
const options = {
|
||||
filters: [
|
||||
{ name: 'Documents', extensions: ['md', 'txt'] }
|
||||
@@ -347,26 +563,41 @@ class NoteList extends React.Component {
|
||||
properties: ['openFile', 'multiSelections']
|
||||
}
|
||||
|
||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note !== null && `${note.storage}-${note.key}` === location.query.key
|
||||
})
|
||||
|
||||
const storageKey = this.notes[targetIndex].storage
|
||||
const folderKey = this.notes[targetIndex].folder
|
||||
|
||||
dialog.showOpenDialog(remote.getCurrentWindow(), options, (filepaths) => {
|
||||
if (filepaths === undefined) return
|
||||
filepaths.forEach((filepath) => {
|
||||
fs.readFile(filepath, (err, data) => {
|
||||
if (err) throw Error('File reading error: ', err)
|
||||
this.addNotesFromFiles(filepaths)
|
||||
})
|
||||
}
|
||||
|
||||
handleDrop (e) {
|
||||
e.preventDefault()
|
||||
const { location } = this.props
|
||||
const filepaths = Array.from(e.dataTransfer.files).map(file => { return file.path })
|
||||
if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths)
|
||||
}
|
||||
|
||||
// Add notes to the current folder
|
||||
addNotesFromFiles (filepaths) {
|
||||
const { dispatch, location } = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
if (filepaths === undefined) return
|
||||
filepaths.forEach((filepath) => {
|
||||
fs.readFile(filepath, (err, data) => {
|
||||
if (err) throw Error('File reading error: ', err)
|
||||
|
||||
fs.stat(filepath, (err, {mtime, birthtime}) => {
|
||||
if (err) throw Error('File stat reading error: ', err)
|
||||
|
||||
const content = data.toString()
|
||||
const newNote = {
|
||||
content: content,
|
||||
folder: folderKey,
|
||||
folder: folder.key,
|
||||
title: markdown.strip(findNoteTitle(content)),
|
||||
type: 'MARKDOWN_NOTE'
|
||||
type: 'MARKDOWN_NOTE',
|
||||
createdAt: birthtime,
|
||||
updatedAt: mtime
|
||||
}
|
||||
dataApi.createNote(storageKey, newNote)
|
||||
dataApi.createNote(storage.key, newNote)
|
||||
.then((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
@@ -374,7 +605,7 @@ class NoteList extends React.Component {
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: `${note.storage}-${note.key}`}
|
||||
query: {key: getNoteKey(note)}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -382,32 +613,92 @@ class NoteList extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
getTargetIndex () {
|
||||
const { location } = this.props
|
||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return getNoteKey(note) === location.query.key
|
||||
})
|
||||
return targetIndex
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
const { data, params } = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
|
||||
// Find first storage
|
||||
if (storage == null) {
|
||||
for (const kv of data.storageMap) {
|
||||
storage = kv[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (storage == null) this.showMessageBox('No storage for importing note(s)')
|
||||
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
|
||||
if (folder == null) this.showMessageBox('No folder for importing note(s)')
|
||||
|
||||
return {
|
||||
storage,
|
||||
folder
|
||||
}
|
||||
}
|
||||
|
||||
showMessageBox (message) {
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: message,
|
||||
buttons: ['OK']
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
let { location, notes, config, dispatch } = this.props
|
||||
let { selectedNoteKeys } = this.state
|
||||
let sortFunc = config.sortBy === 'CREATED_AT'
|
||||
? sortByCreatedAt
|
||||
: config.sortBy === 'ALPHABETICAL'
|
||||
? sortByAlphabetical
|
||||
: sortByUpdatedAt
|
||||
this.notes = notes = this.getNotes()
|
||||
.sort(sortFunc)
|
||||
.filter((note) => {
|
||||
// this is for the trash box
|
||||
if (note.isTrashed !== true || location.pathname === '/trashed') return true
|
||||
})
|
||||
const sortedNotes = location.pathname.match(/\/home|\/starred|\/trash/)
|
||||
? this.getNotes().sort(sortFunc)
|
||||
: this.sortByPin(this.getNotes().sort(sortFunc))
|
||||
this.notes = notes = sortedNotes.filter((note) => {
|
||||
// this is for the trash box
|
||||
if (note.isTrashed !== true || location.pathname === '/trashed') return true
|
||||
})
|
||||
|
||||
let noteList = notes
|
||||
moment.locale('en', {
|
||||
relativeTime: {
|
||||
future: 'in %s',
|
||||
past: '%s ago',
|
||||
s: '%ds',
|
||||
ss: '%ss',
|
||||
m: '1m',
|
||||
mm: '%dm',
|
||||
h: 'an hour',
|
||||
hh: '%dh',
|
||||
d: '1d',
|
||||
dd: '%dd',
|
||||
M: '1M',
|
||||
MM: '%dM',
|
||||
y: '1Y',
|
||||
yy: '%dY'
|
||||
}
|
||||
})
|
||||
|
||||
const noteList = notes
|
||||
.map(note => {
|
||||
if (note == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const isDefault = config.listStyle === 'DEFAULT'
|
||||
const isActive = location.query.key === note.storage + '-' + note.key
|
||||
const uniqueKey = getNoteKey(note)
|
||||
const isActive = selectedNoteKeys.includes(uniqueKey)
|
||||
const dateDisplay = moment(
|
||||
config.sortBy === 'CREATED_AT'
|
||||
? note.createdAt : note.updatedAt
|
||||
).fromNow()
|
||||
).fromNow('D')
|
||||
const key = `${note.storage}-${note.key}`
|
||||
|
||||
if (isDefault) {
|
||||
@@ -416,9 +707,11 @@ class NoteList extends React.Component {
|
||||
isActive={isActive}
|
||||
note={note}
|
||||
dateDisplay={dateDisplay}
|
||||
key={key}
|
||||
key={uniqueKey}
|
||||
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
|
||||
handleNoteClick={this.handleNoteClick.bind(this)}
|
||||
handleDragStart={this.handleDragStart.bind(this)}
|
||||
pathname={location.pathname}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -427,9 +720,11 @@ class NoteList extends React.Component {
|
||||
<NoteItemSimple
|
||||
isActive={isActive}
|
||||
note={note}
|
||||
key={key}
|
||||
key={uniqueKey}
|
||||
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
|
||||
handleNoteClick={this.handleNoteClick.bind(this)}
|
||||
handleDragStart={this.handleDragStart.bind(this)}
|
||||
pathname={location.pathname}
|
||||
/>
|
||||
)
|
||||
})
|
||||
@@ -438,16 +733,17 @@ class NoteList extends React.Component {
|
||||
<div className='NoteList'
|
||||
styleName='root'
|
||||
style={this.props.style}
|
||||
onDrop={(e) => this.handleDrop(e)}
|
||||
>
|
||||
<div styleName='control'>
|
||||
<div styleName='control-sortBy'>
|
||||
<i className='fa fa-bolt' />
|
||||
<i className='fa fa-angle-down' />
|
||||
<select styleName='control-sortBy-select'
|
||||
value={config.sortBy}
|
||||
onChange={(e) => this.handleSortByChange(e)}
|
||||
>
|
||||
<option value='UPDATED_AT'>Last Updated</option>
|
||||
<option value='CREATED_AT'>Creation Time</option>
|
||||
<option value='UPDATED_AT'>Updated</option>
|
||||
<option value='CREATED_AT'>Created</option>
|
||||
<option value='ALPHABETICAL'>Alphabetically</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -458,7 +754,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
|
||||
>
|
||||
<i className='fa fa-th-large' />
|
||||
<img styleName='iconTag' src='../resources/icon/icon-column.svg' />
|
||||
</button>
|
||||
<button styleName={config.listStyle === 'SMALL'
|
||||
? 'control-button--active'
|
||||
@@ -466,7 +762,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
|
||||
>
|
||||
<i className='fa fa-list-ul' />
|
||||
<img styleName='iconTag' src='../resources/icon/icon-column-list.svg' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -474,6 +770,7 @@ class NoteList extends React.Component {
|
||||
ref='list'
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleNoteListKeyDown(e)}
|
||||
onKeyUp={this.handleNoteListKeyUp}
|
||||
>
|
||||
{noteList}
|
||||
</div>
|
||||
|
||||
24
browser/main/SideNav/ListButton.js
Normal file
24
browser/main/SideNav/ListButton.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './SwitchButton.styl'
|
||||
|
||||
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'>Notes</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
ListButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
isTagActive: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(ListButton, styles)
|
||||
19
browser/main/SideNav/PreferenceButton.js
Normal file
19
browser/main/SideNav/PreferenceButton.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './PreferenceButton.styl'
|
||||
|
||||
const PreferenceButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
|
||||
<span styleName='tooltip'>Preferences</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
PreferenceButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(PreferenceButton, styles)
|
||||
51
browser/main/SideNav/PreferenceButton.styl
Normal file
51
browser/main/SideNav/PreferenceButton.styl
Normal file
@@ -0,0 +1,51 @@
|
||||
.top-menu-preference
|
||||
navButtonColor()
|
||||
position absolute
|
||||
top 22px
|
||||
right 10px
|
||||
width 2em
|
||||
background-color transparent
|
||||
&:hover
|
||||
color $ui-button-default--active-backgroundColor
|
||||
background-color transparent
|
||||
.tooltip
|
||||
opacity 1
|
||||
&:active, &:active:hover
|
||||
color $ui-button-default--active-backgroundColor
|
||||
|
||||
body[data-theme="white"]
|
||||
.top-menu-preference
|
||||
navWhiteButtonColor()
|
||||
background-color transparent
|
||||
&:hover
|
||||
color #0B99F1
|
||||
background-color transparent
|
||||
&:active, &:active:hover
|
||||
color #0B99F1
|
||||
background-color transparent
|
||||
|
||||
body[data-theme="dark"]
|
||||
.top-menu-preference
|
||||
navDarkButtonColor()
|
||||
background-color transparent
|
||||
&:active
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
background-color transparent
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
background-color transparent
|
||||
|
||||
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
left -20px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
@@ -1,62 +1,57 @@
|
||||
.root
|
||||
absolute top left bottom
|
||||
width $sideNav-width
|
||||
background-color #f9f9f9
|
||||
background-color #2E3235
|
||||
user-select none
|
||||
color $ui-text-color
|
||||
height: 100vh
|
||||
display: flex
|
||||
flex-direction column
|
||||
|
||||
.top
|
||||
height $topBar-height
|
||||
padding-bottom 15px
|
||||
|
||||
.top-menu
|
||||
navButtonColor()
|
||||
height $topBar-height
|
||||
padding 0 15px
|
||||
font-size 12px
|
||||
width 100%
|
||||
text-align left
|
||||
&:hover
|
||||
color $ui-text-color
|
||||
&:active, &:active:hover
|
||||
color $ui-text-color
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
.switch-buttons
|
||||
background-color transparent
|
||||
border 0
|
||||
margin 24px auto 4px 14px
|
||||
display flex
|
||||
text-align center
|
||||
|
||||
|
||||
|
||||
.top-menu-label
|
||||
margin-left 5px
|
||||
overflow ellipsis
|
||||
opacity 0
|
||||
|
||||
.storageList
|
||||
absolute left right
|
||||
bottom 37px
|
||||
top 160px
|
||||
.tabBody
|
||||
flex 1
|
||||
display flex
|
||||
flex-direction column
|
||||
|
||||
.tag-title
|
||||
padding-left 15px
|
||||
padding-bottom 13px
|
||||
p
|
||||
color $ui-button-default-color
|
||||
|
||||
.tagList
|
||||
overflow-y auto
|
||||
|
||||
.storageList-empty
|
||||
padding 0 10px
|
||||
margin-top 15px
|
||||
line-height 24px
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.navToggle
|
||||
navButtonColor()
|
||||
display block
|
||||
position absolute
|
||||
right 5px
|
||||
bottom 5px
|
||||
border-radius 16.5px
|
||||
height 34px
|
||||
width 34px
|
||||
line-height 32px
|
||||
padding 0
|
||||
flex: 1
|
||||
|
||||
.root--folded
|
||||
@extend .root
|
||||
width 44px
|
||||
.storageList-empty
|
||||
white-space nowrap
|
||||
transform rotate(90deg)
|
||||
height 100vh
|
||||
width $sideNav--folded-width
|
||||
background-color #2E3235
|
||||
.switch-buttons
|
||||
display none
|
||||
.top
|
||||
height 60px
|
||||
.top-menu
|
||||
width 44px - 1
|
||||
position static
|
||||
width $sideNav--folded-width
|
||||
height 60px
|
||||
text-align center
|
||||
&:hover .top-menu-label
|
||||
transition opacity 0.15s
|
||||
@@ -65,66 +60,38 @@
|
||||
position fixed
|
||||
display inline-block
|
||||
height 30px
|
||||
left 32px
|
||||
left $sideNav--folded-width
|
||||
padding 0 10px
|
||||
margin-top -8px
|
||||
opacity 0
|
||||
margin-left 0
|
||||
overflow hidden
|
||||
background-color $ui-tooltip-backgroundColor
|
||||
z-index 10
|
||||
color white
|
||||
line-height 30px
|
||||
border-top-right-radius 2px
|
||||
border-bottom-right-radius 2px
|
||||
pointer-events none
|
||||
font-size 12px
|
||||
.menu-button, .menu-button--active
|
||||
text-align center
|
||||
&:hover .menu-button-label
|
||||
transition opacity 0.15s
|
||||
opacity 1
|
||||
font-size 13px
|
||||
.top-menu-preference
|
||||
position absolute
|
||||
left 7px
|
||||
|
||||
.menu-button-label
|
||||
position fixed
|
||||
display inline-block
|
||||
height 32px
|
||||
left 44px
|
||||
padding 0 10px
|
||||
margin-top -8px
|
||||
margin-left 0
|
||||
overflow ellipsis
|
||||
background-color $ui-tooltip-backgroundColor
|
||||
z-index 10
|
||||
color white
|
||||
line-height 32px
|
||||
border-top-right-radius 2px
|
||||
border-bottom-right-radius 2px
|
||||
pointer-events none
|
||||
opacity 0
|
||||
font-size 12px
|
||||
body[data-theme="white"]
|
||||
.root, .root--folded
|
||||
background-color #f9f9f9
|
||||
color $ui-text-color
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root, .root--folded
|
||||
border-color $ui-dark-borderColor
|
||||
border-right 1px solid $ui-dark-borderColor
|
||||
background-color $ui-dark-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
|
||||
.top
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.top-menu
|
||||
navDarkButtonColor()
|
||||
&:active
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
|
||||
.storageList-empty
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
.navToggle
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
transition 0.15s
|
||||
color $ui-dark-text-color
|
||||
body[data-theme="solarized-dark"]
|
||||
.root, .root--folded
|
||||
background-color $ui-solarized-dark-backgroundColor
|
||||
border-right 1px solid $ui-solarized-dark-borderColor
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StorageItem.styl'
|
||||
import { hashHistory } from 'react-router'
|
||||
@@ -8,6 +9,7 @@ import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import StorageItemChild from 'browser/components/StorageItem'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import _ from 'lodash'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { Menu, MenuItem, dialog } = remote
|
||||
@@ -22,7 +24,7 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleHeaderContextMenu (e) {
|
||||
let menu = new Menu()
|
||||
const menu = new Menu()
|
||||
menu.append(new MenuItem({
|
||||
label: 'Add Folder',
|
||||
click: (e) => this.handleAddFolderButtonClick(e)
|
||||
@@ -38,7 +40,7 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleUnlinkStorageClick (e) {
|
||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Unlink Storage',
|
||||
detail: 'This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
|
||||
@@ -46,7 +48,7 @@ class StorageItem extends React.Component {
|
||||
})
|
||||
|
||||
if (index === 0) {
|
||||
let { storage, dispatch } = this.props
|
||||
const { storage, dispatch } = this.props
|
||||
dataApi.removeStorage(storage.key)
|
||||
.then(() => {
|
||||
dispatch({
|
||||
@@ -67,7 +69,7 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleAddFolderButtonClick (e) {
|
||||
let { storage } = this.props
|
||||
const { storage } = this.props
|
||||
|
||||
modal.open(CreateFolderModal, {
|
||||
storage
|
||||
@@ -75,19 +77,19 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleHeaderInfoClick (e) {
|
||||
let { storage } = this.props
|
||||
const { storage } = this.props
|
||||
hashHistory.push('/storages/' + storage.key)
|
||||
}
|
||||
|
||||
handleFolderButtonClick (folderKey) {
|
||||
return (e) => {
|
||||
let { storage } = this.props
|
||||
const { storage } = this.props
|
||||
hashHistory.push('/storages/' + storage.key + '/folders/' + folderKey)
|
||||
}
|
||||
}
|
||||
|
||||
handleFolderButtonContextMenu (e, folder) {
|
||||
let menu = new Menu()
|
||||
const menu = new Menu()
|
||||
menu.append(new MenuItem({
|
||||
label: 'Rename Folder',
|
||||
click: (e) => this.handleRenameFolderClick(e, folder)
|
||||
@@ -103,7 +105,7 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleRenameFolderClick (e, folder) {
|
||||
let { storage } = this.props
|
||||
const { storage } = this.props
|
||||
modal.open(RenameFolderModal, {
|
||||
storage,
|
||||
folder
|
||||
@@ -111,7 +113,7 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleFolderDeleteClick (e, folder) {
|
||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Delete Folder',
|
||||
detail: 'This will delete all notes in the folder and can not be undone.',
|
||||
@@ -119,7 +121,7 @@ class StorageItem extends React.Component {
|
||||
})
|
||||
|
||||
if (index === 0) {
|
||||
let { storage, dispatch } = this.props
|
||||
const { storage, dispatch } = this.props
|
||||
dataApi
|
||||
.deleteFolder(storage.key, folder.key)
|
||||
.then((data) => {
|
||||
@@ -142,48 +144,57 @@ class StorageItem extends React.Component {
|
||||
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
|
||||
}
|
||||
|
||||
dropNote (storage, folder, dispatch, location, noteData) {
|
||||
noteData = noteData.filter((note) => folder.key !== note.folder)
|
||||
if (noteData.length === 0) return
|
||||
const newNoteData = noteData.map((note) => Object.assign({}, note, {storage: storage, folder: folder.key}))
|
||||
|
||||
Promise.all(
|
||||
newNoteData.map((note) => dataApi.createNote(storage.key, note))
|
||||
)
|
||||
.then((createdNoteData) => {
|
||||
createdNoteData.forEach((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`error on create notes: ${err}`)
|
||||
})
|
||||
.then(() => {
|
||||
return Promise.all(
|
||||
noteData.map((note) => dataApi.deleteNote(note.storage, note.key))
|
||||
)
|
||||
})
|
||||
.then((deletedNoteData) => {
|
||||
deletedNoteData.forEach((note) => {
|
||||
dispatch({
|
||||
type: 'DELETE_NOTE',
|
||||
storageKey: note.storageKey,
|
||||
noteKey: note.noteKey
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`error on delete notes: ${err}`)
|
||||
})
|
||||
}
|
||||
|
||||
handleDrop (e, storage, folder, dispatch, location) {
|
||||
e.target.style.opacity = '1'
|
||||
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
|
||||
const noteData = JSON.parse(e.dataTransfer.getData('note'))
|
||||
const newNoteData = Object.assign({}, noteData, {storage: storage, folder: folder.key})
|
||||
if (folder.key === noteData.folder) return
|
||||
dataApi
|
||||
.createNote(storage.key, newNoteData)
|
||||
.then((note) => {
|
||||
dataApi
|
||||
.deleteNote(noteData.storage, noteData.key)
|
||||
.then((data) => {
|
||||
let dispatchHandler = () => {
|
||||
dispatch({
|
||||
type: 'DELETE_NOTE',
|
||||
storageKey: data.storageKey,
|
||||
noteKey: data.noteKey
|
||||
})
|
||||
}
|
||||
eventEmitter.once('list:moved', dispatchHandler)
|
||||
eventEmitter.emit('list:next')
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: `${note.storage}-${note.key}`}
|
||||
})
|
||||
})
|
||||
this.dropNote(storage, folder, dispatch, location, noteData)
|
||||
}
|
||||
|
||||
render () {
|
||||
let { storage, location, isFolded, data, dispatch } = this.props
|
||||
let { folderNoteMap, trashedSet } = data
|
||||
let folderList = storage.folders.map((folder) => {
|
||||
let isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
|
||||
let noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||
const { storage, location, isFolded, data, dispatch } = this.props
|
||||
const { folderNoteMap, trashedSet } = data
|
||||
const folderList = storage.folders.map((folder) => {
|
||||
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
|
||||
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||
|
||||
let noteCount = 0
|
||||
if (noteSet) {
|
||||
@@ -211,7 +222,7 @@ class StorageItem extends React.Component {
|
||||
)
|
||||
})
|
||||
|
||||
let isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
|
||||
const isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
|
||||
|
||||
return (
|
||||
<div styleName={isFolded ? 'root--folded' : 'root'}
|
||||
@@ -226,9 +237,9 @@ class StorageItem extends React.Component {
|
||||
<button styleName='header-toggleButton'
|
||||
onMouseDown={(e) => this.handleToggleButtonClick(e)}
|
||||
>
|
||||
<i className={this.state.isOpen
|
||||
? 'fa fa-caret-down'
|
||||
: 'fa fa-caret-right'
|
||||
<img src={this.state.isOpen
|
||||
? '../resources/icon/icon-down.svg'
|
||||
: '../resources/icon/icon-right.svg'
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
@@ -237,7 +248,7 @@ class StorageItem extends React.Component {
|
||||
<button styleName='header-addFolderButton'
|
||||
onClick={(e) => this.handleAddFolderButtonClick(e)}
|
||||
>
|
||||
<i className='fa fa-plus' />
|
||||
<img styleName='iconTag' src='../resources/icon/icon-plus.svg' />
|
||||
</button>
|
||||
}
|
||||
|
||||
@@ -245,7 +256,7 @@ class StorageItem extends React.Component {
|
||||
onClick={(e) => this.handleHeaderInfoClick(e)}
|
||||
>
|
||||
<span styleName='header-info-name'>
|
||||
{isFolded ? storage.name.substring(0, 1) : storage.name}
|
||||
{isFolded ? _.truncate(storage.name, {length: 1, omission: ''}) : storage.name}
|
||||
</span>
|
||||
{isFolded &&
|
||||
<span styleName='header-info--folded-tooltip'>
|
||||
|
||||
@@ -5,27 +5,23 @@
|
||||
|
||||
.header
|
||||
position relative
|
||||
height 25px
|
||||
height 36px
|
||||
width 100%
|
||||
margin-bottom 5px
|
||||
transition 0.15s
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
.header--active
|
||||
margin-bottom 5px
|
||||
background-color $ui-button--active-backgroundColor
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
transition color background-color 0.15s
|
||||
|
||||
.header--active
|
||||
display flex
|
||||
align-items center
|
||||
.header-toggleButton
|
||||
color $ui-text-color
|
||||
|
||||
.header--active
|
||||
.header-info
|
||||
color $ui-text-color
|
||||
|
||||
.header--active
|
||||
.header-addFolderButton
|
||||
color $ui-text-color
|
||||
color #1EC38B
|
||||
|
||||
.header-toggleButton
|
||||
navButtonColor()
|
||||
@@ -38,23 +34,31 @@
|
||||
border-radius 50%
|
||||
&:hover
|
||||
transition 0.2s
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
background-color alpha($ui-button-default--hover-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
|
||||
.header-info
|
||||
navButtonColor()
|
||||
display block
|
||||
width 100%
|
||||
height 25px
|
||||
padding-left 23px
|
||||
padding-right 10px
|
||||
height 36px
|
||||
padding-left 25px
|
||||
padding-right 15px
|
||||
line-height 22px
|
||||
cursor pointer
|
||||
font-size 13px
|
||||
font-size 14px
|
||||
border none
|
||||
overflow ellipsis
|
||||
text-align left
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
font-weight 600;
|
||||
background-color transparent
|
||||
&:hover
|
||||
color #1EC38B
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
transition background-color 0.15s
|
||||
&:active, &:active:hover
|
||||
color #1EC38B
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
|
||||
.header-info-path
|
||||
font-size 10px
|
||||
@@ -63,22 +67,20 @@
|
||||
.header-addFolderButton
|
||||
navButtonColor()
|
||||
position absolute
|
||||
right 0
|
||||
right 7px
|
||||
width 25px
|
||||
height 25px
|
||||
padding 0
|
||||
border none
|
||||
margin-right 5px
|
||||
border-radius 50%
|
||||
&:hover
|
||||
transition 0.2s
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
|
||||
.root--folded
|
||||
@extend .root
|
||||
.header
|
||||
width 100%
|
||||
padding-left 5px
|
||||
.header-info
|
||||
overflow ellipsis
|
||||
padding 0 0 0 18px
|
||||
@@ -88,6 +90,7 @@
|
||||
display none
|
||||
.header-toggleButton
|
||||
width 15px
|
||||
padding-left 9px
|
||||
.header-info--folded-tooltip
|
||||
tooltip()
|
||||
position fixed
|
||||
@@ -102,6 +105,33 @@
|
||||
font-size 10px
|
||||
margin 0 5px
|
||||
|
||||
body[data-theme="white"]
|
||||
.header--active
|
||||
background-color $ui-button--active-backgroundColor
|
||||
transition color background-color 0.15s
|
||||
.header-toggleButton
|
||||
color $ui-text-color
|
||||
.header-info
|
||||
color $ui-text-color
|
||||
.header-addFolderButton
|
||||
color $ui-text-color
|
||||
|
||||
.header-toggleButton
|
||||
navWhiteButtonColor()
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
|
||||
.header-info
|
||||
navWhiteButtonColor()
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
|
||||
.header-addFolderButton
|
||||
navWhiteButtonColor()
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
|
||||
body[data-theme="dark"]
|
||||
.header--active
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
@@ -149,4 +179,8 @@ body[data-theme="dark"]
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||
&:active, &:active:hover
|
||||
color $ui-dark-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
59
browser/main/SideNav/SwitchButton.styl
Normal file
59
browser/main/SideNav/SwitchButton.styl
Normal file
@@ -0,0 +1,59 @@
|
||||
.non-active-button
|
||||
color $ui-inactive-text-color
|
||||
font-size 16px
|
||||
border 0
|
||||
background-color transparent
|
||||
transition 0.2s
|
||||
display flex
|
||||
text-align center
|
||||
margin-right 4px
|
||||
position relative
|
||||
&:hover
|
||||
color alpha(#239F86, 60%)
|
||||
.tooltip
|
||||
opacity 1
|
||||
|
||||
.active-button
|
||||
@extend .non-active-button
|
||||
color $ui-button-default--active-backgroundColor
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 22px
|
||||
left -2px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="white"]
|
||||
.non-active-button
|
||||
color $ui-inactive-text-color
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.tag-title
|
||||
p
|
||||
color $ui-text-color
|
||||
|
||||
.non-active-button
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.active-button
|
||||
@extend .non-active-button
|
||||
color #0B99F1
|
||||
|
||||
body[data-theme="dark"]
|
||||
.non-active-button
|
||||
color alpha($ui-dark-text-color, 60%)
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.tag-title
|
||||
p
|
||||
color alpha($ui-dark-text-color, 60%)
|
||||
24
browser/main/SideNav/TagButton.js
Normal file
24
browser/main/SideNav/TagButton.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './SwitchButton.styl'
|
||||
|
||||
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'>Tags</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
TagButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
isTagActive: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(TagButton, styles)
|
||||
@@ -1,30 +1,47 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './SideNav.styl'
|
||||
import { openModal } from 'browser/main/lib/modal'
|
||||
import PreferencesModal from '../modals/PreferencesModal'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import StorageItem from './StorageItem'
|
||||
import TagListItem from 'browser/components/TagListItem'
|
||||
import SideNavFilter from 'browser/components/SideNavFilter'
|
||||
import StorageList from 'browser/components/StorageList'
|
||||
import NavToggleButton from 'browser/components/NavToggleButton'
|
||||
import EventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import PreferenceButton from './PreferenceButton'
|
||||
import ListButton from './ListButton'
|
||||
import TagButton from './TagButton'
|
||||
|
||||
class SideNav extends React.Component {
|
||||
// TODO: should not use electron stuff v0.7
|
||||
|
||||
componentDidMount () {
|
||||
EventEmitter.on('side:preferences', this.handleMenuButtonClick)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
|
||||
}
|
||||
|
||||
handleMenuButtonClick (e) {
|
||||
openModal(PreferencesModal)
|
||||
}
|
||||
|
||||
handleHomeButtonClick (e) {
|
||||
let { router } = this.context
|
||||
const { router } = this.context
|
||||
router.push('/home')
|
||||
}
|
||||
|
||||
handleStarredButtonClick (e) {
|
||||
let { router } = this.context
|
||||
const { router } = this.context
|
||||
router.push('/starred')
|
||||
}
|
||||
|
||||
handleToggleButtonClick (e) {
|
||||
let { dispatch, config } = this.props
|
||||
const { dispatch, config } = this.props
|
||||
|
||||
ConfigManager.set({isSideNavFolded: !config.isSideNavFolded})
|
||||
dispatch({
|
||||
@@ -34,19 +51,100 @@ class SideNav extends React.Component {
|
||||
}
|
||||
|
||||
handleTrashedButtonClick (e) {
|
||||
let { router } = this.context
|
||||
const { router } = this.context
|
||||
router.push('/trashed')
|
||||
}
|
||||
|
||||
handleSwitchFoldersButtonClick () {
|
||||
const { router } = this.context
|
||||
router.push('/home')
|
||||
}
|
||||
|
||||
handleSwitchTagsButtonClick () {
|
||||
const { router } = this.context
|
||||
router.push('/alltags')
|
||||
}
|
||||
|
||||
SideNavComponent (isFolded, storageList) {
|
||||
const { location, data } = this.props
|
||||
|
||||
const isHomeActive = !!location.pathname.match(/^\/home$/)
|
||||
const isStarredActive = !!location.pathname.match(/^\/starred$/)
|
||||
const isTrashedActive = !!location.pathname.match(/^\/trashed$/)
|
||||
|
||||
let component
|
||||
|
||||
// TagsMode is not selected
|
||||
if (!location.pathname.match('/tags') && !location.pathname.match('/alltags')) {
|
||||
component = (
|
||||
<div>
|
||||
<SideNavFilter
|
||||
isFolded={isFolded}
|
||||
isHomeActive={isHomeActive}
|
||||
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
|
||||
isStarredActive={isStarredActive}
|
||||
isTrashedActive={isTrashedActive}
|
||||
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
|
||||
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
|
||||
counterTotalNote={data.noteMap._map.size}
|
||||
counterStarredNote={data.starredSet._set.size}
|
||||
counterDelNote={data.trashedSet._set.size}
|
||||
/>
|
||||
|
||||
<StorageList storageList={storageList} />
|
||||
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
component = (
|
||||
<div styleName='tabBody'>
|
||||
<div styleName='tag-title'>
|
||||
<p>Tags</p>
|
||||
</div>
|
||||
<div styleName='tagList'>
|
||||
{this.tagListComponent(data)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return component
|
||||
}
|
||||
|
||||
tagListComponent () {
|
||||
const { data, location } = this.props
|
||||
const tagList = data.tagNoteMap.map((tag, key) => {
|
||||
return key
|
||||
})
|
||||
return (
|
||||
tagList.map(tag => (
|
||||
<TagListItem
|
||||
name={tag}
|
||||
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
||||
isActive={this.getTagActive(location.pathname, tag)}
|
||||
key={tag}
|
||||
/>
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
getTagActive (path, tag) {
|
||||
const pathSegments = path.split('/')
|
||||
const pathTag = pathSegments[pathSegments.length - 1]
|
||||
return pathTag === tag
|
||||
}
|
||||
|
||||
handleClickTagListItem (name) {
|
||||
const { router } = this.context
|
||||
router.push(`/tags/${name}`)
|
||||
}
|
||||
|
||||
render () {
|
||||
let { data, location, config, dispatch } = this.props
|
||||
const { data, location, config, dispatch } = this.props
|
||||
|
||||
let isFolded = config.isSideNavFolded
|
||||
let isHomeActive = !!location.pathname.match(/^\/home$/)
|
||||
let isStarredActive = !!location.pathname.match(/^\/starred$/)
|
||||
let isTrashedActive = !!location.pathname.match(/^\/trashed$/)
|
||||
const isFolded = config.isSideNavFolded
|
||||
|
||||
let storageList = data.storageMap.map((storage, key) => {
|
||||
const storageList = data.storageMap.map((storage, key) => {
|
||||
return <StorageItem
|
||||
key={storage.key}
|
||||
storage={storage}
|
||||
@@ -56,8 +154,9 @@ class SideNav extends React.Component {
|
||||
dispatch={dispatch}
|
||||
/>
|
||||
})
|
||||
let style = {}
|
||||
const style = {}
|
||||
if (!isFolded) style.width = this.props.width
|
||||
const isTagActive = location.pathname.match(/tag/)
|
||||
return (
|
||||
<div className='SideNav'
|
||||
styleName={isFolded ? 'root--folded' : 'root'}
|
||||
@@ -65,37 +164,15 @@ class SideNav extends React.Component {
|
||||
style={style}
|
||||
>
|
||||
<div styleName='top'>
|
||||
<button styleName='top-menu'
|
||||
onClick={(e) => this.handleMenuButtonClick(e)}
|
||||
>
|
||||
<i className='fa fa-wrench fa-fw' />
|
||||
<span styleName='top-menu-label'>Preferences</span>
|
||||
</button>
|
||||
<div styleName='switch-buttons'>
|
||||
<ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||
<TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||
</div>
|
||||
<div>
|
||||
<PreferenceButton onClick={this.handleMenuButtonClick} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SideNavFilter
|
||||
isFolded={isFolded}
|
||||
isHomeActive={isHomeActive}
|
||||
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
|
||||
isStarredActive={isStarredActive}
|
||||
isTrashedActive={isTrashedActive}
|
||||
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
|
||||
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
|
||||
/>
|
||||
|
||||
<div styleName='storageList'>
|
||||
{storageList.length > 0 ? storageList : (
|
||||
<div styleName='storageList-empty'>No storage mount.</div>
|
||||
)}
|
||||
</div>
|
||||
<button styleName='navToggle'
|
||||
onClick={(e) => this.handleToggleButtonClick(e)}
|
||||
>
|
||||
{isFolded
|
||||
? <i className='fa fa-angle-double-right' />
|
||||
: <i className='fa fa-angle-double-left' />
|
||||
}
|
||||
</button>
|
||||
{this.SideNavComponent(isFolded, storageList)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@import('../Detail/DetailVars')
|
||||
|
||||
.root
|
||||
absolute bottom left right
|
||||
height $statusBar-height
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
position absolute
|
||||
bottom 10px
|
||||
right 10px
|
||||
z-index 100
|
||||
display flex
|
||||
|
||||
.blank
|
||||
@@ -21,8 +22,19 @@
|
||||
|
||||
.zoom
|
||||
navButtonColor()
|
||||
height 24px
|
||||
|
||||
color rgba(0,0,0,.54)
|
||||
height 20px
|
||||
display flex
|
||||
padding 0
|
||||
align-items center
|
||||
background-color transparent
|
||||
&:hover
|
||||
color $ui-active-color
|
||||
&:active
|
||||
color $ui-active-color
|
||||
span
|
||||
margin-left 5px
|
||||
|
||||
.update
|
||||
navButtonColor()
|
||||
height 24px
|
||||
@@ -37,14 +49,14 @@
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
border-color $ui-dark-borderColor
|
||||
box-shadow none
|
||||
|
||||
.zoom
|
||||
border-color $ui-dark-borderColor
|
||||
background-color transparent
|
||||
color #f9f9f9
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
transition 0.15s
|
||||
color $ui-dark-text-color
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StatusBar.styl'
|
||||
import ZoomManager from 'browser/main/lib/ZoomManager'
|
||||
@@ -11,7 +12,7 @@ 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
|
||||
|
||||
class StatusBar extends React.Component {
|
||||
updateApp () {
|
||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Update Boostnote',
|
||||
detail: 'New Boostnote is ready to be installed.',
|
||||
@@ -24,7 +25,7 @@ class StatusBar extends React.Component {
|
||||
}
|
||||
|
||||
handleZoomButtonClick (e) {
|
||||
let menu = new Menu()
|
||||
const menu = new Menu()
|
||||
|
||||
zoomOptions.forEach((zoom) => {
|
||||
menu.append(new MenuItem({
|
||||
@@ -37,7 +38,7 @@ class StatusBar extends React.Component {
|
||||
}
|
||||
|
||||
handleZoomMenuItemClick (zoomFactor) {
|
||||
let { dispatch } = this.props
|
||||
const { dispatch } = this.props
|
||||
ZoomManager.setZoom(zoomFactor)
|
||||
dispatch({
|
||||
type: 'SET_ZOOM',
|
||||
@@ -46,7 +47,7 @@ class StatusBar extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { config, status } = this.context
|
||||
const { config, status } = this.context
|
||||
|
||||
return (
|
||||
<div className='StatusBar'
|
||||
@@ -55,12 +56,10 @@ class StatusBar extends React.Component {
|
||||
<button styleName='zoom'
|
||||
onClick={(e) => this.handleZoomButtonClick(e)}
|
||||
>
|
||||
<i className='fa fa-search-plus' />
|
||||
{Math.floor(config.zoom * 100)}%
|
||||
<img src='../resources/icon/icon-zoom.svg' />
|
||||
<span>{Math.floor(config.zoom * 100)}%</span>
|
||||
</button>
|
||||
|
||||
<div styleName='blank' />
|
||||
|
||||
{status.updateReady
|
||||
? <button onClick={this.updateApp} styleName='update'>
|
||||
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
|
||||
|
||||
@@ -36,7 +36,7 @@ $control-height = 34px
|
||||
outline none
|
||||
border none
|
||||
color $ui-text-color
|
||||
font-size 16px
|
||||
font-size 18px
|
||||
padding-bottom 2px
|
||||
background-color $ui-noteList-backgroundColor
|
||||
|
||||
@@ -112,6 +112,21 @@ $control-height = 34px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="white"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
.control
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.control-search
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
.control-search-input
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
input
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
@@ -170,3 +185,26 @@ body[data-theme="dark"]
|
||||
|
||||
.control-newPostButton-tooltip
|
||||
darkTooltip()
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
|
||||
.control
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
.control-search
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
|
||||
.control-search-icon
|
||||
absolute top bottom left
|
||||
line-height 32px
|
||||
width 35px
|
||||
color $ui-solarized-dark-inactive-text-color
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
|
||||
.control-search-input
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
input
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
@@ -1,16 +1,11 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TopBar.styl'
|
||||
import _ from 'lodash'
|
||||
import NewNoteModal from 'browser/main/modals/NewNoteModal'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import NewNoteButton from 'browser/main/NewNoteButton'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
|
||||
const OSX = window.process.platform === 'darwin'
|
||||
|
||||
class TopBar extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
@@ -18,7 +13,10 @@ class TopBar extends React.Component {
|
||||
this.state = {
|
||||
search: '',
|
||||
searchOptions: [],
|
||||
isSearching: false
|
||||
isSearching: false,
|
||||
isAlphabet: false,
|
||||
isIME: false,
|
||||
isConfirmTranslation: false
|
||||
}
|
||||
|
||||
this.focusSearchHandler = () => {
|
||||
@@ -34,9 +32,52 @@ class TopBar extends React.Component {
|
||||
ee.off('top:focus-search', this.focusSearchHandler)
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
// reset states
|
||||
this.setState({
|
||||
isAlphabet: false,
|
||||
isIME: false
|
||||
})
|
||||
|
||||
// When the key is an alphabet, del, enter or ctr
|
||||
if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) {
|
||||
this.setState({
|
||||
isAlphabet: true
|
||||
})
|
||||
// When the key is an IME input (Japanese, Chinese)
|
||||
} else if (e.keyCode === 229) {
|
||||
this.setState({
|
||||
isIME: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyUp (e) {
|
||||
const { router } = this.context
|
||||
// reset states
|
||||
this.setState({
|
||||
isConfirmTranslation: false
|
||||
})
|
||||
|
||||
// When the key is translation confirmation (Enter, Space)
|
||||
if (this.state.isIME && (e.keyCode === 32 || e.keyCode === 13)) {
|
||||
this.setState({
|
||||
isConfirmTranslation: true
|
||||
})
|
||||
router.push('/searched')
|
||||
this.setState({
|
||||
search: this.refs.searchInput.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchChange (e) {
|
||||
let { router } = this.context
|
||||
router.push('/searched')
|
||||
const { router } = this.context
|
||||
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
|
||||
router.push('/searched')
|
||||
} else {
|
||||
e.preventDefault()
|
||||
}
|
||||
this.setState({
|
||||
search: this.refs.searchInput.value
|
||||
})
|
||||
@@ -75,7 +116,7 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { config, style, data, location } = this.props
|
||||
const { config, style, location } = this.props
|
||||
return (
|
||||
<div className='TopBar'
|
||||
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
||||
@@ -93,6 +134,8 @@ class TopBar extends React.Component {
|
||||
ref='searchInput'
|
||||
value={this.state.search}
|
||||
onChange={(e) => this.handleSearchChange(e)}
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyUp={(e) => this.handleKeyUp(e)}
|
||||
placeholder='Search'
|
||||
type='text'
|
||||
className='searchInput'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
global-reset()
|
||||
@import '../styles/vars.styl'
|
||||
|
||||
DEFAULT_FONTS = 'OpenSans', helvetica, arial, sans-serif
|
||||
|
||||
@@ -12,6 +13,7 @@ body
|
||||
color textColor
|
||||
font-size fontSize
|
||||
font-weight 200
|
||||
-webkit-font-smoothing antialiased
|
||||
|
||||
button, input, select, textarea
|
||||
font-family DEFAULT_FONTS
|
||||
@@ -64,7 +66,7 @@ textarea.block-input
|
||||
fullsize()
|
||||
|
||||
modalZIndex= 1000
|
||||
modalBackColor = transparentify(white, 65%)
|
||||
modalBackColor = white
|
||||
.ace_focus
|
||||
outline-color rgb(59, 153, 252)
|
||||
outline-offset 0px
|
||||
@@ -83,15 +85,19 @@ modalBackColor = transparentify(white, 65%)
|
||||
absolute top left bottom right
|
||||
background-color modalBackColor
|
||||
z-index modalZIndex + 1
|
||||
|
||||
|
||||
body[data-theme="dark"]
|
||||
.ModalBase
|
||||
.modalBack
|
||||
background-color alpha(black, 60%)
|
||||
background-color $ui-dark-backgroundColor
|
||||
.sortableItemHelper
|
||||
color: $ui-dark-text-color
|
||||
|
||||
.CodeMirror
|
||||
font-family inherit !important
|
||||
line-height 1.4em
|
||||
height 100%
|
||||
height 96%
|
||||
.CodeMirror > div > textarea
|
||||
margin-bottom -1em
|
||||
.CodeMirror-focused .CodeMirror-selected
|
||||
@@ -102,3 +108,15 @@ body[data-theme="dark"]
|
||||
background #B1D7FE
|
||||
::selection
|
||||
background #B1D7FE
|
||||
|
||||
.sortableItemHelper
|
||||
z-index modalZIndex + 5
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.ModalBase
|
||||
.modalBack
|
||||
background-color $ui-solarized-dark-backgroundColor
|
||||
.sortableItemHelper
|
||||
color: $ui-solarized-dark-text-color
|
||||
|
||||
|
||||
|
||||
@@ -27,13 +27,16 @@ document.addEventListener('click', function (e) {
|
||||
const className = e.target.className
|
||||
if (!className && typeof (className) !== 'string') return
|
||||
const isInfoButton = className.includes('infoButton')
|
||||
const isInfoPanel = e.target.offsetParent.className.includes('infoPanel')
|
||||
const offsetParent = e.target.offsetParent
|
||||
const isInfoPanel = offsetParent !== null
|
||||
? offsetParent.className.includes('infoPanel')
|
||||
: false
|
||||
if (isInfoButton || isInfoPanel) return
|
||||
const infoPanel = document.querySelector('.infoPanel')
|
||||
if (infoPanel) infoPanel.style.display = 'none'
|
||||
})
|
||||
|
||||
let el = document.getElementById('content')
|
||||
const el = document.getElementById('content')
|
||||
const history = syncHistoryWithStore(hashHistory, store)
|
||||
|
||||
function notify (...args) {
|
||||
@@ -41,7 +44,7 @@ function notify (...args) {
|
||||
}
|
||||
|
||||
function updateApp () {
|
||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Update Boostnote',
|
||||
detail: 'New Boostnote is ready to be installed.',
|
||||
@@ -62,6 +65,11 @@ ReactDOM.render((
|
||||
<Route path='starred' />
|
||||
<Route path='searched' />
|
||||
<Route path='trashed' />
|
||||
<Route path='alltags' />
|
||||
<Route path='tags'>
|
||||
<IndexRedirect to='/alltags' />
|
||||
<Route path=':tagname' />
|
||||
</Route>
|
||||
<Route path='storages'>
|
||||
<IndexRedirect to='/home' />
|
||||
<Route path=':storageKey'>
|
||||
@@ -73,7 +81,7 @@ ReactDOM.render((
|
||||
</Router>
|
||||
</Provider>
|
||||
), el, function () {
|
||||
let loadingCover = document.getElementById('loadingCover')
|
||||
const loadingCover = document.getElementById('loadingCover')
|
||||
loadingCover.parentNode.removeChild(loadingCover)
|
||||
|
||||
ipcRenderer.on('update-ready', function () {
|
||||
|
||||
@@ -2,19 +2,47 @@ const AWS = require('aws-sdk')
|
||||
const AMA = require('aws-sdk-mobile-analytics')
|
||||
const ConfigManager = require('browser/main/lib/ConfigManager')
|
||||
|
||||
const remote = require('electron').remote
|
||||
const os = require('os')
|
||||
let mobileAnalyticsClient
|
||||
|
||||
AWS.config.region = 'us-east-1'
|
||||
if (process.env.NODE_ENV === 'production' && ConfigManager.default.get().amaEnabled) {
|
||||
if (!getSendEventCond()) {
|
||||
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
|
||||
IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
})
|
||||
const mobileAnalyticsClient = new AMA.Manager({
|
||||
|
||||
const validPlatformName = convertPlatformName(os.platform())
|
||||
|
||||
mobileAnalyticsClient = new AMA.Manager({
|
||||
appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||
appTitle: 'xxxxxxxxxx'
|
||||
appTitle: 'xxxxxxxxxx',
|
||||
appVersionName: remote.app.getVersion().toString(),
|
||||
platform: validPlatformName
|
||||
})
|
||||
}
|
||||
|
||||
function convertPlatformName (platformName) {
|
||||
if (platformName === 'darwin') {
|
||||
return 'MacOS'
|
||||
} else if (platformName === 'win32') {
|
||||
return 'Windows'
|
||||
} else if (platformName === 'linux') {
|
||||
return 'Linux'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
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 () {
|
||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
||||
if (getSendEventCond()) return
|
||||
AWS.config.credentials.get((err) => {
|
||||
if (!err) {
|
||||
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
|
||||
@@ -24,16 +52,28 @@ function initAwsMobileAnalytics () {
|
||||
})
|
||||
}
|
||||
|
||||
function recordDynamicCustomEvent (type) {
|
||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
||||
mobileAnalyticsClient.recordEvent(type)
|
||||
function recordDynamicCustomEvent (type, options = {}) {
|
||||
if (getSendEventCond()) return
|
||||
try {
|
||||
mobileAnalyticsClient.recordEvent(type, options)
|
||||
} catch (analyticsError) {
|
||||
if (analyticsError instanceof ReferenceError) {
|
||||
console.log(analyticsError.name + ': ' + analyticsError.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function recordStaticCustomEvent () {
|
||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
||||
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
|
||||
uiColorTheme: ConfigManager.default.get().ui.theme
|
||||
})
|
||||
if (getSendEventCond()) return
|
||||
try {
|
||||
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
|
||||
uiColorTheme: ConfigManager.default.get().ui.theme
|
||||
})
|
||||
} catch (analyticsError) {
|
||||
if (analyticsError instanceof ReferenceError) {
|
||||
console.log(analyticsError.name + ': ' + analyticsError.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -13,10 +13,10 @@ function release (el) {
|
||||
|
||||
function fire (command) {
|
||||
console.info('COMMAND >>', command)
|
||||
let splitted = command.split(':')
|
||||
let target = splitted[0]
|
||||
let targetCommand = splitted[1]
|
||||
let targetCallees = callees
|
||||
const splitted = command.split(':')
|
||||
const target = splitted[0]
|
||||
const targetCommand = splitted[1]
|
||||
const targetCallees = callees
|
||||
.filter((callee) => callee.name === target)
|
||||
|
||||
targetCallees.forEach((callee) => {
|
||||
|
||||
@@ -6,8 +6,6 @@ const win = global.process.platform === 'win32'
|
||||
const electron = require('electron')
|
||||
const { ipcRenderer } = electron
|
||||
const consts = require('browser/lib/consts')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
let isInitialized = false
|
||||
|
||||
@@ -25,6 +23,7 @@ export const DEFAULT_CONFIG = {
|
||||
},
|
||||
ui: {
|
||||
theme: 'default',
|
||||
showCopyNotification: true,
|
||||
disableDirectWrite: false,
|
||||
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
|
||||
},
|
||||
@@ -36,13 +35,19 @@ export const DEFAULT_CONFIG = {
|
||||
indentType: 'space',
|
||||
indentSize: '2',
|
||||
displayLineNumbers: true,
|
||||
switchPreview: 'BLUR' // Available value: RIGHTCLICK, BLUR
|
||||
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
||||
scrollPastEnd: false,
|
||||
type: 'SPLIT'
|
||||
},
|
||||
preview: {
|
||||
fontSize: '14',
|
||||
fontFamily: win ? 'Segoe UI' : 'Lato',
|
||||
codeBlockTheme: 'dracula',
|
||||
lineNumber: true
|
||||
lineNumber: true,
|
||||
latexInlineOpen: '$',
|
||||
latexInlineClose: '$',
|
||||
latexBlockOpen: '$$',
|
||||
latexBlockClose: '$$'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +96,11 @@ function get () {
|
||||
: 'default'
|
||||
|
||||
if (config.editor.theme !== 'default') {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
|
||||
if (config.editor.theme.startsWith('solarized')) {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css')
|
||||
} else {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,13 +108,17 @@ function get () {
|
||||
}
|
||||
|
||||
function set (updates) {
|
||||
let currentConfig = get()
|
||||
let newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, updates)
|
||||
const currentConfig = get()
|
||||
const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, updates)
|
||||
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
|
||||
_save(newConfig)
|
||||
|
||||
if (newConfig.ui.theme === 'dark') {
|
||||
document.body.setAttribute('data-theme', 'dark')
|
||||
} else if (newConfig.ui.theme === 'white') {
|
||||
document.body.setAttribute('data-theme', 'white')
|
||||
} else if (newConfig.ui.theme === 'solarized-dark') {
|
||||
document.body.setAttribute('data-theme', 'solarized-dark')
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'default')
|
||||
}
|
||||
@@ -117,12 +130,16 @@ function set (updates) {
|
||||
editorTheme.setAttribute('rel', 'stylesheet')
|
||||
document.head.appendChild(editorTheme)
|
||||
}
|
||||
let newTheme = consts.THEMES.some((theme) => theme === newConfig.editor.theme)
|
||||
const newTheme = consts.THEMES.some((theme) => theme === newConfig.editor.theme)
|
||||
? newConfig.editor.theme
|
||||
: 'default'
|
||||
|
||||
if (newTheme !== 'default') {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + newTheme + '.css')
|
||||
if (newTheme.startsWith('solarized')) {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css')
|
||||
} else {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + newTheme + '.css')
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.send('config-renew', {
|
||||
@@ -131,7 +148,7 @@ function set (updates) {
|
||||
}
|
||||
|
||||
function assignConfigValues (originalConfig, rcConfig) {
|
||||
let config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
|
||||
const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
|
||||
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey)
|
||||
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
|
||||
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
|
||||
|
||||
@@ -19,7 +19,7 @@ function setZoom (zoomFactor, noSave = false) {
|
||||
}
|
||||
|
||||
function getZoom () {
|
||||
let config = ConfigManager.get()
|
||||
const config = ConfigManager.get()
|
||||
|
||||
return config.zoom
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const _ = require('lodash')
|
||||
const sander = require('sander')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,7 +23,6 @@ const { findStorage } = require('browser/lib/findStorage')
|
||||
* ```
|
||||
*/
|
||||
function createFolder (storageKey, input) {
|
||||
let rawStorages
|
||||
let targetStorage
|
||||
try {
|
||||
if (input == null) throw new Error('No input found.')
|
||||
@@ -41,7 +40,7 @@ function createFolder (storageKey, input) {
|
||||
while (storage.folders.some((folder) => folder.key === key)) {
|
||||
key = keygen()
|
||||
}
|
||||
let newFolder = {
|
||||
const newFolder = {
|
||||
key,
|
||||
color: input.color,
|
||||
name: input.name
|
||||
|
||||
@@ -66,12 +66,16 @@ function createNote (storageKey, input) {
|
||||
}
|
||||
}
|
||||
}
|
||||
let noteData = Object.assign({}, input, {
|
||||
key,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
storage: storageKey
|
||||
})
|
||||
const noteData = Object.assign({},
|
||||
{
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
},
|
||||
input, // input may contain more accurate dates
|
||||
{
|
||||
key,
|
||||
storage: storageKey
|
||||
})
|
||||
|
||||
CSON.writeFileSync(path.join(storage.path, 'notes', key + '.cson'), _.omit(noteData, ['key', 'storage']))
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ const { findStorage } = require('browser/lib/findStorage')
|
||||
* ```
|
||||
*/
|
||||
function deleteFolder (storageKey, folderKey) {
|
||||
let rawStorages
|
||||
let targetStorage
|
||||
try {
|
||||
targetStorage = findStorage(storageKey)
|
||||
@@ -38,17 +37,17 @@ function deleteFolder (storageKey, folderKey) {
|
||||
})
|
||||
})
|
||||
.then(function deleteFolderAndNotes (data) {
|
||||
let { storage, notes } = data
|
||||
const { storage, notes } = data
|
||||
storage.folders = storage.folders
|
||||
.filter(function excludeTargetFolder (folder) {
|
||||
return folder.key !== folderKey
|
||||
})
|
||||
|
||||
let targetNotes = notes.filter(function filterTargetNotes (note) {
|
||||
const targetNotes = notes.filter(function filterTargetNotes (note) {
|
||||
return note.folder === folderKey
|
||||
})
|
||||
|
||||
let deleteAllNotes = targetNotes
|
||||
const deleteAllNotes = targetNotes
|
||||
.map(function deleteNote (note) {
|
||||
const notePath = path.join(storage.path, 'notes', note.key + '.cson')
|
||||
return sander.unlink(notePath)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const resolveStorageData = require('./resolveStorageData')
|
||||
const _ = require('lodash')
|
||||
const path = require('path')
|
||||
const sander = require('sander')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
@@ -14,7 +13,7 @@ function deleteNote (storageKey, noteKey) {
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function deleteNoteFile (storage) {
|
||||
let notePath = path.join(storage.path, 'notes', noteKey + '.cson')
|
||||
const notePath = path.join(storage.path, 'notes', noteKey + '.cson')
|
||||
|
||||
try {
|
||||
sander.unlinkSync(notePath)
|
||||
|
||||
@@ -6,6 +6,7 @@ const dataApi = {
|
||||
createFolder: require('./createFolder'),
|
||||
updateFolder: require('./updateFolder'),
|
||||
deleteFolder: require('./deleteFolder'),
|
||||
reorderFolder: require('./reorderFolder'),
|
||||
createNote: require('./createNote'),
|
||||
updateNote: require('./updateNote'),
|
||||
deleteNote: require('./deleteNote'),
|
||||
|
||||
@@ -20,7 +20,7 @@ const CSON = require('@rokt33r/season')
|
||||
* 3. empty directory
|
||||
*/
|
||||
function init () {
|
||||
let fetchStorages = function () {
|
||||
const fetchStorages = function () {
|
||||
let rawStorages
|
||||
try {
|
||||
rawStorages = JSON.parse(window.localStorage.getItem('storages'))
|
||||
@@ -34,8 +34,8 @@ function init () {
|
||||
.map(resolveStorageData))
|
||||
}
|
||||
|
||||
let fetchNotes = function (storages) {
|
||||
let findNotesFromEachStorage = storages
|
||||
const fetchNotes = function (storages) {
|
||||
const findNotesFromEachStorage = storages
|
||||
.map((storage) => {
|
||||
return resolveStorageNotes(storage)
|
||||
.then((notes) => {
|
||||
|
||||
@@ -9,7 +9,7 @@ const sander = require('sander')
|
||||
function migrateFromV5Storage (storageKey, data) {
|
||||
let targetStorage
|
||||
try {
|
||||
let cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
|
||||
targetStorage = _.find(cachedStorageList, {key: storageKey})
|
||||
@@ -24,15 +24,15 @@ function migrateFromV5Storage (storageKey, data) {
|
||||
}
|
||||
|
||||
function importAll (storage, data) {
|
||||
let oldArticles = data.articles
|
||||
let notes = []
|
||||
const oldArticles = data.articles
|
||||
const notes = []
|
||||
data.folders
|
||||
.forEach(function (oldFolder) {
|
||||
let folderKey = keygen()
|
||||
while (storage.folders.some((folder) => folder.key === folderKey)) {
|
||||
folderKey = keygen()
|
||||
}
|
||||
let newFolder = {
|
||||
const newFolder = {
|
||||
key: folderKey,
|
||||
name: oldFolder.name,
|
||||
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
||||
@@ -40,7 +40,7 @@ function importAll (storage, data) {
|
||||
|
||||
storage.folders.push(newFolder)
|
||||
|
||||
let articles = oldArticles.filter((article) => article.FolderKey === oldFolder.key)
|
||||
const articles = oldArticles.filter((article) => article.FolderKey === oldFolder.key)
|
||||
articles.forEach((article) => {
|
||||
let noteKey = keygen()
|
||||
let isUnique = false
|
||||
@@ -59,7 +59,7 @@ function importAll (storage, data) {
|
||||
}
|
||||
|
||||
if (article.mode === 'markdown') {
|
||||
let newNote = {
|
||||
const newNote = {
|
||||
tags: article.tags,
|
||||
createdAt: article.createdAt,
|
||||
updatedAt: article.updatedAt,
|
||||
@@ -73,7 +73,7 @@ function importAll (storage, data) {
|
||||
}
|
||||
notes.push(newNote)
|
||||
} else {
|
||||
let newNote = {
|
||||
const newNote = {
|
||||
tags: article.tags,
|
||||
createdAt: article.createdAt,
|
||||
updatedAt: article.updatedAt,
|
||||
|
||||
@@ -20,7 +20,7 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
||||
.then(function saveNote (_oldStorage) {
|
||||
oldStorage = _oldStorage
|
||||
let noteData
|
||||
let notePath = path.join(oldStorage.path, 'notes', noteKey + '.cson')
|
||||
const notePath = path.join(oldStorage.path, 'notes', noteKey + '.cson')
|
||||
try {
|
||||
noteData = CSON.readFileSync(notePath)
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const _ = require('lodash')
|
||||
const resolveStorageData = require('./resolveStorageData')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
/**
|
||||
* @param {String} key
|
||||
@@ -19,7 +18,7 @@ function renameStorage (key, name) {
|
||||
console.error(err)
|
||||
return Promise.reject(err)
|
||||
}
|
||||
let targetStorage = _.find(cachedStorageList, {key: key})
|
||||
const targetStorage = _.find(cachedStorageList, {key: key})
|
||||
if (targetStorage == null) return Promise.reject('Storage')
|
||||
|
||||
targetStorage.name = name
|
||||
|
||||
42
browser/main/lib/dataApi/reorderFolder.js
Normal file
42
browser/main/lib/dataApi/reorderFolder.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const _ = require('lodash')
|
||||
_.move = require('lodash-move').default
|
||||
const path = require('path')
|
||||
const resolveStorageData = require('./resolveStorageData')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
/**
|
||||
* @param {String} storageKey
|
||||
* @param {number} oldIndex
|
||||
* @param {number} newIndex
|
||||
*
|
||||
* @return {Object}
|
||||
* ```
|
||||
* {
|
||||
* storage: Object
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function reorderFolder (storageKey, oldIndex, newIndex) {
|
||||
let targetStorage
|
||||
try {
|
||||
if (!_.isNumber(oldIndex)) throw new Error('oldIndex must be a number.')
|
||||
if (!_.isNumber(newIndex)) throw new Error('newIndex must be a number.')
|
||||
|
||||
targetStorage = findStorage(storageKey)
|
||||
} catch (e) {
|
||||
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 {
|
||||
storage
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = reorderFolder
|
||||
@@ -4,7 +4,7 @@ const CSON = require('@rokt33r/season')
|
||||
const migrateFromV6Storage = require('./migrateFromV6Storage')
|
||||
|
||||
function resolveStorageData (storageCache) {
|
||||
let storage = {
|
||||
const storage = {
|
||||
key: storageCache.key,
|
||||
name: storageCache.name,
|
||||
type: storageCache.type,
|
||||
@@ -13,7 +13,7 @@ function resolveStorageData (storageCache) {
|
||||
|
||||
const boostnoteJSONPath = path.join(storageCache.path, 'boostnote.json')
|
||||
try {
|
||||
let jsonData = CSON.readFileSync(boostnoteJSONPath)
|
||||
const jsonData = CSON.readFileSync(boostnoteJSONPath)
|
||||
if (!_.isArray(jsonData.folders)) throw new Error('folders should be an array.')
|
||||
storage.folders = jsonData.folders
|
||||
storage.version = jsonData.version
|
||||
@@ -28,7 +28,7 @@ function resolveStorageData (storageCache) {
|
||||
storage.version = '1.0'
|
||||
}
|
||||
|
||||
let version = parseInt(storage.version, 10)
|
||||
const version = parseInt(storage.version, 10)
|
||||
if (version >= 1) {
|
||||
if (version > 1) {
|
||||
console.log('The repository version is newer than one of current app.')
|
||||
|
||||
@@ -16,13 +16,13 @@ function resolveStorageNotes (storage) {
|
||||
}
|
||||
notePathList = []
|
||||
}
|
||||
let notes = notePathList
|
||||
const notes = notePathList
|
||||
.filter(function filterOnlyCSONFile (notePath) {
|
||||
return /\.cson$/.test(notePath)
|
||||
})
|
||||
.map(function parseCSONFile (notePath) {
|
||||
try {
|
||||
let data = CSON.readFileSync(path.join(notesDirPath, notePath))
|
||||
const data = CSON.readFileSync(path.join(notesDirPath, notePath))
|
||||
data.key = path.basename(notePath, '.cson')
|
||||
data.storage = storage.key
|
||||
return data
|
||||
|
||||
@@ -2,6 +2,7 @@ const _ = require('lodash')
|
||||
const path = require('path')
|
||||
const resolveStorageData = require('./resolveStorageData')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
/**
|
||||
* @param {String} storageKey
|
||||
@@ -22,25 +23,20 @@ const CSON = require('@rokt33r/season')
|
||||
* ```
|
||||
*/
|
||||
function updateFolder (storageKey, folderKey, input) {
|
||||
let rawStorages
|
||||
let targetStorage
|
||||
try {
|
||||
if (input == null) throw new Error('No input found.')
|
||||
if (!_.isString(input.name)) throw new Error('Name must be a string.')
|
||||
if (!_.isString(input.color)) throw new Error('Color must be a string.')
|
||||
|
||||
rawStorages = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(rawStorages)) throw new Error('Target storage doesn\'t exist.')
|
||||
|
||||
targetStorage = _.find(rawStorages, {key: storageKey})
|
||||
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
|
||||
targetStorage = findStorage(storageKey)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function updateFolder (storage) {
|
||||
let targetFolder = _.find(storage.folders, {key: folderKey})
|
||||
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
|
||||
|
||||
@@ -5,7 +5,7 @@ const CSON = require('@rokt33r/season')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
function validateInput (input) {
|
||||
let validatedInput = {}
|
||||
const validatedInput = {}
|
||||
|
||||
if (input.tags != null) {
|
||||
if (!_.isArray(input.tags)) validatedInput.tags = []
|
||||
@@ -26,6 +26,10 @@ function validateInput (input) {
|
||||
validatedInput.isTrashed = !!input.isTrashed
|
||||
}
|
||||
|
||||
if (input.isPinned !== undefined) {
|
||||
validatedInput.isPinned = !!input.isPinned
|
||||
}
|
||||
|
||||
validatedInput.type = input.type
|
||||
switch (input.type) {
|
||||
case 'MARKDOWN_NOTE':
|
||||
@@ -77,7 +81,7 @@ function updateNote (storageKey, noteKey, input) {
|
||||
return resolveStorageData(targetStorage)
|
||||
.then(function saveNote (storage) {
|
||||
let noteData
|
||||
let notePath = path.join(storage.path, 'notes', noteKey + '.cson')
|
||||
const notePath = path.join(storage.path, 'notes', noteKey + '.cson')
|
||||
try {
|
||||
noteData = CSON.readFileSync(notePath)
|
||||
} catch (err) {
|
||||
@@ -104,6 +108,7 @@ function updateNote (storageKey, noteKey, input) {
|
||||
noteData.isStarred = false
|
||||
noteData.isTrashed = false
|
||||
noteData.tags = []
|
||||
noteData.isPinned = false
|
||||
}
|
||||
|
||||
if (noteData.type === 'SNIPPET_NOTE') {
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import store from 'browser/main/store'
|
||||
|
||||
const _ = require('lodash')
|
||||
const keygen = require('browser/lib/keygen')
|
||||
const Mixpanel = require('mixpanel')
|
||||
const mixpanel = Mixpanel.init('7a0aca437d72dfd07cbcbf58d3b61f27', {key: 'fde4fd23f4d550f1b646bcd7d4374b1f'})
|
||||
const moment = require('moment')
|
||||
const electron = require('electron')
|
||||
|
||||
function _getClientKey () {
|
||||
let clientKey = localStorage.getItem('clientKey')
|
||||
if (!_.isString(clientKey) || clientKey.length !== 40) {
|
||||
clientKey = keygen(20)
|
||||
_setClientKey(clientKey)
|
||||
}
|
||||
|
||||
return clientKey
|
||||
}
|
||||
|
||||
function _setClientKey (newKey) {
|
||||
localStorage.setItem('clientKey', newKey)
|
||||
}
|
||||
|
||||
function _fetch () {
|
||||
let events
|
||||
try {
|
||||
events = JSON.parse(localStorage.getItem('events'))
|
||||
if (!_.isArray(events)) throw new Error('events is not an array.')
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
events = []
|
||||
localStorage.setItem('events', JSON.stringify(events))
|
||||
console.info('Events cache initialzed')
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
function _keep (name, properties) {
|
||||
let events = _fetch()
|
||||
properties.time = new Date()
|
||||
events.push({
|
||||
name,
|
||||
properties
|
||||
})
|
||||
localStorage.setItem('events', JSON.stringify(events))
|
||||
}
|
||||
|
||||
function _keepUnique (name, properties) {
|
||||
let events = _fetch()
|
||||
properties.time = new Date()
|
||||
events = events.filter((event) => event.name !== name)
|
||||
events.push({
|
||||
name,
|
||||
properties
|
||||
})
|
||||
localStorage.setItem('events', JSON.stringify(events))
|
||||
}
|
||||
|
||||
function _flush () {
|
||||
let events = _fetch()
|
||||
let spliced = events.splice(0, 50)
|
||||
localStorage.setItem('events', JSON.stringify(events))
|
||||
|
||||
if (spliced.length > 0) {
|
||||
let parsedEvents = spliced
|
||||
.filter((event) => {
|
||||
if (!_.isObject(event)) return false
|
||||
if (!_.isString(event.name)) return false
|
||||
if (!_.isObject(event.properties)) return false
|
||||
if (!moment(event.properties.time).isValid()) return false
|
||||
if (new Date() - moment(event.properties.time).toDate() > 1000 * 3600 * 24 * 3) return false
|
||||
return true
|
||||
})
|
||||
.map((event) => {
|
||||
return {
|
||||
event: event.name,
|
||||
properties: event.properties
|
||||
}
|
||||
})
|
||||
|
||||
mixpanel.import_batch(parsedEvents, {}, (errs) => {
|
||||
if (errs.length > 0) {
|
||||
let events = _fetch()
|
||||
events = events.concat(spliced)
|
||||
localStorage.setItem('events', JSON.stringify(events))
|
||||
} else {
|
||||
_flush()
|
||||
}
|
||||
})
|
||||
|
||||
let state = store.getState()
|
||||
mixpanel.people.set(_getClientKey(), {
|
||||
storage_count: state.data.storageMap.size,
|
||||
note_count: state.data.noteMap.size,
|
||||
version: electron.remote.app.getVersion()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(_flush, 1000 * 60 * 60)
|
||||
|
||||
function track (name, properties) {
|
||||
switch (name) {
|
||||
case 'MAIN_FOCUSED':
|
||||
properties = Object.assign({}, properties, {
|
||||
distinct_id: _getClientKey()
|
||||
})
|
||||
_keepUnique(name, properties)
|
||||
break
|
||||
default:
|
||||
properties = Object.assign({}, properties, {
|
||||
distinct_id: _getClientKey()
|
||||
})
|
||||
_keep(name, properties)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
_mp: mixpanel,
|
||||
track
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class ModalBase extends React.Component {
|
||||
close () {
|
||||
if (modalBase != null) modalBase.setState({component: null, componentProps: null, isHidden: true})
|
||||
// Toggle overflow style on NoteList
|
||||
let list = document.querySelector('.NoteList__list___browser-main-NoteList-')
|
||||
const list = document.querySelector('.NoteList__list___browser-main-NoteList-')
|
||||
list.style.overflow = 'auto'
|
||||
}
|
||||
|
||||
@@ -34,14 +34,14 @@ class ModalBase extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
let el = document.createElement('div')
|
||||
const el = document.createElement('div')
|
||||
document.body.appendChild(el)
|
||||
let modalBase = ReactDOM.render(<ModalBase />, el)
|
||||
const modalBase = ReactDOM.render(<ModalBase />, el)
|
||||
|
||||
export function openModal (component, props) {
|
||||
if (modalBase == null) { return }
|
||||
// Hide scrollbar by removing overflow when modal opens
|
||||
let 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})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './CreateFolderModal.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
@@ -51,8 +52,8 @@ class CreateFolderModal extends React.Component {
|
||||
confirm () {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_FOLDER')
|
||||
if (this.state.name.trim().length > 0) {
|
||||
let { storage } = this.props
|
||||
let input = {
|
||||
const { storage } = this.props
|
||||
const input = {
|
||||
name: this.state.name.trim(),
|
||||
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
||||
}
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
height 270px
|
||||
overflow hidden
|
||||
position relative
|
||||
padding 0 40px
|
||||
|
||||
.header
|
||||
height 70px
|
||||
height 80px
|
||||
margin-bottom 10px
|
||||
margin-top 20px
|
||||
font-size 18px
|
||||
@@ -15,23 +14,27 @@
|
||||
background-color $ui-backgroundColor
|
||||
color $ui-text-color
|
||||
|
||||
.title
|
||||
font-size 36px
|
||||
font-weight 600
|
||||
|
||||
.control-folder-label
|
||||
text-align left
|
||||
font-size 12px
|
||||
font-size 14px
|
||||
color $ui-text-color
|
||||
|
||||
.control-folder-input
|
||||
display block
|
||||
height 30px
|
||||
width 420px
|
||||
height 40px
|
||||
width 490px
|
||||
padding 0 5px
|
||||
margin 10px auto 15px
|
||||
margin 10px 0
|
||||
border 1px solid #C9C9C9 // TODO: use variable.
|
||||
border-radius 2px
|
||||
background-color transparent
|
||||
outline none
|
||||
vertical-align middle
|
||||
font-size 14px
|
||||
font-size 16px
|
||||
&:disabled
|
||||
background-color $ui-input--disabled-backgroundColor
|
||||
&:focus, &:active
|
||||
@@ -39,14 +42,13 @@
|
||||
|
||||
.control-confirmButton
|
||||
display block
|
||||
float right
|
||||
height 30px
|
||||
width 100px
|
||||
height 35px
|
||||
width 140px
|
||||
border none
|
||||
border-radius 2px
|
||||
padding 0 25px
|
||||
margin 20px auto
|
||||
font-size 12px
|
||||
font-size 14px
|
||||
colorPrimaryButton()
|
||||
|
||||
body[data-theme="dark"]
|
||||
@@ -56,7 +58,6 @@ body[data-theme="dark"]
|
||||
height 270px
|
||||
overflow hidden
|
||||
position relative
|
||||
padding 0 40px
|
||||
|
||||
.header
|
||||
background-color transparent
|
||||
|
||||
@@ -5,7 +5,6 @@ import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { hashHistory } from 'react-router'
|
||||
import _ from 'lodash'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
|
||||
const CSON = require('@rokt33r/season')
|
||||
const path = require('path')
|
||||
@@ -13,9 +12,9 @@ const electron = require('electron')
|
||||
const { remote } = electron
|
||||
|
||||
function browseFolder () {
|
||||
let dialog = remote.dialog
|
||||
const dialog = remote.dialog
|
||||
|
||||
let defaultPath = remote.app.getPath('home')
|
||||
const defaultPath = remote.app.getPath('home')
|
||||
return new Promise((resolve, reject) => {
|
||||
dialog.showOpenDialog({
|
||||
title: 'Select Directory',
|
||||
@@ -55,7 +54,7 @@ class InitModal extends React.Component {
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
let newState = {
|
||||
const newState = {
|
||||
isLoading: false
|
||||
}
|
||||
if (data != null) {
|
||||
@@ -122,7 +121,7 @@ class InitModal extends React.Component {
|
||||
notes: data.notes
|
||||
})
|
||||
|
||||
let defaultSnippetNote = dataApi
|
||||
const defaultSnippetNote = dataApi
|
||||
.createNote(data.storage.key, {
|
||||
type: 'SNIPPET_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
@@ -147,12 +146,12 @@ class InitModal extends React.Component {
|
||||
note: note
|
||||
})
|
||||
})
|
||||
let defaultMarkdownNote = dataApi
|
||||
const defaultMarkdownNote = dataApi
|
||||
.createNote(data.storage.key, {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
title: 'Welcome to Boostnote!',
|
||||
content: '# Welcome to Boostnote! \n### _Click to edit this note._\n\n---\n\nBoostnote is an *open source* note-taking app. \nRepository is published on [GitHub](https://github.com/BoostIO/Boostnote), and tweeting everyday on [@Boostnoteapp](https://twitter.com/boostnoteapp)!\n\n## Features \n- [x] No Internet and Registration Required. \n- [ ] Quick search and copy the content of note. `macOS: Cmd + Alt + S / windows: Ctrl + Alt + S` \n- [ ] Markdown & Snippet note. \n- [ ] Available for `vim` and `emacs` mode. \n- [ ] Choose your favorite theme on UI, Editor and Code Block! \n--- \n\n- Copy Codeblock on Markdown Preview.\n```javascript\nvar boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)\n```'
|
||||
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({
|
||||
@@ -184,6 +183,12 @@ class InitModal extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
if (e.keyCode === 27) {
|
||||
this.props.close()
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.state.isLoading) {
|
||||
return <div styleName='root--loading'>
|
||||
@@ -196,17 +201,12 @@ class InitModal extends React.Component {
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
>
|
||||
|
||||
<div styleName='header'>
|
||||
<div styleName='header-title'>Initialize Storage</div>
|
||||
</div>
|
||||
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
|
||||
<div styleName='body'>
|
||||
<div styleName='body-welcome'>
|
||||
Welcome!
|
||||
Welcome to Boostnote!
|
||||
</div>
|
||||
<div styleName='body-description'>
|
||||
Please select a directory for Boostnote storage.
|
||||
Please select a directory for data storage.
|
||||
</div>
|
||||
<div styleName='body-path'>
|
||||
<input styleName='body-path-input'
|
||||
@@ -237,7 +237,7 @@ class InitModal extends React.Component {
|
||||
? <span>
|
||||
<i className='fa fa-spin fa-spinner' /> Loading...
|
||||
</span>
|
||||
: 'Let\'s Go!'
|
||||
: 'CREATE'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
.root
|
||||
modal()
|
||||
max-width 540px
|
||||
background-color #fff
|
||||
max-width 100vw
|
||||
max-height 100vh
|
||||
overflow hidden
|
||||
margin 0
|
||||
padding 150px 0
|
||||
position relative
|
||||
.root--loading
|
||||
@extend .root
|
||||
@@ -13,14 +17,6 @@
|
||||
.loadingMessage
|
||||
color $ui-text-color
|
||||
margin 15px auto 35px
|
||||
.header
|
||||
height 50px
|
||||
font-size 18px
|
||||
line-height 50px
|
||||
padding 0 15px
|
||||
background-color $ui-backgroundColor
|
||||
border-bottom solid 1px $ui-borderColor
|
||||
color $ui-text-color
|
||||
|
||||
.body
|
||||
padding 30px
|
||||
@@ -32,20 +28,20 @@
|
||||
color $ui-text-color
|
||||
|
||||
.body-description
|
||||
font-size 14px
|
||||
font-size 16px
|
||||
color $ui-text-color
|
||||
text-align center
|
||||
margin-bottom 25px
|
||||
|
||||
.body-path
|
||||
margin 0 auto 25px
|
||||
width 280px
|
||||
width 330px
|
||||
|
||||
.body-path-input
|
||||
height 30px
|
||||
height 40px
|
||||
vertical-align middle
|
||||
width 250px
|
||||
font-size 12px
|
||||
width 300px
|
||||
font-size 14px
|
||||
border-style solid
|
||||
border-width 1px 0 1px 1px
|
||||
border-color $border-color
|
||||
@@ -54,7 +50,10 @@
|
||||
padding 0 5px
|
||||
|
||||
.body-path-button
|
||||
height 30px
|
||||
height 42px
|
||||
width 30px
|
||||
font-size 16px
|
||||
font-weight 600
|
||||
border none
|
||||
border-top-right-radius 2px
|
||||
border-bottom-right-radius 2px
|
||||
@@ -69,6 +68,8 @@
|
||||
|
||||
.body-control-createButton
|
||||
colorPrimaryButton()
|
||||
font-size 14px
|
||||
font-weight 600
|
||||
border none
|
||||
border-radius 2px
|
||||
height 40px
|
||||
|
||||
@@ -26,7 +26,7 @@ class NewNoteModal extends React.Component {
|
||||
handleMarkdownNoteButtonClick (e) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||
let { storage, folder, dispatch, location } = this.props
|
||||
const { storage, folder, dispatch, location } = this.props
|
||||
dataApi
|
||||
.createNote(storage, {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
@@ -58,7 +58,7 @@ class NewNoteModal extends React.Component {
|
||||
handleSnippetNoteButtonClick (e) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||
let { storage, folder, dispatch, location } = this.props
|
||||
const { storage, folder, dispatch, location } = this.props
|
||||
|
||||
dataApi
|
||||
.createNote(storage, {
|
||||
|
||||
@@ -9,16 +9,19 @@
|
||||
font-size 18px
|
||||
line-height 50px
|
||||
padding 0 15px
|
||||
background-color $ui-backgroundColor
|
||||
border-bottom solid 1px $ui-borderColor
|
||||
color $ui-text-color
|
||||
margin-bottom 20px
|
||||
|
||||
.title
|
||||
font-size 36px
|
||||
font-weight 600
|
||||
|
||||
.control
|
||||
padding 25px 15px 15px
|
||||
padding 25px 0px
|
||||
text-align center
|
||||
|
||||
.control-button
|
||||
width 220px
|
||||
width 240px
|
||||
height 220px
|
||||
margin 0 15px
|
||||
border $ui-border
|
||||
@@ -30,8 +33,8 @@
|
||||
colorPrimaryButton()
|
||||
|
||||
.control-button-icon
|
||||
font-size 50px
|
||||
margin-bottom 15px
|
||||
font-size 48px
|
||||
margin-bottom 25px
|
||||
|
||||
.control-button-label
|
||||
font-size 18px
|
||||
@@ -49,8 +52,6 @@ body[data-theme="dark"]
|
||||
modalDark()
|
||||
|
||||
.header
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
border-color $ui-dark-borderColor
|
||||
color $ui-dark-text-color
|
||||
|
||||
.control-button
|
||||
@@ -63,3 +64,20 @@ body[data-theme="dark"]
|
||||
.description
|
||||
color $ui-inactive-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
background-color transparent
|
||||
|
||||
.header
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.control-button
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
color $ui-solarized-dark-text-color
|
||||
background-color transparent
|
||||
&:focus
|
||||
colorDarkPrimaryButton()
|
||||
|
||||
.description
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
|
||||
@@ -31,10 +31,15 @@
|
||||
|
||||
.group-section-control
|
||||
flex 1
|
||||
margin-left 5px
|
||||
|
||||
.group-section-control select
|
||||
outline none
|
||||
border 1px solid $ui-borderColor
|
||||
font-size 16px
|
||||
height 30px
|
||||
width 250px
|
||||
margin-bottom 5px
|
||||
background-color transparent
|
||||
|
||||
.group-section-control-input
|
||||
@@ -62,10 +67,17 @@
|
||||
text-align right
|
||||
:global
|
||||
.alert
|
||||
font-size 12px
|
||||
line-height 30px
|
||||
padding 0 5px
|
||||
float right
|
||||
display inline-block
|
||||
position absolute
|
||||
top 60px
|
||||
right 15px
|
||||
font-size 14px
|
||||
.success
|
||||
color #1EC38B
|
||||
.error
|
||||
color red
|
||||
|
||||
|
||||
|
||||
.group-control-leftButton
|
||||
colorDefaultButton()
|
||||
@@ -77,14 +89,16 @@
|
||||
margin-right 10px
|
||||
|
||||
.group-control-rightButton
|
||||
float right
|
||||
position absolute
|
||||
top 10px
|
||||
right 20px
|
||||
colorPrimaryButton()
|
||||
border none
|
||||
border-radius 2px
|
||||
font-size $tab--button-font-size
|
||||
height 35px
|
||||
width 100px
|
||||
margin-right 10px
|
||||
height 40px
|
||||
width 120px
|
||||
padding 0 15px
|
||||
|
||||
.group-hint
|
||||
border $ui-border
|
||||
@@ -101,7 +115,6 @@
|
||||
line-height 1.2
|
||||
|
||||
.note-for-keymap
|
||||
margin-left: 10px
|
||||
font-size: 12px
|
||||
|
||||
.code-mirror
|
||||
@@ -114,6 +127,12 @@ colorDarkControl()
|
||||
border-color $ui-dark-borderColor
|
||||
background-color $ui-dark-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
|
||||
colorSolarizedDarkControl()
|
||||
border none
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
@@ -141,3 +160,33 @@ body[data-theme="dark"]
|
||||
.group-section-control
|
||||
select, .group-section-control-input
|
||||
colorDarkControl()
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.group-header
|
||||
color $ui-solarized-dark-text-color
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
|
||||
.group-header2
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.group-section-control-input
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
|
||||
.group-control
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
.group-control-leftButton
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
.group-control-rightButton
|
||||
colorSolarizedDarkPrimaryButton()
|
||||
.group-hint
|
||||
colorSolarizedDarkControl()
|
||||
.group-section-control
|
||||
select, .group-section-control-input
|
||||
colorSolarizedDarkControl()
|
||||
|
||||
|
||||
|
||||
49
browser/main/modals/PreferencesModal/Crowdfunding.js
Normal file
49
browser/main/modals/PreferencesModal/Crowdfunding.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './Crowdfunding.styl'
|
||||
|
||||
const electron = require('electron')
|
||||
const { shell } = electron
|
||||
|
||||
class Crowdfunding extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
}
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div styleName='root'>
|
||||
<div styleName='header'>Crowdfunding</div>
|
||||
<p>Dear all,</p>
|
||||
<br />
|
||||
<p>Thanks for your using!</p>
|
||||
<p>Boostnote is used in about 200 countries and regions, it is a awesome developer community.</p>
|
||||
<br />
|
||||
<p>To continue supporting this growth, and to satisfy community expectations,</p>
|
||||
<p>we would like to invest more time in this project.</p>
|
||||
<br />
|
||||
<p>If you like this project and see its potential, you can help!</p>
|
||||
<br />
|
||||
<p>Thanks,</p>
|
||||
<p>Boostnote maintainers.</p>
|
||||
<br />
|
||||
<button styleName='cf-link'>
|
||||
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>Support via OpenCollective</a>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Crowdfunding.propTypes = {
|
||||
}
|
||||
|
||||
export default CSSModules(Crowdfunding, styles)
|
||||
36
browser/main/modals/PreferencesModal/Crowdfunding.styl
Normal file
36
browser/main/modals/PreferencesModal/Crowdfunding.styl
Normal file
@@ -0,0 +1,36 @@
|
||||
@import('./Tab')
|
||||
|
||||
.root
|
||||
padding 15px
|
||||
white-space pre
|
||||
line-height 1.4
|
||||
color alpha($ui-text-color, 90%)
|
||||
width 100%
|
||||
font-size 14px
|
||||
p
|
||||
font-size 16px
|
||||
|
||||
.cf-link
|
||||
width 250px
|
||||
height 35px
|
||||
border-radius 2px
|
||||
border none
|
||||
background-color alpha(#1EC38B, 90%)
|
||||
&:hover
|
||||
background-color #1EC38B
|
||||
transition 0.2s
|
||||
a
|
||||
text-decoration none
|
||||
color white
|
||||
font-weight 600
|
||||
font-size 16px
|
||||
|
||||
body[data-theme="dark"]
|
||||
p
|
||||
color $ui-dark-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
color $ui-solarized-dark-text-color
|
||||
p
|
||||
color $ui-solarized-dark-text-color
|
||||
304
browser/main/modals/PreferencesModal/FolderItem.js
Normal file
304
browser/main/modals/PreferencesModal/FolderItem.js
Normal file
@@ -0,0 +1,304 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import ReactDOM from 'react-dom'
|
||||
import styles from './FolderItem.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { SketchPicker } from 'react-color'
|
||||
import { SortableElement, SortableHandle } from 'react-sortable-hoc'
|
||||
|
||||
class FolderItem extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
status: 'IDLE',
|
||||
folder: {
|
||||
showColumnPicker: false,
|
||||
colorPickerPos: { left: 0, top: 0 },
|
||||
color: props.color,
|
||||
name: props.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleEditChange (e) {
|
||||
const { folder } = this.state
|
||||
|
||||
folder.name = this.refs.nameInput.value
|
||||
this.setState({
|
||||
folder
|
||||
})
|
||||
}
|
||||
|
||||
handleConfirmButtonClick (e) {
|
||||
this.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) => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_FOLDER',
|
||||
storage: data.storage
|
||||
})
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
const colorPickerNode = ReactDOM.findDOMNode(this.refs.colorPicker)
|
||||
const colorPickerBox = colorPickerNode.getBoundingClientRect()
|
||||
const offsetTop = hostBoundingBox.bottom - colorPickerBox.bottom
|
||||
const folder = Object.assign({}, this.state.folder, {
|
||||
colorPickerPos: {
|
||||
left: 25,
|
||||
top: offsetTop < 0 ? offsetTop - 5 : 0 // subtract 5px for aestetics
|
||||
}
|
||||
})
|
||||
this.setState({ folder })
|
||||
})
|
||||
}
|
||||
|
||||
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 })
|
||||
this.setState({ folder })
|
||||
}
|
||||
|
||||
handleCancelButtonClick (e) {
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
})
|
||||
}
|
||||
|
||||
handleFolderItemBlur (e) {
|
||||
let el = e.relatedTarget
|
||||
while (el != null) {
|
||||
if (el === this.refs.root) {
|
||||
return false
|
||||
}
|
||||
el = el.parentNode
|
||||
}
|
||||
this.confirm()
|
||||
}
|
||||
|
||||
renderEdit (e) {
|
||||
const popover = { position: 'absolute', zIndex: 2 }
|
||||
const cover = {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
}
|
||||
const pickerStyle = Object.assign({}, {
|
||||
position: 'absolute'
|
||||
}, this.state.folder.colorPickerPos)
|
||||
return (
|
||||
<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)}
|
||||
>
|
||||
{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)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<i className='fa fa-square' />
|
||||
</button>
|
||||
<input styleName='folderItem-left-nameInput'
|
||||
value={this.state.folder.name}
|
||||
ref='nameInput'
|
||||
onChange={(e) => this.handleEditChange(e)}
|
||||
/>
|
||||
</div>
|
||||
<div styleName='folderItem-right'>
|
||||
<button styleName='folderItem-right-confirmButton'
|
||||
onClick={(e) => this.handleConfirmButtonClick(e)}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button styleName='folderItem-right-button'
|
||||
onClick={(e) => this.handleCancelButtonClick(e)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
renderDelete () {
|
||||
return (
|
||||
<div styleName='folderItem'>
|
||||
<div styleName='folderItem-left'>
|
||||
Are you sure to <span styleName='folderItem-left-danger'>delete</span> this folder?
|
||||
</div>
|
||||
<div styleName='folderItem-right'>
|
||||
<button styleName='folderItem-right-dangerButton'
|
||||
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button styleName='folderItem-right-button'
|
||||
onClick={(e) => this.handleCancelButtonClick(e)}
|
||||
>
|
||||
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 () {
|
||||
const { folder } = this.props
|
||||
return (
|
||||
<div styleName='folderItem'
|
||||
onDoubleClick={(e) => this.handleEditButtonClick(e)}
|
||||
>
|
||||
<div styleName='folderItem-left'
|
||||
style={{borderColor: folder.color}}
|
||||
>
|
||||
<span styleName='folderItem-left-name'>{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)}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button styleName='folderItem-right-button'
|
||||
onClick={(e) => this.handleDeleteButtonClick(e)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
switch (this.state.status) {
|
||||
case 'DELETE':
|
||||
return this.renderDelete()
|
||||
case 'EDIT':
|
||||
return this.renderEdit()
|
||||
case 'IDLE':
|
||||
default:
|
||||
return this.renderIdle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FolderItem.propTypes = {
|
||||
hostBoundingBox: PropTypes.shape({
|
||||
bottom: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
left: PropTypes.number,
|
||||
right: PropTypes.number,
|
||||
top: PropTypes.number,
|
||||
width: PropTypes.number
|
||||
}),
|
||||
storage: PropTypes.shape({
|
||||
key: PropTypes.string
|
||||
}),
|
||||
folder: PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
color: PropTypes.string,
|
||||
name: PropTypes.string
|
||||
})
|
||||
}
|
||||
|
||||
class Handle extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<div styleName='folderItem-drag-handle'>
|
||||
<i className='fa fa-reorder' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class SortableFolderItemComponent extends React.Component {
|
||||
render () {
|
||||
const StyledHandle = CSSModules(Handle, this.props.styles)
|
||||
const DragHandle = SortableHandle(StyledHandle)
|
||||
|
||||
const StyledFolderItem = CSSModules(FolderItem, this.props.styles)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DragHandle />
|
||||
<StyledFolderItem {...this.props} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CSSModules(SortableElement(SortableFolderItemComponent), styles)
|
||||
128
browser/main/modals/PreferencesModal/FolderItem.styl
Normal file
128
browser/main/modals/PreferencesModal/FolderItem.styl
Normal file
@@ -0,0 +1,128 @@
|
||||
.folderItem
|
||||
height 35px
|
||||
box-sizing border-box
|
||||
padding 2.5px 15px
|
||||
&:hover
|
||||
background-color darken(white, 3%)
|
||||
|
||||
.folderItem-drag-handle
|
||||
height 35px
|
||||
border none
|
||||
padding 0 10px
|
||||
line-height 35px
|
||||
float left
|
||||
cursor row-resize
|
||||
|
||||
.folderItem-left
|
||||
height 30px
|
||||
border-left solid 2px transparent
|
||||
padding 0 10px
|
||||
line-height 30px
|
||||
float left
|
||||
.folderItem-left-danger
|
||||
color $danger-color
|
||||
font-weight bold
|
||||
|
||||
.folderItem-left-key
|
||||
color $ui-inactive-text-color
|
||||
font-size 13px
|
||||
margin 0 5px
|
||||
border none
|
||||
|
||||
.folderItem-left-colorButton
|
||||
colorDefaultButton()
|
||||
height 25px
|
||||
width 25px
|
||||
line-height 23px
|
||||
padding 0
|
||||
box-sizing border-box
|
||||
vertical-align middle
|
||||
border $ui-border
|
||||
border-radius 2px
|
||||
margin-right 5px
|
||||
margin-left -15px
|
||||
|
||||
.folderItem-left-nameInput
|
||||
height 25px
|
||||
box-sizing border-box
|
||||
vertical-align middle
|
||||
border $ui-border
|
||||
border-radius 2px
|
||||
padding 0 5px
|
||||
outline none
|
||||
|
||||
.folderItem-right
|
||||
float right
|
||||
|
||||
.folderItem-right-button
|
||||
vertical-align middle
|
||||
height 25px
|
||||
margin-top 2.5px
|
||||
colorDefaultButton()
|
||||
border-radius 2px
|
||||
border $ui-border
|
||||
margin-right 5px
|
||||
padding 0 5px
|
||||
&:last-child
|
||||
margin-right 0
|
||||
|
||||
.folderItem-right-confirmButton
|
||||
@extend .folderItem-right-button
|
||||
border none
|
||||
colorPrimaryButton()
|
||||
|
||||
.folderItem-right-dangerButton
|
||||
@extend .folderItem-right-button
|
||||
border none
|
||||
colorDangerButton()
|
||||
|
||||
body[data-theme="dark"]
|
||||
.folderItem
|
||||
&:hover
|
||||
background-color lighten($ui-dark-button--hover-backgroundColor, 5%)
|
||||
|
||||
.folderItem-left-danger
|
||||
color $danger-color
|
||||
font-weight bold
|
||||
|
||||
.folderItem-left-key
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
.folderItem-left-colorButton
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.folderItem-right-button
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.folderItem-right-confirmButton
|
||||
colorDarkPrimaryButton()
|
||||
|
||||
.folderItem-right-dangerButton
|
||||
colorDarkDangerButton()
|
||||
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.folderItem
|
||||
&:hover
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
|
||||
.folderItem-left-danger
|
||||
color $danger-color
|
||||
|
||||
.folderItem-left-key
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
.folderItem-left-colorButton
|
||||
colorSolarizedDarkPrimaryButton()
|
||||
|
||||
.folderItem-right-button
|
||||
colorSolarizedDarkPrimaryButton()
|
||||
|
||||
.folderItem-right-confirmButton
|
||||
colorSolarizedDarkPrimaryButton()
|
||||
|
||||
.folderItem-right-dangerButton
|
||||
colorSolarizedDarkPrimaryButton()
|
||||
85
browser/main/modals/PreferencesModal/FolderList.js
Normal file
85
browser/main/modals/PreferencesModal/FolderList.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import styles from './FolderList.styl'
|
||||
import store from 'browser/main/store'
|
||||
import FolderItem from './FolderItem'
|
||||
import { SortableContainer } from 'react-sortable-hoc'
|
||||
|
||||
class FolderList extends React.Component {
|
||||
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 (
|
||||
<div styleName='folderList'>
|
||||
{folderList.length > 0
|
||||
? folderList
|
||||
: <div styleName='folderList-empty'>No Folders</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FolderList.propTypes = {
|
||||
hostBoundingBox: PropTypes.shape({
|
||||
bottom: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
left: PropTypes.number,
|
||||
right: PropTypes.number,
|
||||
top: PropTypes.number,
|
||||
width: PropTypes.number
|
||||
}),
|
||||
storage: PropTypes.shape({
|
||||
key: PropTypes.string
|
||||
}),
|
||||
folder: PropTypes.shape({
|
||||
key: PropTypes.number,
|
||||
color: PropTypes.string,
|
||||
name: PropTypes.string
|
||||
})
|
||||
}
|
||||
|
||||
class SortableFolderListComponent extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const StyledFolderList = CSSModules(FolderList, this.props.styles)
|
||||
const SortableFolderList = SortableContainer(StyledFolderList)
|
||||
|
||||
return (
|
||||
<SortableFolderList
|
||||
helperClass='sortableItemHelper'
|
||||
onSortEnd={this.onSortEnd}
|
||||
useDragHandle
|
||||
{...this.props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CSSModules(SortableFolderListComponent, styles)
|
||||
@@ -1,8 +1,10 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ConfigTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import _ from 'lodash'
|
||||
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
@@ -40,7 +42,7 @@ class HotkeyTab extends React.Component {
|
||||
}
|
||||
|
||||
handleSaveButtonClick (e) {
|
||||
let newConfig = {
|
||||
const newConfig = {
|
||||
hotkey: this.state.config.hotkey
|
||||
}
|
||||
|
||||
@@ -50,6 +52,7 @@ class HotkeyTab extends React.Component {
|
||||
type: 'SET_UI',
|
||||
config: newConfig
|
||||
})
|
||||
this.clearMessage()
|
||||
}
|
||||
|
||||
handleHintToggleButtonClick (e) {
|
||||
@@ -59,7 +62,7 @@ class HotkeyTab extends React.Component {
|
||||
}
|
||||
|
||||
handleHotkeyChange (e) {
|
||||
let { config } = this.state
|
||||
const { config } = this.state
|
||||
config.hotkey = {
|
||||
toggleFinder: this.refs.toggleFinder.value,
|
||||
toggleMain: this.refs.toggleMain.value
|
||||
@@ -69,14 +72,22 @@ class HotkeyTab extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
clearMessage () {
|
||||
_.debounce(() => {
|
||||
this.setState({
|
||||
keymapAlert: null
|
||||
})
|
||||
}, 2000)()
|
||||
}
|
||||
|
||||
render () {
|
||||
let keymapAlert = this.state.keymapAlert
|
||||
let keymapAlertElement = keymapAlert != null
|
||||
const keymapAlert = this.state.keymapAlert
|
||||
const keymapAlertElement = keymapAlert != null
|
||||
? <p className={`alert ${keymapAlert.type}`}>
|
||||
{keymapAlert.message}
|
||||
</p>
|
||||
: null
|
||||
let { config } = this.state
|
||||
const { config } = this.state
|
||||
|
||||
return (
|
||||
<div styleName='root'>
|
||||
@@ -94,7 +105,7 @@ class HotkeyTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>Toggle Finder(popup)</div>
|
||||
<div styleName='group-section-label'>Toggle Finder (Quick search)</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleHotkeyChange(e)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import styles from './InfoTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import _ from 'lodash'
|
||||
|
||||
const electron = require('electron')
|
||||
const { shell, remote } = electron
|
||||
@@ -36,8 +37,21 @@ class InfoTab extends React.Component {
|
||||
|
||||
if (!newConfig.amaEnabled) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
|
||||
this.setState({
|
||||
amaMessage: 'We hope we will gain your trust'
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
amaMessage: 'Thank\'s for trust us'
|
||||
})
|
||||
}
|
||||
|
||||
_.debounce(() => {
|
||||
this.setState({
|
||||
amaMessage: ''
|
||||
})
|
||||
}, 3000)()
|
||||
|
||||
ConfigManager.set(newConfig)
|
||||
|
||||
store.dispatch({
|
||||
@@ -46,10 +60,49 @@ class InfoTab extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
infoMessage () {
|
||||
const { amaMessage } = this.state
|
||||
return amaMessage ? <p styleName='policy-confirm'>{amaMessage}</p> : null
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div styleName='root'>
|
||||
<div styleName='header'>Info</div>
|
||||
|
||||
<div styleName='header--sub'>Community</div>
|
||||
<div styleName='top'>
|
||||
<ul styleName='list'>
|
||||
<li>
|
||||
<a href='https://boostnote.io/#subscribe'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Subscribe to Newsletter</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://github.com/BoostIO/Boostnote/issues'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>GitHub</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://medium.com/boostnote'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://www.facebook.com/groups/boostnote'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Facebook Group</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://twitter.com/boostnoteapp'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Twitter</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div styleName='header--sub'>Info</div>
|
||||
|
||||
<div styleName='top'>
|
||||
<div styleName='icon-space'>
|
||||
@@ -62,31 +115,17 @@ class InfoTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul styleName='list'>
|
||||
<li>
|
||||
<a href='https://boostnote.io'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Website</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://boostnote.paintory.com/'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Boostnote Shop</a> : Products are shipped to all over the world 🌏
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://salt.bountysource.com/teams/boostnote'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Donate via Bountysource</a> : Thank you for your support 🎉
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://github.com/BoostIO/Boostnote/issues'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>GitHub Issues</a> : We'd love to hear your feedback 🙌
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://github.com/BoostIO/Boostnote/blob/master/docs/build.md'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Development</a> : Development configurations for Boostnote 🚀
|
||||
>Development</a> : Development configurations for Boostnote.
|
||||
</li>
|
||||
<li styleName='cc'>
|
||||
Copyright (C) 2017 Maisin&Co.
|
||||
@@ -95,11 +134,13 @@ class InfoTab extends React.Component {
|
||||
License: GPL v3
|
||||
</li>
|
||||
</ul>
|
||||
<hr />
|
||||
|
||||
<hr styleName='separate-line' />
|
||||
|
||||
<div styleName='policy'>Data collection policy</div>
|
||||
<p>We collect only the number of DAU for Boostnote and DO NOT collect any detail information</p>
|
||||
<p>such as your note content. You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</p>
|
||||
<p>These data are only used for Boostnote improvements.</p>
|
||||
<div>We collect only the number of DAU for Boostnote and **DO NOT collect** any detail information such as your note content.</div>
|
||||
<div>You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
|
||||
<div>This data is only used for Boostnote improvements.</div>
|
||||
<input onChange={(e) => this.handleConfigChange(e)}
|
||||
checked={this.state.config.amaEnabled}
|
||||
ref='amaEnabled'
|
||||
@@ -107,6 +148,7 @@ class InfoTab extends React.Component {
|
||||
/>
|
||||
Enable to send analytics to our servers<br />
|
||||
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>Save</button>
|
||||
{this.infoMessage()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,13 +42,29 @@
|
||||
color #4E8EC6
|
||||
text-decoration none
|
||||
|
||||
.separate-line
|
||||
margin 40px 0
|
||||
|
||||
.policy
|
||||
width 100%
|
||||
font-size 20px
|
||||
margin-bottom 10px
|
||||
|
||||
.policy-submit
|
||||
margin-top 10px
|
||||
|
||||
.policy-confirm
|
||||
margin-top 10px
|
||||
font-size 12px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
color alpha($tab--dark-text-color, 80%)
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
color $ui-solarized-dark-text-color
|
||||
.list
|
||||
a
|
||||
color $ui-solarized-dark-active-color
|
||||
|
||||
@@ -4,9 +4,10 @@ top-bar--height = 50px
|
||||
|
||||
.root
|
||||
modal()
|
||||
max-width 800px
|
||||
min-height 500px
|
||||
height 80%
|
||||
max-width 100vw
|
||||
min-height 100vh
|
||||
height 100vh
|
||||
width 100vw
|
||||
overflow hidden
|
||||
position relative
|
||||
|
||||
@@ -24,23 +25,23 @@ top-bar--height = 50px
|
||||
absolute top left right
|
||||
top top-bar--height
|
||||
left 0
|
||||
width 140px
|
||||
margin-left 30px
|
||||
width 170px
|
||||
margin-left 10px
|
||||
margin-top 20px
|
||||
background-color $ui-backgroundColor
|
||||
|
||||
.nav-button
|
||||
font-size 14px
|
||||
text-align left
|
||||
width 100px
|
||||
margin 4px 0
|
||||
padding 5px 0
|
||||
width 150px
|
||||
margin 5px 0
|
||||
padding 7px 0
|
||||
padding-left 10px
|
||||
border none
|
||||
border-radius 2px
|
||||
background-color transparent
|
||||
color $ui-text-color
|
||||
font-size 14px
|
||||
font-size 16px
|
||||
|
||||
.nav-button--active
|
||||
@extend .nav-button
|
||||
@@ -55,7 +56,7 @@ top-bar--height = 50px
|
||||
.content
|
||||
absolute left right bottom
|
||||
top top-bar--height
|
||||
left 140px
|
||||
left 170px
|
||||
margin-top 10px
|
||||
overflow-y auto
|
||||
|
||||
@@ -85,3 +86,29 @@ body[data-theme="dark"]
|
||||
background-color $dark-primary-button-background--active
|
||||
&:hover
|
||||
color white
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
background-color transparent
|
||||
.top-bar
|
||||
background-color transparent
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
p
|
||||
color $ui-solarized-dark-text-color
|
||||
.nav
|
||||
background-color transparent
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
.nav-button
|
||||
background-color transparent
|
||||
color $ui-solarized-dark-text-color
|
||||
&:hover
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.nav-button--active
|
||||
@extend .nav-button
|
||||
color $ui-solarized-dark-button--active-color
|
||||
background-color $ui-solarized-dark-button--active-backgroundColor
|
||||
&:hover
|
||||
color white
|
||||
|
||||
|
||||
@@ -1,265 +1,14 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StorageItem.styl'
|
||||
import consts from 'browser/lib/consts'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import FolderList from './FolderList'
|
||||
|
||||
const { shell, remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
import { SketchPicker } from 'react-color'
|
||||
|
||||
class UnstyledFolderItem extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
status: 'IDLE',
|
||||
folder: {
|
||||
showColumnPicker: false,
|
||||
colorPickerPos: { left: 0, top: 0 },
|
||||
color: props.color,
|
||||
name: props.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleEditChange (e) {
|
||||
let { folder } = this.state
|
||||
|
||||
folder.name = this.refs.nameInput.value
|
||||
this.setState({
|
||||
folder
|
||||
})
|
||||
}
|
||||
|
||||
handleConfirmButtonClick (e) {
|
||||
this.confirm()
|
||||
}
|
||||
|
||||
confirm () {
|
||||
let { storage, folder } = this.props
|
||||
dataApi
|
||||
.updateFolder(storage.key, folder.key, {
|
||||
color: this.state.folder.color,
|
||||
name: this.state.folder.name
|
||||
})
|
||||
.then((data) => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_FOLDER',
|
||||
storage: data.storage
|
||||
})
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
const colorPickerNode = ReactDOM.findDOMNode(this.refs.colorPicker)
|
||||
const colorPickerBox = colorPickerNode.getBoundingClientRect()
|
||||
const offsetTop = hostBoundingBox.bottom - colorPickerBox.bottom
|
||||
const folder = Object.assign({}, this.state.folder, {
|
||||
colorPickerPos: {
|
||||
left: 25,
|
||||
top: offsetTop < 0 ? offsetTop - 5 : 0 // subtract 5px for aestetics
|
||||
}
|
||||
})
|
||||
this.setState({ folder })
|
||||
})
|
||||
}
|
||||
|
||||
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 })
|
||||
this.setState({ folder })
|
||||
}
|
||||
|
||||
handleCancelButtonClick (e) {
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
})
|
||||
}
|
||||
|
||||
handleFolderItemBlur (e) {
|
||||
let el = e.relatedTarget
|
||||
while (el != null) {
|
||||
if (el === this.refs.root) {
|
||||
return false
|
||||
}
|
||||
el = el.parentNode
|
||||
}
|
||||
this.confirm()
|
||||
}
|
||||
|
||||
renderEdit (e) {
|
||||
const popover = { position: 'absolute', zIndex: 2 }
|
||||
const cover = {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
}
|
||||
const pickerStyle = Object.assign({}, {
|
||||
position: 'absolute'
|
||||
}, this.state.folder.colorPickerPos)
|
||||
return (
|
||||
<div styleName='folderList-item'
|
||||
onBlur={(e) => this.handleFolderItemBlur(e)}
|
||||
tabIndex='-1'
|
||||
ref='root'
|
||||
>
|
||||
<div styleName='folderList-item-left'>
|
||||
<button styleName='folderList-item-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}
|
||||
onClick={() => this.handleColorPickerClose()}
|
||||
/>
|
||||
<div style={pickerStyle}>
|
||||
<SketchPicker
|
||||
ref='colorPicker'
|
||||
color={this.state.folder.color}
|
||||
onChange={(color) => this.handleColorChange(color)}
|
||||
onChangeComplete={(color) => this.handleColorChange(color)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
<i className='fa fa-square' />
|
||||
</button>
|
||||
<input styleName='folderList-item-left-nameInput'
|
||||
value={this.state.folder.name}
|
||||
ref='nameInput'
|
||||
onChange={(e) => this.handleEditChange(e)}
|
||||
/>
|
||||
</div>
|
||||
<div styleName='folderList-item-right'>
|
||||
<button styleName='folderList-item-right-confirmButton'
|
||||
onClick={(e) => this.handleConfirmButtonClick(e)}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button styleName='folderList-item-right-button'
|
||||
onClick={(e) => this.handleCancelButtonClick(e)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
handleDeleteConfirmButtonClick (e) {
|
||||
let { storage, folder } = this.props
|
||||
dataApi
|
||||
.deleteFolder(storage.key, folder.key)
|
||||
.then((data) => {
|
||||
store.dispatch({
|
||||
type: 'DELETE_FOLDER',
|
||||
storage: data.storage,
|
||||
folderKey: data.folderKey
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
renderDelete () {
|
||||
return (
|
||||
<div styleName='folderList-item'>
|
||||
<div styleName='folderList-item-left'>
|
||||
Are you sure to <span styleName='folderList-item-left-danger'>delete</span> this folder?
|
||||
</div>
|
||||
<div styleName='folderList-item-right'>
|
||||
<button styleName='folderList-item-right-dangerButton'
|
||||
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
<button styleName='folderList-item-right-button'
|
||||
onClick={(e) => this.handleCancelButtonClick(e)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
handleEditButtonClick (e) {
|
||||
let { folder: propsFolder } = this.props
|
||||
let { 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 () {
|
||||
let { folder } = this.props
|
||||
return (
|
||||
<div styleName='folderList-item'
|
||||
onDoubleClick={(e) => this.handleEditButtonClick(e)}
|
||||
>
|
||||
<div styleName='folderList-item-left'
|
||||
style={{borderColor: folder.color}}
|
||||
>
|
||||
<span styleName='folderList-item-left-name'>{folder.name}</span>
|
||||
<span styleName='folderList-item-left-key'>({folder.key})</span>
|
||||
</div>
|
||||
<div styleName='folderList-item-right'>
|
||||
<button styleName='folderList-item-right-button'
|
||||
onClick={(e) => this.handleEditButtonClick(e)}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button styleName='folderList-item-right-button'
|
||||
onClick={(e) => this.handleDeleteButtonClick(e)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
switch (this.state.status) {
|
||||
case 'DELETE':
|
||||
return this.renderDelete()
|
||||
case 'EDIT':
|
||||
return this.renderEdit()
|
||||
case 'IDLE':
|
||||
default:
|
||||
return this.renderIdle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FolderItem = CSSModules(UnstyledFolderItem, styles)
|
||||
|
||||
class StorageItem extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -271,8 +20,8 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleNewFolderButtonClick (e) {
|
||||
let { storage } = this.props
|
||||
let input = {
|
||||
const { storage } = this.props
|
||||
const input = {
|
||||
name: 'Untitled',
|
||||
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
|
||||
}
|
||||
@@ -290,12 +39,12 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleExternalButtonClick () {
|
||||
let { storage } = this.props
|
||||
const { storage } = this.props
|
||||
shell.showItemInFolder(storage.path)
|
||||
}
|
||||
|
||||
handleUnlinkButtonClick (e) {
|
||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Unlink Storage',
|
||||
detail: 'Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.',
|
||||
@@ -303,7 +52,7 @@ class StorageItem extends React.Component {
|
||||
})
|
||||
|
||||
if (index === 0) {
|
||||
let { storage } = this.props
|
||||
const { storage } = this.props
|
||||
dataApi.removeStorage(storage.key)
|
||||
.then(() => {
|
||||
store.dispatch({
|
||||
@@ -318,7 +67,7 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleLabelClick (e) {
|
||||
let { storage } = this.props
|
||||
const { storage } = this.props
|
||||
this.setState({
|
||||
isLabelEditing: true,
|
||||
name: storage.name
|
||||
@@ -333,7 +82,7 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleLabelBlur (e) {
|
||||
let { storage } = this.props
|
||||
const { storage } = this.props
|
||||
dataApi
|
||||
.renameStorage(storage.key, this.state.name)
|
||||
.then((_storage) => {
|
||||
@@ -348,14 +97,8 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { storage, hostBoundingBox } = this.props
|
||||
let folderList = storage.folders.map((folder) => {
|
||||
return <FolderItem key={folder.key}
|
||||
folder={folder}
|
||||
storage={storage}
|
||||
hostBoundingBox={hostBoundingBox}
|
||||
/>
|
||||
})
|
||||
const { storage, hostBoundingBox } = this.props
|
||||
|
||||
return (
|
||||
<div styleName='root' key={storage.key}>
|
||||
<div styleName='header'>
|
||||
@@ -404,12 +147,9 @@ class StorageItem extends React.Component {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='folderList'>
|
||||
{folderList.length > 0
|
||||
? folderList
|
||||
: <div styleName='folderList-empty'>No Folders</div>
|
||||
}
|
||||
</div>
|
||||
<FolderList storage={storage}
|
||||
hostBoundingBox={hostBoundingBox}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -426,11 +166,6 @@ StorageItem.propTypes = {
|
||||
}),
|
||||
storage: PropTypes.shape({
|
||||
key: PropTypes.string
|
||||
}),
|
||||
folder: PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
color: PropTypes.string,
|
||||
name: PropTypes.string
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -63,75 +63,6 @@
|
||||
z-index 10
|
||||
white-space nowrap
|
||||
|
||||
.folderList-item
|
||||
height 35px
|
||||
box-sizing border-box
|
||||
padding 2.5px 15px
|
||||
&:hover
|
||||
background-color darken(white, 3%)
|
||||
.folderList-item-left
|
||||
height 30px
|
||||
border-left solid 2px transparent
|
||||
padding 0 10px
|
||||
line-height 30px
|
||||
float left
|
||||
.folderList-item-left-danger
|
||||
color $danger-color
|
||||
font-weight bold
|
||||
|
||||
.folderList-item-left-key
|
||||
color $ui-inactive-text-color
|
||||
font-size 10px
|
||||
margin 0 5px
|
||||
border none
|
||||
|
||||
.folderList-item-left-colorButton
|
||||
colorDefaultButton()
|
||||
height 25px
|
||||
width 25px
|
||||
line-height 23px
|
||||
padding 0
|
||||
box-sizing border-box
|
||||
vertical-align middle
|
||||
border $ui-border
|
||||
border-radius 2px
|
||||
margin-right 5px
|
||||
margin-left -15px
|
||||
|
||||
.folderList-item-left-nameInput
|
||||
height 25px
|
||||
box-sizing border-box
|
||||
vertical-align middle
|
||||
border $ui-border
|
||||
border-radius 2px
|
||||
padding 0 5px
|
||||
outline none
|
||||
|
||||
.folderList-item-right
|
||||
float right
|
||||
|
||||
.folderList-item-right-button
|
||||
vertical-align middle
|
||||
height 25px
|
||||
margin-top 2.5px
|
||||
colorDefaultButton()
|
||||
border-radius 2px
|
||||
border $ui-border
|
||||
margin-right 5px
|
||||
padding 0 5px
|
||||
&:last-child
|
||||
margin-right 0
|
||||
|
||||
.folderList-item-right-confirmButton
|
||||
@extend .folderList-item-right-button
|
||||
border none
|
||||
colorPrimaryButton()
|
||||
|
||||
.folderList-item-right-dangerButton
|
||||
@extend .folderList-item-right-button
|
||||
border none
|
||||
colorDangerButton()
|
||||
|
||||
body[data-theme="dark"]
|
||||
.header
|
||||
border-color $ui-dark-borderColor
|
||||
@@ -153,28 +84,3 @@ body[data-theme="dark"]
|
||||
top 25px
|
||||
z-index 10
|
||||
white-space nowrap
|
||||
|
||||
.folderList-item
|
||||
&:hover
|
||||
background-color lighten($ui-dark-button--hover-backgroundColor, 5%)
|
||||
|
||||
.folderList-item-left-danger
|
||||
color $danger-color
|
||||
font-weight bold
|
||||
|
||||
.folderList-item-left-key
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
.folderList-item-left-colorButton
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.folderList-item-right-button
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.folderList-item-right-confirmButton
|
||||
colorDarkPrimaryButton()
|
||||
|
||||
.folderList-item-right-dangerButton
|
||||
colorDarkDangerButton()
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StoragesTab.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import StorageItem from './StorageItem'
|
||||
|
||||
const electron = require('electron')
|
||||
const remote = electron.remote
|
||||
const { shell, remote } = electron
|
||||
|
||||
function browseFolder () {
|
||||
let dialog = remote.dialog
|
||||
const dialog = remote.dialog
|
||||
|
||||
let defaultPath = remote.app.getPath('home')
|
||||
const defaultPath = remote.app.getPath('home')
|
||||
return new Promise((resolve, reject) => {
|
||||
dialog.showOpenDialog({
|
||||
title: 'Select Directory',
|
||||
@@ -50,11 +51,16 @@ class StoragesTab extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
renderList () {
|
||||
let { data, boundingBox } = this.props
|
||||
const { data, boundingBox } = this.props
|
||||
|
||||
if (!boundingBox) { return null }
|
||||
let storageList = data.storageMap.map((storage) => {
|
||||
const storageList = data.storageMap.map((storage) => {
|
||||
return <StorageItem
|
||||
key={storage.key}
|
||||
storage={storage}
|
||||
@@ -83,7 +89,7 @@ class StoragesTab extends React.Component {
|
||||
browseFolder()
|
||||
.then((targetPath) => {
|
||||
if (targetPath.length > 0) {
|
||||
let { newStorage } = this.state
|
||||
const { newStorage } = this.state
|
||||
newStorage.path = targetPath
|
||||
this.setState({
|
||||
newStorage
|
||||
@@ -97,7 +103,7 @@ class StoragesTab extends React.Component {
|
||||
}
|
||||
|
||||
handleAddStorageChange (e) {
|
||||
let { newStorage } = this.state
|
||||
const { newStorage } = this.state
|
||||
newStorage.name = this.refs.addStorageName.value
|
||||
newStorage.path = this.refs.addStoragePath.value
|
||||
this.setState({
|
||||
@@ -112,7 +118,7 @@ class StoragesTab extends React.Component {
|
||||
path: this.state.newStorage.path
|
||||
})
|
||||
.then((data) => {
|
||||
let { dispatch } = this.props
|
||||
const { dispatch } = this.props
|
||||
dispatch({
|
||||
type: 'ADD_STORAGE',
|
||||
storage: data.storage,
|
||||
@@ -161,7 +167,10 @@ class StoragesTab extends React.Component {
|
||||
<option value='FILESYSTEM'>File System</option>
|
||||
</select>
|
||||
<div styleName='addStorage-body-section-type-description'>
|
||||
3rd party cloud integration(such as Google Drive and Dropbox) will be available soon.
|
||||
3rd party cloud integration:
|
||||
<a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Cloud-Syncing-and-Backup</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -158,3 +158,44 @@ body[data-theme="dark"]
|
||||
.addStorage-body-control-cancelButton
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.folderList-item
|
||||
border-bottom $ui-solarized-dark-borderColor
|
||||
|
||||
.folderList-empty
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.list-empty
|
||||
color $ui-solarized-dark-text-color
|
||||
.list-control-addStorageButton
|
||||
border-color $ui-solarized-dark-button-backgroundColor
|
||||
background-color $ui-solarized-dark-button-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.addStorage-header
|
||||
color $ui-solarized-dark-text-color
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
|
||||
.addStorage-body-section-name-input
|
||||
border-color $$ui-solarized-dark-borderColor
|
||||
|
||||
.addStorage-body-section-type-description
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.addStorage-body-section-path-button
|
||||
colorPrimaryButton()
|
||||
.addStorage-body-control
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
|
||||
.addStorage-body-control-createButton
|
||||
colorDarkPrimaryButton()
|
||||
.addStorage-body-control-cancelButton
|
||||
colorDarkDefaultButton()
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
|
||||
|
||||
@@ -10,8 +10,12 @@ $tab--button-font-size = 14px
|
||||
$tab--dark-text-color = #E5E5E5
|
||||
|
||||
.header
|
||||
font-size 24px
|
||||
margin-bottom 30px
|
||||
font-size 36px
|
||||
margin-bottom 60px
|
||||
|
||||
.header--sub
|
||||
font-size 36px
|
||||
margin-bottom 20px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.header
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ConfigTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
@@ -9,6 +10,11 @@ import CodeMirror from 'codemirror'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
import _ from 'lodash'
|
||||
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
|
||||
class UiTab extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
@@ -18,8 +24,27 @@ class UiTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
CodeMirror.autoLoadMode(ReactCodeMirror, 'javascript')
|
||||
componentDidMount () {
|
||||
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
|
||||
this.handleSettingDone = () => {
|
||||
this.setState({UiAlert: {
|
||||
type: 'success',
|
||||
message: 'Successfully applied!'
|
||||
}})
|
||||
}
|
||||
this.handleSettingError = (err) => {
|
||||
this.setState({UiAlert: {
|
||||
type: 'error',
|
||||
message: err.message != null ? err.message : 'Error occurs!'
|
||||
}})
|
||||
}
|
||||
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||
ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||
}
|
||||
|
||||
handleUIChange (e) {
|
||||
@@ -36,6 +61,7 @@ class UiTab extends React.Component {
|
||||
const newConfig = {
|
||||
ui: {
|
||||
theme: this.refs.uiTheme.value,
|
||||
showCopyNotification: this.refs.showCopyNotification.checked,
|
||||
disableDirectWrite: this.refs.uiD2w != null
|
||||
? this.refs.uiD2w.checked
|
||||
: false
|
||||
@@ -48,20 +74,25 @@ class UiTab extends React.Component {
|
||||
indentSize: this.refs.editorIndentSize.value,
|
||||
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
|
||||
switchPreview: this.refs.editorSwitchPreview.value,
|
||||
keyMap: this.refs.editorKeyMap.value
|
||||
keyMap: this.refs.editorKeyMap.value,
|
||||
scrollPastEnd: this.refs.scrollPastEnd.checked
|
||||
},
|
||||
preview: {
|
||||
fontSize: this.refs.previewFontSize.value,
|
||||
fontFamily: this.refs.previewFontFamily.value,
|
||||
codeBlockTheme: this.refs.previewCodeBlockTheme.value,
|
||||
lineNumber: this.refs.previewLineNumber.checked
|
||||
lineNumber: this.refs.previewLineNumber.checked,
|
||||
latexInlineOpen: this.refs.previewLatexInlineOpen.value,
|
||||
latexInlineClose: this.refs.previewLatexInlineClose.value,
|
||||
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
|
||||
latexBlockClose: this.refs.previewLatexBlockClose.value
|
||||
}
|
||||
}
|
||||
|
||||
const newCodemirrorTheme = this.refs.editorTheme.value
|
||||
|
||||
if (newCodemirrorTheme !== codemirrorTheme) {
|
||||
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme}.css`)
|
||||
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`)
|
||||
}
|
||||
|
||||
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme })
|
||||
@@ -80,9 +111,25 @@ class UiTab extends React.Component {
|
||||
type: 'SET_UI',
|
||||
config: newConfig
|
||||
})
|
||||
this.clearMessage()
|
||||
}
|
||||
|
||||
clearMessage () {
|
||||
_.debounce(() => {
|
||||
this.setState({
|
||||
UiAlert: null
|
||||
})
|
||||
}, 2000)()
|
||||
}
|
||||
|
||||
render () {
|
||||
const UiAlert = this.state.UiAlert
|
||||
const UiAlertElement = UiAlert != null
|
||||
? <p className={`alert ${UiAlert.type}`}>
|
||||
{UiAlert.message}
|
||||
</p>
|
||||
: null
|
||||
|
||||
const themes = consts.THEMES
|
||||
const { config, codemirrorTheme } = this.state
|
||||
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
|
||||
@@ -91,19 +138,30 @@ class UiTab extends React.Component {
|
||||
<div styleName='group'>
|
||||
<div styleName='group-header'>UI</div>
|
||||
|
||||
<div styleName='group-header2'>Theme</div>
|
||||
|
||||
<div styleName='group-section'>
|
||||
Color Theme
|
||||
<div styleName='group-section-control'>
|
||||
<select value={config.ui.theme}
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
ref='uiTheme'
|
||||
>
|
||||
<option value='default'>Light</option>
|
||||
<option value='default'>Default</option>
|
||||
<option value='white'>White</option>
|
||||
<option value='solarized-dark'>Solarized Dark</option>
|
||||
<option value='dark'>Dark</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
checked={this.state.config.ui.showCopyNotification}
|
||||
ref='showCopyNotification'
|
||||
type='checkbox'
|
||||
/>
|
||||
Show "Saved to Clipboard" notification when copying
|
||||
</label>
|
||||
</div>
|
||||
{
|
||||
global.process.platform === 'win32'
|
||||
? <div styleName='group-checkBoxSection'>
|
||||
@@ -137,7 +195,7 @@ class UiTab extends React.Component {
|
||||
}
|
||||
</select>
|
||||
<div styleName='code-mirror'>
|
||||
<ReactCodeMirror value={codemirrorSampleCode} options={{ lineNumbers: true, readOnly: true, mode: 'javascript', theme: codemirrorTheme }} />
|
||||
<ReactCodeMirror ref={e => (this.codeMirrorInstance = e)} value={codemirrorSampleCode} options={{ lineNumbers: true, readOnly: true, mode: 'javascript', theme: codemirrorTheme }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,7 +277,7 @@ class UiTab extends React.Component {
|
||||
<option value='vim'>vim</option>
|
||||
<option value='emacs'>emacs</option>
|
||||
</select>
|
||||
<span styleName='note-for-keymap'>Please restart boostnote after you change the keymap</span>
|
||||
<p styleName='note-for-keymap'>⚠️ Please restart boostnote after you change the keymap</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -234,6 +292,18 @@ class UiTab extends React.Component {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
checked={this.state.config.editor.scrollPastEnd}
|
||||
ref='scrollPastEnd'
|
||||
type='checkbox'
|
||||
/>
|
||||
Allow editor to scroll past the last line
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<div styleName='group-header2'>Preview</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
@@ -286,13 +356,64 @@ class UiTab extends React.Component {
|
||||
Show line numbers for preview code blocks
|
||||
</label>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
LaTeX Inline Open Delimiter
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
ref='previewLatexInlineOpen'
|
||||
value={config.preview.latexInlineOpen}
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
LaTeX Inline Close Delimiter
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
ref='previewLatexInlineClose'
|
||||
value={config.preview.latexInlineClose}
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
LaTeX Block Open Delimiter
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
ref='previewLatexBlockOpen'
|
||||
value={config.preview.latexBlockOpen}
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
LaTeX Block Close Delimiter
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input styleName='group-section-control-input'
|
||||
ref='previewLatexBlockClose'
|
||||
value={config.preview.latexBlockClose}
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='group-control'>
|
||||
<div styleName='group-control'>
|
||||
<button styleName='group-control-rightButton'
|
||||
onClick={(e) => this.handleSaveUIClick(e)}
|
||||
>
|
||||
Save
|
||||
onClick={(e) => this.handleSaveUIClick(e)}>Save
|
||||
</button>
|
||||
{UiAlertElement}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import HotkeyTab from './HotkeyTab'
|
||||
import UiTab from './UiTab'
|
||||
import InfoTab from './InfoTab'
|
||||
import Crowdfunding from './Crowdfunding'
|
||||
import StoragesTab from './StoragesTab'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './PreferencesModal.styl'
|
||||
import RealtimeNotification from 'browser/components/RealtimeNotification'
|
||||
|
||||
class Preferences extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -40,7 +43,7 @@ class Preferences extends React.Component {
|
||||
|
||||
renderContent () {
|
||||
const { boundingBox } = this.state
|
||||
let { dispatch, config, data } = this.props
|
||||
const { dispatch, config, data } = this.props
|
||||
|
||||
switch (this.state.currentTab) {
|
||||
case 'INFO':
|
||||
@@ -64,6 +67,10 @@ class Preferences extends React.Component {
|
||||
config={config}
|
||||
/>
|
||||
)
|
||||
case 'CROWDFUNDING':
|
||||
return (
|
||||
<Crowdfunding />
|
||||
)
|
||||
case 'STORAGES':
|
||||
default:
|
||||
return (
|
||||
@@ -88,17 +95,18 @@ class Preferences extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let content = this.renderContent()
|
||||
const content = this.renderContent()
|
||||
|
||||
let tabs = [
|
||||
const tabs = [
|
||||
{target: 'STORAGES', label: 'Storages'},
|
||||
{target: 'HOTKEY', label: 'Hotkey'},
|
||||
{target: 'UI', label: 'UI'},
|
||||
{target: 'INFO', label: 'Info'}
|
||||
{target: 'INFO', label: 'Community / Info'},
|
||||
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
|
||||
]
|
||||
|
||||
let navButtons = tabs.map((tab) => {
|
||||
let isActive = this.state.currentTab === tab.target
|
||||
const navButtons = tabs.map((tab) => {
|
||||
const isActive = this.state.currentTab === tab.target
|
||||
return (
|
||||
<button styleName={isActive
|
||||
? 'nav-button--active'
|
||||
@@ -130,6 +138,7 @@ class Preferences extends React.Component {
|
||||
<div ref='content' styleName='content'>
|
||||
{content}
|
||||
</div>
|
||||
<RealtimeNotification />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './RenameFolderModal.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
@@ -48,7 +49,7 @@ class RenameFolderModal extends React.Component {
|
||||
|
||||
confirm () {
|
||||
if (this.state.name.trim().length > 0) {
|
||||
let { storage, folder } = this.props
|
||||
const { storage, folder } = this.props
|
||||
dataApi
|
||||
.updateFolder(storage.key, folder.key, {
|
||||
name: this.state.name,
|
||||
|
||||
@@ -27,8 +27,8 @@ function data (state = defaultDataMap(), action) {
|
||||
|
||||
action.notes.some((note) => {
|
||||
if (note === undefined) return true
|
||||
let uniqueKey = note.storage + '-' + note.key
|
||||
let folderKey = note.storage + '-' + note.folder
|
||||
const uniqueKey = note.storage + '-' + note.key
|
||||
const folderKey = note.storage + '-' + note.folder
|
||||
state.noteMap.set(uniqueKey, note)
|
||||
|
||||
if (note.isStarred) {
|
||||
@@ -65,10 +65,10 @@ function data (state = defaultDataMap(), action) {
|
||||
return state
|
||||
case 'UPDATE_NOTE':
|
||||
{
|
||||
let note = action.note
|
||||
let uniqueKey = note.storage + '-' + note.key
|
||||
let folderKey = note.storage + '-' + note.folder
|
||||
let oldNote = state.noteMap.get(uniqueKey)
|
||||
const note = action.note
|
||||
const uniqueKey = note.storage + '-' + note.key
|
||||
const folderKey = note.storage + '-' + note.folder
|
||||
const oldNote = state.noteMap.get(uniqueKey)
|
||||
|
||||
state = Object.assign({}, state)
|
||||
state.noteMap = new Map(state.noteMap)
|
||||
@@ -110,7 +110,7 @@ function data (state = defaultDataMap(), action) {
|
||||
state.folderNoteMap.set(folderKey, folderNoteSet)
|
||||
|
||||
if (oldNote != null) {
|
||||
let oldFolderKey = oldNote.storage + '-' + oldNote.folder
|
||||
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
|
||||
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
|
||||
oldFolderNoteList = new Set(oldFolderNoteList)
|
||||
oldFolderNoteList.delete(uniqueKey)
|
||||
@@ -119,8 +119,8 @@ function data (state = defaultDataMap(), action) {
|
||||
}
|
||||
|
||||
if (oldNote != null) {
|
||||
let discardedTags = _.difference(oldNote.tags, note.tags)
|
||||
let addedTags = _.difference(note.tags, oldNote.tags)
|
||||
const discardedTags = _.difference(oldNote.tags, note.tags)
|
||||
const addedTags = _.difference(note.tags, oldNote.tags)
|
||||
if (discardedTags.length + addedTags.length > 0) {
|
||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||
|
||||
@@ -156,12 +156,12 @@ function data (state = defaultDataMap(), action) {
|
||||
}
|
||||
case 'MOVE_NOTE':
|
||||
{
|
||||
let originNote = action.originNote
|
||||
let originKey = originNote.storage + '-' + originNote.key
|
||||
let note = action.note
|
||||
let uniqueKey = note.storage + '-' + note.key
|
||||
let folderKey = note.storage + '-' + note.folder
|
||||
let oldNote = state.noteMap.get(uniqueKey)
|
||||
const originNote = action.originNote
|
||||
const originKey = originNote.storage + '-' + originNote.key
|
||||
const note = action.note
|
||||
const uniqueKey = note.storage + '-' + note.key
|
||||
const folderKey = note.storage + '-' + note.folder
|
||||
const oldNote = state.noteMap.get(uniqueKey)
|
||||
|
||||
state = Object.assign({}, state)
|
||||
state.noteMap = new Map(state.noteMap)
|
||||
@@ -191,7 +191,7 @@ function data (state = defaultDataMap(), action) {
|
||||
|
||||
// From folderNoteMap
|
||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||
let originFolderKey = originNote.storage + '-' + originNote.folder
|
||||
const originFolderKey = originNote.storage + '-' + originNote.folder
|
||||
let originFolderList = state.folderNoteMap.get(originFolderKey)
|
||||
originFolderList = new Set(originFolderList)
|
||||
originFolderList.delete(originKey)
|
||||
@@ -245,7 +245,7 @@ function data (state = defaultDataMap(), action) {
|
||||
state.folderNoteMap.set(folderKey, folderNoteList)
|
||||
|
||||
if (oldNote != null) {
|
||||
let oldFolderKey = oldNote.storage + '-' + oldNote.folder
|
||||
const oldFolderKey = oldNote.storage + '-' + oldNote.folder
|
||||
let oldFolderNoteList = state.folderNoteMap.get(oldFolderKey)
|
||||
oldFolderNoteList = new Set(oldFolderNoteList)
|
||||
oldFolderNoteList.delete(uniqueKey)
|
||||
@@ -255,8 +255,8 @@ function data (state = defaultDataMap(), action) {
|
||||
|
||||
// Remove from old folder map
|
||||
if (oldNote != null) {
|
||||
let discardedTags = _.difference(oldNote.tags, note.tags)
|
||||
let addedTags = _.difference(note.tags, oldNote.tags)
|
||||
const discardedTags = _.difference(oldNote.tags, note.tags)
|
||||
const addedTags = _.difference(note.tags, oldNote.tags)
|
||||
if (discardedTags.length + addedTags.length > 0) {
|
||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||
|
||||
@@ -292,8 +292,8 @@ function data (state = defaultDataMap(), action) {
|
||||
}
|
||||
case 'DELETE_NOTE':
|
||||
{
|
||||
let uniqueKey = action.storageKey + '-' + action.noteKey
|
||||
let targetNote = state.noteMap.get(uniqueKey)
|
||||
const uniqueKey = action.storageKey + '-' + action.noteKey
|
||||
const targetNote = state.noteMap.get(uniqueKey)
|
||||
|
||||
state = Object.assign({}, state)
|
||||
|
||||
@@ -317,7 +317,7 @@ function data (state = defaultDataMap(), action) {
|
||||
}
|
||||
|
||||
// From folderNoteMap
|
||||
let folderKey = targetNote.storage + '-' + targetNote.folder
|
||||
const folderKey = targetNote.storage + '-' + targetNote.folder
|
||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||
let folderSet = state.folderNoteMap.get(folderKey)
|
||||
folderSet = new Set(folderSet)
|
||||
@@ -340,11 +340,14 @@ function data (state = defaultDataMap(), action) {
|
||||
return state
|
||||
}
|
||||
case 'UPDATE_FOLDER':
|
||||
{
|
||||
state = Object.assign({}, state)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
}
|
||||
state = Object.assign({}, state)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
return state
|
||||
case 'REORDER_FOLDER':
|
||||
state = Object.assign({}, state)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
return state
|
||||
case 'DELETE_FOLDER':
|
||||
{
|
||||
@@ -354,8 +357,8 @@ function data (state = defaultDataMap(), action) {
|
||||
|
||||
// Get note list from folder-note map
|
||||
// and delete note set from folder-note map
|
||||
let folderKey = action.storage.key + '-' + action.folderKey
|
||||
let noteSet = state.folderNoteMap.get(folderKey)
|
||||
const folderKey = action.storage.key + '-' + action.folderKey
|
||||
const noteSet = state.folderNoteMap.get(folderKey)
|
||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||
state.folderNoteMap.delete(folderKey)
|
||||
|
||||
@@ -368,7 +371,7 @@ function data (state = defaultDataMap(), action) {
|
||||
if (noteSet != null) {
|
||||
noteSet.forEach(function handleNoteKey (noteKey) {
|
||||
// Get note from noteMap
|
||||
let note = state.noteMap.get(noteKey)
|
||||
const note = state.noteMap.get(noteKey)
|
||||
if (note != null) {
|
||||
state.noteMap.delete(noteKey)
|
||||
|
||||
@@ -410,8 +413,8 @@ function data (state = defaultDataMap(), action) {
|
||||
state.folderNoteMap = new Map(state.folderNoteMap)
|
||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||
action.notes.forEach((note) => {
|
||||
let uniqueKey = note.storage + '-' + note.key
|
||||
let folderKey = note.storage + '-' + note.folder
|
||||
const uniqueKey = note.storage + '-' + note.key
|
||||
const folderKey = note.storage + '-' + note.folder
|
||||
state.noteMap.set(uniqueKey, note)
|
||||
|
||||
if (note.isStarred) {
|
||||
@@ -444,7 +447,7 @@ function data (state = defaultDataMap(), action) {
|
||||
return state
|
||||
case 'REMOVE_STORAGE':
|
||||
state = Object.assign({}, state)
|
||||
let storage = state.storageMap.get(action.storageKey)
|
||||
const storage = state.storageMap.get(action.storageKey)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.delete(action.storageKey)
|
||||
|
||||
@@ -452,17 +455,17 @@ function data (state = defaultDataMap(), action) {
|
||||
if (storage != null) {
|
||||
state.folderMap = new Map(state.folderMap)
|
||||
storage.folders.forEach((folder) => {
|
||||
let folderKey = storage.key + '-' + folder.key
|
||||
const folderKey = storage.key + '-' + folder.key
|
||||
state.folderMap.delete(folderKey)
|
||||
})
|
||||
}
|
||||
|
||||
// Remove notes from noteMap and tagNoteMap
|
||||
let storageNoteSet = state.storageNoteMap.get(action.storageKey)
|
||||
const storageNoteSet = state.storageNoteMap.get(action.storageKey)
|
||||
state.storageNoteMap = new Map(state.storageNoteMap)
|
||||
state.storageNoteMap.delete(action.storageKey)
|
||||
if (storageNoteSet != null) {
|
||||
let notes = storageNoteSet
|
||||
const notes = storageNoteSet
|
||||
.map((noteKey) => state.noteMap.get(noteKey))
|
||||
.filter((note) => note != null)
|
||||
|
||||
@@ -470,7 +473,7 @@ function data (state = defaultDataMap(), action) {
|
||||
state.tagNoteMap = new Map(state.tagNoteMap)
|
||||
state.starredSet = new Set(state.starredSet)
|
||||
notes.forEach((note) => {
|
||||
let noteKey = storage.key + '-' + note.key
|
||||
const noteKey = storage.key + '-' + note.key
|
||||
state.noteMap.delete(noteKey)
|
||||
state.starredSet.delete(noteKey)
|
||||
note.tags.forEach((tag) => {
|
||||
@@ -528,13 +531,13 @@ function status (state = defaultStatus, action) {
|
||||
return state
|
||||
}
|
||||
|
||||
let reducer = combineReducers({
|
||||
const reducer = combineReducers({
|
||||
data,
|
||||
config,
|
||||
status,
|
||||
routing: routerReducer
|
||||
})
|
||||
|
||||
let store = createStore(reducer)
|
||||
const store = createStore(reducer)
|
||||
|
||||
export default store
|
||||
|
||||
Reference in New Issue
Block a user