1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00
This commit is contained in:
Rokt33r
2015-12-28 16:11:42 +09:00
parent f9539ab50a
commit e4d8438801
5 changed files with 125 additions and 34 deletions

View File

@@ -1,7 +1,35 @@
import React, { PropTypes } from 'react' import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import ExternalLink from 'browser/components/ExternalLink' import ExternalLink from 'browser/components/ExternalLink'
import { setSearchFilter, clearSearch, toggleOnlyUnsavedFilter, toggleTutorial } from '../actions' import { setSearchFilter, clearSearch, toggleOnlyUnsavedFilter, toggleTutorial, saveAllArticles, switchArticle } from '../actions'
import store from '../store'
const electron = require('electron')
const remote = electron.remote
const Menu = remote.Menu
const MenuItem = remote.MenuItem
var menu = new Menu()
var lastIndex = -1
menu.append(new MenuItem({
label: 'Show only unsaved',
click: function () {
store.dispatch(setSearchFilter('--unsaved'))
}
}))
menu.append(new MenuItem({
label: 'Go to an unsaved article',
click: function () {
lastIndex++
let state = store.getState()
let modified = state.articles.modified
if (modified.length === 0) return
if (modified.length <= lastIndex) {
lastIndex = 0
}
store.dispatch(switchArticle(modified[lastIndex].key))
}
}))
const BRAND_COLOR = '#18AF90' const BRAND_COLOR = '#18AF90'
@@ -111,19 +139,28 @@ export default class ArticleTopBar extends React.Component {
dispatch(toggleOnlyUnsavedFilter()) dispatch(toggleOnlyUnsavedFilter())
} }
handleSaveAllButtonClick (e) {
let { dispatch } = this.props
dispatch(saveAllArticles())
}
handleSaveMenuButtonClick (e) {
menu.popup(590, 45)
}
handleTutorialButtonClick (e) { handleTutorialButtonClick (e) {
let { dispatch } = this.props let { dispatch } = this.props
console.log(e.target.value)
dispatch(toggleTutorial()) dispatch(toggleTutorial())
} }
render () { render () {
let { status } = this.props let { status, modified } = this.props
return ( return (
<div className='ArticleTopBar'> <div className='ArticleTopBar'>
<div className='left'> <div className='ArticleTopBar-left'>
<div className='search'> <div className='ArticleTopBar-left-search'>
<i className='fa fa-search fa-fw' /> <i className='fa fa-search fa-fw' />
<input <input
ref='searchInput' ref='searchInput'
@@ -142,16 +179,25 @@ export default class ArticleTopBar extends React.Component {
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}> <div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
<ul> <ul>
<li>- Search by tag : #{'{string}'}</li> <li>- Search by tag : #{'{string}'}</li>
<li>- Search by folder : /{'{folder_name}'}</li> <li>- Search by folder : /{'{folder_name}'}<br/><small>exact match : //{'{folder_name}'}</small></li>
<li><small>exact match : //{'{folder_name}'}</small></li> <li>- Only unsaved : --unsaved</li>
</ul> </ul>
</div> </div>
</div> </div>
{status.isTutorialOpen ? searchTutorialElement : null} {status.isTutorialOpen ? searchTutorialElement : null}
<label className='only-unsaved'><input value={status.onlyUnsaved} onChange={e => this.handleOnlyUnsavedChange(e)} type='checkbox'/> only unsaved</label>
<div className={'ArticleTopBar-left-unsaved'}>
<button onClick={e => this.handleSaveAllButtonClick(e)} className='ArticleTopBar-left-unsaved-save-button' disabled={modified.length === 0}>
<i className='fa fa-save'/>
<span className={'ArticleTopBar-left-unsaved-save-button-count' + (modified.length === 0 ? ' hide' : '')} children={modified.length}/>
<span className='ArticleTopBar-left-unsaved-save-button-tooltip' children={`Save all ${modified.length} articles (⌘ + Shift + s)`}></span>
</button>
<button onClick={e => this.handleSaveMenuButtonClick(e)} className='ArticleTopBar-left-unsaved-menu-button'><i className='fa fa-angle-down'/></button>
</div>
</div> </div>
<div className='right'>
<div className='ArticleTopBar-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>
<a ref='links' className='linksBtn' href> <a ref='links' className='linksBtn' href>
@@ -198,5 +244,6 @@ ArticleTopBar.propTypes = {
dispatch: PropTypes.func, dispatch: PropTypes.func,
status: PropTypes.shape({ status: PropTypes.shape({
search: PropTypes.string search: PropTypes.string
}) }),
modified: PropTypes.array
} }

View File

@@ -84,6 +84,7 @@ class HomePage extends React.Component {
ref='top' ref='top'
dispatch={dispatch} dispatch={dispatch}
status={status} status={status}
modified={modified}
/> />
<ArticleList <ArticleList
ref='list' ref='list'
@@ -112,7 +113,7 @@ class HomePage extends React.Component {
// Ignore invalid key // Ignore invalid key
function ignoreInvalidKey (key) { function ignoreInvalidKey (key) {
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/) return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/) && !key.match(/^--/)
} }
// Build filter object by key // Build filter object by key
@@ -144,10 +145,6 @@ function remap (state) {
let articles = _articles != null ? _articles.data : [] let articles = _articles != null ? _articles.data : []
let modified = _articles != null ? _articles.modified : [] let modified = _articles != null ? _articles.modified : []
if (state.status.onlyUnsaved) {
articles = modified.map(modifiedArticle => _.findWhere(articles, {key: modifiedArticle.key}))
}
articles.sort((a, b) => { articles.sort((a, b) => {
return new Date(b.updatedAt) - new Date(a.updatedAt) return new Date(b.updatedAt) - new Date(a.updatedAt)
}) })
@@ -158,6 +155,7 @@ function remap (state) {
return sum.concat(article.tags) return sum.concat(article.tags)
}, [])) }, []))
if (status.search.split(' ').some(key => key === '--unsaved')) articles = articles.filter(article => _.findWhere(modified, {key: article.key}))
// Filter articles // Filter articles
let filters = status.search.split(' ') let filters = status.search.split(' ')
.map(key => key.trim()) .map(key => key.trim())

View File

@@ -5,6 +5,7 @@ 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'
export const ARTICLE_SAVE = 'ARTICLE_SAVE' export const ARTICLE_SAVE = 'ARTICLE_SAVE'
export const ARTICLE_SAVE_ALL = 'ARTICLE_SAVE_ALL'
export const ARTICLE_CACHE = 'ARTICLE_CACHE' export const ARTICLE_CACHE = 'ARTICLE_CACHE'
export const FOLDER_CREATE = 'FOLDER_CREATE' export const FOLDER_CREATE = 'FOLDER_CREATE'
export const FOLDER_UPDATE = 'FOLDER_UPDATE' export const FOLDER_UPDATE = 'FOLDER_UPDATE'
@@ -16,7 +17,6 @@ export const SWITCH_ARTICLE = 'SWITCH_ARTICLE'
export const SET_SEARCH_FILTER = 'SET_SEARCH_FILTER' export const SET_SEARCH_FILTER = 'SET_SEARCH_FILTER'
export const SET_TAG_FILTER = 'SET_TAG_FILTER' export const SET_TAG_FILTER = 'SET_TAG_FILTER'
export const CLEAR_SEARCH = 'CLEAR_SEARCH' export const CLEAR_SEARCH = 'CLEAR_SEARCH'
export const TOGGLE_ONLY_UNSAVED_FILTER = 'TOGGLE_ONLY_UNSAVED_FILTER'
export const TOGGLE_TUTORIAL = 'TOGGLE_TUTORIAL' export const TOGGLE_TUTORIAL = 'TOGGLE_TUTORIAL'
@@ -51,6 +51,12 @@ export function saveArticle (key, article, forceSwitch) {
} }
} }
export function saveAllArticles () {
return {
type: ARTICLE_SAVE_ALL
}
}
export function updateArticle (article) { export function updateArticle (article) {
return { return {
type: ARTICLE_UPDATE, type: ARTICLE_UPDATE,
@@ -132,12 +138,6 @@ export function clearSearch () {
} }
} }
export function toggleOnlyUnsavedFilter () {
return {
type: TOGGLE_ONLY_UNSAVED_FILTER
}
}
export function toggleTutorial () { export function toggleTutorial () {
return { return {
type: TOGGLE_TUTORIAL type: TOGGLE_TUTORIAL
@@ -146,20 +146,23 @@ export function toggleTutorial () {
export default { export default {
updateUser, updateUser,
clearNewArticle, clearNewArticle,
updateArticle, updateArticle,
destroyArticle, destroyArticle,
cacheArticle, cacheArticle,
saveArticle, saveArticle,
saveAllArticles,
createFolder, createFolder,
updateFolder, updateFolder,
destroyFolder, destroyFolder,
replaceFolder, replaceFolder,
switchFolder, switchFolder,
switchArticle, switchArticle,
setSearchFilter, setSearchFilter,
setTagFilter, setTagFilter,
clearSearch, clearSearch,
toggleOnlyUnsavedFilter,
toggleTutorial toggleTutorial
} }

View File

@@ -7,7 +7,6 @@ import {
SET_SEARCH_FILTER, SET_SEARCH_FILTER,
SET_TAG_FILTER, SET_TAG_FILTER,
CLEAR_SEARCH, CLEAR_SEARCH,
TOGGLE_ONLY_UNSAVED_FILTER,
TOGGLE_TUTORIAL, TOGGLE_TUTORIAL,
// user // user
@@ -18,6 +17,7 @@ import {
ARTICLE_DESTROY, ARTICLE_DESTROY,
ARTICLE_CACHE, ARTICLE_CACHE,
ARTICLE_SAVE, ARTICLE_SAVE,
ARTICLE_SAVE_ALL,
// Folder action type // Folder action type
FOLDER_CREATE, FOLDER_CREATE,
@@ -31,8 +31,7 @@ import activityRecord from 'browser/lib/activityRecord'
const initialStatus = { const initialStatus = {
search: '', search: '',
isTutorialOpen: false, isTutorialOpen: false
onlyUnsaved: false
} }
let data = dataStore.getData() let data = dataStore.getData()
@@ -195,6 +194,18 @@ function articles (state = initialArticles, action) {
dataStore.setArticles(state.data) dataStore.setArticles(state.data)
return state return state
} }
case ARTICLE_SAVE_ALL:
if (state.modified.length > 0) {
state.modified.forEach(modifiedArticle => {
let targetIndex = _.findIndex(state.data, _article => modifiedArticle.key === _article.key)
Object.assign(state.data[targetIndex], modifiedArticle, {key: modifiedArticle.key, updatedAt: new Date()})
})
}
state.modified = []
dataStore.setArticles(state.data)
return state
case ARTICLE_UPDATE: case ARTICLE_UPDATE:
{ {
let article = action.data.article let article = action.data.article
@@ -265,10 +276,6 @@ function status (state = initialStatus, action) {
case CLEAR_SEARCH: case CLEAR_SEARCH:
state.search = '' state.search = ''
return state
case TOGGLE_ONLY_UNSAVED_FILTER:
state.onlyUnsaved = !state.onlyUnsaved
return state return state
default: default:
return state return state

View File

@@ -36,14 +36,14 @@ infoBtnActiveBgColor = #3A3A3A
fixed top left bottom right fixed top left bottom right
z-index 20 z-index 20
background-color transparentify(black, 80%) background-color transparentify(black, 80%)
&>.left &>.ArticleTopBar-left
float left float left
&>.tutorial &>.tutorial
fixed top fixed top
left 200px left 200px
z-index 36 z-index 36
font-style italic font-style italic
&>.search &>.ArticleTopBar-left-search
position relative position relative
float left float left
height 33px height 33px
@@ -63,11 +63,15 @@ infoBtnActiveBgColor = #3A3A3A
&.hide &.hide
opacity 0 opacity 0
ul ul
li
line-height 18px
li:last-child li:last-child
line-height 10px line-height 10px
margin-bottom 3px margin-bottom 3px
small small
font-size 10px font-size 10px
position relative
top -2px
margin-left 15px margin-left 15px
input input
absolute top left absolute top left
@@ -122,13 +126,45 @@ infoBtnActiveBgColor = #3A3A3A
transition 0.1s transition 0.1s
&:hover &:hover
color refreshBtnActiveColor color refreshBtnActiveColor
.only-unsaved .ArticleTopBar-left-unsaved
line-height 33px line-height 33px
float left float left
height 33px height 33px
margin-top 13.5px margin-top 13.5px
margin-left 10px margin-left 10px
&>.right button
background transparent
font-size 20px
border none
outline none
color inactiveTextColor
&:hover
color inherit
&.ArticleTopBar-left-unsaved-save-button
position relative
.ArticleTopBar-left-unsaved-save-button-count
position absolute
font-size 10px
background-color brandColor
color white
height 14px
width 14px
line-height 14px
border-radius 7px
top 16px
right -3px
transition 0.15s
&.hide
transform scale(0)
.ArticleTopBar-left-unsaved-save-button-tooltip
tooltip()
margin-top 30px
margin-left -100px
&:hover
.ArticleTopBar-left-unsaved-save-button-tooltip
opacity 1
&>.ArticleTopBar-right
float right float right
&>button &>button
display block display block