1
0
mirror of https://github.com/BoostIo/Boostnote synced 2026-01-04 20:49:19 +00:00

Merge remote-tracking branch 'origin/master' into windows

Conflicts:
	browser/main/HomePage.js
	browser/main/HomePage/ArticleNavigator.js
	webpack.config.js
This commit is contained in:
Dick Choi
2015-12-15 13:06:01 +09:00
51 changed files with 1310 additions and 862 deletions

View File

@@ -1,15 +1,16 @@
import React, { PropTypes} from 'react'
import { connect } from 'react-redux'
import { CREATE_MODE, EDIT_MODE, IDLE_MODE, NEW, toggleTutorial } from 'boost/actions'
// import UserNavigator from './HomePage/UserNavigator'
import { EDIT_MODE, IDLE_MODE, toggleTutorial } from 'boost/actions'
import ArticleNavigator from './HomePage/ArticleNavigator'
import ArticleTopBar from './HomePage/ArticleTopBar'
import ArticleList from './HomePage/ArticleList'
import ArticleDetail from './HomePage/ArticleDetail'
import _ from 'lodash'
import keygen from 'boost/keygen'
import { isModalOpen, closeModal } from 'boost/modal'
const electron = require('electron')
const remote = electron.remote
const TEXT_FILTER = 'TEXT_FILTER'
const FOLDER_FILTER = 'FOLDER_FILTER'
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
@@ -49,7 +50,7 @@ class HomePage extends React.Component {
}
switch (status.mode) {
case CREATE_MODE:
case EDIT_MODE:
if (e.keyCode === 27) {
detail.handleCancelButtonClick()
@@ -57,6 +58,13 @@ class HomePage extends React.Component {
if ((e.keyCode === 13 && (e.metaKey || e.ctrlKey)) || (e.keyCode === 83 && (e.metaKey || e.ctrlKey))) {
detail.handleSaveButtonClick()
}
if (e.keyCode === 80 && e.metaKey) {
detail.handleTogglePreviewButtonClick()
}
if (e.keyCode === 78 && e.metaKey) {
nav.handleNewPostButtonClick()
e.preventDefault()
}
break
case IDLE_MODE:
if (e.keyCode === 69) {
@@ -91,7 +99,7 @@ class HomePage extends React.Component {
list.selectNextArticle()
}
if (e.keyCode === 65 || e.keyCode === 13 && (e.metaKey || e.ctrlKey)) {
if ((e.keyCode === 65 && !e.metaKey && !e.ctrlKey) || (e.keyCode === 13 && e.metaKey) || (e.keyCode === 78 && e.metaKey)) {
nav.handleNewPostButtonClick()
e.preventDefault()
}
@@ -99,13 +107,14 @@ class HomePage extends React.Component {
}
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 (
<div className='HomePage'>
<ArticleNavigator
ref='nav'
dispatch={dispatch}
user={user}
folders={folders}
status={status}
allArticles={allArticles}
@@ -126,6 +135,7 @@ class HomePage extends React.Component {
<ArticleDetail
ref='detail'
dispatch={dispatch}
user={user}
activeArticle={activeArticle}
folders={folders}
status={status}
@@ -156,8 +166,16 @@ function buildFilter (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) {
let { folders, articles, status } = state
let { user, folders, articles, status } = state
if (articles == null) articles = []
articles.sort((a, b) => {
@@ -184,10 +202,10 @@ function remap (state) {
let targetFolders
if (folders != null) {
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 => {
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)
@@ -200,7 +218,7 @@ function remap (state) {
if (textFilters.length > 0) {
articles = textFilters.reduce((articles, textFilter) => {
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)
}
@@ -208,7 +226,7 @@ function remap (state) {
if (tagFilters.length > 0) {
articles = tagFilters.reduce((articles, tagFilter) => {
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)
}
@@ -218,43 +236,8 @@ function remap (state) {
let activeArticle = _.findWhere(articles, {key: status.articleKey})
if (activeArticle == null) activeArticle = articles[0]
// remove Unsaved new article if user is not CREATE_MODE
if (status.mode !== CREATE_MODE) {
let targetIndex = _.findIndex(articles, article => article.status === NEW)
if (targetIndex >= 0) articles.splice(targetIndex, 1)
}
// switching CREATE_MODE
// restrict
// 1. team have one folder at least
// or Change IDLE MODE
if (status.mode === CREATE_MODE) {
let newArticle = _.findWhere(articles, {status: 'NEW'})
console.log('targetFolders')
let FolderKey = targetFolders.length > 0
? targetFolders[0].key
: folders[0].key
if (newArticle == null) {
newArticle = {
id: null,
key: keygen(),
title: '',
content: '',
mode: 'markdown',
tags: [],
FolderKey: FolderKey,
status: NEW
}
articles.unshift(newArticle)
}
activeArticle = newArticle
} else if (status.mode === CREATE_MODE) {
status.mode = IDLE_MODE
}
return {
user,
folders,
status,
allArticles,
@@ -270,11 +253,9 @@ function remap (state) {
}
HomePage.propTypes = {
params: PropTypes.shape({
userId: PropTypes.string
}),
status: PropTypes.shape({
userId: PropTypes.string
status: PropTypes.shape(),
user: PropTypes.shape({
name: PropTypes.string
}),
articles: PropTypes.array,
allArticles: PropTypes.array,
@@ -285,7 +266,8 @@ HomePage.propTypes = {
folder: PropTypes.array,
tag: PropTypes.array,
text: PropTypes.array
})
}),
tags: PropTypes.array
}
export default connect(remap)(HomePage)

View File

@@ -0,0 +1,151 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import api from 'boost/api'
import clientKey from 'boost/clientKey'
import activityRecord from 'boost/activityRecord'
const clipboard = require('electron').clipboard
function getDefault () {
return {
openDropdown: false,
isSharing: false,
// Fetched url
url: null,
// for tooltip Copy -> Copied!
copied: false,
failed: false
}
}
export default class ShareButton extends React.Component {
constructor (props) {
super(props)
this.state = getDefault()
}
componentWillReceiveProps (nextProps) {
this.setState(getDefault())
}
componentDidMount () {
this.dropdownInterceptor = e => {
this.dropdownClicked = true
}
ReactDOM.findDOMNode(this.refs.dropdown).addEventListener('click', this.dropdownInterceptor)
this.shareViaPublicURLHandler = e => {
this.handleShareViaPublicURLClick(e)
}
}
componentWillUnmount () {
document.removeEventListener('click', this.dropdownHandler)
ReactDOM.findDOMNode(this.refs.dropdown).removeEventListener('click', this.dropdownInterceptor)
}
handleOpenButtonClick (e) {
this.openDropdown()
if (this.dropdownHandler == null) {
this.dropdownHandler = e => {
if (!this.dropdownClicked) {
this.closeDropdown()
} else {
this.dropdownClicked = false
}
}
}
document.removeEventListener('click', this.dropdownHandler)
document.addEventListener('click', this.dropdownHandler)
}
openDropdown () {
this.setState({openDropdown: true})
}
closeDropdown () {
document.removeEventListener('click', this.dropdownHandler)
this.setState({openDropdown: false})
}
handleShareViaPublicURLClick (e) {
let { user } = this.props
let input = Object.assign({}, this.props.article, {
clientKey: clientKey.get(),
writerName: user.name
})
this.setState({
isSharing: true,
failed: false
}, () => {
api.shareViaPublicURL(input)
.then(res => {
let url = res.body.url
this.setState({url: url, isSharing: false})
activityRecord.emit('ARTICLE_SHARE')
})
.catch(err => {
console.log(err)
this.setState({isSharing: false, failed: true})
})
})
}
handleCopyURLClick () {
clipboard.writeText(this.state.url)
this.setState({copied: true})
}
// Restore copy url tooltip
handleCopyURLMouseLeave () {
this.setState({copied: false})
}
render () {
let hasPublicURL = this.state.url != null
return (
<div className='ShareButton'>
<button ref='openButton' onClick={e => this.handleOpenButtonClick(e)} className='ShareButton-open-button'>
<i className='fa fa-fw fa-share-alt'/>
{
this.state.openDropdown ? null : (
<span className='tooltip'>Share</span>
)
}
</button>
<div ref='dropdown' className={'share-dropdown' + (this.state.openDropdown ? '' : ' hide')}>
{
!hasPublicURL ? (
<button
onClick={e => this.shareViaPublicURLHandler(e)}
ref='sharePublicURL'
disabled={this.state.isSharing}>
<i className='fa fa-fw fa-external-link'/> {this.state.failed ? 'Failed : Click to Try again' : !this.state.isSharing ? 'Share via public URL' : 'Sharing...'}
</button>
) : (
<div className='ShareButton-url'>
<input className='ShareButton-url-input' value={this.state.url} readOnly/>
<button
onClick={e => this.handleCopyURLClick(e)}
className='ShareButton-url-button'
onMouseLeave={e => this.handleCopyURLMouseLeave(e)}
>
<i className='fa fa-fw fa-clipboard'/>
<div className='ShareButton-url-button-tooltip'>{this.state.copied ? 'Copied!' : 'Copy URL'}</div>
</button>
<div className='ShareButton-url-alert'>This url is valid for 7 days.</div>
</div>
)
}
</div>
</div>
)
}
}
ShareButton.propTypes = {
article: PropTypes.shape({
publicURL: PropTypes.string
}),
user: PropTypes.shape({
name: PropTypes.string
})
}

View File

@@ -7,7 +7,6 @@ import MarkdownPreview from 'boost/components/MarkdownPreview'
import CodeEditor from 'boost/components/CodeEditor'
import {
IDLE_MODE,
CREATE_MODE,
EDIT_MODE,
switchMode,
switchArticle,
@@ -25,6 +24,11 @@ import TagLink from 'boost/components/TagLink'
import TagSelect from 'boost/components/TagSelect'
import ModeSelect from 'boost/components/ModeSelect'
import activityRecord from 'boost/activityRecord'
import api from 'boost/api'
import ShareButton from './ShareButton'
const electron = require('electron')
const clipboard = electron.clipboard
const BRAND_COLOR = '#18AF90'
@@ -85,6 +89,10 @@ const modeSelectTutorialElement = (
</svg>
)
function notify (...args) {
return new window.Notification(...args)
}
function makeInstantArticle (article) {
return Object.assign({}, article)
}
@@ -100,12 +108,16 @@ export default class ArticleDetail extends React.Component {
isTagChanged: false,
isTitleChanged: false,
isContentChanged: false,
isModeChanged: false
isModeChanged: false,
openShareDropdown: false
}
}
componentDidMount () {
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
this.shareDropdownInterceptor = e => {
e.stopPropagation()
}
}
componentWillUnmount () {
@@ -114,7 +126,7 @@ export default class ArticleDetail extends React.Component {
componentDidUpdate (prevProps) {
let isModeChanged = prevProps.status.mode !== this.props.status.mode
if (isModeChanged && this.props.status.mode !== IDLE_MODE) {
if (isModeChanged && this.props.status.mode === EDIT_MODE) {
ReactDOM.findDOMNode(this.refs.title).focus()
}
}
@@ -124,6 +136,7 @@ export default class ArticleDetail extends React.Component {
let isArticleChanged = nextProps.activeArticle != null && (nextProps.activeArticle.key !== this.state.article.key)
let isModeChanged = nextProps.status.mode !== this.props.status.mode
// Reset article input
if (isArticleChanged || (isModeChanged && nextProps.status.mode !== IDLE_MODE)) {
Object.assign(nextState, {
@@ -154,6 +167,14 @@ export default class ArticleDetail extends React.Component {
)
}
handleClipboardButtonClick (e) {
activityRecord.emit('MAIN_DETAIL_COPY')
clipboard.writeText(this.props.activeArticle.content)
notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!'
})
}
handleEditButtonClick (e) {
let { dispatch } = this.props
dispatch(switchMode(EDIT_MODE))
@@ -176,7 +197,7 @@ export default class ArticleDetail extends React.Component {
}
renderIdle () {
let { status, activeArticle, folders } = this.props
let { status, activeArticle, folders, user } = this.props
let tags = activeArticle.tags != null ? activeArticle.tags.length > 0
? activeArticle.tags.map(tag => {
@@ -185,8 +206,13 @@ export default class ArticleDetail extends React.Component {
: (
<span className='noTags'>Not tagged yet</span>
) : null
let folder = _.findWhere(folders, {key: activeArticle.FolderKey})
let title = activeArticle.title.trim().length === 0
? <small>(Untitled)</small>
: activeArticle.title
return (
<div className='ArticleDetail idle'>
{this.state.openDeleteConfirmMenu
@@ -214,6 +240,15 @@ export default class ArticleDetail extends React.Component {
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
</div>
<div className='right'>
<ShareButton
article={activeArticle}
user={user}
/>
<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'>
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Edit (e)</span>
</button>
@@ -232,7 +267,7 @@ export default class ArticleDetail extends React.Component {
<div className='detailPanel'>
<div className='header'>
<ModeIcon className='mode' mode={activeArticle.mode}/>
<div className='title'>{activeArticle.title}</div>
<div className='title'>{title}</div>
</div>
{activeArticle.mode === 'markdown'
? <MarkdownPreview content={activeArticle.content}/>
@@ -247,13 +282,14 @@ export default class ArticleDetail extends React.Component {
handleCancelButtonClick (e) {
let { activeArticle, dispatch } = this.props
dispatch(unlockStatus())
if (activeArticle.status === NEW) dispatch(switchArticle(null))
if (activeArticle.status === NEW) {
dispatch(switchArticle(null))
}
dispatch(switchMode(IDLE_MODE))
}
handleSaveButtonClick (e) {
let { dispatch, folders, filters } = this.props
let { dispatch, folders, status } = this.props
let article = this.state.article
let newArticle = Object.assign({}, article)
@@ -262,13 +298,17 @@ export default class ArticleDetail extends React.Component {
dispatch(unlockStatus())
delete newArticle.status
newArticle.status = null
newArticle.updatedAt = new Date()
newArticle.title = newArticle.title.trim()
if (newArticle.createdAt == null) {
newArticle.createdAt = new Date()
activityRecord.emit('ARTICLE_CREATE')
if (newArticle.title.length === 0) {
newArticle.title = `Created at ${moment(newArticle.createdAt).format('YYYY/MM/DD HH:mm')}`
}
activityRecord.emit('ARTICLE_CREATE', {mode: newArticle.mode})
} else {
activityRecord.emit('ARTICLE_UPDATE')
activityRecord.emit('ARTICLE_UPDATE', {mode: newArticle.mode})
}
dispatch(updateArticle(newArticle))
@@ -277,7 +317,7 @@ export default class ArticleDetail extends React.Component {
// Searchを初期化し、更新先のFolder filterをかける
// かかれていない時に
// Searchを初期化する
if (filters.folder.length !== 0) dispatch(switchFolder(folder.name))
if (status.targetFolders.length > 0) dispatch(switchFolder(folder.name))
else dispatch(clearSearch())
dispatch(switchArticle(newArticle.key))
}
@@ -319,8 +359,6 @@ export default class ArticleDetail extends React.Component {
let article = this.state.article
article.tags = tags
this.setState({article: article})
let _isTagChanged = _.difference(article.tags, this.props.activeArticle.tags).length > 0 || _.difference(this.props.activeArticle.tags, article.tags).length > 0
let { isTitleChanged, isContentChanged, isArticleEdited, isModeChanged } = this.state
@@ -378,6 +416,11 @@ export default class ArticleDetail extends React.Component {
}
handleContentChange (e, value) {
let { status } = this.props
if (status.mode === IDLE_MODE) {
return
}
let { article } = this.state
article.content = value
let _isContentChanged = article.content !== this.props.activeArticle.content
@@ -404,7 +447,36 @@ export default class ArticleDetail extends React.Component {
}
handleTogglePreviewButtonClick (e) {
this.setState({previewMode: !this.state.previewMode})
if (this.state.article.mode === 'markdown') {
if (!this.state.previewMode) {
let cursorPosition = this.refs.code.getCursorPosition()
let firstVisibleRow = this.refs.code.getFirstVisibleRow()
this.setState({
previewMode: true,
cursorPosition,
firstVisibleRow
}, function () {
let previewEl = ReactDOM.findDOMNode(this.refs.preview)
let anchors = previewEl.querySelectorAll('.lineAnchor')
for (let i = 0; i < anchors.length; i++) {
if (parseInt(anchors[i].dataset.key, 10) > cursorPosition.row || i === anchors.length - 1) {
var targetAnchor = anchors[i > 0 ? i - 1 : 0]
previewEl.scrollTop = targetAnchor.offsetTop - 100
break
}
}
})
} else {
this.setState({
previewMode: false
}, function () {
console.log(this.state.cursorPosition)
this.refs.code.moveCursorTo(this.state.cursorPosition.row, this.state.cursorPosition.column)
this.refs.code.scrollToLine(this.state.firstVisibleRow)
this.refs.code.editor.focus()
})
}
}
}
handleTitleKeyDown (e) {
@@ -449,18 +521,36 @@ export default class ArticleDetail extends React.Component {
<div className='right'>
{
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
}
<button onClick={e => this.handleCancelButtonClick(e)}>Cancel</button>
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>Save</button>
<button onClick={e => this.handleCancelButtonClick(e)}>
Cancel
</button>
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>
Save
</button>
</div>
</div>
<div className='detailBody'>
<div className='detailPanel'>
<div className='header'>
<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>
<ModeSelect
ref='mode'
@@ -474,7 +564,7 @@ export default class ArticleDetail extends React.Component {
</div>
{this.state.previewMode
? <MarkdownPreview content={this.state.article.content}/>
? <MarkdownPreview ref='preview' content={this.state.article.content}/>
: (<CodeEditor
ref='code'
onChange={(e, value) => this.handleContentChange(e, value)}
@@ -495,7 +585,6 @@ export default class ArticleDetail extends React.Component {
if (activeArticle == null) return this.renderEmpty()
switch (status.mode) {
case CREATE_MODE:
case EDIT_MODE:
return this.renderEdit()
case IDLE_MODE:
@@ -509,7 +598,8 @@ export default class ArticleDetail extends React.Component {
ArticleDetail.propTypes = {
status: PropTypes.shape(),
activeArticle: PropTypes.shape(),
activeUser: PropTypes.shape(),
user: PropTypes.shape(),
folders: PropTypes.array,
dispatch: PropTypes.func
}
ArticleDetail.prototype.linkState = linkState

View File

@@ -80,6 +80,12 @@ export default class ArticleList extends React.Component {
: (<span>Not tagged yet</span>)
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 (
<div key={'article-' + article.key}>
<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>
</div>
<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 className='bottom'>
<div className='tags'><i className='fa fa-fw fa-tags'/>{tagElements}</div>

View File

@@ -1,16 +1,11 @@
import React, { PropTypes } from 'react'
import { findWhere } from 'lodash'
import { setSearchFilter, switchFolder, switchMode, CREATE_MODE } from 'boost/actions'
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, clearNewArticle, EDIT_MODE } from 'boost/actions'
import { openModal } from 'boost/modal'
import FolderMark from 'boost/components/FolderMark'
import Preferences from 'boost/components/modal/Preferences'
import CreateNewFolder from 'boost/components/modal/CreateNewFolder'
import remote from 'remote'
let mainEnv = remote.getGlobal('process').env
let userName = mainEnv.USER != null
? mainEnv.USER
: mainEnv.USERNAME
import keygen from 'boost/keygen'
const BRAND_COLOR = '#18AF90'
@@ -68,9 +63,28 @@ export default class ArticleNavigator extends React.Component {
}
handleNewPostButtonClick (e) {
let { dispatch } = this.props
let { dispatch, folders, status } = this.props
let { targetFolders } = status
dispatch(switchMode(CREATE_MODE))
let FolderKey = targetFolders.length > 0
? targetFolders[0].key
: folders[0].key
let newArticle = {
id: null,
key: keygen(),
title: '',
content: '',
mode: 'markdown',
tags: [],
FolderKey: FolderKey,
status: 'NEW'
}
dispatch(clearNewArticle())
dispatch(updateArticle(newArticle))
dispatch(switchArticle(newArticle.key, true))
dispatch(switchMode(EDIT_MODE))
}
handleNewFolderButton (e) {
@@ -91,13 +105,13 @@ export default class ArticleNavigator extends React.Component {
}
render () {
let { status, folders, allArticles } = this.props
let { status, user, folders, allArticles } = this.props
let { targetFolders } = status
if (targetFolders == null) targetFolders = []
let folderElememts = folders.map((folder, index) => {
let isActive = findWhere(targetFolders, {key: folder.key})
let articleCount = allArticles.filter(article => article.FolderKey === folder.key).length
let articleCount = allArticles.filter(article => article.FolderKey === folder.key && article.status !== 'NEW').length
return (
<button onClick={e => this.handleFolderButtonClick(folder.name)(e)} key={'folder-' + folder.key} className={isActive ? 'active' : ''}>
@@ -109,7 +123,7 @@ export default class ArticleNavigator extends React.Component {
return (
<div className='ArticleNavigator'>
<div className='userInfo'>
<div className='userProfileName'>{userName}</div>
<div className='userProfileName'>{user.name}</div>
<div className='userName'>localStorage</div>
<button onClick={e => this.handlePreferencesButtonClick(e)} className='settingBtn'>
<i className='fa fa-fw fa-chevron-down'/>

View File

@@ -35,18 +35,33 @@ export default class ArticleTopBar extends React.Component {
super(props)
this.state = {
isTooltipHidden: true
isTooltipHidden: true,
isLinksDropdownOpen: false
}
}
componentDidMount () {
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 () {
this.searchInput.removeEventListener('keydown', this.showTooltip)
this.searchInput.removeEventListener('focus', this.showTooltip)
this.searchInput.removeEventListener('blur', this.showTooltip)
document.removeEventListener('click', this.hideLinksDropdown)
this.linksButton.removeEventListener('click', this.showLinksDropdown())
}
handleTooltipRequest (e) {
@@ -118,8 +133,11 @@ export default class ArticleTopBar extends React.Component {
: null
}
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
- Search by tag : #{'{string}'}<br/>
- Search by folder : /{'{folder_name}'}
<ul>
<li>- Search by tag : #{'{string}'}</li>
<li>- Search by folder : /{'{folder_name}'}</li>
<li><small>exact match : //{'{folder_name}'}</small></li>
</ul>
</div>
</div>
@@ -129,10 +147,23 @@ export default class ArticleTopBar extends React.Component {
<div className='right'>
<button onClick={e => this.handleTutorialButtonClick(e)}>?<span className='tooltip'>How to use</span>
</button>
<ExternalLink className='logo' href='http://b00st.io'>
<a ref='links' className='linksBtn' href>
<img src='../../resources/favicon-230x230.png' width='44' height='44'/>
<span className='tooltip'>Boost official page</span>
</ExternalLink>
</a>
{
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>
{status.isTutorialOpen ? (

View File

@@ -1,8 +1,7 @@
import ipc from 'ipc'
const electron = require('electron')
const ipc = electron.ipcRenderer
import React, { PropTypes } from 'react'
var ContactModal = require('boost/components/modal/ContactModal')
export default class MainContainer extends React.Component {
constructor (props) {
super(props)
@@ -19,20 +18,12 @@ export default class MainContainer extends React.Component {
ipc.send('update-app', 'Deal with it.')
}
openContactModal () {
this.openModal(ContactModal)
}
render () {
return (
<div className='Main'>
{this.state.updateAvailable ? (
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
) : null}
{/* <button onClick={this.openContactModal} className='contactButton'>
<i className='fa fa-paper-plane-o'/>
<div className='tooltip'>Contact us</div>
</button> */}
{this.props.children}
</div>
)

View File

@@ -6,6 +6,7 @@
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
<link rel="stylesheet" href="../../node_modules/highlight.js/styles/xcode.css">
<link rel="shortcut icon" href="favicon.ico">
<style>
@@ -53,8 +54,9 @@
<script src="../../submodules/ace/src-min/ace.js"></script>
<script type='text/javascript'>
require('web-frame').setZoomLevelLimits(1, 1)
var version = require('remote').require('app').getVersion()
const electron = require('electron')
electron.webFrame.setZoomLevelLimits(1, 1)
var version = electron.remote.app.getVersion()
document.title = 'Boost' + ((version == null || version.length === 0) ? ' DEV' : '')
var scriptUrl = process.env.BOOST_ENV === 'development'
? 'http://localhost:8080/assets/main.js'

View File

@@ -11,13 +11,31 @@ require('../styles/main/index.styl')
import { openModal } from 'boost/modal'
import Tutorial from 'boost/components/modal/Tutorial'
import activityRecord from 'boost/activityRecord'
import ipc from 'ipc'
const electron = require('electron')
const ipc = electron.ipcRenderer
activityRecord.init()
window.addEventListener('online', function () {
ipc.send('check-update', 'check-update')
})
function notify (...args) {
return new window.Notification(...args)
}
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 routes = (
<Route path='/' component={MainPage}>
<IndexRoute name='home' component={HomePage}/>