mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-26 16:11:45 +00:00
Merge branch 'master' into export-yfm
This commit is contained in:
@@ -24,23 +24,16 @@ body[data-theme="dark"]
|
||||
.empty-message
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
border-left 1px solid $ui-solarized-dark-borderColor
|
||||
.empty-message
|
||||
color $ui-solarized-dark-text-color
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||
.empty-message
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
border-left 1px solid $ui-monokai-borderColor
|
||||
.empty-message
|
||||
color $ui-monokai-text-color
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
.empty-message
|
||||
color $ui-dracula-text-color
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class FolderSelect extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -16,24 +16,27 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
componentDidUpdate() {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
handleClick (e) {
|
||||
this.setState({
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
}, () => {
|
||||
this.refs.search.focus()
|
||||
})
|
||||
handleClick(e) {
|
||||
this.setState(
|
||||
{
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
},
|
||||
() => {
|
||||
this.refs.search.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleFocus (e) {
|
||||
handleFocus(e) {
|
||||
if (this.state.status === 'IDLE') {
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
@@ -41,7 +44,7 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur (e) {
|
||||
handleBlur(e) {
|
||||
if (this.state.status === 'FOCUS') {
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
@@ -49,40 +52,49 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
handleKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 13:
|
||||
if (this.state.status === 'FOCUS') {
|
||||
this.setState({
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
}, () => {
|
||||
this.refs.search.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'SEARCH',
|
||||
optionIndex: -1
|
||||
},
|
||||
() => {
|
||||
this.refs.search.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
break
|
||||
case 40:
|
||||
case 38:
|
||||
if (this.state.status === 'FOCUS') {
|
||||
this.setState({
|
||||
status: 'SEARCH',
|
||||
optionIndex: 0
|
||||
}, () => {
|
||||
this.refs.search.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'SEARCH',
|
||||
optionIndex: 0
|
||||
},
|
||||
() => {
|
||||
this.refs.search.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
break
|
||||
case 9:
|
||||
if (e.shiftKey) {
|
||||
e.preventDefault()
|
||||
const tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
|
||||
const previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
||||
const tabbable = document.querySelectorAll(
|
||||
'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'
|
||||
)
|
||||
const previousEl =
|
||||
tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
||||
if (previousEl != null) previousEl.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchInputBlur (e) {
|
||||
handleSearchInputBlur(e) {
|
||||
if (e.relatedTarget !== this.refs.root) {
|
||||
this.setState({
|
||||
status: 'IDLE'
|
||||
@@ -90,14 +102,17 @@ class FolderSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchInputChange (e) {
|
||||
handleSearchInputChange(e) {
|
||||
const { folders } = this.props
|
||||
const search = this.refs.search.value
|
||||
const optionIndex = search.length > 0
|
||||
? _.findIndex(folders, (folder) => {
|
||||
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
|
||||
})
|
||||
: -1
|
||||
const optionIndex =
|
||||
search.length > 0
|
||||
? _.findIndex(folders, folder => {
|
||||
return folder.name.match(
|
||||
new RegExp('^' + _.escapeRegExp(search), 'i')
|
||||
)
|
||||
})
|
||||
: -1
|
||||
|
||||
this.setState({
|
||||
search: this.refs.search.value,
|
||||
@@ -105,7 +120,7 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleSearchInputKeyDown (e) {
|
||||
handleSearchInputKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 40:
|
||||
e.stopPropagation()
|
||||
@@ -121,15 +136,18 @@ class FolderSelect extends React.Component {
|
||||
break
|
||||
case 27:
|
||||
e.stopPropagation()
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
}, () => {
|
||||
this.refs.root.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'FOCUS'
|
||||
},
|
||||
() => {
|
||||
this.refs.root.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
nextOption () {
|
||||
nextOption() {
|
||||
let { optionIndex } = this.state
|
||||
const { folders } = this.props
|
||||
|
||||
@@ -141,7 +159,7 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
previousOption () {
|
||||
previousOption() {
|
||||
const { folders } = this.props
|
||||
let { optionIndex } = this.state
|
||||
|
||||
@@ -153,46 +171,52 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
selectOption () {
|
||||
selectOption() {
|
||||
const { folders } = this.props
|
||||
const optionIndex = this.state.optionIndex
|
||||
|
||||
const folder = folders[optionIndex]
|
||||
if (folder != null) {
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
}, () => {
|
||||
this.setValue(folder.key)
|
||||
this.refs.root.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'FOCUS'
|
||||
},
|
||||
() => {
|
||||
this.setValue(folder.key)
|
||||
this.refs.root.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
handleOptionClick (storageKey, folderKey) {
|
||||
return (e) => {
|
||||
handleOptionClick(storageKey, folderKey) {
|
||||
return e => {
|
||||
e.stopPropagation()
|
||||
this.setState({
|
||||
status: 'FOCUS'
|
||||
}, () => {
|
||||
this.setValue(storageKey + '-' + folderKey)
|
||||
this.refs.root.focus()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
status: 'FOCUS'
|
||||
},
|
||||
() => {
|
||||
this.setValue(storageKey + '-' + folderKey)
|
||||
this.refs.root.focus()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
setValue (value) {
|
||||
setValue(value) {
|
||||
this.value = value
|
||||
this.props.onChange()
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { className, data, value } = this.props
|
||||
const splitted = value.split('-')
|
||||
const storageKey = splitted.shift()
|
||||
const folderKey = splitted.shift()
|
||||
let options = []
|
||||
data.storageMap.forEach((storage, index) => {
|
||||
storage.folders.forEach((folder) => {
|
||||
storage.folders.forEach(folder => {
|
||||
options.push({
|
||||
storage: storage,
|
||||
folder: folder
|
||||
@@ -200,68 +224,78 @@ class FolderSelect extends React.Component {
|
||||
})
|
||||
})
|
||||
|
||||
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||
const currentOption = options.filter(
|
||||
option =>
|
||||
option.storage.key === storageKey && option.folder.key === folderKey
|
||||
)[0]
|
||||
|
||||
if (this.state.search.trim().length > 0) {
|
||||
const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
||||
options = options.filter((option) => filter.test(option.folder.name))
|
||||
options = options.filter(option => filter.test(option.folder.name))
|
||||
}
|
||||
|
||||
const optionList = options
|
||||
.map((option, index) => {
|
||||
return (
|
||||
<div styleName={index === this.state.optionIndex
|
||||
const optionList = options.map((option, index) => {
|
||||
return (
|
||||
<div
|
||||
styleName={
|
||||
index === this.state.optionIndex
|
||||
? 'search-optionList-item--active'
|
||||
: 'search-optionList-item'
|
||||
}
|
||||
key={option.storage.key + '-' + option.folder.key}
|
||||
onClick={(e) => this.handleOptionClick(option.storage.key, option.folder.key)(e)}
|
||||
}
|
||||
key={option.storage.key + '-' + option.folder.key}
|
||||
onClick={e =>
|
||||
this.handleOptionClick(option.storage.key, option.folder.key)(e)
|
||||
}
|
||||
>
|
||||
<span
|
||||
styleName='search-optionList-item-name'
|
||||
style={{ borderColor: option.folder.color }}
|
||||
>
|
||||
<span styleName='search-optionList-item-name'
|
||||
style={{borderColor: option.folder.color}}
|
||||
>
|
||||
{option.folder.name}
|
||||
<span styleName='search-optionList-item-name-surfix'>in {option.storage.name}</span>
|
||||
{option.folder.name}
|
||||
<span styleName='search-optionList-item-name-surfix'>
|
||||
in {option.storage.name}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={_.isString(className)
|
||||
? 'FolderSelect ' + className
|
||||
: 'FolderSelect'
|
||||
<div
|
||||
className={
|
||||
_.isString(className) ? 'FolderSelect ' + className : 'FolderSelect'
|
||||
}
|
||||
styleName={this.state.status === 'SEARCH'
|
||||
? 'root--search'
|
||||
: this.state.status === 'FOCUS'
|
||||
? 'root--focus'
|
||||
: 'root'
|
||||
styleName={
|
||||
this.state.status === 'SEARCH'
|
||||
? 'root--search'
|
||||
: this.state.status === 'FOCUS'
|
||||
? 'root--focus'
|
||||
: 'root'
|
||||
}
|
||||
ref='root'
|
||||
tabIndex='0'
|
||||
onClick={(e) => this.handleClick(e)}
|
||||
onFocus={(e) => this.handleFocus(e)}
|
||||
onBlur={(e) => this.handleBlur(e)}
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onClick={e => this.handleClick(e)}
|
||||
onFocus={e => this.handleFocus(e)}
|
||||
onBlur={e => this.handleBlur(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
{this.state.status === 'SEARCH'
|
||||
? <div styleName='search'>
|
||||
<input styleName='search-input'
|
||||
{this.state.status === 'SEARCH' ? (
|
||||
<div styleName='search'>
|
||||
<input
|
||||
styleName='search-input'
|
||||
ref='search'
|
||||
value={this.state.search}
|
||||
placeholder={i18n.__('Folder...')}
|
||||
onChange={(e) => this.handleSearchInputChange(e)}
|
||||
onBlur={(e) => this.handleSearchInputBlur(e)}
|
||||
onKeyDown={(e) => this.handleSearchInputKeyDown(e)}
|
||||
onChange={e => this.handleSearchInputChange(e)}
|
||||
onBlur={e => this.handleSearchInputBlur(e)}
|
||||
onKeyDown={e => this.handleSearchInputKeyDown(e)}
|
||||
/>
|
||||
<div styleName='search-optionList'
|
||||
ref='optionList'
|
||||
>
|
||||
<div styleName='search-optionList' ref='optionList'>
|
||||
{optionList}
|
||||
</div>
|
||||
</div>
|
||||
: <div styleName='idle' style={{color: currentOption.folder.color}}>
|
||||
) : currentOption ? (
|
||||
<div styleName='idle' style={{ color: currentOption.folder.color }}>
|
||||
<div styleName='idle-label'>
|
||||
<i className='fa fa-folder' />
|
||||
<span styleName='idle-label-name'>
|
||||
@@ -269,8 +303,7 @@ class FolderSelect extends React.Component {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -280,11 +313,13 @@ FolderSelect.propTypes = {
|
||||
className: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
folders: PropTypes.arrayOf(PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
color: PropTypes.string
|
||||
}))
|
||||
folders: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
color: PropTypes.string
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export default CSSModules(FolderSelect, styles)
|
||||
|
||||
@@ -134,54 +134,39 @@ body[data-theme="dark"]
|
||||
.search-optionList-item-name-surfix
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
color $ui-dark-text-color
|
||||
&:hover
|
||||
color white
|
||||
background-color $ui-monokai-button--hover-backgroundColor
|
||||
border-color $ui-monokai-borderColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
&:hover
|
||||
background-color get-theme-var(theme, 'button--hover-backgroundColor')
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
|
||||
.search-optionList
|
||||
color white
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-button-backgroundColor
|
||||
.search-input
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color transparent
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
|
||||
.search-optionList-item
|
||||
&:hover
|
||||
background-color lighten($ui-monokai-button--hover-backgroundColor, 15%)
|
||||
.search-optionList
|
||||
color get-theme-var(theme, 'text-color')
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||
|
||||
.search-optionList-item--active
|
||||
background-color $ui-monokai-button--active-backgroundColor
|
||||
color $ui-monokai-button--active-color
|
||||
&:hover
|
||||
background-color $ui-monokai-button--active-backgroundColor
|
||||
color $ui-monokai-button--active-color
|
||||
.search-optionList-item-name-surfix
|
||||
color $ui-monokai-inactive-text-color
|
||||
.search-optionList-item
|
||||
&:hover
|
||||
background-color lighten(get-theme-var(theme, 'button--hover-backgroundColor'), 15%)
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
color $ui-dracula-text-color
|
||||
&:hover
|
||||
color #f8f8f2
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
border-color $ui-dracula-borderColor
|
||||
.search-optionList-item--active
|
||||
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||
color get-theme-var(theme, 'button--active-color')
|
||||
&:hover
|
||||
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||
color get-theme-var(theme, 'button--active-color')
|
||||
|
||||
.search-optionList
|
||||
color #f8f8f2
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-button-backgroundColor
|
||||
.search-optionList-item-name-surfix
|
||||
color get-theme-var(theme, 'inactive-text-color')
|
||||
|
||||
.search-optionList-item
|
||||
&:hover
|
||||
background-color lighten($ui-dracula-button--hover-backgroundColor, 15%)
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.search-optionList-item--active
|
||||
background-color $ui-dracula-button--active-backgroundColor
|
||||
color $ui-dracula-button--active-color
|
||||
&:hover
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
color $ui-dracula-button--active-color
|
||||
.search-optionList-item-name-surfix
|
||||
color $ui-dracula-inactive-text-color
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
71
browser/main/Detail/FromUrlButton.js
Normal file
71
browser/main/Detail/FromUrlButton.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './FromUrlButton.styl'
|
||||
import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class FromUrlButton extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isActive: false
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDown(e) {
|
||||
this.setState({
|
||||
isActive: true
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseUp(e) {
|
||||
this.setState({
|
||||
isActive: false
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseLeave(e) {
|
||||
this.setState({
|
||||
isActive: false
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className } = this.props
|
||||
|
||||
return (
|
||||
<button
|
||||
className={
|
||||
_.isString(className) ? 'FromUrlButton ' + className : 'FromUrlButton'
|
||||
}
|
||||
styleName={
|
||||
this.state.isActive || this.props.isActive ? 'root--active' : 'root'
|
||||
}
|
||||
onMouseDown={e => this.handleMouseDown(e)}
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
onMouseLeave={e => this.handleMouseLeave(e)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<img
|
||||
styleName='icon'
|
||||
src={
|
||||
this.state.isActive || this.props.isActive
|
||||
? '../resources/icon/icon-external.svg'
|
||||
: '../resources/icon/icon-external.svg'
|
||||
}
|
||||
/>
|
||||
<span styleName='tooltip'>{i18n.__('Convert URL to Markdown')}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FromUrlButton.propTypes = {
|
||||
isActive: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
export default CSSModules(FromUrlButton, styles)
|
||||
41
browser/main/Detail/FromUrlButton.styl
Normal file
41
browser/main/Detail/FromUrlButton.styl
Normal file
@@ -0,0 +1,41 @@
|
||||
.root
|
||||
top 45px
|
||||
topBarButtonRight()
|
||||
&:hover
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 50px
|
||||
right 125px
|
||||
width 90px
|
||||
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.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
|
||||
.icon
|
||||
transition transform 0.15s
|
||||
height 13px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
topBarButtonDark()
|
||||
&:hover
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
@@ -5,15 +5,21 @@ import styles from './FullscreenButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||
const FullscreenButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
|
||||
</button>
|
||||
)
|
||||
const FullscreenButton = ({ onClick }) => {
|
||||
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||
return (
|
||||
<button
|
||||
styleName='control-fullScreenButton'
|
||||
title={i18n.__('Fullscreen')}
|
||||
onMouseDown={e => onClick(e)}
|
||||
>
|
||||
<img src='../resources/icon/icon-full.svg' />
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Fullscreen')}({hotkey})
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
FullscreenButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
.control-fullScreenButton
|
||||
top 80px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 50px
|
||||
right 70px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
.control-fullScreenButton
|
||||
top 80px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 50px
|
||||
right 70px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.tooltip:lang(ja)
|
||||
@extend .tooltip
|
||||
right 35px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const InfoButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-infoButton'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
const InfoButton = ({ onClick }) => (
|
||||
<button styleName='control-infoButton' onClick={e => onClick(e)}>
|
||||
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Info')}</span>
|
||||
</button>
|
||||
|
||||
@@ -6,28 +6,47 @@ import copy from 'copy-to-clipboard'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class InfoPanel extends React.Component {
|
||||
copyNoteLink () {
|
||||
const {noteLink} = this.props
|
||||
copyNoteLink() {
|
||||
const { noteLink } = this.props
|
||||
this.refs.noteLink.select()
|
||||
copy(noteLink)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const {
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
|
||||
storageName,
|
||||
folderName,
|
||||
noteLink,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
exportAsMd,
|
||||
exportAsTxt,
|
||||
exportAsHtml,
|
||||
exportAsPdf,
|
||||
wordCount,
|
||||
letterCount,
|
||||
type,
|
||||
print
|
||||
} = this.props
|
||||
return (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||
<div
|
||||
className='infoPanel'
|
||||
styleName='control-infoButton-panel'
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<div>
|
||||
<p styleName='modification-date'>{updatedAt}</p>
|
||||
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
||||
<p styleName='modification-date-desc'>
|
||||
{i18n.__('MODIFICATION DATE')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <div styleName='count-wrap'>
|
||||
{type === 'SNIPPET_NOTE' ? (
|
||||
''
|
||||
) : (
|
||||
<div styleName='count-wrap'>
|
||||
<div styleName='count-number'>
|
||||
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
||||
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p>
|
||||
@@ -37,12 +56,9 @@ class InfoPanel extends React.Component {
|
||||
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <hr />
|
||||
}
|
||||
{type === 'SNIPPET_NOTE' ? '' : <hr />}
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{storageName}</p>
|
||||
@@ -60,8 +76,18 @@ class InfoPanel extends React.Component {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input styleName='infoPanel-noteLink' ref='noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
|
||||
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
|
||||
<input
|
||||
styleName='infoPanel-noteLink'
|
||||
ref='noteLink'
|
||||
defaultValue={noteLink}
|
||||
onClick={e => {
|
||||
e.target.select()
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
onClick={() => this.copyNoteLink()}
|
||||
styleName='infoPanel-copyButton'
|
||||
>
|
||||
<i className='fa fa-clipboard' />
|
||||
</button>
|
||||
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
|
||||
@@ -70,22 +96,39 @@ class InfoPanel extends React.Component {
|
||||
<hr />
|
||||
|
||||
<div id='export-wrap'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsMd(e, 'export-md')}
|
||||
>
|
||||
<i className='fa fa-file-code-o' />
|
||||
<p>{i18n.__('.md')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsTxt(e, 'export-txt')}
|
||||
>
|
||||
<i className='fa fa-file-text-o' />
|
||||
<p>{i18n.__('.txt')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsHtml(e, 'export-html')}
|
||||
>
|
||||
<i className='fa fa-html5' />
|
||||
<p>{i18n.__('.html')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => print(e, 'print')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||
>
|
||||
<i className='fa fa-file-pdf-o' />
|
||||
<p>{i18n.__('.pdf')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={e => print(e, 'print')}>
|
||||
<i className='fa fa-print' />
|
||||
<p>{i18n.__('Print')}</p>
|
||||
</button>
|
||||
@@ -104,6 +147,7 @@ InfoPanel.propTypes = {
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
exportAsHtml: PropTypes.func.isRequired,
|
||||
exportAsPdf: PropTypes.func.isRequired,
|
||||
wordCount: PropTypes.number,
|
||||
letterCount: PropTypes.number,
|
||||
type: PropTypes.string.isRequired,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
right 25px
|
||||
position absolute
|
||||
padding 20px 25px 0 25px
|
||||
width 300px
|
||||
// width 300px
|
||||
overflow auto
|
||||
background-color $ui-noteList-backgroundColor
|
||||
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||
@@ -138,162 +138,49 @@
|
||||
.export--unable
|
||||
cursor not-allowed
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-infoButton-panel
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.control-infoButton-panel
|
||||
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
.control-infoButton-panel-trash
|
||||
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||
|
||||
.modification-date
|
||||
color $ui-dark-text-color
|
||||
.modification-date
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-defaul-count
|
||||
color $ui-dark-text-color
|
||||
.infoPanel-defaul-count
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-default
|
||||
color $ui-dark-text-color
|
||||
.infoPanel-default
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-noteLink
|
||||
background-color alpha($ui-dark-borderColor, 60%)
|
||||
color $ui-dark-text-color
|
||||
.infoPanel-noteLink
|
||||
background-color alpha(get-theme-var(theme, 'borderColor'), 20%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
[id=export-wrap]
|
||||
button
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-dark-borderColor, 20%)
|
||||
color $ui-dark-text-color
|
||||
p
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-dark-text-color
|
||||
[id=export-wrap]
|
||||
button
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color alpha(get-theme-var(theme, 'borderColor'), 20%)
|
||||
color get-theme-var(theme, 'text-color')
|
||||
p
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.control-infoButton-panel
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
for theme in 'dark' 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-solarized-ark-noteList-backgroundColor
|
||||
|
||||
.modification-date
|
||||
color $ui-solarized-ark-text-color
|
||||
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-defaul-count
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-default
|
||||
color $ui-solarized-ark-text-color
|
||||
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-noteLink
|
||||
background-color alpha($ui-solarized-dark-borderColor, 20%)
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
[id=export-wrap]
|
||||
button
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-solarized-dark-borderColor, 20%)
|
||||
color $ui-solarized-ark-text-color
|
||||
p
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-solarized-ark-text-color
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.control-infoButton-panel
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-monokai-noteList-backgroundColor
|
||||
|
||||
.modification-date
|
||||
color $ui-monokai-text-color
|
||||
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-defaul-count
|
||||
color $ui-monokai-text-color
|
||||
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-default
|
||||
color $ui-monokai-text-color
|
||||
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-noteLink
|
||||
background-color alpha($ui-monokai-borderColor, 20%)
|
||||
color $ui-monokai-text-color
|
||||
|
||||
[id=export-wrap]
|
||||
button
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-monokai-borderColor, 20%)
|
||||
color $ui-monokai-text-color
|
||||
p
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-monokai-text-color
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.control-infoButton-panel
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-dracula-noteList-backgroundColor
|
||||
|
||||
.modification-date
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-defaul-count
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-default
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-noteLink
|
||||
background-color alpha($ui-dracula-borderColor, 20%)
|
||||
color $ui-dracula-text-color
|
||||
|
||||
[id=export-wrap]
|
||||
button
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-dracula-borderColor, 20%)
|
||||
color $ui-dracula-text-color
|
||||
p
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-dracula-text-color
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -5,9 +5,20 @@ import styles from './InfoPanel.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const InfoPanelTrashed = ({
|
||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
|
||||
storageName,
|
||||
folderName,
|
||||
updatedAt,
|
||||
createdAt,
|
||||
exportAsMd,
|
||||
exportAsTxt,
|
||||
exportAsHtml,
|
||||
exportAsPdf
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||
<div
|
||||
className='infoPanel'
|
||||
styleName='control-infoButton-panel-trash'
|
||||
style={{ display: 'none' }}
|
||||
>
|
||||
<div>
|
||||
<p styleName='modification-date'>{updatedAt}</p>
|
||||
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
||||
@@ -21,7 +32,10 @@ const InfoPanelTrashed = ({
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
|
||||
<p styleName='infoPanel-default'>
|
||||
<text styleName='infoPanel-trash'>Trash</text>
|
||||
{folderName}
|
||||
</p>
|
||||
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
|
||||
</div>
|
||||
|
||||
@@ -31,22 +45,34 @@ const InfoPanelTrashed = ({
|
||||
</div>
|
||||
|
||||
<div id='export-wrap'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsMd(e, 'export-md')}
|
||||
>
|
||||
<i className='fa fa-file-code-o' />
|
||||
<p>.md</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsTxt(e, 'export-txt')}
|
||||
>
|
||||
<i className='fa fa-file-text-o' />
|
||||
<p>.txt</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsHtml(e, 'export-html')}
|
||||
>
|
||||
<i className='fa fa-html5' />
|
||||
<p>.html</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--unable'>
|
||||
<button
|
||||
styleName='export--enable'
|
||||
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||
>
|
||||
<i className='fa fa-file-pdf-o' />
|
||||
<p>.pdf</p>
|
||||
</button>
|
||||
@@ -61,7 +87,8 @@ InfoPanelTrashed.propTypes = {
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
exportAsHtml: PropTypes.func.isRequired
|
||||
exportAsHtml: PropTypes.func.isRequired,
|
||||
exportAsPdf: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(InfoPanelTrashed, styles)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable camelcase */
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
@@ -9,7 +10,6 @@ import StarButton from './StarButton'
|
||||
import TagSelect from './TagSelect'
|
||||
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/markdownTextHelper'
|
||||
import StatusBar from '../StatusBar'
|
||||
@@ -30,109 +30,147 @@ import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||
import striptags from 'striptags'
|
||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||
import queryString from 'query-string'
|
||||
import { replace } from 'connected-react-router'
|
||||
import ToggleDirectionButton from 'browser/main/Detail/ToggleDirectionButton'
|
||||
|
||||
class MarkdownNoteDetail extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isMovingNote: false,
|
||||
note: Object.assign({
|
||||
title: '',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
}, props.note),
|
||||
isLockButtonShown: false,
|
||||
note: Object.assign(
|
||||
{
|
||||
title: '',
|
||||
content: '',
|
||||
linesHighlighted: []
|
||||
},
|
||||
props.note
|
||||
),
|
||||
isLockButtonShown: props.config.editor.type !== 'SPLIT',
|
||||
isLocked: false,
|
||||
editorType: props.config.editor.type
|
||||
editorType: props.config.editor.type,
|
||||
switchPreview: props.config.editor.switchPreview,
|
||||
RTL: false
|
||||
}
|
||||
|
||||
this.dispatchTimer = null
|
||||
|
||||
this.generateToc = this.handleGenerateToc.bind(this)
|
||||
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||
this.generateToc = () => this.handleGenerateToc()
|
||||
|
||||
this.handleUpdateContent = this.handleUpdateContent.bind(this)
|
||||
this.handleSwitchStackDirection = this.handleSwitchStackDirection.bind(this)
|
||||
this.getNote = this.getNote.bind(this)
|
||||
}
|
||||
|
||||
focus () {
|
||||
focus() {
|
||||
this.refs.content.focus()
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
ee.on('editor:orientation', this.handleSwitchStackDirection)
|
||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
|
||||
ee.on('topbar:togglemodebutton', () => {
|
||||
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||
const reversedType =
|
||||
this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||
this.handleSwitchMode(reversedType)
|
||||
})
|
||||
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
|
||||
ee.on('code:generate-toc', this.generateToc)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
const isNewNote = nextProps.note.key !== this.props.note.key
|
||||
const hasDeletedTags = nextProps.note.tags.length < this.props.note.tags.length
|
||||
const hasDeletedTags =
|
||||
nextProps.note.tags.length < this.props.note.tags.length
|
||||
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
this.setState(
|
||||
{
|
||||
note: Object.assign({ linesHighlighted: [] }, nextProps.note)
|
||||
},
|
||||
() => {
|
||||
this.refs.content.reload()
|
||||
if (this.refs.tags) this.refs.tags.reset()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Focus content if using blur or double click
|
||||
// --> Moved here from componentDidMount so a re-render during search won't set focus to the editor
|
||||
const { switchPreview } = nextProps.config.editor
|
||||
|
||||
if (this.state.switchPreview !== switchPreview) {
|
||||
this.setState({
|
||||
note: Object.assign({linesHighlighted: []}, nextProps.note)
|
||||
}, () => {
|
||||
this.refs.content.reload()
|
||||
if (this.refs.tags) this.refs.tags.reset()
|
||||
switchPreview
|
||||
})
|
||||
if (switchPreview === 'BLUR' || switchPreview === 'DBL_CLICK') {
|
||||
console.log('setting focus', switchPreview)
|
||||
this.focus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||
ee.on('topbar:toggledirectionbutton', this.handleSwitchDirection)
|
||||
ee.off('code:generate-toc', this.generateToc)
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
}
|
||||
|
||||
handleUpdateTag () {
|
||||
handleUpdateTag() {
|
||||
const { note } = this.state
|
||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
handleUpdateContent () {
|
||||
handleUpdateContent() {
|
||||
const { note } = this.state
|
||||
note.content = this.refs.content.value
|
||||
note.title = markdown.strip(striptags(findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)))
|
||||
|
||||
let title = findNoteTitle(
|
||||
note.content,
|
||||
this.props.config.editor.enableFrontMatterTitle,
|
||||
this.props.config.editor.frontMatterTitleField
|
||||
)
|
||||
title = striptags(title)
|
||||
title = markdown.strip(title)
|
||||
note.title = title
|
||||
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
updateNote (note) {
|
||||
updateNote(note) {
|
||||
note.updatedAt = new Date()
|
||||
this.setState({note}, () => {
|
||||
this.setState({ note }, () => {
|
||||
this.save()
|
||||
})
|
||||
}
|
||||
|
||||
save () {
|
||||
save() {
|
||||
clearTimeout(this.saveQueue)
|
||||
this.saveQueue = setTimeout(() => {
|
||||
this.saveNow()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
saveNow () {
|
||||
saveNow() {
|
||||
const { note, dispatch } = this.props
|
||||
clearTimeout(this.saveQueue)
|
||||
this.saveQueue = null
|
||||
|
||||
dataApi
|
||||
.updateNote(note.storage, note.key, this.state.note)
|
||||
.then((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||
dataApi.updateNote(note.storage, note.key, this.state.note).then(note => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||
})
|
||||
}
|
||||
|
||||
handleFolderChange (e) {
|
||||
handleFolderChange(e) {
|
||||
const { note } = this.state
|
||||
const value = this.refs.folder.value
|
||||
const splitted = value.split('-')
|
||||
@@ -141,60 +179,71 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
dataApi
|
||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||
.then((newNote) => {
|
||||
this.setState({
|
||||
isMovingNote: true,
|
||||
note: Object.assign({}, newNote)
|
||||
}, () => {
|
||||
const { dispatch, location } = this.props
|
||||
dispatch({
|
||||
type: 'MOVE_NOTE',
|
||||
originNote: note,
|
||||
note: newNote
|
||||
})
|
||||
hashHistory.replace({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
key: newNote.key
|
||||
}
|
||||
})
|
||||
this.setState({
|
||||
isMovingNote: false
|
||||
})
|
||||
})
|
||||
.then(newNote => {
|
||||
this.setState(
|
||||
{
|
||||
isMovingNote: true,
|
||||
note: Object.assign({}, newNote)
|
||||
},
|
||||
() => {
|
||||
const { dispatch, location } = this.props
|
||||
dispatch({
|
||||
type: 'MOVE_NOTE',
|
||||
originNote: note,
|
||||
note: newNote
|
||||
})
|
||||
dispatch(
|
||||
replace({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({
|
||||
key: newNote.key
|
||||
})
|
||||
})
|
||||
)
|
||||
this.setState({
|
||||
isMovingNote: false
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
handleStarButtonClick (e) {
|
||||
handleStarButtonClick(e) {
|
||||
const { note } = this.state
|
||||
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||
if (!note.isStarred)
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||
|
||||
note.isStarred = !note.isStarred
|
||||
|
||||
this.setState({
|
||||
note
|
||||
}, () => {
|
||||
this.save()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note
|
||||
},
|
||||
() => {
|
||||
this.save()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
exportAsFile () {
|
||||
exportAsFile() {}
|
||||
|
||||
}
|
||||
|
||||
exportAsMd () {
|
||||
exportAsMd() {
|
||||
ee.emit('export:save-md')
|
||||
}
|
||||
|
||||
exportAsTxt () {
|
||||
exportAsTxt() {
|
||||
ee.emit('export:save-text')
|
||||
}
|
||||
|
||||
exportAsHtml () {
|
||||
exportAsHtml() {
|
||||
ee.emit('export:save-html')
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
exportAsPdf() {
|
||||
ee.emit('export:save-pdf')
|
||||
}
|
||||
|
||||
handleKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
// tab key
|
||||
case 9:
|
||||
@@ -204,7 +253,11 @@ class MarkdownNoteDetail extends React.Component {
|
||||
} else if (e.ctrlKey && e.shiftKey) {
|
||||
e.preventDefault()
|
||||
this.jumpPrevTab()
|
||||
} else if (!e.ctrlKey && !e.shiftKey && e.target === this.refs.description) {
|
||||
} else if (
|
||||
!e.ctrlKey &&
|
||||
!e.shiftKey &&
|
||||
e.target === this.refs.description
|
||||
) {
|
||||
e.preventDefault()
|
||||
this.focusEditor()
|
||||
}
|
||||
@@ -212,9 +265,8 @@ class MarkdownNoteDetail extends React.Component {
|
||||
// I key
|
||||
case 73:
|
||||
{
|
||||
const isSuper = global.process.platform === 'darwin'
|
||||
? e.metaKey
|
||||
: e.ctrlKey
|
||||
const isSuper =
|
||||
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey
|
||||
if (isSuper) {
|
||||
e.preventDefault()
|
||||
this.handleInfoButtonClick(e)
|
||||
@@ -224,17 +276,17 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleTrashButtonClick (e) {
|
||||
handleTrashButtonClick(e) {
|
||||
const { note } = this.state
|
||||
const { isTrashed } = note
|
||||
const { confirmDeletion } = this.props.config.ui
|
||||
|
||||
if (isTrashed) {
|
||||
if (confirmDeleteNote(confirmDeletion, true)) {
|
||||
const {note, dispatch} = this.props
|
||||
const { note, dispatch } = this.props
|
||||
dataApi
|
||||
.deleteNote(note.storage, note.key)
|
||||
.then((data) => {
|
||||
.then(data => {
|
||||
const dispatchHandler = () => {
|
||||
dispatch({
|
||||
type: 'DELETE_NOTE',
|
||||
@@ -250,102 +302,139 @@ class MarkdownNoteDetail extends React.Component {
|
||||
if (confirmDeleteNote(confirmDeletion, false)) {
|
||||
note.isTrashed = true
|
||||
|
||||
this.setState({
|
||||
note
|
||||
}, () => {
|
||||
this.save()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note
|
||||
},
|
||||
() => {
|
||||
this.save()
|
||||
}
|
||||
)
|
||||
|
||||
ee.emit('list:next')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleUndoButtonClick (e) {
|
||||
handleUndoButtonClick(e) {
|
||||
const { note } = this.state
|
||||
|
||||
note.isTrashed = false
|
||||
|
||||
this.setState({
|
||||
note
|
||||
}, () => {
|
||||
this.save()
|
||||
this.refs.content.reload()
|
||||
ee.emit('list:next')
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
note
|
||||
},
|
||||
() => {
|
||||
this.save()
|
||||
this.refs.content.reload()
|
||||
ee.emit('list:next')
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleFullScreenButton (e) {
|
||||
handleFullScreenButton(e) {
|
||||
ee.emit('editor:fullscreen')
|
||||
}
|
||||
|
||||
handleLockButtonMouseDown (e) {
|
||||
handleLockButtonMouseDown(e) {
|
||||
e.preventDefault()
|
||||
ee.emit('editor:lock')
|
||||
this.setState({ isLocked: !this.state.isLocked })
|
||||
if (this.state.isLocked) this.focus()
|
||||
}
|
||||
|
||||
getToggleLockButton () {
|
||||
return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
|
||||
getToggleLockButton() {
|
||||
return this.state.isLocked
|
||||
? '../resources/icon/icon-lock.svg'
|
||||
: '../resources/icon/icon-unlock.svg'
|
||||
}
|
||||
|
||||
handleDeleteKeyDown (e) {
|
||||
handleDeleteKeyDown(e) {
|
||||
if (e.keyCode === 27) this.handleDeleteCancelButtonClick(e)
|
||||
}
|
||||
|
||||
handleToggleLockButton (event, noteStatus) {
|
||||
handleToggleLockButton(event, noteStatus) {
|
||||
// first argument event is not used
|
||||
if (this.props.config.editor.switchPreview === 'BLUR' && noteStatus === 'CODE') {
|
||||
this.setState({isLockButtonShown: true})
|
||||
if (noteStatus === 'CODE') {
|
||||
this.setState({ isLockButtonShown: true })
|
||||
} else {
|
||||
this.setState({isLockButtonShown: false})
|
||||
this.setState({ isLockButtonShown: false })
|
||||
}
|
||||
}
|
||||
|
||||
handleGenerateToc () {
|
||||
handleGenerateToc() {
|
||||
const editor = this.refs.content.refs.code.editor
|
||||
markdownToc.generateInEditor(editor)
|
||||
}
|
||||
|
||||
handleFocus (e) {
|
||||
handleFocus(e) {
|
||||
this.focus()
|
||||
}
|
||||
|
||||
handleInfoButtonClick (e) {
|
||||
handleInfoButtonClick(e) {
|
||||
const infoPanel = document.querySelector('.infoPanel')
|
||||
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||
if (infoPanel.style)
|
||||
infoPanel.style.display =
|
||||
infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||
}
|
||||
|
||||
print (e) {
|
||||
print(e) {
|
||||
ee.emit('print')
|
||||
}
|
||||
|
||||
handleSwitchMode (type) {
|
||||
this.setState({ editorType: type }, () => {
|
||||
this.focus()
|
||||
const newConfig = Object.assign({}, this.props.config)
|
||||
newConfig.editor.type = type
|
||||
ConfigManager.set(newConfig)
|
||||
})
|
||||
handleSwitchMode(type) {
|
||||
// If in split mode, hide the lock button
|
||||
this.setState(
|
||||
{ editorType: type, isLockButtonShown: type !== 'SPLIT' },
|
||||
() => {
|
||||
this.focus()
|
||||
const newConfig = Object.assign({}, this.props.config)
|
||||
newConfig.editor.type = type
|
||||
ConfigManager.set(newConfig)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleDeleteNote () {
|
||||
handleSwitchStackDirection() {
|
||||
this.setState(
|
||||
prevState => ({ isStacking: !prevState.isStacking }),
|
||||
() => {
|
||||
this.focus()
|
||||
const newConfig = Object.assign({}, this.props.config)
|
||||
newConfig.ui.isStacking = this.state.isStacking
|
||||
ConfigManager.set(newConfig)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
handleSwitchDirection() {
|
||||
if (!this.props.config.editor.rtlEnabled) {
|
||||
return
|
||||
}
|
||||
// If in split mode, hide the lock button
|
||||
const direction = this.state.RTL
|
||||
this.setState({ RTL: !direction })
|
||||
}
|
||||
|
||||
handleDeleteNote() {
|
||||
this.handleTrashButtonClick()
|
||||
}
|
||||
|
||||
handleClearTodo () {
|
||||
handleClearTodo() {
|
||||
const { note } = this.state
|
||||
const splitted = note.content.split('\n')
|
||||
|
||||
const clearTodoContent = splitted.map((line) => {
|
||||
const trimmedLine = line.trim()
|
||||
if (trimmedLine.match(/\[x\]/i)) {
|
||||
return line.replace(/\[x\]/i, '[ ]')
|
||||
} else {
|
||||
return line
|
||||
}
|
||||
}).join('\n')
|
||||
const clearTodoContent = splitted
|
||||
.map(line => {
|
||||
const trimmedLine = line.trim()
|
||||
if (trimmedLine.match(/\[x\]/i)) {
|
||||
return line.replace(/\[x\]/i, '[ ]')
|
||||
} else {
|
||||
return line
|
||||
}
|
||||
})
|
||||
.join('\n')
|
||||
|
||||
note.content = clearTodoContent
|
||||
this.refs.content.setValue(note.content)
|
||||
@@ -353,161 +442,199 @@ class MarkdownNoteDetail extends React.Component {
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
getNote () {
|
||||
getNote() {
|
||||
return this.state.note
|
||||
}
|
||||
|
||||
renderEditor () {
|
||||
renderEditor() {
|
||||
const { config, ignorePreviewPointerEvents } = this.props
|
||||
const { note } = this.state
|
||||
|
||||
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
||||
return <MarkdownEditor
|
||||
ref='content'
|
||||
styleName='body-noteEditor'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
getNote={this.getNote}
|
||||
/>
|
||||
return (
|
||||
<MarkdownEditor
|
||||
ref='content'
|
||||
styleName='body-noteEditor'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
getNote={this.getNote}
|
||||
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return <MarkdownSplitEditor
|
||||
ref='content'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
getNote={this.getNote}
|
||||
/>
|
||||
return (
|
||||
<MarkdownSplitEditor
|
||||
ref='content'
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
noteKey={note.key}
|
||||
linesHighlighted={note.linesHighlighted}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
getNote={this.getNote}
|
||||
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, location, config } = this.props
|
||||
render() {
|
||||
const { data, dispatch, location, config } = 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) => {
|
||||
storage.folders.forEach(folder => {
|
||||
options.push({
|
||||
storage: storage,
|
||||
folder: folder
|
||||
})
|
||||
})
|
||||
})
|
||||
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||
|
||||
const trashTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
<InfoPanelTrashed
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
const currentOption = _.find(
|
||||
options,
|
||||
option =>
|
||||
option.storage.key === storageKey && option.folder.key === folderKey
|
||||
)
|
||||
|
||||
const detailTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<div styleName='info-left-top'>
|
||||
<FolderSelect styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
ref='folder'
|
||||
data={data}
|
||||
onChange={(e) => this.handleFolderChange(e)}
|
||||
// currentOption may be undefined
|
||||
const storageName = _.get(currentOption, 'storage.name') || ''
|
||||
const folderName = _.get(currentOption, 'folder.name') || ''
|
||||
|
||||
const trashTopBar = (
|
||||
<div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<RestoreButton onClick={e => this.handleUndoButtonClick(e)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<PermanentDeleteButton
|
||||
onClick={e => this.handleTrashButtonClick(e)}
|
||||
/>
|
||||
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||
<InfoPanelTrashed
|
||||
storageName={storageName}
|
||||
folderName={folderName}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<TagSelect
|
||||
ref='tags'
|
||||
value={this.state.note.tags}
|
||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
data={data}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
/>
|
||||
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||
<StarButton
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
)
|
||||
|
||||
{(() => {
|
||||
const imgSrc = `${this.getToggleLockButton()}`
|
||||
const lockButtonComponent =
|
||||
<button styleName='control-lockButton'
|
||||
onFocus={(e) => this.handleFocus(e)}
|
||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src={imgSrc} />
|
||||
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
|
||||
</button>
|
||||
const detailTopBar = (
|
||||
<div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<div>
|
||||
<FolderSelect
|
||||
styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
ref='folder'
|
||||
data={data}
|
||||
onChange={e => this.handleFolderChange(e)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
return (
|
||||
this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||
)
|
||||
})()}
|
||||
<TagSelect
|
||||
ref='tags'
|
||||
value={this.state.note.tags}
|
||||
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||
data={data}
|
||||
dispatch={dispatch}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
coloredTags={config.coloredTags}
|
||||
/>
|
||||
<TodoListPercentage
|
||||
onClearCheckboxClick={e => this.handleClearTodo(e)}
|
||||
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
||||
/>
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<ToggleModeButton
|
||||
onClick={e => this.handleSwitchMode(e)}
|
||||
editorType={editorType}
|
||||
/>
|
||||
{this.props.config.editor.rtlEnabled && (
|
||||
<ToggleDirectionButton
|
||||
onClick={e => this.handleSwitchDirection(e)}
|
||||
isRTL={this.state.RTL}
|
||||
/>
|
||||
)}
|
||||
<StarButton
|
||||
onClick={e => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
|
||||
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
|
||||
{(() => {
|
||||
const imgSrc = `${this.getToggleLockButton()}`
|
||||
const lockButtonComponent = (
|
||||
<button
|
||||
styleName='control-lockButton'
|
||||
onFocus={e => this.handleFocus(e)}
|
||||
onMouseDown={e => this.handleLockButtonMouseDown(e)}
|
||||
>
|
||||
<img src={imgSrc} />
|
||||
{this.state.isLocked ? (
|
||||
<span styleName='tooltip'>Unlock</span>
|
||||
) : (
|
||||
<span styleName='tooltip'>Lock</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
return this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||
})()}
|
||||
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
<FullscreenButton onClick={e => this.handleFullScreenButton(e)} />
|
||||
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
noteLink={`[${note.title}](:note:${location.query.key})`}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
wordCount={note.content.split(' ').length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
print={this.print}
|
||||
/>
|
||||
<TrashButton onClick={e => this.handleTrashButtonClick(e)} />
|
||||
|
||||
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||
|
||||
<InfoPanel
|
||||
storageName={storageName}
|
||||
folderName={folderName}
|
||||
noteLink={`[${note.title}](:note:${
|
||||
queryString.parse(location.search).key
|
||||
})`}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
wordCount={note.content.trim().split(/\s+/g).length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
print={this.print}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='NoteDetail'
|
||||
<div
|
||||
className='NoteDetail'
|
||||
style={this.props.style}
|
||||
styleName='root'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyDown={e => this.handleKeyDown(e)}
|
||||
>
|
||||
|
||||
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||
|
||||
<div styleName='body'>
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
<div styleName='body'>{this.renderEditor()}</div>
|
||||
|
||||
<StatusBar
|
||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||
@@ -521,9 +648,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
MarkdownNoteDetail.propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
repositories: PropTypes.array,
|
||||
note: PropTypes.shape({
|
||||
|
||||
}),
|
||||
note: PropTypes.shape({}),
|
||||
style: PropTypes.shape({
|
||||
left: PropTypes.number
|
||||
}),
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
.control-lockButton
|
||||
topBarButtonRight()
|
||||
position absolute
|
||||
right 225px
|
||||
right 265px
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
@@ -66,18 +66,14 @@ body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
border-left 1px solid $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
border-left 1px solid $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
|
||||
@@ -15,6 +15,14 @@ $info-margin-under-border = 30px
|
||||
padding 0 20px
|
||||
z-index 99
|
||||
|
||||
.info > div
|
||||
> button
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
> img, span
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
|
||||
.info-left
|
||||
padding 0 10px
|
||||
width 100%
|
||||
@@ -94,17 +102,14 @@ body[data-theme="dark"]
|
||||
.undo-button
|
||||
topBarButtonDark()
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.info
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.info
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.info
|
||||
border-color $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.info
|
||||
border-color $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
|
||||
@@ -4,13 +4,9 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TrashButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const PermanentDeleteButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-trashButton--in-trash'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||
const PermanentDeleteButton = ({ onClick }) => (
|
||||
<button styleName='control-trashButton--in-trash' onClick={e => onClick(e)}>
|
||||
<img src='../resources/icon/icon-trash.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './RestoreButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const RestoreButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-restoreButton'
|
||||
onClick={onClick}
|
||||
>
|
||||
const RestoreButton = ({ onClick }) => (
|
||||
<button styleName='control-restoreButton' onClick={onClick}>
|
||||
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
|
||||
<span styleName='tooltip'>{i18n.__('Restore')}</span>
|
||||
</button>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -156,78 +156,35 @@ 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
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.root
|
||||
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
|
||||
.body
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
.body
|
||||
background-color get-theme-var(theme, '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
|
||||
.body .description textarea
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
border 1px solid get-theme-var(theme, 'borderColor')
|
||||
|
||||
.tabList .tabButton
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
.tabList .tabButton
|
||||
border-color get-theme-var(theme, 'borderColor')
|
||||
|
||||
.tabButton
|
||||
&:hover
|
||||
color $ui-solarized-dark-button--active-color
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
transition 0.15s
|
||||
|
||||
.tabList
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
color $ui-solarized-dark-text-color
|
||||
.tabButton
|
||||
&:hover
|
||||
color get-theme-var(theme, 'text-color')
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
transition 0.15s
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
border-left 1px solid $ui-monokai-borderColor
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
.tabList
|
||||
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
.body
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.body .description textarea
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
border 1px solid $ui-monokai-borderColor
|
||||
|
||||
.tabList .tabButton
|
||||
border-color $ui-monokai-borderColor
|
||||
|
||||
.tabButton
|
||||
&:hover
|
||||
color $ui-monokai-text-color
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
|
||||
.tabList
|
||||
background-color $ui-monokai-noteDetail-backgroundColor
|
||||
color $ui-monokai-text-color
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
|
||||
.body
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
|
||||
.body .description textarea
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
color $ui-dracula-text-color
|
||||
border 1px solid $ui-dracula-borderColor
|
||||
|
||||
.tabList .tabButton
|
||||
border-color $ui-dracula-borderColor
|
||||
|
||||
.tabButton
|
||||
&:hover
|
||||
color $ui-dracula-text-color
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
|
||||
.tabList
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
color $ui-dracula-text-color
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
class StarButton extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -14,47 +14,51 @@ class StarButton extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDown (e) {
|
||||
handleMouseDown(e) {
|
||||
this.setState({
|
||||
isActive: true
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
handleMouseUp(e) {
|
||||
this.setState({
|
||||
isActive: false
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseLeave (e) {
|
||||
handleMouseLeave(e) {
|
||||
this.setState({
|
||||
isActive: false
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { className } = this.props
|
||||
|
||||
return (
|
||||
<button className={_.isString(className)
|
||||
? 'StarButton ' + className
|
||||
: 'StarButton'
|
||||
<button
|
||||
className={
|
||||
_.isString(className) ? 'StarButton ' + className : 'StarButton'
|
||||
}
|
||||
styleName={this.state.isActive || this.props.isActive
|
||||
? 'root--active'
|
||||
: 'root'
|
||||
styleName={
|
||||
this.state.isActive || this.props.isActive ? 'root--active' : 'root'
|
||||
}
|
||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
||||
onClick={this.props.onClick}>
|
||||
<img styleName='icon'
|
||||
src={this.state.isActive || this.props.isActive
|
||||
? '../resources/icon/icon-starred.svg'
|
||||
: '../resources/icon/icon-star.svg'
|
||||
onMouseDown={e => this.handleMouseDown(e)}
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
onMouseLeave={e => this.handleMouseLeave(e)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<img
|
||||
styleName='icon'
|
||||
src={
|
||||
this.state.isActive || this.props.isActive
|
||||
? '../resources/icon/icon-starred.svg'
|
||||
: '../resources/icon/icon-star.svg'
|
||||
}
|
||||
/>
|
||||
<span styleName='tooltip'>{i18n.__('Star')}</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Star')}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.tooltip:lang(ja)
|
||||
@extend .tooltip
|
||||
right 103px
|
||||
width 70px
|
||||
|
||||
.root--active
|
||||
@extend .root
|
||||
transition 0.15s
|
||||
@@ -37,4 +42,4 @@ body[data-theme="dark"]
|
||||
topBarButtonDark()
|
||||
&:hover
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import invertColor from 'invert-color'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TagSelect.styl'
|
||||
import _ from 'lodash'
|
||||
@@ -7,9 +8,10 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import Autosuggest from 'react-autosuggest'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
class TagSelect extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
@@ -18,15 +20,20 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
|
||||
this.handleAddTag = this.handleAddTag.bind(this)
|
||||
this.handleRenameTag = this.handleRenameTag.bind(this)
|
||||
this.onInputBlur = this.onInputBlur.bind(this)
|
||||
this.onInputChange = this.onInputChange.bind(this)
|
||||
this.onInputKeyDown = this.onInputKeyDown.bind(this)
|
||||
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
|
||||
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
|
||||
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(
|
||||
this
|
||||
)
|
||||
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(
|
||||
this
|
||||
)
|
||||
this.onSuggestionSelected = this.onSuggestionSelected.bind(this)
|
||||
}
|
||||
|
||||
addNewTag (newTag) {
|
||||
addNewTag(newTag) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
||||
|
||||
newTag = newTag.trim().replace(/ +/g, '_')
|
||||
@@ -42,9 +49,7 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
|
||||
let { value } = this.props
|
||||
value = _.isArray(value)
|
||||
? value.slice()
|
||||
: []
|
||||
value = _.isArray(value) ? value.slice() : []
|
||||
|
||||
if (!_.includes(value, newTag)) {
|
||||
value.push(newTag)
|
||||
@@ -54,68 +59,87 @@ class TagSelect extends React.Component {
|
||||
value = _.sortBy(value)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
newTag: ''
|
||||
}, () => {
|
||||
this.value = value
|
||||
this.props.onChange()
|
||||
})
|
||||
this.setState(
|
||||
{
|
||||
newTag: ''
|
||||
},
|
||||
() => {
|
||||
this.value = value
|
||||
this.props.onChange()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
buildSuggestions () {
|
||||
this.suggestions = _.sortBy(this.props.data.tagNoteMap.map(
|
||||
(tag, name) => ({
|
||||
name,
|
||||
nameLC: name.toLowerCase(),
|
||||
size: tag.size
|
||||
})
|
||||
).filter(
|
||||
tag => tag.size > 0
|
||||
), ['name'])
|
||||
buildSuggestions() {
|
||||
this.suggestions = _.sortBy(
|
||||
this.props.data.tagNoteMap
|
||||
.map((tag, name) => ({
|
||||
name,
|
||||
nameLC: name.toLowerCase(),
|
||||
size: tag.size
|
||||
}))
|
||||
.filter(tag => tag.size > 0),
|
||||
['name']
|
||||
)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
this.value = this.props.value
|
||||
|
||||
this.buildSuggestions()
|
||||
|
||||
ee.on('editor:add-tag', this.handleAddTag)
|
||||
ee.on('sidebar:rename-tag', this.handleRenameTag)
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
componentDidUpdate() {
|
||||
this.value = this.props.value
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ee.off('editor:add-tag', this.handleAddTag)
|
||||
ee.off('sidebar:rename-tag', this.handleRenameTag)
|
||||
}
|
||||
|
||||
handleAddTag () {
|
||||
handleAddTag() {
|
||||
this.refs.newTag.input.focus()
|
||||
}
|
||||
|
||||
handleTagLabelClick (tag) {
|
||||
const { router } = this.context
|
||||
router.push(`/tags/${tag}`)
|
||||
handleRenameTag(event, tagChange) {
|
||||
const { value } = this.props
|
||||
const { tag, updatedTag } = tagChange
|
||||
const newTags = value.slice()
|
||||
|
||||
newTags[value.indexOf(tag)] = updatedTag
|
||||
this.value = newTags
|
||||
this.props.onChange()
|
||||
}
|
||||
|
||||
handleTagRemoveButtonClick (tag) {
|
||||
handleTagLabelClick(tag) {
|
||||
const { dispatch } = this.props
|
||||
|
||||
// Note: `tag` requires encoding later.
|
||||
// E.g. % in tag is a problem (see issue #3170) - encodeURIComponent(tag) is not working.
|
||||
dispatch(push(`/tags/${tag}`))
|
||||
}
|
||||
|
||||
handleTagRemoveButtonClick(tag) {
|
||||
this.removeTagByCallback((value, tag) => {
|
||||
value.splice(value.indexOf(tag), 1)
|
||||
}, tag)
|
||||
}
|
||||
|
||||
onInputBlur (e) {
|
||||
onInputBlur(e) {
|
||||
this.submitNewTag()
|
||||
}
|
||||
|
||||
onInputChange (e, { newValue, method }) {
|
||||
onInputChange(e, { newValue, method }) {
|
||||
this.setState({
|
||||
newTag: newValue
|
||||
})
|
||||
}
|
||||
|
||||
onInputKeyDown (e) {
|
||||
onInputKeyDown(e) {
|
||||
switch (e.keyCode) {
|
||||
case 9:
|
||||
e.preventDefault()
|
||||
@@ -131,17 +155,18 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onSuggestionsClearRequested () {
|
||||
onSuggestionsClearRequested() {
|
||||
this.setState({
|
||||
suggestions: []
|
||||
})
|
||||
}
|
||||
|
||||
onSuggestionsFetchRequested ({ value }) {
|
||||
onSuggestionsFetchRequested({ value }) {
|
||||
const valueLC = value.toLowerCase()
|
||||
const suggestions = _.filter(
|
||||
this.suggestions,
|
||||
tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
|
||||
tag =>
|
||||
!_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
|
||||
)
|
||||
|
||||
this.setState({
|
||||
@@ -149,22 +174,20 @@ class TagSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
onSuggestionSelected (event, { suggestion, suggestionValue }) {
|
||||
onSuggestionSelected(event, { suggestion, suggestionValue }) {
|
||||
this.addNewTag(suggestionValue)
|
||||
}
|
||||
|
||||
removeLastTag () {
|
||||
this.removeTagByCallback((value) => {
|
||||
removeLastTag() {
|
||||
this.removeTagByCallback(value => {
|
||||
value.pop()
|
||||
})
|
||||
}
|
||||
|
||||
removeTagByCallback (callback, tag = null) {
|
||||
removeTagByCallback(callback, tag = null) {
|
||||
let { value } = this.props
|
||||
|
||||
value = _.isArray(value)
|
||||
? value.slice()
|
||||
: []
|
||||
value = _.isArray(value) ? value.slice() : []
|
||||
callback(value, tag)
|
||||
value = _.uniq(value)
|
||||
|
||||
@@ -172,7 +195,7 @@ class TagSelect extends React.Component {
|
||||
this.props.onChange()
|
||||
}
|
||||
|
||||
reset () {
|
||||
reset() {
|
||||
this.buildSuggestions()
|
||||
|
||||
this.setState({
|
||||
@@ -180,36 +203,60 @@ class TagSelect extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
submitNewTag () {
|
||||
submitNewTag() {
|
||||
this.addNewTag(this.refs.newTag.input.value)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, className, showTagsAlphabetically } = this.props
|
||||
render() {
|
||||
const { value, className, showTagsAlphabetically, coloredTags } = this.props
|
||||
|
||||
const tagList = _.isArray(value)
|
||||
? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
|
||||
return (
|
||||
<span styleName='tag'
|
||||
key={tag}
|
||||
>
|
||||
<span styleName='tag-label' onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
|
||||
<button styleName='tag-removeButton'
|
||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
||||
>
|
||||
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
? (showTagsAlphabetically ? _.sortBy(value) : value).map(tag => {
|
||||
const wrapperStyle = {}
|
||||
const textStyle = {}
|
||||
const BLACK = '#333333'
|
||||
const WHITE = '#f1f1f1'
|
||||
const color = coloredTags[tag]
|
||||
const invertedColor =
|
||||
color && invertColor(color, { black: BLACK, white: WHITE })
|
||||
let iconRemove = '../resources/icon/icon-x.svg'
|
||||
if (color) {
|
||||
wrapperStyle.backgroundColor = color
|
||||
textStyle.color = invertedColor
|
||||
}
|
||||
if (invertedColor === WHITE) {
|
||||
iconRemove = '../resources/icon/icon-x-light.svg'
|
||||
}
|
||||
return (
|
||||
<span styleName='tag' key={tag} style={wrapperStyle}>
|
||||
<span
|
||||
styleName='tag-label'
|
||||
style={textStyle}
|
||||
onClick={e => this.handleTagLabelClick(tag)}
|
||||
>
|
||||
#{tag}
|
||||
</span>
|
||||
<button
|
||||
styleName='tag-removeButton'
|
||||
onClick={e => this.handleTagRemoveButtonClick(tag)}
|
||||
>
|
||||
<img
|
||||
className='tag-removeButton-icon'
|
||||
src={iconRemove}
|
||||
width='8px'
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
: []
|
||||
|
||||
const { newTag, suggestions } = this.state
|
||||
|
||||
return (
|
||||
<div className={_.isString(className)
|
||||
? 'TagSelect ' + className
|
||||
: 'TagSelect'
|
||||
<div
|
||||
className={
|
||||
_.isString(className) ? 'TagSelect ' + className : 'TagSelect'
|
||||
}
|
||||
styleName='root'
|
||||
>
|
||||
@@ -221,11 +268,7 @@ class TagSelect extends React.Component {
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
getSuggestionValue={suggestion => suggestion.name}
|
||||
renderSuggestion={suggestion => (
|
||||
<div>
|
||||
{suggestion.name}
|
||||
</div>
|
||||
)}
|
||||
renderSuggestion={suggestion => <div>{suggestion.name}</div>}
|
||||
inputProps={{
|
||||
placeholder: i18n.__('Add tag...'),
|
||||
value: newTag,
|
||||
@@ -239,14 +282,12 @@ class TagSelect extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
TagSelect.contextTypes = {
|
||||
router: PropTypes.shape({})
|
||||
}
|
||||
|
||||
TagSelect.propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
value: PropTypes.arrayOf(PropTypes.string),
|
||||
onChange: PropTypes.func
|
||||
onChange: PropTypes.func,
|
||||
coloredTags: PropTypes.object
|
||||
}
|
||||
|
||||
export default CSSModules(TagSelect, styles)
|
||||
|
||||
@@ -3,19 +3,18 @@
|
||||
align-items center
|
||||
user-select none
|
||||
vertical-align middle
|
||||
width 100%
|
||||
overflow-x scroll
|
||||
width 96%
|
||||
overflow-x auto
|
||||
white-space nowrap
|
||||
margin-top 31px
|
||||
top 50px
|
||||
position absolute
|
||||
|
||||
.root::-webkit-scrollbar
|
||||
display none
|
||||
&::-webkit-scrollbar
|
||||
height 8px
|
||||
|
||||
.tag
|
||||
display flex
|
||||
align-items center
|
||||
margin 0px 2px
|
||||
margin 0px 2px 2px
|
||||
padding 2px 4px
|
||||
background-color alpha($ui-tag-backgroundColor, 3%)
|
||||
border-radius 4px
|
||||
@@ -55,35 +54,20 @@ body[data-theme="dark"]
|
||||
.tag-label
|
||||
color $ui-dark-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.tag
|
||||
background-color $ui-solarized-dark-tag-backgroundColor
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.tag
|
||||
background-color get-theme-var(theme, 'tag-backgroundColor')
|
||||
|
||||
.tag-removeButton
|
||||
border-color $ui-button--focus-borderColor
|
||||
background-color transparent
|
||||
.tag-removeButton
|
||||
border-color $ui-button--focus-borderColor
|
||||
background-color transparent
|
||||
|
||||
.tag-label
|
||||
color $ui-solarized-dark-text-color
|
||||
.tag-label
|
||||
color get-theme-var(theme, 'text-color')
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.tag
|
||||
background-color $ui-monokai-tag-backgroundColor
|
||||
for theme in 'solarized-dark' 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
.tag-removeButton
|
||||
border-color $ui-button--focus-borderColor
|
||||
background-color transparent
|
||||
|
||||
.tag-label
|
||||
color $ui-monokai-text-color
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.tag
|
||||
background-color $ui-dracula-tag-backgroundColor
|
||||
|
||||
.tag-removeButton
|
||||
border-color $ui-dracula-button--focus-borderColor
|
||||
background-color transparent
|
||||
|
||||
.tag-label
|
||||
color $ui-dracula-borderColor
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
26
browser/main/Detail/ToggleDirectionButton.js
Normal file
26
browser/main/Detail/ToggleDirectionButton.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ToggleDirectionButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const ToggleDirectionButton = ({ onClick, isRTL }) => (
|
||||
<div styleName='control-toggleModeButton'>
|
||||
<div styleName={isRTL ? 'active' : undefined} onClick={() => onClick()}>
|
||||
<img src={!isRTL ? '../resources/icon/icon-left-to-right.svg' : ''} />
|
||||
</div>
|
||||
<div styleName={!isRTL ? 'active' : undefined} onClick={() => onClick()}>
|
||||
<img src={!isRTL ? '' : '../resources/icon/icon-right-to-left.svg'} />
|
||||
</div>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Toggle Direction')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
ToggleDirectionButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
isRTL: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(ToggleDirectionButton, styles)
|
||||
85
browser/main/Detail/ToggleDirectionButton.styl
Normal file
85
browser/main/Detail/ToggleDirectionButton.styl
Normal file
@@ -0,0 +1,85 @@
|
||||
.control-toggleModeButton
|
||||
height 25px
|
||||
border-radius 50px
|
||||
background-color #F4F4F4
|
||||
width 52px
|
||||
display flex
|
||||
align-items center
|
||||
position: relative
|
||||
top 2px
|
||||
margin-left 5px
|
||||
.active
|
||||
background-color #1EC38B
|
||||
width 33px
|
||||
height 24px
|
||||
box-shadow 2px 0px 7px #eee
|
||||
z-index 1
|
||||
|
||||
div
|
||||
width 40px
|
||||
height 100%
|
||||
border-radius 50%
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
cursor pointer
|
||||
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 33px
|
||||
left -10px
|
||||
z-index 200
|
||||
width 80px
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.tooltip:lang(ja)
|
||||
@extend .tooltip
|
||||
left -8px
|
||||
width 70px
|
||||
|
||||
.control-toggleModeButton
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
> div img
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
.control-toggleModeButton
|
||||
background-color #3A404C
|
||||
.active
|
||||
background-color #1EC38B
|
||||
box-shadow 2px 0px 7px #444444
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.control-toggleModeButton
|
||||
background-color #002B36
|
||||
.active
|
||||
background-color #1EC38B
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.control-toggleModeButton
|
||||
background-color get-theme-var(theme, 'borderColor')
|
||||
.active
|
||||
background-color get-theme-var(theme, 'active-color')
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
for theme in 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
@@ -4,23 +4,41 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ToggleModeButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const ToggleModeButton = ({
|
||||
onClick, editorType
|
||||
}) => (
|
||||
const ToggleModeButton = ({ onClick, editorType }) => (
|
||||
<div styleName='control-toggleModeButton'>
|
||||
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
|
||||
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
|
||||
<div
|
||||
styleName={editorType === 'SPLIT' ? 'active' : undefined}
|
||||
onClick={() => onClick('SPLIT')}
|
||||
>
|
||||
<img
|
||||
src={
|
||||
editorType === 'EDITOR_PREVIEW'
|
||||
? '../resources/icon/icon-mode-markdown-off-active.svg'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
|
||||
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
||||
<div
|
||||
styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined}
|
||||
onClick={() => onClick('EDITOR_PREVIEW')}
|
||||
>
|
||||
<img
|
||||
src={
|
||||
editorType === 'EDITOR_PREVIEW'
|
||||
? ''
|
||||
: '../resources/icon/icon-mode-split-on-active.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Toggle Mode')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
ToggleModeButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
editorType: PropTypes.string.Required
|
||||
editorType: PropTypes.string.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(ToggleModeButton, styles)
|
||||
|
||||
@@ -1,72 +1,84 @@
|
||||
.control-toggleModeButton
|
||||
height 25px
|
||||
border-radius 50px
|
||||
background-color #F4F4F4
|
||||
width 52px
|
||||
display flex
|
||||
align-items center
|
||||
position: relative
|
||||
top 2px
|
||||
.active
|
||||
background-color #1EC38B
|
||||
width 33px
|
||||
height 24px
|
||||
box-shadow 2px 0px 7px #eee
|
||||
z-index 1
|
||||
|
||||
div
|
||||
width 40px
|
||||
height 100%
|
||||
border-radius 50%
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
cursor pointer
|
||||
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 33px
|
||||
left -10px
|
||||
z-index 200
|
||||
width 80px
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
.control-toggleModeButton
|
||||
background-color #3A404C
|
||||
.active
|
||||
background-color #1EC38B
|
||||
box-shadow 2px 0px 7px #444444
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.control-toggleModeButton
|
||||
background-color #002B36
|
||||
.active
|
||||
background-color #1EC38B
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
body[data-theme="monokai"]
|
||||
.control-toggleModeButton
|
||||
background-color #373831
|
||||
.active
|
||||
background-color #f92672
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
body[data-theme="dracula"]
|
||||
.control-toggleModeButton
|
||||
background-color #44475a
|
||||
.active
|
||||
background-color #bd93f9
|
||||
box-shadow 2px 0px 7px #222222
|
||||
.control-toggleModeButton
|
||||
height 25px
|
||||
border-radius 50px
|
||||
background-color #F4F4F4
|
||||
width 52px
|
||||
display flex
|
||||
align-items center
|
||||
position: relative
|
||||
top 2px
|
||||
.active
|
||||
background-color #1EC38B
|
||||
width 33px
|
||||
height 24px
|
||||
box-shadow 2px 0px 7px #eee
|
||||
z-index 1
|
||||
|
||||
div
|
||||
width 40px
|
||||
height 100%
|
||||
border-radius 50%
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
cursor pointer
|
||||
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.control-toggleModeButton
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
> div img
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 33px
|
||||
left -10px
|
||||
z-index 200
|
||||
width 80px
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.tooltip:lang(ja)
|
||||
@extend .tooltip
|
||||
left -8px
|
||||
width 70px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
.control-toggleModeButton
|
||||
background-color #3A404C
|
||||
.active
|
||||
background-color #1EC38B
|
||||
box-shadow 2px 0px 7px #444444
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.control-toggleModeButton
|
||||
background-color #002B36
|
||||
.active
|
||||
background-color #1EC38B
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
apply-theme(theme)
|
||||
body[data-theme={theme}]
|
||||
.control-toggleModeButton
|
||||
background-color get-theme-var(theme, 'borderColor')
|
||||
.active
|
||||
background-color get-theme-var(theme, 'active-color')
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
for theme in 'dracula'
|
||||
apply-theme(theme)
|
||||
|
||||
for theme in $themes
|
||||
apply-theme(theme)
|
||||
|
||||
@@ -4,14 +4,12 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TrashButton.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const TrashButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-trashButton'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||
<span styleName='tooltip'>{i18n.__('Trash')}</span>
|
||||
const TrashButton = ({ onClick }) => (
|
||||
<button styleName='control-trashButton' onClick={e => onClick(e)}>
|
||||
<img src='../resources/icon/icon-trash.svg' />
|
||||
<span lang={i18n.locale} styleName='tooltip'>
|
||||
{i18n.__('Trash')}
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.tooltip:lang(ja)
|
||||
@extend .tooltip
|
||||
right 46px
|
||||
|
||||
.control-trashButton--in-trash
|
||||
top 60px
|
||||
topBarButtonRight()
|
||||
|
||||
@@ -10,11 +10,12 @@ import StatusBar from '../StatusBar'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import debounceRender from 'react-debounce-render'
|
||||
import searchFromNotes from 'browser/lib/search'
|
||||
import queryString from 'query-string'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
class Detail extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.focusHandler = () => {
|
||||
@@ -25,43 +26,55 @@ class Detail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
ee.on('detail:focus', this.focusHandler)
|
||||
ee.on('detail:delete', this.deleteHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
ee.off('detail:focus', this.focusHandler)
|
||||
ee.off('detail:delete', this.deleteHandler)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, data, params, config } = this.props
|
||||
render() {
|
||||
const {
|
||||
location,
|
||||
data,
|
||||
match: { params },
|
||||
config
|
||||
} = this.props
|
||||
const noteKey =
|
||||
location.search !== '' && queryString.parse(location.search).key
|
||||
let note = null
|
||||
|
||||
if (location.query.key != null) {
|
||||
const noteKey = location.query.key
|
||||
if (location.search !== '') {
|
||||
const allNotes = data.noteMap.map(note => note)
|
||||
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||
const trashedNotes = data.trashedSet
|
||||
.toJS()
|
||||
.map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||
let displayedNotes = allNotes
|
||||
|
||||
if (location.pathname.match(/\/searched/)) {
|
||||
const searchStr = params.searchword
|
||||
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
|
||||
: searchFromNotes(allNotes, searchStr)
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/tags/)) {
|
||||
displayedNotes =
|
||||
searchStr === undefined || searchStr === ''
|
||||
? allNotes
|
||||
: searchFromNotes(allNotes, searchStr)
|
||||
} else if (location.pathname.match(/^\/tags/)) {
|
||||
const listOfTags = params.tagname.split(' ')
|
||||
displayedNotes = data.noteMap.map(note => note).filter(note =>
|
||||
listOfTags.every(tag => note.tags.includes(tag))
|
||||
)
|
||||
displayedNotes = data.noteMap
|
||||
.map(note => note)
|
||||
.filter(note => listOfTags.every(tag => note.tags.includes(tag)))
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/trashed/)) {
|
||||
if (location.pathname.match(/^\/trashed/)) {
|
||||
displayedNotes = trashedNotes
|
||||
} else {
|
||||
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
|
||||
displayedNotes = _.differenceWith(
|
||||
displayedNotes,
|
||||
trashedNotes,
|
||||
(note, trashed) => note.key === trashed.key
|
||||
)
|
||||
}
|
||||
|
||||
const noteKeys = displayedNotes.map(note => note.key)
|
||||
@@ -72,12 +85,12 @@ class Detail extends React.Component {
|
||||
|
||||
if (note == null) {
|
||||
return (
|
||||
<div styleName='root'
|
||||
style={this.props.style}
|
||||
tabIndex='0'
|
||||
>
|
||||
<div styleName='root' style={this.props.style} tabIndex='0'>
|
||||
<div styleName='empty'>
|
||||
<div styleName='empty-message'>{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />{i18n.__('to create a new note')}</div>
|
||||
<div styleName='empty-message'>
|
||||
{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />
|
||||
{i18n.__('to create a new note')}
|
||||
</div>
|
||||
</div>
|
||||
<StatusBar
|
||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||
|
||||
Reference in New Issue
Block a user