mirror of
https://github.com/BoostIo/Boostnote
synced 2026-01-19 11:55:30 +00:00
restructure DONE
This commit is contained in:
68
lib/components/CodeEditor.js
Normal file
68
lib/components/CodeEditor.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from 'react'
|
||||
|
||||
var ace = window.ace
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
code: React.PropTypes.string,
|
||||
mode: React.PropTypes.string,
|
||||
className: React.PropTypes.string,
|
||||
onChange: React.PropTypes.func,
|
||||
readOnly: React.PropTypes.bool
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
readOnly: false
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
var el = React.findDOMNode(this.refs.target)
|
||||
var editor = ace.edit(el)
|
||||
editor.$blockScrolling = Infinity
|
||||
editor.setValue(this.props.code)
|
||||
editor.renderer.setShowGutter(true)
|
||||
editor.setTheme('ace/theme/xcode')
|
||||
editor.clearSelection()
|
||||
if (this.props.readOnly) {
|
||||
editor.setReadOnly(true)
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
session.setUseSoftTabs(true)
|
||||
session.setOption('useWorker', false)
|
||||
session.setUseWrapMode(true)
|
||||
|
||||
session.on('change', function (e) {
|
||||
if (this.props.onChange != null) {
|
||||
var value = editor.getValue()
|
||||
this.props.onChange(e, value)
|
||||
}
|
||||
}.bind(this))
|
||||
|
||||
this.setState({editor: editor})
|
||||
},
|
||||
componentDidUpdate: function (prevProps) {
|
||||
if (this.state.editor.getValue() !== this.props.code) {
|
||||
this.state.editor.setValue(this.props.code)
|
||||
this.state.editor.clearSelection()
|
||||
}
|
||||
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')
|
||||
}
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div ref='target' className={this.props.className == null ? 'CodeEditor' : 'CodeEditor ' + this.props.className}></div>
|
||||
)
|
||||
}
|
||||
})
|
||||
19
lib/components/ExternalLink.js
Normal file
19
lib/components/ExternalLink.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import shell from 'shell'
|
||||
|
||||
export default class ExternalLink extends React.Component {
|
||||
handleClick (e) {
|
||||
shell.openExternal(this.props.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<a onClick={e => this.handleClick(e)} {...this.props}/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ExternalLink.propTypes = {
|
||||
href: PropTypes.string
|
||||
}
|
||||
53
lib/components/MarkdownPreview.js
Normal file
53
lib/components/MarkdownPreview.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import shell from 'shell'
|
||||
import React, { PropTypes } from 'react'
|
||||
import markdown from 'boost/markdown'
|
||||
|
||||
function handleAnchorClick (e) {
|
||||
shell.openExternal(e.target.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
export default class MarkdownPreview extends React.Component {
|
||||
componentDidMount () {
|
||||
this.addListener()
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this.addListener()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.removeListener()
|
||||
}
|
||||
|
||||
componentWillUpdate () {
|
||||
this.removeListener()
|
||||
}
|
||||
|
||||
addListener () {
|
||||
var anchors = React.findDOMNode(this).querySelectorAll('a')
|
||||
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].addEventListener('click', handleAnchorClick)
|
||||
}
|
||||
}
|
||||
|
||||
removeListener () {
|
||||
var anchors = React.findDOMNode(this).querySelectorAll('a')
|
||||
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].removeEventListener('click', handleAnchorClick)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className={'MarkdownPreview' + (this.props.className != null ? ' ' + this.props.className : '')} dangerouslySetInnerHTML={{__html: ' ' + markdown(this.props.content)}}/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownPreview.propTypes = {
|
||||
className: PropTypes.string,
|
||||
content: PropTypes.string
|
||||
}
|
||||
81
lib/components/ModeIcon.js
Normal file
81
lib/components/ModeIcon.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
export default class ModeIcon extends React.Component {
|
||||
getClassName () {
|
||||
var mode = this.props.mode
|
||||
switch (mode) {
|
||||
// Script
|
||||
case 'javascript':
|
||||
return 'devicon-javascript-plain'
|
||||
case 'jsx':
|
||||
return 'devicon-react-original'
|
||||
case 'coffee':
|
||||
return 'devicon-coffeescript-original'
|
||||
case 'ruby':
|
||||
return 'devicon-ruby-plain'
|
||||
case 'erlang':
|
||||
return 'devicon-erlang-plain'
|
||||
case 'php':
|
||||
return 'devicon-php-plain'
|
||||
|
||||
// HTML
|
||||
case 'html':
|
||||
return 'devicon-html5-plain'
|
||||
|
||||
// Stylesheet
|
||||
case 'css':
|
||||
return 'devicon-css3-plain'
|
||||
case 'less':
|
||||
return 'devicon-less-plain-wordmark'
|
||||
case 'sass':
|
||||
case 'scss':
|
||||
return 'devicon-sass-original'
|
||||
|
||||
// Compile
|
||||
case 'c_cpp':
|
||||
return 'devicon-c-plain'
|
||||
case 'csharp':
|
||||
return 'devicon-csharp-plain'
|
||||
case 'objc':
|
||||
return 'devicon-apple-original'
|
||||
case 'golang':
|
||||
return 'devicon-go-plain'
|
||||
case 'java':
|
||||
return 'devicon-java-plain'
|
||||
|
||||
// Framework
|
||||
case 'django':
|
||||
return 'devicon-django-plain'
|
||||
|
||||
// Config
|
||||
case 'dockerfile':
|
||||
return 'devicon-docker-plain'
|
||||
case 'gitignore':
|
||||
return 'devicon-git-plain'
|
||||
|
||||
// Shell
|
||||
case 'sh':
|
||||
case 'batchfile':
|
||||
case 'powershell':
|
||||
return 'fa fa-fw fa-terminal'
|
||||
|
||||
case 'text':
|
||||
case 'plain_text':
|
||||
case 'markdown':
|
||||
return 'fa fa-fw fa-file-text-o'
|
||||
}
|
||||
return 'fa fa-fw fa-code'
|
||||
}
|
||||
|
||||
render () {
|
||||
var className = this.getClassName()
|
||||
return (
|
||||
<i className={this.props.className + ' ' + className}/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ModeIcon.propTypes = {
|
||||
className: PropTypes.string,
|
||||
mode: PropTypes.string
|
||||
}
|
||||
24
lib/components/ProfileImage.js
Normal file
24
lib/components/ProfileImage.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { PropTypes} from 'react'
|
||||
import md5 from 'md5'
|
||||
|
||||
export default class ProfileImage extends React.Component {
|
||||
render () {
|
||||
let className = this.props.className == null ? 'ProfileImage' : 'ProfileImage ' + this.props.className
|
||||
let email = this.props.email != null ? this.props.email : ''
|
||||
let src = 'http://www.gravatar.com/avatar/' + md5(email.trim().toLowerCase()) + '?s=' + this.props.size
|
||||
|
||||
return (
|
||||
<img
|
||||
className={className}
|
||||
width={this.props.size}
|
||||
height={this.props.size}
|
||||
src={src}/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ProfileImage.propTypes = {
|
||||
email: PropTypes.string,
|
||||
size: PropTypes.string,
|
||||
className: PropTypes.string
|
||||
}
|
||||
85
lib/components/modal/ContactModal.js
Normal file
85
lib/components/modal/ContactModal.js
Normal file
@@ -0,0 +1,85 @@
|
||||
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
|
||||
}
|
||||
262
lib/components/modal/CreateNewTeam.js
Normal file
262
lib/components/modal/CreateNewTeam.js
Normal file
@@ -0,0 +1,262 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ProfileImage from 'boost/components/ProfileImage'
|
||||
import { searchUser, createTeam, setMember, deleteMember } from 'boost/api'
|
||||
import linkState from 'boost/linkState'
|
||||
import Select from 'react-select'
|
||||
|
||||
function getUsers (input, cb) {
|
||||
searchUser(input)
|
||||
.then(function (res) {
|
||||
let users = res.body
|
||||
|
||||
cb(null, {
|
||||
options: users.map(user => {
|
||||
return { value: user.name, label: user.name }
|
||||
}),
|
||||
complete: false
|
||||
})
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
export default class CreateNewTeam extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
create: {
|
||||
name: '',
|
||||
alert: null
|
||||
},
|
||||
select: {
|
||||
team: null,
|
||||
newMember: null,
|
||||
alert: null
|
||||
},
|
||||
currentTab: 'create',
|
||||
currentUser: JSON.parse(localStorage.getItem('currentUser'))
|
||||
}
|
||||
}
|
||||
|
||||
handleCloseClick (e) {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
handleContinueClick (e) {
|
||||
let createState = this.state.create
|
||||
createState.isSending = true
|
||||
createState.alert = {
|
||||
type: 'info',
|
||||
message: 'sending...'
|
||||
}
|
||||
this.setState({create: createState})
|
||||
|
||||
function onTeamCreate (res) {
|
||||
let createState = this.state.create
|
||||
createState.isSending = false
|
||||
createState.alert = null
|
||||
|
||||
let selectState = this.state.select
|
||||
selectState.team = res.body
|
||||
|
||||
this.setState({
|
||||
currentTab: 'select',
|
||||
create: createState,
|
||||
select: {
|
||||
team: res.body
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onError (err) {
|
||||
let errorMessage = err.response != null ? err.response.body.message : 'Can\'t connect to API server.'
|
||||
|
||||
let createState = this.state.create
|
||||
createState.isSending = false
|
||||
createState.alert = {
|
||||
type: 'error',
|
||||
message: errorMessage
|
||||
}
|
||||
|
||||
this.setState({
|
||||
create: createState
|
||||
})
|
||||
}
|
||||
|
||||
createTeam({name: this.state.create.name})
|
||||
.then(onTeamCreate.bind(this))
|
||||
.catch(onError.bind(this))
|
||||
}
|
||||
|
||||
renderCreateTab () {
|
||||
let createState = this.state.create
|
||||
let alertEl = createState.alert != null ? (
|
||||
<p className={['alert'].concat([createState.alert.type]).join(' ')}>{createState.alert.message}</p>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<div className='createTab'>
|
||||
<div className='title'>Create new team</div>
|
||||
|
||||
<input valueLink={linkState(this, 'create.name')} className='ipt' type='text' placeholder='Enter your team name'/>
|
||||
{alertEl}
|
||||
<button onClick={e => this.handleContinueClick(e)} disabled={createState.isSending} className='confirmBtn'>Continue <i className='fa fa-arrow-right fa-fw'/></button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
handleNewMemberChange (value) {
|
||||
let selectState = this.state.select
|
||||
selectState.newMember = value
|
||||
this.setState({select: selectState})
|
||||
}
|
||||
|
||||
handleClickAddMemberButton (e) {
|
||||
let selectState = this.state.select
|
||||
let input = {
|
||||
name: selectState.newMember,
|
||||
role: 'member'
|
||||
}
|
||||
|
||||
setMember(selectState.team.id, input)
|
||||
.then(res => {
|
||||
let selectState = this.state.select
|
||||
let team = res.body
|
||||
team.Members = team.Members.sort((a, b) => {
|
||||
return new Date(a._pivot_createdAt) - new Date(b._pivot_createdAt)
|
||||
})
|
||||
selectState.team = team
|
||||
selectState.newMember = ''
|
||||
|
||||
this.setState({select: selectState})
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.status != null) throw err
|
||||
else console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
handleMemberDeleteButtonClick (name) {
|
||||
let selectState = this.state.select
|
||||
let input = {
|
||||
name: name,
|
||||
role: 'member'
|
||||
}
|
||||
|
||||
return function (e) {
|
||||
deleteMember(selectState.team.id, input)
|
||||
.then(res => {
|
||||
let selectState = this.state.select
|
||||
let team = res.body
|
||||
team.Members = team.Members.sort((a, b) => {
|
||||
return new Date(a._pivot_createdAt) - new Date(b._pivot_createdAt)
|
||||
})
|
||||
selectState.team = team
|
||||
selectState.newMember = ''
|
||||
|
||||
this.setState({select: selectState})
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err, err.response)
|
||||
if (err.status != null) throw err
|
||||
else console.error(err)
|
||||
})
|
||||
}.bind(this)
|
||||
}
|
||||
|
||||
handleMemberRoleChange (name) {
|
||||
return function (e) {
|
||||
let selectState = this.state.select
|
||||
let input = {
|
||||
name: name,
|
||||
role: e.target.value
|
||||
}
|
||||
|
||||
setMember(selectState.team.id, input)
|
||||
.then(res => {
|
||||
let selectState = this.state.select
|
||||
let team = res.body
|
||||
team.Members = team.Members.sort((a, b) => {
|
||||
return new Date(a._pivot_createdAt) - new Date(b._pivot_createdAt)
|
||||
})
|
||||
selectState.team = team
|
||||
selectState.newMember = ''
|
||||
|
||||
this.setState({select: selectState})
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.status != null) throw err
|
||||
else console.error(err)
|
||||
})
|
||||
}.bind(this)
|
||||
}
|
||||
|
||||
renderSelectTab () {
|
||||
let selectState = this.state.select
|
||||
|
||||
let membersEl = selectState.team.Members.map(member => {
|
||||
let isCurrentUser = this.state.currentUser.id === member.id
|
||||
|
||||
return (
|
||||
<li key={'user-' + member.id}>
|
||||
<ProfileImage className='userPhoto' email={member.email} size='30'/>
|
||||
<div className='userInfo'>
|
||||
<div className='userName'>{`${member.profileName} (${member.name})`}</div>
|
||||
<div className='userEmail'>{member.email}</div>
|
||||
</div>
|
||||
|
||||
<div className='userControl'>
|
||||
<select onChange={e => this.handleMemberRoleChange(member.name)(e)} disabled={isCurrentUser} value={member._pivot_role} className='userRole'>
|
||||
<option value='owner'>Owner</option>
|
||||
<option value='member'>Member</option>
|
||||
</select>
|
||||
<button onClick={e => this.handleMemberDeleteButtonClick(member.name)(e)} disabled={isCurrentUser}><i className='fa fa-times fa-fw'/></button>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='selectTab'>
|
||||
<div className='title'>Select member</div>
|
||||
<div className='memberForm'>
|
||||
<Select
|
||||
className='memberName'
|
||||
autoload={false}
|
||||
asyncOptions={getUsers}
|
||||
onChange={val => this.handleNewMemberChange(val)}
|
||||
value={selectState.newMember}
|
||||
/>
|
||||
<button onClick={e => this.handleClickAddMemberButton(e)} className='addMemberBtn'>add</button>
|
||||
</div>
|
||||
<ul className='memberList'>
|
||||
{membersEl}
|
||||
</ul>
|
||||
<button onClick={e => this.props.close(e)}className='confirmBtn'>Done</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
let currentTab = this.state.currentTab === 'create'
|
||||
? this.renderCreateTab()
|
||||
: this.renderSelectTab()
|
||||
|
||||
return (
|
||||
<div className='CreateNewTeam modal'>
|
||||
<button onClick={e => this.handleCloseClick(e)} className='closeBtn'><i className='fa fa-fw fa-times'/></button>
|
||||
|
||||
{currentTab}
|
||||
|
||||
<div className='tabNav'>
|
||||
<i className={'fa fa-circle fa-fw' + (this.state.currentTab === 'create' ? ' active' : '')}/>
|
||||
<i className={'fa fa-circle fa-fw' + (this.state.currentTab === 'select' ? ' active' : '')}/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
CreateNewTeam.propTypes = {
|
||||
close: PropTypes.func
|
||||
}
|
||||
Reference in New Issue
Block a user