1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 01:36:22 +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,23 +138,16 @@ 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 article = Object.assign({content: ''}, nextProps.activeArticle, nextModified)
let nextState = {
article,
previewMode: false
}
if (article.content.trim().length > 0 && article.mode === 'markdown') {
nextState.previewMode = true
article
}
this.setState(nextState)
}
}
editArticle () {
ReactDOM.findDOMNode(this.refs.title).focus()
@@ -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
let newArticle = {
key: keygen(),
title: '',
content: '',
mode: 'markdown',
tags: [],
FolderKey: FolderKey,
craetedAt: new Date(),
updatedAt: new Date()
}
handleSaveAllButtonClick (e) {
let { dispatch } = this.props
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
}

View File

@@ -56,42 +56,91 @@ articleCount = #999
&:active
background-color brandColor
border-color brandColor
.controlSection
height 88px
padding 22px 15px
margin-bottom 44px
.tutorial
fixed top left
z-index 35
pointer-event none
font-style italic
transition 0.1s
&.hide
opacity 0
.newPostBtn
position relative
border none
background-color brandColor
color white
height 44px
width 170px
border-radius 5px
font-size 20px
transition 0.1s
z-index 30
.tooltip
tooltip()
margin-left 48px
margin-top -3px
&:hover
background-color lighten(brandColor, 7%)
.tooltip
opacity 1
.folders, .members
.header
border-bottom 1px solid borderColor
.ArticleNavigator-unsaved
position absolute
top 100px
width 100%
height 225px
.ArticleNavigator-unsaved-header
border-bottom 1px solid alpha(borderColor, 0.5)
padding-bottom 5px
clearfix()
position relative
z-index 30
padding-left 10px
font-size 18px
line-height 22px
margin-bottom 5px
.ArticleNavigator-unsaved-list
.ArticleNavigator-unsaved-list-item
height 33px
padding-left 15px
clearfix()
transition 0.1s
cursor pointer
overflow hidden
&:hover
background-color alpha(white, 0.05)
&.active, &:active
background-color alpha(lighten(brandColor, 25%), 70%)
.ArticleNavigator-unsaved-list-item-label
float left
width 151px
line-height 33px
overflow ellipsis
.ArticleNavigator-unsaved-list-item-discard-button
float right
width 33px
line-height 30px
height 33px
border none
background-color transparent
color white
font-size 18px
opacity 0.5
&:hover
opacity 1
.ArticleNavigator-unsaved-list-empty
height 33px
padding-left 15px
color alpha(white, 0.4)
transition 0.1s
line-height 33px
&:hover
color alpha(white, 0.6)
.ArticleNavigator-unsaved-control
absolute bottom
height 33px
border-top 1px solid alpha(borderColor, 0.5)
width 100%
.ArticleNavigator-unsaved-control-save-all-button
border none
background-color transparent
font-size 14px
color brandColor
padding-left 15px
width 100%
height 33px
text-align left
&:hover
color lighten(brandColor, 15%)
background-color alpha(white, 0.05)
&:active
color white
&:disabled
color alpha(brandColor, 0.5)
&:hover
color alpha(lighten(brandColor, 25%), 0.5)
background-color transparent
.ArticleNavigator-folders
absolute bottom
top 365px
width 100%
.ArticleNavigator-folders-header
border-bottom 1px solid alpha(borderColor, 0.5)
padding-bottom 5px
margin-bottom 10px
clearfix()
position relative
z-index 30
@@ -124,11 +173,6 @@ articleCount = #999
&:active
background-color brandColor
border-color brandColor
.folders
absolute bottom
top 200px
width 100%
.header
.tutorial
position fixed
z-index 35px
@@ -136,7 +180,7 @@ articleCount = #999
font-style italic
.folderList
absolute bottom
top 38px
top 33px
overflow-y auto
.folderList button
height 33px
@@ -149,23 +193,9 @@ articleCount = #999
padding-left 15px
overflow ellipsis
&:hover
background-color transparentify(white, 5%)
background-color alpha(white, 0.05)
&.active, &:active
background-color transparentify(lighten(brandColor, 25%), 70%)
background-color alpha(lighten(brandColor, 25%), 70%)
.articleCount
color articleCount
font-size 12px
.members
.memberList>div
height 33px
width 200px
margin-bottom 5px
padding-left 15px
.memberImage
float left
margin-top 5.5px
border-radius 11px
.memberProfileName
float left
line-height 33px
margin-left 7px

View File

@@ -1,6 +1,5 @@
bgColor = #E6E6E6
inputBgColor = white
iptFocusBorderColor = #369DCD
topBarBtnColor = #B3B3B3
topBarBtnBgColor = #B3B3B3
@@ -87,7 +86,7 @@ infoBtnActiveBgColor = #3A3A3A
line-height 33px
z-index 0
&:focus
border-color iptFocusBorderColor
border-color focusBorderColor
i.fa.fa-search
position absolute
display block
@@ -110,13 +109,15 @@ infoBtnActiveBgColor = #3A3A3A
line-height 20px
text-align center
padding 0
&:focus
color textColor
&:hover
color white
background-color topBarBtnBgColor
&:active
color white
background-color darken(topBarBtnBgColor, 35%)
.ArticleTopBar-left-unsaved
.ArticleTopBar-left-control
line-height 33px
float left
height 33px
@@ -132,40 +133,22 @@ infoBtnActiveBgColor = #3A3A3A
height 33px
border-radius 16.5px
transition 0.1s
border 1px solid transparent
&:hover
color inherit
color textColor
&:active
color inherit
color textColor
background-color lighten(topBarBtnBgColor, 15%)
&:disabled
color inactiveTextColor
background transparent
&:focus
color focusBorderColor
&.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
color textColor
.tooltip
tooltip()
margin-top 30px
margin-left -100px
&:hover
.ArticleTopBar-left-unsaved-save-button-tooltip
.tooltip
opacity 1
&>.ArticleTopBar-right
float right
&>button
@@ -183,7 +166,7 @@ infoBtnActiveBgColor = #3A3A3A
border 1px solid bgColor
transition 0.1s
&:focus
border-color focusBorderColor
background-color lighten(infoBtnActiveBgColor, 15%)
.tooltip
tooltip()
margin-left -50px