mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fcaaa297a | ||
|
|
7c2d2044a9 | ||
|
|
aa32f59dc6 | ||
|
|
182af99e7c | ||
|
|
5b520a7a81 | ||
|
|
364917c910 | ||
|
|
ca7b9c786a | ||
|
|
15c2363098 | ||
|
|
1a11095121 | ||
|
|
2b384b1d15 | ||
|
|
a1d61edb9c | ||
|
|
96a8687896 | ||
|
|
0448773682 | ||
|
|
57998ba727 | ||
|
|
de83447cb3 | ||
|
|
eba19468d5 | ||
|
|
65c78df671 | ||
|
|
a7096aa89f | ||
|
|
15a50ef452 | ||
|
|
04036e5c87 | ||
|
|
2bbb5ef74e | ||
|
|
91eb7feb3c | ||
|
|
978d77142c | ||
|
|
e9cfb2c4ee | ||
|
|
190b6edfb1 |
@@ -16,8 +16,6 @@ const url = path.resolve(__dirname, '../browser/main/index.html')
|
|||||||
|
|
||||||
mainWindow.loadURL('file://' + url)
|
mainWindow.loadURL('file://' + url)
|
||||||
|
|
||||||
mainWindow.setVisibleOnAllWorkspaces(true)
|
|
||||||
|
|
||||||
mainWindow.webContents.on('new-window', function (e) {
|
mainWindow.webContents.on('new-window', function (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import _ from 'lodash'
|
|||||||
import dataStore from 'boost/dataStore'
|
import dataStore from 'boost/dataStore'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote, clipboard } = electron
|
const { remote, clipboard, ipcRenderer } = electron
|
||||||
|
|
||||||
var hideFinder = remote.getGlobal('hideFinder')
|
var hideFinder = remote.getGlobal('hideFinder')
|
||||||
|
|
||||||
@@ -64,6 +64,7 @@ class FinderMain extends React.Component {
|
|||||||
let { activeArticle } = this.props
|
let { activeArticle } = this.props
|
||||||
clipboard.writeText(activeArticle.content)
|
clipboard.writeText(activeArticle.content)
|
||||||
|
|
||||||
|
ipcRenderer.send('copy-finder')
|
||||||
notify('Saved to Clipboard!', {
|
notify('Saved to Clipboard!', {
|
||||||
body: 'Paste it wherever you want!'
|
body: 'Paste it wherever you want!'
|
||||||
})
|
})
|
||||||
@@ -152,6 +153,14 @@ function buildFilter (key) {
|
|||||||
return {type: TEXT_FILTER, value: key}
|
return {type: TEXT_FILTER, value: key}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isContaining (target, needle) {
|
||||||
|
return target.match(new RegExp(_.escapeRegExp(needle)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function startsWith (target, needle) {
|
||||||
|
return target.match(new RegExp('^' + _.escapeRegExp(needle)))
|
||||||
|
}
|
||||||
|
|
||||||
function remap (state) {
|
function remap (state) {
|
||||||
let { articles, folders, status } = state
|
let { articles, folders, status } = state
|
||||||
|
|
||||||
@@ -168,10 +177,10 @@ function remap (state) {
|
|||||||
let targetFolders
|
let targetFolders
|
||||||
if (folders != null) {
|
if (folders != null) {
|
||||||
let exactTargetFolders = folders.filter(folder => {
|
let exactTargetFolders = folders.filter(folder => {
|
||||||
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
|
return _.find(folderExactFilters, filter => isContaining(folder.name, filter.value))
|
||||||
})
|
})
|
||||||
let fuzzyTargetFolders = folders.filter(folder => {
|
let fuzzyTargetFolders = folders.filter(folder => {
|
||||||
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
|
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
|
||||||
})
|
})
|
||||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
||||||
|
|
||||||
@@ -184,7 +193,7 @@ function remap (state) {
|
|||||||
if (textFilters.length > 0) {
|
if (textFilters.length > 0) {
|
||||||
articles = textFilters.reduce((articles, textFilter) => {
|
articles = textFilters.reduce((articles, textFilter) => {
|
||||||
return articles.filter(article => {
|
return articles.filter(article => {
|
||||||
return article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
|
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
|
||||||
})
|
})
|
||||||
}, articles)
|
}, articles)
|
||||||
}
|
}
|
||||||
@@ -192,7 +201,7 @@ function remap (state) {
|
|||||||
if (tagFilters.length > 0) {
|
if (tagFilters.length > 0) {
|
||||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
articles = tagFilters.reduce((articles, tagFilter) => {
|
||||||
return articles.filter(article => {
|
return articles.filter(article => {
|
||||||
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
|
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
|
||||||
})
|
})
|
||||||
}, articles)
|
}, articles)
|
||||||
}
|
}
|
||||||
@@ -201,7 +210,6 @@ function remap (state) {
|
|||||||
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
||||||
if (activeArticle == null) activeArticle = articles[0]
|
if (activeArticle == null) activeArticle = articles[0]
|
||||||
|
|
||||||
console.log(status.search)
|
|
||||||
return {
|
return {
|
||||||
articles,
|
articles,
|
||||||
activeArticle,
|
activeArticle,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React, { PropTypes} from 'react'
|
import React, { PropTypes} from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { CREATE_MODE, EDIT_MODE, IDLE_MODE, NEW, toggleTutorial } from 'boost/actions'
|
import { EDIT_MODE, IDLE_MODE, toggleTutorial } from 'boost/actions'
|
||||||
// import UserNavigator from './HomePage/UserNavigator'
|
|
||||||
import ArticleNavigator from './HomePage/ArticleNavigator'
|
import ArticleNavigator from './HomePage/ArticleNavigator'
|
||||||
import ArticleTopBar from './HomePage/ArticleTopBar'
|
import ArticleTopBar from './HomePage/ArticleTopBar'
|
||||||
import ArticleList from './HomePage/ArticleList'
|
import ArticleList from './HomePage/ArticleList'
|
||||||
import ArticleDetail from './HomePage/ArticleDetail'
|
import ArticleDetail from './HomePage/ArticleDetail'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { isModalOpen, closeModal } from 'boost/modal'
|
import { isModalOpen, closeModal } from 'boost/modal'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const BrowserWindow = electron.remote.BrowserWindow
|
const BrowserWindow = electron.remote.BrowserWindow
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ class HomePage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (status.mode) {
|
switch (status.mode) {
|
||||||
case CREATE_MODE:
|
|
||||||
case EDIT_MODE:
|
case EDIT_MODE:
|
||||||
if (e.keyCode === 27) {
|
if (e.keyCode === 27) {
|
||||||
detail.handleCancelButtonClick()
|
detail.handleCancelButtonClick()
|
||||||
@@ -65,6 +65,13 @@ class HomePage extends React.Component {
|
|||||||
if ((e.keyCode === 13 && e.metaKey) || (e.keyCode === 83 && e.metaKey)) {
|
if ((e.keyCode === 13 && e.metaKey) || (e.keyCode === 83 && e.metaKey)) {
|
||||||
detail.handleSaveButtonClick()
|
detail.handleSaveButtonClick()
|
||||||
}
|
}
|
||||||
|
if (e.keyCode === 80 && e.metaKey) {
|
||||||
|
detail.handleTogglePreviewButtonClick()
|
||||||
|
}
|
||||||
|
if (e.keyCode === 78 && e.metaKey) {
|
||||||
|
nav.handleNewPostButtonClick()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case IDLE_MODE:
|
case IDLE_MODE:
|
||||||
if (e.keyCode === 69) {
|
if (e.keyCode === 69) {
|
||||||
@@ -99,7 +106,7 @@ class HomePage extends React.Component {
|
|||||||
list.selectNextArticle()
|
list.selectNextArticle()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.keyCode === 65 || e.keyCode === 13 && e.metaKey) {
|
if (e.keyCode === 65 || (e.keyCode === 13 && e.metaKey) || (e.keyCode === 78 && e.metaKey)) {
|
||||||
nav.handleNewPostButtonClick()
|
nav.handleNewPostButtonClick()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
@@ -107,13 +114,14 @@ class HomePage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { dispatch, status, articles, allArticles, activeArticle, folders, tags, filters } = this.props
|
let { dispatch, status, user, articles, allArticles, activeArticle, folders, tags, filters } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='HomePage'>
|
<div className='HomePage'>
|
||||||
<ArticleNavigator
|
<ArticleNavigator
|
||||||
ref='nav'
|
ref='nav'
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
|
user={user}
|
||||||
folders={folders}
|
folders={folders}
|
||||||
status={status}
|
status={status}
|
||||||
allArticles={allArticles}
|
allArticles={allArticles}
|
||||||
@@ -164,8 +172,16 @@ function buildFilter (key) {
|
|||||||
return {type: TEXT_FILTER, value: key}
|
return {type: TEXT_FILTER, value: key}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isContaining (target, needle) {
|
||||||
|
return target.match(new RegExp(_.escapeRegExp(needle)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function startsWith (target, needle) {
|
||||||
|
return target.match(new RegExp('^' + _.escapeRegExp(needle)))
|
||||||
|
}
|
||||||
|
|
||||||
function remap (state) {
|
function remap (state) {
|
||||||
let { folders, articles, status } = state
|
let { user, folders, articles, status } = state
|
||||||
|
|
||||||
if (articles == null) articles = []
|
if (articles == null) articles = []
|
||||||
articles.sort((a, b) => {
|
articles.sort((a, b) => {
|
||||||
@@ -192,10 +208,10 @@ function remap (state) {
|
|||||||
let targetFolders
|
let targetFolders
|
||||||
if (folders != null) {
|
if (folders != null) {
|
||||||
let exactTargetFolders = folders.filter(folder => {
|
let exactTargetFolders = folders.filter(folder => {
|
||||||
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
|
return _.findWhere(folderExactFilters, {value: folder.name})
|
||||||
})
|
})
|
||||||
let fuzzyTargetFolders = folders.filter(folder => {
|
let fuzzyTargetFolders = folders.filter(folder => {
|
||||||
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
|
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
|
||||||
})
|
})
|
||||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
||||||
|
|
||||||
@@ -208,7 +224,7 @@ function remap (state) {
|
|||||||
if (textFilters.length > 0) {
|
if (textFilters.length > 0) {
|
||||||
articles = textFilters.reduce((articles, textFilter) => {
|
articles = textFilters.reduce((articles, textFilter) => {
|
||||||
return articles.filter(article => {
|
return articles.filter(article => {
|
||||||
return article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
|
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
|
||||||
})
|
})
|
||||||
}, articles)
|
}, articles)
|
||||||
}
|
}
|
||||||
@@ -216,7 +232,7 @@ function remap (state) {
|
|||||||
if (tagFilters.length > 0) {
|
if (tagFilters.length > 0) {
|
||||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
articles = tagFilters.reduce((articles, tagFilter) => {
|
||||||
return articles.filter(article => {
|
return articles.filter(article => {
|
||||||
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
|
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
|
||||||
})
|
})
|
||||||
}, articles)
|
}, articles)
|
||||||
}
|
}
|
||||||
@@ -227,6 +243,7 @@ function remap (state) {
|
|||||||
if (activeArticle == null) activeArticle = articles[0]
|
if (activeArticle == null) activeArticle = articles[0]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
user,
|
||||||
folders,
|
folders,
|
||||||
status,
|
status,
|
||||||
allArticles,
|
allArticles,
|
||||||
@@ -242,11 +259,9 @@ function remap (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HomePage.propTypes = {
|
HomePage.propTypes = {
|
||||||
params: PropTypes.shape({
|
status: PropTypes.shape(),
|
||||||
userId: PropTypes.string
|
user: PropTypes.shape({
|
||||||
}),
|
name: PropTypes.string
|
||||||
status: PropTypes.shape({
|
|
||||||
userId: PropTypes.string
|
|
||||||
}),
|
}),
|
||||||
articles: PropTypes.array,
|
articles: PropTypes.array,
|
||||||
allArticles: PropTypes.array,
|
allArticles: PropTypes.array,
|
||||||
@@ -257,7 +272,8 @@ HomePage.propTypes = {
|
|||||||
folder: PropTypes.array,
|
folder: PropTypes.array,
|
||||||
tag: PropTypes.array,
|
tag: PropTypes.array,
|
||||||
text: PropTypes.array
|
text: PropTypes.array
|
||||||
})
|
}),
|
||||||
|
tags: PropTypes.array
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(remap)(HomePage)
|
export default connect(remap)(HomePage)
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ import TagSelect from 'boost/components/TagSelect'
|
|||||||
import ModeSelect from 'boost/components/ModeSelect'
|
import ModeSelect from 'boost/components/ModeSelect'
|
||||||
import activityRecord from 'boost/activityRecord'
|
import activityRecord from 'boost/activityRecord'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const clipboard = electron.clipboard
|
||||||
|
|
||||||
const BRAND_COLOR = '#18AF90'
|
const BRAND_COLOR = '#18AF90'
|
||||||
|
|
||||||
const editDeleteTutorialElement = (
|
const editDeleteTutorialElement = (
|
||||||
@@ -84,6 +87,10 @@ const modeSelectTutorialElement = (
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function notify (...args) {
|
||||||
|
return new window.Notification(...args)
|
||||||
|
}
|
||||||
|
|
||||||
function makeInstantArticle (article) {
|
function makeInstantArticle (article) {
|
||||||
return Object.assign({}, article)
|
return Object.assign({}, article)
|
||||||
}
|
}
|
||||||
@@ -154,6 +161,13 @@ export default class ArticleDetail extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleClipboardButtonClick (e) {
|
||||||
|
clipboard.writeText(this.props.activeArticle.content)
|
||||||
|
notify('Saved to Clipboard!', {
|
||||||
|
body: 'Paste it wherever you want!'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleEditButtonClick (e) {
|
handleEditButtonClick (e) {
|
||||||
let { dispatch } = this.props
|
let { dispatch } = this.props
|
||||||
dispatch(switchMode(EDIT_MODE))
|
dispatch(switchMode(EDIT_MODE))
|
||||||
@@ -185,8 +199,13 @@ export default class ArticleDetail extends React.Component {
|
|||||||
: (
|
: (
|
||||||
<span className='noTags'>Not tagged yet</span>
|
<span className='noTags'>Not tagged yet</span>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
let folder = _.findWhere(folders, {key: activeArticle.FolderKey})
|
let folder = _.findWhere(folders, {key: activeArticle.FolderKey})
|
||||||
|
|
||||||
|
let title = activeArticle.title.trim().length === 0
|
||||||
|
? <small>(Untitled)</small>
|
||||||
|
: activeArticle.title
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='ArticleDetail idle'>
|
<div className='ArticleDetail idle'>
|
||||||
{this.state.openDeleteConfirmMenu
|
{this.state.openDeleteConfirmMenu
|
||||||
@@ -214,6 +233,9 @@ export default class ArticleDetail extends React.Component {
|
|||||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
|
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='right'>
|
<div className='right'>
|
||||||
|
<button onClick={e => this.handleClipboardButtonClick(e)} className='editBtn'>
|
||||||
|
<i className='fa fa-fw fa-clipboard'/><span className='tooltip'>Copy to clipboard</span>
|
||||||
|
</button>
|
||||||
<button onClick={e => this.handleEditButtonClick(e)} className='editBtn'>
|
<button onClick={e => this.handleEditButtonClick(e)} className='editBtn'>
|
||||||
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Edit (e)</span>
|
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Edit (e)</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -232,7 +254,7 @@ export default class ArticleDetail extends React.Component {
|
|||||||
<div className='detailPanel'>
|
<div className='detailPanel'>
|
||||||
<div className='header'>
|
<div className='header'>
|
||||||
<ModeIcon className='mode' mode={activeArticle.mode}/>
|
<ModeIcon className='mode' mode={activeArticle.mode}/>
|
||||||
<div className='title'>{activeArticle.title}</div>
|
<div className='title'>{title}</div>
|
||||||
</div>
|
</div>
|
||||||
{activeArticle.mode === 'markdown'
|
{activeArticle.mode === 'markdown'
|
||||||
? <MarkdownPreview content={activeArticle.content}/>
|
? <MarkdownPreview content={activeArticle.content}/>
|
||||||
@@ -265,8 +287,12 @@ export default class ArticleDetail extends React.Component {
|
|||||||
|
|
||||||
delete newArticle.status
|
delete newArticle.status
|
||||||
newArticle.updatedAt = new Date()
|
newArticle.updatedAt = new Date()
|
||||||
|
newArticle.title = newArticle.title.trim()
|
||||||
if (newArticle.createdAt == null) {
|
if (newArticle.createdAt == null) {
|
||||||
newArticle.createdAt = new Date()
|
newArticle.createdAt = new Date()
|
||||||
|
if (newArticle.title.length === 0) {
|
||||||
|
newArticle.title = `Created at ${moment(newArticle.createdAt).format('YYYY/MM/DD HH:mm')}`
|
||||||
|
}
|
||||||
activityRecord.emit('ARTICLE_CREATE')
|
activityRecord.emit('ARTICLE_CREATE')
|
||||||
} else {
|
} else {
|
||||||
activityRecord.emit('ARTICLE_UPDATE')
|
activityRecord.emit('ARTICLE_UPDATE')
|
||||||
@@ -408,7 +434,9 @@ export default class ArticleDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTogglePreviewButtonClick (e) {
|
handleTogglePreviewButtonClick (e) {
|
||||||
this.setState({previewMode: !this.state.previewMode})
|
if (this.state.article.mode === 'markdown') {
|
||||||
|
this.setState({previewMode: !this.state.previewMode})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTitleKeyDown (e) {
|
handleTitleKeyDown (e) {
|
||||||
@@ -453,18 +481,36 @@ export default class ArticleDetail extends React.Component {
|
|||||||
<div className='right'>
|
<div className='right'>
|
||||||
{
|
{
|
||||||
this.state.article.mode === 'markdown'
|
this.state.article.mode === 'markdown'
|
||||||
? (<button className='preview' onClick={e => this.handleTogglePreviewButtonClick(e)}>{!this.state.previewMode ? 'Preview' : 'Edit'}</button>)
|
? (<button className='preview' onClick={e => this.handleTogglePreviewButtonClick(e)}>
|
||||||
|
{
|
||||||
|
!this.state.previewMode
|
||||||
|
? 'Preview'
|
||||||
|
: 'Edit'
|
||||||
|
}
|
||||||
|
</button>)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
<button onClick={e => this.handleCancelButtonClick(e)}>Cancel</button>
|
<button onClick={e => this.handleCancelButtonClick(e)}>
|
||||||
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>Save</button>
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='detailBody'>
|
<div className='detailBody'>
|
||||||
<div className='detailPanel'>
|
<div className='detailPanel'>
|
||||||
<div className='header'>
|
<div className='header'>
|
||||||
<div className='title'>
|
<div className='title'>
|
||||||
<input onKeyDown={e => this.handleTitleKeyDown(e)} placeholder='Title' ref='title' value={this.state.article.title} onChange={e => this.handleTitleChange(e)}/>
|
<input
|
||||||
|
onKeyDown={e => this.handleTitleKeyDown(e)}
|
||||||
|
placeholder={this.state.article.createdAt == null
|
||||||
|
? `Created at ${moment().format('YYYY/MM/DD HH:mm')}`
|
||||||
|
: 'Title'}
|
||||||
|
ref='title'
|
||||||
|
value={this.state.article.title}
|
||||||
|
onChange={e => this.handleTitleChange(e)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ModeSelect
|
<ModeSelect
|
||||||
ref='mode'
|
ref='mode'
|
||||||
|
|||||||
@@ -80,6 +80,12 @@ export default class ArticleList extends React.Component {
|
|||||||
: (<span>Not tagged yet</span>)
|
: (<span>Not tagged yet</span>)
|
||||||
let folder = _.findWhere(folders, {key: article.FolderKey})
|
let folder = _.findWhere(folders, {key: article.FolderKey})
|
||||||
|
|
||||||
|
let title = article.status !== NEW
|
||||||
|
? article.title.trim().length === 0
|
||||||
|
? <small>(Untitled)</small>
|
||||||
|
: article.title
|
||||||
|
: '(New article)'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={'article-' + article.key}>
|
<div key={'article-' + article.key}>
|
||||||
<div onClick={e => this.handleArticleClick(article)(e)} className={'articleItem' + (activeArticle.key === article.key ? ' active' : '')}>
|
<div onClick={e => this.handleArticleClick(article)(e)} className={'articleItem' + (activeArticle.key === article.key ? ' active' : '')}>
|
||||||
@@ -91,7 +97,7 @@ export default class ArticleList extends React.Component {
|
|||||||
<span className='updatedAt'>{article.status != null ? article.status : moment(article.updatedAt).fromNow()}</span>
|
<span className='updatedAt'>{article.status != null ? article.status : moment(article.updatedAt).fromNow()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='middle'>
|
<div className='middle'>
|
||||||
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{article.status !== NEW ? article.title : '(New article)'}</div>
|
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{title}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='bottom'>
|
<div className='bottom'>
|
||||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tagElements}</div>
|
<div className='tags'><i className='fa fa-fw fa-tags'/>{tagElements}</div>
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
import { findWhere } from 'lodash'
|
import { findWhere } from 'lodash'
|
||||||
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, EDIT_MODE } from 'boost/actions'
|
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, clearNewArticle, EDIT_MODE } from 'boost/actions'
|
||||||
import { openModal } from 'boost/modal'
|
import { openModal } from 'boost/modal'
|
||||||
import FolderMark from 'boost/components/FolderMark'
|
import FolderMark from 'boost/components/FolderMark'
|
||||||
import Preferences from 'boost/components/modal/Preferences'
|
import Preferences from 'boost/components/modal/Preferences'
|
||||||
import CreateNewFolder from 'boost/components/modal/CreateNewFolder'
|
import CreateNewFolder from 'boost/components/modal/CreateNewFolder'
|
||||||
import keygen from 'boost/keygen'
|
import keygen from 'boost/keygen'
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const remote = electron.remote
|
|
||||||
let userName = remote.getGlobal('process').env.USER
|
|
||||||
|
|
||||||
const BRAND_COLOR = '#18AF90'
|
const BRAND_COLOR = '#18AF90'
|
||||||
|
|
||||||
const preferenceTutorialElement = (
|
const preferenceTutorialElement = (
|
||||||
@@ -85,6 +81,7 @@ export default class ArticleNavigator extends React.Component {
|
|||||||
status: 'NEW'
|
status: 'NEW'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatch(clearNewArticle())
|
||||||
dispatch(updateArticle(newArticle))
|
dispatch(updateArticle(newArticle))
|
||||||
dispatch(switchArticle(newArticle.key, true))
|
dispatch(switchArticle(newArticle.key, true))
|
||||||
dispatch(switchMode(EDIT_MODE))
|
dispatch(switchMode(EDIT_MODE))
|
||||||
@@ -108,7 +105,7 @@ export default class ArticleNavigator extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { status, folders, allArticles } = this.props
|
let { status, user, folders, allArticles } = this.props
|
||||||
let { targetFolders } = status
|
let { targetFolders } = status
|
||||||
if (targetFolders == null) targetFolders = []
|
if (targetFolders == null) targetFolders = []
|
||||||
|
|
||||||
@@ -126,7 +123,7 @@ export default class ArticleNavigator extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className='ArticleNavigator'>
|
<div className='ArticleNavigator'>
|
||||||
<div className='userInfo'>
|
<div className='userInfo'>
|
||||||
<div className='userProfileName'>{userName}</div>
|
<div className='userProfileName'>{user.name}</div>
|
||||||
<div className='userName'>localStorage</div>
|
<div className='userName'>localStorage</div>
|
||||||
<button onClick={e => this.handlePreferencesButtonClick(e)} className='settingBtn'>
|
<button onClick={e => this.handlePreferencesButtonClick(e)} className='settingBtn'>
|
||||||
<i className='fa fa-fw fa-chevron-down'/>
|
<i className='fa fa-fw fa-chevron-down'/>
|
||||||
|
|||||||
@@ -35,18 +35,33 @@ export default class ArticleTopBar extends React.Component {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isTooltipHidden: true
|
isTooltipHidden: true,
|
||||||
|
isLinksDropdownOpen: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.searchInput = ReactDOM.findDOMNode(this.refs.searchInput)
|
this.searchInput = ReactDOM.findDOMNode(this.refs.searchInput)
|
||||||
|
this.linksButton = ReactDOM.findDOMNode(this.refs.links)
|
||||||
|
this.showLinksDropdown = e => {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
if (!this.state.isLinksDropdownOpen) {
|
||||||
|
this.setState({isLinksDropdownOpen: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.linksButton.addEventListener('click', this.showLinksDropdown)
|
||||||
|
this.hideLinksDropdown = e => {
|
||||||
|
if (this.state.isLinksDropdownOpen) {
|
||||||
|
this.setState({isLinksDropdownOpen: false})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('click', this.hideLinksDropdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.searchInput.removeEventListener('keydown', this.showTooltip)
|
document.removeEventListener('click', this.hideLinksDropdown)
|
||||||
this.searchInput.removeEventListener('focus', this.showTooltip)
|
this.linksButton.removeEventListener('click', this.showLinksDropdown())
|
||||||
this.searchInput.removeEventListener('blur', this.showTooltip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTooltipRequest (e) {
|
handleTooltipRequest (e) {
|
||||||
@@ -118,8 +133,11 @@ export default class ArticleTopBar extends React.Component {
|
|||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
|
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
|
||||||
- Search by tag : #{'{string}'}<br/>
|
<ul>
|
||||||
- Search by folder : /{'{folder_name}'}
|
<li>- Search by tag : #{'{string}'}</li>
|
||||||
|
<li>- Search by folder : /{'{folder_name}'}</li>
|
||||||
|
<li><small>exact match : //{'{folder_name}'}</small></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -129,10 +147,23 @@ export default class ArticleTopBar extends React.Component {
|
|||||||
<div className='right'>
|
<div className='right'>
|
||||||
<button onClick={e => this.handleTutorialButtonClick(e)}>?<span className='tooltip'>How to use</span>
|
<button onClick={e => this.handleTutorialButtonClick(e)}>?<span className='tooltip'>How to use</span>
|
||||||
</button>
|
</button>
|
||||||
<ExternalLink className='logo' href='http://b00st.io'>
|
<a ref='links' className='linksBtn' href>
|
||||||
<img src='../../resources/favicon-230x230.png' width='44' height='44'/>
|
<img src='../../resources/favicon-230x230.png' width='44' height='44'/>
|
||||||
<span className='tooltip'>Boost official page</span>
|
</a>
|
||||||
</ExternalLink>
|
{
|
||||||
|
this.state.isLinksDropdownOpen
|
||||||
|
? (
|
||||||
|
<div className='links-dropdown'>
|
||||||
|
<ExternalLink className='links-item' href='https://b00st.io'>
|
||||||
|
<i className='fa fa-fw fa-home'/>Boost official page
|
||||||
|
</ExternalLink>
|
||||||
|
<ExternalLink className='links-item' href='https://github.com/BoostIO/boost-app-discussions/issues'>
|
||||||
|
<i className='fa fa-fw fa-bullhorn'/> Discuss
|
||||||
|
</ExternalLink>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{status.isTutorialOpen ? (
|
{status.isTutorialOpen ? (
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ ipc.on('notify', function (e, payload) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipc.on('copy-finder', function () {
|
||||||
|
activityRecord.emit('FINDER_COPY')
|
||||||
|
})
|
||||||
|
ipc.on('open-finder', function () {
|
||||||
|
activityRecord.emit('FINDER_OPEN')
|
||||||
|
})
|
||||||
|
|
||||||
let routes = (
|
let routes = (
|
||||||
<Route path='/' component={MainPage}>
|
<Route path='/' component={MainPage}>
|
||||||
<IndexRoute name='home' component={HomePage}/>
|
<IndexRoute name='home' component={HomePage}/>
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ body
|
|||||||
width 100%
|
width 100%
|
||||||
height 100%
|
height 100%
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
button, input
|
||||||
|
font-family "Lato"
|
||||||
|
|
||||||
.Finder
|
.Finder
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
|
|||||||
@@ -323,7 +323,8 @@ iptFocusBorderColor = #369DCD
|
|||||||
right 15px
|
right 15px
|
||||||
font-size 24px
|
font-size 24px
|
||||||
line-height 60px
|
line-height 60px
|
||||||
|
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
overflow-x auto
|
overflow-x auto
|
||||||
overflow-y hidden
|
overflow-y hidden
|
||||||
|
small
|
||||||
|
color #AAA
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ articleItemColor = #777
|
|||||||
left 19px
|
left 19px
|
||||||
right 0
|
right 0
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
|
small
|
||||||
|
color #AAA
|
||||||
.bottom
|
.bottom
|
||||||
padding 5px 0
|
padding 5px 0
|
||||||
overflow-x auto
|
overflow-x auto
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ articleCount = #999
|
|||||||
.userProfileName
|
.userProfileName
|
||||||
color brandColor
|
color brandColor
|
||||||
font-size 28px
|
font-size 28px
|
||||||
padding 6px 0 0 10px
|
padding 6px 37px 0 10px
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
text-overflow ellipsis
|
text-overflow ellipsis
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|||||||
@@ -62,6 +62,13 @@ infoBtnActiveBgColor = #3A3A3A
|
|||||||
opacity 1
|
opacity 1
|
||||||
&.hide
|
&.hide
|
||||||
opacity 0
|
opacity 0
|
||||||
|
ul
|
||||||
|
li:last-child
|
||||||
|
line-height 10px
|
||||||
|
margin-bottom 3px
|
||||||
|
small
|
||||||
|
font-size 10px
|
||||||
|
margin-left 15px
|
||||||
input
|
input
|
||||||
absolute top left
|
absolute top left
|
||||||
width 350px
|
width 350px
|
||||||
@@ -140,17 +147,33 @@ infoBtnActiveBgColor = #3A3A3A
|
|||||||
.tooltip
|
.tooltip
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
&>.logo
|
&>.linksBtn
|
||||||
display block
|
display block
|
||||||
position absolute
|
position absolute
|
||||||
top 8px
|
top 8px
|
||||||
right 15px
|
right 15px
|
||||||
opacity 0.7
|
opacity 0.7
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
margin-top 44px
|
|
||||||
margin-left -120px
|
|
||||||
&:hover
|
&:hover
|
||||||
opacity 1
|
opacity 1
|
||||||
.tooltip
|
.tooltip
|
||||||
opacity 1
|
opacity 1
|
||||||
|
&>.links-dropdown
|
||||||
|
position fixed
|
||||||
|
z-index 50
|
||||||
|
right 10px
|
||||||
|
top 40px
|
||||||
|
background-color transparentify(invBackgroundColor, 80%)
|
||||||
|
padding 5px 0
|
||||||
|
.links-item
|
||||||
|
padding 0 10px
|
||||||
|
height 33px
|
||||||
|
width 100%
|
||||||
|
display block
|
||||||
|
line-height 33px
|
||||||
|
text-decoration none
|
||||||
|
color white
|
||||||
|
&:hover
|
||||||
|
background-color transparentify(lighten(invBackgroundColor, 30%), 80%)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const app = electron.app
|
|||||||
const Tray = electron.Tray
|
const Tray = electron.Tray
|
||||||
const Menu = electron.Menu
|
const Menu = electron.Menu
|
||||||
const MenuItem = electron.MenuItem
|
const MenuItem = electron.MenuItem
|
||||||
|
const ipcMain = electron.ipcMain
|
||||||
|
|
||||||
process.stdin.setEncoding('utf8')
|
process.stdin.setEncoding('utf8')
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ app.on('ready', function () {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
trayMenu.append(new MenuItem({
|
trayMenu.append(new MenuItem({
|
||||||
label: 'Open Finder',
|
label: 'Open Finder window',
|
||||||
click: function () {
|
click: function () {
|
||||||
finderWindow.show()
|
finderWindow.show()
|
||||||
}
|
}
|
||||||
@@ -63,6 +64,10 @@ app.on('ready', function () {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.on('copy-finder', function () {
|
||||||
|
emit('copy-finder')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
global.hideFinder = function () {
|
global.hideFinder = function () {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// Action types
|
// Action types
|
||||||
|
export const USER_UPDATE = 'USER_UPDATE'
|
||||||
|
|
||||||
export const CLEAR_NEW_ARTICLE = 'CLEAR_NEW_ARTICLE'
|
export const CLEAR_NEW_ARTICLE = 'CLEAR_NEW_ARTICLE'
|
||||||
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
|
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
|
||||||
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
|
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
|
||||||
@@ -24,6 +26,13 @@ export const EDIT_MODE = 'EDIT_MODE'
|
|||||||
// Article status
|
// Article status
|
||||||
export const NEW = 'NEW'
|
export const NEW = 'NEW'
|
||||||
|
|
||||||
|
export function updateUser (input) {
|
||||||
|
return {
|
||||||
|
type: USER_UPDATE,
|
||||||
|
data: input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DB
|
// DB
|
||||||
export function clearNewArticle () {
|
export function clearNewArticle () {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
import linkState from 'boost/linkState'
|
import linkState from 'boost/linkState'
|
||||||
import { createFolder } from 'boost/actions'
|
import { createFolder } from 'boost/actions'
|
||||||
import store from 'boost/store'
|
import store from 'boost/store'
|
||||||
@@ -15,6 +16,10 @@ export default class CreateNewFolder extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
ReactDOM.findDOMNode(this.refs.folderName).focus()
|
||||||
|
}
|
||||||
|
|
||||||
handleCloseButton (e) {
|
handleCloseButton (e) {
|
||||||
this.props.close()
|
this.props.close()
|
||||||
}
|
}
|
||||||
@@ -84,7 +89,7 @@ export default class CreateNewFolder extends React.Component {
|
|||||||
|
|
||||||
<div className='title'>Create new folder</div>
|
<div className='title'>Create new folder</div>
|
||||||
|
|
||||||
<input onKeyDown={e => this.handleKeyDown(e)} className='ipt' type='text' valueLink={this.linkState('name')} placeholder='Enter folder name'/>
|
<input ref='folderName' onKeyDown={e => this.handleKeyDown(e)} className='ipt' type='text' valueLink={this.linkState('name')} placeholder='Enter folder name'/>
|
||||||
<div className='colorSelect'>
|
<div className='colorSelect'>
|
||||||
{colorElements}
|
{colorElements}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
import store from 'boost/store'
|
import store from 'boost/store'
|
||||||
import { unlockStatus, clearNewArticle } from 'boost/actions'
|
import { unlockStatus, clearNewArticle } from 'boost/actions'
|
||||||
|
|
||||||
export default class EditedAlert extends React.Component {
|
export default class EditedAlert extends React.Component {
|
||||||
|
componentDidMount () {
|
||||||
|
ReactDOM.findDOMNode(this.refs.no).focus()
|
||||||
|
}
|
||||||
|
|
||||||
handleNoButtonClick (e) {
|
handleNoButtonClick (e) {
|
||||||
this.props.close()
|
this.props.close()
|
||||||
}
|
}
|
||||||
@@ -22,8 +27,8 @@ export default class EditedAlert extends React.Component {
|
|||||||
<div className='message'>Do you really want to leave without finishing?</div>
|
<div className='message'>Do you really want to leave without finishing?</div>
|
||||||
|
|
||||||
<div className='control'>
|
<div className='control'>
|
||||||
<button onClick={e => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close'/> No</button>
|
<button ref='no' onClick={e => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close'/> No</button>
|
||||||
<button onClick={e => this.handleYesButtonClick(e)} className='primary'><i className='fa fa-fw fa-check'/> Yes</button>
|
<button ref='yes' onClick={e => this.handleYesButtonClick(e)} className='primary'><i className='fa fa-fw fa-check'/> Yes</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
import linkState from 'boost/linkState'
|
import linkState from 'boost/linkState'
|
||||||
|
import { updateUser } from 'boost/actions'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const ipc = electron.ipcRenderer
|
const ipc = electron.ipcRenderer
|
||||||
@@ -9,24 +10,32 @@ export default class AppSettingTab extends React.Component {
|
|||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
let keymap = remote.getGlobal('keymap')
|
let keymap = remote.getGlobal('keymap')
|
||||||
|
let userName = props.user != null ? props.user.name : null
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
toggleFinder: keymap.toggleFinder,
|
user: {
|
||||||
alert: null
|
name: userName,
|
||||||
|
alert: null
|
||||||
|
},
|
||||||
|
userAlert: null,
|
||||||
|
keymap: {
|
||||||
|
toggleFinder: keymap.toggleFinder
|
||||||
|
},
|
||||||
|
keymapAlert: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.handleSettingDone = () => {
|
this.handleSettingDone = () => {
|
||||||
this.setState({alert: {
|
this.setState({keymapAlert: {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: 'Successfully done!'
|
message: 'Successfully done!'
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
this.handleSettingError = err => {
|
this.handleSettingError = err => {
|
||||||
this.setState({alert: {
|
this.setState({keymapAlert: {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: err.message
|
message: err.message != null ? err.message : 'Error occurs!'
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
@@ -40,7 +49,7 @@ export default class AppSettingTab extends React.Component {
|
|||||||
|
|
||||||
submitHotKey () {
|
submitHotKey () {
|
||||||
ipc.send('hotkeyUpdated', {
|
ipc.send('hotkeyUpdated', {
|
||||||
toggleFinder: this.state.toggleFinder
|
toggleFinder: this.state.keymap.toggleFinder
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,25 +63,56 @@ export default class AppSettingTab extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleNameSaveButtonClick (e) {
|
||||||
|
let { dispatch } = this.props
|
||||||
|
|
||||||
|
dispatch(updateUser({name: this.state.user.name}))
|
||||||
|
this.setState({
|
||||||
|
userAlert: {
|
||||||
|
type: 'success',
|
||||||
|
message: 'Successfully done!'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let alert = this.state.alert
|
let keymapAlert = this.state.keymapAlert
|
||||||
let alertElement = alert != null ? (
|
let keymapAlertElement = keymapAlert != null
|
||||||
<p className={`alert ${alert.type}`}>
|
? (
|
||||||
{alert.message}
|
<p className={`alert ${keymapAlert.type}`}>
|
||||||
|
{keymapAlert.message}
|
||||||
|
</p>
|
||||||
|
) : null
|
||||||
|
let userAlert = this.state.userAlert
|
||||||
|
let userAlertElement = userAlert != null
|
||||||
|
? (
|
||||||
|
<p className={`alert ${userAlert.type}`}>
|
||||||
|
{userAlert.message}
|
||||||
</p>
|
</p>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='AppSettingTab content'>
|
<div className='AppSettingTab content'>
|
||||||
|
<div className='section'>
|
||||||
|
<div className='sectionTitle'>User's info</div>
|
||||||
|
<div className='sectionInput'>
|
||||||
|
<label>User name</label>
|
||||||
|
<input valueLink={this.linkState('user.name')} type='text'/>
|
||||||
|
</div>
|
||||||
|
<div className='sectionConfirm'>
|
||||||
|
<button onClick={e => this.handleNameSaveButtonClick(e)}>Save</button>
|
||||||
|
{userAlertElement}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className='section'>
|
<div className='section'>
|
||||||
<div className='sectionTitle'>Hotkey</div>
|
<div className='sectionTitle'>Hotkey</div>
|
||||||
<div className='sectionInput'>
|
<div className='sectionInput'>
|
||||||
<label>Toggle Finder(popup)</label>
|
<label>Toggle Finder(popup)</label>
|
||||||
<input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('toggleFinder')} type='text'/>
|
<input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('keymap.toggleFinder')} type='text'/>
|
||||||
</div>
|
</div>
|
||||||
<div className='sectionConfirm'>
|
<div className='sectionConfirm'>
|
||||||
<button onClick={e => this.handleSaveButtonClick(e)}>Save</button>
|
<button onClick={e => this.handleSaveButtonClick(e)}>Save</button>
|
||||||
{alertElement}
|
{keymapAlertElement}
|
||||||
</div>
|
</div>
|
||||||
<div className='description'>
|
<div className='description'>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -101,3 +141,6 @@ export default class AppSettingTab extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppSettingTab.prototype.linkState = linkState
|
AppSettingTab.prototype.linkState = linkState
|
||||||
|
AppSettingTab.propTypes = {
|
||||||
|
dispatch: PropTypes.func
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class Preferences extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderContent () {
|
renderContent () {
|
||||||
let { folders, dispatch } = this.props
|
let { user, folders, dispatch } = this.props
|
||||||
|
|
||||||
switch (this.state.currentTab) {
|
switch (this.state.currentTab) {
|
||||||
case HELP:
|
case HELP:
|
||||||
@@ -80,12 +80,20 @@ class Preferences extends React.Component {
|
|||||||
)
|
)
|
||||||
case APP:
|
case APP:
|
||||||
default:
|
default:
|
||||||
return (<AppSettingTab/>)
|
return (
|
||||||
|
<AppSettingTab
|
||||||
|
user={user}
|
||||||
|
dispatch={dispatch}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Preferences.propTypes = {
|
Preferences.propTypes = {
|
||||||
|
user: PropTypes.shape({
|
||||||
|
name: PropTypes.string
|
||||||
|
}),
|
||||||
folders: PropTypes.array,
|
folders: PropTypes.array,
|
||||||
dispatch: PropTypes.func
|
dispatch: PropTypes.func
|
||||||
}
|
}
|
||||||
@@ -93,9 +101,10 @@ Preferences.propTypes = {
|
|||||||
Preferences.prototype.linkState = linkState
|
Preferences.prototype.linkState = linkState
|
||||||
|
|
||||||
function remap (state) {
|
function remap (state) {
|
||||||
let { folders, status } = state
|
let { user, folders, status } = state
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
user,
|
||||||
folders,
|
folders,
|
||||||
status
|
status
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import keygen from 'boost/keygen'
|
import keygen from 'boost/keygen'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const remote = electron.remote
|
const remote = electron.remote
|
||||||
const jetpack = require('fs-jetpack')
|
const jetpack = require('fs-jetpack')
|
||||||
@@ -10,15 +12,66 @@ function getLocalPath () {
|
|||||||
return path.join(remote.app.getPath('userData'), 'local.json')
|
return path.join(remote.app.getPath('userData'), 'local.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function forgeInitialRepositories () {
|
||||||
|
return [{
|
||||||
|
key: keygen(),
|
||||||
|
name: 'local',
|
||||||
|
type: 'userData',
|
||||||
|
user: {
|
||||||
|
name: remote.getGlobal('process').env.USER
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRepositories () {
|
||||||
|
let raw = localStorage.getItem('repositories')
|
||||||
|
try {
|
||||||
|
let parsed = JSON.parse(raw)
|
||||||
|
if (!_.isArray(parsed)) {
|
||||||
|
throw new Error('repositories data is currupte. re-init data.')
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
let newRepos = forgeInitialRepositories()
|
||||||
|
saveRepositories(newRepos)
|
||||||
|
return newRepos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveRepositories (repos) {
|
||||||
|
localStorage.setItem('repositories', JSON.stringify(repos))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUser (repoName) {
|
||||||
|
if (repoName == null) {
|
||||||
|
return getRepositories()[0]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveUser (repoName, user) {
|
||||||
|
let repos = getRepositories()
|
||||||
|
if (repoName == null) {
|
||||||
|
Object.assign(repos[0].user, user)
|
||||||
|
}
|
||||||
|
saveRepositories(repos)
|
||||||
|
}
|
||||||
|
|
||||||
export function init () {
|
export function init () {
|
||||||
console.log('initialize data store')
|
// set repositories info
|
||||||
|
getRepositories()
|
||||||
|
|
||||||
|
// set local.json
|
||||||
let data = jetpack.read(getLocalPath(), 'json')
|
let data = jetpack.read(getLocalPath(), 'json')
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
// for 0.4.1 -> 0.4.2
|
||||||
if (localStorage.getItem('local') != null) {
|
if (localStorage.getItem('local') != null) {
|
||||||
data = JSON.parse(localStorage.getItem('local'))
|
data = JSON.parse(localStorage.getItem('local'))
|
||||||
jetpack.write(getLocalPath(), data)
|
jetpack.write(getLocalPath(), data)
|
||||||
localStorage.removeItem('local')
|
localStorage.removeItem('local')
|
||||||
|
console.log('update 0.4.1 => 0.4.2')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +123,8 @@ export default (function () {
|
|||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
getUser,
|
||||||
|
saveUser,
|
||||||
init,
|
init,
|
||||||
getData,
|
getData,
|
||||||
setArticles,
|
setArticles,
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ var crypto = require('crypto')
|
|||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
var shasum = crypto.createHash('sha1')
|
var shasum = crypto.createHash('sha1')
|
||||||
shasum.update(((new Date()).getTime()).toString())
|
shasum.update(((new Date()).getTime() + Math.round(Math.random()*1000)).toString())
|
||||||
return shasum.digest('hex')
|
return shasum.digest('hex')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import {
|
|||||||
UNLOCK_STATUS,
|
UNLOCK_STATUS,
|
||||||
TOGGLE_TUTORIAL,
|
TOGGLE_TUTORIAL,
|
||||||
|
|
||||||
|
// user
|
||||||
|
USER_UPDATE,
|
||||||
|
|
||||||
// Article action type
|
// Article action type
|
||||||
ARTICLE_UPDATE,
|
ARTICLE_UPDATE,
|
||||||
ARTICLE_DESTROY,
|
ARTICLE_DESTROY,
|
||||||
@@ -42,10 +45,22 @@ const initialStatus = {
|
|||||||
let data = dataStore.getData()
|
let data = dataStore.getData()
|
||||||
let initialArticles = data.articles
|
let initialArticles = data.articles
|
||||||
let initialFolders = data.folders
|
let initialFolders = data.folders
|
||||||
|
let initialUser = dataStore.getUser().user
|
||||||
|
|
||||||
let isStatusLocked = false
|
let isStatusLocked = false
|
||||||
let isCreatingNew = false
|
let isCreatingNew = false
|
||||||
|
|
||||||
|
function user (state = initialUser, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case USER_UPDATE:
|
||||||
|
let updated = Object.assign(state, action.data)
|
||||||
|
dataStore.saveUser(null, updated)
|
||||||
|
return updated
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function folders (state = initialFolders, action) {
|
function folders (state = initialFolders, action) {
|
||||||
state = state.slice()
|
state = state.slice()
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@@ -250,6 +265,7 @@ function status (state = initialStatus, action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
|
user,
|
||||||
folders,
|
folders,
|
||||||
articles,
|
articles,
|
||||||
status
|
status
|
||||||
|
|||||||
11
main.js
11
main.js
@@ -66,9 +66,7 @@ app.on('ready', function () {
|
|||||||
if (finderProcess) finderProcess.kill()
|
if (finderProcess) finderProcess.kill()
|
||||||
appQuit = true
|
appQuit = true
|
||||||
})
|
})
|
||||||
console.log('Version ' + version)
|
|
||||||
autoUpdater.setFeedURL('https://orbital.b00st.io/rokt33r/boost-app/latest?version=' + version)
|
autoUpdater.setFeedURL('https://orbital.b00st.io/rokt33r/boost-app/latest?version=' + version)
|
||||||
autoUpdater.checkForUpdates()
|
|
||||||
|
|
||||||
var template = require('./atom-lib/menu-template')
|
var template = require('./atom-lib/menu-template')
|
||||||
var menu = Menu.buildFromTemplate(template)
|
var menu = Menu.buildFromTemplate(template)
|
||||||
@@ -130,9 +128,10 @@ app.on('ready', function () {
|
|||||||
console.log('FINDER(stdout): ' + payload.data)
|
console.log('FINDER(stdout): ' + payload.data)
|
||||||
break
|
break
|
||||||
case 'show-main-window':
|
case 'show-main-window':
|
||||||
if (mainWindow != null) {
|
mainWindow.show()
|
||||||
mainWindow.show()
|
break
|
||||||
}
|
case 'copy-finder':
|
||||||
|
mainWindow.webContents.send('copy-finder')
|
||||||
break
|
break
|
||||||
case 'request-data':
|
case 'request-data':
|
||||||
mainWindow.webContents.send('request-data')
|
mainWindow.webContents.send('request-data')
|
||||||
@@ -175,6 +174,7 @@ app.on('ready', function () {
|
|||||||
try {
|
try {
|
||||||
globalShortcut.register(toggleFinderKey, function () {
|
globalShortcut.register(toggleFinderKey, function () {
|
||||||
emitToFinder('open-finder')
|
emitToFinder('open-finder')
|
||||||
|
mainWindow.webContents.send('open-finder', {})
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err.name)
|
console.log(err.name)
|
||||||
@@ -191,6 +191,7 @@ app.on('ready', function () {
|
|||||||
try {
|
try {
|
||||||
globalShortcut.register(toggleFinderKey, function () {
|
globalShortcut.register(toggleFinderKey, function () {
|
||||||
emitToFinder('open-finder')
|
emitToFinder('open-finder')
|
||||||
|
mainWindow.webContents.send('open-finder', {})
|
||||||
})
|
})
|
||||||
mainWindow.webContents.send('APP_SETTING_DONE', {})
|
mainWindow.webContents.send('APP_SETTING_DONE', {})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"version": "0.4.2",
|
"version": "0.4.4",
|
||||||
"description": "Boost App",
|
"description": "Boost App",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Reference in New Issue
Block a user