diff --git a/browser/finder/index.js b/browser/finder/index.js
index fb2a40a7..7fef8c7d 100644
--- a/browser/finder/index.js
+++ b/browser/finder/index.js
@@ -8,6 +8,7 @@ import FinderList from './FinderList'
import FinderDetail from './FinderDetail'
import { selectArticle, searchArticle, refreshData } from './actions'
import _ from 'lodash'
+import activityRecord from 'boost/activityRecord'
import remote from 'remote'
var hideFinder = remote.getGlobal('hideFinder')
@@ -46,6 +47,7 @@ class FinderMain extends React.Component {
if (e.keyCode === 13) {
let { activeArticle } = this.props
clipboard.writeText(activeArticle.content)
+ activityRecord.emit('FINDER_COPY')
hideFinder()
e.preventDefault()
}
@@ -174,6 +176,7 @@ var store = createStore(reducer)
window.onfocus = e => {
store.dispatch(refreshData())
+ activityRecord.emit('FINDER_OPEN')
}
ReactDOM.render((
diff --git a/browser/main/HomePage.js b/browser/main/HomePage.js
index da1f6162..57094068 100644
--- a/browser/main/HomePage.js
+++ b/browser/main/HomePage.js
@@ -1,6 +1,6 @@
import React, { PropTypes} from 'react'
import { connect } from 'react-redux'
-import { CREATE_MODE, EDIT_MODE, IDLE_MODE, NEW } from 'boost/actions'
+import { CREATE_MODE, EDIT_MODE, IDLE_MODE, NEW, toggleTutorial } from 'boost/actions'
// import UserNavigator from './HomePage/UserNavigator'
import ArticleNavigator from './HomePage/ArticleNavigator'
import ArticleTopBar from './HomePage/ArticleTopBar'
@@ -32,9 +32,15 @@ class HomePage extends React.Component {
return
}
- let { status } = this.props
+ let { status, dispatch } = this.props
let { nav, top, list, detail } = this.refs
+ if (status.isTutorialOpen) {
+ dispatch(toggleTutorial())
+ e.preventDefault()
+ return
+ }
+
// Search inputがfocusされていたら大体のキー入力は無視される。
if (top.isInputFocused() && !e.metaKey) {
if (e.keyCode === 13 || e.keyCode === 27) top.escape()
@@ -47,7 +53,7 @@ class HomePage extends React.Component {
if (e.keyCode === 27) {
detail.handleCancelButtonClick()
}
- if (e.keyCode === 13 && e.metaKey) {
+ if ((e.keyCode === 13 && e.metaKey) || (e.keyCode === 83 && e.metaKey)) {
detail.handleSaveButtonClick()
}
break
diff --git a/browser/main/HomePage/ArticleDetail.js b/browser/main/HomePage/ArticleDetail.js
index 5820ecd3..10c702c5 100644
--- a/browser/main/HomePage/ArticleDetail.js
+++ b/browser/main/HomePage/ArticleDetail.js
@@ -6,19 +6,71 @@ 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 aceModes from 'boost/ace-modes'
-import Select from 'react-select'
import linkState from 'boost/linkState'
import FolderMark from 'boost/components/FolderMark'
import TagLink from 'boost/components/TagLink'
import TagSelect from 'boost/components/TagSelect'
+import ModeSelect from 'boost/components/ModeSelect'
+import activityRecord from 'boost/activityRecord'
-var modeOptions = aceModes.map(function (mode) {
- return {
- label: mode,
- value: mode
- }
-})
+const BRAND_COLOR = '#18AF90'
+
+const editDeleteTutorialElement = (
+
+ Edit / Delete a post
+ press `e`/`d`
+
+
+
+
+
+
+
+)
+
+const tagSelectTutorialElement = (
+
+ Attach some tags here!
+
+
+
+
+
+
+
+
+)
+
+const modeSelectTutorialElement = (
+
+ Select code syntax!!
+
+
+
+
+
+
+
+)
function makeInstantArticle (article) {
return Object.assign({}, article)
@@ -93,6 +145,7 @@ export default class ArticleDetail extends React.Component {
let { dispatch, activeArticle } = this.props
dispatch(destroyArticle(activeArticle.key))
+ activityRecord.emit('ARTICLE_DESTROY')
this.setState({openDeleteConfirmMenu: false})
}
@@ -101,7 +154,7 @@ export default class ArticleDetail extends React.Component {
}
renderIdle () {
- let { activeArticle, folders } = this.props
+ let { status, activeArticle, folders } = this.props
let tags = activeArticle.tags != null ? activeArticle.tags.length > 0
? activeArticle.tags.map(tag => {
@@ -132,7 +185,7 @@ export default class ArticleDetail extends React.Component {
- {folder.name}
+ {folder.name}
Created : {moment(activeArticle.createdAt).format('YYYY/MM/DD')}
Updated : {moment(activeArticle.updatedAt).format('YYYY/MM/DD')}
@@ -146,6 +199,9 @@ export default class ArticleDetail extends React.Component {
Delete (d)
+
+ {status.isTutorialOpen ? editDeleteTutorialElement : null}
+
)
}
@@ -182,6 +238,12 @@ export default class ArticleDetail extends React.Component {
delete newArticle.status
newArticle.updatedAt = new Date()
+ if (newArticle.createdAt == null) {
+ newArticle.createdAt = new Date()
+ activityRecord.emit('ARTICLE_CREATE')
+ } else {
+ activityRecord.emit('ARTICLE_UPDATE')
+ }
dispatch(updateArticle(newArticle))
dispatch(switchMode(IDLE_MODE))
@@ -211,7 +273,16 @@ export default class ArticleDetail extends React.Component {
handleModeChange (value) {
let article = this.state.article
article.mode = value
- this.setState({article: article})
+ this.setState({
+ article: article,
+ previewMode: false
+ })
+ }
+
+ handleModeSelectBlur () {
+ if (this.refs.code != null) {
+ this.refs.code.editor.focus()
+ }
}
handleContentChange (e, value) {
@@ -224,8 +295,15 @@ export default class ArticleDetail extends React.Component {
this.setState({previewMode: !this.state.previewMode})
}
+ handleTitleKeyDown (e) {
+ if (e.keyCode === 9 && !e.shiftKey) {
+ e.preventDefault()
+ this.refs.mode.handleIdleSelectClick()
+ }
+ }
+
renderEdit () {
- let { folders } = this.props
+ let { folders, status } = this.props
let folderOptions = folders.map(folder => {
return (
@@ -249,7 +327,11 @@ export default class ArticleDetail extends React.Component {
tags={this.state.article.tags}
onChange={(tags, tag) => this.handleTagsChange(tags, tag)}
/>
+
+ {status.isTutorialOpen ? tagSelectTutorialElement : null}
+
+
{
this.state.article.mode === 'markdown'
@@ -264,22 +346,23 @@ export default class ArticleDetail extends React.Component {
{this.state.previewMode
?
: ( this.handleContentChange(e, value)}
readOnly={false}
mode={this.state.article.mode}
diff --git a/browser/main/HomePage/ArticleList.js b/browser/main/HomePage/ArticleList.js
index 5232ca79..4f48f48a 100644
--- a/browser/main/HomePage/ArticleList.js
+++ b/browser/main/HomePage/ArticleList.js
@@ -85,7 +85,7 @@ export default class ArticleList extends React.Component {
this.handleArticleClick(article)(e)} className={'articleItem' + (activeArticle.key === article.key ? ' active' : '')}>
{folder != null
- ?
{folder.name}
+ ?
{folder.name}
:
Unknown
}
{article.status != null ? article.status : moment(article.updatedAt).fromNow()}
diff --git a/browser/main/HomePage/ArticleNavigator.js b/browser/main/HomePage/ArticleNavigator.js
index 5f353e4a..17e37c45 100644
--- a/browser/main/HomePage/ArticleNavigator.js
+++ b/browser/main/HomePage/ArticleNavigator.js
@@ -9,6 +9,56 @@ import CreateNewFolder from 'boost/components/modal/CreateNewFolder'
import remote from 'remote'
let userName = remote.getGlobal('process').env.USER
+const BRAND_COLOR = '#18AF90'
+
+const preferenceTutorialElement = (
+
+ Preference
+
+
+
+
+)
+
+const newPostTutorialElement = (
+
+ Create a new post!!
+ press `⌘ + Enter` or `a`
+
+
+
+
+
+
+
+)
+
+const newFolderTutorialElement = (
+
+ Create a new folder!!
+
+
+
+
+
+)
+
export default class ArticleNavigator extends React.Component {
handlePreferencesButtonClick (e) {
openModal(Preferences)
@@ -56,11 +106,14 @@ export default class ArticleNavigator extends React.Component {
{userName}
-
local
+
localStorage
this.handlePreferencesButtonClick(e)} className='settingBtn'>
Preferences
+
+ {status.isTutorialOpen ? preferenceTutorialElement : null}
+
@@ -68,6 +121,9 @@ export default class ArticleNavigator extends React.Component {
New Post
Create a new Post (⌘ + Enter or a)
+
+ {status.isTutorialOpen ? newPostTutorialElement : null}
+
@@ -77,6 +133,9 @@ export default class ArticleNavigator extends React.Component {
Create a new folder
+
+ {status.isTutorialOpen ? newFolderTutorialElement : null}
+
this.handleAllFoldersButtonClick(e)} className={targetFolders.length === 0 ? 'active' : ''}>All folders
diff --git a/browser/main/HomePage/ArticleTopBar.js b/browser/main/HomePage/ArticleTopBar.js
index a7391d20..bfa38cae 100644
--- a/browser/main/HomePage/ArticleTopBar.js
+++ b/browser/main/HomePage/ArticleTopBar.js
@@ -1,9 +1,32 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import ExternalLink from 'boost/components/ExternalLink'
-import { setSearchFilter, clearSearch } from 'boost/actions'
-import { openModal } from 'boost/modal'
-import Tutorial from 'boost/components/modal/Tutorial'
+import { setSearchFilter, clearSearch, toggleTutorial } from 'boost/actions'
+
+const BRAND_COLOR = '#18AF90'
+
+const searchTutorialElement = (
+
+ Search some posts!!
+ {'- Search by tag : #{string}'}
+
+ {'- Search by folder : in:{folder_name}\n'}
+
+
+
+
+
+
+)
export default class ArticleTopBar extends React.Component {
constructor (props) {
@@ -66,10 +89,13 @@ export default class ArticleTopBar extends React.Component {
}
handleTutorialButtonClick (e) {
- openModal(Tutorial)
+ let { dispatch } = this.props
+
+ dispatch(toggleTutorial())
}
render () {
+ let { status } = this.props
return (
@@ -94,14 +120,34 @@ export default class ArticleTopBar extends React.Component {
- Search by folder : in:{'{folder_name}'}
+
+ {status.isTutorialOpen ? searchTutorialElement : null}
+
-
this.handleTutorialButtonClick(e)}>?How to use
+
this.handleTutorialButtonClick(e)}>?How to use
+
Boost official page
+
+ {status.isTutorialOpen ? (
+
+
this.handleTutorialButtonClick(e)} className='clickJammer'/>
+
+ Also, you can open Finder!!
+ with pressing `Control` + `shift` + `tab`
+
+
+ Hope you to enjoy our app :D
+ Press any key or click to escape tutorial mode
+
+
+
+ ) : null}
+
)
}
diff --git a/browser/main/index.js b/browser/main/index.js
index 29e05a9c..a749bdbe 100644
--- a/browser/main/index.js
+++ b/browser/main/index.js
@@ -10,6 +10,9 @@ import ReactDOM from 'react-dom'
require('../styles/main/index.styl')
import { openModal } from 'boost/modal'
import Tutorial from 'boost/components/modal/Tutorial'
+import activityRecord from 'boost/activityRecord'
+
+activityRecord.init()
let routes = (
diff --git a/browser/styles/main/HomeContainer/components/ArticleDetail.styl b/browser/styles/main/HomeContainer/components/ArticleDetail.styl
index c9536b16..0a3182f4 100644
--- a/browser/styles/main/HomeContainer/components/ArticleDetail.styl
+++ b/browser/styles/main/HomeContainer/components/ArticleDetail.styl
@@ -44,6 +44,11 @@ iptFocusBorderColor = #369DCD
.left
absolute top left bottom
right 120px
+ .folderName
+ display inline-block
+ max-width 100px
+ overflow ellipsis
+ height 10px
.right
absolute top right
.detailBody
@@ -80,6 +85,10 @@ iptFocusBorderColor = #369DCD
&.edit
.detailInfo
.left
+ &>.tutorial
+ position fixed
+ z-index 35
+ font-style italic
.folder
border none
width 150px
@@ -94,6 +103,8 @@ iptFocusBorderColor = #369DCD
position relative
margin-top 5px
noSelect()
+ z-index 30
+ background-color #E6E6E6
.tagItem
background-color brandColor
border-radius 2px
@@ -150,16 +161,73 @@ iptFocusBorderColor = #369DCD
.detailBody
.detailPanel
&>.header
+ &>.tutorial
+ fixed right
+ z-index 35
+ font-style italic
.mode
+ position relative
+ z-index 30
absolute top bottom right
display block
height 33px
margin-top 12px
- width 120px
+ width 150px
margin-right 15px
+ border-radius 5px
+ border solid 1px borderColor
+ transition 0.1s
+ &.idle
+ background-color darken(white, 5%)
+ cursor pointer
+ &:hover
+ background-color white
+ .ModeIcon
+ float left
+ width 25px
+ line-height 33px
+ text-align center
+ .modeLabel
+ line-height 30px
+ &.edit
+ background-color white
+ input
+ width 150px
+ line-height 30px
+ padding 0 10px
+ border none
+ outline none
+ background-color transparent
+ font-size 14px
+ .modeOptions
+ position fixed
+ width 150px
+ z-index 10
+ margin-top 5px
+ border 1px solid borderColor
+ border-radius 5px
+ background-color white
+ max-height 250px
+ overflow-y auto
+ .option
+ height 33px
+ line-height 33px
+ cursor pointer
+ &.active, &:hover.active
+ background-color brandColor
+ color white
+ .ModeIcon
+ width 30px
+ text-align center
+ display inline-block
+ &:hover
+ background-color darken(white, 10%)
+
+
+
.title
absolute left top bottom
- right 120px
+ right 150px
padding 0 15px
input
width 100%
@@ -170,6 +238,10 @@ iptFocusBorderColor = #369DCD
outline none
&.idle
.detailInfo
+ &>.tutorial
+ fixed top right
+ z-index 35
+ font-style italic
.left
right 99px
.info
@@ -192,14 +264,17 @@ iptFocusBorderColor = #369DCD
span.noTags
color noTagsColor
.right
+ z-index 30
button
+ border-radius 16.5px
cursor pointer
height 33px
width 33px
border none
+ margin-right 5px
font-size 18px
color inactiveTextColor
- background-color transparent
+ background-color darken(white, 5%)
padding 0
.tooltip
tooltip()
@@ -210,7 +285,7 @@ iptFocusBorderColor = #369DCD
margin-top 25px
margin-left -73px
&:hover
- color inherit
+ color textColor
.tooltip
opacity 1
.detailBody
diff --git a/browser/styles/main/HomeContainer/components/ArticleList.styl b/browser/styles/main/HomeContainer/components/ArticleList.styl
index 071fee3e..6617773a 100644
--- a/browser/styles/main/HomeContainer/components/ArticleList.styl
+++ b/browser/styles/main/HomeContainer/components/ArticleList.styl
@@ -26,23 +26,28 @@ articleItemColor = #777
line-height 20px
padding 5px 0
color articleItemColor
- .profileImage
- vertical-align middle
+ .folderName
+ overflow ellipsis
+ display inline-block
+ width 120px
.updatedAt
float right
line-height 20px
.middle
- clearfix()
padding 3px 0 7px
font-size 16px
+ position relative
+ height 26px
.mode
- float left
+ position absolute
+ left 0
font-size 12px
line-height 16px
.title
- float left
+ position absolute
+ left 19px
+ right 0
overflow ellipsis
- padding 0 5px
.bottom
padding 5px 0
overflow-x auto
diff --git a/browser/styles/main/HomeContainer/components/ArticleNavigator.styl b/browser/styles/main/HomeContainer/components/ArticleNavigator.styl
index 05cb70a2..77b19b03 100644
--- a/browser/styles/main/HomeContainer/components/ArticleNavigator.styl
+++ b/browser/styles/main/HomeContainer/components/ArticleNavigator.styl
@@ -21,6 +21,16 @@ articleNavBgColor = #353535
color white
padding-left 20px
margin-top 3px
+ .tutorial
+ position fixed
+ z-index 35
+ top 0
+ left 0
+ pointer-event none
+ font-style italic
+ transition 0.1s
+ &.hide
+ opacity 0
.settingBtn
width 22px
height 22px
@@ -33,6 +43,7 @@ articleNavBgColor = #353535
padding 0
background-color transparent
border 1px solid white
+ z-index 31
.tooltip
tooltip()
margin-top -5px
@@ -47,7 +58,16 @@ articleNavBgColor = #353535
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
@@ -56,6 +76,7 @@ articleNavBgColor = #353535
border-radius 5px
font-size 20px
transition 0.1s
+ z-index 30
.tooltip
tooltip()
margin-left 48px
@@ -70,6 +91,8 @@ articleNavBgColor = #353535
padding-bottom 5px
margin-bottom 10px
clearfix()
+ position relative
+ z-index 30
.title
float left
padding-left 10px
@@ -100,9 +123,18 @@ articleNavBgColor = #353535
background-color brandColor
border-color brandColor
.folders
- margin-bottom 15px
+ absolute bottom
+ top 200px
+ width 100%
+ .header
+ .tutorial
+ position fixed
+ z-index 35px
+ top 200px
+ font-style italic
.folderList
- height 340px
+ absolute bottom
+ top 38px
overflow-y auto
.folderList button
height 33px
diff --git a/browser/styles/main/HomeContainer/components/ArticleTopBar.styl b/browser/styles/main/HomeContainer/components/ArticleTopBar.styl
index 56e21827..1d53d822 100644
--- a/browser/styles/main/HomeContainer/components/ArticleTopBar.styl
+++ b/browser/styles/main/HomeContainer/components/ArticleTopBar.styl
@@ -15,8 +15,34 @@ infoBtnActiveBgColor = #3A3A3A
left 200px
height 60px
background-color bgColor
+ &>.tutorial
+ .clickJammer
+ fixed top left bottom right
+ z-index 40
+ background transparent
+ .global
+ fixed bottom right
+ height 100px
+ z-index 35
+ font-style italic
+ .finder
+ fixed bottom right
+ height 250px
+ left 50%
+ margin-left -250px
+ z-index 35
+ font-style italic
+ .back
+ fixed top left bottom right
+ z-index 20
+ background-color transparentify(black, 80%)
&>.left
float left
+ &>.tutorial
+ fixed top
+ left 200px
+ z-index 36
+ font-style italic
&>.search
position relative
float left
@@ -28,6 +54,7 @@ infoBtnActiveBgColor = #3A3A3A
transition 0.1s
font-size 16px
border 1px solid transparent
+ z-index 30
.tooltip
tooltip()
margin-left -24px
diff --git a/browser/styles/main/HomeContainer/lib/Preferences.styl b/browser/styles/main/HomeContainer/lib/Preferences.styl
index 3dbdbd14..79fe0e56 100644
--- a/browser/styles/main/HomeContainer/lib/Preferences.styl
+++ b/browser/styles/main/HomeContainer/lib/Preferences.styl
@@ -103,6 +103,11 @@ iptFocusBorderColor = #369DCD
font-size 14px
&:hover
background-color lighten(brandColor, 10%)
+ .alert
+ float right
+ width 250px
+ padding 10px 15px
+ margin 0 10px 0
.alert
color infoTextColor
background-color infoBackgroundColor
@@ -114,6 +119,70 @@ iptFocusBorderColor = #369DCD
&.error
color errorTextColor
background-color errorBackgroundColor
+ &.ContactTab
+ &.done
+ .message
+ margin-top 75px
+ margin-bottom 15px
+ text-align center
+ font-size 22px
+ .checkIcon
+ margin-bottom 15px
+ font-size 144px
+ color brandColor
+ text-align center
+ .control
+ text-align center
+ button
+ border solid 1px borderColor
+ border-radius 5px
+ background-color white
+ padding 15px 15px
+ font-size 14px
+ &:hover
+ background-color darken(white, 10%)
+ &.form
+ padding 10px
+ .title
+ font-size 18px
+ color brandColor
+ margin-top 10px
+ margin-bottom 10px
+ .description
+ margin-bottom 15px
+ .iptGroup
+ margin-bottom 10px
+ input, textarea
+ border-radius 5px
+ border 1px solid borderColor
+ font-size 14px
+ outline none
+ padding 10px 15px
+ width 100%
+ &:focus
+ border-color iptFocusBorderColor
+ textarea
+ resize vertical
+ min-height 150px
+ .formControl
+ clearfix()
+ .alert
+ float right
+ padding 10px 15px
+ margin 0 5px 0
+ font-size 14px
+ line-height normal
+ button
+ padding 10px 15px
+ background-color brandColor
+ color white
+ font-size 14px
+ border-radius 5px
+ border none
+ float right
+ &:hover
+ background-color lighten(brandColor, 10%)
+
&.AppSettingTab
.description
marked()
@@ -374,6 +443,7 @@ iptFocusBorderColor = #369DCD
.folderName
float left
width 175px
+ overflow ellipsis
padding-left 15px
.folderPublic
float left
@@ -480,6 +550,7 @@ iptFocusBorderColor = #369DCD
height 33px
width 250px
padding-left 15px
+ overflow ellipsis
strong
font-size 16px
color brandColor
diff --git a/browser/styles/main/HomeContainer/lib/Tutorial.styl b/browser/styles/main/HomeContainer/lib/Tutorial.styl
index ced4547c..913bfbe5 100644
--- a/browser/styles/main/HomeContainer/lib/Tutorial.styl
+++ b/browser/styles/main/HomeContainer/lib/Tutorial.styl
@@ -109,8 +109,9 @@ slideBgColor4 = #00B493
.slide3
background-color slideBgColor3
.content
+ font-size 18px
&>img
- margin-top 45px
+ margin-top 25px
.slide4
background-color slideBgColor4
.content
diff --git a/lib/ace-modes.js b/lib/ace-modes.js
deleted file mode 100644
index 98cc119d..00000000
--- a/lib/ace-modes.js
+++ /dev/null
@@ -1,16 +0,0 @@
-var fs = require('fs')
-var path = require('path')
-
-var rootUrl = process.cwd()
-if (rootUrl === '/') rootUrl = require('remote').require('app').getAppPath()
-var url = path.resolve(rootUrl, './submodules/ace/src-min')
-console.log(url)
-
-module.exports = fs.readdirSync(url)
- .filter(function (file) {
- return file.match(/^mode-/)
- })
- .map(function (file) {
- var match = file.match(/^mode-([a-z0-9\_]+).js$/)
- return match[1]
- })
diff --git a/lib/actions.js b/lib/actions.js
index ba0f668e..d99aa2e4 100644
--- a/lib/actions.js
+++ b/lib/actions.js
@@ -11,6 +11,7 @@ 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 TOGGLE_TUTORIAL = 'TOGGLE_TUTORIAL'
// Status - mode
export const IDLE_MODE = 'IDLE_MODE'
@@ -96,3 +97,9 @@ export function clearSearch () {
type: CLEAR_SEARCH
}
}
+
+export function toggleTutorial() {
+ return {
+ type: TOGGLE_TUTORIAL
+ }
+}
diff --git a/lib/activityRecord.js b/lib/activityRecord.js
new file mode 100644
index 00000000..71e61a82
--- /dev/null
+++ b/lib/activityRecord.js
@@ -0,0 +1,122 @@
+import _ from 'lodash'
+import moment from 'moment'
+import keygen from 'boost/keygen'
+import dataStore from 'boost/dataStore'
+import { request, WEB_URL } from 'boost/api'
+
+function isSameDate (a, b) {
+ a = moment(a).utcOffset(+540).format('YYYYMMDD')
+ b = moment(b).utcOffset(+540).format('YYYYMMDD')
+
+ return a === b
+}
+
+export function init () {
+ let records = getAllRecords()
+ if (records == null) {
+ saveAllRecords([])
+ }
+
+ postRecords()
+ if (window != null) {
+ window.addEventListener('online', postRecords)
+ window.setInterval(postRecords, 1000 * 60 * 60 * 24)
+ }
+}
+
+export function getClientKey () {
+ let clientKey = localStorage.getItem('clientKey')
+ if (!_.isString(clientKey) || clientKey.length !== 40) {
+ clientKey = keygen()
+ localStorage.setItem('clientKey', clientKey)
+ }
+
+ return clientKey
+}
+
+export function getAllRecords () {
+ return JSON.parse(localStorage.getItem('activityRecords'))
+}
+
+export function saveAllRecords (records) {
+ localStorage.setItem('activityRecords', JSON.stringify(records))
+}
+
+/*
+Post all records(except today)
+and remove all posted records
+*/
+export function postRecords (data) {
+ let records = getAllRecords()
+ records = records.filter(record => {
+ return !isSameDate(new Date(), record.date)
+ })
+
+ if (records.length === 0) {
+ console.log('No records to post')
+ return
+ }
+
+ console.log('posting...', records)
+ let input = {
+ clientKey: getClientKey(),
+ records
+ }
+ return request.post(WEB_URL + 'apis/activity')
+ .send(input)
+ .then(res => {
+ let records = getAllRecords()
+ let todayRecord = _.find(records, record => {
+ return isSameDate(new Date(), record.date)
+ })
+ if (todayRecord != null) saveAllRecords([todayRecord])
+ else saveAllRecords([])
+ })
+ .catch(err => {
+ console.error(err)
+ })
+}
+
+export function emit (type, data) {
+ let records = getAllRecords()
+
+ let index = _.findIndex(records, record => {
+ return isSameDate(new Date(), record.date)
+ })
+
+ let todayRecord
+ if (index < 0) {
+ todayRecord = {date: new Date()}
+ records.push(todayRecord)
+ }
+ else todayRecord = records[index]
+ console.log(type)
+ switch (type) {
+ case 'ARTICLE_CREATE':
+ case 'ARTICLE_UPDATE':
+ case 'ARTICLE_DESTROY':
+ case 'FOLDER_CREATE':
+ case 'FOLDER_UPDATE':
+ case 'FOLDER_DESTROY':
+ case 'FINDER_OPEN':
+ case 'FINDER_COPY':
+ todayRecord[type] = todayRecord[type] == null
+ ? 1
+ : todayRecord[type] + 1
+
+ break
+ }
+
+ let storeData = dataStore.getData()
+ todayRecord.FOLDER_COUNT = _.isArray(storeData.folders) ? storeData.folders.length : 0
+ todayRecord.ARTICLE_COUNT = _.isArray(storeData.articles) ? storeData.articles.length : 0
+
+ saveAllRecords(records)
+}
+
+export default {
+ init,
+ emit,
+ getClientKey,
+ postRecords
+}
diff --git a/lib/api.js b/lib/api.js
index 5a3ffa8f..6a5f9f91 100644
--- a/lib/api.js
+++ b/lib/api.js
@@ -1,9 +1,12 @@
import superagent from 'superagent'
import superagentPromise from 'superagent-promise'
-import { API_URL } from '../config'
import auth from 'boost/auth'
-const request = superagentPromise(superagent, Promise)
+export const API_URL = 'http://boost-api4.elasticbeanstalk.com/'
+export const WEB_URL = 'https://b00st.io/'
+// export const WEB_URL = 'http://localhost:3333/'
+
+export const request = superagentPromise(superagent, Promise)
export function login (input) {
return request
@@ -163,6 +166,9 @@ export function sendEmail (input) {
}
export default {
+ API_URL,
+ WEB_URL,
+ request,
login,
signup,
updateUserInfo,
diff --git a/lib/components/CodeEditor.js b/lib/components/CodeEditor.js
index 5bde5639..d82ca3f2 100644
--- a/lib/components/CodeEditor.js
+++ b/lib/components/CodeEditor.js
@@ -1,5 +1,7 @@
import React from 'react'
import ReactDOM from 'react-dom'
+import modes from 'boost/vars/modes'
+import _ from 'lodash'
var ace = window.ace
module.exports = React.createClass({
@@ -32,11 +34,12 @@ module.exports = React.createClass({
editor.setReadOnly(!!this.props.readOnly)
var session = editor.getSession()
- if (this.props.mode != null && this.props.mode.length > 0) {
- session.setMode('ace/mode/' + this.props.mode)
- } else {
- session.setMode('ace/mode/text')
- }
+ let mode = _.findWhere(modes, {name: this.props.mode})
+ let syntaxMode = mode != null
+ ? mode.mode
+ : 'text'
+ session.setMode('ace/mode/' + syntaxMode)
+
session.setUseSoftTabs(true)
session.setOption('useWorker', false)
session.setUseWrapMode(true)
@@ -57,11 +60,11 @@ module.exports = React.createClass({
}
if (prevProps.mode !== this.props.mode) {
var session = this.state.editor.getSession()
- if (this.props.mode != null && this.props.mode.length > 0) {
- session.setMode('ace/mode/' + this.props.mode)
- } else {
- session.setMode('ace/mode/text')
- }
+ let mode = _.findWhere(modes, {name: this.props.mode})
+ let syntaxMode = mode != null
+ ? mode.mode
+ : 'text'
+ session.setMode('ace/mode/' + syntaxMode)
}
},
render: function () {
diff --git a/lib/components/ModeIcon.js b/lib/components/ModeIcon.js
index 0b3e4886..9fe48390 100644
--- a/lib/components/ModeIcon.js
+++ b/lib/components/ModeIcon.js
@@ -32,8 +32,10 @@ export default class ModeIcon extends React.Component {
return 'devicon-sass-original'
// Compile
- case 'c_cpp':
+ case 'c':
return 'devicon-c-plain'
+ case 'cpp':
+ return 'devicon-cplusplus-plain'
case 'csharp':
return 'devicon-csharp-plain'
case 'objc':
@@ -60,7 +62,6 @@ export default class ModeIcon extends React.Component {
return 'fa fa-fw fa-terminal'
case 'text':
- case 'plain_text':
case 'markdown':
return 'fa fa-fw fa-file-text-o'
}
@@ -68,9 +69,9 @@ export default class ModeIcon extends React.Component {
}
render () {
- var className = this.getClassName()
+ let className = `ModeIcon ${this.getClassName()} ${this.props.className}`
return (
-
+
)
}
}
diff --git a/lib/components/ModeSelect.js b/lib/components/ModeSelect.js
new file mode 100644
index 00000000..554e361c
--- /dev/null
+++ b/lib/components/ModeSelect.js
@@ -0,0 +1,190 @@
+import React, { PropTypes } from 'react'
+import ReactDOM from 'react-dom'
+import ModeIcon from 'boost/components/ModeIcon'
+import modes from 'boost/vars/modes'
+import _ from 'lodash'
+
+const IDLE_MODE = 'IDLE_MODE'
+const EDIT_MODE = 'EDIT_MODE'
+
+export default class ModeSelect extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ mode: IDLE_MODE,
+ search: '',
+ focusIndex: 0
+ }
+ }
+
+ componentDidMount (e) {
+ this.blurHandler = e => {
+ let searchElement = ReactDOM.findDOMNode(this.refs.search)
+ if (this.state.mode === EDIT_MODE && document.activeElement !== searchElement) {
+ this.handleBlur()
+ }
+ }
+ window.addEventListener('click', this.blurHandler)
+ }
+
+ componentWillUnmount (e) {
+ window.removeEventListener('click', this.blurHandler)
+ let searchElement = ReactDOM.findDOMNode(this.refs.search)
+ if (searchElement != null && this.searchKeyDownListener != null) {
+ searchElement.removeEventListener('keydown', this.searchKeyDownListener)
+ }
+ }
+
+ handleIdleSelectClick (e) {
+ this.setState({mode: EDIT_MODE})
+ }
+
+ componentDidUpdate (prevProps, prevState) {
+ if (prevState.mode !== this.state.mode && this.state.mode === EDIT_MODE) {
+ let searchElement = ReactDOM.findDOMNode(this.refs.search)
+ searchElement.focus()
+ if (this.searchKeyDownListener == null) {
+ this.searchKeyDownListener = e => this.handleSearchKeyDown
+ }
+ searchElement.addEventListener('keydown', this.searchKeyDownListener)
+ }
+ }
+
+ componentWillUpdate (nextProps, nextState) {
+ if (nextProps.mode !== this.state.mode && nextState.mode === IDLE_MODE) {
+ let searchElement = ReactDOM.findDOMNode(this.refs.search)
+ if (searchElement != null && this.searchKeyDownListener != null) {
+ searchElement.removeEventListener('keydown', this.searchKeyDownListener)
+ }
+ }
+ }
+
+ handleModeOptionClick (modeName) {
+ return e => {
+ this.props.onChange(modeName)
+ this.setState({
+ mode: IDLE_MODE,
+ search: '',
+ focusIndex: 0
+ })
+ }
+ }
+
+ handleSearchKeyDown (e) {
+ switch (e.keyCode) {
+ // up
+ case 38:
+ e.preventDefault()
+ if (this.state.focusIndex > 0) this.setState({focusIndex: this.state.focusIndex - 1})
+ break
+ // down
+ case 40:
+ e.preventDefault()
+ {
+ let filteredModes = modes
+ .filter(mode => {
+ let search = this.state.search
+ let nameMatched = mode.name.match(search)
+ let aliasMatched = _.some(mode.alias, alias => alias.match(search))
+ return nameMatched || aliasMatched
+ })
+ if (filteredModes.length === this.state.focusIndex + 1) this.setState({focusIndex: filteredModes.length - 1})
+ else this.setState({focusIndex: this.state.focusIndex + 1})
+ }
+ break
+ // enter
+ case 13:
+ e.preventDefault()
+ {
+ let filteredModes = modes
+ .filter(mode => {
+ let search = this.state.search
+ let nameMatched = mode.name.match(search)
+ let aliasMatched = _.some(mode.alias, alias => alias.match(search))
+ return nameMatched || aliasMatched
+ })
+ let targetMode = filteredModes[this.state.focusIndex]
+ if (targetMode != null) {
+ this.props.onChange(targetMode.name)
+ this.handleBlur()
+ }
+ }
+ break
+ // esc
+ case 27:
+ e.preventDefault()
+ e.stopPropagation()
+ this.handleBlur()
+ break
+ case 9:
+ this.handleBlur()
+ }
+ }
+
+ handleSearchChange (e) {
+ this.setState({
+ search: e.target.value,
+ focusIndex: 0
+ })
+ }
+
+ handleBlur () {
+ if (this.state.mode === EDIT_MODE) {
+ this.setState({
+ mode: IDLE_MODE,
+ search: '',
+ focusIndex: 0
+ })
+ }
+ if (this.props.onBlur != null) this.props.onBlur()
+ }
+
+ render () {
+ let className = this.props.className != null
+ ? `ModeSelect ${this.props.className}`
+ : this.props.className
+
+ if (this.state.mode === IDLE_MODE) {
+ let mode = _.findWhere(modes, {name: this.props.value})
+ let modeName = mode != null ? mode.name : 'text'
+ let modeLabel = mode != null ? mode.label : 'Plain text'
+
+ return (
+ this.handleIdleSelectClick(e)}>
+
+ {modeLabel}
+
+ )
+ }
+
+ let filteredOptions = modes
+ .filter(mode => {
+ let search = this.state.search
+ let nameMatched = mode.name.match(search)
+ let aliasMatched = _.some(mode.alias, alias => alias.match(search))
+ return nameMatched || aliasMatched
+ })
+ .map((mode, index) => {
+ return (
+ this.handleModeOptionClick(mode.name)(e)}>{mode.label}
+ )
+ })
+
+ return (
+
+
this.handleSearchKeyDown(e)} ref='search' onChange={e => this.handleSearchChange(e)} value={this.state.search} type='text'/>
+
+ {filteredOptions}
+
+
+ )
+ }
+}
+
+ModeSelect.propTypes = {
+ className: PropTypes.string,
+ value: PropTypes.string,
+ onChange: PropTypes.func,
+ onBlur: PropTypes.func
+}
diff --git a/lib/components/modal/Preference/AppSettingTab.js b/lib/components/modal/Preference/AppSettingTab.js
index f96b01db..77bec059 100644
--- a/lib/components/modal/Preference/AppSettingTab.js
+++ b/lib/components/modal/Preference/AppSettingTab.js
@@ -9,10 +9,33 @@ export default class AppSettingTab extends React.Component {
let keymap = remote.getGlobal('keymap')
this.state = {
- toggleFinder: keymap.toggleFinder
+ toggleFinder: keymap.toggleFinder,
+ alert: null
}
}
+ componentDidMount () {
+ this.handleSettingDone = () => {
+ this.setState({alert: {
+ type: 'success',
+ message: 'Successfully done!'
+ }})
+ }
+ this.handleSettingError = err => {
+ this.setState({alert: {
+ type: 'error',
+ message: err.message
+ }})
+ }
+ ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
+ ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
+ }
+
+ componentWillUnmount () {
+ ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
+ ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
+ }
+
handleSaveButtonClick (e) {
ipc.send('hotkeyUpdated', {
toggleFinder: this.state.toggleFinder
@@ -20,6 +43,13 @@ export default class AppSettingTab extends React.Component {
}
render () {
+ let alert = this.state.alert
+ let alertElement = alert != null ? (
+
+ {alert.message}
+
+ ) : null
+
return (
@@ -30,6 +60,7 @@ export default class AppSettingTab extends React.Component {
this.handleSaveButtonClick(e)}>Save
+ {alertElement}
diff --git a/lib/components/modal/Preference/ContactTab.js b/lib/components/modal/Preference/ContactTab.js
new file mode 100644
index 00000000..0de972a3
--- /dev/null
+++ b/lib/components/modal/Preference/ContactTab.js
@@ -0,0 +1,123 @@
+import React from 'react'
+import ReactDOM from 'react-dom'
+import { getClientKey } from 'boost/activityRecord'
+import linkState from 'boost/linkState'
+import _ from 'lodash'
+import { request, WEB_URL } from 'boost/api'
+
+const FORM_MODE = 'FORM_MODE'
+const DONE_MODE = 'DONE_MODE'
+
+export default class ContactTab extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ title: '',
+ content: '',
+ email: '',
+ mode: FORM_MODE,
+ alert: null
+ }
+ }
+
+ componentDidMount () {
+ let titleInput = ReactDOM.findDOMNode(this.refs.title)
+ if (titleInput != null) titleInput.focus()
+ }
+
+ handleBackButtonClick (e) {
+ this.setState({
+ mode: FORM_MODE
+ })
+ }
+
+ handleSendButtonClick (e) {
+ let input = _.pick(this.state, ['title', 'content', 'email'])
+ input.clientKey = getClientKey()
+
+ this.setState({
+ alert: {
+ type: 'info',
+ message: 'Sending...'
+ }
+ }, () => {
+ request.post(WEB_URL + 'apis/inquiry')
+ .send(input)
+ .then(res => {
+ console.log('sent')
+ this.setState({
+ title: '',
+ content: '',
+ mode: DONE_MODE,
+ alert: null
+ })
+ })
+ .catch(err => {
+ if (err.code === 'ECONNREFUSED') {
+ this.setState({
+ alert: {
+ type: 'error',
+ message: 'Can\'t connect to API server.'
+ }
+ })
+ } else {
+ console.error(err)
+ this.setState({
+ alert: {
+ type: 'error',
+ message: err.message
+ }
+ })
+ }
+ })
+ })
+ }
+
+ render () {
+ switch (this.state.mode) {
+ case DONE_MODE:
+ return (
+
+
+
+ Your message has been sent successfully!!
+
+
+ this.handleBackButtonClick(e)}>Back to Contact form
+
+
+ )
+ case FORM_MODE:
+ default:
+ let alertElement = this.state.alert != null
+ ? (
+ {this.state.alert.message}
+ )
+ : null
+ return (
+
+
Contact form
+
+ Your feedback is highly appreciated and will help us to improve our app. :D
+
+
+
+
+
+
+
+
+
+
+
+ this.handleSendButtonClick(e)} className='primary'>Send
+ {alertElement}
+
+
+ )
+ }
+ }
+}
+
+ContactTab.prototype.linkState = linkState
diff --git a/lib/components/modal/Preferences.js b/lib/components/modal/Preferences.js
index edda1b0b..c2a34d0c 100644
--- a/lib/components/modal/Preferences.js
+++ b/lib/components/modal/Preferences.js
@@ -5,11 +5,13 @@ import store from 'boost/store'
import AppSettingTab from './Preference/AppSettingTab'
import HelpTab from './Preference/HelpTab'
import FolderSettingTab from './Preference/FolderSettingTab'
+import ContactTab from './Preference/ContactTab'
import { closeModal } from 'boost/modal'
const APP = 'APP'
const HELP = 'HELP'
const FOLDER = 'FOLDER'
+const CONTACT = 'CONTACT'
class Preferences extends React.Component {
constructor (props) {
@@ -35,7 +37,8 @@ class Preferences extends React.Component {
let tabs = [
{target: APP, label: 'Preferences'},
- {target: FOLDER, label: 'Manage folder'}
+ {target: FOLDER, label: 'Manage folder'},
+ {target: CONTACT, label: 'Contact form'}
]
let navButtons = tabs.map(tab => (
@@ -71,6 +74,10 @@ class Preferences extends React.Component {
folders={folders}
/>
)
+ case CONTACT:
+ return (
+
+ )
case APP:
default:
return ( )
@@ -239,7 +246,6 @@ Preferences.prototype.linkState = linkState
function remap (state) {
let { folders, status } = state
- console.log(state)
return {
folders,
diff --git a/lib/components/modal/Tutorial.js b/lib/components/modal/Tutorial.js
index 1891ebd1..cb2f2ed4 100644
--- a/lib/components/modal/Tutorial.js
+++ b/lib/components/modal/Tutorial.js
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, { PropTypes } from 'react'
import MarkdownPreview from 'boost/components/MarkdownPreview'
import CodeEditor from 'boost/components/CodeEditor'
@@ -88,8 +88,11 @@ export default class Tutorial extends React.Component {
return (
Easy to access with Finder
- Finder is a small popup window.
- With finder, You can search your articles faster.
+ With Finder, You can search your articles faster.
+ You can open Finder by pressing Control + shift + tab
+ To put the content of an article in the clipboard, press Enter.
+ So you can paste it with Cmd(⌘) + V
+
)
@@ -105,3 +108,7 @@ export default class Tutorial extends React.Component {
}
}
}
+
+Tutorial.propTypes = {
+ close: PropTypes.func
+}
diff --git a/lib/dataStore.js b/lib/dataStore.js
index a142f6c2..f05be868 100644
--- a/lib/dataStore.js
+++ b/lib/dataStore.js
@@ -1,6 +1,6 @@
import keygen from 'boost/keygen'
-let defaultContent = '**Boost**は全く新しいエンジニアライクのノートアプリです。\n\n# ◎特徴\nBoostはエンジニアの仕事を圧倒的に効率化するいくつかの機能を備えています。\nその一部をご紹介します。\n1. Folderで情報を分類\n2. 豊富なsyantaxに対応\n3. Finder機能\n4. チーム機能(リアルタイム搭載)\n\n* * * *\n\n# 1. Folderで情報を分類、欲しい情報にすぐアクセス。\n左側のバーに存在する「Folders」。\n今すぐプラスボタンを押しましょう。\n分類の仕方も自由自在です。\n- 言語やフレームワークごとにFolderを作成\n- 自分用のカジュアルなメモをまとめる場としてFolderを作成\n\n\n# 2. 豊富なsyantaxに対応、自分の脳の代わりに。\nプログラミングに関する情報を全て、手軽に保存しましょう。\n- mdで、apiの仕様をまとめる\n- よく使うモジュールやスニペット\n\nBoostに保存しておくことで、何度も同じコードを書いたり調べたりする必要がなくなります。\n\n# 3. Finder機能を搭載、もうコマンドを手打ちする必要はありません。\n**「shift+cmd+tab」** を同時に押してみてください。\nここでは、一瞬でBoostの中身を検索するウィンドウを表示させることができます。\n\n矢印キーで選択、Enterを押し、cmd+vでペーストすると…続きはご自身の目でお確かめください。\n- sqlやlinux等の、よく使うが手打ちが面倒なコマンド\n- (メールやカスタマーサポート等でよく使うフレーズ)\n\n私たちは、圧倒的な効率性を支援します。\n\* * * *\n\n\n## ◎詳しくは\nこちらのブログ( http://blog-jp.b00st.io )にて随時更新しています。\n\nそれでは素晴らしいエンジニアライフを!\n\n## Hack your memory**'
+let defaultContent = 'Boost is a brand new note App for programmers.\n\n> 下に日本語版があります。\n\n# \u25CEfeature\n\nBoost has some preponderant functions for efficient engineer\'s task.See some part of it.\n\n1. classify information by\u300CFolders\u300D\n2. deal with great variety of syntax\n3. Finder function\n\n\uFF0A\u3000\uFF0A\u3000\uFF0A\u3000\uFF0A\n\n# 1. classify information by \u300CFolders\u300D- access the information you needed easily.\n\n\u300CFolders\u300D which on the left side bar. Press plus button now. flexible way of classification.\n- Create Folder every language or flamework\n- Make Folder for your own casual memos\n\n# 2. Deal with a great variety of syntax \u2013 instead of your brain\nSave handy all information related with programming\n- Use markdown and gather api specification\n- Well using module and snippet\n\nSave them on Boost, you don\'t need to rewrite or re-search same code again.\n\n# 3. Load Finder function \u2013 now you don\'t need to spell command by hand typing.\n\n**Shift +cmd+tab** press buttons at same time.\nThen, the window will show up for search Boost contents that instant.\n\nUsing cursor key to chose, press enter, cmd+v to paste and\u2026 please check it out by your own eye.\n\n- Such command spl or linux which programmers often use but troublesome to hand type\n\n- (Phrases commonly used for e-mail or customer support)\n\nWe support preponderant efficiency\n\n\uFF0A\u3000\uFF0A\u3000\uFF0A\u3000\uFF0A\n\n## \u25CEfor more information\nFrequently updated with this blog ( http:\/\/blog-jp.b00st.io )\n\nHave wonderful programmer life!\n\n## Hack your memory**\n\n\n\n# 日本語版\n\n**Boost**は全く新しいエンジニアライクのノートアプリです。\n\n# ◎特徴\nBoostはエンジニアの仕事を圧倒的に効率化するいくつかの機能を備えています。\nその一部をご紹介します。\n1. Folderで情報を分類\n2. 豊富なsyantaxに対応\n3. Finder機能\n4. チーム機能(リアルタイム搭載)\n\n* * * *\n\n# 1. Folderで情報を分類、欲しい情報にすぐアクセス。\n左側のバーに存在する「Folders」。\n今すぐプラスボタンを押しましょう。\n分類の仕方も自由自在です。\n- 言語やフレームワークごとにFolderを作成\n- 自分用のカジュアルなメモをまとめる場としてFolderを作成\n\n\n# 2. 豊富なsyntaxに対応、自分の脳の代わりに。\nプログラミングに関する情報を全て、手軽に保存しましょう。\n- mdで、apiの仕様をまとめる\n- よく使うモジュールやスニペット\n\nBoostに保存しておくことで、何度も同じコードを書いたり調べたりする必要がなくなります。\n\n# 3. Finder機能を搭載、もうコマンドを手打ちする必要はありません。\n**「shift+cmd+tab」** を同時に押してみてください。\nここでは、一瞬でBoostの中身を検索するウィンドウを表示させることができます。\n\n矢印キーで選択、Enterを押し、cmd+vでペーストすると…続きはご自身の目でお確かめください。\n- sqlやlinux等の、よく使うが手打ちが面倒なコマンド\n- (メールやカスタマーサポート等でよく使うフレーズ)\n\n私たちは、圧倒的な効率性を支援します。\n\* * * *\n\n\n## ◎詳しくは\nこちらのブログ( http://blog-jp.b00st.io )にて随時更新しています。\n\nそれでは素晴らしいエンジニアライフを!\n\n## Hack your memory**'
export function init () {
console.log('initialize data store')
@@ -11,7 +11,7 @@ export function init () {
key: keygen()
}
let defaultArticle = {
- title: 'Boostとは',
+ title: 'About Boost',
tags: ['boost', 'intro'],
content: defaultContent,
mode: 'markdown',
diff --git a/lib/reducer.js b/lib/reducer.js
index 728f4384..52cd5916 100644
--- a/lib/reducer.js
+++ b/lib/reducer.js
@@ -1,12 +1,14 @@
import { combineReducers } from 'redux'
import _ from 'lodash'
-import { SWITCH_FOLDER, SWITCH_MODE, SWITCH_ARTICLE, SET_SEARCH_FILTER, SET_TAG_FILTER, CLEAR_SEARCH, ARTICLE_UPDATE, ARTICLE_DESTROY, FOLDER_CREATE, FOLDER_UPDATE, FOLDER_DESTROY, IDLE_MODE, CREATE_MODE } from './actions'
+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 dataStore from 'boost/dataStore'
import keygen from 'boost/keygen'
+import activityRecord from 'boost/activityRecord'
const initialStatus = {
mode: IDLE_MODE,
- search: ''
+ search: '',
+ isTutorialOpen: false
}
let data = dataStore.getData()
@@ -23,7 +25,7 @@ function folders (state = initialFolders, action) {
newFolder.name = newFolder.name.trim().replace(/\s/, '_')
Object.assign(newFolder, {
key: keygen(),
- createAt: new Date(),
+ createdAt: new Date(),
updatedAt: new Date(),
// random number (0-7)
color: Math.round(Math.random() * 7)
@@ -36,6 +38,7 @@ function folders (state = initialFolders, action) {
state.push(newFolder)
dataStore.setFolders(null, state)
+ activityRecord.emit('FOLDER_CREATE')
return state
}
case FOLDER_UPDATE:
@@ -54,7 +57,6 @@ function folders (state = initialFolders, action) {
let conflictFolder = _.find(state, _folder => {
return folder.name === _folder.name && folder.key !== _folder.key
})
- console.log(conflictFolder)
if (conflictFolder != null) throw new Error('Name conflicted')
}
Object.assign(targetFolder, folder, {
@@ -62,6 +64,7 @@ function folders (state = initialFolders, action) {
})
dataStore.setFolders(null, state)
+ activityRecord.emit('FOLDER_UPDATE')
return state
}
case FOLDER_DESTROY:
@@ -74,6 +77,7 @@ function folders (state = initialFolders, action) {
state.splice(targetIndex, 1)
}
dataStore.setFolders(null, state)
+ activityRecord.emit('FOLDER_DESTROY')
return state
}
default:
@@ -122,6 +126,9 @@ 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 SWITCH_FOLDER:
state.mode = IDLE_MODE
state.search = `in:${action.data} `
diff --git a/lib/vars/modes.js b/lib/vars/modes.js
new file mode 100644
index 00000000..fe53c756
--- /dev/null
+++ b/lib/vars/modes.js
@@ -0,0 +1,756 @@
+const modes = [
+ // Major
+ {
+ name: 'text',
+ label: 'Plain text',
+ mode: 'text'
+ },
+ {
+ name: 'markdown',
+ label: 'Markdown',
+ alias: ['md'],
+ mode: 'markdown'
+ },
+ {
+ name: 'javascript',
+ label: 'JavaScript',
+ alias: ['js', 'jscript', 'babel', 'es'],
+ mode: 'javascript'
+ },
+ {
+ name: 'html',
+ label: 'HTML',
+ alias: [],
+ mode: 'html'
+ },
+ {
+ name: 'css',
+ label: 'CSS',
+ alias: ['cascade', 'stylesheet'],
+ mode: 'css'
+ },
+ {
+ name: 'php',
+ label: 'PHP',
+ alias: [],
+ mode: 'php'
+ },
+ {
+ name: 'python',
+ label: 'Python',
+ alias: ['py'],
+ mode: 'python'
+ },
+ {
+ name: 'ruby',
+ label: 'Ruby',
+ alias: ['rb'],
+ mode: 'ruby'
+ },
+ {
+ name: 'java',
+ label: 'Java',
+ alias: [],
+ mode: 'java'
+ },
+ {
+ name: 'c',
+ label: 'C',
+ alias: ['c', 'h', 'clang', 'clang'],
+ mode: 'c_cpp'
+ },
+ {
+ name: 'cpp',
+ label: 'C++',
+ alias: ['cc', 'cpp', 'cxx', 'hh', 'c++', 'cplusplus'],
+ mode: 'c_cpp'
+ },
+ {
+ name: 'csharp',
+ label: 'C#',
+ alias: ['cs'],
+ mode: 'csharp'
+ },
+ {
+ name: 'swift',
+ label: 'Swift',
+ alias: [],
+ mode: 'swift'
+ },
+ {
+ name: 'golang',
+ label: 'Go',
+ alias: ['go'],
+ mode: 'golang'
+ },
+
+ // Minor
+ {
+ name: 'abap',
+ label: 'ABAP',
+ alias: [],
+ mode: 'abap'
+ },
+ {
+ name: 'abc',
+ label: 'ABC',
+ alias: [],
+ mode: 'abc'
+ },
+ {
+ name: 'actionscript',
+ label: 'ActionScript',
+ alias: ['as'],
+ mode: 'actionscript'
+ },
+ {
+ name: 'ada',
+ label: 'Ada',
+ alias: [],
+ mode: 'ada'
+ },
+ {
+ name: 'apache_conf',
+ label: 'Apache config',
+ alias: ['apache', 'conf'],
+ mode: 'apache_conf'
+ },
+ {
+ name: 'applescript',
+ label: 'AppleScript',
+ alias: ['scpt'],
+ mode: 'applescript'
+ },
+ {
+ name: 'asciidoc',
+ label: 'AsciiDoc',
+ alias: ['ascii', 'doc', 'txt'],
+ mode: 'asciidoc'
+ },
+ {
+ name: 'assembly_x86',
+ label: 'Assembly x86',
+ alias: ['assembly', 'x86', 'asm'],
+ mode: 'assembly_x86'
+ },
+ {
+ name: 'autohotkey',
+ label: 'AutoHotkey',
+ alias: ['ahk'],
+ mode: 'autohotkey'
+ },
+ {
+ name: 'batchfile',
+ label: 'Batch file',
+ alias: ['dos', 'windows', 'bat', 'cmd', 'btm'],
+ mode: 'batchfile'
+ },
+ {
+ name: 'cirru',
+ label: 'Cirru',
+ alias: [],
+ mode: 'cirru'
+ },
+ {
+ name: 'clojure',
+ label: 'Clojure',
+ alias: ['clj', 'cljs', 'cljc', 'edn'],
+ mode: 'clojure'
+ },
+ {
+ name: 'cobol',
+ label: 'COBOL',
+ alias: ['cbl', 'cob', 'cpy'],
+ mode: 'cobol'
+ },
+ {
+ name: 'coffee',
+ label: 'CoffeeScript',
+ alias: ['coffee'],
+ mode: 'coffee'
+ },
+ {
+ name: 'coldfusion',
+ label: 'ColdFusion',
+ alias: ['cfm', 'cfc'],
+ mode: 'coldfusion'
+ },
+ {
+ name: 'curly',
+ label: 'Curly',
+ alias: [],
+ mode: 'curly'
+ },
+ {
+ name: 'd',
+ label: 'D',
+ alias: ['dlang'],
+ mode: 'd'
+ },
+ {
+ name: 'dockerfile',
+ label: 'DockerFile',
+ alias: ['docker'],
+ mode: 'docker'
+ },
+ {
+ name: 'dart',
+ label: 'Dart',
+ alias: [],
+ mode: 'dart'
+ },
+ {
+ name: 'diff',
+ label: 'Diff',
+ alias: [],
+ mode: 'diff'
+ },
+ {
+ name: 'django',
+ label: 'Django',
+ alias: [],
+ mode: 'djt'
+ },
+ {
+ name: 'dot',
+ label: 'DOT',
+ alias: ['gv'],
+ mode: 'dot'
+ },
+ {
+ name: 'eiffel',
+ label: 'Eiffel',
+ alias: [],
+ mode: 'eiffel'
+ },
+ {
+ name: 'ejs',
+ label: 'EJS',
+ alias: [],
+ mode: 'ejs'
+ },
+ {
+ name: 'elixir',
+ label: 'Elixir',
+ alias: ['ex', 'exs'],
+ mode: 'elixir'
+ },
+ {
+ name: 'elm',
+ label: 'Elm',
+ alias: [],
+ mode: 'elm'
+ },
+ {
+ name: 'erlang',
+ label: 'Erlang',
+ alias: ['erl', 'hrl'],
+ mode: 'erlang'
+ },
+ {
+ name: 'forth',
+ label: 'Forth',
+ alias: ['fs', 'fth'],
+ mode: 'forth'
+ },
+ {
+ name: 'freemaker',
+ label: 'Freemaker',
+ alias: ['ftl'],
+ mode: 'ftl'
+ },
+ {
+ name: 'gcode',
+ label: 'G-code',
+ alias: ['mpt', 'mpf', 'nc'],
+ mode: 'gcode'
+ },
+ {
+ name: 'gherkin',
+ label: 'Gherkin',
+ alias: ['cucumber'],
+ mode: 'gherkin'
+ },
+ {
+ name: 'gitignore',
+ label: 'Gitignore',
+ alias: ['git'],
+ mode: 'gitignore'
+ },
+ {
+ name: 'glsl',
+ label: 'GLSL',
+ alias: ['opengl', 'shading'],
+ mode: 'glsl'
+ },
+ {
+ name: 'groovy',
+ label: 'Groovy',
+ alias: [],
+ mode: 'grooby'
+ },
+ {
+ name: 'haml',
+ label: 'Haml',
+ alias: [],
+ mode: 'haml'
+ },
+ {
+ name: 'handlebars',
+ label: 'Handlebars',
+ alias: ['hbs'],
+ mode: 'handlebars'
+ },
+ {
+ name: 'haskell',
+ label: 'Haskell',
+ alias: ['hs', 'lhs'],
+ mode: 'haskell'
+ },
+ {
+ name: 'haxe',
+ label: 'Haxe',
+ alias: ['hx', 'hxml'],
+ mode: 'haxe'
+ },
+ {
+ name: 'html_ruby',
+ label: 'HTML (Ruby)',
+ alias: ['erb', 'rhtml'],
+ mode: 'html_ruby'
+ },
+ {
+ name: 'jsx',
+ label: 'JSX',
+ alias: ['es', 'babel', 'js', 'jsx', 'react'],
+ mode: 'jsx'
+ },
+ {
+ name: 'typescript',
+ label: 'TypeScript',
+ alias: ['ts'],
+ mode: 'typescript'
+ },
+ {
+ name: 'ini',
+ label: 'INI file',
+ alias: [],
+ mode: 'ini'
+ },
+ {
+ name: 'io',
+ label: 'Io',
+ alias: [],
+ mode: 'io'
+ },
+ {
+ name: 'jack',
+ label: 'Jack',
+ alias: [],
+ mode: 'jack'
+ },
+ {
+ name: 'jade',
+ label: 'Jade',
+ alias: [],
+ mode: 'jade'
+ },
+ {
+ name: 'json',
+ label: 'JSON',
+ alias: [],
+ mode: 'json'
+ },
+ {
+ name: 'jsoniq',
+ label: 'JSONiq',
+ alias: ['query'],
+ mode: 'jsoniq'
+ },
+ {
+ name: 'jsp',
+ label: 'JSP',
+ alias: [],
+ mode: 'jsp'
+ },
+ {
+ name: 'julia',
+ label: 'Julia',
+ alias: [],
+ mode: 'julia'
+ },
+ {
+ name: 'latex',
+ label: 'Latex',
+ alias: ['tex'],
+ mode: 'latex'
+ },
+ {
+ name: 'lean',
+ label: 'Lean',
+ alias: [],
+ mode: 'lean'
+ },
+ {
+ name: 'less',
+ label: 'Less',
+ alias: [],
+ mode: 'less'
+ },
+ {
+ name: 'liquid',
+ label: 'Liquid',
+ alias: [],
+ mode: 'liquid'
+ },
+ {
+ name: 'lisp',
+ label: 'Lisp',
+ alias: ['lsp'],
+ mode: 'lisp'
+ },
+ {
+ name: 'livescript',
+ label: 'LiveScript',
+ alias: ['ls'],
+ mode: 'livescript'
+ },
+ {
+ name: 'logiql',
+ label: 'LogiQL',
+ alias: [],
+ mode: 'logiql'
+ },
+ {
+ name: 'lsl',
+ label: 'LSL',
+ alias: [],
+ mode: 'lsl'
+ },
+ {
+ name: 'lua',
+ label: 'Lua',
+ alias: [],
+ mode: 'lua'
+ },
+ {
+ name: 'luapage',
+ label: 'Luapage',
+ alias: [],
+ mode: 'luapage'
+ },
+ {
+ name: 'lucene',
+ label: 'Lucene',
+ alias: [],
+ mode: 'lucene'
+ },
+ {
+ name: 'makefile',
+ label: 'Makefile',
+ alias: [],
+ mode: 'makefile'
+ },
+ {
+ name: 'mask',
+ label: 'Mask',
+ alias: [],
+ mode: 'mask'
+ },
+ {
+ name: 'matlab',
+ label: 'MATLAB',
+ alias: [],
+ mode: 'matlab'
+ },
+ {
+ name: 'maze',
+ label: 'Maze',
+ alias: [],
+ mode: 'maze'
+ },
+ {
+ name: 'mel',
+ label: 'MEL',
+ alias: [],
+ mode: 'mel'
+ },
+ {
+ name: 'mipsassembler',
+ label: 'MIPS assembly',
+ alias: [],
+ mode: 'mipsassembler'
+ },
+ {
+ name: 'mushcode',
+ label: 'MUSHCode',
+ alias: [],
+ mode: 'mushcode'
+ },
+ {
+ name: 'mysql',
+ label: 'MySQL',
+ alias: [],
+ mode: 'mysql'
+ },
+ {
+ name: 'nix',
+ label: 'Nix',
+ alias: [],
+ mode: 'nix'
+ },
+ {
+ name: 'objectivec',
+ label: 'Objective C',
+ alias: ['objc'],
+ mode: 'objectivec'
+ },
+ {
+ name: 'ocaml',
+ label: 'OCaml',
+ alias: [],
+ mode: 'ocaml'
+ },
+ {
+ name: 'pascal',
+ label: 'Pascal',
+ alias: [],
+ mode: 'pascal'
+ },
+ {
+ name: 'perl',
+ label: 'Perl',
+ alias: [],
+ mode: 'perl'
+ },
+ {
+ name: 'pgsql',
+ label: 'Postgres SQL',
+ alias: ['postgres'],
+ mode: 'pgsql'
+ },
+ {
+ name: 'powershell',
+ label: 'PowerShell',
+ alias: ['ps1'],
+ mode: 'powershell'
+ },
+ {
+ name: 'praat',
+ label: 'Praat',
+ alias: [],
+ mode: 'praat'
+ },
+ {
+ name: 'prolog',
+ label: 'Prolog',
+ alias: ['pl', 'pro'],
+ mode: 'prolog'
+ },
+ {
+ name: 'properties',
+ label: 'Properties',
+ alias: [],
+ mode: 'properties'
+ },
+ {
+ name: 'protobuf',
+ label: 'Protocol Buffers',
+ alias: ['protocol', 'buffers'],
+ mode: 'protobuf'
+ },
+ {
+ name: 'r',
+ label: 'R',
+ alias: ['rlang'],
+ mode: 'r'
+ },
+ {
+ name: 'rdoc',
+ label: 'RDoc',
+ alias: [],
+ mode: 'rdoc'
+ },
+ {
+ name: 'rust',
+ label: 'Rust',
+ alias: [],
+ mode: 'rust'
+ },
+ {
+ name: 'sass',
+ label: 'Sass',
+ alias: [],
+ mode: 'sass'
+ },
+ {
+ name: 'scad',
+ label: 'SCAD',
+ alias: [],
+ mode: 'scad'
+ },
+ {
+ name: 'scala',
+ label: 'Scala',
+ alias: [],
+ mode: 'scala'
+ },
+ {
+ name: 'scheme',
+ label: 'Scheme',
+ alias: ['scm', 'ss'],
+ mode: 'scheme'
+ },
+ {
+ name: 'scss',
+ label: 'Scss',
+ alias: [],
+ mode: 'scss'
+ },
+ {
+ name: 'sh',
+ label: 'Shell',
+ alias: ['shell'],
+ mode: 'sh'
+ },
+ {
+ name: 'sjs',
+ label: 'StratifiedJS',
+ alias: ['stratified'],
+ mode: 'sjs'
+ },
+ {
+ name: 'smarty',
+ label: 'Smarty',
+ alias: [],
+ mode: 'smarty'
+ },
+ {
+ name: 'snippets',
+ label: 'Snippets',
+ alias: [],
+ mode: 'snippets'
+ },
+ {
+ name: 'soy_template',
+ label: 'Soy Template',
+ alias: ['soy'],
+ mode: 'soy_template'
+ },
+ {
+ name: 'space',
+ label: 'Space',
+ alias: [],
+ mode: 'space'
+ },
+ {
+ name: 'sql',
+ label: 'SQL',
+ alias: [],
+ mode: 'sql'
+ },
+ {
+ name: 'sqlserver',
+ label: 'SQL Server',
+ alias: [],
+ mode: 'sqlserver'
+ },
+ {
+ name: 'stylus',
+ label: 'Stylus',
+ alias: [],
+ mode: 'stylus'
+ },
+ {
+ name: 'svg',
+ label: 'SVG',
+ alias: [],
+ mode: 'svg'
+ },
+ {
+ name: 'swig',
+ label: 'SWIG',
+ alias: [],
+ mode: 'swig'
+ },
+ {
+ name: 'tcl',
+ label: 'Tcl',
+ alias: [],
+ mode: 'tcl'
+ },
+ {
+ name: 'tex',
+ label: 'TeX',
+ alias: [],
+ mode: 'tex'
+ },
+ {
+ name: 'textile',
+ label: 'Textile',
+ alias: [],
+ mode: 'textile'
+ },
+ {
+ name: 'toml',
+ label: 'TOML',
+ alias: [],
+ mode: 'toml'
+ },
+ {
+ name: 'twig',
+ label: 'Twig',
+ alias: [],
+ mode: 'twig'
+ },
+ {
+ name: 'vala',
+ label: 'Vala',
+ alias: [],
+ mode: 'vala'
+ },
+ {
+ name: 'vbscript',
+ label: 'VBScript',
+ alias: ['vbs', 'vbe'],
+ mode: 'vbscript'
+ },
+ {
+ name: 'velocity',
+ label: 'Velocity',
+ alias: [],
+ mode: 'velocity'
+ },
+ {
+ name: 'verilog',
+ label: 'Verilog',
+ alias: [],
+ mode: 'verilog'
+ },
+ {
+ name: 'vhdl',
+ label: 'VHDL',
+ alias: [],
+ mode: 'vhdl'
+ },
+ {
+ name: 'xml',
+ label: 'XML',
+ alias: [],
+ mode: 'xml'
+ },
+ {
+ name: 'xquery',
+ label: 'XQuery',
+ alias: [],
+ mode: 'xquery'
+ },
+ {
+ name: 'yaml',
+ label: 'YAML',
+ alias: [],
+ mode: 'yaml'
+ }
+]
+
+export default modes
diff --git a/main.js b/main.js
index 9a5a3e9f..f96acb1a 100644
--- a/main.js
+++ b/main.js
@@ -138,8 +138,12 @@ app.on('ready', function () {
}
finderWindow.show()
})
+ mainWindow.webContents.send('APP_SETTING_DONE', {})
} catch (err) {
- console.log(err.name)
+ console.error(err)
+ mainWindow.webContents.send('APP_SETTING_ERROR', {
+ message: 'Failed to apply hotkey: Invalid format'
+ })
}
})
diff --git a/package.json b/package.json
index 85e93092..f3007e40 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "boost",
- "version": "0.4.0-beta.1",
+ "version": "0.4.0-beta.2",
"description": "Boost App",
"main": "main.js",
"scripts": {