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

Merge branch 'dev'

* dev:
  fix typo shareWith -> shareVia
  bump up version to 0.4.6
  Finderを開き直したら内容初期化
  ARTICLE_SHARE イベント追跡
  debug - 新規投稿が不可能
  enable copy (finder)
  modify dock.menu
  Folderの位置修正の保存
  add hot key:Navigate up(Ctrl + P) for CodeEditor
  switch API URL
  submit user name
  Url share done

Conflicts:
	package.json
This commit is contained in:
Rokt33r
2015-12-14 10:39:53 +09:00
16 changed files with 372 additions and 310 deletions

View File

@@ -1,5 +1,6 @@
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const shell = electron.shell
module.exports = [
{
@@ -90,7 +91,17 @@ module.exports = [
click: function () {
BrowserWindow.getFocusedWindow().reload()
}
}
},
// {
// label: 'Toggle Developer Tools',
// accelerator: (function () {
// if (process.platform === 'darwin') return 'Alt+Command+I'
// else return 'Ctrl+Shift+I'
// })(),
// click: function (item, focusedWindow) {
// if (focusedWindow) BrowserWindow.getFocusedWindow().toggleDevTools()
// }
// }
]
},
{
@@ -117,6 +128,24 @@ module.exports = [
},
{
label: 'Help',
submenu: []
role: 'help',
submenu: [
{
label: 'Boost official site',
click: function () { shell.openExternal('https://b00st.io/') }
},
{
label: 'Tutorial page',
click: function () { shell.openExternal('https://b00st.io/tutorial.html') }
},
{
label: 'Discussions',
click: function () { shell.openExternal('https://github.com/BoostIO/boost-app-discussions/issues') }
},
{
label: 'Changelog',
click: function () { shell.openExternal('https://github.com/BoostIO/boost-releases/blob/master/changelog.md') }
}
]
}
]

View File

@@ -32,11 +32,20 @@ class FinderMain extends React.Component {
}
componentDidMount () {
this.keyDownHandler = e => this.handleKeyDown(e)
document.addEventListener('keydown', this.keyDownHandler)
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
this.focusHandler = e => {
let { dispatch } = this.props
dispatch(searchArticle(''))
}
window.addEventListener('focus', this.focusHandler)
}
handleClick (e) {
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
componentWillUnmount () {
document.removeEventListener('keydown', this.keyDownHandler)
window.removeEventListener('focus', this.focusHandler)
}
handleKeyDown (e) {
@@ -58,6 +67,11 @@ class FinderMain extends React.Component {
hideFinder()
e.preventDefault()
}
if (e.keyCode === 91 || e.metaKey) {
return
}
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
}
saveToClipboard () {
@@ -99,7 +113,7 @@ class FinderMain extends React.Component {
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'>
<div onClick={e => this.handleClick(e)} className='Finder'>
<FinderInput
handleSearchChange={e => this.handleSearchChange(e)}
ref='finderInput'

View File

@@ -9,7 +9,7 @@ import _ from 'lodash'
import { isModalOpen, closeModal } from 'boost/modal'
const electron = require('electron')
const BrowserWindow = electron.remote.BrowserWindow
const remote = electron.remote
const TEXT_FILTER = 'TEXT_FILTER'
const FOLDER_FILTER = 'FOLDER_FILTER'
@@ -29,13 +29,6 @@ class HomePage extends React.Component {
}
handleKeyDown (e) {
if (process.env.BOOST_ENV === 'development' && e.keyCode === 73 && e.metaKey && e.altKey) {
e.preventDefault()
e.stopPropagation()
BrowserWindow.getFocusedWindow().toggleDevTools()
return
}
if (isModalOpen()) {
if (e.keyCode === 27) closeModal()
return
@@ -106,7 +99,7 @@ class HomePage extends React.Component {
list.selectNextArticle()
}
if (e.keyCode === 65 || (e.keyCode === 13 && e.metaKey) || (e.keyCode === 78 && e.metaKey)) {
if ((e.keyCode === 65 && !e.metaKey && !e.ctrlKey) || (e.keyCode === 13 && e.metaKey) || (e.keyCode === 78 && e.metaKey)) {
nav.handleNewPostButtonClick()
e.preventDefault()
}
@@ -142,6 +135,7 @@ class HomePage extends React.Component {
<ArticleDetail
ref='detail'
dispatch={dispatch}
user={user}
activeArticle={activeArticle}
folders={folders}
status={status}

View File

@@ -0,0 +1,151 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import api from 'boost/api'
import clientKey from 'boost/clientKey'
import activityRecord from 'boost/activityRecord'
const clipboard = require('electron').clipboard
function getDefault () {
return {
openDropdown: false,
isSharing: false,
// Fetched url
url: null,
// for tooltip Copy -> Copied!
copied: false,
failed: false
}
}
export default class ShareButton extends React.Component {
constructor (props) {
super(props)
this.state = getDefault()
}
componentWillReceiveProps (nextProps) {
this.setState(getDefault())
}
componentDidMount () {
this.dropdownInterceptor = e => {
this.dropdownClicked = true
}
ReactDOM.findDOMNode(this.refs.dropdown).addEventListener('click', this.dropdownInterceptor)
this.shareViaPublicURLHandler = e => {
this.handleShareViaPublicURLClick(e)
}
}
componentWillUnmount () {
document.removeEventListener('click', this.dropdownHandler)
ReactDOM.findDOMNode(this.refs.dropdown).removeEventListener('click', this.dropdownInterceptor)
}
handleOpenButtonClick (e) {
this.openDropdown()
if (this.dropdownHandler == null) {
this.dropdownHandler = e => {
if (!this.dropdownClicked) {
this.closeDropdown()
} else {
this.dropdownClicked = false
}
}
}
document.removeEventListener('click', this.dropdownHandler)
document.addEventListener('click', this.dropdownHandler)
}
openDropdown () {
this.setState({openDropdown: true})
}
closeDropdown () {
document.removeEventListener('click', this.dropdownHandler)
this.setState({openDropdown: false})
}
handleShareViaPublicURLClick (e) {
let { user } = this.props
let input = Object.assign({}, this.props.article, {
clientKey: clientKey.get(),
writerName: user.name
})
this.setState({
isSharing: true,
failed: false
}, () => {
api.shareViaPublicURL(input)
.then(res => {
let url = res.body.url
this.setState({url: url, isSharing: false})
activityRecord.emit('ARTICLE_SHARE')
})
.catch(err => {
console.log(err)
this.setState({isSharing: false, failed: true})
})
})
}
handleCopyURLClick () {
clipboard.writeText(this.state.url)
this.setState({copied: true})
}
// Restore copy url tooltip
handleCopyURLMouseLeave () {
this.setState({copied: false})
}
render () {
let hasPublicURL = this.state.url != null
return (
<div className='ShareButton'>
<button ref='openButton' onClick={e => this.handleOpenButtonClick(e)} className='ShareButton-open-button'>
<i className='fa fa-fw fa-share-alt'/>
{
this.state.openDropdown ? null : (
<span className='tooltip'>Share</span>
)
}
</button>
<div ref='dropdown' className={'share-dropdown' + (this.state.openDropdown ? '' : ' hide')}>
{
!hasPublicURL ? (
<button
onClick={e => this.shareViaPublicURLHandler(e)}
ref='sharePublicURL'
disabled={this.state.isSharing}>
<i className='fa fa-fw fa-external-link'/> {this.state.failed ? 'Failed : Click to Try again' : !this.state.isSharing ? 'Share via public URL' : 'Sharing...'}
</button>
) : (
<div className='ShareButton-url'>
<input className='ShareButton-url-input' value={this.state.url} readOnly/>
<button
onClick={e => this.handleCopyURLClick(e)}
className='ShareButton-url-button'
onMouseLeave={e => this.handleCopyURLMouseLeave(e)}
>
<i className='fa fa-fw fa-clipboard'/>
<div className='ShareButton-url-button-tooltip'>{this.state.copied ? 'Copied!' : 'Copy URL'}</div>
</button>
<div className='ShareButton-url-alert'>This url is valid for 7 days.</div>
</div>
)
}
</div>
</div>
)
}
}
ShareButton.propTypes = {
article: PropTypes.shape({
publicURL: PropTypes.string
}),
user: PropTypes.shape({
name: PropTypes.string
})
}

View File

@@ -24,6 +24,8 @@ import TagLink from 'boost/components/TagLink'
import TagSelect from 'boost/components/TagSelect'
import ModeSelect from 'boost/components/ModeSelect'
import activityRecord from 'boost/activityRecord'
import api from 'boost/api'
import ShareButton from './ShareButton'
const electron = require('electron')
const clipboard = electron.clipboard
@@ -106,12 +108,16 @@ export default class ArticleDetail extends React.Component {
isTagChanged: false,
isTitleChanged: false,
isContentChanged: false,
isModeChanged: false
isModeChanged: false,
openShareDropdown: false
}
}
componentDidMount () {
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
this.shareDropdownInterceptor = e => {
e.stopPropagation()
}
}
componentWillUnmount () {
@@ -191,7 +197,7 @@ export default class ArticleDetail extends React.Component {
}
renderIdle () {
let { status, activeArticle, folders } = this.props
let { status, activeArticle, folders, user } = this.props
let tags = activeArticle.tags != null ? activeArticle.tags.length > 0
? activeArticle.tags.map(tag => {
@@ -234,9 +240,15 @@ export default class ArticleDetail extends React.Component {
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
</div>
<div className='right'>
<ShareButton
article={activeArticle}
user={user}
/>
<button onClick={e => this.handleClipboardButtonClick(e)} className='editBtn'>
<i className='fa fa-fw fa-clipboard'/><span className='tooltip'>Copy to clipboard</span>
</button>
<button onClick={e => this.handleEditButtonClick(e)} className='editBtn'>
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Edit (e)</span>
</button>
@@ -286,7 +298,7 @@ export default class ArticleDetail extends React.Component {
dispatch(unlockStatus())
delete newArticle.status
newArticle.status = null
newArticle.updatedAt = new Date()
newArticle.title = newArticle.title.trim()
if (newArticle.createdAt == null) {
@@ -586,7 +598,8 @@ export default class ArticleDetail extends React.Component {
ArticleDetail.propTypes = {
status: PropTypes.shape(),
activeArticle: PropTypes.shape(),
activeUser: PropTypes.shape(),
user: PropTypes.shape(),
folders: PropTypes.array,
dispatch: PropTypes.func
}
ArticleDetail.prototype.linkState = linkState

View File

@@ -2,8 +2,6 @@ const electron = require('electron')
const ipc = electron.ipcRenderer
import React, { PropTypes } from 'react'
var ContactModal = require('boost/components/modal/ContactModal')
export default class MainContainer extends React.Component {
constructor (props) {
super(props)
@@ -20,20 +18,12 @@ export default class MainContainer extends React.Component {
ipc.send('update-app', 'Deal with it.')
}
openContactModal () {
this.openModal(ContactModal)
}
render () {
return (
<div className='Main'>
{this.state.updateAvailable ? (
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
) : null}
{/* <button onClick={this.openContactModal} className='contactButton'>
<i className='fa fa-paper-plane-o'/>
<div className='tooltip'>Contact us</div>
</button> */}
{this.props.children}
</div>
)

View File

@@ -284,7 +284,87 @@ iptFocusBorderColor = #369DCD
color noTagsColor
.right
z-index 30
button
div.share-dropdown
position absolute
right 5px
top 30px
background-color transparentify(invBackgroundColor, 80%)
padding 5px 0
width 200px
&.hide
display none
&>button
width 200px
text-align left
display block
height 33px
background-color transparent
color white
font-size 14px
padding 0 10px
border none
&:hover
background-color transparentify(lighten(invBackgroundColor, 30%), 80%)
&>.ShareButton-url
clearfix()
input.ShareButton-url-input
width 155px
margin 0 0 0 5px
height 25px
outline none
border none
border-top-left-radius 5px
border-bottom-left-radius 5px
float left
padding 5px
button.ShareButton-url-button
width 35px
height 25px
border none
margin 0 5px 0 0
outline none
border-top-right-radius 5px
border-bottom-right-radius 5px
background-color darken(white, 5%)
color inactiveTextColor
float right
div.ShareButton-url-button-tooltip
tooltip()
right 10px
&:hover
color textColor
div.ShareButton-url-button-tooltip
opacity 1
div.ShareButton-url-alert
float left
height 25px
line-height 25px
padding 0 15px
color white
.ShareButton
display inline-block
button.ShareButton-open-button
border-radius 16.5px
cursor pointer
height 33px
width 33px
border none
margin-right 5px
font-size 18px
color inactiveTextColor
background-color darken(white, 5%)
padding 0
.tooltip
tooltip()
margin-top 25px
margin-left -40px
&:hover
color textColor
.tooltip
opacity 1
&>button
border-radius 16.5px
cursor pointer
height 33px

View File

@@ -27,6 +27,10 @@ app.on('ready', function () {
var appIcon = new Tray(__dirname + '/resources/tray-icon.png')
appIcon.setToolTip('Boost')
var template = require('./atom-lib/menu-template')
var menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
finderWindow = require('./atom-lib/finder-window')
finderWindow.webContents.on('did-finish-load', function () {
var trayMenu = new Menu()

View File

@@ -146,3 +146,23 @@ export function toggleTutorial () {
type: TOGGLE_TUTORIAL
}
}
export default {
updateUser,
clearNewArticle,
updateArticle,
destroyArticle,
createFolder,
updateFolder,
destroyFolder,
replaceFolder,
switchFolder,
switchMode,
switchArticle,
setSearchFilter,
setTagFilter,
clearSearch,
lockStatus,
unlockStatus,
toggleTutorial
}

View File

@@ -1,8 +1,8 @@
import _ from 'lodash'
import moment from 'moment'
import keygen from 'boost/keygen'
import dataStore from 'boost/dataStore'
import { request, WEB_URL } from 'boost/api'
import clientKey from 'boost/clientKey'
const electron = require('electron')
const version = electron.remote.app.getVersion()
@@ -28,16 +28,6 @@ export function init () {
}
}
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'))
}
@@ -67,7 +57,7 @@ export function postRecords (data) {
console.log('posting...', records)
let input = {
clientKey: getClientKey(),
clientKey: clientKey.get(),
records
}
return request.post(WEB_URL + 'apis/activity')
@@ -108,6 +98,7 @@ export function emit (type, data = {}) {
case 'FINDER_OPEN':
case 'FINDER_COPY':
case 'MAIN_DETAIL_COPY':
case 'ARTICLE_SHARE':
todayRecord[type] = todayRecord[type] == null
? 1
: todayRecord[type] + 1
@@ -142,6 +133,5 @@ export function emit (type, data = {}) {
export default {
init,
emit,
getClientKey,
postRecords
}

View File

@@ -1,191 +1,22 @@
import superagent from 'superagent'
import superagentPromise from 'superagent-promise'
import auth from 'boost/auth'
// import auth from 'boost/auth'
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 SERVER_URL = 'https://b00st.io/'
// export const SERVER_URL = 'http://localhost:3333/'
export const request = superagentPromise(superagent, Promise)
export function login (input) {
export function shareViaPublicURL (input) {
return request
.post(API_URL + 'auth/login')
.send(input)
}
export function signup (input) {
return request
.post(API_URL + 'auth/register')
.send(input)
}
export function updateUserInfo (input) {
return request
.put(API_URL + 'auth/user')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function updatePassword (input) {
return request
.post(API_URL + 'auth/password')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function fetchCurrentUser () {
return request
.get(API_URL + 'auth/user')
.set({
Authorization: 'Bearer ' + auth.token()
})
}
export function fetchArticles (userId) {
return request
.get(API_URL + 'teams/' + userId + '/articles')
.set({
Authorization: 'Bearer ' + auth.token()
})
}
export function createArticle (input) {
return request
.post(API_URL + 'articles/')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function saveArticle (input) {
return request
.put(API_URL + 'articles/' + input.id)
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function destroyArticle (articleId) {
return request
.del(API_URL + 'articles/' + articleId)
.set({
Authorization: 'Bearer ' + auth.token()
})
}
export function createTeam (input) {
return request
.post(API_URL + 'teams')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function updateTeamInfo (teamId, input) {
return request
.put(API_URL + 'teams/' + teamId)
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function destroyTeam (teamId) {
return request
.del(API_URL + 'teams/' + teamId)
.set({
Authorization: 'Bearer ' + auth.token()
})
}
export function searchUser (key) {
return request
.get(API_URL + 'search/users')
.query({key: key})
}
export function setMember (teamId, input) {
return request
.post(API_URL + 'teams/' + teamId + '/members')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function deleteMember (teamId, input) {
return request
.del(API_URL + 'teams/' + teamId + '/members')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function createFolder (input) {
return request
.post(API_URL + 'folders/')
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function updateFolder (id, input) {
return request
.put(API_URL + 'folders/' + id)
.set({
Authorization: 'Bearer ' + auth.token()
})
.send(input)
}
export function destroyFolder (id) {
return request
.del(API_URL + 'folders/' + id)
.set({
Authorization: 'Bearer ' + auth.token()
})
}
export function sendEmail (input) {
return request
.post(API_URL + 'mail')
.set({
Authorization: 'Bearer ' + auth.token()
})
.post(SERVER_URL + 'apis/share')
// .set({
// Authorization: 'Bearer ' + auth.token()
// })
.send(input)
}
export default {
API_URL,
WEB_URL,
request,
login,
signup,
updateUserInfo,
updatePassword,
fetchCurrentUser,
fetchArticles,
createArticle,
saveArticle,
destroyArticle,
createTeam,
updateTeamInfo,
destroyTeam,
searchUser,
setMember,
deleteMember,
createFolder,
updateFolder,
destroyFolder,
sendEmail
SERVER_URL,
shareViaPublicURL
}

23
lib/clientKey.js Normal file
View File

@@ -0,0 +1,23 @@
import _ from 'lodash'
import keygen from 'boost/keygen'
function getClientKey () {
let clientKey = localStorage.getItem('clientKey')
if (!_.isString(clientKey) || clientKey.length !== 40) {
clientKey = keygen()
setClientKey(clientKey)
}
return clientKey
}
function setClientKey (newKey) {
localStorage.setItem('clientKey', newKey)
}
getClientKey()
export default {
get: getClientKey,
set: setClientKey
}

View File

@@ -31,6 +31,14 @@ module.exports = React.createClass({
editor.setTheme('ace/theme/xcode')
editor.clearSelection()
editor.moveCursorTo(0, 0)
editor.commands.addCommand({
name: 'Emacs cursor up',
bindKey: {mac: 'Ctrl-P'},
exec: function (editor) {
editor.navigateUp(1)
},
readOnly: true
})
editor.setReadOnly(!!this.props.readOnly)

View File

@@ -1,85 +0,0 @@
import React, { PropTypes, findDOMNode } from 'react'
import linkState from 'boost/linkState'
import { sendEmail } from 'boost/api'
export default class ContactModal extends React.Component {
constructor (props) {
super(props)
this.linkState = linkState
this.state = {
isSent: false,
mail: {
title: '',
content: ''
}
}
}
onKeyCast (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
case 'submitContactModal':
if (this.state.isSent) {
this.props.close()
return
}
this.sendEmail()
break
}
}
componentDidMount () {
findDOMNode(this.refs.title).focus()
}
sendEmail () {
sendEmail(this.state.mail)
.then(function (res) {
this.setState({isSent: !this.state.isSent})
}.bind(this))
.catch(function (err) {
console.error(err)
})
}
render () {
return (
<div className='ContactModal modal'>
<div className='modal-header'><h1>Contact form</h1></div>
{!this.state.isSent ? (
<div className='contactForm'>
<div className='modal-body'>
<div className='formField'>
<input ref='title' valueLink={this.linkState('mail.title')} placeholder='Title'/>
</div>
<div className='formField'>
<textarea valueLink={this.linkState('mail.content')} placeholder='Content'/>
</div>
</div>
<div className='modal-footer'>
<div className='formControl'>
<button onClick={this.sendEmail} className='sendButton'>Send</button>
<button onClick={this.props.close}>Cancel</button>
</div>
</div>
</div>
) : (
<div className='confirmation'>
<div className='confirmationMessage'>Thanks for sharing your opinion!</div>
<button className='doneButton' onClick={this.props.close}>Done</button>
</div>
)}
</div>
)
}
}
ContactModal.propTypes = {
close: PropTypes.func
}

View File

@@ -134,6 +134,7 @@ function folders (state = initialFolders, action) {
state.splice(a, 1, folderB)
state.splice(b, 1, folderA)
}
dataStore.setFolders(state)
return state
default:
return state
@@ -180,10 +181,10 @@ function articles (state = initialArticles, action) {
let targetIndex = _.findIndex(state, _article => article.key === _article.key)
if (targetIndex < 0) state.unshift(article)
else state.splice(targetIndex, 1, article)
else Object.assign(state[targetIndex], article)
if (article.status !== 'NEW') dataStore.setArticles(state)
else isCreatingNew = true
else isCreatingNew = true
return state
}
case ARTICLE_DESTROY:

View File

@@ -1,7 +1,6 @@
{
"name": "boost",
"version": "0.4.4",
"version": "0.4.5",
"version": "0.4.6",
"description": "Boost App",
"main": "index.js",
"scripts": {