1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 10:16:26 +00:00

Compare commits

...

24 Commits

Author SHA1 Message Date
Rokt33r
e731b7882d Merge branch 'dev'
* dev:
  no source map
  bump version
  hidden code
2015-11-21 06:05:17 +09:00
Rokt33r
84e0728ff3 no source map 2015-11-21 06:03:32 +09:00
Rokt33r
666bc18e91 bump version 2015-11-21 05:56:29 +09:00
Rokt33r
8f83124a0d hidden code 2015-11-21 05:54:15 +09:00
Rokt33r
ee91daad7e Merge branch 'dev'
* dev:
  hotfix: Edited alertが変な時に出る
2015-11-18 18:51:39 +09:00
Rokt33r
ee78c0d33b hotfix: Edited alertが変な時に出る 2015-11-18 18:39:27 +09:00
Dick Choi
09482ebcf3 fix wrong dependency 2015-11-17 05:36:08 +09:00
Rokt33r
67424f2d3a Merge branch 'dev'
* dev:
  bump up version 0.4.1
  記事が編集された状態で他の記事を見ようとすると警告をだす
  updateが準備できたら、再起動されるまで改めてUpdateの確認をしない
  Tag suggest
  EnterでSubmitができる - Hotkey, folder edit, folder create(preference/create new folder modal両方)
  Folderの位置を変えることができる
  Preferenceからもフォルダーの色の選択ができる。
  // filterを使うと確実にFolder名が一致するもののみを表示する
  fix style
  new folder modalにcolor select追加
  auto update確認
  FinderからCopyした時、通知を出す
  FinderにCopy to clipboard button追加
  Folder リストに articleの数をだす
  フォルダーで検索するときに in:じゃなくて /にする +バグ修正
  IntroのFinder説明変更
2015-11-16 07:03:46 +09:00
Rokt33r
51f530ffbe bump up version 0.4.1 2015-11-16 05:36:37 +09:00
Rokt33r
013f96a754 記事が編集された状態で他の記事を見ようとすると警告をだす 2015-11-16 05:34:37 +09:00
Rokt33r
df6a018fb6 updateが準備できたら、再起動されるまで改めてUpdateの確認をしない 2015-11-16 04:17:56 +09:00
Rokt33r
409eaf54c1 Tag suggest 2015-11-16 04:06:14 +09:00
Rokt33r
7e04fd342c EnterでSubmitができる - Hotkey, folder edit, folder create(preference/create new folder modal両方) 2015-11-16 02:45:46 +09:00
Rokt33r
1fe15bc6a5 Folderの位置を変えることができる 2015-11-16 02:38:36 +09:00
Rokt33r
ff1bffbb55 Preferenceからもフォルダーの色の選択ができる。 2015-11-16 01:22:22 +09:00
Rokt33r
b28b18a19a // filterを使うと確実にFolder名が一致するもののみを表示する 2015-11-15 23:20:06 +09:00
Rokt33r
bbc3c85212 fix style 2015-11-15 20:40:43 +09:00
Rokt33r
26a08fac06 new folder modalにcolor select追加 2015-11-15 20:32:02 +09:00
Rokt33r
da9d7a4336 auto update確認 2015-11-15 03:52:34 +09:00
Rokt33r
46c6555f94 FinderからCopyした時、通知を出す 2015-11-15 01:39:40 +09:00
Rokt33r
3e980fd2d4 FinderにCopy to clipboard button追加 2015-11-15 01:22:56 +09:00
Rokt33r
fb1462f669 Folder リストに articleの数をだす 2015-11-15 01:07:46 +09:00
Rokt33r
41e1630aac フォルダーで検索するときに in:じゃなくて /にする +バグ修正 2015-11-15 00:57:29 +09:00
Rokt33r
ef84c4e3da IntroのFinder説明変更 2015-11-14 23:49:49 +09:00
35 changed files with 910 additions and 196 deletions

View File

@@ -89,13 +89,6 @@ module.exports = [
click: function () {
BrowserWindow.getFocusedWindow().reload()
}
},
{
label: 'Toggle DevTools',
accelerator: 'Alt+Command+I',
click: function () {
BrowserWindow.getFocusedWindow().toggleDevTools()
}
}
]
},

View File

@@ -5,33 +5,38 @@ var path = require('path')
var version = app.getVersion()
var versionText = (version == null || version.length === 0) ? 'DEV version' : 'v' + version
var versionNotified = false
autoUpdater
.on('error', function (err, message) {
console.error(err)
console.error(message)
console.log(path.resolve(__dirname, '../resources/favicon-230x230.png'))
nn.notify({
title: 'Error! ' + versionText,
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
icon: path.resolve(__dirname, '../resources/favicon-230x230.png'),
message: message
})
})
// .on('checking-for-update', function () {
// // Connecting
// console.log('checking...')
// })
.on('update-available', function () {
nn.notify({
title: 'Update is available!! ' + versionText,
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
icon: path.resolve(__dirname, '../resources/favicon-230x230.png'),
message: 'Download started.. wait for the update ready.'
})
})
.on('update-not-available', function () {
nn.notify({
title: 'Latest Build!! ' + versionText,
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
message: 'Hope you to enjoy our app :D'
})
if (!versionNotified) {
nn.notify({
title: 'Latest Build!! ' + versionText,
icon: path.resolve(__dirname, '../resources/favicon-230x230.png'),
message: 'Hope you to enjoy our app :D'
})
versionNotified = true
}
})
module.exports = autoUpdater

View File

@@ -11,7 +11,16 @@ export default class FinderDetail extends React.Component {
return (
<div className='FinderDetail'>
<div className='header'>
<ModeIcon mode={activeArticle.mode}/> {activeArticle.title}</div>
<div className='left'>
<ModeIcon mode={activeArticle.mode}/> {activeArticle.title}
</div>
<div className='right'>
<button onClick={this.props.saveToClipboard} className='clipboardBtn'>
<i className='fa fa-clipboard fa-fw'/>
<span className='tooltip'>Copy to clipboard (Enter)</span>
</button>
</div>
</div>
<div className='content'>
{activeArticle.mode === 'markdown'
? <MarkdownPreview content={activeArticle.content}/>
@@ -30,5 +39,6 @@ export default class FinderDetail extends React.Component {
}
FinderDetail.propTypes = {
activeArticle: PropTypes.shape()
activeArticle: PropTypes.shape(),
saveToClipboard: PropTypes.func
}

View File

@@ -2,7 +2,7 @@
<html>
<head>
<title>CodeXen Popup</title>
<title>Boost Finder</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>

View File

@@ -14,9 +14,16 @@ import remote from 'remote'
var hideFinder = remote.getGlobal('hideFinder')
import clipboard from 'clipboard'
var notifier = require('node-notifier')
var path = require('path')
function getIconPath () {
return path.resolve(global.__dirname, '../../resources/favicon-230x230.png')
}
require('../styles/finder/index.styl')
const FOLDER_FILTER = 'FOLDER_FILTER'
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
const TEXT_FILTER = 'TEXT_FILTER'
const TAG_FILTER = 'TAG_FILTER'
@@ -45,10 +52,7 @@ class FinderMain extends React.Component {
}
if (e.keyCode === 13) {
let { activeArticle } = this.props
clipboard.writeText(activeArticle.content)
activityRecord.emit('FINDER_COPY')
hideFinder()
this.saveToClipboard()
e.preventDefault()
}
if (e.keyCode === 27) {
@@ -57,6 +61,19 @@ class FinderMain extends React.Component {
}
}
saveToClipboard () {
let { activeArticle } = this.props
clipboard.writeText(activeArticle.content)
activityRecord.emit('FINDER_COPY')
notifier.notify({
icon: getIconPath(),
'title': 'Saved to Clipboard!',
'message': 'Paste it wherever you want!'
})
hideFinder()
}
handleSearchChange (e) {
let { dispatch } = this.props
@@ -83,6 +100,7 @@ class FinderMain extends React.Component {
render () {
let { articles, activeArticle, status, dispatch } = this.props
let saveToClipboard = () => this.saveToClipboard()
return (
<div onClick={e => this.handleClick(e)} onKeyDown={e => this.handleKeyDown(e)} className='Finder'>
<FinderInput
@@ -98,7 +116,10 @@ class FinderMain extends React.Component {
dispatch={dispatch}
selectArticle={article => this.selectArticle(article)}
/>
<FinderDetail activeArticle={activeArticle}/>
<FinderDetail
activeArticle={activeArticle}
saveToClipboard={saveToClipboard}
/>
</div>
)
}
@@ -116,27 +137,47 @@ FinderMain.propTypes = {
dispatch: PropTypes.func
}
// Ignore invalid key
function ignoreInvalidKey (key) {
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/)
}
// Build filter object by key
function buildFilter (key) {
if (key.match(/^\/\/.+/)) {
return {type: FOLDER_EXACT_FILTER, value: key.match(/^\/\/(.+)$/)[1]}
}
if (key.match(/^\/.+/)) {
return {type: FOLDER_FILTER, value: key.match(/^\/(.+)$/)[1]}
}
if (key.match(/^#(.+)/)) {
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
}
return {type: TEXT_FILTER, value: key}
}
function remap (state) {
let { articles, folders, status } = state
let filters = status.search.split(' ').map(key => key.trim()).filter(key => key.length > 0 && !key.match(/^#$/)).map(key => {
if (key.match(/^in:.+$/)) {
return {type: FOLDER_FILTER, value: key.match(/^in:(.+)$/)[1]}
}
if (key.match(/^#(.+)/)) {
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
}
return {type: TEXT_FILTER, value: key}
})
let filters = status.search.split(' ')
.map(key => key.trim())
.filter(ignoreInvalidKey)
.map(buildFilter)
let folderExactFilters = filters.filter(filter => filter.type === FOLDER_EXACT_FILTER)
let folderFilters = filters.filter(filter => filter.type === FOLDER_FILTER)
let textFilters = filters.filter(filter => filter.type === TEXT_FILTER)
let tagFilters = filters.filter(filter => filter.type === TAG_FILTER)
let targetFolders
if (folders != null) {
let targetFolders = folders.filter(folder => {
return _.findWhere(folderFilters, {value: folder.name})
let exactTargetFolders = folders.filter(folder => {
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
})
status.targetFolders = targetFolders
let fuzzyTargetFolders = folders.filter(folder => {
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
})
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
if (targetFolders.length > 0) {
articles = articles.filter(article => {
@@ -164,6 +205,7 @@ function remap (state) {
let activeArticle = _.findWhere(articles, {key: status.articleKey})
if (activeArticle == null) activeArticle = articles[0]
console.log(status.search)
return {
articles,
activeArticle,

View File

@@ -14,10 +14,10 @@ function status (state = initialStatus, action) {
switch (action.type) {
case SELECT_ARTICLE:
state.articleKey = action.data.key
return state
return Object.assign({}, state)
case SEARCH_ARTICLE:
state.search = action.data.input
return state
return Object.assign({}, state)
default:
return state
}

View File

@@ -12,6 +12,7 @@ import { isModalOpen, closeModal } from 'boost/modal'
const TEXT_FILTER = 'TEXT_FILTER'
const FOLDER_FILTER = 'FOLDER_FILTER'
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
const TAG_FILTER = 'TAG_FILTER'
class HomePage extends React.Component {
@@ -98,7 +99,7 @@ class HomePage extends React.Component {
}
render () {
let { dispatch, status, articles, activeArticle, folders, filters } = this.props
let { dispatch, status, articles, allArticles, activeArticle, folders, tags, filters } = this.props
return (
<div className='HomePage'>
@@ -107,6 +108,7 @@ class HomePage extends React.Component {
dispatch={dispatch}
folders={folders}
status={status}
allArticles={allArticles}
/>
<ArticleTopBar
ref='top'
@@ -127,6 +129,7 @@ class HomePage extends React.Component {
activeArticle={activeArticle}
folders={folders}
status={status}
tags={tags}
filters={filters}
/>
</div>
@@ -134,6 +137,25 @@ class HomePage extends React.Component {
}
}
// Ignore invalid key
function ignoreInvalidKey (key) {
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/)
}
// Build filter object by key
function buildFilter (key) {
if (key.match(/^\/\/.+/)) {
return {type: FOLDER_EXACT_FILTER, value: key.match(/^\/\/(.+)$/)[1]}
}
if (key.match(/^\/.+/)) {
return {type: FOLDER_FILTER, value: key.match(/^\/(.+)$/)[1]}
}
if (key.match(/^#(.+)/)) {
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
}
return {type: TEXT_FILTER, value: key}
}
function remap (state) {
let { folders, articles, status } = state
@@ -141,26 +163,33 @@ function remap (state) {
articles.sort((a, b) => {
return new Date(b.updatedAt) - new Date(a.updatedAt)
})
let allArticles = articles.slice()
let tags = _.uniq(allArticles.reduce((sum, article) => {
if (!_.isArray(article.tags)) return sum
return sum.concat(article.tags)
}, []))
// Filter articles
let filters = status.search.split(' ').map(key => key.trim()).filter(key => key.length > 0 && !key.match(/^#$/)).map(key => {
if (key.match(/^in:.+$/)) {
return {type: FOLDER_FILTER, value: key.match(/^in:(.+)$/)[1]}
}
if (key.match(/^#(.+)/)) {
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
}
return {type: TEXT_FILTER, value: key}
})
let filters = status.search.split(' ')
.map(key => key.trim())
.filter(ignoreInvalidKey)
.map(buildFilter)
let folderExactFilters = filters.filter(filter => filter.type === FOLDER_EXACT_FILTER)
let folderFilters = filters.filter(filter => filter.type === FOLDER_FILTER)
let textFilters = filters.filter(filter => filter.type === TEXT_FILTER)
let tagFilters = filters.filter(filter => filter.type === TAG_FILTER)
let targetFolders
if (folders != null) {
let targetFolders = folders.filter(folder => {
return _.findWhere(folderFilters, {value: folder.name})
let exactTargetFolders = folders.filter(folder => {
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
})
status.targetFolders = targetFolders
let fuzzyTargetFolders = folders.filter(folder => {
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
})
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
if (targetFolders.length > 0) {
articles = articles.filter(article => {
@@ -202,11 +231,10 @@ function remap (state) {
// or Change IDLE MODE
if (status.mode === CREATE_MODE) {
let newArticle = _.findWhere(articles, {status: 'NEW'})
let FolderKey = folders[0].key
if (folderFilters.length > 0) {
let targetFolder = _.findWhere(folders, {name: folderFilters[0].value})
if (targetFolder != null) FolderKey = targetFolder.key
}
console.log('targetFolders')
let FolderKey = targetFolders.length > 0
? targetFolders[0].key
: folders[0].key
if (newArticle == null) {
newArticle = {
@@ -229,8 +257,10 @@ function remap (state) {
return {
folders,
status,
allArticles,
articles,
activeArticle,
tags,
filters: {
folder: folderFilters,
tag: tagFilters,
@@ -247,6 +277,7 @@ HomePage.propTypes = {
userId: PropTypes.string
}),
articles: PropTypes.array,
allArticles: PropTypes.array,
activeArticle: PropTypes.shape(),
dispatch: PropTypes.func,
folders: PropTypes.array,

View File

@@ -5,7 +5,20 @@ import _ from 'lodash'
import ModeIcon from 'boost/components/ModeIcon'
import MarkdownPreview from 'boost/components/MarkdownPreview'
import CodeEditor from 'boost/components/CodeEditor'
import { IDLE_MODE, CREATE_MODE, EDIT_MODE, switchMode, switchArticle, switchFolder, clearSearch, updateArticle, destroyArticle, NEW } from 'boost/actions'
import {
IDLE_MODE,
CREATE_MODE,
EDIT_MODE,
switchMode,
switchArticle,
switchFolder,
clearSearch,
lockStatus,
unlockStatus,
updateArticle,
destroyArticle,
NEW
} from 'boost/actions'
import linkState from 'boost/linkState'
import FolderMark from 'boost/components/FolderMark'
import TagLink from 'boost/components/TagLink'
@@ -82,7 +95,12 @@ export default class ArticleDetail extends React.Component {
this.state = {
article: makeInstantArticle(props.activeArticle),
previewMode: false
previewMode: false,
isArticleEdited: false,
isTagChanged: false,
isTitleChanged: false,
isContentChanged: false,
isModeChanged: false
}
}
@@ -117,7 +135,11 @@ export default class ArticleDetail extends React.Component {
if (isModeChanged) {
Object.assign(nextState, {
openDeleteConfirmMenu: false,
previewMode: false
previewMode: false,
isArticleEdited: false,
isTagChanged: false,
isTitleChanged: false,
isContentChanged: false
})
}
@@ -224,6 +246,8 @@ export default class ArticleDetail extends React.Component {
handleCancelButtonClick (e) {
let { activeArticle, dispatch } = this.props
dispatch(unlockStatus())
if (activeArticle.status === NEW) dispatch(switchArticle(null))
dispatch(switchMode(IDLE_MODE))
}
@@ -236,6 +260,8 @@ export default class ArticleDetail extends React.Component {
let folder = _.findWhere(folders, {key: article.FolderKey})
if (folder == null) return false
dispatch(unlockStatus())
delete newArticle.status
newArticle.updatedAt = new Date()
if (newArticle.createdAt == null) {
@@ -263,19 +289,85 @@ export default class ArticleDetail extends React.Component {
this.setState({article: article})
}
handleTitleChange (e) {
let { article } = this.state
article.title = e.target.value
let _isTitleChanged = article.title !== this.props.activeArticle.title
let { isTagChanged, isContentChanged, isArticleEdited, isModeChanged } = this.state
let _isArticleEdited = _isTitleChanged || isTagChanged || isContentChanged || isModeChanged
this.setState({
article,
isTitleChanged: _isTitleChanged,
isArticleEdited: _isArticleEdited
}, () => {
if (isArticleEdited !== _isArticleEdited) {
let { dispatch } = this.props
if (_isArticleEdited) {
console.log('lockit')
dispatch(lockStatus())
} else {
console.log('unlockit')
dispatch(unlockStatus())
}
}
})
}
handleTagsChange (newTag, tags) {
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
let _isArticleEdited = _isTagChanged || isTitleChanged || isContentChanged || isModeChanged
this.setState({
article,
isTagChanged: _isTagChanged,
isArticleEdited: _isArticleEdited
}, () => {
if (isArticleEdited !== _isArticleEdited) {
let { dispatch } = this.props
if (_isArticleEdited) {
console.log('lockit')
dispatch(lockStatus())
} else {
console.log('unlockit')
dispatch(unlockStatus())
}
}
})
}
handleModeChange (value) {
let article = this.state.article
let { article } = this.state
article.mode = value
let _isModeChanged = article.mode !== this.props.activeArticle.mode
let { isTagChanged, isContentChanged, isArticleEdited, isTitleChanged } = this.state
let _isArticleEdited = _isModeChanged || isTagChanged || isContentChanged || isTitleChanged
this.setState({
article: article,
previewMode: false
article,
previewMode: false,
isModeChanged: _isModeChanged,
isArticleEdited: _isArticleEdited
}, () => {
if (isArticleEdited !== _isArticleEdited) {
let { dispatch } = this.props
if (_isArticleEdited) {
console.log('lockit')
dispatch(lockStatus())
} else {
console.log('unlockit')
dispatch(unlockStatus())
}
}
})
}
@@ -286,9 +378,34 @@ export default class ArticleDetail extends React.Component {
}
handleContentChange (e, value) {
let article = this.state.article
let { status } = this.props
if (status.mode === IDLE_MODE) {
return
}
let { article } = this.state
article.content = value
this.setState({article: article})
let _isContentChanged = article.content !== this.props.activeArticle.content
let { isTagChanged, isModeChanged, isArticleEdited, isTitleChanged } = this.state
let _isArticleEdited = _isContentChanged || isTagChanged || isModeChanged || isTitleChanged
this.setState({
article,
isContentChanged: _isContentChanged,
isArticleEdited: _isArticleEdited
}, () => {
if (isArticleEdited !== _isArticleEdited) {
let { dispatch } = this.props
if (_isArticleEdited) {
console.log('lockit')
dispatch(lockStatus())
} else {
console.log('unlockit')
dispatch(unlockStatus())
}
}
})
}
handleTogglePreviewButtonClick (e) {
@@ -303,7 +420,7 @@ export default class ArticleDetail extends React.Component {
}
renderEdit () {
let { folders, status } = this.props
let { folders, status, tags } = this.props
let folderOptions = folders.map(folder => {
return (
@@ -322,10 +439,12 @@ export default class ArticleDetail extends React.Component {
>
{folderOptions}
</select>
{this.state.isArticleEdited ? ' (edited)' : ''}
<TagSelect
tags={this.state.article.tags}
onChange={(tags, tag) => this.handleTagsChange(tags, tag)}
suggestTags={tags}
/>
{status.isTutorialOpen ? tagSelectTutorialElement : null}
@@ -346,7 +465,7 @@ export default class ArticleDetail extends React.Component {
<div className='detailPanel'>
<div className='header'>
<div className='title'>
<input onKeyDown={e => this.handleTitleKeyDown(e)} placeholder='Title' ref='title' valueLink={this.linkState('article.title')}/>
<input onKeyDown={e => this.handleTitleKeyDown(e)} placeholder='Title' ref='title' value={this.state.article.title} onChange={e => this.handleTitleChange(e)}/>
</div>
<ModeSelect
ref='mode'
@@ -395,6 +514,7 @@ export default class ArticleDetail extends React.Component {
ArticleDetail.propTypes = {
status: PropTypes.shape(),
activeArticle: PropTypes.shape(),
activeUser: PropTypes.shape()
activeUser: PropTypes.shape(),
dispatch: PropTypes.func
}
ArticleDetail.prototype.linkState = linkState

View File

@@ -88,16 +88,17 @@ export default class ArticleNavigator extends React.Component {
}
render () {
let { status, folders } = this.props
let { status, 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
return (
<button onClick={e => this.handleFolderButtonClick(folder.name)(e)} key={'folder-' + folder.key} className={isActive ? 'active' : ''}>
<FolderMark color={folder.color}/> {folder.name}
<FolderMark color={folder.color}/> {folder.name} <span className='articleCount'>{articleCount}</span>
</button>
)
})
@@ -150,6 +151,7 @@ export default class ArticleNavigator extends React.Component {
ArticleNavigator.propTypes = {
activeUser: PropTypes.object,
folders: PropTypes.array,
allArticles: PropTypes.array,
status: PropTypes.shape({
folderId: PropTypes.number
}),

View File

@@ -10,7 +10,9 @@ const searchTutorialElement = (
<text x='450' y='33' fill={BRAND_COLOR} fontSize='24'>Search some posts!!</text>
<text x='450' y='60' fill={BRAND_COLOR} fontSize='18'>{'- Search by tag : #{string}'}</text>
<text x='450' y='85' fill={BRAND_COLOR} fontSize='18'>
{'- Search by folder : in:{folder_name}\n'}</text>
{'- Search by folder : /{folder_name}\n'}</text>
<text x='465' y='105' fill={BRAND_COLOR} fontSize='14'>
{'exact match : //{folder_name}'}</text>
<svg width='500' height='300'>
<path fill='white' d='M54.5,51.5c-12.4,3.3-27.3-1.4-38.4-7C11.2,42,5,38.1,5.6,31.8c0.7-6.9,8.1-11.2,13.8-13.7
@@ -117,7 +119,7 @@ export default class ArticleTopBar extends React.Component {
}
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
- Search by tag : #{'{string}'}<br/>
- Search by folder : in:{'{folder_name}'}
- Search by folder : /{'{folder_name}'}
</div>
</div>

View File

@@ -11,8 +11,12 @@ 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'
activityRecord.init()
window.addEventListener('online', function () {
ipc.send('check-update', 'check-update')
})
let routes = (
<Route path='/' component={MainPage}>

View File

@@ -79,6 +79,31 @@ body
white-space nowrap
text-overflow ellipsis
overflow-x hidden
clearfix()
.left
float left
.right
float right
button
border-radius 16.5px
cursor pointer
height 33px
width 33px
border none
margin-right 5px
font-size 18px
color inactiveTextColor
background-color transparent
padding 0
.tooltip
tooltip()
&.clipboardBtn .tooltip
margin-left -160px
margin-top 25px
&:hover
color textColor
.tooltip
opacity 1
.content
position absolute
top 55px

View File

@@ -98,44 +98,66 @@ iptFocusBorderColor = #369DCD
&:hover
background-color white
.TagSelect
white-space nowrap
overflow-x auto
position relative
margin-top 5px
noSelect()
z-index 30
background-color #E6E6E6
.tagItem
background-color brandColor
border-radius 2px
color white
margin 0 2px
padding 0
border 1px solid darken(brandColor, 10%)
button.tagRemoveBtn
.tags
white-space nowrap
overflow-x auto
position relative
max-width 350px
margin-top 5px
noSelect()
z-index 30
background-color #E6E6E6
.tagItem
background-color brandColor
border-radius 2px
color white
margin 0 2px
padding 0
border 1px solid darken(brandColor, 10%)
button.tagRemoveBtn
color white
border-radius 2px
border none
background-color transparent
padding 4px 2px
border-right 1px solid #E6E6E6
font-size 8px
line-height 12px
transition 0.1s
&:hover
background-color lighten(brandColor, 10%)
.tagLabel
padding 4px 4px
font-size 12px
line-height 12px
input.tagInput
background-color transparent
outline none
margin 0 2px
border-radius 2px
border none
background-color transparent
padding 4px 2px
border-right 1px solid #E6E6E6
font-size 8px
line-height 12px
transition 0.1s
height 18px
.suggestTags
position fixed
width 150px
max-height 150px
background-color white
z-index 5
border 1px solid borderColor
border-radius 5px
button
width 100%
display block
padding 0 15px
height 33px
line-height 33px
background-color transparent
border none
text-align left
font-size 14px
&:hover
background-color lighten(brandColor, 10%)
.tagLabel
padding 4px 4px
font-size 12px
line-height 12px
input.tagInput
background-color transparent
outline none
margin 0 2px
border-radius 2px
border none
transition 0.1s
height 18px
background-color darken(white, 10%)
.right
button
cursor pointer
@@ -222,9 +244,6 @@ iptFocusBorderColor = #369DCD
display inline-block
&:hover
background-color darken(white, 10%)
.title
absolute left top bottom
right 150px

View File

@@ -1,4 +1,5 @@
articleNavBgColor = #353535
articleCount = #999
.ArticleNavigator
background-color articleNavBgColor
@@ -149,7 +150,10 @@ articleNavBgColor = #353535
&:hover
background-color transparentify(white, 5%)
&.active, &:active
background-color brandColor
background-color transparentify(lighten(brandColor, 25%), 70%)
.articleCount
color articleCount
font-size 12px
.members
.memberList>div
height 33px

View File

@@ -9,3 +9,4 @@
@require './lib/CreateNewFolder'
@require './lib/Preferences'
@require './lib/Tutorial'
@require './lib/EditedAlert'

View File

@@ -34,9 +34,30 @@ iptFocusBorderColor = #369DCD
border-radius 5px
border solid 1px borderColor
outline none
margin 100px auto 25px
margin 75px auto 20px
&:focus
border-color iptFocusBorderColor
.colorSelect
text-align center
.option
cursor pointer
font-size 22px
height 48px
width 48px
margin 0 2px
border 1px solid transparent
border-radius 5px
overflow hidden
line-height 45px
text-align center
transition 0.1s
display inline-block
&:hover
border-color borderColor
font-size 28px
&.active
font-size 28px
border-color iptFocusBorderColor
.alert
color infoTextColor
background-color infoBackgroundColor
@@ -44,7 +65,7 @@ iptFocusBorderColor = #369DCD
padding 15px 15px
width 330px
border-radius 5px
margin 0 auto
margin 15px auto 0
&.error
color errorTextColor
background-color errorBackgroundColor

View File

@@ -0,0 +1,28 @@
.EditedAlert.modal
width 350px
top 100px
.title
font-size 24px
margin-bottom 15px
.message
font-size 14px
margin-bottom 15px
.control
text-align right
button
border-radius 5px
height 33px
padding 0 15px
font-size 14px
background-color white
border 1px solid borderColor
border-radius 5px
margin-left 5px
&:hover
background-color darken(white, 10%)
&.primary
border-color brandColor
background-color brandColor
color white
&:hover
background-color lighten(brandColor, 10%)

View File

@@ -440,19 +440,22 @@ iptFocusBorderColor = #369DCD
padding 5px 0
&:last-child
border-color transparent
.folderColor
float left
margin-left 10px
text-align center
width 44px
.folderName
float left
width 175px
overflow ellipsis
padding-left 15px
.folderPublic
float left
text-align center
width 100px
.folderControl
float right
width 145px
width 125px
text-align center
&.folderHeader
.folderName
padding-left 25px
&.newFolder
.alert
display block
@@ -502,6 +505,30 @@ iptFocusBorderColor = #369DCD
&:hover
color lighten(brandColor, 10%)
&.FolderRow
.sortBtns
float left
display block
height 30px
width 30px
margin-top 1.5px
position absolute
button
absolute left
background-color transparent
border none
height 15px
padding 0
margin 0
color stripBtnColor
&:first-child
top 0
&:last-child
top 15px
&:hover
color stripHoverBtnColor
&:disabled
color lighten(stripBtnColor, 10%)
cursor not-allowed
.folderName input
height 33px
border 1px solid borderColor
@@ -512,16 +539,52 @@ iptFocusBorderColor = #369DCD
width 150px
&:focus
border-color iptFocusBorderColor
.folderPublic select
height 33px
border 1px solid borderColor
background-color white
outline none
display block
margin 0 auto
font-size 14px
&:focus
border-color iptFocusBorderColor
.folderColor
.select
height 33px
width 33px
border 1px solid borderColor
background-color white
outline none
display block
margin 0 auto
font-size 14px
border-radius 5px
&:focus
border-color iptFocusBorderColor
.options
position absolute
background-color white
text-align left
border 1px solid borderColor
border-radius 5px
padding 0 5px 5px
margin-left 5px
margin-top -34px
clearfix()
.label
margin-left 5px
line-height 22px
font-size 12px
button
float left
border none
width 33px
height 33px
margin-right 5px
border 1px solid transparent
line-height 29px
overflow hidden
border-radius 5px
background-color transparent
outline none
transition 0.1s
&:hover
border-color borderColor
&.active
border-color iptFocusBorderColor
.FolderMark
transform scale(1.4)
.folderControl
button
border none

View File

@@ -108,6 +108,8 @@ slideBgColor4 = #00B493
height 140px
.slide3
background-color slideBgColor3
.title
margin-bottom 15px
.content
font-size 18px
&>img

View File

@@ -4,6 +4,7 @@ export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
export const FOLDER_CREATE = 'FOLDER_CREATE'
export const FOLDER_UPDATE = 'FOLDER_UPDATE'
export const FOLDER_DESTROY = 'FOLDER_DESTROY'
export const FOLDER_REPLACE = 'FOLDER_REPLACE'
export const SWITCH_FOLDER = 'SWITCH_FOLDER'
export const SWITCH_MODE = 'SWITCH_MODE'
@@ -11,6 +12,8 @@ export const SWITCH_ARTICLE = 'SWITCH_ARTICLE'
export const SET_SEARCH_FILTER = 'SET_SEARCH_FILTER'
export const SET_TAG_FILTER = 'SET_TAG_FILTER'
export const CLEAR_SEARCH = 'CLEAR_SEARCH'
export const LOCK_STATUS = 'LOCK_STATUS'
export const UNLOCK_STATUS = 'UNLOCK_STATUS'
export const TOGGLE_TUTORIAL = 'TOGGLE_TUTORIAL'
// Status - mode
@@ -57,6 +60,16 @@ export function destroyFolder (key) {
}
}
export function replaceFolder (a, b) {
return {
type: FOLDER_REPLACE,
data: {
a,
b
}
}
}
export function switchFolder (folderName) {
return {
type: SWITCH_FOLDER,
@@ -98,7 +111,19 @@ export function clearSearch () {
}
}
export function toggleTutorial() {
export function lockStatus () {
return {
type: LOCK_STATUS
}
}
export function unlockStatus () {
return {
type: UNLOCK_STATUS
}
}
export function toggleTutorial () {
return {
type: TOGGLE_TUTORIAL
}

View File

@@ -3,45 +3,50 @@ import React, { PropTypes } from 'react'
const BLUE = '#3460C7'
const LIGHTBLUE = '#2BA5F7'
const ORANGE = '#FF8E00'
const YELLOW = '#EAEF31'
const GREEN = '#02FF26'
const DARKGREEN = '#008A59'
const YELLOW = '#E8D252'
const GREEN = '#3FD941'
const DARKGREEN = '#1FAD85'
const RED = '#E10051'
const PURPLE = '#B013A4'
const BRAND_COLOR = '#2BAC8F'
function getColorByIndex (index) {
switch (index % 8) {
case 0:
return LIGHTBLUE
return RED
case 1:
return ORANGE
case 2:
return RED
return YELLOW
case 3:
return GREEN
case 4:
return DARKGREEN
case 5:
return YELLOW
return LIGHTBLUE
case 6:
return BLUE
case 7:
return PURPLE
default:
return BRAND_COLOR
return DARKGREEN
}
}
export default class FolderMark extends React.Component {
render () {
let color = getColorByIndex(this.props.color)
let className = 'FolderMark fa fa-square fa-fw'
if (this.props.className != null) {
className += ' active'
}
return (
<i className='fa fa-square fa-fw' style={{color: color}}/>
<i className={className} style={{color: color}}/>
)
}
}
FolderMark.propTypes = {
color: PropTypes.number
color: PropTypes.number,
className: PropTypes.string
}

View File

@@ -18,7 +18,7 @@ export default class ModeSelect extends React.Component {
}
}
componentDidMount (e) {
componentDidMount () {
this.blurHandler = e => {
let searchElement = ReactDOM.findDOMNode(this.refs.search)
if (this.state.mode === EDIT_MODE && document.activeElement !== searchElement) {
@@ -28,7 +28,7 @@ export default class ModeSelect extends React.Component {
window.addEventListener('click', this.blurHandler)
}
componentWillUnmount (e) {
componentWillUnmount () {
window.removeEventListener('click', this.blurHandler)
let searchElement = ReactDOM.findDOMNode(this.refs.search)
if (searchElement != null && this.searchKeyDownListener != null) {

View File

@@ -3,23 +3,54 @@ import ReactDOM from 'react-dom'
import _ from 'lodash'
import linkState from 'boost/linkState'
function isNotEmptyString (str) {
return _.isString(str) && str.length > 0
}
export default class TagSelect extends React.Component {
constructor (props) {
super(props)
this.state = {
input: ''
input: '',
isInputFocused: false
}
}
handleKeyDown (e) {
if (e.keyCode !== 13) return false
e.preventDefault()
componentDidMount () {
this.blurInputBlurHandler = e => {
if (ReactDOM.findDOMNode(this.refs.tagInput) !== document.activeElement) {
this.setState({isInputFocused: false})
}
}
window.addEventListener('click', this.blurInputBlurHandler)
}
componentWillUnmount (e) {
window.removeEventListener('click', this.blurInputBlurHandler)
}
// Suggestは必ずInputの下に位置するようにする
componentDidUpdate () {
if (this.shouldShowSuggest()) {
let inputRect = ReactDOM.findDOMNode(this.refs.tagInput).getBoundingClientRect()
let suggestElement = ReactDOM.findDOMNode(this.refs.suggestTags)
if (suggestElement != null) {
suggestElement.style.top = inputRect.top + 20 + 'px'
suggestElement.style.left = inputRect.left + 'px'
}
}
}
shouldShowSuggest () {
return this.state.isInputFocused && isNotEmptyString(this.state.input)
}
addTag (tag, clearInput = true) {
let tags = this.props.tags.slice(0)
let newTag = this.state.input.trim()
let newTag = tag.trim()
if (newTag.length === 0) {
if (newTag.length === 0 && clearInput) {
this.setState({input: ''})
return
}
@@ -30,13 +61,38 @@ export default class TagSelect extends React.Component {
if (_.isFunction(this.props.onChange)) {
this.props.onChange(newTag, tags)
}
this.setState({input: ''})
if (clearInput) this.setState({input: ''})
}
handleKeyDown (e) {
switch (e.keyCode) {
case 8:
{
if (this.state.input.length > 0) break
e.preventDefault()
let tags = this.props.tags.slice(0)
tags.pop()
this.props.onChange(null, tags)
}
break
case 13:
{
e.preventDefault()
this.addTag(this.state.input)
}
}
}
handleThisClick (e) {
ReactDOM.findDOMNode(this.refs.tagInput).focus()
}
handleInputFocus (e) {
this.setState({isInputFocused: true})
}
handleItemRemoveButton (tag) {
return e => {
e.stopPropagation()
@@ -50,8 +106,16 @@ export default class TagSelect extends React.Component {
}
}
handleSuggestClick (tag) {
return e => {
this.addTag(tag)
}
}
render () {
var tagElements = _.isArray(this.props.tags)
let { tags, suggestTags } = this.props
let tagElements = _.isArray(tags)
? this.props.tags.map(tag => (
<span key={tag} className='tagItem'>
<button onClick={e => this.handleItemRemoveButton(tag)(e)} className='tagRemoveBtn'><i className='fa fa-fw fa-times'/></button>
@@ -59,16 +123,37 @@ export default class TagSelect extends React.Component {
</span>))
: null
let suggestElements = this.shouldShowSuggest() ? suggestTags
.filter(tag => {
return tag.match(this.state.input)
})
.map(tag => {
return <button onClick={e => this.handleSuggestClick(tag)(e)} key={tag}>{tag}</button>
})
: null
return (
<div className='TagSelect' onClick={e => this.handleThisClick(e)}>
{tagElements}
<input
type='text'
onKeyDown={e => this.handleKeyDown(e)}
ref='tagInput'
valueLink={this.linkState('input')}
placeholder='Click here to add tags'
className='tagInput'/>
<div className='tags'>
{tagElements}
<input
type='text'
onKeyDown={e => this.handleKeyDown(e)}
ref='tagInput'
valueLink={this.linkState('input')}
placeholder='Click here to add tags'
className='tagInput'
onFocus={e => this.handleInputFocus(e)}
/>
</div>
{suggestElements != null && suggestElements.length > 0
? (
<div ref='suggestTags' className='suggestTags'>
{suggestElements}
</div>
)
: null
}
</div>
)
}
@@ -76,7 +161,8 @@ export default class TagSelect extends React.Component {
TagSelect.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func
onChange: PropTypes.func,
suggestTags: PropTypes.array
}
TagSelect.prototype.linkState = linkState

View File

@@ -2,6 +2,7 @@ import React, { PropTypes } from 'react'
import linkState from 'boost/linkState'
import { createFolder } from 'boost/actions'
import store from 'boost/store'
import FolderMark from 'boost/components/FolderMark'
export default class CreateNewFolder extends React.Component {
constructor (props) {
@@ -9,6 +10,7 @@ export default class CreateNewFolder extends React.Component {
this.state = {
name: '',
color: Math.round(Math.random() * 7),
alert: null
}
}
@@ -20,9 +22,11 @@ export default class CreateNewFolder extends React.Component {
handleConfirmButton (e) {
this.setState({alert: null}, () => {
let { close } = this.props
let name = this.state.name
let { name, color } = this.state
let input = {
name
name,
color
}
try {
@@ -38,6 +42,20 @@ export default class CreateNewFolder extends React.Component {
})
}
handleColorClick (colorIndex) {
return e => {
this.setState({
color: colorIndex
})
}
}
handleKeyDown (e) {
if (e.keyCode === 13) {
this.handleConfirmButton()
}
}
render () {
let alert = this.state.alert
let alertElement = alert != null ? (
@@ -45,6 +63,20 @@ export default class CreateNewFolder extends React.Component {
{alert.message}
</p>
) : null
let colorIndexes = []
for (let i = 0; i < 8; i++) {
colorIndexes.push(i)
}
let colorElements = colorIndexes.map(index => {
let className = 'option'
if (index === this.state.color) className += ' active'
return (
<span className={className} key={index} onClick={e => this.handleColorClick(index)(e)}>
<FolderMark color={index}/>
</span>
)
})
return (
<div className='CreateNewFolder modal'>
@@ -52,7 +84,10 @@ export default class CreateNewFolder extends React.Component {
<div className='title'>Create new folder</div>
<input className='ipt' type='text' valueLink={this.linkState('name')} placeholder='Enter folder name'/>
<input onKeyDown={e => this.handleKeyDown(e)} className='ipt' type='text' valueLink={this.linkState('name')} placeholder='Enter folder name'/>
<div className='colorSelect'>
{colorElements}
</div>
{alertElement}
<button onClick={e => this.handleConfirmButton(e)} className='confirmBtn'>Create</button>

View File

@@ -0,0 +1,35 @@
import React, { PropTypes } from 'react'
import store from 'boost/store'
import { unlockStatus } from 'boost/actions'
export default class EditedAlert extends React.Component {
handleNoButtonClick (e) {
this.props.close()
}
handleYesButtonClick (e) {
store.dispatch(unlockStatus())
store.dispatch(this.props.action)
this.props.close()
}
render () {
return (
<div className='EditedAlert modal'>
<div className='title'>Your article is still editing!</div>
<div className='message'>Do you really want to leave without finishing?</div>
<div className='control'>
<button onClick={e => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close'/> No</button>
<button onClick={e => this.handleYesButtonClick(e)} className='primary'><i className='fa fa-fw fa-check'/> Yes</button>
</div>
</div>
)
}
}
EditedAlert.propTypes = {
action: PropTypes.object,
close: PropTypes.func
}

View File

@@ -36,12 +36,20 @@ export default class AppSettingTab extends React.Component {
ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
}
handleSaveButtonClick (e) {
submitHotKey () {
ipc.send('hotkeyUpdated', {
toggleFinder: this.state.toggleFinder
})
}
handleSaveButtonClick (e) {
this.submitHotKey()
}
handleKeyDown (e) {
this.submitHotKey()
}
render () {
let alert = this.state.alert
let alertElement = alert != null ? (
@@ -56,7 +64,7 @@ export default class AppSettingTab extends React.Component {
<div className='sectionTitle'>Hotkey</div>
<div className='sectionInput'>
<label>Toggle Finder(popup)</label>
<input valueLink={this.linkState('toggleFinder')} type='text'/>
<input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('toggleFinder')} type='text'/>
</div>
<div className='sectionConfirm'>
<button onClick={e => this.handleSaveButtonClick(e)}>Save</button>

View File

@@ -2,7 +2,7 @@ import React, { PropTypes } from 'react'
import linkState from 'boost/linkState'
import FolderMark from 'boost/components/FolderMark'
import store from 'boost/store'
import { updateFolder, destroyFolder } from 'boost/actions'
import { updateFolder, destroyFolder, replaceFolder } from 'boost/actions'
const IDLE = 'IDLE'
const EDIT = 'EDIT'
@@ -17,6 +17,20 @@ export default class FolderRow extends React.Component {
}
}
handleUpClick (e) {
let { index } = this.props
if (index > 0) {
store.dispatch(replaceFolder(index, index - 1))
}
}
handleDownClick (e) {
let { index, count } = this.props
if (index < count - 1) {
store.dispatch(replaceFolder(index, index + 1))
}
}
handleCancelButtonClick (e) {
this.setState({
mode: IDLE
@@ -26,7 +40,9 @@ export default class FolderRow extends React.Component {
handleEditButtonClick (e) {
this.setState({
mode: EDIT,
name: this.props.folder.name
name: this.props.folder.name,
color: this.props.folder.color,
isColorEditing: false
})
}
@@ -34,12 +50,34 @@ export default class FolderRow extends React.Component {
this.setState({mode: DELETE})
}
handleNameInputKeyDown (e) {
if (e.keyCode === 13) {
this.handleSaveButtonClick()
}
}
handleColorSelectClick (e) {
this.setState({
isColorEditing: true
})
}
handleColorButtonClick (index) {
return e => {
this.setState({
color: index,
isColorEditing: false
})
}
}
handleSaveButtonClick (e) {
let { folder, setAlert } = this.props
setAlert(null, () => {
let input = {
name: this.state.name
name: this.state.name,
color: this.state.color
}
folder = Object.assign({}, folder, input)
@@ -68,10 +106,40 @@ export default class FolderRow extends React.Component {
switch (this.state.mode) {
case EDIT:
let colorIndexes = []
for (let i = 0; i < 8; i++) {
colorIndexes.push(i)
}
let colorOptions = colorIndexes.map(index => {
let className = this.state.color === index
? 'active'
: null
return (
<button onClick={e => this.handleColorButtonClick(index)(e)} className={className} key={index}>
<FolderMark color={index}/>
</button>
)
})
return (
<div className='FolderRow edit'>
<div className='folderColor'>
<button onClick={e => this.handleColorSelectClick(e)} className='select'>
<FolderMark color={this.state.color}/>
</button>
{this.state.isColorEditing
? (
<div className='options'>
<div className='label'>Color select</div>
{colorOptions}
</div>
)
: null
}
</div>
<div className='folderName'>
<input valueLink={this.linkState('name')} type='text'/>
<input onKeyDown={e => this.handleNameInputKeyDown(e)} valueLink={this.linkState('name')} type='text'/>
</div>
<div className='folderControl'>
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>Save</button>
@@ -93,7 +161,12 @@ export default class FolderRow extends React.Component {
default:
return (
<div className='FolderRow'>
<div className='folderName'><FolderMark color={folder.color}/> {folder.name}</div>
<div className='sortBtns'>
<button onClick={e => this.handleUpClick(e)}><i className='fa fa-sort-up fa-fw'/></button>
<button onClick={e => this.handleDownClick(e)}><i className='fa fa-sort-down fa-fw'/></button>
</div>
<div className='folderColor'><FolderMark color={folder.color}/></div>
<div className='folderName'>{folder.name}</div>
<div className='folderControl'>
<button onClick={e => this.handleEditButtonClick(e)}><i className='fa fa-fw fa-edit'/></button>
<button onClick={e => this.handleDeleteButtonClick(e)}><i className='fa fa-fw fa-close'/></button>
@@ -106,6 +179,8 @@ export default class FolderRow extends React.Component {
FolderRow.propTypes = {
folder: PropTypes.shape(),
index: PropTypes.number,
count: PropTypes.number,
setAlert: PropTypes.func
}

View File

@@ -12,6 +12,12 @@ export default class FolderSettingTab extends React.Component {
}
}
handleNewFolderNameKeyDown (e) {
if (e.keyCode === 13) {
this.handleSaveButtonClick()
}
}
handleSaveButtonClick (e) {
this.setState({alert: null}, () => {
if (this.state.name.trim().length === 0) return false
@@ -40,10 +46,16 @@ export default class FolderSettingTab extends React.Component {
render () {
let { folders } = this.props
let folderElements = folders.map(folder => {
let folderElements = folders.map((folder, index) => {
return (
<FolderRow key={'folder-' + folder.key} folder={folder} setAlert={(alert, cb) => this.setAlert(alert, cb)}/>
)
<FolderRow
key={'folder-' + folder.key}
folder={folder}
index={index}
count={folders.length}
setAlert={(alert, cb) => this.setAlert(alert, cb)}
/>
)
})
let alert = this.state.alert
@@ -59,13 +71,13 @@ export default class FolderSettingTab extends React.Component {
<div className='sectionTitle'>Manage folder</div>
<div className='folderTable'>
<div className='folderHeader'>
<div className='folderName'>Folder name</div>
<div className='folderName'>Folder</div>
<div className='folderControl'>Edit/Delete</div>
</div>
{folderElements}
<div className='newFolder'>
<div className='folderName'>
<input valueLink={this.linkState('name')} type='text' placeholder='New Folder'/>
<input onKeyDown={e => this.handleNewFolderNameKeyDown(e)} valueLink={this.linkState('name')} type='text' placeholder='New Folder'/>
</div>
<div className='folderControl'>
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>Add</button>

View File

@@ -88,10 +88,11 @@ export default class Tutorial extends React.Component {
return (<div className='slide slide3'>
<div className='title'>Easy to access with Finder</div>
<div className='content'>
With Finder, You can search your articles faster.<br/>
You can open Finder by pressing Control + shift + tab<br/>
To put the content of an article in the clipboard, press Enter.<br/>
So you can paste it with Cmd() + V
The Finder helps you organize all of the files and documents.<br/>
There is a short-cut key [control + shift + tab] to open the Finder.<br/>
It is available to save your articles on the Clipboard<br/>
by selecting your file with pressing Enter key,<br/>
and to paste the contents of the Clipboard with [Command-V]
<img width='480' src='../../resources/finder.png'/>
</div>

View File

@@ -1,14 +1,42 @@
import { combineReducers } from 'redux'
import _ from 'lodash'
import { SWITCH_FOLDER, SWITCH_MODE, SWITCH_ARTICLE, SET_SEARCH_FILTER, SET_TAG_FILTER, CLEAR_SEARCH, TOGGLE_TUTORIAL, ARTICLE_UPDATE, ARTICLE_DESTROY, FOLDER_CREATE, FOLDER_UPDATE, FOLDER_DESTROY, IDLE_MODE, CREATE_MODE } from './actions'
import {
// Status action type
SWITCH_FOLDER,
SWITCH_MODE,
SWITCH_ARTICLE,
SET_SEARCH_FILTER,
SET_TAG_FILTER,
CLEAR_SEARCH,
LOCK_STATUS,
UNLOCK_STATUS,
TOGGLE_TUTORIAL,
// Article action type
ARTICLE_UPDATE,
ARTICLE_DESTROY,
// Folder action type
FOLDER_CREATE,
FOLDER_UPDATE,
FOLDER_DESTROY,
FOLDER_REPLACE,
// view mode
IDLE_MODE,
CREATE_MODE
} from './actions'
import dataStore from 'boost/dataStore'
import keygen from 'boost/keygen'
import activityRecord from 'boost/activityRecord'
import { openModal } from 'boost/modal'
import EditedAlert from 'boost/components/modal/EditedAlert'
const initialStatus = {
mode: IDLE_MODE,
search: '',
isTutorialOpen: false
isTutorialOpen: false,
isStatusLocked: false
}
let data = dataStore.getData()
@@ -26,12 +54,11 @@ function folders (state = initialFolders, action) {
Object.assign(newFolder, {
key: keygen(),
createdAt: new Date(),
updatedAt: new Date(),
// random number (0-7)
color: Math.round(Math.random() * 7)
updatedAt: new Date()
})
if (newFolder.length === 0) throw new Error('Folder name is required')
if (newFolder.name == null && newFolder.name.length === 0) throw new Error('Folder name is required')
if (newFolder.name.match(/\//)) throw new Error('`/` is not available for folder name')
let conflictFolder = _.findWhere(state, {name: newFolder.name})
if (conflictFolder != null) throw new Error(`${newFolder.name} already exists!`)
@@ -48,7 +75,8 @@ function folders (state = initialFolders, action) {
if (!_.isString(folder.name)) throw new Error('Folder name must be a string')
folder.name = folder.name.trim().replace(/\s/, '_')
if (folder.length === 0) throw new Error('Folder name is required')
if (folder.name.length === 0) throw new Error('Folder name is required')
if (folder.name.match(/\//)) throw new Error('`/` is not available for folder name')
// Folder existence check
if (targetFolder == null) throw new Error('Folder doesnt exist')
@@ -80,6 +108,15 @@ function folders (state = initialFolders, action) {
activityRecord.emit('FOLDER_DESTROY')
return state
}
case FOLDER_REPLACE:
{
let { a, b } = action.data
let folderA = state[a]
let folderB = state[b]
state.splice(a, 1, folderB)
state.splice(b, 1, folderA)
}
return state
default:
return state
}
@@ -125,13 +162,28 @@ function articles (state = initialArticles, action) {
function status (state = initialStatus, action) {
state = Object.assign({}, state)
switch (action.type) {
case TOGGLE_TUTORIAL:
state.isTutorialOpen = !state.isTutorialOpen
return state
case LOCK_STATUS:
state.isStatusLocked = true
return state
case UNLOCK_STATUS:
state.isStatusLocked = false
return state
}
// if status locked, status become unmutable
if (state.isStatusLocked) {
openModal(EditedAlert, {action})
return state
}
switch (action.type) {
case SWITCH_FOLDER:
state.mode = IDLE_MODE
state.search = `in:${action.data} `
state.search = `//${action.data} `
return state
case SWITCH_MODE:

12
main.js
View File

@@ -30,8 +30,8 @@ updater
.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
nn.notify({
title: 'Ready to Update!! ' + versionText,
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
message: 'Click tray icon to update app: ' + releaseName
icon: path.join(__dirname, '/resources/favicon-230x230.png'),
message: 'Click update button on Main window: ' + releaseName
})
update = quitAndUpdate
@@ -50,6 +50,14 @@ app.on('ready', function () {
// menu start
var template = require('./atom-lib/menu-template')
setInterval(function () {
if (update == null) updater.checkForUpdates()
}, 1000 * 60 * 60 * 24)
ipc.on('check-update', function (event, msg) {
if (update == null) updater.checkForUpdates()
})
ipc.on('update-app', function (event, msg) {
if (update != null) {
appQuit = true

View File

@@ -1,13 +1,13 @@
{
"name": "boost",
"version": "0.4.0-beta.2",
"version": "0.4.1-beta.2",
"description": "Boost App",
"main": "main.js",
"scripts": {
"start": "BOOST_ENV=development electron ./main.js",
"webpack": "webpack-dev-server --hot --inline --config webpack.config.js",
"compile": "NODE_ENV=production webpack --config webpack.config.production.js",
"build": "electron-packager ./ Boost --app-version=$npm_package_version $npm_package_config_platform $npm_package_config_version $npm_package_config_ignore --overwrite",
"build": "electron-packager ./ Boost --app-version=$npm_package_version $npm_package_config_platform $npm_package_config_version $npm_package_config_ignore --overwrite --asar",
"codesign": "codesign --verbose --deep --force --sign \"MAISIN solutions Inc.\" Boost-darwin-x64/Boost.app"
},
"config": {
@@ -38,7 +38,6 @@
"homepage": "https://github.com/Rokt33r/codexen-app#readme",
"dependencies": {
"devicon": "^2.0.0",
"electron-packager": "^5.1.1",
"font-awesome": "^4.3.0",
"fs-jetpack": "^0.7.0",
"lodash": "^3.10.1",

View File

@@ -68,7 +68,8 @@ var config = {
'superagent-promise',
'lodash',
'markdown-it',
'moment'
'moment',
'node-notifier'
]
}

View File

@@ -1,6 +1,5 @@
var webpack = require('webpack')
module.exports = {
devtool: 'source-map',
entry: {
main: './browser/main/index.js',
finder: './browser/finder/index.js'
@@ -8,7 +7,7 @@ module.exports = {
output: {
path: 'compiled',
filename: '[name].js',
sourceMapFilename: '[name].map',
// sourceMapFilename: '[name].map',
libraryTarget: 'commonjs2'
},
module: {
@@ -31,12 +30,12 @@ module.exports = {
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false
}
})
// new webpack.optimize.UglifyJsPlugin({
// compressor: {
// warnings: false
// }
// })
],
externals: [
'socket.io-client',
@@ -45,7 +44,8 @@ module.exports = {
'superagent-promise',
'lodash',
'markdown-it',
'moment'
'moment',
'node-notifier'
],
resolve: {
extensions: ['', '.js', '.jsx', 'styl']