1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

行動データ, contact form, default articleに英語文追加, Intro fix

This commit is contained in:
Rokt33r
2015-11-09 15:07:17 +09:00
parent 8428588a4c
commit 746df9277c
12 changed files with 356 additions and 11 deletions

View File

@@ -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((

View File

@@ -12,6 +12,7 @@ import linkState from 'boost/linkState'
import FolderMark from 'boost/components/FolderMark'
import TagLink from 'boost/components/TagLink'
import TagSelect from 'boost/components/TagSelect'
import activityRecord from 'boost/activityRecord'
var modeOptions = aceModes.map(function (mode) {
return {
@@ -93,6 +94,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})
}
@@ -182,7 +184,12 @@ export default class ArticleDetail extends React.Component {
delete newArticle.status
newArticle.updatedAt = new Date()
if (newArticle.createdAt == null) newArticle.createdAt = 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))

View File

@@ -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 = (
<Route path='/' component={MainPage}>

View File

@@ -119,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()

View File

@@ -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

122
lib/activityRecord.js Normal file
View File

@@ -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
}

View File

@@ -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 = 'http://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,

View File

@@ -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 (
<div className='ContactTab content done'>
<div className='message'>
<i className='checkIcon fa fa-check-circle'/><br/>
Your message has been sent successfully!!
</div>
<div className='control'>
<button onClick={e => this.handleBackButtonClick(e)}>Back to Contact form</button>
</div>
</div>
)
case FORM_MODE:
default:
let alertElement = this.state.alert != null
? (
<div className={'alert ' + this.state.alert.type}>{this.state.alert.message}</div>
)
: null
return (
<div className='ContactTab content form'>
<div className='title'>Contact form</div>
<div className='description'>
Your feedback is highly appreciated and will help us to improve our app. :D
</div>
<div className='iptGroup'>
<input ref='title' valueLink={this.linkState('title')} placeholder='Title' type='text'/>
</div>
<div className='iptGroup'>
<textarea valueLink={this.linkState('content')} placeholder='Content'/>
</div>
<div className='iptGroup'>
<input valueLink={this.linkState('email')} placeholder='E-mail (Optional)' type='email'/>
</div>
<div className='formControl'>
<button onClick={e => this.handleSendButtonClick(e)} className='primary'>Send</button>
{alertElement}
</div>
</div>
)
}
}
}
ContactTab.prototype.linkState = linkState

View File

@@ -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 (
<ContactTab/>
)
case APP:
default:
return (<AppSettingTab/>)
@@ -239,7 +246,6 @@ Preferences.prototype.linkState = linkState
function remap (state) {
let { folders, status } = state
console.log(state)
return {
folders,

View File

@@ -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 (<div className='slide slide3'>
<div className='title'>Easy to access with Finder</div>
<div className='content'>
Finder is a small popup window.<br/>
With finder, You can search your articles faster.<br/>
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
<img width='480' src='../../resources/finder.png'/>
</div>
</div>)
@@ -105,3 +108,7 @@ export default class Tutorial extends React.Component {
}
}
}
Tutorial.propTypes = {
close: PropTypes.func
}

View File

@@ -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. 豊富な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'
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')

View File

@@ -3,6 +3,7 @@ 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 dataStore from 'boost/dataStore'
import keygen from 'boost/keygen'
import activityRecord from 'boost/activityRecord'
const initialStatus = {
mode: IDLE_MODE,
@@ -36,6 +37,7 @@ function folders (state = initialFolders, action) {
state.push(newFolder)
dataStore.setFolders(null, state)
activityRecord.emit('FOLDER_CREATE')
return state
}
case FOLDER_UPDATE:
@@ -54,7 +56,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 +63,7 @@ function folders (state = initialFolders, action) {
})
dataStore.setFolders(null, state)
activityRecord.emit('FOLDER_UPDATE')
return state
}
case FOLDER_DESTROY:
@@ -74,6 +76,7 @@ function folders (state = initialFolders, action) {
state.splice(targetIndex, 1)
}
dataStore.setFolders(null, state)
activityRecord.emit('FOLDER_DESTROY')
return state
}
default: