1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-25 07:31:49 +00:00

add unsaved list & move new post button to top bar

This commit is contained in:
Rokt33r
2016-01-02 04:42:04 +09:00
parent 8a62cd386e
commit 00360c77d2
7 changed files with 240 additions and 229 deletions

View File

@@ -138,22 +138,15 @@ export default class ArticleDetail extends React.Component {
}
componentWillReceiveProps (nextProps) {
if (nextProps.activeArticle == null || this.props.activeArticle == null || nextProps.activeArticle.key !== this.props.activeArticle.key) {
let nextArticle = nextProps.activeArticle
let nextModified = nextArticle != null ? _.findWhere(nextProps.modified, {key: nextArticle.key}) : null
let nextArticle = nextProps.activeArticle
let nextModified = nextArticle != null ? _.findWhere(nextProps.modified, {key: nextArticle.key}) : null
let article = Object.assign({content: ''}, nextProps.activeArticle, nextModified)
let nextState = {
article,
previewMode: false
}
if (article.content.trim().length > 0 && article.mode === 'markdown') {
nextState.previewMode = true
}
this.setState(nextState)
let article = Object.assign({content: ''}, nextProps.activeArticle, nextModified)
let nextState = {
article
}
this.setState(nextState)
}
editArticle () {
@@ -404,12 +397,12 @@ export default class ArticleDetail extends React.Component {
}
ArticleDetail.propTypes = {
dispatch: PropTypes.func,
status: PropTypes.shape(),
activeArticle: PropTypes.shape(),
modified: PropTypes.array,
tags: PropTypes.array,
user: PropTypes.shape(),
folders: PropTypes.array,
tags: PropTypes.array,
dispatch: PropTypes.func
modified: PropTypes.array,
activeArticle: PropTypes.shape()
}
ArticleDetail.prototype.linkState = linkState

View File

@@ -85,12 +85,16 @@ export default class ArticleList extends React.Component {
}
handleArticleListKeyDown (e) {
console.log(e.keyCode)
if (e.metaKey || e.ctrlKey) return true
if (e.keyCode === 65) {
if (e.keyCode === 65 && !e.shiftKey) {
e.preventDefault()
remote.getCurrentWebContents().send('nav-new-post')
remote.getCurrentWebContents().send('top-new-post')
}
if (e.keyCode === 65 && e.shiftKey) {
e.preventDefault()
remote.getCurrentWebContents().send('nav-new-folder')
}
if (e.keyCode === 68) {
@@ -129,7 +133,7 @@ export default class ArticleList extends React.Component {
article = Object.assign({}, article, modifiedArticle)
}
let tagElements = Array.isArray(article.tags) && article.tags.length > 0
? article.tags.map(tag => {
? article.tags.slice().map(tag => {
return (<TagLink key={tag} tag={tag}/>)
})
: (<span>Not tagged yet</span>)
@@ -189,9 +193,9 @@ export default class ArticleList extends React.Component {
}
ArticleList.propTypes = {
dispatch: PropTypes.func,
folders: PropTypes.array,
articles: PropTypes.array,
modified: PropTypes.array,
activeArticle: PropTypes.shape(),
dispatch: PropTypes.func
activeArticle: PropTypes.shape()
}

View File

@@ -1,15 +1,17 @@
import React, { PropTypes } from 'react'
import { findWhere } from 'lodash'
import { setSearchFilter, switchFolder, saveArticle } from '../actions'
import { setSearchFilter, switchFolder, uncacheArticle, saveAllArticles, switchArticle, clearSearch } from '../actions'
import { openModal, isModalOpen } from 'browser/lib/modal'
import FolderMark from 'browser/components/FolderMark'
import Preferences from '../modal/Preferences'
import CreateNewFolder from '../modal/CreateNewFolder'
import keygen from 'browser/lib/keygen'
import _ from 'lodash'
import ModeIcon from 'browser/components/ModeIcon'
const ipc = require('electron').ipcRenderer
const BRAND_COLOR = '#18AF90'
const OSX = global.process.platform === 'darwin'
const preferenceTutorialElement = (
<svg width='300' height='300' className='tutorial'>
@@ -29,7 +31,7 @@ c-4,0-7.9,0-11.9-0.1C164,294,164,297,165.9,297L165.9,297z'/>
const newPostTutorialElement = (
<svg width='900' height='900' className='tutorial'>
<text x='290' y='155' fill={BRAND_COLOR} fontSize='24'>Create a new post!!</text>
<text x='300' y='180' fill={BRAND_COLOR} fontSize='16' children={`press \`${process.platform === 'darwin' ? '⌘' : '^'} + Enter\` or \`a\``}/>
<text x='300' y='180' fill={BRAND_COLOR} fontSize='16' children={`press \`${OSX === 'darwin' ? '⌘' : '^'} + Enter\` or \`a\``}/>
<svg x='130' y='-20' width='400' height='400'>
<path fill='white' d='M56.2,132.5c11.7-2.9,23.9-6,36.1-4.1c8.7,1.4,16.6,5.5,23.7,10.5c13.3,9.4,24.5,21.5,40.2,27
c1.8,0.6,2.6-2.3,0.8-2.9c-17.1-6-28.9-20.3-44-29.7c-7-4.4-14.8-7.4-23-8.2c-11.7-1.1-23.3,1.7-34.5,4.5
@@ -62,10 +64,6 @@ c-3.4-6.1-8.2-11.3-13.8-15.4C50.2,11.6,31,10.9,15.3,19C13.6,19.8,15.1,22.4,16.8,
export default class ArticleNavigator extends React.Component {
constructor (props) {
super(props)
this.newPostHandler = e => {
if (isModalOpen()) return true
this.handleNewPostButtonClick(e)
}
this.newFolderHandler = e => {
if (isModalOpen()) return true
this.handleNewFolderButton(e)
@@ -73,12 +71,10 @@ export default class ArticleNavigator extends React.Component {
}
componentDidMount () {
ipc.on('nav-new-post', this.newPostHandler)
ipc.on('nav-new-folder', this.newFolderHandler)
}
componentWillUnmount () {
ipc.removeListener('nav-new-post', this.newPostHandler)
ipc.removeListener('nav-new-folder', this.newFolderHandler)
}
@@ -86,30 +82,6 @@ export default class ArticleNavigator extends React.Component {
openModal(Preferences)
}
handleNewPostButtonClick (e) {
let { dispatch, folders, status } = this.props
let { targetFolders } = status
let isFolderFilterApplied = targetFolders.length > 0
let FolderKey = isFolderFilterApplied
? targetFolders[0].key
: folders[0].key
let newArticle = {
key: keygen(),
title: '',
content: '',
mode: 'markdown',
tags: [],
FolderKey: FolderKey,
craetedAt: new Date(),
updatedAt: new Date()
}
dispatch(saveArticle(newArticle.key, newArticle, true))
if (isFolderFilterApplied) dispatch(switchFolder(targetFolders[0].name))
}
handleNewFolderButton (e) {
let { user } = this.props
openModal(CreateNewFolder, {user: user})
@@ -127,11 +99,52 @@ export default class ArticleNavigator extends React.Component {
dispatch(setSearchFilter(''))
}
handleUnsavedItemClick (article) {
let { dispatch } = this.props
return e => {
let { articles } = this.props
let isInArticleList = articles.some(_article => _article.key === article.key)
if (!isInArticleList) dispatch(clearSearch())
dispatch(switchArticle(article.key))
}
}
handleUncacheButtonClick (article) {
let { dispatch } = this.props
return e => {
dispatch(uncacheArticle(article.key))
}
}
handleSaveAllClick (e) {
let { dispatch } = this.props
dispatch(saveAllArticles())
}
render () {
let { status, user, folders, allArticles } = this.props
let { status, user, folders, allArticles, modified, activeArticle } = this.props
let { targetFolders } = status
if (targetFolders == null) targetFolders = []
let modifiedElements = modified.map(modifiedArticle => {
let originalArticle = _.findWhere(allArticles, {key: modifiedArticle.key})
if (originalArticle == null) return false
let combinedArticle = Object.assign({}, originalArticle, modifiedArticle)
let className = 'ArticleNavigator-unsaved-list-item'
if (activeArticle && activeArticle.key === combinedArticle.key) className += ' active'
return (
<div key={modifiedArticle.key} onClick={e => this.handleUnsavedItemClick(combinedArticle)(e)} className={className}>
<div className='ArticleNavigator-unsaved-list-item-label'>
<ModeIcon mode={combinedArticle.mode}/>&nbsp;
{combinedArticle.title}
</div>
<button onClick={e => this.handleUncacheButtonClick(combinedArticle)(e)} className='ArticleNavigator-unsaved-list-item-discard-button'><i className='fa fa-times'/></button>
</div>
)
}).filter(modifiedArticle => modifiedArticle).sort((a, b) => a.updatedAt - b.updatedAt)
let folderElememts = folders.map((folder, index) => {
let isActive = findWhere(targetFolders, {key: folder.key})
let articleCount = allArticles.filter(article => article.FolderKey === folder.key && article.status !== 'NEW').length
@@ -157,22 +170,27 @@ export default class ArticleNavigator extends React.Component {
</div>
<div className='controlSection'>
<button onClick={e => this.handleNewPostButtonClick(e)} className='newPostBtn'>
New Post
<span className='tooltip'>Create a new Post ({process.platform === 'darwin' ? '⌘' : '^'} + n)</span>
</button>
{status.isTutorialOpen ? newPostTutorialElement : null}
<div className='ArticleNavigator-unsaved'>
<div className='ArticleNavigator-unsaved-header'>Work in progress</div>
<div className='ArticleNavigator-unsaved-list'>
{modifiedElements.length > 0
? modifiedElements
: (
<div className='ArticleNavigator-unsaved-list-empty'>Empty list</div>
)
}
</div>
<div className='ArticleNavigator-unsaved-control'>
<button onClick={e => this.handleSaveAllClick()} className='ArticleNavigator-unsaved-control-save-all-button' disabled={modifiedElements.length === 0}>Save all</button>
</div>
</div>
<div className='folders'>
<div className='header'>
<div className='ArticleNavigator-folders'>
<div className='ArticleNavigator-folders-header'>
<div className='title'>Folders</div>
<button onClick={e => this.handleNewFolderButton(e)} className='addBtn'>
<i className='fa fa-fw fa-plus'/>
<span className='tooltip'>Create a new folder ({process.platform === 'darwin' ? '⌘' : '^'} + Shift + n)</span>
<span className='tooltip'>Create a new folder ({OSX === 'darwin' ? '⌘' : '^'} + Shift + n)</span>
</button>
{status.isTutorialOpen ? newFolderTutorialElement : null}
@@ -189,12 +207,17 @@ export default class ArticleNavigator extends React.Component {
}
ArticleNavigator.propTypes = {
user: PropTypes.object,
folders: PropTypes.array,
allArticles: PropTypes.array,
dispatch: PropTypes.func,
status: PropTypes.shape({
folderId: PropTypes.number
}),
dispatch: PropTypes.func
user: PropTypes.object,
folders: PropTypes.array,
allArticles: PropTypes.array,
articles: PropTypes.array,
modified: PropTypes.array,
activeArticle: PropTypes.shape({
key: PropTypes.string
})
}

View File

@@ -1,39 +1,15 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import ExternalLink from 'browser/components/ExternalLink'
import { setSearchFilter, clearSearch, toggleOnlyUnsavedFilter, toggleTutorial, saveAllArticles, switchArticle } from '../actions'
import store from '../store'
import { setSearchFilter, clearSearch, toggleTutorial, saveArticle, switchFolder } from '../actions'
import { isModalOpen } from 'browser/lib/modal'
import keygen from 'browser/lib/keygen'
const electron = require('electron')
const remote = electron.remote
const Menu = remote.Menu
const MenuItem = remote.MenuItem
const ipc = electron.ipcRenderer
const OSX = process.platform === 'darwin'
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 OSX = global.process.platform === 'darwin'
const BRAND_COLOR = '#18AF90'
@@ -74,6 +50,10 @@ export default class ArticleTopBar extends React.Component {
if (isModalOpen()) return true
this.focusInput(e)
}
this.newPostHandler = e => {
if (isModalOpen()) return true
this.handleNewPostButtonClick(e)
}
this.state = {
isTooltipHidden: true,
@@ -101,6 +81,7 @@ export default class ArticleTopBar extends React.Component {
ipc.on('top-save-all', this.saveAllHandler)
ipc.on('top-focus-search', this.focusSearchHandler)
ipc.on('top-new-post', this.newPostHandler)
}
componentWillUnmount () {
@@ -109,6 +90,7 @@ export default class ArticleTopBar extends React.Component {
ipc.removeListener('top-save-all', this.saveAllHandler)
ipc.removeListener('top-focus-search', this.focusSearchHandler)
ipc.removeListener('top-new-post', this.newPostHandler)
}
handleTooltipRequest (e) {
@@ -152,21 +134,29 @@ export default class ArticleTopBar extends React.Component {
this.focusInput()
}
handleOnlyUnsavedChange (e) {
let { dispatch } = this.props
handleNewPostButtonClick (e) {
let { dispatch, folders, status } = this.props
let { targetFolders } = status
dispatch(toggleOnlyUnsavedFilter())
}
let isFolderFilterApplied = targetFolders.length > 0
let FolderKey = isFolderFilterApplied
? targetFolders[0].key
: folders[0].key
handleSaveAllButtonClick (e) {
let { dispatch } = this.props
let newArticle = {
key: keygen(),
title: '',
content: '',
mode: 'markdown',
tags: [],
FolderKey: FolderKey,
craetedAt: new Date(),
updatedAt: new Date()
}
dispatch(saveAllArticles())
remote.getCurrentWebContents().send('list-focus')
}
handleSaveMenuButtonClick (e) {
menu.popup(590, 45)
dispatch(saveArticle(newArticle.key, newArticle, true))
if (isFolderFilterApplied) dispatch(switchFolder(targetFolders[0].name))
remote.getCurrentWebContents().send('detail-edit')
}
handleTutorialButtonClick (e) {
@@ -176,7 +166,7 @@ export default class ArticleTopBar extends React.Component {
}
render () {
let { status, modified } = this.props
let { status } = this.props
return (
<div className='ArticleTopBar'>
<div className='ArticleTopBar-left'>
@@ -207,13 +197,11 @@ export default class ArticleTopBar extends React.Component {
{status.isTutorialOpen ? searchTutorialElement : null}
<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 (${OSX ? '⌘ + Shift + s' : '^ + Shift + s'})`}></span>
<div className={'ArticleTopBar-left-control'}>
<button onClick={e => this.handleNewPostButtonClick(e)}>
<i className='fa fa-plus'/>
<span className='tooltip'>New Post ({OSX ? '' : '^'} + n)</span>
</button>
<button onClick={e => this.handleSaveMenuButtonClick(e)} className='ArticleTopBar-left-unsaved-menu-button'><i className='fa fa-angle-down'/></button>
</div>
</div>
@@ -260,10 +248,9 @@ export default class ArticleTopBar extends React.Component {
}
ArticleTopBar.propTypes = {
search: PropTypes.string,
dispatch: PropTypes.func,
status: PropTypes.shape({
search: PropTypes.string
}),
modified: PropTypes.array
folders: PropTypes.array
}

View File

@@ -64,23 +64,26 @@ class HomePage extends React.Component {
}
render () {
let { dispatch, status, user, articles, allArticles, modified, activeArticle, folders, tags, filters } = this.props
let { dispatch, status, user, articles, allArticles, modified, activeArticle, folders, tags } = this.props
return (
<div className='HomePage'>
<ArticleNavigator
ref='nav'
dispatch={dispatch}
status={status}
user={user}
folders={folders}
status={status}
allArticles={allArticles}
articles={articles}
modified={modified}
activeArticle={activeArticle}
/>
<ArticleTopBar
ref='top'
dispatch={dispatch}
status={status}
modified={modified}
folders={folders}
/>
<ArticleList
ref='list'
@@ -88,19 +91,17 @@ class HomePage extends React.Component {
folders={folders}
articles={articles}
modified={modified}
status={status}
activeArticle={activeArticle}
/>
<ArticleDetail
ref='detail'
dispatch={dispatch}
user={user}
activeArticle={activeArticle}
modified={modified}
folders={folders}
status={status}
tags={tags}
filters={filters}
user={user}
folders={folders}
modified={modified}
activeArticle={activeArticle}
/>
</div>
)
@@ -208,12 +209,7 @@ function remap (state) {
allArticles,
modified,
activeArticle,
tags,
filters: {
folder: folderFilters,
tag: tagFilters,
text: textFilters
}
tags
}
}
@@ -228,11 +224,6 @@ HomePage.propTypes = {
activeArticle: PropTypes.shape(),
dispatch: PropTypes.func,
folders: PropTypes.array,
filters: PropTypes.shape({
folder: PropTypes.array,
tag: PropTypes.array,
text: PropTypes.array
}),
tags: PropTypes.array
}