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.handleTitleKeyDown(e)} placeholder='Title' ref='title' valueLink={this.linkState('article.title')}/>
- 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 {
+ {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!! +
    +
    + +
    +
    + ) + 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 +
    +
    + +
    +
    +