mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 17:56:25 +00:00
save ALL
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user