mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 17:56:25 +00:00
Finder
This commit is contained in:
@@ -1,44 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import CodeEditor from 'browser/components/CodeEditor'
|
|
||||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
|
||||||
import ModeIcon from 'browser/components/ModeIcon'
|
|
||||||
|
|
||||||
export default class FinderDetail extends React.Component {
|
|
||||||
render () {
|
|
||||||
let { activeArticle } = this.props
|
|
||||||
|
|
||||||
if (activeArticle != null) {
|
|
||||||
return (
|
|
||||||
<div className='FinderDetail'>
|
|
||||||
<div className='header'>
|
|
||||||
<div className='left'>
|
|
||||||
<ModeIcon mode={activeArticle.mode}/> {activeArticle.title}
|
|
||||||
</div>
|
|
||||||
<div className='right'>
|
|
||||||
<button onClick={this.props.saveToClipboard} className='clipboardBtn'>
|
|
||||||
<i className='fa fa-clipboard fa-fw'/>
|
|
||||||
<span className='tooltip'>Copy to clipboard (Enter)</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='content'>
|
|
||||||
{activeArticle.mode === 'markdown'
|
|
||||||
? <MarkdownPreview content={activeArticle.content}/>
|
|
||||||
: <CodeEditor readOnly article={activeArticle}/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className='FinderDetail'>
|
|
||||||
<div className='nothing'>Nothing selected</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderDetail.propTypes = {
|
|
||||||
activeArticle: PropTypes.shape(),
|
|
||||||
saveToClipboard: PropTypes.func
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
|
|
||||||
export default class FinderInput extends React.Component {
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className='FinderInput'>
|
|
||||||
<input ref='input' value={this.props.value} onChange={this.props.handleSearchChange} type='text'/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderInput.propTypes = {
|
|
||||||
handleSearchChange: PropTypes.func,
|
|
||||||
value: PropTypes.string
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import ModeIcon from 'browser/components/ModeIcon'
|
|
||||||
import { selectArticle } from './actions'
|
|
||||||
|
|
||||||
export default class FinderList extends React.Component {
|
|
||||||
componentDidUpdate () {
|
|
||||||
var index = this.props.articles.indexOf(this.props.activeArticle)
|
|
||||||
var el = ReactDOM.findDOMNode(this)
|
|
||||||
var li = el.querySelectorAll('li')[index]
|
|
||||||
|
|
||||||
if (li == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var overflowBelow = el.clientHeight + el.scrollTop < li.offsetTop + li.clientHeight
|
|
||||||
if (overflowBelow) {
|
|
||||||
el.scrollTop = li.offsetTop + li.clientHeight - el.clientHeight
|
|
||||||
}
|
|
||||||
var overflowAbove = el.scrollTop > li.offsetTop
|
|
||||||
if (overflowAbove) {
|
|
||||||
el.scrollTop = li.offsetTop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleArticleClick (article) {
|
|
||||||
return (e) => {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
dispatch(selectArticle(article.key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let articleElements = this.props.articles.map(function (article) {
|
|
||||||
if (article == null) {
|
|
||||||
return (
|
|
||||||
<li className={isActive ? 'active' : ''}>
|
|
||||||
<div className='articleItem'>Undefined</div>
|
|
||||||
<div className='divider'/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var isActive = this.props.activeArticle != null && (article.key === this.props.activeArticle.key)
|
|
||||||
return (
|
|
||||||
<li key={'article-' + article.key} onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
|
|
||||||
<div className='articleItem'>
|
|
||||||
<ModeIcon mode={article.mode}/> {article.title}</div>
|
|
||||||
<div className='divider'/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}.bind(this))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='FinderList'>
|
|
||||||
<ul>
|
|
||||||
{articleElements}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderList.propTypes = {
|
|
||||||
articles: PropTypes.array,
|
|
||||||
activeArticle: PropTypes.shape({
|
|
||||||
type: PropTypes.string,
|
|
||||||
key: PropTypes.string
|
|
||||||
}),
|
|
||||||
dispatch: PropTypes.func
|
|
||||||
}
|
|
||||||
78
browser/finder/FinderMain.styl
Normal file
78
browser/finder/FinderMain.styl
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
$search-height = 50px
|
||||||
|
$nav-width = 175px
|
||||||
|
$list-width = 250px
|
||||||
|
|
||||||
|
.root
|
||||||
|
absolute top left right bottom
|
||||||
|
|
||||||
|
.search
|
||||||
|
height $search-height
|
||||||
|
padding 10px
|
||||||
|
box-sizing border-box
|
||||||
|
border-bottom $ui-border
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
.search-input
|
||||||
|
height 30px
|
||||||
|
width 100%
|
||||||
|
margin 0 auto
|
||||||
|
font-size 18px
|
||||||
|
border none
|
||||||
|
outline none
|
||||||
|
text-align center
|
||||||
|
.result
|
||||||
|
absolute left right bottom
|
||||||
|
top $search-height
|
||||||
|
|
||||||
|
.result-nav
|
||||||
|
user-select none
|
||||||
|
absolute left top bottom
|
||||||
|
width $nav-width
|
||||||
|
background-color $ui-backgroundColor
|
||||||
|
.result-nav-filter
|
||||||
|
margin-bottom 5px
|
||||||
|
.result-nav-filter-option
|
||||||
|
height 25px
|
||||||
|
line-height 25px
|
||||||
|
padding 0 10px
|
||||||
|
label
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
.result-nav-menu
|
||||||
|
navButtonColor()
|
||||||
|
height 40px
|
||||||
|
padding 0 10px
|
||||||
|
font-size 14px
|
||||||
|
width 100%
|
||||||
|
outline none
|
||||||
|
text-align left
|
||||||
|
line-height 40px
|
||||||
|
box-sizing border-box
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
.result-nav-menu--active
|
||||||
|
@extend .result-nav-menu
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.result-nav-storageList
|
||||||
|
absolute bottom left right
|
||||||
|
top 80px + 50px + 10px
|
||||||
|
overflow-y auto
|
||||||
|
|
||||||
|
.result-list
|
||||||
|
user-select none
|
||||||
|
absolute top bottom
|
||||||
|
left $nav-width
|
||||||
|
width $list-width
|
||||||
|
border-width 0 1px
|
||||||
|
border-style solid
|
||||||
|
border-color $ui-borderColor
|
||||||
|
box-sizing border-box
|
||||||
|
overflow-y auto
|
||||||
|
|
||||||
|
.result-detail
|
||||||
|
absolute top bottom right
|
||||||
|
left $nav-width + $list-width
|
||||||
198
browser/finder/NoteDetail.js
Normal file
198
browser/finder/NoteDetail.js
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './NoteDetail.styl'
|
||||||
|
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||||
|
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||||
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
|
import modes from 'browser/lib/modes'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const { clipboard } = electron
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
function notify (title, options) {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
||||||
|
}
|
||||||
|
return new window.Notification(title, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteDetail extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
snippetIndex: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (nextProps.note !== this.props.note) {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: 0
|
||||||
|
}, () => {
|
||||||
|
if (nextProps.note.type === 'SNIPPET_NOTE') {
|
||||||
|
nextProps.note.snippets.forEach((snippet, index) => {
|
||||||
|
this.refs['code-' + index].reload()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectPriorSnippet () {
|
||||||
|
let { note } = this.props
|
||||||
|
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: (this.state.snippetIndex + note.snippets.length - 1) % note.snippets.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectNextSnippet () {
|
||||||
|
let { note } = this.props
|
||||||
|
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: (this.state.snippetIndex + 1) % note.snippets.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveToClipboard () {
|
||||||
|
let { note } = this.props
|
||||||
|
|
||||||
|
if (note.type === 'MARKDOWN_NOTE') {
|
||||||
|
clipboard.writeText(note.content)
|
||||||
|
} else {
|
||||||
|
clipboard.writeText(note.snippets[this.state.snippetIndex].content)
|
||||||
|
}
|
||||||
|
|
||||||
|
notify('Saved to Clipboard!', {
|
||||||
|
body: 'Paste it wherever you want!',
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTabButtonClick (e, index) {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
let { note, config } = this.props
|
||||||
|
if (note == null) {
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
|
||||||
|
if (note.type === 'SNIPPET_NOTE') {
|
||||||
|
let tabList = note.snippets.map((snippet, index) => {
|
||||||
|
let isActive = this.state.snippetIndex === index
|
||||||
|
return <div styleName={isActive
|
||||||
|
? 'tabList-item--active'
|
||||||
|
: 'tabList-item'
|
||||||
|
}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
<button styleName='tabList-item-button'
|
||||||
|
onClick={(e) => this.handleTabButtonClick(e, index)}
|
||||||
|
>
|
||||||
|
{snippet.name.trim().length > 0
|
||||||
|
? snippet.name
|
||||||
|
: <span styleName='tabList-item-unnamed'>
|
||||||
|
Unnamed
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
let viewList = note.snippets.map((snippet, index) => {
|
||||||
|
let isActive = this.state.snippetIndex === index
|
||||||
|
let mode = snippet.mode === 'text'
|
||||||
|
? null
|
||||||
|
: modes.filter((mode) => mode.name === snippet.mode)[0]
|
||||||
|
|
||||||
|
return <div styleName='tabView'
|
||||||
|
key={index}
|
||||||
|
style={{zIndex: isActive ? 5 : 4}}
|
||||||
|
>
|
||||||
|
<div styleName='tabView-top'>
|
||||||
|
<input styleName='tabView-top-name'
|
||||||
|
placeholder='Filename including extensions...'
|
||||||
|
value={snippet.name}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
<button styleName='tabView-top-mode'
|
||||||
|
>
|
||||||
|
{mode.label}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{snippet.mode === 'markdown'
|
||||||
|
? <MarkdownEditor styleName='tabView-content'
|
||||||
|
config={config}
|
||||||
|
value={snippet.content}
|
||||||
|
ref={'code-' + index}
|
||||||
|
/>
|
||||||
|
: <CodeEditor styleName='tabView-content'
|
||||||
|
mode={snippet.mode}
|
||||||
|
value={snippet.content}
|
||||||
|
theme={config.editor.theme}
|
||||||
|
fontFamily={config.editor.fontFamily}
|
||||||
|
fontSize={editorFontSize}
|
||||||
|
indentType={config.editor.indentType}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
|
readOnly
|
||||||
|
ref={'code-' + index}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='description'>
|
||||||
|
<textarea styleName='description-textarea'
|
||||||
|
style={{
|
||||||
|
fontFamily: config.preview.fontFamily,
|
||||||
|
fontSize: parseInt(config.preview.fontSize, 10)
|
||||||
|
}}
|
||||||
|
ref='description'
|
||||||
|
placeholder='Description...'
|
||||||
|
value={note.description}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div styleName='tabList'>
|
||||||
|
{tabList}
|
||||||
|
</div>
|
||||||
|
{viewList}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MarkdownPreview styleName='root'
|
||||||
|
fontSize={config.preview.fontSize}
|
||||||
|
fontFamily={config.preview.fontFamily}
|
||||||
|
codeBlockTheme={config.preview.codeBlockTheme}
|
||||||
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
|
lineNumber={config.preview.lineNumber}
|
||||||
|
value={note.content}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteDetail.propTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(NoteDetail, styles)
|
||||||
93
browser/finder/NoteDetail.styl
Normal file
93
browser/finder/NoteDetail.styl
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
.root
|
||||||
|
absolute top bottom left right
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
|
||||||
|
.description
|
||||||
|
absolute top left right
|
||||||
|
height 80px
|
||||||
|
border-bottom $ui-border
|
||||||
|
box-sizing border-box
|
||||||
|
|
||||||
|
.description-textarea
|
||||||
|
display block
|
||||||
|
height 100%
|
||||||
|
width 100%
|
||||||
|
resize none
|
||||||
|
border none
|
||||||
|
padding 10px
|
||||||
|
line-height 1.6
|
||||||
|
box-sizing border-box
|
||||||
|
|
||||||
|
.tabList
|
||||||
|
absolute left right
|
||||||
|
top 80px
|
||||||
|
box-sizing border-box
|
||||||
|
height 30px
|
||||||
|
border-bottom $ui-border
|
||||||
|
display flex
|
||||||
|
background-color $ui-backgroundColor
|
||||||
|
.tabList-item
|
||||||
|
position relative
|
||||||
|
flex 1
|
||||||
|
border-right $ui-border
|
||||||
|
&:hover
|
||||||
|
.tabList-item-deleteButton
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
background-color darken($ui-backgroundColor, 15%)
|
||||||
|
&:active
|
||||||
|
color white
|
||||||
|
background-color $ui-active-color
|
||||||
|
.tabList-item--active
|
||||||
|
@extend .tabList-item
|
||||||
|
.tabList-item-button
|
||||||
|
border-color $brand-color
|
||||||
|
.tabList-item-button
|
||||||
|
width 100%
|
||||||
|
height 29px
|
||||||
|
navButtonColor()
|
||||||
|
border-left 4px solid transparent
|
||||||
|
.tabList-item-deleteButton
|
||||||
|
position absolute
|
||||||
|
top 5px
|
||||||
|
height 20px
|
||||||
|
right 5px
|
||||||
|
width 20px
|
||||||
|
text-align center
|
||||||
|
border none
|
||||||
|
padding 0
|
||||||
|
color transparent
|
||||||
|
background-color transparent
|
||||||
|
border-radius 2px
|
||||||
|
.tabList-plusButton
|
||||||
|
navButtonColor()
|
||||||
|
width 30px
|
||||||
|
.tabView
|
||||||
|
absolute left right bottom
|
||||||
|
top 110px
|
||||||
|
.tabView-top
|
||||||
|
absolute top left right
|
||||||
|
height 30px
|
||||||
|
border-bottom $ui-border
|
||||||
|
display flex
|
||||||
|
box-sizing border-box
|
||||||
|
.tabView-top-name
|
||||||
|
flex 1
|
||||||
|
border none
|
||||||
|
padding 0 10px
|
||||||
|
font-size 14px
|
||||||
|
.tabView-top-mode
|
||||||
|
width 110px
|
||||||
|
padding 0
|
||||||
|
border none
|
||||||
|
border-left $ui-border
|
||||||
|
colorDefaultButton()
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
pointer-events none
|
||||||
|
.tabView-content
|
||||||
|
absolute left right bottom
|
||||||
|
top 30px
|
||||||
|
box-sizing border-box
|
||||||
|
height 100%
|
||||||
|
width 100%
|
||||||
86
browser/finder/NoteItem.js
Normal file
86
browser/finder/NoteItem.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './NoteItem.styl'
|
||||||
|
import moment from 'moment'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
class NoteItem extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick (e) {
|
||||||
|
this.props.onClick(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
let { note, folder, storage, isActive } = this.props
|
||||||
|
|
||||||
|
let tagList = _.isArray(note.tags)
|
||||||
|
? note.tags.map((tag) => {
|
||||||
|
return (
|
||||||
|
<span styleName='item-tagList-item'
|
||||||
|
key={tag}>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
return (
|
||||||
|
<div styleName={isActive
|
||||||
|
? 'root--active'
|
||||||
|
: 'root'
|
||||||
|
}
|
||||||
|
key={note.uniqueKey}
|
||||||
|
onClick={(e) => this.handleClick(e)}
|
||||||
|
>
|
||||||
|
<div styleName='border'/>
|
||||||
|
<div styleName='info'>
|
||||||
|
|
||||||
|
<div styleName='info-left'>
|
||||||
|
<span styleName='info-left-folder'
|
||||||
|
style={{borderColor: folder.color}}
|
||||||
|
>
|
||||||
|
{folder.name}
|
||||||
|
<span styleName='info-left-folder-surfix'>in {storage.name}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='info-right'>
|
||||||
|
{moment(note.updatedAt).fromNow()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='title'>
|
||||||
|
{note.type === 'SNIPPET_NOTE'
|
||||||
|
? <i styleName='title-icon' className='fa fa-fw fa-code'/>
|
||||||
|
: <i styleName='title-icon' className='fa fa-fw fa-file-text-o'/>
|
||||||
|
}
|
||||||
|
{note.title.trim().length > 0
|
||||||
|
? note.title
|
||||||
|
: <span styleName='title-empty'>Empty</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div styleName='tagList'>
|
||||||
|
<i styleName='tagList-icon'
|
||||||
|
className='fa fa-tags fa-fw'
|
||||||
|
/>
|
||||||
|
{tagList.length > 0
|
||||||
|
? tagList
|
||||||
|
: <span styleName='tagList-empty'>Not tagged yet</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteItem.propTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(NoteItem, styles)
|
||||||
86
browser/finder/NoteItem.styl
Normal file
86
browser/finder/NoteItem.styl
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
.root
|
||||||
|
position relative
|
||||||
|
height 80px
|
||||||
|
border-bottom $ui-border
|
||||||
|
padding 0 5px
|
||||||
|
user-select none
|
||||||
|
cursor pointer
|
||||||
|
transition background-color 0.15s
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-active-color, 10%)
|
||||||
|
|
||||||
|
.root--active
|
||||||
|
@extend .root
|
||||||
|
.border
|
||||||
|
border-color $ui-active-color
|
||||||
|
|
||||||
|
.border
|
||||||
|
absolute top bottom left right
|
||||||
|
border-style solid
|
||||||
|
border-width 2px
|
||||||
|
border-color transparent
|
||||||
|
transition 0.15s
|
||||||
|
|
||||||
|
.info
|
||||||
|
height 30px
|
||||||
|
clearfix()
|
||||||
|
font-size 12px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
line-height 30px
|
||||||
|
overflow-y hidden
|
||||||
|
|
||||||
|
.info-left
|
||||||
|
float left
|
||||||
|
overflow ellipsis
|
||||||
|
|
||||||
|
.info-left-folder
|
||||||
|
border-left 4px solid transparent
|
||||||
|
padding 2px 5px
|
||||||
|
color $ui-text-color
|
||||||
|
.info-left-folder-surfix
|
||||||
|
font-size 10px
|
||||||
|
margin-left 5px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
.info-right
|
||||||
|
float right
|
||||||
|
|
||||||
|
.title
|
||||||
|
height 20px
|
||||||
|
line-height 20px
|
||||||
|
padding 0 5px 0 0
|
||||||
|
font-weight bold
|
||||||
|
overflow ellipsis
|
||||||
|
color $ui-text-color
|
||||||
|
.title-icon
|
||||||
|
font-size 12px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding-right 3px
|
||||||
|
.title-empty
|
||||||
|
font-weight normal
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.tagList
|
||||||
|
height 30px
|
||||||
|
font-size 12px
|
||||||
|
line-height 30px
|
||||||
|
overflow ellipsis
|
||||||
|
|
||||||
|
.tagList-icon
|
||||||
|
vertical-align middle
|
||||||
|
color $ui-button-color
|
||||||
|
|
||||||
|
.tagList-item
|
||||||
|
margin 0 4px
|
||||||
|
padding 0 4px
|
||||||
|
height 20px
|
||||||
|
border-radius 3px
|
||||||
|
vertical-align middle
|
||||||
|
border-style solid
|
||||||
|
border-color $ui-button--focus-borderColor
|
||||||
|
border-width 0 0 0 3px
|
||||||
|
background-color $ui-backgroundColor
|
||||||
|
|
||||||
|
.tagList-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
vertical-align middle
|
||||||
|
|
||||||
89
browser/finder/NoteList.js
Normal file
89
browser/finder/NoteList.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import NoteItem from './NoteItem'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
class NoteList extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
range: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (this.props.search !== nextProps.search) {
|
||||||
|
this.resetScroll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate () {
|
||||||
|
let { index } = this.props
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
let list = this.refs.root
|
||||||
|
let item = list.childNodes[index]
|
||||||
|
if (item == null) return null
|
||||||
|
|
||||||
|
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
||||||
|
if (overflowBelow) {
|
||||||
|
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
|
||||||
|
}
|
||||||
|
let overflowAbove = list.scrollTop > item.offsetTop
|
||||||
|
if (overflowAbove) {
|
||||||
|
list.scrollTop = item.offsetTop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetScroll () {
|
||||||
|
this.refs.root.scrollTop = 0
|
||||||
|
this.setState({
|
||||||
|
range: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll (e) {
|
||||||
|
let { notes } = this.props
|
||||||
|
|
||||||
|
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 100 && notes.length > this.state.range * 10 + 10) {
|
||||||
|
this.setState({
|
||||||
|
range: this.state.range + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
let { storages, notes, index } = this.props
|
||||||
|
|
||||||
|
let notesList = notes
|
||||||
|
.slice(0, 10 + 10 * this.state.range)
|
||||||
|
.map((note, _index) => {
|
||||||
|
let storage = _.find(storages, {key: note.storage})
|
||||||
|
let folder = _.find(storage.folders, {key: note.folder})
|
||||||
|
return (
|
||||||
|
<NoteItem
|
||||||
|
note={note}
|
||||||
|
key={`${note.storage}-${note.folder}-${note.key}`}
|
||||||
|
storage={storage}
|
||||||
|
folder={folder}
|
||||||
|
isActive={index === _index}
|
||||||
|
onClick={(e) => this.props.handleNoteClick(e, _index)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div className={this.props.className}
|
||||||
|
onScroll={(e) => this.handleScroll(e)}
|
||||||
|
ref='root'
|
||||||
|
>
|
||||||
|
{notesList}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteList.propTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NoteList
|
||||||
72
browser/finder/StorageSection.js
Normal file
72
browser/finder/StorageSection.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './StorageSection.styl'
|
||||||
|
|
||||||
|
class StorageSection extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isOpen: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToggleButtonClick (e) {
|
||||||
|
this.setState({
|
||||||
|
isOpen: !this.state.isOpen
|
||||||
|
})
|
||||||
|
}
|
||||||
|
handleHeaderClick (e) {
|
||||||
|
let { storage } = this.props
|
||||||
|
this.props.handleStorageButtonClick(e, storage.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFolderClick (e, folder) {
|
||||||
|
let { storage } = this.props
|
||||||
|
this.props.handleFolderButtonClick(e, storage.key, folder.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
let { storage, filter } = this.props
|
||||||
|
let folderList = storage.folders
|
||||||
|
.map((folder) => {
|
||||||
|
return (
|
||||||
|
<button styleName={filter.type === 'FOLDER' && filter.folder === folder.key && filter.storage === storage.key
|
||||||
|
? 'folderList-item--active'
|
||||||
|
: 'folderList-item'
|
||||||
|
}
|
||||||
|
style={{borderColor: folder.color}}
|
||||||
|
key={folder.key}
|
||||||
|
onClick={(e) => this.handleFolderClick(e, folder)}
|
||||||
|
>
|
||||||
|
{folder.name}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='header'>
|
||||||
|
<button styleName='header-toggleButton'
|
||||||
|
onClick={(e) => this.handleToggleButtonClick(e)}
|
||||||
|
><i className='fa fa-caret-down'/></button>
|
||||||
|
<button styleName={filter.type === 'STORAGE' && filter.storage === storage.key
|
||||||
|
? 'header-name--active'
|
||||||
|
: 'header-name'
|
||||||
|
}
|
||||||
|
onClick={(e) => this.handleHeaderClick(e)}
|
||||||
|
>{storage.name}</button>
|
||||||
|
</div>
|
||||||
|
{this.state.isOpen &&
|
||||||
|
<div styleName='folderList'>
|
||||||
|
{folderList}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageSection.propTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(StorageSection, styles)
|
||||||
59
browser/finder/StorageSection.styl
Normal file
59
browser/finder/StorageSection.styl
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
.root
|
||||||
|
position relative
|
||||||
|
|
||||||
|
.header
|
||||||
|
height 30px
|
||||||
|
.header-toggleButton
|
||||||
|
absolute top left
|
||||||
|
width 25px
|
||||||
|
height 30px
|
||||||
|
navButtonColor()
|
||||||
|
border none
|
||||||
|
outline none
|
||||||
|
.header-name
|
||||||
|
display block
|
||||||
|
height 30px
|
||||||
|
navButtonColor()
|
||||||
|
padding 0 10px 0 25px
|
||||||
|
font-size 14px
|
||||||
|
width 100%
|
||||||
|
text-align left
|
||||||
|
line-height 30px
|
||||||
|
box-sizing border-box
|
||||||
|
cursor pointer
|
||||||
|
outline none
|
||||||
|
|
||||||
|
.header-name--active
|
||||||
|
@extend .header-name
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.folderList-item
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
height 30px
|
||||||
|
navButtonColor()
|
||||||
|
padding 0 10px 0 25px
|
||||||
|
font-size 14px
|
||||||
|
width 100%
|
||||||
|
text-align left
|
||||||
|
line-height 30px
|
||||||
|
box-sizing border-box
|
||||||
|
cursor pointer
|
||||||
|
outline none
|
||||||
|
padding 0 10px
|
||||||
|
margin 2px 0
|
||||||
|
height 30px
|
||||||
|
line-height 30px
|
||||||
|
border-width 0 0 0 6px
|
||||||
|
border-style solid
|
||||||
|
border-color transparent
|
||||||
|
|
||||||
|
.folderList-item--active
|
||||||
|
@extend .folderList-item
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
export const SELECT_ARTICLE = 'SELECT_ARTICLE'
|
|
||||||
export const SEARCH_ARTICLE = 'SEARCH_ARTICLE'
|
|
||||||
export const REFRESH_DATA = 'REFRESH_DATA'
|
|
||||||
|
|
||||||
export function selectArticle (key) {
|
|
||||||
return {
|
|
||||||
type: SELECT_ARTICLE,
|
|
||||||
data: { key }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function searchArticle (input) {
|
|
||||||
return {
|
|
||||||
type: SEARCH_ARTICLE,
|
|
||||||
data: { input }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshData (data) {
|
|
||||||
console.log('refreshing data')
|
|
||||||
let { folders, articles } = data
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: REFRESH_DATA,
|
|
||||||
data: {
|
|
||||||
articles,
|
|
||||||
folders
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
SELECT_ARTICLE,
|
|
||||||
SEARCH_ARTICLE,
|
|
||||||
REFRESH_DATA,
|
|
||||||
selectArticle,
|
|
||||||
searchArticle,
|
|
||||||
refreshData
|
|
||||||
}
|
|
||||||
@@ -1,85 +1,86 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import { connect, Provider } from 'react-redux'
|
import { connect, Provider } from 'react-redux'
|
||||||
import reducer from './reducer'
|
|
||||||
import { createStore } from 'redux'
|
|
||||||
import FinderInput from './FinderInput'
|
|
||||||
import FinderList from './FinderList'
|
|
||||||
import FinderDetail from './FinderDetail'
|
|
||||||
import actions, { selectArticle, searchArticle } from './actions'
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import dataStore from 'browser/lib/dataStore'
|
import ipc from './ipcClient'
|
||||||
import fetchConfig from '../lib/fetchConfig'
|
import store from './store'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './FinderMain.styl'
|
||||||
|
import StorageSection from './StorageSection'
|
||||||
|
import NoteList from './NoteList'
|
||||||
|
import NoteDetail from './NoteDetail'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { clipboard, ipcRenderer, remote } = electron
|
const { remote } = electron
|
||||||
const path = require('path')
|
const { Menu } = remote
|
||||||
|
|
||||||
let config = fetchConfig()
|
|
||||||
applyConfig(config)
|
|
||||||
|
|
||||||
ipcRenderer.on('config-apply', function (e, newConfig) {
|
|
||||||
config = newConfig
|
|
||||||
applyConfig(config)
|
|
||||||
})
|
|
||||||
|
|
||||||
function applyConfig () {
|
|
||||||
let body = document.body
|
|
||||||
body.setAttribute('data-theme', config['theme-ui'])
|
|
||||||
|
|
||||||
let hljsCss = document.getElementById('hljs-css')
|
|
||||||
hljsCss.setAttribute('href', '../node_modules/highlight.js/styles/' + config['theme-code'] + '.css')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
window.addEventListener('keydown', function (e) {
|
|
||||||
if (e.keyCode === 73 && e.metaKey && e.altKey) {
|
|
||||||
remote.getCurrentWindow().toggleDevTools()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideFinder () {
|
function hideFinder () {
|
||||||
ipcRenderer.send('hide-finder')
|
|
||||||
}
|
|
||||||
|
|
||||||
function notify (title, options) {
|
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
remote.getCurrentWindow().minimize()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return new window.Notification(title, options)
|
if (process.platform === 'darwin') {
|
||||||
|
Menu.sendActionToFirstResponder('hide:')
|
||||||
|
}
|
||||||
|
remote.getCurrentWindow().hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
require('!!style!css!stylus?sourceMap!../styles/finder/index.styl')
|
require('!!style!css!stylus?sourceMap!../styles/finder/index.styl')
|
||||||
|
|
||||||
const FOLDER_FILTER = 'FOLDER_FILTER'
|
|
||||||
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
|
|
||||||
const TEXT_FILTER = 'TEXT_FILTER'
|
|
||||||
const TAG_FILTER = 'TAG_FILTER'
|
|
||||||
|
|
||||||
class FinderMain extends React.Component {
|
class FinderMain extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
search: '',
|
||||||
|
index: 0,
|
||||||
|
filter: {
|
||||||
|
includeSnippet: true,
|
||||||
|
includeMarkdown: false,
|
||||||
|
type: 'ALL',
|
||||||
|
storage: null,
|
||||||
|
folder: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.focusHandler = (e) => this.handleWindowFocus(e)
|
||||||
|
this.blurHandler = (e) => this.handleWindowBlur(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.keyDownHandler = e => this.handleKeyDown(e)
|
|
||||||
document.addEventListener('keydown', this.keyDownHandler)
|
|
||||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
|
||||||
this.focusHandler = e => {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
|
|
||||||
dispatch(searchArticle(''))
|
|
||||||
dispatch(selectArticle(null))
|
|
||||||
}
|
|
||||||
window.addEventListener('focus', this.focusHandler)
|
window.addEventListener('focus', this.focusHandler)
|
||||||
|
window.addEventListener('blur', this.blurHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
document.removeEventListener('keydown', this.keyDownHandler)
|
|
||||||
window.removeEventListener('focus', this.focusHandler)
|
window.removeEventListener('focus', this.focusHandler)
|
||||||
|
window.removeEventListener('blur', this.blurHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWindowFocus (e) {
|
||||||
|
this.refs.search.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWindowBlur (e) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.type = 'ALL'
|
||||||
|
this.setState({
|
||||||
|
search: '',
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown (e) {
|
||||||
|
this.refs.search.focus()
|
||||||
|
if (e.keyCode === 9) {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
this.refs.detail.selectPriorSnippet()
|
||||||
|
} else {
|
||||||
|
this.refs.detail.selectNextSnippet()
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
if (e.keyCode === 38) {
|
if (e.keyCode === 38) {
|
||||||
this.selectPrevious()
|
this.selectPrevious()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -91,7 +92,8 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
this.saveToClipboard()
|
this.refs.detail.saveToClipboard()
|
||||||
|
hideFinder()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
if (e.keyCode === 27) {
|
if (e.keyCode === 27) {
|
||||||
@@ -101,26 +103,13 @@ class FinderMain extends React.Component {
|
|||||||
if (e.keyCode === 91 || e.metaKey) {
|
if (e.keyCode === 91 || e.metaKey) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToClipboard () {
|
|
||||||
let { activeArticle } = this.props
|
|
||||||
clipboard.writeText(activeArticle.content)
|
|
||||||
|
|
||||||
ipcRenderer.send('copy-finder')
|
|
||||||
notify('Saved to Clipboard!', {
|
|
||||||
body: 'Paste it wherever you want!',
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
hideFinder()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchChange (e) {
|
handleSearchChange (e) {
|
||||||
let { dispatch } = this.props
|
this.setState({
|
||||||
|
search: e.target.value,
|
||||||
dispatch(searchArticle(e.target.value))
|
index: 0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
selectArticle (article) {
|
selectArticle (article) {
|
||||||
@@ -128,41 +117,204 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectPrevious () {
|
selectPrevious () {
|
||||||
let { activeArticle, dispatch } = this.props
|
if (this.state.index > 0) {
|
||||||
let index = this.refs.finderList.props.articles.indexOf(activeArticle)
|
this.setState({
|
||||||
let previousArticle = this.refs.finderList.props.articles[index - 1]
|
index: this.state.index - 1
|
||||||
if (previousArticle != null) dispatch(selectArticle(previousArticle.key))
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNext () {
|
selectNext () {
|
||||||
let { activeArticle, dispatch } = this.props
|
if (this.state.index < this.noteCount - 1) {
|
||||||
let index = this.refs.finderList.props.articles.indexOf(activeArticle)
|
this.setState({
|
||||||
let previousArticle = this.refs.finderList.props.articles[index + 1]
|
index: this.state.index + 1
|
||||||
if (previousArticle != null) dispatch(selectArticle(previousArticle.key))
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnlySnippetCheckboxChange (e) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.includeSnippet = e.target.checked
|
||||||
|
this.setState({
|
||||||
|
filter: filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnlyMarkdownCheckboxChange (e) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.includeMarkdown = e.target.checked
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter: filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAllNotesButtonClick (e) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.type = 'ALL'
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStarredButtonClick (e) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.type = 'STARRED'
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStorageButtonClick (e, storage) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.type = 'STORAGE'
|
||||||
|
filter.storage = storage
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFolderButtonClick (e, storage, folder) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.type = 'FOLDER'
|
||||||
|
filter.storage = storage
|
||||||
|
filter.folder = folder
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNoteClick (e, index) {
|
||||||
|
this.setState({
|
||||||
|
index
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { articles, activeArticle, status, dispatch } = this.props
|
let { storages, notes, config } = this.props
|
||||||
let saveToClipboard = () => this.saveToClipboard()
|
let { filter, search } = this.state
|
||||||
|
let storageList = storages
|
||||||
|
.map((storage) => <StorageSection
|
||||||
|
filter={filter}
|
||||||
|
storage={storage}
|
||||||
|
key={storage.key}
|
||||||
|
handleStorageButtonClick={(e, storage) => this.handleStorageButtonClick(e, storage)}
|
||||||
|
handleFolderButtonClick={(e, storage, folder) => this.handleFolderButtonClick(e, storage, folder)}
|
||||||
|
/>)
|
||||||
|
if (!filter.includeSnippet && filter.includeMarkdown) {
|
||||||
|
notes = notes.filter((note) => note.type === 'MARKDOWN_NOTE')
|
||||||
|
} else if (filter.includeSnippet && !filter.includeMarkdown) {
|
||||||
|
notes = notes.filter((note) => note.type === 'SNIPPET_NOTE')
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (filter.type) {
|
||||||
|
case 'STORAGE':
|
||||||
|
notes = notes.filter((note) => note.storage === filter.storage)
|
||||||
|
break
|
||||||
|
case 'FOLDER':
|
||||||
|
notes = notes.filter((note) => note.storage === filter.storage && note.folder === filter.folder)
|
||||||
|
break
|
||||||
|
case 'STARRED':
|
||||||
|
notes = notes.filter((note) => note.isStarred)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search.trim().length > 0) {
|
||||||
|
let needle = new RegExp(_.escapeRegExp(search.trim()), 'i')
|
||||||
|
notes = notes.filter((note) => note.title.match(needle))
|
||||||
|
}
|
||||||
|
|
||||||
|
let activeNote = notes[this.state.index]
|
||||||
|
this.noteCount = notes.length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='Finder'>
|
<div className='Finder'
|
||||||
<FinderInput
|
styleName='root'
|
||||||
handleSearchChange={e => this.handleSearchChange(e)}
|
ref='-1'
|
||||||
ref='finderInput'
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
onChange={this.handleChange}
|
>
|
||||||
value={status.search}
|
<div styleName='search'>
|
||||||
/>
|
<input
|
||||||
<FinderList
|
styleName='search-input'
|
||||||
ref='finderList'
|
ref='search'
|
||||||
activeArticle={activeArticle}
|
value={search}
|
||||||
articles={articles}
|
placeholder='Search...'
|
||||||
dispatch={dispatch}
|
onChange={(e) => this.handleSearchChange(e)}
|
||||||
selectArticle={article => this.selectArticle(article)}
|
/>
|
||||||
/>
|
</div>
|
||||||
<FinderDetail
|
<div styleName='result'>
|
||||||
activeArticle={activeArticle}
|
<div styleName='result-nav'>
|
||||||
saveToClipboard={saveToClipboard}
|
<div styleName='result-nav-filter'>
|
||||||
/>
|
<div styleName='result-nav-filter-option'>
|
||||||
|
<label>
|
||||||
|
<input type='checkbox'
|
||||||
|
checked={filter.includeSnippet}
|
||||||
|
onChange={(e) => this.handleOnlySnippetCheckboxChange(e)}
|
||||||
|
/> Only Snippets</label>
|
||||||
|
</div>
|
||||||
|
<div styleName='result-nav-filter-option'>
|
||||||
|
<label>
|
||||||
|
<input type='checkbox'
|
||||||
|
checked={filter.includeMarkdown}
|
||||||
|
onChange={(e) => this.handleOnlyMarkdownCheckboxChange(e)}
|
||||||
|
/> Only Markdown</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button styleName={filter.type === 'ALL'
|
||||||
|
? 'result-nav-menu--active'
|
||||||
|
: 'result-nav-menu'
|
||||||
|
}
|
||||||
|
onClick={(e) => this.handleAllNotesButtonClick(e)}
|
||||||
|
><i className='fa fa-files-o fa-fw'/> All Notes</button>
|
||||||
|
<button styleName={filter.type === 'STARRED'
|
||||||
|
? 'result-nav-menu--active'
|
||||||
|
: 'result-nav-menu'
|
||||||
|
}
|
||||||
|
onClick={(e) => this.handleStarredButtonClick(e)}
|
||||||
|
><i className='fa fa-star fa-fw'/> Starred</button>
|
||||||
|
<div styleName='result-nav-storageList'>
|
||||||
|
{storageList}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NoteList styleName='result-list'
|
||||||
|
storages={storages}
|
||||||
|
notes={notes}
|
||||||
|
ref='list'
|
||||||
|
search={search}
|
||||||
|
index={this.state.index}
|
||||||
|
handleNoteClick={(e, _index) => this.handleNoteClick(e, _index)}
|
||||||
|
/>
|
||||||
|
<div styleName='result-detail'>
|
||||||
|
<NoteDetail
|
||||||
|
note={activeNote}
|
||||||
|
config={config}
|
||||||
|
ref='detail'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -180,99 +332,10 @@ FinderMain.propTypes = {
|
|||||||
dispatch: PropTypes.func
|
dispatch: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore invalid key
|
var Finder = connect((x) => x)(CSSModules(FinderMain, styles))
|
||||||
function ignoreInvalidKey (key) {
|
|
||||||
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build filter object by key
|
|
||||||
function buildFilter (key) {
|
|
||||||
if (key.match(/^\/\/.+/)) {
|
|
||||||
return {type: FOLDER_EXACT_FILTER, value: key.match(/^\/\/(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
if (key.match(/^\/.+/)) {
|
|
||||||
return {type: FOLDER_FILTER, value: key.match(/^\/(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
if (key.match(/^#(.+)/)) {
|
|
||||||
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
return {type: TEXT_FILTER, value: key}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isContaining (target, needle) {
|
|
||||||
return target.match(new RegExp(_.escapeRegExp(needle), 'i'))
|
|
||||||
}
|
|
||||||
|
|
||||||
function startsWith (target, needle) {
|
|
||||||
return target.match(new RegExp('^' + _.escapeRegExp(needle), 'i'))
|
|
||||||
}
|
|
||||||
|
|
||||||
function remap (state) {
|
|
||||||
let { articles, folders, status } = state
|
|
||||||
|
|
||||||
let filters = status.search.split(' ')
|
|
||||||
.map(key => key.trim())
|
|
||||||
.filter(ignoreInvalidKey)
|
|
||||||
.map(buildFilter)
|
|
||||||
|
|
||||||
let folderExactFilters = filters.filter(filter => filter.type === FOLDER_EXACT_FILTER)
|
|
||||||
let folderFilters = filters.filter(filter => filter.type === FOLDER_FILTER)
|
|
||||||
let textFilters = filters.filter(filter => filter.type === TEXT_FILTER)
|
|
||||||
let tagFilters = filters.filter(filter => filter.type === TAG_FILTER)
|
|
||||||
|
|
||||||
let targetFolders
|
|
||||||
if (folders != null) {
|
|
||||||
let exactTargetFolders = folders.filter(folder => {
|
|
||||||
return _.find(folderExactFilters, filter => filter.value.toLowerCase() === folder.name.toLowerCase())
|
|
||||||
})
|
|
||||||
let fuzzyTargetFolders = folders.filter(folder => {
|
|
||||||
return _.find(folderFilters, filter => startsWith(folder.name.replace(/_/g, ''), filter.value.replace(/_/g, '')))
|
|
||||||
})
|
|
||||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
|
||||||
|
|
||||||
if (targetFolders.length > 0) {
|
|
||||||
articles = articles.filter(article => {
|
|
||||||
return _.findWhere(targetFolders, {key: article.FolderKey})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textFilters.length > 0) {
|
|
||||||
articles = textFilters.reduce((articles, textFilter) => {
|
|
||||||
return articles.filter(article => {
|
|
||||||
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
|
|
||||||
})
|
|
||||||
}, articles)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tagFilters.length > 0) {
|
|
||||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
|
||||||
return articles.filter(article => {
|
|
||||||
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
|
|
||||||
})
|
|
||||||
}, articles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
|
||||||
if (activeArticle == null) activeArticle = articles[0]
|
|
||||||
|
|
||||||
return {
|
|
||||||
articles,
|
|
||||||
activeArticle,
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var Finder = connect(remap)(FinderMain)
|
|
||||||
var store = createStore(reducer)
|
|
||||||
|
|
||||||
function refreshData () {
|
function refreshData () {
|
||||||
let data = dataStore.getData(true)
|
// let data = dataStore.getData(true)
|
||||||
store.dispatch(actions.refreshData(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onfocus = e => {
|
|
||||||
refreshData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render((
|
ReactDOM.render((
|
||||||
|
|||||||
97
browser/finder/ipcClient.js
Normal file
97
browser/finder/ipcClient.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
const nodeIpc = require('node-ipc')
|
||||||
|
const { remote, ipcRenderer } = require('electron')
|
||||||
|
const { app, Menu } = remote
|
||||||
|
const path = require('path')
|
||||||
|
const store = require('./store')
|
||||||
|
|
||||||
|
nodeIpc.config.id = 'finder'
|
||||||
|
nodeIpc.config.retry = 1500
|
||||||
|
nodeIpc.config.silent = true
|
||||||
|
|
||||||
|
function killFinder () {
|
||||||
|
let finderWindow = remote.getCurrentWindow()
|
||||||
|
finderWindow.removeAllListeners()
|
||||||
|
if (global.process.platform === 'darwin') {
|
||||||
|
// Only OSX has another app process.
|
||||||
|
app.quit()
|
||||||
|
} else {
|
||||||
|
finderWindow.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFinder () {
|
||||||
|
let finderWindow = remote.getCurrentWindow()
|
||||||
|
if (global.process.platform === 'darwin') {
|
||||||
|
if (finderWindow.isVisible()) {
|
||||||
|
finderWindow.hide()
|
||||||
|
Menu.sendActionToFirstResponder('hide:')
|
||||||
|
} else {
|
||||||
|
nodeIpc.of.node.emit('request-data')
|
||||||
|
finderWindow.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!finderWindow.isMinimized()) {
|
||||||
|
finderWindow.minimize()
|
||||||
|
} else {
|
||||||
|
nodeIpc.of.node.emit('request-data')
|
||||||
|
finderWindow.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeIpc.connectTo(
|
||||||
|
'node',
|
||||||
|
path.join(app.getPath('userData'), 'boostnote.service'),
|
||||||
|
function () {
|
||||||
|
nodeIpc.of.node.on('error', function (err) {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
nodeIpc.of.node.on('connect', function () {
|
||||||
|
console.log('Conncted successfully')
|
||||||
|
})
|
||||||
|
nodeIpc.of.node.on('disconnect', function () {
|
||||||
|
console.log('disconnected')
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.of.node.on('open-finder', function (payload) {
|
||||||
|
toggleFinder()
|
||||||
|
})
|
||||||
|
ipcRenderer.on('open-finder-from-tray', function () {
|
||||||
|
toggleFinder()
|
||||||
|
})
|
||||||
|
ipcRenderer.on('open-main-from-tray', function () {
|
||||||
|
nodeIpc.of.node.emit('open-main-from-finder')
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('quit-from-tray', function () {
|
||||||
|
nodeIpc.of.node.emit('quit-from-finder')
|
||||||
|
killFinder()
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.of.node.on('throttle-data', function (payload) {
|
||||||
|
console.log('Received data from Main renderer')
|
||||||
|
store.default.dispatch({
|
||||||
|
type: 'THROTTLE_DATA',
|
||||||
|
storages: payload.storages,
|
||||||
|
notes: payload.notes
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.of.node.on('config-renew', function (payload) {
|
||||||
|
console.log('config', payload)
|
||||||
|
store.default.dispatch({
|
||||||
|
type: 'SET_CONFIG',
|
||||||
|
config: payload
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.of.node.on('quit-finder-app', function () {
|
||||||
|
nodeIpc.of.node.emit('quit-finder-app-confirm')
|
||||||
|
killFinder()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const ipc = {}
|
||||||
|
|
||||||
|
module.exports = ipc
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { combineReducers } from 'redux'
|
|
||||||
import { SELECT_ARTICLE, SEARCH_ARTICLE, REFRESH_DATA } from './actions'
|
|
||||||
|
|
||||||
let initialArticles = []
|
|
||||||
let initialFolders = []
|
|
||||||
let initialStatus = {
|
|
||||||
articleKey: null,
|
|
||||||
search: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function status (state = initialStatus, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case SELECT_ARTICLE:
|
|
||||||
state.articleKey = action.data.key
|
|
||||||
return Object.assign({}, state)
|
|
||||||
case SEARCH_ARTICLE:
|
|
||||||
state.search = action.data.input
|
|
||||||
return Object.assign({}, state)
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function articles (state = initialArticles, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case REFRESH_DATA:
|
|
||||||
return action.data.articles
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function folders (state = initialFolders, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case REFRESH_DATA:
|
|
||||||
console.log(action)
|
|
||||||
return action.data.folders
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default combineReducers({
|
|
||||||
status,
|
|
||||||
folders,
|
|
||||||
articles
|
|
||||||
})
|
|
||||||
77
browser/finder/store.js
Normal file
77
browser/finder/store.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { combineReducers, createStore } from 'redux'
|
||||||
|
import { routerReducer } from 'react-router-redux'
|
||||||
|
|
||||||
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
const defaultConfig = {
|
||||||
|
zoom: 1,
|
||||||
|
isSideNavFolded: false,
|
||||||
|
listWidth: 250,
|
||||||
|
hotkey: {
|
||||||
|
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
|
||||||
|
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
theme: 'default',
|
||||||
|
disableDirectWrite: false
|
||||||
|
},
|
||||||
|
editor: {
|
||||||
|
theme: 'xcode',
|
||||||
|
fontSize: '14',
|
||||||
|
fontFamily: 'Monaco, Consolas',
|
||||||
|
indentType: 'space',
|
||||||
|
indentSize: '4',
|
||||||
|
switchPreview: 'BLUR' // Available value: RIGHTCLICK, BLUR
|
||||||
|
},
|
||||||
|
preview: {
|
||||||
|
fontSize: '14',
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
codeBlockTheme: 'xcode',
|
||||||
|
lineNumber: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function storages (state = [], action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'THROTTLE_DATA':
|
||||||
|
state = action.storages
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
function notes (state = [], action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'THROTTLE_DATA':
|
||||||
|
state = action.notes
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
function config (state = defaultConfig, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'INIT_CONFIG':
|
||||||
|
case 'SET_CONFIG':
|
||||||
|
return Object.assign({}, state, action.config)
|
||||||
|
case 'SET_IS_SIDENAV_FOLDED':
|
||||||
|
state.isSideNavFolded = action.isFolded
|
||||||
|
return Object.assign({}, state)
|
||||||
|
case 'SET_ZOOM':
|
||||||
|
state.zoom = action.zoom
|
||||||
|
return Object.assign({}, state)
|
||||||
|
case 'SET_LIST_WIDTH':
|
||||||
|
state.listWidth = action.listWidth
|
||||||
|
return Object.assign({}, state)
|
||||||
|
case 'SET_UI':
|
||||||
|
return Object.assign({}, state, action.config)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
let reducer = combineReducers({
|
||||||
|
storages,
|
||||||
|
notes,
|
||||||
|
config,
|
||||||
|
routing: routerReducer
|
||||||
|
})
|
||||||
|
|
||||||
|
let store = createStore(reducer)
|
||||||
|
|
||||||
|
export default store
|
||||||
@@ -12,6 +12,7 @@ import _ from 'lodash'
|
|||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import modal from 'browser/main/lib/modal'
|
import modal from 'browser/main/lib/modal'
|
||||||
import InitModal from 'browser/main/modals/InitModal'
|
import InitModal from 'browser/main/modals/InitModal'
|
||||||
|
import ipc from 'browser/main/lib/ipc'
|
||||||
|
|
||||||
class Main extends React.Component {
|
class Main extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
.folderList-item
|
.folderList-item
|
||||||
display block
|
display block
|
||||||
width 100%
|
width 100%
|
||||||
height 3 0px
|
height 30px
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
padding 0
|
padding 0
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
position absolute
|
position absolute
|
||||||
right 60px
|
right 60px
|
||||||
height 24px
|
height 24px
|
||||||
width 125px
|
|
||||||
border-width 0 0 0 1px
|
border-width 0 0 0 1px
|
||||||
border-style solid
|
border-style solid
|
||||||
border-color $ui-borderColor
|
border-color $ui-borderColor
|
||||||
|
|||||||
@@ -15,13 +15,17 @@ class StatusBar extends React.Component {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
updateAvailable: false
|
updateReady: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
ipc.on('update-available', function (message) {
|
ipc.on('update-ready', function (message) {
|
||||||
this.setState({updateAvailable: true})
|
this.setState({
|
||||||
|
updateReady: true
|
||||||
|
}, () => {
|
||||||
|
this.updateApp()
|
||||||
|
})
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +38,7 @@ class StatusBar extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
ipc.send('update-app', 'Deal with it.')
|
remote.getCurrentWindow().webContents.send('update-app')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +72,9 @@ class StatusBar extends React.Component {
|
|||||||
styleName='root'
|
styleName='root'
|
||||||
>
|
>
|
||||||
<div styleName='pathname'>{location.pathname + location.search}</div>
|
<div styleName='pathname'>{location.pathname + location.search}</div>
|
||||||
{this.state.updateAvailable
|
{this.state.updateReady
|
||||||
? <button onClick={this.updateApp} styleName='update'>
|
? <button onClick={this.updateApp} styleName='update'>
|
||||||
<i styleName='update-icon' className='fa fa-cloud-download'/> Update is available!
|
<i styleName='update-icon' className='fa fa-cloud-download'/> Update is ready!
|
||||||
</button>
|
</button>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,18 +10,9 @@ import { syncHistoryWithStore } from 'react-router-redux'
|
|||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const ipc = electron.ipcRenderer
|
const ipc = electron.ipcRenderer
|
||||||
const path = require('path')
|
|
||||||
const remote = electron.remote
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
window.addEventListener('keydown', function (e) {
|
|
||||||
if (e.keyCode === 73 && e.metaKey && e.altKey) {
|
|
||||||
remote.getCurrentWindow().toggleDevTools()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
activityRecord.init()
|
activityRecord.init()
|
||||||
|
ipc.send('check-update', 'check-update')
|
||||||
window.addEventListener('online', function () {
|
window.addEventListener('online', function () {
|
||||||
ipc.send('check-update', 'check-update')
|
ipc.send('check-update', 'check-update')
|
||||||
})
|
})
|
||||||
@@ -35,28 +26,6 @@ document.addEventListener('dragover', function (e) {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
})
|
})
|
||||||
|
|
||||||
function notify (title, options) {
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
|
||||||
options.silent = false
|
|
||||||
}
|
|
||||||
console.log(options)
|
|
||||||
return new window.Notification(title, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipc.on('notify', function (e, payload) {
|
|
||||||
notify(payload.title, {
|
|
||||||
body: payload.body
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
ipc.on('copy-finder', function () {
|
|
||||||
activityRecord.emit('FINDER_COPY')
|
|
||||||
})
|
|
||||||
ipc.on('open-finder', function () {
|
|
||||||
activityRecord.emit('FINDER_OPEN')
|
|
||||||
})
|
|
||||||
|
|
||||||
let el = document.getElementById('content')
|
let el = document.getElementById('content')
|
||||||
const history = syncHistoryWithStore(hashHistory, store)
|
const history = syncHistoryWithStore(hashHistory, store)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import _ from 'lodash'
|
|||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { ipcRenderer } = electron
|
const { remote } = electron
|
||||||
|
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
zoom: 1,
|
zoom: 1,
|
||||||
@@ -66,17 +66,13 @@ function set (updates) {
|
|||||||
let newConfig = Object.assign({}, defaultConfig, currentConfig, updates)
|
let newConfig = Object.assign({}, defaultConfig, currentConfig, updates)
|
||||||
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
|
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
|
||||||
_save(newConfig)
|
_save(newConfig)
|
||||||
ipcRenderer.send('CONFIG_RENEW', {
|
|
||||||
|
remote.getCurrentWindow().webContents.send('config-renew', {
|
||||||
config: get(),
|
config: get(),
|
||||||
silent: false
|
silent: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcRenderer.send('CONFIG_RENEW', {
|
|
||||||
config: get(),
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
get,
|
get,
|
||||||
set,
|
set,
|
||||||
|
|||||||
138
browser/main/lib/ipc.js
Normal file
138
browser/main/lib/ipc.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import store from 'browser/main/store'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
|
||||||
|
const nodeIpc = require('node-ipc')
|
||||||
|
const { remote, ipcRenderer } = require('electron')
|
||||||
|
const { app, Menu } = remote
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
nodeIpc.config.id = 'node'
|
||||||
|
nodeIpc.config.retry = 1500
|
||||||
|
nodeIpc.config.silent = true
|
||||||
|
console.log('Initializing IPC Server')
|
||||||
|
|
||||||
|
// TODO: IPC SERVER WILL BE MOVED TO MAIN PROCESS FROM MAIN WINDOW PROCESS(RENDERER)
|
||||||
|
nodeIpc.serve(
|
||||||
|
path.join(app.getPath('userData'), 'boostnote.service'),
|
||||||
|
function () {
|
||||||
|
console.log('IPC Server Started')
|
||||||
|
ipcRenderer.on('open-finder', function () {
|
||||||
|
console.log('Open finder')
|
||||||
|
nodeIpc.server.broadcast('open-finder')
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Quit Sequence
|
||||||
|
1. `quit-app` Main process -> Main window by Electron IPC
|
||||||
|
2. `quit-finder-app` Main window -> Finder window by Node IPC(socket)
|
||||||
|
3. Finder window (and Finder main process: OSX only) killed by remote API
|
||||||
|
4. `quit-finder-app-confirm` Finder window -> Main window by NodeIPC
|
||||||
|
5. `quit-app-confirm` Main window -> Main process by Electron IPC
|
||||||
|
6. Main process discard close preventer and terminate Main window and itself.
|
||||||
|
|
||||||
|
If the platform is a linux without cinnamon, the app will skip 2.-4. because it doesn't launch finder window.
|
||||||
|
`quit-app` will fires directly `quit-app-confirm`.
|
||||||
|
*/
|
||||||
|
ipcRenderer.on('quit-app', function () {
|
||||||
|
// Finder app exists only in the linux with cinnamon.
|
||||||
|
if (global.process.env.platform === 'linux' && global.process.env.DESKTOP_SESSION !== 'cinnamon') {
|
||||||
|
ipcRenderer.send('quit-app-confirm')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let confirmHandler = function () {
|
||||||
|
ipcRenderer.send('quit-app-confirm')
|
||||||
|
}
|
||||||
|
nodeIpc.server.on('quit-finder-app-confirm', confirmHandler)
|
||||||
|
setTimeout(() => {
|
||||||
|
nodeIpc.server.removeListener('quit-finder-app-confirm', confirmHandler)
|
||||||
|
}, 1000)
|
||||||
|
nodeIpc.server.broadcast('quit-finder-app')
|
||||||
|
})
|
||||||
|
|
||||||
|
/** Update Sequence
|
||||||
|
1. `update-ready` Main process -> Main window by Electron IPC
|
||||||
|
2. `update-app` Main window -> Main window by Electron IPC
|
||||||
|
3. `quit-finder-app` Main window -> Finder window by Node IPC
|
||||||
|
4. Finder window (and Finder main process: OSX only) killed by remote API
|
||||||
|
5. `quit-finder-app-confirm` Finder window -> Main window by NodeIPC
|
||||||
|
6. `update-app-confirm` Main window -> Main process by Electron IPC
|
||||||
|
7. Main process discard close preventer and start updating.
|
||||||
|
|
||||||
|
Handlers of 1. and 2. can be found in StatusBar component.
|
||||||
|
*/
|
||||||
|
ipcRenderer.on('update-app', function () {
|
||||||
|
// Linux app doesn't support auto updater
|
||||||
|
if (global.process.env.platform === 'linux') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let confirmHandler = function () {
|
||||||
|
ipcRenderer.send('update-app-confirm')
|
||||||
|
}
|
||||||
|
nodeIpc.server.on('quit-finder-app-confirm', confirmHandler)
|
||||||
|
setTimeout(() => {
|
||||||
|
nodeIpc.server.removeListener('quit-finder-app-confirm', confirmHandler)
|
||||||
|
}, 1000)
|
||||||
|
nodeIpc.server.broadcast('quit-finder-app')
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('update-found', function () {
|
||||||
|
console.log('Update found')
|
||||||
|
})
|
||||||
|
|
||||||
|
let config = ConfigManager.get()
|
||||||
|
nodeIpc.server.broadcast('config-renew', config)
|
||||||
|
ipcRenderer.send('config-renew', {
|
||||||
|
config: config,
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
ipcRenderer.on('config-renew', function (e, data) {
|
||||||
|
nodeIpc.server.broadcast('config-renew', data.config)
|
||||||
|
ipcRenderer.send('config-renew', data)
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.server.on('open-main-from-finder', function () {
|
||||||
|
let mainWindow = remote.getCurrentWindow()
|
||||||
|
console.log('open main from finder')
|
||||||
|
if (mainWindow.isFocused()) {
|
||||||
|
if (global.process.platform === 'darwin') {
|
||||||
|
Menu.sendActionToFirstResponder('hide:')
|
||||||
|
} else {
|
||||||
|
mainWindow.minimize()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (global.process.platform === 'darwin') {
|
||||||
|
mainWindow.show()
|
||||||
|
} else {
|
||||||
|
mainWindow.minimize()
|
||||||
|
mainWindow.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.server.on('quit-from-finder', function () {
|
||||||
|
ipcRenderer.send('quit-app-confirm')
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.server.on('connect', function (socket) {
|
||||||
|
console.log('connected')
|
||||||
|
socket.on('close', function () {
|
||||||
|
console.log('socket dead')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
nodeIpc.server.on('error', function (err) {
|
||||||
|
console.error('Node IPC error', err)
|
||||||
|
})
|
||||||
|
nodeIpc.server.on('request-data', function (data, socket) {
|
||||||
|
let state = store.getState()
|
||||||
|
nodeIpc.server.broadcast('throttle-data', {
|
||||||
|
storages: state.storages,
|
||||||
|
notes: state.notes
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const ipc = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeIpc.server.start()
|
||||||
|
module.exports = ipc
|
||||||
11
browser/main/lib/notify.js
Normal file
11
browser/main/lib/notify.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
function notify (title, options) {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
||||||
|
options.silent = false
|
||||||
|
}
|
||||||
|
return new window.Notification(title, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default notify
|
||||||
@@ -180,7 +180,6 @@ class ConfigTab extends React.Component {
|
|||||||
ref='toggleFinder'
|
ref='toggleFinder'
|
||||||
value={config.hotkey.toggleFinder}
|
value={config.hotkey.toggleFinder}
|
||||||
type='text'
|
type='text'
|
||||||
disabled
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ app.on('ready', function () {
|
|||||||
app.dock.hide()
|
app.dock.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
var template = require('./finder-menu')
|
// var template = require('./finder-menu')
|
||||||
var menu = Menu.buildFromTemplate(template)
|
// var menu = Menu.buildFromTemplate(template)
|
||||||
Menu.setApplicationMenu(menu)
|
// Menu.setApplicationMenu(menu)
|
||||||
|
|
||||||
finderWindow = require('./finder-window')
|
finderWindow = require('./finder-window')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,80 +2,12 @@ const electron = require('electron')
|
|||||||
const BrowserWindow = electron.BrowserWindow
|
const BrowserWindow = electron.BrowserWindow
|
||||||
const Menu = electron.Menu
|
const Menu = electron.Menu
|
||||||
const MenuItem = electron.MenuItem
|
const MenuItem = electron.MenuItem
|
||||||
const app = electron.app
|
|
||||||
const ipcMain = electron.ipcMain
|
|
||||||
const Tray = electron.Tray
|
const Tray = electron.Tray
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const nodeIpc = require('@rokt33r/node-ipc')
|
|
||||||
|
|
||||||
var appQuit = false
|
|
||||||
var isFinderLoaded = false
|
|
||||||
|
|
||||||
nodeIpc.config.id = 'finder'
|
|
||||||
nodeIpc.config.retry = 1500
|
|
||||||
nodeIpc.config.silent = true
|
|
||||||
|
|
||||||
nodeIpc.connectTo(
|
|
||||||
'main',
|
|
||||||
path.join(app.getPath('userData'), 'boost.service'),
|
|
||||||
function () {
|
|
||||||
nodeIpc.of.main.on(
|
|
||||||
'error',
|
|
||||||
function (err) {
|
|
||||||
nodeIpc.log('<< ## err ##'.rainbow, nodeIpc.config.delay)
|
|
||||||
nodeIpc.log(err)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
nodeIpc.of.main.on(
|
|
||||||
'connect',
|
|
||||||
function () {
|
|
||||||
nodeIpc.log('<< ## connected to world ##'.rainbow, nodeIpc.config.delay)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
nodeIpc.of.main.on(
|
|
||||||
'disconnect',
|
|
||||||
function () {
|
|
||||||
nodeIpc.log('<< disconnected from main'.notice)
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
appQuit = true
|
|
||||||
app.quit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
nodeIpc.of.main.on(
|
|
||||||
'message',
|
|
||||||
function (payload) {
|
|
||||||
if (isFinderLoaded) {
|
|
||||||
switch (payload.type) {
|
|
||||||
case 'open-finder':
|
|
||||||
if (finderWindow.isFocused()) {
|
|
||||||
hideFinder()
|
|
||||||
} else {
|
|
||||||
openFinder()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'config-apply': {
|
|
||||||
finderWindow.webContents.send('config-apply', payload.data)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function emit (type, data) {
|
|
||||||
var payload = {
|
|
||||||
type: type,
|
|
||||||
data: data
|
|
||||||
}
|
|
||||||
nodeIpc.of.main.emit('message', payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
width: 640,
|
width: 840,
|
||||||
height: 400,
|
height: 540,
|
||||||
show: false,
|
show: false,
|
||||||
frame: false,
|
frame: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
@@ -107,11 +39,8 @@ finderWindow.on('blur', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
finderWindow.on('close', function (e) {
|
finderWindow.on('close', function (e) {
|
||||||
if (process.platform === 'darwin') {
|
e.preventDefault()
|
||||||
if (appQuit) return true
|
finderWindow.hide()
|
||||||
e.preventDefault()
|
|
||||||
finderWindow.hide()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
var appIcon = new Tray(path.join(__dirname, '../resources/tray-icon.png'))
|
var appIcon = new Tray(path.join(__dirname, '../resources/tray-icon.png'))
|
||||||
@@ -121,19 +50,21 @@ var trayMenu = new Menu()
|
|||||||
trayMenu.append(new MenuItem({
|
trayMenu.append(new MenuItem({
|
||||||
label: 'Open Main window',
|
label: 'Open Main window',
|
||||||
click: function () {
|
click: function () {
|
||||||
emit('show-main-window')
|
finderWindow.webContents.send('open-main-from-tray')
|
||||||
}
|
|
||||||
}))
|
|
||||||
trayMenu.append(new MenuItem({
|
|
||||||
label: 'Open Finder window',
|
|
||||||
click: function () {
|
|
||||||
openFinder()
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
if (process.env.platform !== 'linux' || process.env.DESKTOP_SESSION === 'cinnamon') {
|
||||||
|
trayMenu.append(new MenuItem({
|
||||||
|
label: 'Open Finder window',
|
||||||
|
click: function () {
|
||||||
|
finderWindow.webContents.send('open-finder-from-tray')
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
trayMenu.append(new MenuItem({
|
trayMenu.append(new MenuItem({
|
||||||
label: 'Quit',
|
label: 'Quit',
|
||||||
click: function () {
|
click: function () {
|
||||||
emit('quit-app')
|
finderWindow.webContents.send('quit-from-tray')
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@@ -143,21 +74,6 @@ appIcon.on('click', function (e) {
|
|||||||
appIcon.popUpContextMenu(trayMenu)
|
appIcon.popUpContextMenu(trayMenu)
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.on('copy-finder', function () {
|
|
||||||
emit('copy-finder')
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcMain.on('hide-finder', function () {
|
|
||||||
hideFinder()
|
|
||||||
})
|
|
||||||
|
|
||||||
finderWindow.webContents.on('did-finish-load', function () {
|
|
||||||
isFinderLoaded = true
|
|
||||||
})
|
|
||||||
|
|
||||||
function openFinder () {
|
|
||||||
if (isFinderLoaded) finderWindow.show()
|
|
||||||
}
|
|
||||||
function hideFinder () {
|
function hideFinder () {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
finderWindow.minimize()
|
finderWindow.minimize()
|
||||||
|
|||||||
@@ -28,11 +28,12 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="content"></div>
|
<div id="content"></div>
|
||||||
<script src="../node_modules/ace-builds/src-min/ace.js"></script>
|
<script src="../node_modules/ace-builds/src-min/ace.js"></script>
|
||||||
|
<script src="../node_modules/ace-builds/src-min/ext-themelist.js"></script>
|
||||||
<script src="../resources/katex.min.js"></script>
|
<script src="../resources/katex.min.js"></script>
|
||||||
<script src="../compiled/react.min.js"></script>
|
<script src="../compiled/react.js"></script>
|
||||||
<script src="../compiled/react-dom.min.js"></script>
|
<script src="../compiled/react-dom.js"></script>
|
||||||
<script src="../compiled/redux.min.js"></script>
|
<script src="../compiled/redux.js"></script>
|
||||||
<script src="../compiled/react-redux.min.js"></script>
|
<script src="../compiled/react-redux.js"></script>
|
||||||
<script>
|
<script>
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
electron.webFrame.setZoomLevelLimits(1, 1)
|
||||||
|
|||||||
@@ -3,19 +3,8 @@ const ipc = electron.ipcMain
|
|||||||
const Menu = electron.Menu
|
const Menu = electron.Menu
|
||||||
const globalShortcut = electron.globalShortcut
|
const globalShortcut = electron.globalShortcut
|
||||||
const mainWindow = require('./main-window')
|
const mainWindow = require('./main-window')
|
||||||
// const nodeIpc = require('@rokt33r/node-ipc')
|
|
||||||
|
|
||||||
function emitToFinder (type, data) {
|
|
||||||
var payload = {
|
|
||||||
type: type,
|
|
||||||
data: data
|
|
||||||
}
|
|
||||||
|
|
||||||
// nodeIpc.server.broadcast('message', payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleFinder () {
|
function toggleFinder () {
|
||||||
emitToFinder('open-finder')
|
|
||||||
mainWindow.webContents.send('open-finder', {})
|
mainWindow.webContents.send('open-finder', {})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,11 +22,10 @@ function toggleMain () {
|
|||||||
mainWindow.minimize()
|
mainWindow.minimize()
|
||||||
mainWindow.restore()
|
mainWindow.restore()
|
||||||
}
|
}
|
||||||
mainWindow.webContents.send('top-focus-search')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ipc.on('CONFIG_RENEW', (e, payload) => {
|
ipc.on('config-renew', (e, payload) => {
|
||||||
globalShortcut.unregisterAll()
|
globalShortcut.unregisterAll()
|
||||||
var { config } = payload
|
var { config } = payload
|
||||||
|
|
||||||
|
|||||||
195
lib/main-app.js
195
lib/main-app.js
@@ -12,7 +12,6 @@ const GhReleases = require('electron-gh-releases')
|
|||||||
var mainWindow = null
|
var mainWindow = null
|
||||||
var finderProcess = null
|
var finderProcess = null
|
||||||
var finderWindow = null
|
var finderWindow = null
|
||||||
var update = null
|
|
||||||
|
|
||||||
const appRootPath = path.join(process.execPath, '../..')
|
const appRootPath = path.join(process.execPath, '../..')
|
||||||
const updateDotExePath = path.join(appRootPath, 'Update.exe')
|
const updateDotExePath = path.join(appRootPath, 'Update.exe')
|
||||||
@@ -58,20 +57,19 @@ var handleStartupEvent = function () {
|
|||||||
switch (squirrelCommand) {
|
switch (squirrelCommand) {
|
||||||
case '--squirrel-install':
|
case '--squirrel-install':
|
||||||
spawnUpdate(['--createShortcut', exeName], function (err) {
|
spawnUpdate(['--createShortcut', exeName], function (err) {
|
||||||
quitApp()
|
app.quit()
|
||||||
})
|
})
|
||||||
return true
|
return true
|
||||||
case '--squirrel-updated':
|
case '--squirrel-updated':
|
||||||
quitApp()
|
app.quit()
|
||||||
return true
|
return true
|
||||||
case '--squirrel-uninstall':
|
case '--squirrel-uninstall':
|
||||||
spawnUpdate(['--removeShortcut', exeName], function (err) {
|
spawnUpdate(['--removeShortcut', exeName], function (err) {
|
||||||
quitApp()
|
app.quit()
|
||||||
})
|
})
|
||||||
quitApp()
|
|
||||||
return true
|
return true
|
||||||
case '--squirrel-obsolete':
|
case '--squirrel-obsolete':
|
||||||
quitApp()
|
app.quit()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,24 +90,13 @@ var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory)
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (shouldQuit) {
|
if (shouldQuit) {
|
||||||
quitApp()
|
if (mainWindow != null) mainWindow.removeAllListeners()
|
||||||
|
app.quit()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var appQuit = false
|
|
||||||
|
|
||||||
var version = app.getVersion()
|
var version = app.getVersion()
|
||||||
var versionText = (version == null || version.length === 0) ? 'DEV version' : 'v' + version
|
var versionText = (version == null || version.length === 0) ? 'DEV version' : 'v' + version
|
||||||
var versionNotified = false
|
|
||||||
|
|
||||||
function notify (title, body) {
|
|
||||||
if (mainWindow != null) {
|
|
||||||
mainWindow.webContents.send('notify', {
|
|
||||||
title: title,
|
|
||||||
body: body
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isUpdateReady = false
|
var isUpdateReady = false
|
||||||
|
|
||||||
@@ -129,17 +116,15 @@ function checkUpdate () {
|
|||||||
updater.check((err, status) => {
|
updater.check((err, status) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
var isLatest = err.message === 'There is no newer version.'
|
var isLatest = err.message === 'There is no newer version.'
|
||||||
if (!isLatest && !versionNotified) console.error('Updater error! %s', err.message)
|
if (!isLatest) console.error('Updater error! %s', err.message)
|
||||||
}
|
}
|
||||||
if (!err) {
|
if (!err) {
|
||||||
if (status) {
|
if (status) {
|
||||||
notify('Update is available!', 'Download started.. wait for the update ready.')
|
// Download start
|
||||||
|
mainWindow.webContents.send('update-found', 'Update found!')
|
||||||
updater.download()
|
updater.download()
|
||||||
} else {
|
} else {
|
||||||
if (!versionNotified) {
|
// Latest version
|
||||||
versionNotified = true
|
|
||||||
notify('Latest Build!! ' + versionText, 'Hope you to enjoy our app :D')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -147,167 +132,73 @@ function checkUpdate () {
|
|||||||
|
|
||||||
updater.on('update-downloaded', (info) => {
|
updater.on('update-downloaded', (info) => {
|
||||||
if (mainWindow != null) {
|
if (mainWindow != null) {
|
||||||
notify('Ready to Update!', 'Click update button on Main window.')
|
mainWindow.webContents.send('update-ready', 'Update available!')
|
||||||
mainWindow.webContents.send('update-available', 'Update available!')
|
|
||||||
isUpdateReady = true
|
isUpdateReady = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// nodeIpc.config.id = 'node'
|
|
||||||
// nodeIpc.config.retry = 1500
|
|
||||||
// nodeIpc.config.silent = true
|
|
||||||
|
|
||||||
// nodeIpc.serve(
|
|
||||||
// path.join(app.getPath('userData'), 'boost.service'),
|
|
||||||
// function () {
|
|
||||||
// nodeIpc.server.on(
|
|
||||||
// 'connect',
|
|
||||||
// function (socket) {
|
|
||||||
// socket.on('close', function () {
|
|
||||||
// console.log('socket dead')
|
|
||||||
// if (!appQuit) spawnFinder()
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// nodeIpc.server.on(
|
|
||||||
// 'message',
|
|
||||||
// function (data, socket) {
|
|
||||||
// console.log('>>', data)
|
|
||||||
// handleIpcEvent(data)
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// nodeIpc.server.on(
|
|
||||||
// 'error',
|
|
||||||
// function (err) {
|
|
||||||
// console.log('>>', err)
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
|
|
||||||
function handleIpcEvent (payload) {
|
|
||||||
switch (payload.type) {
|
|
||||||
case 'show-main-window':
|
|
||||||
switch (process.platform) {
|
|
||||||
case 'darwin':
|
|
||||||
mainWindow.show()
|
|
||||||
case 'win32':
|
|
||||||
mainWindow.minimize()
|
|
||||||
mainWindow.restore()
|
|
||||||
case 'linux':
|
|
||||||
// Do nothing
|
|
||||||
// due to bug of `app.focus()` some desktop Env
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'copy-finder':
|
|
||||||
mainWindow.webContents.send('copy-finder')
|
|
||||||
break
|
|
||||||
case 'quit-app':
|
|
||||||
quitApp()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function spawnFinder() {
|
function spawnFinder() {
|
||||||
// if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
// var finderArgv = [path.join(__dirname, 'finder-app.js'), '--finder']
|
var finderArgv = [path.join(__dirname, 'finder-app.js'), '--finder']
|
||||||
// if (_.find(process.argv, a => a === '--hot')) finderArgv.push('--hot')
|
if (_.find(process.argv, a => a === '--hot')) finderArgv.push('--hot')
|
||||||
// finderProcess = ChildProcess
|
finderProcess = ChildProcess
|
||||||
// .execFile(process.execPath, finderArgv)
|
.execFile(process.execPath, finderArgv)
|
||||||
// }
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function quitApp () {
|
|
||||||
appQuit = true
|
|
||||||
if (finderProcess) finderProcess.kill()
|
|
||||||
app.quit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.on('ready', function () {
|
app.on('ready', function () {
|
||||||
app.on('before-quit', function () {
|
|
||||||
appQuit = true
|
|
||||||
if (finderProcess) finderProcess.kill()
|
|
||||||
})
|
|
||||||
|
|
||||||
var template = require('./main-menu')
|
var template = require('./main-menu')
|
||||||
if (process.platform === 'win32') {
|
|
||||||
template.unshift({
|
|
||||||
label: 'Boostnote',
|
|
||||||
submenu: [
|
|
||||||
{
|
|
||||||
label: 'Quit',
|
|
||||||
accelerator: 'Control+Q',
|
|
||||||
click: function (e) {
|
|
||||||
quitApp()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
var menu = Menu.buildFromTemplate(template)
|
var menu = Menu.buildFromTemplate(template)
|
||||||
if (process.platform === 'darwin' || process.platform === 'linux') {
|
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||||
Menu.setApplicationMenu(menu)
|
Menu.setApplicationMenu(menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check update every 24 hours
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
checkUpdate()
|
checkUpdate()
|
||||||
}, 1000 * 60 * 60 * 24)
|
}, 1000 * 60 * 60 * 24)
|
||||||
|
|
||||||
ipc.on('check-update', function (event, msg) {
|
ipc.on('check-update', function (event, msg) {
|
||||||
if (update == null) checkUpdate()
|
checkUpdate()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipc.on('update-app', function (event, msg) {
|
ipc.on('update-app-confirm', function (event, msg) {
|
||||||
if (isUpdateReady) {
|
if (isUpdateReady) {
|
||||||
appQuit = true
|
mainWindow.removeAllListeners()
|
||||||
updater.install()
|
updater.install()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipc.on('quit-app-confirm', function () {
|
||||||
|
mainWindow.removeAllListeners()
|
||||||
|
app.quit()
|
||||||
|
})
|
||||||
|
|
||||||
checkUpdate()
|
checkUpdate()
|
||||||
|
|
||||||
mainWindow = require('./main-window')
|
mainWindow = require('./main-window')
|
||||||
if (process.platform === 'win32' || process.platform === 'linux') {
|
if (process.platform === 'win32' || process.platform === 'linux') {
|
||||||
mainWindow.setMenu(menu)
|
mainWindow.setMenu(menu)
|
||||||
}
|
}
|
||||||
mainWindow.on('close', function (e) {
|
|
||||||
if (appQuit || process.platform != 'darwin') {
|
|
||||||
app.quit()
|
|
||||||
} else {
|
|
||||||
mainWindow.hide()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// switch (process.platform) {
|
|
||||||
// case 'darwin':
|
|
||||||
// spawnFinder()
|
|
||||||
// break
|
|
||||||
// case 'win32':
|
|
||||||
// finderWindow = require('./finder-window')
|
|
||||||
// finderWindow.on('close', function (e) {
|
|
||||||
// if (appQuit) return true
|
|
||||||
// e.preventDefault()
|
|
||||||
// finderWindow.hide()
|
|
||||||
// })
|
|
||||||
// break
|
|
||||||
// case 'linux':
|
|
||||||
// if (process.env.DESKTOP_SESSION === 'cinnamon') {
|
|
||||||
// finderWindow = require('./finder-window')
|
|
||||||
// finderWindow.on('close', function (e) {
|
|
||||||
// if (appQuit) return true
|
|
||||||
// e.preventDefault()
|
|
||||||
// finderWindow.hide()
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// // Do nothing.
|
|
||||||
// }
|
|
||||||
|
|
||||||
// nodeIpc.server.start(function (err) {
|
switch (process.platform) {
|
||||||
// if (err.code === 'EADDRINUSE') {
|
case 'darwin':
|
||||||
// notify('Error occurs!', 'You have to kill other Boostnote processes.')
|
spawnFinder()
|
||||||
// quitApp()
|
break
|
||||||
// }
|
case 'win32':
|
||||||
// })
|
finderWindow = require('./finder-window')
|
||||||
|
finderWindow.on('close', function (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
finderWindow.hide()
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'linux':
|
||||||
|
// Finder is available on cinnamon only.
|
||||||
|
if (process.env.DESKTOP_SESSION === 'cinnamon') {
|
||||||
|
finderWindow = require('./finder-window')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
require('./hotkey')
|
require('./hotkey')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,46 +7,61 @@ const OSX = process.platform === 'darwin'
|
|||||||
// const WIN = process.platform === 'win32'
|
// const WIN = process.platform === 'win32'
|
||||||
const LINUX = process.platform === 'linux'
|
const LINUX = process.platform === 'linux'
|
||||||
|
|
||||||
var boost = {
|
var boost = OSX
|
||||||
label: 'Boostnote',
|
? {
|
||||||
submenu: [
|
label: 'Boostnote',
|
||||||
{
|
submenu: [
|
||||||
label: 'About Boostnote',
|
{
|
||||||
selector: 'orderFrontStandardAboutPanel:'
|
label: 'About Boostnote',
|
||||||
},
|
selector: 'orderFrontStandardAboutPanel:'
|
||||||
{
|
},
|
||||||
type: 'separator'
|
{
|
||||||
},
|
type: 'separator'
|
||||||
{
|
},
|
||||||
label: 'Hide Boostnote',
|
{
|
||||||
accelerator: 'Command+H',
|
label: 'Hide Boostnote',
|
||||||
selector: 'hide:'
|
accelerator: 'Command+H',
|
||||||
},
|
selector: 'hide:'
|
||||||
{
|
},
|
||||||
label: 'Hide Others',
|
{
|
||||||
accelerator: 'Command+Shift+H',
|
label: 'Hide Others',
|
||||||
selector: 'hideOtherApplications:'
|
accelerator: 'Command+Shift+H',
|
||||||
},
|
selector: 'hideOtherApplications:'
|
||||||
{
|
},
|
||||||
label: 'Show All',
|
{
|
||||||
selector: 'unhideAllApplications:'
|
label: 'Show All',
|
||||||
},
|
selector: 'unhideAllApplications:'
|
||||||
{
|
},
|
||||||
type: 'separator'
|
{
|
||||||
},
|
type: 'separator'
|
||||||
{
|
},
|
||||||
label: 'Quit',
|
{
|
||||||
accelerator: 'Command+Q',
|
label: 'Quit',
|
||||||
selector: 'terminate:'
|
accelerator: 'Command+Q',
|
||||||
}
|
click: function () {
|
||||||
]
|
mainWindow.webContents.send('quit-app', {})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
label: 'Boostnote',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
accelerator: 'Command+Q',
|
||||||
|
click: function () {
|
||||||
|
mainWindow.webContents.send('quit-app', {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
var file = {
|
var file = {
|
||||||
label: 'File',
|
label: 'File',
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'New Post',
|
label: 'New Note',
|
||||||
accelerator: OSX ? 'Command + N' : 'Control + N',
|
accelerator: OSX ? 'Command + N' : 'Control + N',
|
||||||
click: function () {
|
click: function () {
|
||||||
mainWindow.webContents.send('top:new-note')
|
mainWindow.webContents.send('top:new-note')
|
||||||
@@ -56,7 +71,7 @@ var file = {
|
|||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Delete Post',
|
label: 'Delete Note',
|
||||||
accelerator: OSX ? 'Control + Backspace' : 'Control + Delete',
|
accelerator: OSX ? 'Control + Backspace' : 'Control + Delete',
|
||||||
click: function () {
|
click: function () {
|
||||||
mainWindow.webContents.send('detail:delete')
|
mainWindow.webContents.send('detail:delete')
|
||||||
@@ -188,5 +203,5 @@ var help = {
|
|||||||
module.exports = process.platform === 'darwin'
|
module.exports = process.platform === 'darwin'
|
||||||
? [boost, file, edit, view, window, help]
|
? [boost, file, edit, view, window, help]
|
||||||
: process.platform === 'win32'
|
: process.platform === 'win32'
|
||||||
? [file, view, help]
|
? [boost, file, view, help]
|
||||||
: [file, view, help]
|
: [file, view, help]
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ mainWindow.webContents.sendInputEvent({
|
|||||||
keyCode: '\u0008'
|
keyCode: '\u0008'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mainWindow.on('close', function (e) {
|
||||||
|
mainWindow.hide()
|
||||||
|
e.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
if (mainWindow == null) return null
|
if (mainWindow == null) return null
|
||||||
mainWindow.show()
|
mainWindow.show()
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
"markdown-it-footnote": "^3.0.0",
|
"markdown-it-footnote": "^3.0.0",
|
||||||
"md5": "^2.0.0",
|
"md5": "^2.0.0",
|
||||||
"moment": "^2.10.3",
|
"moment": "^2.10.3",
|
||||||
|
"node-ipc": "^8.1.0",
|
||||||
"sander": "^0.5.1",
|
"sander": "^0.5.1",
|
||||||
"season": "^5.3.0",
|
"season": "^5.3.0",
|
||||||
"superagent": "^1.2.0",
|
"superagent": "^1.2.0",
|
||||||
@@ -75,6 +76,7 @@
|
|||||||
"nib": "^1.1.0",
|
"nib": "^1.1.0",
|
||||||
"oh-my-cdn": "^0.1.1",
|
"oh-my-cdn": "^0.1.1",
|
||||||
"react-css-modules": "^3.7.6",
|
"react-css-modules": "^3.7.6",
|
||||||
|
"react-input-autosize": "^1.1.0",
|
||||||
"react-router": "^2.4.0",
|
"react-router": "^2.4.0",
|
||||||
"react-router-redux": "^4.0.4",
|
"react-router-redux": "^4.0.4",
|
||||||
"standard": "^6.0.8",
|
"standard": "^6.0.8",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin')
|
|||||||
var config = {
|
var config = {
|
||||||
entry: {
|
entry: {
|
||||||
main: './browser/main/index.js',
|
main: './browser/main/index.js',
|
||||||
// finder: './browser/finder/index.js'
|
finder: './browser/finder/index.js'
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['', '.js', '.jsx', '.styl'],
|
extensions: ['', '.js', '.jsx', '.styl'],
|
||||||
@@ -27,6 +27,7 @@ var config = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
externals: [
|
externals: [
|
||||||
|
'node-ipc',
|
||||||
'electron',
|
'electron',
|
||||||
'md5',
|
'md5',
|
||||||
'superagent',
|
'superagent',
|
||||||
|
|||||||
Reference in New Issue
Block a user