mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 09:46:22 +00:00
before applying redux
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ electron_build/
|
||||
.env
|
||||
dist/
|
||||
vendor/
|
||||
Boost-darwin-x64/
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -0,0 +1,4 @@
|
||||
[submodule "submodules/ace"]
|
||||
path = submodules/ace
|
||||
url = https://github.com/ajaxorg/ace-builds.git
|
||||
branch = master
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var CodeViewer = require('../../main/Components/CodeViewer')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<title>CodeXen Popup</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('../../resources/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('mousewheel', function(e) {
|
||||
if(e.deltaY % 1 !== 0) {
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
if (!Object.assign) {
|
||||
Object.defineProperty(Object, 'assign', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: function(target) {
|
||||
'use strict';
|
||||
if (target === undefined || target === null) {
|
||||
throw new TypeError('Cannot convert first argument to object');
|
||||
}
|
||||
|
||||
var to = Object(target);
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var nextSource = arguments[i];
|
||||
if (nextSource === undefined || nextSource === null) {
|
||||
continue;
|
||||
}
|
||||
nextSource = Object(nextSource);
|
||||
|
||||
var keysArray = Object.keys(Object(nextSource));
|
||||
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
|
||||
var nextKey = keysArray[nextIndex];
|
||||
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
|
||||
if (desc !== undefined && desc.enumerable) {
|
||||
to[nextKey] = nextSource[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
return to;
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script src="../ace/src-min/ace.js"></script>
|
||||
<script>
|
||||
require('../electron-stylus')(__dirname + '/../styles/finder/index.styl', 'finderCss')
|
||||
require('node-jsx').install({ harmony: true, extension: '.jsx' })
|
||||
require('./index.jsx')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<title>CodeXen Popup</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('../../resources/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('mousewheel', function(e) {
|
||||
if(e.deltaY % 1 !== 0) {
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script src="../ace/src-min/ace.js"></script>
|
||||
<script>
|
||||
|
||||
require("babel-core/register")
|
||||
require('./index.jsx')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,7 +3,7 @@ var remote = require('remote')
|
||||
var hideFinder = remote.getGlobal('hideFinder')
|
||||
var clipboard = require('clipboard')
|
||||
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var ArticleFilter = require('../main/Mixins/ArticleFilter')
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/main">Go Main</a>
|
||||
<a href="/main">Go Popup</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var ExternalLink = require('../Mixins/ExternalLink')
|
||||
var KeyCaster = require('../Mixins/KeyCaster')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
var Select = require('react-select')
|
||||
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
|
||||
209
browser/main/Components/ArticleDetail.jsx
Normal file
209
browser/main/Components/ArticleDetail.jsx
Normal file
@@ -0,0 +1,209 @@
|
||||
var React = require('react')
|
||||
var moment = require('moment')
|
||||
var _ = require('lodash')
|
||||
|
||||
var CodeEditor = require('./CodeEditor')
|
||||
var MarkdownPreview = require('./MarkdownPreview')
|
||||
var ModeIcon = require('./ModeIcon')
|
||||
var Select = require('react-select')
|
||||
|
||||
var Modal = require('../Mixins/Modal')
|
||||
var ForceUpdate = require('../Mixins/ForceUpdate')
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
|
||||
var aceModes = require('../../../modules/ace-modes')
|
||||
|
||||
var modeOptions = aceModes.map(function (mode) {
|
||||
return {
|
||||
label: mode,
|
||||
value: mode
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [ForceUpdate(60000), Modal, LinkedState],
|
||||
propTypes: {
|
||||
currentArticle: React.PropTypes.object,
|
||||
showOnlyWithTag: React.PropTypes.func,
|
||||
planet: React.PropTypes.object,
|
||||
switchDetailMode: React.PropTypes.func,
|
||||
user: React.PropTypes.shape({
|
||||
id: React.PropTypes.number,
|
||||
name: React.PropTypes.string,
|
||||
Folders: React.PropTypes.array
|
||||
}),
|
||||
article: React.PropTypes.object,
|
||||
saveCurrentArticle: React.PropTypes.func,
|
||||
detailMode: React.PropTypes.string
|
||||
},
|
||||
getInitialState: function () {
|
||||
var article = this.props.currentArticle != null ? {
|
||||
id: this.props.currentArticle.id,
|
||||
title: this.props.currentArticle.title,
|
||||
content: this.props.currentArticle.CurrentRevision.title,
|
||||
tags: this.props.currentArticle.Tags.map(function (tag) {
|
||||
return tag.name
|
||||
}),
|
||||
mode: this.props.currentArticle.mode,
|
||||
status: this.props.currentArticle.status
|
||||
} : null
|
||||
// console.log('init staet')
|
||||
// console.log(article)
|
||||
return {
|
||||
isEditModalOpen: false,
|
||||
article: article
|
||||
}
|
||||
},
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
if (nextProps.detailMode === 'edit') {
|
||||
var article = {
|
||||
id: nextProps.currentArticle.id,
|
||||
title: nextProps.currentArticle.title,
|
||||
content: nextProps.currentArticle.CurrentRevision.content,
|
||||
tags: nextProps.currentArticle.Tags.map(function (tag) {
|
||||
return tag.name
|
||||
}),
|
||||
mode: nextProps.currentArticle.mode,
|
||||
FolderId: nextProps.currentArticle.FolderId,
|
||||
status: nextProps.currentArticle.status
|
||||
}
|
||||
this.setState({article: article})
|
||||
}
|
||||
},
|
||||
openDeleteModal: function () {
|
||||
if (this.props.article == null) return
|
||||
},
|
||||
handleFolderIdChange: function (FolderId) {
|
||||
this.state.article.FolderId = FolderId
|
||||
this.setState({article: this.state.article})
|
||||
},
|
||||
handleTagsChange: function (tag, tags) {
|
||||
tags = _.uniq(tags, function (tag) {
|
||||
return tag.value
|
||||
})
|
||||
|
||||
this.state.article.tags = tags.map(function (tag) {
|
||||
return tag.value
|
||||
})
|
||||
this.setState({article: this.state.article})
|
||||
},
|
||||
handleModeChange: function (mode) {
|
||||
this.state.article.mode = mode
|
||||
this.setState({article: this.state.article})
|
||||
},
|
||||
handleContentChange: function (e, value) {
|
||||
var article = this.state.article
|
||||
article.content = value
|
||||
this.setState({article: article})
|
||||
},
|
||||
saveArticle: function () {
|
||||
if (this.state.article.mode === '') {
|
||||
return this.refs.mode.focus()
|
||||
}
|
||||
if (this.state.article.FolderId === '') {
|
||||
return this.refs.folder.focus()
|
||||
}
|
||||
this.props.saveCurrentArticle(this.state.article)
|
||||
},
|
||||
render: function () {
|
||||
if (this.props.currentArticle == null) {
|
||||
return (
|
||||
<div className='ArticleDetail'>
|
||||
Nothing selected
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (this.props.detailMode === 'show') {
|
||||
return this.renderViewer()
|
||||
}
|
||||
if (this.state.article == null) {
|
||||
return (
|
||||
<div className='ArticleDetail'>
|
||||
Nothing selected
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return this.renderEditor()
|
||||
},
|
||||
renderEditor: function () {
|
||||
var article = this.state.article
|
||||
|
||||
var folderOptions = this.props.user.Folders.map(function (folder) {
|
||||
return {
|
||||
label: folder.name,
|
||||
value: folder.id
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='ArticleDetail edit'>
|
||||
<div className='detailInfo'>
|
||||
<div className='left'>
|
||||
<Select ref='folder' onChange={this.handleFolderIdChange} clearable={false} options={folderOptions} placeholder='select folder...' value={article.FolderId} className='folder'/>
|
||||
<Select onChange={this.handleTagsChange} clearable={false} multi={true} placeholder='add some tags...' allowCreate={true} value={article.tags} className='tags'/>
|
||||
</div>
|
||||
<div className='right'>
|
||||
<button onClick={this.props.switchDetailMode('show')}>Cancel</button>
|
||||
<button onClick={this.saveArticle} className='primary'>Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='detailBody'>
|
||||
<div className='detailPanel'>
|
||||
<div className='header'>
|
||||
<div className='title'>
|
||||
<input ref='title' valueLink={this.linkState('article.title')}/>
|
||||
</div>
|
||||
<Select ref='mode' onChange={this.handleModeChange} clearable={false} options={modeOptions}placeholder='select mode...' value={article.mode} className='mode'/>
|
||||
</div>
|
||||
<CodeEditor onChange={this.handleContentChange} mode={article.mode} code={article.content}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
renderViewer: function () {
|
||||
var article = this.props.currentArticle
|
||||
var tags = article.Tags.length > 0 ? article.Tags.map(function (tag) {
|
||||
return (
|
||||
<a key={tag.id}>{tag.name}</a>
|
||||
)
|
||||
}) : (
|
||||
<span className='noTags'>Not tagged yet</span>
|
||||
)
|
||||
|
||||
var folder = _.findWhere(this.props.user.Folders, {id: article.FolderId})
|
||||
var folderName = folder != null ? folder.name : null
|
||||
|
||||
return (
|
||||
<div className='ArticleDetail show'>
|
||||
<div className='detailInfo'>
|
||||
<div className='left'>
|
||||
<div className='info'>
|
||||
<i className='fa fa-fw fa-square'/> {folderName}
|
||||
by {article.User.profileName}
|
||||
Created {moment(article.createdAt).format('YYYY/MM/DD')}
|
||||
Updated {moment(article.updatedAt).format('YYYY/MM/DD')}
|
||||
</div>
|
||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
|
||||
</div>
|
||||
|
||||
<div className='right'>
|
||||
<button onClick={this.props.switchDetailMode('edit')}><i className='fa fa-fw fa-edit'/></button>
|
||||
<button><i className='fa fa-fw fa-trash'/></button>
|
||||
<button><i className='fa fa-fw fa-share-alt'/></button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='detailBody'>
|
||||
<div className='detailPanel'>
|
||||
<div className='header'>
|
||||
<ModeIcon className='mode' mode={article.mode}/>
|
||||
<div className='title'>{article.title}</div>
|
||||
</div>
|
||||
{article.mode === 'markdown' ? <MarkdownPreview content={article.CurrentRevision.content}/> : <CodeEditor readOnly={true} onChange={this.handleContentChange} mode={article.mode} code={article.CurrentRevision.content}/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
63
browser/main/Components/ArticleList.jsx
Normal file
63
browser/main/Components/ArticleList.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
var React = require('react')
|
||||
var ReactRouter = require('react-router')
|
||||
var moment = require('moment')
|
||||
var _ = require('lodash')
|
||||
|
||||
var ForceUpdate = require('../Mixins/ForceUpdate')
|
||||
var Markdown = require('../Mixins/Markdown')
|
||||
|
||||
var ProfileImage = require('../Components/ProfileImage')
|
||||
var ModeIcon = require('../Components/ModeIcon')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [ReactRouter.Navigation, ReactRouter.State, ForceUpdate(60000), Markdown],
|
||||
propTypes: {
|
||||
articles: React.PropTypes.array
|
||||
},
|
||||
handleArticleClick: function (article) {
|
||||
return function () {
|
||||
this.props.selectArticle(article.id)
|
||||
}.bind(this)
|
||||
},
|
||||
render: function () {
|
||||
var articles = this.props.articles.map(function (article) {
|
||||
if (article == null) return null
|
||||
var tags = _.isArray(article.Tags) && article.Tags.length > 0 ? article.Tags.map(function (tag) {
|
||||
return (
|
||||
<a key={tag.id}>#{tag.name}</a>
|
||||
)
|
||||
}.bind(this)) : (
|
||||
<span>Not tagged yet</span>
|
||||
)
|
||||
var params = this.getParams()
|
||||
var isActive = this.props.currentArticle.id === article.id
|
||||
|
||||
return (
|
||||
<li key={'article-' + article.id}>
|
||||
<div onClick={this.handleArticleClick(article)} className={'articleItem' + (isActive ? ' active' : '')}>
|
||||
<div className='top'>
|
||||
<i className='fa fa-fw fa-square'/>
|
||||
by <ProfileImage className='profileImage' size='20' email={article.User.email}/> {article.User.profileName}
|
||||
<span className='updatedAt'>{article.status != null ? article.status : moment(article.updatedAt).fromNow()}</span>
|
||||
</div>
|
||||
<div className='middle'>
|
||||
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{article.status !== 'new' ? article.title : '(New article)'}</div>
|
||||
</div>
|
||||
<div className='bottom'>
|
||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='divider'></div>
|
||||
</li>
|
||||
)
|
||||
}.bind(this))
|
||||
|
||||
return (
|
||||
<div className='ArticleList'>
|
||||
<ul ref='articles'>
|
||||
{articles}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var ace = window.ace
|
||||
|
||||
@@ -7,7 +7,13 @@ module.exports = React.createClass({
|
||||
code: React.PropTypes.string,
|
||||
mode: React.PropTypes.string,
|
||||
className: React.PropTypes.string,
|
||||
onChange: React.PropTypes.func
|
||||
onChange: React.PropTypes.func,
|
||||
readOnly: React.PropTypes.bool
|
||||
},
|
||||
getDefaultProps: function () {
|
||||
return {
|
||||
readOnly: false
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
var el = React.findDOMNode(this.refs.target)
|
||||
@@ -17,6 +23,9 @@ module.exports = React.createClass({
|
||||
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) {
|
||||
@@ -53,7 +62,7 @@ module.exports = React.createClass({
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div ref='target' className={this.props.className}></div>
|
||||
<div ref='target' className={this.props.className == null ? 'CodeEditor' : 'CodeEditor ' + this.props.className}></div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
var CodeEditor = require('./CodeEditor')
|
||||
var Select = require('react-select')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var ace = window.ace
|
||||
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
var React = require('react')
|
||||
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var KeyCaster = require('../Mixins/KeyCaster')
|
||||
|
||||
import React, { PropTypes, findDOMNode } from 'react'
|
||||
import linkState from '../helpers/linkState'
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [LinkedState, KeyCaster('contactModal')],
|
||||
propTypes: {
|
||||
close: React.PropTypes.func
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
export default class ContactModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.linkState = linkState
|
||||
|
||||
this.state = {
|
||||
isSent: false,
|
||||
mail: {
|
||||
title: '',
|
||||
content: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
onKeyCast: function (e) {
|
||||
}
|
||||
|
||||
onKeyCast (e) {
|
||||
switch (e.status) {
|
||||
case 'closeModal':
|
||||
this.props.close()
|
||||
@@ -32,11 +30,13 @@ module.exports = React.createClass({
|
||||
this.sendEmail()
|
||||
break
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
React.findDOMNode(this.refs.title).focus()
|
||||
},
|
||||
sendEmail: function () {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
findDOMNode(this.refs.title).focus()
|
||||
}
|
||||
|
||||
sendEmail () {
|
||||
Hq.sendEmail(this.state.mail)
|
||||
.then(function (res) {
|
||||
this.setState({isSent: !this.state.isSent})
|
||||
@@ -44,8 +44,9 @@ module.exports = React.createClass({
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
},
|
||||
render: function () {
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='ContactModal modal'>
|
||||
<div className='modal-header'><h1>Contact form</h1></div>
|
||||
@@ -77,4 +78,8 @@ module.exports = React.createClass({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ContactModal.propTypes = {
|
||||
close: PropTypes.func
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
var ReactRouter = require('react-router')
|
||||
var Navigation = ReactRouter.Navigation
|
||||
var State = ReactRouter.State
|
||||
var Link = ReactRouter.Link
|
||||
var Reflux = require('reflux')
|
||||
var _ = require('lodash')
|
||||
|
||||
var Modal = require('../Mixins/Modal')
|
||||
|
||||
@@ -65,30 +64,12 @@ module.exports = React.createClass({
|
||||
openTeamCreateModal: function () {
|
||||
this.openModal(TeamCreateModal, {user: this.state.currentUser, transitionTo: this.transitionTo})
|
||||
},
|
||||
openPreferencesModal: function () {
|
||||
this.openModal(PreferencesModal)
|
||||
},
|
||||
openPlanetCreateModal: function () {
|
||||
this.openModal(PlanetCreateModal, {transitionTo: this.transitionTo})
|
||||
},
|
||||
toggleProfilePopup: function () {
|
||||
this.openProfilePopup()
|
||||
},
|
||||
openProfilePopup: function () {
|
||||
this.setState({isProfilePopupOpen: true}, function () {
|
||||
document.addEventListener('click', this.closeProfilePopup)
|
||||
})
|
||||
},
|
||||
closeProfilePopup: function () {
|
||||
document.removeEventListener('click', this.closeProfilePopup)
|
||||
this.setState({isProfilePopupOpen: false})
|
||||
},
|
||||
handleLogoutClick: function () {
|
||||
this.openModal(LogoutModal, {transitionTo: this.transitionTo})
|
||||
},
|
||||
switchPlanetByIndex: function (index) {
|
||||
var planetProps = this.refs.planets.props.children[index - 1].props
|
||||
this.transitionTo('planet', {userName: planetProps.userName, planetName: planetProps.planetName})
|
||||
switchUserByIndex: function (index) {
|
||||
var userProp = this.refs.users.props.children[index - 1].props
|
||||
this.transitionTo('user', {userId: userProp.id})
|
||||
},
|
||||
render: function () {
|
||||
var params = this.getParams()
|
||||
@@ -96,88 +77,33 @@ module.exports = React.createClass({
|
||||
if (this.state.currentUser == null) {
|
||||
return null
|
||||
}
|
||||
console.log(this.state.currentUser.Teams)
|
||||
|
||||
var planets = this.state.currentUser.Planets.map(function (planet) {
|
||||
planet.userName = this.state.currentUser.name
|
||||
return planet
|
||||
}.bind(this)).concat(this.state.currentUser.Teams.reduce(function (_planets, team) {
|
||||
return _planets.concat(team.Planets == null ? [] : team.Planets.map(function (planet) {
|
||||
planet.userName = team.name
|
||||
return planet
|
||||
}))
|
||||
}, [])).map(function (planet, index) {
|
||||
var users = [this.state.currentUser]
|
||||
if (_.isArray(this.state.currentUser.Teams)) users = users.concat(this.state.currentUser.Teams)
|
||||
|
||||
var userButtons = users.map(function (user, index) {
|
||||
return (
|
||||
<li userName={planet.userName} planetName={planet.name} key={planet.id} className={params.userName === planet.userName && params.planetName === planet.name ? 'active' : ''}>
|
||||
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>
|
||||
{planet.name[0]}
|
||||
<div className='planetTooltip'>{planet.userName}/{planet.name}</div>
|
||||
<li key={'user-' + user.id}>
|
||||
<Link to='user' params={{userId: user.id}}>
|
||||
{user.userType === 'person' ? (<ProfileImage email={user.email} size='44'/>): user.name[0]}
|
||||
<div className='userTooltip'>{user.name}</div>
|
||||
</Link>
|
||||
{index < 9 ? (<div className='shortCut'>⌘{index + 1}</div>) : null}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
var popup = this.renderPopup()
|
||||
|
||||
return (
|
||||
<div className='HomeNavigator'>
|
||||
<button onClick={this.toggleProfilePopup} className='profileButton'>
|
||||
<ProfileImage size='55' email={this.state.currentUser.email}/>
|
||||
</button>
|
||||
{popup}
|
||||
<ul ref='planets' className='planetList'>
|
||||
{planets}
|
||||
<ul ref='users' className='userList'>
|
||||
{userButtons}
|
||||
</ul>
|
||||
<button onClick={this.openPlanetCreateModal} className='newPlanet'>
|
||||
<button onClick={this.openTeamCreateModal} className='newTeamButton'>
|
||||
<i className='fa fa-plus'/>
|
||||
<div className='tooltip'>Create new planet</div>
|
||||
<div className='tooltip'>Create new team</div>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
renderPopup: function () {
|
||||
var teams = this.state.currentUser.Teams == null ? [] : this.state.currentUser.Teams.map(function (team) {
|
||||
return (
|
||||
<li key={'user-' + team.id}>
|
||||
<Link to='userHome' params={{userName: team.name}} className='userName'>{team.profileName} ({team.name})</Link>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div ref='profilePopup' className={'profilePopup' + (this.state.isProfilePopupOpen ? '' : ' close')}>
|
||||
<div className='profileGroup'>
|
||||
<div className='profileGroupLabel'>
|
||||
<span>You</span>
|
||||
</div>
|
||||
<ul className='profileGroupList'>
|
||||
<li>
|
||||
<Link to='userHome' params={{userName: this.state.currentUser.name}} className='userName'>Profile ({this.state.currentUser.name})</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className='profileGroup'>
|
||||
<div className='profileGroupLabel'>
|
||||
<span>Team</span>
|
||||
</div>
|
||||
<ul className='profileGroupList'>
|
||||
{teams}
|
||||
<li>
|
||||
<button onClick={this.openTeamCreateModal} className='createNewTeam'><i className='fa fa-plus-square-o'/> create new team</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul className='controlGroup'>
|
||||
<li>
|
||||
<button onClick={this.openPreferencesModal}><i className='fa fa-gears fa-fw'/> Preferences</button>
|
||||
</li>
|
||||
<li>
|
||||
<button onClick={this.handleLogoutClick}><i className='fa fa-sign-out fa-fw'/> Log out</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var CodeForm = require('./CodeForm')
|
||||
var NoteForm = require('./NoteForm')
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react')
|
||||
|
||||
var socket = require('../Services/socket')
|
||||
|
||||
79
browser/main/Components/ModeIcon.jsx
Normal file
79
browser/main/Components/ModeIcon.jsx
Normal file
@@ -0,0 +1,79 @@
|
||||
var React = require('react')
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
mode: React.PropTypes.string
|
||||
},
|
||||
getClassName: function () {
|
||||
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: function () {
|
||||
var className = this.getClassName()
|
||||
return (
|
||||
<i className={this.props.className + ' ' + className}/>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
var Select = require('react-select')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
var moment = require('moment')
|
||||
|
||||
var CodeViewer = require('./CodeViewer')
|
||||
var CodeEditModal = require('./CodeEditModal')
|
||||
var CodeDeleteModal = require('./CodeDeleteModal')
|
||||
var NoteEditModal = require('./NoteEditModal')
|
||||
var NoteDeleteModal = require('./NoteDeleteModal')
|
||||
var MarkdownPreview = require('./MarkdownPreview')
|
||||
var ProfileImage = require('./ProfileImage')
|
||||
|
||||
var Modal = require('../Mixins/Modal')
|
||||
var ForceUpdate = require('../Mixins/ForceUpdate')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [ForceUpdate(60000), Modal],
|
||||
propTypes: {
|
||||
article: React.PropTypes.object,
|
||||
showOnlyWithTag: React.PropTypes.func,
|
||||
planet: React.PropTypes.object
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isEditModalOpen: false
|
||||
}
|
||||
},
|
||||
openEditModal: function () {
|
||||
if (this.props.article == null) return
|
||||
switch (this.props.article.type) {
|
||||
case 'code' :
|
||||
this.openModal(CodeEditModal, {code: this.props.article, planet: this.props.planet})
|
||||
break
|
||||
case 'note' :
|
||||
this.openModal(NoteEditModal, {note: this.props.article, planet: this.props.planet})
|
||||
}
|
||||
},
|
||||
openDeleteModal: function () {
|
||||
if (this.props.article == null) return
|
||||
switch (this.props.article.type) {
|
||||
case 'code' :
|
||||
this.openModal(CodeDeleteModal, {code: this.props.article, planet: this.props.planet})
|
||||
break
|
||||
case 'note' :
|
||||
this.openModal(NoteDeleteModal, {note: this.props.article, planet: this.props.planet})
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
var article = this.props.article
|
||||
if (article == null) {
|
||||
return (
|
||||
<div className='PlanetArticleDetail'>
|
||||
Nothing selected
|
||||
</div>
|
||||
)
|
||||
}
|
||||
var tags = article.Tags.length > 0 ? article.Tags.map(function (tag) {
|
||||
return (
|
||||
<a onClick={this.props.showOnlyWithTag(tag.name)} key={tag.id}>#{tag.name}</a>
|
||||
)
|
||||
}.bind(this)) : (
|
||||
<a className='noTag'>Not tagged yet</a>
|
||||
)
|
||||
if (article.type === 'code') {
|
||||
return (
|
||||
<div className='PlanetArticleDetail codeDetail'>
|
||||
<div className='detailHeader'>
|
||||
<div className='itemLeft'>
|
||||
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
|
||||
<i className='fa fa-code fa-fw'></i>
|
||||
</div>
|
||||
|
||||
<div className='itemRight'>
|
||||
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
|
||||
<div className='description'>{article.description}</div>
|
||||
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
|
||||
</div>
|
||||
|
||||
<span className='itemControl'>
|
||||
<button id='articleEditButton' onClick={this.openEditModal} className='editButton'>
|
||||
<i className='fa fa-edit fa-fw'></i>
|
||||
<div className='tooltip'>Edit</div>
|
||||
</button>
|
||||
<button onClick={this.openDeleteModal} className='deleteButton'>
|
||||
<i className='fa fa-trash fa-fw'></i>
|
||||
<div className='tooltip'>Delete</div>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div className='detailBody'>
|
||||
<CodeViewer className='content' code={article.content} mode={article.mode}/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className='PlanetArticleDetail noteDetail'>
|
||||
<div className='detailHeader'>
|
||||
<div className='itemLeft'>
|
||||
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
|
||||
<i className='fa fa-file-text-o fa-fw'></i>
|
||||
</div>
|
||||
|
||||
<div className='itemRight'>
|
||||
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
|
||||
<div className='description'>{article.title}</div>
|
||||
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
|
||||
</div>
|
||||
|
||||
<span className='itemControl'>
|
||||
<button id='articleEditButton' onClick={this.openEditModal} className='editButton'>
|
||||
<i className='fa fa-edit fa-fw'></i>
|
||||
<div className='tooltip'>Edit</div>
|
||||
</button>
|
||||
<button onClick={this.openDeleteModal} className='deleteButton'>
|
||||
<i className='fa fa-trash fa-fw'></i>
|
||||
<div className='tooltip'>Delete</div>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div className='detailBody'>
|
||||
<MarkdownPreview className='content' content={article.content}/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,100 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var moment = require('moment')
|
||||
|
||||
var ForceUpdate = require('../Mixins/ForceUpdate')
|
||||
var Markdown = require('../Mixins/Markdown')
|
||||
|
||||
var ProfileImage = require('../Components/ProfileImage')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [ReactRouter.Navigation, ReactRouter.State, ForceUpdate(60000), Markdown],
|
||||
propTypes: {
|
||||
articles: React.PropTypes.array,
|
||||
showOnlyWithTag: React.PropTypes.func
|
||||
},
|
||||
handleArticleClikck: function (article) {
|
||||
if (article.type === 'code') {
|
||||
return function (e) {
|
||||
var params = this.getParams()
|
||||
|
||||
this.transitionTo('codes', {
|
||||
userName: params.userName,
|
||||
planetName: params.planetName,
|
||||
localId: article.localId
|
||||
})
|
||||
}.bind(this)
|
||||
}
|
||||
|
||||
if (article.type === 'note') {
|
||||
return function (e) {
|
||||
var params = this.getParams()
|
||||
|
||||
this.transitionTo('notes', {
|
||||
userName: params.userName,
|
||||
planetName: params.planetName,
|
||||
localId: article.localId
|
||||
})
|
||||
}.bind(this)
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
var articles = this.props.articles.map(function (article) {
|
||||
var tags = article.Tags.length > 0 ? article.Tags.map(function (tag) {
|
||||
return (
|
||||
<a onClick={this.props.showOnlyWithTag(tag.name)} key={tag.id}>#{tag.name}</a>
|
||||
)
|
||||
}.bind(this)) : (
|
||||
<a className='noTag'>Not tagged yet</a>
|
||||
)
|
||||
var params = this.getParams()
|
||||
var isActive = article.type === 'code' ? this.isActive('codes') && parseInt(params.localId, 10) === article.localId : this.isActive('notes') && parseInt(params.localId, 10) === article.localId
|
||||
|
||||
if (article.type === 'code') {
|
||||
return (
|
||||
<li onClick={this.handleArticleClikck(article)} key={'code-' + article.id}>
|
||||
<div className={'articleItem' + (isActive ? ' active' : '')}>
|
||||
<div className='itemLeft'>
|
||||
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
|
||||
<i className='fa fa-code fa-fw'></i>
|
||||
</div>
|
||||
<div className='itemRight'>
|
||||
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
|
||||
<div className='description'>{article.description}</div>
|
||||
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='divider'></div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<li onClick={this.handleArticleClikck(article)} key={'note-' + article.id}>
|
||||
<div className={'articleItem blueprintItem' + (isActive ? ' active' : '')}>
|
||||
<div className='itemLeft'>
|
||||
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
|
||||
<i className='fa fa-file-text-o fa-fw'></i>
|
||||
</div>
|
||||
|
||||
<div className='itemRight'>
|
||||
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
|
||||
<div className='description'>{article.title}</div>
|
||||
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='divider'></div>
|
||||
</li>
|
||||
)
|
||||
|
||||
}.bind(this))
|
||||
|
||||
return (
|
||||
<div className='PlanetArticleList'>
|
||||
<ul ref='articles'>
|
||||
{articles}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
var ReactRouter = require('react-router')
|
||||
var Link = ReactRouter.Link
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
var ReactRouter = require('react-router')
|
||||
var Navigation = ReactRouter.Navigation
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
var ipc = require('ipc')
|
||||
var remote = require('remote')
|
||||
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var ExternalLink = require('../Mixins/ExternalLink')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
var md5 = require('md5')
|
||||
|
||||
module.exports = React.createClass({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
var socket = require('../Services/socket')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
var Reflux = require('reflux')
|
||||
var Select = require('react-select')
|
||||
|
||||
|
||||
30
browser/main/Components/TopBar.jsx
Normal file
30
browser/main/Components/TopBar.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
var React = require('react')
|
||||
|
||||
var ExternalLink = require('../Mixins/ExternalLink')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [ExternalLink],
|
||||
propTypes: {
|
||||
search: React.PropTypes.string,
|
||||
changeSearch: React.PropTypes.func
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className='TopBar'>
|
||||
<div className='left'>
|
||||
<div className='search'>
|
||||
<i className='fa fa-search'/>
|
||||
<input value={this.props.search} onChange={this.props.changeSearch} className='searchInput' placeholder='Search...'/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='right'>
|
||||
<a onClick={this.openExternal} href='http://b00st.io' className='logo'>
|
||||
<img width='44' height='44' src='resources/favicon-230x230.png'/>
|
||||
<div className='tooltip'>Boost official page</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
53
browser/main/Components/UserNavigator.jsx
Normal file
53
browser/main/Components/UserNavigator.jsx
Normal file
@@ -0,0 +1,53 @@
|
||||
var React = require('react')
|
||||
var _ = require('lodash')
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
createNewArticle: React.PropTypes.func,
|
||||
search: React.PropTypes.string,
|
||||
user: React.PropTypes.object
|
||||
},
|
||||
render: function () {
|
||||
var user = this.props.user
|
||||
|
||||
var folders = _.isArray(user.Folders) ? user.Folders.map(function (folder) {
|
||||
var isActive = this.props.search.match(new RegExp('in:' + folder.name))
|
||||
return (
|
||||
<button className={'folderButton' + (isActive ? ' active' : '')}><i className='fa fa-fw fa-square'/> {folder.name}</button>
|
||||
)
|
||||
}.bind(this)) : null
|
||||
|
||||
var members = _.isArray(user.Members) ? user.Members.map(function (member) {
|
||||
return <button className='memberButton'>{member.profileName}</button>
|
||||
}) : null
|
||||
|
||||
return (
|
||||
<div className='UserNavigator'>
|
||||
<div className='profile'>
|
||||
<div className='profileName'>{user.profileName}</div>
|
||||
<div className='name'>{user.name}</div>
|
||||
<div className='dropdownIcon'><i className='fa fa-chevron-down'/></div>
|
||||
</div>
|
||||
|
||||
<div className='control'>
|
||||
<button onClick={this.props.createNewArticle} className='newPostButton'>New Post</button>
|
||||
</div>
|
||||
|
||||
<div className='menu'>
|
||||
<div className='menuGruop folders'>
|
||||
<div className='label'>
|
||||
Folders
|
||||
<button className='plusButton'><i className='fa fa-plus'/></button>
|
||||
</div>
|
||||
<button className={'folderButton' + (this.props.search.match(/in:[a-z0-9-_]/) ? '' : ' active')}>All Folders</button>
|
||||
{folders}
|
||||
</div>
|
||||
|
||||
{user.userType === 'team' ? (
|
||||
<div className='members'>{members}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var RouteHandler = ReactRouter.RouteHandler
|
||||
var State = ReactRouter.State
|
||||
var Navigation = ReactRouter.Navigation
|
||||
|
||||
var AuthFilter = require('../Mixins/AuthFilter')
|
||||
var KeyCaster = require('../Mixins/KeyCaster')
|
||||
|
||||
var HomeNavigator = require('../Components/HomeNavigator')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [AuthFilter.OnlyUser, State, Navigation, KeyCaster('homeContainer')],
|
||||
componentDidMount: function () {
|
||||
if (this.isActive('homeEmpty')) {
|
||||
var user = JSON.parse(localStorage.getItem('currentUser'))
|
||||
if (user.Planets != null && user.Planets.length > 0) {
|
||||
this.transitionTo('planet', {userName: user.name, planetName: user.Planets[0].name})
|
||||
return
|
||||
}
|
||||
this.transitionTo('userHome', {userName: user.name})
|
||||
}
|
||||
},
|
||||
onKeyCast: function (e) {
|
||||
switch (e.status) {
|
||||
case 'switchPlanet':
|
||||
this.refs.navigator.switchPlanetByIndex(e.data)
|
||||
break
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className='HomeContainer'>
|
||||
<HomeNavigator ref='navigator'/>
|
||||
<RouteHandler/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
92
browser/main/Containers/LoginContainer.js
Normal file
92
browser/main/Containers/LoginContainer.js
Normal file
@@ -0,0 +1,92 @@
|
||||
var Hq = require('../Services/Hq')
|
||||
var socket = require('../Services/socket')
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import linkState from '../helpers/linkState'
|
||||
|
||||
export default class LoginPage extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
user: {},
|
||||
isSending: false,
|
||||
error: null
|
||||
}
|
||||
this.linkState = linkState
|
||||
}
|
||||
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.setState({
|
||||
isSending: true,
|
||||
error: null
|
||||
}, function () {
|
||||
console.log(this.state.user)
|
||||
Hq.login(this.state.user)
|
||||
.then(function (res) {
|
||||
localStorage.setItem('token', res.body.token)
|
||||
localStorage.setItem('currentUser', JSON.stringify(res.body.user))
|
||||
|
||||
try {
|
||||
this.props.history.pushState('home')
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
}
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
if (err.response == null) {
|
||||
return this.setState({
|
||||
error: {name: 'CunnectionRefused', message: 'API server doesn\'t respond. Check your internet connection.'},
|
||||
isSending: false
|
||||
})
|
||||
}
|
||||
|
||||
var res = err.response
|
||||
|
||||
// Connection Failed or Whatever
|
||||
this.setState({
|
||||
error: err.response.body,
|
||||
isSending: false
|
||||
})
|
||||
}.bind(this))
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='LoginContainer'>
|
||||
<img className='logo' src='resources/favicon-230x230.png'/>
|
||||
|
||||
<nav className='authNavigator text-center'><Link to='/login' activeClassName='active'>Log In</Link> / <Link to='/signup' activeClassName='active'>Sign Up</Link></nav>
|
||||
|
||||
<form onSubmit={e => this.handleSubmit(e)}>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.password')} onChange={this.handleChange} type='password' placeholder='Password'/>
|
||||
</div>
|
||||
|
||||
{this.state.isSending ? (
|
||||
<p className='alertInfo'>Logging in...</p>
|
||||
) : null}
|
||||
|
||||
{this.state.error != null ? <p className='alertError'>{this.state.error.message}</p> : null}
|
||||
|
||||
<div className='formField'>
|
||||
<button className='logInButton' type='submit'>Log In</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LoginPage.propTypes = {
|
||||
history: PropTypes.shape({
|
||||
pushState: PropTypes.func
|
||||
})
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/* global localStorage */
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var Link = ReactRouter.Link
|
||||
|
||||
var AuthFilter = require('../Mixins/AuthFilter')
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var Hq = require('../Services/Hq')
|
||||
var socket = require('../Services/socket')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [LinkedState, ReactRouter.Navigation, AuthFilter.OnlyGuest],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
user: {},
|
||||
authenticationFailed: false,
|
||||
connectionFailed: false,
|
||||
isSending: false
|
||||
}
|
||||
},
|
||||
onListen: function (res) {
|
||||
if (res.status === 'failedToLogIn') {
|
||||
if (res.data.status === 401) {
|
||||
// Wrong E-mail or Password
|
||||
this.setState({
|
||||
authenticationFailed: true,
|
||||
connectionFailed: false,
|
||||
isSending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
// Connection Failed or Whatever
|
||||
this.setState({
|
||||
authenticationFailed: false,
|
||||
connectionFailed: true,
|
||||
isSending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
},
|
||||
handleSubmit: function (e) {
|
||||
this.setState({
|
||||
authenticationFailed: false,
|
||||
connectionFailed: false,
|
||||
isSending: true
|
||||
}, function () {
|
||||
Hq.login(this.state.user)
|
||||
.then(function (res) {
|
||||
localStorage.setItem('token', res.body.token)
|
||||
localStorage.setItem('currentUser', JSON.stringify(res.body.user))
|
||||
socket.reconnect()
|
||||
|
||||
this.transitionTo('userHome', {userName: res.body.user.name})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
if (err.status === 401) {
|
||||
this.setState({
|
||||
authenticationFailed: true,
|
||||
connectionFailed: false,
|
||||
isSending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
this.setState({
|
||||
authenticationFailed: false,
|
||||
connectionFailed: true,
|
||||
isSending: false
|
||||
})
|
||||
}.bind(this))
|
||||
})
|
||||
|
||||
e.preventDefault()
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className='LoginContainer'>
|
||||
<img className='logo' src='resources/favicon-230x230.png'/>
|
||||
|
||||
<nav className='authNavigator text-center'><Link to='login'>Log In</Link> / <Link to='signup'>Sign Up</Link></nav>
|
||||
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<div className='form-group'>
|
||||
<input className='stripInput' valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<input className='stripInput' valueLink={this.linkState('user.password')} onChange={this.handleChange} type='password' placeholder='Password'/>
|
||||
</div>
|
||||
|
||||
{this.state.isSending ? (
|
||||
<p className='alertInfo'>Logging in...</p>
|
||||
) : null}
|
||||
|
||||
{this.state.connectionFailed ? (
|
||||
<p className='alertError'>Please try again.</p>
|
||||
) : null}
|
||||
|
||||
{this.state.authenticationFailed ? (
|
||||
<p className='alertError'>Wrong E-mail or Password.</p>
|
||||
) : null}
|
||||
|
||||
<div className='form-group'>
|
||||
<button className='logInButton' type='submit'>Log In</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
45
browser/main/Containers/MainContainer.js
Normal file
45
browser/main/Containers/MainContainer.js
Normal file
@@ -0,0 +1,45 @@
|
||||
var ipc = require('ipc')
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
var ContactModal = require('../Components/ContactModal')
|
||||
|
||||
export default class MainContainer extends React.Component {
|
||||
// mixins: [Modal],
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {updateAvailable: false}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
ipc.on('update-available', function (message) {
|
||||
this.setState({updateAvailable: true})
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
updateApp () {
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MainContainer.propTypes = {
|
||||
children: PropTypes.element
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var ipc = require('ipc')
|
||||
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var RouteHandler = ReactRouter.RouteHandler
|
||||
var Navigation = ReactRouter.Navigation
|
||||
var State = ReactRouter.State
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
var socket = require('../Services/socket')
|
||||
|
||||
var Modal = require('../Mixins/Modal')
|
||||
|
||||
var UserStore = require('../Stores/UserStore')
|
||||
|
||||
var ContactModal = require('../Components/ContactModal')
|
||||
|
||||
function fetchPlanet (userName, planetName) {
|
||||
return Hq.fetchPlanet(userName, planetName)
|
||||
.then(function (res) {
|
||||
var planet = res.body
|
||||
|
||||
planet.Codes.forEach(function (code) {
|
||||
code.type = 'code'
|
||||
})
|
||||
|
||||
planet.Notes.forEach(function (note) {
|
||||
note.type = 'note'
|
||||
})
|
||||
|
||||
localStorage.setItem('planet-' + planet.id, JSON.stringify(planet))
|
||||
|
||||
return planet
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [State, Navigation, Modal],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
updateAvailable: false
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
ipc.on('update-available', function (message) {
|
||||
this.setState({updateAvailable: true})
|
||||
}.bind(this))
|
||||
|
||||
if (this.isActive('root')) {
|
||||
if (localStorage.getItem('currentUser') == null) {
|
||||
this.transitionTo('login')
|
||||
return
|
||||
} else {
|
||||
this.transitionTo('home')
|
||||
}
|
||||
}
|
||||
|
||||
Hq.getUser()
|
||||
.then(function (res) {
|
||||
var user = res.body
|
||||
UserStore.Actions.update(user)
|
||||
|
||||
user.Planets.forEach(function (planet) {
|
||||
fetchPlanet(user.name, planet.name)
|
||||
})
|
||||
user.Teams.forEach(function (team) {
|
||||
team.Planets.forEach(function (planet) {
|
||||
fetchPlanet(team.name, planet.name)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (err.status === 401) {
|
||||
console.log('Not logged in yet')
|
||||
localStorage.removeItem('currentUser')
|
||||
this.transitionTo('login')
|
||||
return
|
||||
}
|
||||
console.error(err)
|
||||
}.bind(this))
|
||||
},
|
||||
updateApp: function () {
|
||||
ipc.send('update-app', 'Deal with it.')
|
||||
},
|
||||
openContactModal: function () {
|
||||
this.openModal(ContactModal)
|
||||
},
|
||||
render: function () {
|
||||
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>
|
||||
<RouteHandler/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,5 @@
|
||||
/* global localStorage*/
|
||||
'strict'
|
||||
var React = require('react/addons')
|
||||
var React = require('react')
|
||||
var ReactRouter = require('react-router')
|
||||
var Reflux = require('reflux')
|
||||
|
||||
98
browser/main/Containers/SignupContainer.js
Normal file
98
browser/main/Containers/SignupContainer.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import linkState from '../helpers/linkState'
|
||||
import openExternal from '../helpers/openExternal'
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
export default class SignupContainer extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
user: {},
|
||||
connectionFailed: false,
|
||||
emailConflicted: false,
|
||||
nameConflicted: false,
|
||||
validationFailed: false,
|
||||
isSending: false,
|
||||
error: null
|
||||
}
|
||||
this.linkState = linkState
|
||||
this.openExternal = openExternal
|
||||
}
|
||||
|
||||
handleSubmit (e) {
|
||||
this.setState({
|
||||
isSending: true,
|
||||
error: null
|
||||
}, function () {
|
||||
Hq.signup(this.state.user)
|
||||
.then(res => {
|
||||
localStorage.setItem('token', res.body.token)
|
||||
localStorage.setItem('currentUser', JSON.stringify(res.body.user))
|
||||
|
||||
this.props.history.pushState('userHome', {userId: res.body.user.id})
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
if (err.response == null) {
|
||||
return this.setState({
|
||||
error: {name: 'CunnectionRefused', message: 'API server doesn\'t respond. Check your internet connection.'},
|
||||
isSending: false
|
||||
})
|
||||
}
|
||||
|
||||
// Connection Failed or Whatever
|
||||
this.setState({
|
||||
error: err.response.body,
|
||||
isSending: false
|
||||
})
|
||||
}.bind(this))
|
||||
})
|
||||
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='SignupContainer'>
|
||||
<img className='logo' src='resources/favicon-230x230.png'/>
|
||||
|
||||
<nav className='authNavigator text-center'><Link to='/login' activeClassName='active'>Log In</Link> / <Link to='/signup' activeClassName='active'>Sign Up</Link></nav>
|
||||
|
||||
<form onSubmit={e => this.handleSubmit(e)}>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.password')} type='password' placeholder='Password'/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.name')} type='text' placeholder='name'/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.profileName')} type='text' placeholder='Profile name'/>
|
||||
</div>
|
||||
|
||||
{this.state.isSending ? (
|
||||
<p className='alertInfo'>Signing up...</p>
|
||||
) : null}
|
||||
|
||||
{this.state.error != null ? <p className='alertError'>{this.state.error.message}</p> : null}
|
||||
|
||||
<div className='formField'>
|
||||
<button className='logInButton' type='submit'>Sign Up</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p className='alert'>会員登録することで、<a onClick={this.openExternal} href='http://boostio.github.io/regulations.html'>当サイトの利用規約</a>及び<a onClick={this.openExternal} href='http://boostio.github.io/privacypolicies.html'>Cookieの使用を含むデータに関するポリシー</a>に同意するものとします。</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SignupContainer.propTypes = {
|
||||
history: PropTypes.shape({
|
||||
pushState: PropTypes.func
|
||||
})
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var Link = ReactRouter.Link
|
||||
|
||||
var AuthFilter = require('../Mixins/AuthFilter')
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var ExternalLink = require('../Mixins/ExternalLink')
|
||||
var Hq = require('../Services/Hq')
|
||||
var socket = require('../Services/socket')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [LinkedState, ReactRouter.Navigation, AuthFilter.OnlyGuest, ExternalLink],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
user: {},
|
||||
connectionFailed: false,
|
||||
emailConflicted: false,
|
||||
nameConflicted: false,
|
||||
validationFailed: false,
|
||||
isSending: false
|
||||
}
|
||||
},
|
||||
handleSubmit: function (e) {
|
||||
this.setState({
|
||||
connectionFailed: false,
|
||||
emailConflicted: false,
|
||||
nameConflicted: false,
|
||||
validationFailed: false,
|
||||
isSending: true
|
||||
}, function () {
|
||||
Hq.signup(this.state.user)
|
||||
.then(function (res) {
|
||||
localStorage.setItem('token', res.body.token)
|
||||
localStorage.setItem('currentUser', JSON.stringify(res.body.user))
|
||||
socket.reconnect()
|
||||
|
||||
this.transitionTo('userHome', {userName: res.body.user.name})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
var res = err.response
|
||||
if (err.status === 409) {
|
||||
// Confliction
|
||||
var emailConflicted = res.body.errors[0].path === 'email'
|
||||
var nameConflicted = res.body.errors[0].path === 'name'
|
||||
|
||||
this.setState({
|
||||
connectionFailed: false,
|
||||
emailConflicted: emailConflicted,
|
||||
nameConflicted: nameConflicted,
|
||||
validationFailed: false,
|
||||
isSending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (err.status === 422) {
|
||||
// Validation Failed
|
||||
this.setState({
|
||||
connectionFailed: false,
|
||||
emailConflicted: false,
|
||||
nameConflicted: false,
|
||||
validationFailed: {
|
||||
errors: res.body.errors.map(function (error) {
|
||||
return error.path
|
||||
})
|
||||
},
|
||||
isSending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Connection Failed or Whatever
|
||||
this.setState({
|
||||
connectionFailed: true,
|
||||
emailConflicted: false,
|
||||
nameConflicted: false,
|
||||
validationFailed: false,
|
||||
isSending: false
|
||||
})
|
||||
return
|
||||
}.bind(this))
|
||||
})
|
||||
|
||||
e.preventDefault()
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className='SignupContainer'>
|
||||
<img className='logo' src='resources/favicon-230x230.png'/>
|
||||
|
||||
<nav className='authNavigator text-center'><Link to='login'>Log In</Link> / <Link to='signup'>Sign Up</Link></nav>
|
||||
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<div className='form-group'>
|
||||
<input className='stripInput' valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<input className='stripInput' valueLink={this.linkState('user.password')} type='password' placeholder='Password'/>
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<input className='stripInput' valueLink={this.linkState('user.name')} type='text' placeholder='name'/>
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<input className='stripInput' valueLink={this.linkState('user.profileName')} type='text' placeholder='Profile name'/>
|
||||
</div>
|
||||
|
||||
{this.state.isSending ? (
|
||||
<p className='alertInfo'>Signing up...</p>
|
||||
) : null}
|
||||
|
||||
{this.state.connectionFailed ? (
|
||||
<p className='alertError'>Please try again.</p>
|
||||
) : null}
|
||||
|
||||
{this.state.emailConflicted ? (
|
||||
<p className='alertError'>E-mail already exists.</p>
|
||||
) : null}
|
||||
|
||||
{this.state.nameConflicted ? (
|
||||
<p className='alertError'>Username already exists.</p>
|
||||
) : null}
|
||||
|
||||
{this.state.validationFailed ? (
|
||||
<p className='alertError'>Please fill every field correctly: {this.state.validationFailed.errors.join(', ')}</p>
|
||||
) : null}
|
||||
|
||||
<div className='form-group'>
|
||||
<button className='logInButton' type='submit'>Sign Up</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p className='alert'>会員登録することで、<a onClick={this.openExternal} href='http://boostio.github.io/regulations.html'>当サイトの利用規約</a>及び<a onClick={this.openExternal} href='http://boostio.github.io/privacypolicies.html'>Cookieの使用を含むデータに関するポリシー</a>に同意するものとします。</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,367 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var Navigation = ReactRouter.Navigation
|
||||
var State = ReactRouter.State
|
||||
var RouteHandler = ReactRouter.RouteHandler
|
||||
var Link = ReactRouter.Link
|
||||
var Reflux = require('reflux')
|
||||
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var Modal = require('../Mixins/Modal')
|
||||
var Helper = require('../Mixins/Helper')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
var ProfileImage = require('../Components/ProfileImage')
|
||||
var EditProfileModal = require('../Components/EditProfileModal')
|
||||
var TeamSettingsModal = require('../Components/TeamSettingsModal')
|
||||
var PlanetCreateModal = require('../Components/PlanetCreateModal')
|
||||
var AddMemberModal = require('../Components/AddMemberModal')
|
||||
var TeamCreateModal = require('../Components/TeamCreateModal')
|
||||
|
||||
var UserStore = require('../Stores/UserStore')
|
||||
var PlanetStore = require('../Stores/PlanetStore')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [LinkedState, State, Navigation, Modal, Reflux.listenTo(UserStore, 'onUserChange'), Reflux.listenTo(PlanetStore, 'onPlanetChange'), Helper],
|
||||
propTypes: {
|
||||
params: React.PropTypes.shape({
|
||||
userName: React.PropTypes.string,
|
||||
planetName: React.PropTypes.string
|
||||
})
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
user: null
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
this.fetchUser()
|
||||
},
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
if (this.state.user == null) {
|
||||
this.fetchUser(nextProps.params.userName)
|
||||
return
|
||||
}
|
||||
|
||||
if (nextProps.params.userName !== this.state.user.name) {
|
||||
this.setState({
|
||||
user: null
|
||||
}, function () {
|
||||
this.fetchUser(nextProps.params.userName)
|
||||
})
|
||||
}
|
||||
},
|
||||
onUserChange: function (res) {
|
||||
if (this.state.user == null) return
|
||||
|
||||
var member
|
||||
switch (res.status) {
|
||||
case 'userUpdated':
|
||||
if (this.state.user.id === res.data.id) {
|
||||
this.setState({user: res.data})
|
||||
}
|
||||
break
|
||||
case 'memberAdded':
|
||||
member = res.data
|
||||
if (this.state.user.userType === 'team' && member.TeamMember.TeamId === this.state.user.id) {
|
||||
this.state.user.Members = this.updateItemToTargetArray(member, this.state.user.Members)
|
||||
|
||||
this.setState({user: this.state.user})
|
||||
}
|
||||
break
|
||||
case 'memberRemoved':
|
||||
member = res.data
|
||||
if (this.state.user.userType === 'team' && member.TeamMember.TeamId === this.state.user.id) {
|
||||
this.state.user.Members = this.deleteItemFromTargetArray(member, this.state.user.Members)
|
||||
|
||||
this.setState({user: this.state.user})
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
onPlanetChange: function (res) {
|
||||
if (this.state.user == null) return
|
||||
|
||||
var currentUser, planet, isOwner, team
|
||||
switch (res.status) {
|
||||
case 'updated':
|
||||
// if state.user is currentUser, planet will be fetched by UserStore
|
||||
currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
if (currentUser.id === this.state.user.id) return
|
||||
|
||||
planet = res.data
|
||||
isOwner = planet.Owner.id === this.state.user.id
|
||||
if (isOwner) {
|
||||
this.state.user.Planets = this.updateItemToTargetArray(planet, this.state.user.Planets)
|
||||
this.setState({user: this.state.user})
|
||||
return
|
||||
}
|
||||
// check if team of user has this planet
|
||||
team = null
|
||||
this.state.user.userType !== 'team' && this.state.user.Teams.some(function (_team) {
|
||||
if (planet.Owner.id === _team.id) {
|
||||
team = _team
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (team != null) {
|
||||
team.Planets = this.updateItemToTargetArray(planet, team.Planets)
|
||||
this.setState({user: this.state.user})
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
case 'destroyed':
|
||||
// if state.user is currentUser, planet will be fetched by UserStore
|
||||
currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
if (currentUser.id === this.state.user.id) return
|
||||
|
||||
planet = res.data
|
||||
isOwner = planet.Owner.id === this.state.user.id
|
||||
if (isOwner) {
|
||||
this.state.user.Planets = this.deleteItemFromTargetArray(planet, this.state.user.Planets)
|
||||
this.setState({user: this.state.user})
|
||||
return
|
||||
}
|
||||
// check if team of user has this planet
|
||||
team = null
|
||||
this.state.user.userType !== 'team' && this.state.user.Teams.some(function (_team) {
|
||||
if (planet.Owner.id === _team.id) {
|
||||
team = _team
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (team != null) {
|
||||
team.Planets = this.deleteItemFromTargetArray(planet, team.Planets)
|
||||
this.setState({user: this.state.user})
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
fetchUser: function (userName) {
|
||||
if (userName == null) userName = this.props.params.userName
|
||||
|
||||
Hq.fetchUser(userName)
|
||||
.then(function (res) {
|
||||
this.setState({user: res.body})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
},
|
||||
openEditProfileModal: function () {
|
||||
this.openModal(EditProfileModal, {user: this.state.user})
|
||||
},
|
||||
openTeamSettingsModal: function () {
|
||||
this.openModal(TeamSettingsModal, {team: this.state.user})
|
||||
},
|
||||
openAddUserModal: function () {
|
||||
this.openModal(AddMemberModal, {team: this.state.user})
|
||||
},
|
||||
openTeamCreateModal: function () {
|
||||
this.openModal(TeamCreateModal, {user: this.state.user})
|
||||
},
|
||||
openPlanetCreateModalWithOwnerName: function (name) {
|
||||
return function () {
|
||||
this.openModal(PlanetCreateModal, {ownerName: name})
|
||||
}.bind(this)
|
||||
},
|
||||
render: function () {
|
||||
var user = this.state.user
|
||||
|
||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
|
||||
if (this.isActive('userHome')) {
|
||||
if (user == null) {
|
||||
return (
|
||||
<div className='UserContainer'>
|
||||
User Loading...
|
||||
</div>
|
||||
)
|
||||
} else if (user.userType === 'team') {
|
||||
return this.renderTeamHome(currentUser)
|
||||
} else {
|
||||
return this.renderUserHome(currentUser)
|
||||
}
|
||||
} else if (this.isActive('planet') && user != null && user.userType === 'team') {
|
||||
var members = user.Members.map(function (member) {
|
||||
return (
|
||||
<li key={'user-' + member.id}><Link to='userHome' params={{userName: member.name}}>
|
||||
<ProfileImage className='memberImage' size='22' email={member.email}/>
|
||||
<div className='memberInfo'>
|
||||
<div className='memberProfileName'>{member.profileName}</div>
|
||||
<div className='memberName'>@{member.name}</div>
|
||||
</div>
|
||||
</Link></li>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div className='UserContainer'>
|
||||
<RouteHandler/>
|
||||
<div className='memberPopup'>
|
||||
<div className='label'>Members</div>
|
||||
<ul className='members'>
|
||||
{members}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className='UserContainer'>
|
||||
<RouteHandler/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
renderTeamHome: function (currentUser) {
|
||||
var user = this.state.user
|
||||
|
||||
var isOwner = user.Members == null ? false : user.Members.some(function (member) {
|
||||
return member.id === currentUser.id && member.TeamMember.role === 'owner'
|
||||
})
|
||||
|
||||
var userPlanets = user.Planets.map(function (planet) {
|
||||
return (
|
||||
<li key={'planet-' + planet.id}>
|
||||
<Link to='planet' params={{userName: user.name, planetName: planet.name}}>{user.name}/{planet.name}</Link>
|
||||
{!planet.public ? (<i className='fa fa-lock'/>) : null}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
var members = user.Members == null ? [] : user.Members.map(function (member) {
|
||||
return (
|
||||
<li key={'user-' + member.id}>
|
||||
<Link to='userHome' params={{userName: member.name}}>
|
||||
<ProfileImage size='22' className='memberImage' email={member.email}/>
|
||||
<div className='memberInfo'>
|
||||
<div className='memberProfileName'>{member.profileName} <span className='memberRole'>({member.TeamMember.role})</span></div>
|
||||
<div className='memberName'>@{member.name}</div>
|
||||
</div>
|
||||
</Link>
|
||||
<div className='role'></div>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div className='UserContainer'>
|
||||
<div className='userProfile'>
|
||||
<ProfileImage className='userPhoto' size='75' email={user.email}/>
|
||||
<div className='userInfo'>
|
||||
<div className='userProfileName'>{user.profileName}</div>
|
||||
<div className='userName'>{user.name}</div>
|
||||
</div>
|
||||
|
||||
{isOwner ? (<button onClick={this.openTeamSettingsModal} className='editProfileButton'>Team settings</button>) : null}
|
||||
</div>
|
||||
<div className='memberList'>
|
||||
<div className='memberLabel'>{members.length} {members.length > 1 ? 'Members' : 'Member'}</div>
|
||||
<ul className='members'>
|
||||
{members}
|
||||
{isOwner ? (<li><button onClick={this.openAddUserModal} className='addMemberButton'><i className='fa fa-plus-square-o'/> add Member</button></li>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
<div className='planetList'>
|
||||
<div className='planetLabel'>{userPlanets.length} {userPlanets.length > 0 ? 'Planets' : 'Planet'}</div>
|
||||
<div className='planetGroup'>
|
||||
<ul className='planets'>
|
||||
{userPlanets}
|
||||
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(user.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
renderUserHome: function (currentUser) {
|
||||
var user = this.state.user
|
||||
|
||||
var isOwner = currentUser.id === user.id
|
||||
|
||||
var userPlanets = user.Planets.map(function (planet) {
|
||||
return (
|
||||
<li key={'planet-' + planet.id}>
|
||||
<Link to='planet' params={{userName: user.name, planetName: planet.name}}>{user.name}/{planet.name}</Link>
|
||||
{!planet.public ? (<i className='fa fa-lock'/>) : null}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
var teams = user.Teams == null ? [] : user.Teams.map(function (team) {
|
||||
return (
|
||||
<li key={'user-' + team.id}>
|
||||
<Link to='userHome' params={{userName: team.name}}>
|
||||
<div className='teamInfo'>
|
||||
<div className='teamProfileName'>{team.profileName}</div>
|
||||
<div className='teamName'>@{team.name}</div>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
var teamPlanets = user.Teams == null ? [] : user.Teams.map(function (team) {
|
||||
var planets = (team.Planets == null ? [] : team.Planets).map(function (planet) {
|
||||
return (
|
||||
<li key={'planet-' + planet.id}>
|
||||
<Link to='planet' params={{userName: team.name, planetName: planet.name}}>{team.name}/{planet.name}</Link>
|
||||
{!planet.public ? (<i className='fa fa-lock'/>) : null}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div key={'user-' + team.id} className='planetGroup'>
|
||||
<div className='planetGroupLabel'>{team.profileName} <small>@{team.name}</small></div>
|
||||
<ul className='planets'>
|
||||
{planets}
|
||||
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(team.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}.bind(this))
|
||||
|
||||
var planetCount = userPlanets.length + user.Teams.reduce(function (sum, team) {
|
||||
return sum + (team.Planets != null ? team.Planets.length : 0)
|
||||
}, 0)
|
||||
|
||||
return (
|
||||
<div className='UserContainer'>
|
||||
<div className='userProfile'>
|
||||
<ProfileImage className='userPhoto' size='75' email={user.email}/>
|
||||
<div className='userInfo'>
|
||||
<div className='userProfileName'>{user.profileName}</div>
|
||||
<div className='userName'>{user.name}</div>
|
||||
</div>
|
||||
|
||||
{isOwner ? (
|
||||
<button onClick={this.openEditProfileModal} className='editProfileButton'>Edit profile</button>) : null}
|
||||
</div>
|
||||
<div className='teamList'>
|
||||
<div className='teamLabel'>{teams.length} {teams.length > 1 ? 'Teams' : 'Team'}</div>
|
||||
<ul className='teams'>
|
||||
{teams}
|
||||
{isOwner ? (<li><button onClick={this.openTeamCreateModal} className='createTeamButton'><i className='fa fa-plus-square-o'/> Create new team</button></li>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
<div className='planetList'>
|
||||
<div className='planetLabel'>{planetCount} {planetCount > 1 ? 'Planets' : 'Planet'}</div>
|
||||
<div className='planetGroup'>
|
||||
<div className='planetGroupLabel'>{user.profileName} <small>@{user.name}</small></div>
|
||||
<ul className='planets'>
|
||||
{userPlanets}
|
||||
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(user.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
{teamPlanets}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
9
browser/main/HomeContainer/Components/ArticleDetail.js
Normal file
9
browser/main/HomeContainer/Components/ArticleDetail.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
export default class ArticleDetail extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<div></div>
|
||||
)
|
||||
}
|
||||
}
|
||||
11
browser/main/HomeContainer/Components/ArticleList.js
Normal file
11
browser/main/HomeContainer/Components/ArticleList.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
class ArticleList extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className='ArticleList'></div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ArticleList
|
||||
12
browser/main/HomeContainer/Components/ArticleNavigator.js
Normal file
12
browser/main/HomeContainer/Components/ArticleNavigator.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
class ArticleNavigator extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<div className='ArticleNavigator'>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ArticleNavigator
|
||||
33
browser/main/HomeContainer/Components/UserNavigator.js
Normal file
33
browser/main/HomeContainer/Components/UserNavigator.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
|
||||
export default class UserNavigator extends Component {
|
||||
|
||||
renderUserList () {
|
||||
var users = this.props.users.map(user => (
|
||||
<li key={'user-' + user.id}>
|
||||
<Link to={'/users/' + user.id}>
|
||||
<div className='userTooltip'>{user.name}</div>
|
||||
</Link>
|
||||
</li>
|
||||
))
|
||||
|
||||
return (
|
||||
<div className='userList'>
|
||||
{users}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='UserNavigator'>
|
||||
{this.renderUserList()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
UserNavigator.propTypes = {
|
||||
users: PropTypes.array
|
||||
}
|
||||
10
browser/main/HomeContainer/actions.js
Normal file
10
browser/main/HomeContainer/actions.js
Normal file
@@ -0,0 +1,10 @@
|
||||
function updateUser (user) {
|
||||
return {
|
||||
type: 'USER_UPDATE',
|
||||
data: user
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
updateUser: updateUser
|
||||
}
|
||||
50
browser/main/HomeContainer/index.js
Normal file
50
browser/main/HomeContainer/index.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from 'react'
|
||||
// import { connect } from 'react-redux'
|
||||
// import actionss....
|
||||
import UserNavigator from './Components/UserNavigator'
|
||||
import ArticleNavigator from './Components/ArticleNavigator'
|
||||
import ArticleList from './Components/ArticleList'
|
||||
import ArticleDetail from './Components/ArticleDetail'
|
||||
|
||||
// var AuthFilter = require('../Mixins/AuthFilter')
|
||||
// var KeyCaster = require('../Mixins/KeyCaster')
|
||||
|
||||
class HomeContainer extends React.Component {
|
||||
componentDidMount () {
|
||||
// if (!this.isActive('user')) {
|
||||
// console.log('redirect to user home')
|
||||
// var user = JSON.parse(localStorage.getItem('currentUser'))
|
||||
// this.transitionTo('userHome', {userId: user.id})
|
||||
// }
|
||||
}
|
||||
render () {
|
||||
let users = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'me',
|
||||
email: 'fll@eme.com'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'me',
|
||||
email: 'fll@eme.com'
|
||||
}
|
||||
]
|
||||
return (
|
||||
<div className='HomeContainer'>
|
||||
<UserNavigator users={users} />
|
||||
<ArticleNavigator/>
|
||||
<ArticleList/>
|
||||
<ArticleDetail/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// function remap (state) {
|
||||
// console.log('mapped')
|
||||
// console.log(state)
|
||||
// return {}
|
||||
// }
|
||||
|
||||
export default HomeContainer
|
||||
15
browser/main/HomeContainer/reducer.js
Normal file
15
browser/main/HomeContainer/reducer.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import {combineReducers} from 'redux'
|
||||
|
||||
const initialCurrentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
|
||||
function currentUser (state, action) {
|
||||
switch (action.type) {
|
||||
|
||||
default:
|
||||
return initialCurrentUser
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
currentUser
|
||||
})
|
||||
@@ -4,12 +4,12 @@ var mixin = {}
|
||||
|
||||
mixin.OnlyGuest = {
|
||||
componentDidMount: function () {
|
||||
var currentUser = localStorage.getItem('currentUser')
|
||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
|
||||
if (currentUser == null) {
|
||||
return
|
||||
}
|
||||
this.transitionTo('userHome', {userName: currentUser.name})
|
||||
this.transitionTo('homeDefault')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ var md = markdownit({
|
||||
|
||||
var Markdown = {
|
||||
markdown: function (content) {
|
||||
if (content == null) content = ''
|
||||
return md.render(content)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
var React = require('react/addons')
|
||||
import React from 'react'
|
||||
var ModalBase = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
|
||||
@@ -21,11 +21,9 @@ function setPartialState (component, path, value) {
|
||||
updateIn(component.state, path, value))
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
linkState: function (path) {
|
||||
return {
|
||||
value: getIn(this.state, path),
|
||||
requestChange: setPartialState.bind(null, this, path)
|
||||
}
|
||||
export default function linkState (path) {
|
||||
return {
|
||||
value: getIn(this.state, path),
|
||||
requestChange: setPartialState.bind(null, this, path)
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,12 @@ module.exports = {
|
||||
// Auth
|
||||
login: function (input) {
|
||||
return request
|
||||
.post(apiUrl + 'auth')
|
||||
.post(apiUrl + 'auth/login')
|
||||
.send(input)
|
||||
},
|
||||
signup: function (input) {
|
||||
return request
|
||||
.post(apiUrl + 'auth/signup')
|
||||
.post(apiUrl + 'auth/register')
|
||||
.send(input)
|
||||
},
|
||||
getUser: function () {
|
||||
@@ -30,124 +30,36 @@ module.exports = {
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
|
||||
// Resources
|
||||
fetchUser: function (userName) {
|
||||
fetchArticles: function (userId) {
|
||||
return request
|
||||
.get(apiUrl + 'resources/' + userName)
|
||||
.get(apiUrl + 'teams/' + userId +'/articles')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
},
|
||||
updateUser: function (userName, input) {
|
||||
fetchArticlesByFolderId: function (folderId) {
|
||||
return request
|
||||
.put(apiUrl + 'resources/' + userName)
|
||||
.get(apiUrl + 'folders/' + folderId +'/articles')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
},
|
||||
createArticle: function (input) {
|
||||
return request
|
||||
.post(apiUrl + 'folders/' + input.FolderId + '/articles')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
createTeam: function (userName, input) {
|
||||
updateArticle: function (articleId, input) {
|
||||
return request
|
||||
.post(apiUrl + 'resources/' + userName + '/teams')
|
||||
.put(apiUrl + 'articles/' + articleId)
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
addMember: function (userName, input) {
|
||||
return request
|
||||
.post(apiUrl + 'resources/' + userName + '/members')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
removeMember: function (userName, input) {
|
||||
return request
|
||||
.del(apiUrl + 'resources/' + userName + '/members')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
createPlanet: function (userName, input) {
|
||||
return request
|
||||
.post(apiUrl + 'resources/' + userName + '/planets')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
fetchPlanet: function (userName, planetName) {
|
||||
return request
|
||||
.get(apiUrl + 'resources/' + userName + '/planets/' + planetName)
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
},
|
||||
updatePlanet: function (userName, planetName, input) {
|
||||
return request
|
||||
.put(apiUrl + 'resources/' + userName + '/planets/' + planetName)
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
destroyPlanet: function (userName, planetName) {
|
||||
return request
|
||||
.del(apiUrl + 'resources/' + userName + '/planets/' + planetName)
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
},
|
||||
createCode: function (userName, planetName, input) {
|
||||
return request
|
||||
.post(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/codes')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
updateCode: function (userName, planetName, localId, input) {
|
||||
return request
|
||||
.put(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/codes/' + localId)
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
destroyCode: function (userName, planetName, localId) {
|
||||
return request
|
||||
.del(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/codes/' + localId)
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
},
|
||||
createNote: function (userName, planetName, input) {
|
||||
return request
|
||||
.post(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/notes')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
updateNote: function (userName, planetName, localId, input) {
|
||||
return request
|
||||
.put(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/notes/' + localId)
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
destroyNote: function (userName, planetName, localId) {
|
||||
return request
|
||||
.del(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/notes/' + localId)
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
},
|
||||
|
||||
// Search
|
||||
searchTag: function (tagName) {
|
||||
return request
|
||||
|
||||
@@ -1,64 +1,17 @@
|
||||
/* global localStorage */
|
||||
|
||||
var config = require('../../../config')
|
||||
var UserStore = require('../Stores/UserStore')
|
||||
var PlanetStore = require('../Stores/PlanetStore')
|
||||
|
||||
var io = require('socket.io-client')(config.apiUrl)
|
||||
|
||||
io.on('connected', function (data) {
|
||||
console.log('connected by WS')
|
||||
reconnect()
|
||||
})
|
||||
|
||||
io.on('userUpdated', function (data) {
|
||||
console.log('userUpdated')
|
||||
UserStore.Actions.update(data)
|
||||
})
|
||||
|
||||
// Planet
|
||||
io.on('planetUpdated', function (data) {
|
||||
console.log('planetUpdated')
|
||||
PlanetStore.Actions.update(data)
|
||||
})
|
||||
|
||||
io.on('planetDestroyed', function (data) {
|
||||
console.log('planetDestroyed')
|
||||
PlanetStore.Actions.destroy(data)
|
||||
})
|
||||
|
||||
// Article
|
||||
io.on('codeUpdated', function (data) {
|
||||
console.log('codeUpdated')
|
||||
PlanetStore.Actions.updateCode(data)
|
||||
})
|
||||
io.on('codeDestroyed', function (data) {
|
||||
console.log('codeDestroyed')
|
||||
PlanetStore.Actions.destroyCode(data)
|
||||
})
|
||||
io.on('noteUpdated', function (data) {
|
||||
console.log('noteUpdated')
|
||||
PlanetStore.Actions.updateNote(data)
|
||||
})
|
||||
io.on('noteDestroyed', function (data) {
|
||||
console.log('noteDestroyed')
|
||||
PlanetStore.Actions.destroyNote(data)
|
||||
})
|
||||
|
||||
var reconnect = function (currentUser) {
|
||||
if (currentUser == null) currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
if (currentUser != null) {
|
||||
var rooms = ['user:' + currentUser.id].concat(currentUser.Teams.map(function (team) {
|
||||
return 'user:' + team.id
|
||||
}))
|
||||
|
||||
io.emit('room:sync', {rooms: rooms})
|
||||
} else {
|
||||
io.emit('room:sync', {rooms: []})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
io: io,
|
||||
reconnect: reconnect
|
||||
io: io
|
||||
}
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
/* global localStorage */
|
||||
var Reflux = require('reflux')
|
||||
var request = require('superagent')
|
||||
|
||||
var apiUrl = require('../../../config').apiUrl
|
||||
|
||||
var AuthStore = Reflux.createStore({
|
||||
init: function () {
|
||||
},
|
||||
// Reflux Store
|
||||
login: function (input) {
|
||||
request
|
||||
.post(apiUrl + 'auth/login')
|
||||
.send(input)
|
||||
.set('Accept', 'application/json')
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
this.trigger({
|
||||
status: 'failedToLogIn',
|
||||
data: res
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user = res.body.user
|
||||
localStorage.setItem('token', res.body.token)
|
||||
localStorage.setItem('user', JSON.stringify(res.body.user))
|
||||
|
||||
this.trigger({
|
||||
status: 'loggedIn',
|
||||
data: user
|
||||
})
|
||||
}.bind(this))
|
||||
},
|
||||
register: function (input) {
|
||||
request
|
||||
.post(apiUrl + 'auth/signup')
|
||||
.send(input)
|
||||
.set('Accept', 'application/json')
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
console.error(res)
|
||||
this.trigger({
|
||||
status: 'failedToRegister',
|
||||
data: res
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user = res.body.user
|
||||
localStorage.setItem('token', res.body.token)
|
||||
localStorage.setItem('user', JSON.stringify(res.body.user))
|
||||
|
||||
this.trigger({
|
||||
status: 'registered',
|
||||
data: user
|
||||
})
|
||||
}.bind(this))
|
||||
},
|
||||
refreshUser: function () {
|
||||
request
|
||||
.get(apiUrl + 'auth/user')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
AuthActions.logout()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var user = res.body
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
|
||||
this.trigger({
|
||||
status: 'userRefreshed',
|
||||
data: user
|
||||
})
|
||||
}.bind(this))
|
||||
},
|
||||
logout: function () {
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('currentUser')
|
||||
|
||||
this.trigger({
|
||||
status: 'loggedOut'
|
||||
})
|
||||
},
|
||||
updateProfile: function (input) {
|
||||
request
|
||||
.put(apiUrl + 'auth/user')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
this.trigger({
|
||||
status: 'userProfileUpdatingFailed',
|
||||
data: err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user = res.body
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
|
||||
this.trigger({
|
||||
status: 'userProfileUpdated',
|
||||
data: user
|
||||
})
|
||||
}.bind(this))
|
||||
},
|
||||
// Methods
|
||||
check: function () {
|
||||
if (localStorage.getItem('token')) return true
|
||||
return false
|
||||
},
|
||||
getUser: function () {
|
||||
var userJSON = localStorage.getItem('currentUser')
|
||||
if (userJSON == null) return null
|
||||
return JSON.parse(userJSON)
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = AuthStore
|
||||
@@ -1,179 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var Reflux = require('reflux')
|
||||
|
||||
var UserStore = require('./UserStore')
|
||||
|
||||
var Helper = require('../Mixins/Helper')
|
||||
|
||||
var actions = Reflux.createActions([
|
||||
'update',
|
||||
'destroy',
|
||||
'updateCode',
|
||||
'destroyCode',
|
||||
'updateNote',
|
||||
'destroyNote'
|
||||
])
|
||||
|
||||
module.exports = Reflux.createStore({
|
||||
mixins: [Helper],
|
||||
listenables: [actions],
|
||||
Actions: actions,
|
||||
/*
|
||||
Planet must be updated like below
|
||||
Planet
|
||||
Codes
|
||||
Tags
|
||||
User
|
||||
Notes
|
||||
Tags
|
||||
User
|
||||
Owner
|
||||
*/
|
||||
onUpdate: function (planet) {
|
||||
// Copy the planet object
|
||||
var aPlanet = Object.assign({}, planet)
|
||||
delete aPlanet.Codes
|
||||
delete aPlanet.Notes
|
||||
delete aPlanet.Owner
|
||||
|
||||
// Check if the planet should be updated to currentUser
|
||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
|
||||
var currentUserMustBeUpdated = false
|
||||
|
||||
var ownedByCurrentUser = currentUser.id === aPlanet.OwnerId
|
||||
if (ownedByCurrentUser) {
|
||||
currentUser.Planets = this.updateItemToTargetArray(aPlanet, currentUser.Planets)
|
||||
currentUserMustBeUpdated = true
|
||||
} else {
|
||||
var team = null
|
||||
if (currentUser.Teams.some(function (_team) {
|
||||
if (_team.id === aPlanet.OwnerId) {
|
||||
team = _team
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})) {
|
||||
team.Planets = this.updateItemToTargetArray(aPlanet, team.Planets)
|
||||
currentUserMustBeUpdated = true
|
||||
}
|
||||
}
|
||||
|
||||
// Update currentUser
|
||||
if (currentUserMustBeUpdated) {
|
||||
UserStore.Actions.update(currentUser)
|
||||
}
|
||||
|
||||
planet.Codes.forEach(function (code) {
|
||||
code.type = 'code'
|
||||
})
|
||||
|
||||
planet.Notes.forEach(function (note) {
|
||||
note.type = 'note'
|
||||
})
|
||||
|
||||
// Update the planet
|
||||
localStorage.setItem('planet-' + planet.id, JSON.stringify(planet))
|
||||
|
||||
this.trigger({
|
||||
status: 'updated',
|
||||
data: planet
|
||||
})
|
||||
},
|
||||
onDestroy: function (planet) {
|
||||
// Check if the planet should be updated to currentUser
|
||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
|
||||
var ownedByCurrentUser = currentUser.id === planet.OwnerId
|
||||
|
||||
if (ownedByCurrentUser) {
|
||||
currentUser.Planets = this.deleteItemFromTargetArray(planet, currentUser.Planets)
|
||||
}
|
||||
|
||||
if (!ownedByCurrentUser) {
|
||||
var team = null
|
||||
currentUser.Teams.some(function (_team) {
|
||||
if (_team.id === planet.OwnerId) {
|
||||
team = _team
|
||||
return true
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
if (team) {
|
||||
team.Planets = this.deleteItemFromTargetArray(planet, team.Planets)
|
||||
}
|
||||
}
|
||||
|
||||
// Update currentUser
|
||||
localStorage.setItem('currentUser', JSON.stringify(currentUser))
|
||||
UserStore.Actions.update(currentUser)
|
||||
|
||||
// Update the planet
|
||||
localStorage.setItem('planet-' + planet.id, JSON.stringify(planet))
|
||||
|
||||
this.trigger({
|
||||
status: 'destroyed',
|
||||
data: planet
|
||||
})
|
||||
},
|
||||
onUpdateCode: function (code) {
|
||||
code.type = 'code'
|
||||
|
||||
var planet = JSON.parse(localStorage.getItem('planet-' + code.PlanetId))
|
||||
if (planet != null) {
|
||||
planet.Codes = this.updateItemToTargetArray(code, planet.Codes)
|
||||
|
||||
localStorage.setItem('planet-' + code.PlanetId, JSON.stringify(planet))
|
||||
}
|
||||
|
||||
this.trigger({
|
||||
status: 'codeUpdated',
|
||||
data: code
|
||||
})
|
||||
},
|
||||
onDestroyCode: function (code) {
|
||||
var planet = JSON.parse(localStorage.getItem('planet-' + code.PlanetId))
|
||||
if (planet != null) {
|
||||
planet.Codes = this.deleteItemFromTargetArray(code, planet.Codes)
|
||||
|
||||
localStorage.setItem('planet-' + code.PlanetId, JSON.stringify(planet))
|
||||
}
|
||||
code.type = 'code'
|
||||
|
||||
this.trigger({
|
||||
status: 'codeDestroyed',
|
||||
data: code
|
||||
})
|
||||
},
|
||||
onUpdateNote: function (note) {
|
||||
note.type = 'note'
|
||||
|
||||
var planet = JSON.parse(localStorage.getItem('planet-' + note.PlanetId))
|
||||
if (planet != null) {
|
||||
planet.Notes = this.updateItemToTargetArray(note, planet.Notes)
|
||||
|
||||
localStorage.setItem('planet-' + note.PlanetId, JSON.stringify(planet))
|
||||
}
|
||||
|
||||
this.trigger({
|
||||
status: 'noteUpdated',
|
||||
data: note
|
||||
})
|
||||
},
|
||||
onDestroyNote: function (note) {
|
||||
var planet = JSON.parse(localStorage.getItem('planet-' + note.PlanetId))
|
||||
if (planet != null) {
|
||||
planet.Notes = this.deleteItemFromTargetArray(note, planet.Notes)
|
||||
|
||||
localStorage.setItem('planet-' + note.PlanetId, JSON.stringify(planet))
|
||||
}
|
||||
note.type = 'note'
|
||||
|
||||
this.trigger({
|
||||
status: 'noteDestroyed',
|
||||
data: note
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,58 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var Reflux = require('reflux')
|
||||
|
||||
var actions = Reflux.createActions([
|
||||
'update',
|
||||
'destroy'
|
||||
])
|
||||
|
||||
module.exports = Reflux.createStore({
|
||||
listenables: [actions],
|
||||
onUpdate: function (user) {
|
||||
if (this.socket == null) this.socket = require('../Services/socket')
|
||||
|
||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
if (currentUser.id === user.id) {
|
||||
localStorage.setItem('currentUser', JSON.stringify(user))
|
||||
|
||||
this.socket.reconnect(user)
|
||||
}
|
||||
|
||||
if (user.userType === 'team') {
|
||||
var isMyTeam = user.Members.some(function (member) {
|
||||
if (currentUser.id === member.id) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (isMyTeam) {
|
||||
var isNew = !currentUser.Teams.some(function (team, index) {
|
||||
if (user.id === team.id) {
|
||||
currentUser.Teams.splice(index, 1, user)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (isNew) {
|
||||
currentUser.Teams.push(user)
|
||||
}
|
||||
localStorage.setItem('currentUser', JSON.stringify(currentUser))
|
||||
}
|
||||
}
|
||||
|
||||
this.trigger({
|
||||
status: 'userUpdated',
|
||||
data: user
|
||||
})
|
||||
},
|
||||
onDestroy: function (user) {
|
||||
this.trigger({
|
||||
status: 'userDestroyed',
|
||||
data: user
|
||||
})
|
||||
},
|
||||
Actions: actions
|
||||
})
|
||||
29
browser/main/helpers/linkState.js
Normal file
29
browser/main/helpers/linkState.js
Normal file
@@ -0,0 +1,29 @@
|
||||
function getIn (object, path) {
|
||||
var stack = path.split('.')
|
||||
while (stack.length > 1) {
|
||||
object = object[stack.shift()]
|
||||
}
|
||||
return object[stack.shift()]
|
||||
}
|
||||
|
||||
function updateIn (object, path, value) {
|
||||
var current = object
|
||||
var stack = path.split('.')
|
||||
while (stack.length > 1) {
|
||||
current = current[stack.shift()]
|
||||
}
|
||||
current[stack.shift()] = value
|
||||
return object
|
||||
}
|
||||
|
||||
function setPartialState (component, path, value) {
|
||||
component.setState(
|
||||
updateIn(component.state, path, value))
|
||||
}
|
||||
|
||||
export default function linkState (path) {
|
||||
return {
|
||||
value: getIn(this.state, path),
|
||||
requestChange: setPartialState.bind(null, this, path)
|
||||
}
|
||||
}
|
||||
6
browser/main/helpers/openExternal.js
Normal file
6
browser/main/helpers/openExternal.js
Normal file
@@ -0,0 +1,6 @@
|
||||
var shell = require('shell')
|
||||
|
||||
export default function (e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('../../resources/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
#loadingCover{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 65px 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#loadingCover img{
|
||||
display: block;
|
||||
margin: 75px auto 5px;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
#loadingCover .message{
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
font-weight: 100;
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loadingCover">
|
||||
<img src="resources/favicon-230x230.png">
|
||||
<div class='message'>Loading...</div>
|
||||
</div>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
<script>
|
||||
if (!Object.assign) {
|
||||
Object.defineProperty(Object, 'assign', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: function(target) {
|
||||
'use strict';
|
||||
if (target === undefined || target === null) {
|
||||
throw new TypeError('Cannot convert first argument to object');
|
||||
}
|
||||
|
||||
var to = Object(target);
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var nextSource = arguments[i];
|
||||
if (nextSource === undefined || nextSource === null) {
|
||||
continue;
|
||||
}
|
||||
nextSource = Object(nextSource);
|
||||
|
||||
var keysArray = Object.keys(Object(nextSource));
|
||||
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
|
||||
var nextKey = keysArray[nextIndex];
|
||||
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
|
||||
if (desc !== undefined && desc.enumerable) {
|
||||
to[nextKey] = nextSource[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
return to;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
<script src="../ace/src-min/ace.js"></script>
|
||||
<script>
|
||||
var version = require('remote').require('app').getVersion()
|
||||
global.version = version
|
||||
document.title = 'Boost ' + ((version == null || version.length === 0) ? 'DEV version' : 'v' + version)
|
||||
// require('../electron-stylus')(__dirname + '/../styles/main/index.styl', 'mainCss')
|
||||
require('../electron-stylus')(__dirname + '/../styles/main/index.styl')
|
||||
require('node-jsx').install({ harmony: true, extension: '.jsx' })
|
||||
require('./index.jsx')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,56 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CodeXen</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
|
||||
<link rel="stylesheet" href="../vendor/fontawesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
|
||||
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
|
||||
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
|
||||
<link rel="stylesheet" href="../styles/main/index.css" media="screen" charset="utf-8">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<script>
|
||||
if (!Object.assign) {
|
||||
Object.defineProperty(Object, 'assign', {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: function(target) {
|
||||
'use strict';
|
||||
if (target === undefined || target === null) {
|
||||
throw new TypeError('Cannot convert first argument to object');
|
||||
}
|
||||
|
||||
var to = Object(target);
|
||||
for (var i = 1; i < arguments.length; i++) {
|
||||
var nextSource = arguments[i];
|
||||
if (nextSource === undefined || nextSource === null) {
|
||||
continue;
|
||||
}
|
||||
nextSource = Object(nextSource);
|
||||
|
||||
var keysArray = Object.keys(Object(nextSource));
|
||||
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
|
||||
var nextKey = keysArray[nextIndex];
|
||||
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
|
||||
if (desc !== undefined && desc.enumerable) {
|
||||
to[nextKey] = nextSource[nextKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
return to;
|
||||
}
|
||||
});
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('../../resources/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
</script>
|
||||
<script src="../vendor/moment/min/moment.min.js"></script>
|
||||
<script src="../vendor/markdown-it/dist/markdown-it.min.js"></script>
|
||||
<script src="../vendor/react/react-with-addons.js"></script>
|
||||
<script src="../vendor/react-router/build/umd/ReactRouter.js"></script>
|
||||
<script src="../vendor/reflux/dist/reflux.js"></script>
|
||||
<script src="../ace/src-min/ace.js"></script>
|
||||
|
||||
#loadingCover{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 65px 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#loadingCover img{
|
||||
display: block;
|
||||
margin: 75px auto 5px;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
#loadingCover .message{
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
font-weight: 100;
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loadingCover">
|
||||
<img src="resources/favicon-230x230.png">
|
||||
<div class='message'>Loading...</div>
|
||||
</div>
|
||||
|
||||
<div id="content"></div>
|
||||
<script src="http://localhost:8090/webpack-dev-server.js"></script>
|
||||
<script type="text/javascript" src="http://localhost:8090/assets/main.js"></script>
|
||||
<script type="text/javascript" src="http://localhost:8090/assets/main-style.js"></script>
|
||||
|
||||
<script src="../../submodules/ace/src-min/ace.js"></script>
|
||||
<script>
|
||||
var version = require('remote').require('app').getVersion()
|
||||
global.version = version
|
||||
document.title = 'Boost' + ((version == null || version.length === 0) ? ' DEV' : '')
|
||||
require("babel-core/register")
|
||||
require('./index')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
29
browser/main/index.js
Normal file
29
browser/main/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
import { Router, Route, IndexRoute } from 'react-router'
|
||||
import MainContainer from './Containers/MainContainer'
|
||||
import LoginContainer from './Containers/LoginContainer'
|
||||
import SignupContainer from './Containers/SignupContainer'
|
||||
import HomeContainer from './HomeContainer'
|
||||
|
||||
function onlyUser (state, replaceState) {
|
||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
if (currentUser == null) replaceState('login', '/login')
|
||||
}
|
||||
|
||||
let routes = (
|
||||
<Route path='/' component={MainContainer}>
|
||||
|
||||
<Route name='login' path='login' component={LoginContainer}/>
|
||||
<Route name='signup' path='signup' component={SignupContainer}/>
|
||||
<IndexRoute name='home' component={HomeContainer} onEnter={onlyUser}>
|
||||
<IndexRoute name='homeDefault'/>
|
||||
<Route name='user' path=':userId'/>
|
||||
</IndexRoute>
|
||||
</Route>
|
||||
)
|
||||
|
||||
let el = document.getElementById('content')
|
||||
React.render(<Router>{routes}</Router>, el, function () {
|
||||
let loadingCover = document.getElementById('loadingCover')
|
||||
loadingCover.parentNode.removeChild(loadingCover)
|
||||
})
|
||||
@@ -1,46 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
|
||||
var ReactRouter = require('react-router')
|
||||
var Route = ReactRouter.Route
|
||||
var DefaultRoute = ReactRouter.DefaultRoute
|
||||
|
||||
var MainContainer = require('./Containers/MainContainer')
|
||||
|
||||
var LoginContainer = require('./Containers/LoginContainer')
|
||||
var SignupContainer = require('./Containers/SignupContainer')
|
||||
|
||||
var HomeContainer = require('./Containers/HomeContainer')
|
||||
var UserContainer = require('./Containers/UserContainer')
|
||||
|
||||
var PlanetContainer = require('./Containers/PlanetContainer')
|
||||
|
||||
var routes = (
|
||||
<Route path='/' handler={MainContainer}>
|
||||
<DefaultRoute name='root'/>
|
||||
|
||||
<Route name='login' path='login' handler={LoginContainer}/>
|
||||
<Route name='signup' path='signup' handler={SignupContainer}/>
|
||||
|
||||
<Route name='home' path='home' handler={HomeContainer}>
|
||||
<DefaultRoute name='homeEmpty'/>
|
||||
<Route name='user' path=':userName' handler={UserContainer}>
|
||||
<DefaultRoute name='userHome'/>
|
||||
<Route name='planet' path=':planetName' handler={PlanetContainer}>
|
||||
<DefaultRoute name='planetHome'/>
|
||||
<Route name='codes' path='codes/:localId'/>
|
||||
<Route name='notes' path='notes/:localId'/>
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
)
|
||||
var loadingCover = document.getElementById('loadingCover')
|
||||
|
||||
ReactRouter.run(routes, ReactRouter.HashLocation, function (Root) {
|
||||
React.render(<Root/>, document.getElementById('content'))
|
||||
|
||||
if (loadingCover != null) {
|
||||
loadingCover.parentNode.removeChild(loadingCover)
|
||||
loadingCover = null
|
||||
}
|
||||
})
|
||||
@@ -1,2 +0,0 @@
|
||||
require('../styles/main/index.styl')
|
||||
require('react-select/dist/default.css')
|
||||
169
browser/styles/main/components/ArticleDetail.styl
Normal file
169
browser/styles/main/components/ArticleDetail.styl
Normal file
@@ -0,0 +1,169 @@
|
||||
noTagsColor = #999
|
||||
|
||||
.ArticleDetail
|
||||
absolute right bottom
|
||||
top 60px
|
||||
left 250px
|
||||
padding 10px
|
||||
*
|
||||
-webkit-user-select all
|
||||
.detailInfo
|
||||
height 70px
|
||||
width 100%
|
||||
transition 0.1s
|
||||
font-size 12px
|
||||
position relative
|
||||
.left
|
||||
absolute top left bottom
|
||||
right 120px
|
||||
.right
|
||||
absolute top right
|
||||
.detailBody
|
||||
absolute left right bottom
|
||||
top 70px
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
.detailPanel
|
||||
absolute top
|
||||
left 10px
|
||||
right 10px
|
||||
bottom 10px
|
||||
background-color white
|
||||
border-radius 5px
|
||||
border solid 1px borderColor
|
||||
&>.header
|
||||
absolute top left right
|
||||
height 60px
|
||||
.MarkdownPreview
|
||||
absolute left right bottom
|
||||
top 60px
|
||||
marked()
|
||||
box-sizing border-box
|
||||
padding 5px 15px
|
||||
border-top solid 1px borderColor
|
||||
overflow-y auto
|
||||
.CodeEditor
|
||||
absolute left right bottom
|
||||
top 60px
|
||||
border-top solid 1px borderColor
|
||||
min-height 300px
|
||||
border-bottom-left-radius 5px
|
||||
border-bottom-right-radius 5px
|
||||
&.edit
|
||||
.detailInfo
|
||||
.left
|
||||
.Select
|
||||
.Select-control
|
||||
border none
|
||||
background-color transparent
|
||||
.folder.Select
|
||||
width 150px
|
||||
.Select-control
|
||||
&:hover
|
||||
background-color darken(white, 5%)
|
||||
&.is-focused
|
||||
.Select-control
|
||||
background-color white
|
||||
.tags.Select
|
||||
.Select-control
|
||||
white-space nowrap
|
||||
overflow-x auto
|
||||
position relative
|
||||
.Select-arrow-zone, .Select-arrow
|
||||
display none
|
||||
.right
|
||||
button
|
||||
cursor pointer
|
||||
height 33px
|
||||
width 55px
|
||||
margin-left 5px
|
||||
font-size 14px
|
||||
color inactiveTextColor
|
||||
background-color darken(white, 5%)
|
||||
border solid 1px borderColor
|
||||
border-radius 5px
|
||||
&:hover
|
||||
background-color white
|
||||
&.primary
|
||||
border none
|
||||
background-color brandColor
|
||||
color white
|
||||
&:hover
|
||||
color white
|
||||
background-color lighten(brandColor, 10%)
|
||||
.detailBody
|
||||
.detailPanel
|
||||
&>.header
|
||||
.mode
|
||||
absolute top bottom right
|
||||
display block
|
||||
height 33px
|
||||
margin-top 12px
|
||||
width 120px
|
||||
margin-right 15px
|
||||
.title
|
||||
absolute left top bottom
|
||||
right 120px
|
||||
padding 0 15px
|
||||
input
|
||||
width 100%
|
||||
border none
|
||||
background-color transparent
|
||||
line-height 60px
|
||||
font-size 32px
|
||||
font-weight bold
|
||||
outline none
|
||||
&.show
|
||||
.detailInfo
|
||||
.left
|
||||
right 99px
|
||||
.info
|
||||
padding 5px
|
||||
overflow ellipsis
|
||||
.tags
|
||||
padding 10px 10px 5px
|
||||
color articleItemColor
|
||||
a
|
||||
background-color brandColor
|
||||
color white
|
||||
border-radius 2px
|
||||
padding 1.5px 5px
|
||||
margin 2px
|
||||
font-size 10px
|
||||
opacity 0.8
|
||||
&:hover
|
||||
opacity 1
|
||||
span.noTags
|
||||
color noTagsColor
|
||||
.right
|
||||
button
|
||||
cursor pointer
|
||||
height 33px
|
||||
width 33px
|
||||
border none
|
||||
font-size 18px
|
||||
color inactiveTextColor
|
||||
background-color transparent
|
||||
padding 0
|
||||
&:hover
|
||||
color inherit
|
||||
.detailBody
|
||||
.detailPanel
|
||||
&>.header
|
||||
.mode
|
||||
display block
|
||||
line-height 60px
|
||||
width 45px
|
||||
height 60px
|
||||
font-size 18px
|
||||
text-align center
|
||||
.title
|
||||
absolute top bottom
|
||||
left 45px
|
||||
right 15px
|
||||
font-size 32px
|
||||
line-height 60px
|
||||
font-weight bold
|
||||
white-space nowrap
|
||||
overflow-x auto
|
||||
overflow-y hidden
|
||||
69
browser/styles/main/components/ArticleList.styl
Normal file
69
browser/styles/main/components/ArticleList.styl
Normal file
@@ -0,0 +1,69 @@
|
||||
articleItemHoverBgColor = darken(white, 5%)
|
||||
articleItemColor = #777
|
||||
|
||||
.ArticleList
|
||||
absolute left bottom
|
||||
top 60px
|
||||
width 250px
|
||||
border-right solid 1px highlightenBorderColor
|
||||
&>ul
|
||||
absolute top bottom left right
|
||||
overflow-y auto
|
||||
noSelect()
|
||||
li
|
||||
.articleItem
|
||||
border solid 2px transparent
|
||||
position relative
|
||||
height 88px
|
||||
width 100%
|
||||
cursor pointer
|
||||
transition 0.1s
|
||||
background-color white
|
||||
padding 0 10px
|
||||
font-size 12px
|
||||
.top
|
||||
clearfix()
|
||||
line-height 20px
|
||||
padding 5px 0
|
||||
color articleItemColor
|
||||
.profileImage
|
||||
vertical-align middle
|
||||
.updatedAt
|
||||
float right
|
||||
line-height 20px
|
||||
.middle
|
||||
clearfix()
|
||||
padding 3px 0 7px
|
||||
font-size 16px
|
||||
.mode
|
||||
float left
|
||||
font-size 12px
|
||||
line-height 16px
|
||||
.title
|
||||
float left
|
||||
overflow ellipsis
|
||||
padding 0 5px
|
||||
.bottom
|
||||
padding 5px 0
|
||||
overflow-x auto
|
||||
white-space nowrap
|
||||
.tags
|
||||
color articleItemColor
|
||||
a
|
||||
background-color brandColor
|
||||
color white
|
||||
border-radius 2px
|
||||
padding 1.5px 5px
|
||||
margin 2px
|
||||
font-size 10px
|
||||
opacity 0.8
|
||||
&:hover
|
||||
opacity 1
|
||||
&:hover, &.hover
|
||||
background-color articleItemHoverBgColor
|
||||
&:active, &.active
|
||||
background-color white
|
||||
&:active, &.active
|
||||
border-color brandBorderColor
|
||||
.divider
|
||||
border-bottom solid 1px borderColor
|
||||
@@ -23,7 +23,7 @@
|
||||
transition: all 200ms ease;
|
||||
}
|
||||
.Select-control:hover {
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
|
||||
// box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.is-searchable.is-open > .Select-control {
|
||||
cursor: text;
|
||||
@@ -42,8 +42,8 @@
|
||||
cursor: text;
|
||||
}
|
||||
.is-focused:not(.is-open) > .Select-control {
|
||||
border-color: #0088cc #0099e6 #0099e6;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 5px -1px rgba(0, 136, 204, 0.5);
|
||||
// border-color: #0088cc #0099e6 #0099e6;
|
||||
// box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 5px -1px rgba(0, 136, 204, 0.5);
|
||||
}
|
||||
.Select-placeholder {
|
||||
color: #aaaaaa;
|
||||
@@ -196,10 +196,10 @@
|
||||
padding: 3px 0;
|
||||
}
|
||||
.Select-item {
|
||||
background-color: #f2f9fc;
|
||||
background-color: brandColor;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #c9e6f2;
|
||||
color: #0088cc;
|
||||
// border: 1px solid #c9e6f2;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
font-size: 1em;
|
||||
margin: 2px;
|
||||
@@ -216,20 +216,19 @@
|
||||
padding: 3px 5px;
|
||||
}
|
||||
.Select-item-label .Select-item-label__a {
|
||||
color: #0088cc;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
cursor: white;
|
||||
}
|
||||
.Select-item-icon {
|
||||
cursor: pointer;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-top-left-radius: 2px;
|
||||
border-right: 1px solid #c9e6f2;
|
||||
border-right: 1px solid darken(brandColor, 10%)
|
||||
padding: 2px 5px 4px;
|
||||
}
|
||||
.Select-item-icon:hover,
|
||||
.Select-item-icon:focus {
|
||||
background-color: #ddeff7;
|
||||
color: #0077b3;
|
||||
background-color: lighten(brandColor, 10%)
|
||||
}
|
||||
.Select-item-icon:active {
|
||||
background-color: #c9e6f2;
|
||||
|
||||
43
browser/styles/main/components/TopBar.styl
Normal file
43
browser/styles/main/components/TopBar.styl
Normal file
@@ -0,0 +1,43 @@
|
||||
.TopBar
|
||||
absolute top left right
|
||||
height 60px
|
||||
border-bottom solid 1px borderColor
|
||||
noSelect()
|
||||
.left
|
||||
float left
|
||||
.search
|
||||
position absolute
|
||||
top 13.5px
|
||||
left 15px
|
||||
height 33px
|
||||
i.fa
|
||||
position absolute
|
||||
line-height 33px
|
||||
z-index 1
|
||||
width 33px
|
||||
text-align center
|
||||
input.searchInput
|
||||
absolute top left
|
||||
background-color white
|
||||
borderInput()
|
||||
width 350px
|
||||
padding-left 30px
|
||||
border-radius 16.5px
|
||||
font-size 14px
|
||||
height 33px
|
||||
line-height 33px
|
||||
outline none
|
||||
&:focus
|
||||
border-color brandColor
|
||||
.right
|
||||
float right
|
||||
.logo
|
||||
&>img
|
||||
margin-top 7px
|
||||
margin-right 15px
|
||||
.tooltip
|
||||
tooltip()
|
||||
right 5px
|
||||
&:hover
|
||||
.tooltip
|
||||
opacity 1.0
|
||||
153
browser/styles/main/containers/HomeContainer.styl
Normal file
153
browser/styles/main/containers/HomeContainer.styl
Normal file
@@ -0,0 +1,153 @@
|
||||
homeNavigatorBgColor = #1B1C1C
|
||||
homeNavigatorColor = #DDD
|
||||
userAnchorColor = #979797
|
||||
userAnchorBgColor = #BEBEBE
|
||||
userAnchorActiveColor = textColor
|
||||
userAnchorActiveBgColor = white
|
||||
|
||||
.HomeContainer
|
||||
.HomeNavigator
|
||||
noSelect()
|
||||
background-color homeNavigatorBgColor
|
||||
absolute left top bottom
|
||||
width 60px
|
||||
text-align center
|
||||
box-sizing border-box
|
||||
// must be moved
|
||||
// .profilePopup
|
||||
// position fixed
|
||||
// left 35px
|
||||
// top 35px
|
||||
// z-index popupZIndex
|
||||
// width 200px
|
||||
// background-color backgroundColor
|
||||
// box-shadow popupShadow
|
||||
// border-radius 10px
|
||||
// padding 10px 0 0px
|
||||
// &.close
|
||||
// display none
|
||||
// .profileGroup
|
||||
// margin-bottom 10px
|
||||
// .profileGroupLabel
|
||||
// text-align left
|
||||
// height 1em
|
||||
// padding 0 15px
|
||||
// span
|
||||
// position absolute
|
||||
// z-index 2
|
||||
// background-color backgroundColor
|
||||
// padding-right 5px
|
||||
// color inactiveTextColor
|
||||
// font-size 0.8em
|
||||
// &::before
|
||||
// content ''
|
||||
// position absolute
|
||||
// display block
|
||||
// z-index 1
|
||||
// height 0.5em
|
||||
// width 175px
|
||||
// border-bottom solid 1px borderColor
|
||||
// .profileGroupList
|
||||
// li
|
||||
// clearfix()
|
||||
// &:hover
|
||||
// background-color hoverBackgroundColor
|
||||
// .userName
|
||||
// width 155px
|
||||
// padding 10px 15px
|
||||
// text-align left
|
||||
// display block
|
||||
// text-decoration none
|
||||
// cursor pointer
|
||||
// .createNewTeam
|
||||
// btnStripDefault()
|
||||
// width 100%
|
||||
// padding 10px 20px
|
||||
// font-size 1em
|
||||
// cursor pointer
|
||||
// text-align left
|
||||
// .controlGroup
|
||||
// list-style none
|
||||
// border-top solid 1px borderColor
|
||||
// padding 10px 0
|
||||
// li
|
||||
// &:hover
|
||||
// background-color hoverBackgroundColor
|
||||
// button
|
||||
// btnStripDefault()
|
||||
// width 100%
|
||||
// padding 10px 20px
|
||||
// font-size 1em
|
||||
// cursor pointer
|
||||
// text-align left
|
||||
ul.userList
|
||||
margin-top 25px
|
||||
&>li
|
||||
.shortCut
|
||||
margin-top 5px
|
||||
font-size 0.8em
|
||||
color homeNavigatorColor
|
||||
a
|
||||
display block
|
||||
width 44px
|
||||
height 44px
|
||||
margin 0 auto
|
||||
text-align center
|
||||
background-color userAnchorBgColor
|
||||
text-decoration none
|
||||
color userAnchorColor
|
||||
line-height 44px
|
||||
font-size 1.1em
|
||||
cursor pointer
|
||||
circle()
|
||||
img
|
||||
width 44px
|
||||
height 44px
|
||||
transition 0.1s
|
||||
&:hover, &.active
|
||||
background-color userAnchorActiveBgColor
|
||||
color userAnchorActiveColor
|
||||
.userTooltip
|
||||
position absolute
|
||||
z-index popupZIndex
|
||||
background-color transparentify(invBackgroundColor, 80%)
|
||||
color invTextColor
|
||||
padding 10px
|
||||
line-height 1em
|
||||
border-radius 5px
|
||||
margin-top -52px
|
||||
margin-left 52px
|
||||
white-space nowrap
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
pointer-events none
|
||||
&:hover .userTooltip
|
||||
opacity 1
|
||||
button.newTeamButton
|
||||
display block
|
||||
margin 0 auto
|
||||
width 30px
|
||||
height 30px
|
||||
circle()
|
||||
border solid 1px lightButtonColor
|
||||
color lightButtonColor
|
||||
text-align center
|
||||
background-image none
|
||||
background-color transparent
|
||||
box-sizing border-box
|
||||
absolute left bottom right
|
||||
bottom 15px
|
||||
&:hover, &.hover, &:focus, &.focus
|
||||
border-color darken(lightButtonColor, 50%)
|
||||
color darken(lightButtonColor, 50%)
|
||||
&:active, &.active
|
||||
border-color darken(brandBorderColor, 10%)
|
||||
background-color brandColor
|
||||
color white
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-top -22px
|
||||
margin-left 33px
|
||||
font-size 14px
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
@@ -1,6 +1,6 @@
|
||||
.LoginContainer, .SignupContainer
|
||||
margin 0 auto
|
||||
padding 25px 15px
|
||||
padding 105px 15px
|
||||
box-sizing border-box
|
||||
color inactiveTextColor
|
||||
.logo
|
||||
@@ -58,17 +58,24 @@
|
||||
.alertInfo, .alertError
|
||||
margin-top 15px
|
||||
margin-bottom 15px
|
||||
height 44px
|
||||
padding 5px
|
||||
border-radius 10px
|
||||
line-height 44px
|
||||
padding 10px
|
||||
border-radius 5px
|
||||
line-height 1.6
|
||||
text-align center
|
||||
.alertInfo
|
||||
alertInfo()
|
||||
.alertError
|
||||
alertError()
|
||||
div.form-group:last-child
|
||||
margin-top 15px
|
||||
div.formField
|
||||
input
|
||||
stripInput()
|
||||
height 33px
|
||||
width 100%
|
||||
margin-bottom 10px
|
||||
text-align center
|
||||
font-size 1.1em
|
||||
&:last-child
|
||||
margin-top 15px
|
||||
button.logInButton
|
||||
btnPrimary()
|
||||
height 44px
|
||||
|
||||
@@ -1,310 +1,123 @@
|
||||
.HomeContainer
|
||||
.HomeNavigator
|
||||
noSelect()
|
||||
background-color planetNavBgColor
|
||||
absolute left top bottom
|
||||
width 55px
|
||||
text-align center
|
||||
box-sizing border-box
|
||||
border-right solid 1px borderColor
|
||||
.profileButton
|
||||
display block
|
||||
width 55px
|
||||
height 55px
|
||||
border-bottom solid 1px borderColor
|
||||
overflow hidden
|
||||
background-color black
|
||||
margin 0
|
||||
padding 0
|
||||
cursor pointer
|
||||
box-sizing border-box
|
||||
border none
|
||||
img
|
||||
transition 0.1s
|
||||
opacity 0.9
|
||||
&.vivid.active, &.focus, &:focus, &.hover, &:hover
|
||||
img
|
||||
opacity 1
|
||||
.profilePopup
|
||||
position fixed
|
||||
left 35px
|
||||
top 35px
|
||||
z-index popupZIndex
|
||||
width 200px
|
||||
background-color backgroundColor
|
||||
box-shadow popupShadow
|
||||
border-radius 10px
|
||||
padding 10px 0 0px
|
||||
&.close
|
||||
display none
|
||||
.profileGroup
|
||||
margin-bottom 10px
|
||||
.profileGroupLabel
|
||||
text-align left
|
||||
height 1em
|
||||
padding 0 15px
|
||||
span
|
||||
position absolute
|
||||
z-index 2
|
||||
background-color backgroundColor
|
||||
padding-right 5px
|
||||
color inactiveTextColor
|
||||
font-size 0.8em
|
||||
&::before
|
||||
content ''
|
||||
position absolute
|
||||
display block
|
||||
z-index 1
|
||||
height 0.5em
|
||||
width 175px
|
||||
border-bottom solid 1px borderColor
|
||||
.profileGroupList
|
||||
li
|
||||
clearfix()
|
||||
&:hover
|
||||
background-color hoverBackgroundColor
|
||||
.userName
|
||||
width 155px
|
||||
padding 10px 15px
|
||||
text-align left
|
||||
display block
|
||||
text-decoration none
|
||||
cursor pointer
|
||||
.createNewTeam
|
||||
btnStripDefault()
|
||||
width 100%
|
||||
padding 10px 20px
|
||||
font-size 1em
|
||||
cursor pointer
|
||||
text-align left
|
||||
.controlGroup
|
||||
list-style none
|
||||
border-top solid 1px borderColor
|
||||
padding 10px 0
|
||||
li
|
||||
&:hover
|
||||
background-color hoverBackgroundColor
|
||||
button
|
||||
btnStripDefault()
|
||||
width 100%
|
||||
padding 10px 20px
|
||||
font-size 1em
|
||||
cursor pointer
|
||||
text-align left
|
||||
userNavigatorWidth = 200px
|
||||
userNavigatorBgColor = #333
|
||||
userNavigatorColor = #DDD
|
||||
userNavigatorProfileNameColor = brandColor
|
||||
userNavigatorBorderColor = #666
|
||||
|
||||
ul.planetList>li
|
||||
margin 15px 0
|
||||
.shortCut
|
||||
margin-top 5px
|
||||
color lighten(textColor, 5%)
|
||||
font-size 0.8em
|
||||
&.active
|
||||
a
|
||||
background-color planetAnchorActiveBgColor
|
||||
color planetAnchorActiveColor
|
||||
a
|
||||
display block
|
||||
width 44px
|
||||
height 44px
|
||||
margin 0 auto
|
||||
text-align center
|
||||
background-color planetAnchorBgColor
|
||||
text-decoration none
|
||||
color planetAnchorColor
|
||||
line-height 44px
|
||||
font-size 1.1em
|
||||
cursor pointer
|
||||
circle()
|
||||
transition 0.1s
|
||||
&:hover, &:active
|
||||
background-color white
|
||||
.planetTooltip
|
||||
position absolute
|
||||
z-index popupZIndex
|
||||
background-color transparentify(invBackgroundColor, 80%)
|
||||
color invTextColor
|
||||
padding 10px
|
||||
line-height 1em
|
||||
border-radius 5px
|
||||
margin-top -41px
|
||||
margin-left 52px
|
||||
white-space nowrap
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
pointer-events none
|
||||
&:hover .planetTooltip
|
||||
opacity 1
|
||||
img
|
||||
circle()
|
||||
width 55px
|
||||
height 55px
|
||||
button.newPlanet
|
||||
display block
|
||||
margin 0 auto
|
||||
width 30px
|
||||
height 30px
|
||||
circle()
|
||||
border solid 1px lightButtonColor
|
||||
color lightButtonColor
|
||||
text-align center
|
||||
font-size 1
|
||||
background-image none
|
||||
background-color transparent
|
||||
userContentBgColor = #E6E6E6
|
||||
|
||||
.UserContainer
|
||||
absolute top bottom right
|
||||
left 60px
|
||||
.content
|
||||
absolute top bottom right
|
||||
left userNavigatorWidth
|
||||
background-color userContentBgColor
|
||||
.UserNavigator
|
||||
absolute left top bottom
|
||||
width userNavigatorWidth
|
||||
background-color userNavigatorBgColor
|
||||
color userNavigatorColor
|
||||
noSelect()
|
||||
&>.profile
|
||||
height 60px
|
||||
padding 10px 15px 0
|
||||
box-sizing border-box
|
||||
absolute left bottom right
|
||||
bottom 15px
|
||||
&:hover, &.hover, &:focus, &.focus
|
||||
border-color darken(lightButtonColor, 50%)
|
||||
color darken(lightButtonColor, 50%)
|
||||
&:active, &.active
|
||||
border-color darken(brandBorderColor, 10%)
|
||||
position relative
|
||||
border-bottom solid 1px userNavigatorBorderColor
|
||||
cursor pointer
|
||||
&>.profileName
|
||||
color userNavigatorProfileNameColor
|
||||
font-size 22px
|
||||
cursor pointer
|
||||
transition 0.1s
|
||||
&>.name
|
||||
padding 5px 10px
|
||||
font-size 14px
|
||||
color userNavigatorColor
|
||||
cursor pointer
|
||||
transition 0.1s
|
||||
&>.dropdownIcon
|
||||
position absolute
|
||||
top 20px
|
||||
right 25px
|
||||
float right
|
||||
width 20px
|
||||
height 20px
|
||||
line-height 20px
|
||||
font-size 8px
|
||||
border solid 1px userNavigatorColor
|
||||
border-radius 12.5px
|
||||
text-align center
|
||||
transition 0.1s
|
||||
&:hover
|
||||
&>.profileName
|
||||
color lighten(brandColor, 10%)
|
||||
&>.name
|
||||
color white
|
||||
&>.dropdownIcon
|
||||
border-color white
|
||||
&:active
|
||||
&>.dropdownIcon
|
||||
background-color brandColor
|
||||
border-color brandColor
|
||||
&>.control
|
||||
padding 15px 15px
|
||||
&>.newPostButton
|
||||
background-color brandColor
|
||||
color white
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-top -22px
|
||||
margin-left 33px
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
.UserContainer
|
||||
absolute top bottom right
|
||||
left 55px
|
||||
.memberPopup
|
||||
absolute left
|
||||
top 235px
|
||||
z-index 1
|
||||
padding 0 15px 10px
|
||||
width 200px
|
||||
.label
|
||||
padding 10px 0
|
||||
font-size 0.9em
|
||||
border-bottom solid 1px borderColor
|
||||
margin-bottom 15px
|
||||
.members
|
||||
li
|
||||
padding 0 10px
|
||||
margin-bottom 15px
|
||||
clearfix()
|
||||
.memberImage
|
||||
float left
|
||||
margin-right 7px
|
||||
circle()
|
||||
.memberInfo
|
||||
float left
|
||||
.memberProfileName
|
||||
margin-bottom 5px
|
||||
font-size 1.05em
|
||||
.memberName
|
||||
margin-left 5px
|
||||
font-size 0.9em
|
||||
color inactiveTextColor
|
||||
a:hover .memberProfileName, a:hover .memberName
|
||||
text-decoration underline
|
||||
.userProfile
|
||||
absolute top left right
|
||||
padding 15px
|
||||
border-bottom solid 1px borderColor
|
||||
height 125px
|
||||
clearfix()
|
||||
.userPhoto
|
||||
circle()
|
||||
float left
|
||||
margin 5px 15px 15px
|
||||
.userInfo
|
||||
float left
|
||||
margin-top 15px
|
||||
.userProfileName
|
||||
font-size 1.5em
|
||||
color brandColor
|
||||
margin-bottom 10px
|
||||
.userName
|
||||
font-size 1.1em
|
||||
.editProfileButton
|
||||
float right
|
||||
btnDefault()
|
||||
margin-top 25px
|
||||
padding 10px 15px
|
||||
height 44px
|
||||
width 100%
|
||||
border none
|
||||
border-radius 5px
|
||||
.teamList, .memberList
|
||||
absolute left bottom
|
||||
top 125px
|
||||
width 200px
|
||||
padding 15px
|
||||
border-right solid 1px borderColor
|
||||
overflow-y auto
|
||||
.teamLabel, .memberLabel
|
||||
font-size 1.2em
|
||||
margin-bottom 15px
|
||||
.teams
|
||||
li
|
||||
padding 0 10px
|
||||
margin-bottom 15px
|
||||
clearfix()
|
||||
.teamInfo
|
||||
float left
|
||||
.teamProfileName
|
||||
margin-bottom 5px
|
||||
font-size 1.05em
|
||||
.teamName
|
||||
margin-left 5px
|
||||
font-size 0.9em
|
||||
color inactiveTextColor
|
||||
a:hover .teamProfileName, a:hover .teamName
|
||||
text-decoration underline
|
||||
font-size 16px
|
||||
font-weight 600
|
||||
transition 0.1s
|
||||
&:hover
|
||||
background-color lighten(brandColor, 10%)
|
||||
&>.menu
|
||||
absolute left right bottom
|
||||
top 134px
|
||||
padding 15px 0
|
||||
overflow auto
|
||||
&>.menuGruop
|
||||
&>.label
|
||||
border-bottom 1px solid userNavigatorBorderColor
|
||||
padding 10px 15px
|
||||
font-size 18px
|
||||
margin-bottom 10px
|
||||
font-size 1.1em
|
||||
.createTeamButton, .addMemberButton
|
||||
btnStripDefault()
|
||||
.members
|
||||
li
|
||||
padding 0 10px
|
||||
margin-bottom 15px
|
||||
clearfix()
|
||||
.memberImage
|
||||
float left
|
||||
margin-right 7px
|
||||
circle()
|
||||
.memberInfo
|
||||
float left
|
||||
.memberProfileName
|
||||
margin-bottom 5px
|
||||
font-size 1.05em
|
||||
.memberRole
|
||||
font-size 0.9em
|
||||
color inactiveTextColor
|
||||
.memberName
|
||||
margin-left 5px
|
||||
font-size 0.9em
|
||||
color inactiveTextColor
|
||||
.createTeamButton, .addMemberButton
|
||||
btnStripDefault()
|
||||
a:hover .memberProfileName, a:hover .memberName
|
||||
text-decoration underline
|
||||
.planetList
|
||||
absolute right bottom
|
||||
top 125px
|
||||
left 200px
|
||||
padding 15px
|
||||
overflow-y auto
|
||||
.planetLabel
|
||||
font-size 1.2em
|
||||
margin-bottom 15px
|
||||
.planetGroup
|
||||
margin-left 15px
|
||||
.planetGroupLabel
|
||||
font-size 1.1em
|
||||
margin-bottom 15px
|
||||
small
|
||||
font-size 0.8em
|
||||
color inactiveTextColor
|
||||
.planets
|
||||
margin-left 15px
|
||||
li
|
||||
a
|
||||
font-size 1.1em
|
||||
text-decoration none
|
||||
&:hover
|
||||
text-decoration underline
|
||||
margin-bottom 10px
|
||||
.createPlanetButton
|
||||
btnStripDefault()
|
||||
&>.plusButton
|
||||
float right
|
||||
width 20px
|
||||
height 20px
|
||||
margin-top -2.5px
|
||||
margin-right -5px
|
||||
line-height 15px
|
||||
font-size 8px
|
||||
border solid 1px userNavigatorColor
|
||||
border-radius 10px
|
||||
background-color transparent
|
||||
text-align center
|
||||
color userNavigatorColor
|
||||
&:hover
|
||||
border-color white
|
||||
color white
|
||||
&:active
|
||||
background-color brandColor
|
||||
border-color brandColor
|
||||
&>.folders
|
||||
.folderButton
|
||||
padding 10px 25px
|
||||
width 100%
|
||||
background-color transparent
|
||||
border none
|
||||
font-size 14px
|
||||
color userNavigatorColor
|
||||
transition 0.1s
|
||||
text-align left
|
||||
&:hover
|
||||
background-color transparentify(white, 20%)
|
||||
color white
|
||||
&.active
|
||||
background-color brandColor
|
||||
color white
|
||||
|
||||
3733
browser/styles/main/index.css
Normal file
3733
browser/styles/main/index.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ global-reset()
|
||||
|
||||
*
|
||||
-webkit-app-region no-drag
|
||||
-webkit-user-select none
|
||||
|
||||
html, body
|
||||
width 100%
|
||||
@@ -19,24 +20,12 @@ body
|
||||
color textColor
|
||||
font-size fontSize
|
||||
font-weight 400
|
||||
button
|
||||
button, input, select
|
||||
font-family "Lato"
|
||||
|
||||
div, span, a, button, input, textarea
|
||||
box-sizing border-box
|
||||
|
||||
h1
|
||||
font-size 2em
|
||||
h2
|
||||
font-size 1.5em
|
||||
h3
|
||||
font-size 1.17em
|
||||
h4
|
||||
font-size 1em
|
||||
h5
|
||||
font-size 0.83em
|
||||
h6
|
||||
font-size 0.67em
|
||||
a
|
||||
color brandColor
|
||||
&:hover
|
||||
@@ -55,6 +44,9 @@ button
|
||||
&:focus, &.focus
|
||||
outline none
|
||||
|
||||
.noSelect
|
||||
noSelect()
|
||||
|
||||
.text-center
|
||||
text-align center
|
||||
|
||||
@@ -64,13 +56,6 @@ button
|
||||
display block
|
||||
margin-bottom 5px
|
||||
|
||||
.stripInput
|
||||
stripInput()
|
||||
display block
|
||||
width 100%
|
||||
font-size 1em
|
||||
height 33px
|
||||
|
||||
.block-input, .inline-input
|
||||
border solid 1px borderColor
|
||||
padding 0 10px
|
||||
@@ -110,19 +95,29 @@ textarea.block-input
|
||||
z-index 2000
|
||||
bottom 5px
|
||||
right 53px
|
||||
btnPrimary()
|
||||
padding 10px 15px
|
||||
border none
|
||||
border-radius 5px
|
||||
background-color backgroundColor
|
||||
background-color brandColor
|
||||
color white
|
||||
opacity 0.7
|
||||
&:hover
|
||||
opacity 1
|
||||
background-color lighten(brandColor, 10%)
|
||||
.contactButton
|
||||
position fixed
|
||||
z-index 2000
|
||||
bottom 5px
|
||||
right 5px
|
||||
btnPrimary()
|
||||
padding 10px 15px
|
||||
border none
|
||||
border-radius 5px
|
||||
background-color backgroundColor
|
||||
background-color brandColor
|
||||
color white
|
||||
opacity 0.7
|
||||
&:hover
|
||||
opacity 1
|
||||
background-color lighten(brandColor, 10%)
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-top -22px
|
||||
|
||||
@@ -6,28 +6,28 @@ marked()
|
||||
h1
|
||||
font-size 2em
|
||||
border-bottom solid 2px borderColor
|
||||
margin 0.67em auto
|
||||
margin 0.33em auto 0.67em
|
||||
h2
|
||||
font-size 1.5em
|
||||
margin 0.83em auto
|
||||
margin 0.42em auto 0.83em
|
||||
h3
|
||||
font-size 1.17em
|
||||
margin 1em auto
|
||||
margin 0.5em auto 1em
|
||||
h4
|
||||
font-size 1em
|
||||
margin 1.33em auto
|
||||
margin 0.67em auto 1.33em
|
||||
h5
|
||||
font-size 0.83em
|
||||
margin 1.67em auto
|
||||
margin 0.84em auto 1.67em
|
||||
h6
|
||||
font-size 0.67em
|
||||
margin 2.33em auto
|
||||
margin 1.16em auto 2.33em
|
||||
h1, h2, h3, h4, h5, h6
|
||||
font-weight 700
|
||||
line-height 1.8em
|
||||
p
|
||||
line-height 1.8em
|
||||
margin-bottom 25px
|
||||
margin 15px 0 25px
|
||||
img
|
||||
max-width 100%
|
||||
strong
|
||||
@@ -38,12 +38,12 @@ marked()
|
||||
text-decoration line-through
|
||||
blockquote
|
||||
border-left solid 4px brandBorderColor
|
||||
margin 15px 0 15px
|
||||
margin 15px 0 25px
|
||||
padding 0 25px
|
||||
ul
|
||||
list-style-type disc
|
||||
padding-left 35px
|
||||
margin-bottom 25px
|
||||
margin-bottom 35px
|
||||
li
|
||||
display list-item
|
||||
margin 15px 0
|
||||
@@ -54,7 +54,7 @@ marked()
|
||||
ol
|
||||
list-style-type decimal
|
||||
padding-left 35px
|
||||
margin-bottom 25px
|
||||
margin-bottom 35px
|
||||
li
|
||||
display list-item
|
||||
margin 15px 0
|
||||
@@ -72,7 +72,7 @@ marked()
|
||||
border solid 1px borderColor
|
||||
border-radius 5px
|
||||
overflow-x auto
|
||||
margin-bottom 25px
|
||||
margin 15px 0 25px
|
||||
background-color #F6F6F6
|
||||
&>code
|
||||
padding 0
|
||||
|
||||
@@ -3,5 +3,4 @@ borderBox()
|
||||
|
||||
noSelect()
|
||||
-webkit-user-select none
|
||||
-webkit-app-region drag
|
||||
|
||||
cursor default
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
borderColor = #E8E8E8
|
||||
borderColor = #D0D0D0
|
||||
highlightenBorderColor = darken(borderColor, 20%)
|
||||
invBorderColor = #404849
|
||||
brandBorderColor = #3FB399
|
||||
@@ -24,12 +24,6 @@ btnHighlightenColor = #000
|
||||
|
||||
brandColor = #2BAC8F
|
||||
|
||||
planetNavBgColor = #ECECEC
|
||||
planetAnchorColor = #979797
|
||||
planetAnchorBgColor = #BEBEBE
|
||||
planetAnchorActiveColor = textColor
|
||||
planetAnchorActiveBgColor = white
|
||||
|
||||
popupShadow = 0 0 5px 0 #888
|
||||
modalBackColor = transparentify(white, 65%)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
// apiUrl: 'https://api.b00st.io/'
|
||||
apiUrl: 'https://api2.b00st.io/'
|
||||
// apiUrl: 'http://localhost:8000/'
|
||||
// apiUrl: 'https://api2.b00st.io/'
|
||||
apiUrl: 'http://localhost:8000/'
|
||||
}
|
||||
|
||||
@@ -6,16 +6,16 @@ var finderWindow = new BrowserWindow({
|
||||
show: false,
|
||||
frame: false,
|
||||
resizable: false,
|
||||
'zoom-factor': 1.0,
|
||||
'always-on-top': true,
|
||||
'web-preferences': {
|
||||
'zoom-factor': 1.0,
|
||||
'overlay-scrollbars': true,
|
||||
'skip-taskbar': true
|
||||
},
|
||||
'standard-window': false
|
||||
})
|
||||
|
||||
finderWindow.loadUrl('file://' + __dirname + '/browser/finder/index.electron.html')
|
||||
finderWindow.loadUrl('file://' + __dirname + '/browser/finder/index.html')
|
||||
|
||||
finderWindow.on('blur', function () {
|
||||
finderWindow.hide()
|
||||
|
||||
7
lib/key-gen.js
Normal file
7
lib/key-gen.js
Normal file
@@ -0,0 +1,7 @@
|
||||
var crypto = require('crypto')
|
||||
|
||||
module.exports = function () {
|
||||
var shasum = crypto.createHash('sha1')
|
||||
shasum.update(((new Date()).getTime()).toString())
|
||||
return shasum.digest('hex')
|
||||
}
|
||||
@@ -3,15 +3,14 @@ var BrowserWindow = require('browser-window')
|
||||
var mainWindow = new BrowserWindow({
|
||||
width: 1080,
|
||||
height: 720,
|
||||
// frame: false,
|
||||
'zoom-factor': 1.0,
|
||||
'web-preferences': {
|
||||
'zoom-factor': 1.0,
|
||||
'overlay-scrollbars': true
|
||||
},
|
||||
'standard-window': false
|
||||
})
|
||||
|
||||
mainWindow.loadUrl('file://' + __dirname + '/browser/main/index.electron.html')
|
||||
mainWindow.loadUrl('file://' + __dirname + '/browser/main/index.html')
|
||||
|
||||
mainWindow.setVisibleOnAllWorkspaces(true)
|
||||
|
||||
|
||||
39
package.json
39
package.json
@@ -1,13 +1,16 @@
|
||||
{
|
||||
"name": "boost",
|
||||
"version": "0.3.1",
|
||||
"version": "0.4.0",
|
||||
"description": "Boost App",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron ./main.js",
|
||||
"web": "npm run serve | npm run dev",
|
||||
"serve": "./node_modules/.bin/http-server ./browser -p 8080",
|
||||
"dev": "webpack-dev-server --progress --colors --port 8090"
|
||||
"build": "electron-packager ./ Boost $npm_package_config_platform $npm_package_config_version $npm_package_config_ignore --overwrite"
|
||||
},
|
||||
"config": {
|
||||
"version": "--version=0.33.0 --app-version=$npm_package_version --app-bundle-id=com.maisin.boost",
|
||||
"platform": "--platform=darwin --arch=x64 --prune --icon=app.icns",
|
||||
"ignore": "--ignore=Boost-darwin-x64 --ignore=node_modules/devicon/icons --ignore=submodules\/ace\/(?!src-min)|submodules\/ace\/(?=src-min-noconflict)"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -31,38 +34,38 @@
|
||||
},
|
||||
"homepage": "https://github.com/Rokt33r/codexen-app#readme",
|
||||
"dependencies": {
|
||||
"babel-core": "^5.8.25",
|
||||
"devicon": "^2.0.0",
|
||||
"font-awesome": "^4.3.0",
|
||||
"fs-jetpack": "^0.7.0",
|
||||
"lodash": "^3.10.1",
|
||||
"markdown-it": "^4.3.1",
|
||||
"md5": "^2.0.0",
|
||||
"moment": "^2.10.3",
|
||||
"nib": "^1.1.0",
|
||||
"node-jsx": "^0.13.3",
|
||||
"node-notifier": "^4.2.3",
|
||||
"react": "^0.13.3",
|
||||
"react-router": "^0.13.3",
|
||||
"react-select": "^0.5.4",
|
||||
"react-redux": "^3.1.0",
|
||||
"react-router": "^1.0.0-rc1",
|
||||
"react-select": "^0.6.10",
|
||||
"redux": "^3.0.2",
|
||||
"reflux": "^0.2.8",
|
||||
"stylus": "^0.52.0",
|
||||
"socket.io-client": "^1.3.6",
|
||||
"superagent": "^1.2.0",
|
||||
"superagent-promise": "^1.0.3",
|
||||
"titlebar": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"css-loader": "^0.15.1",
|
||||
"http-server": "^0.8.0",
|
||||
"jsx-loader": "^0.13.2",
|
||||
"node-libs-browser": "^0.5.2",
|
||||
"style-loader": "^0.12.3",
|
||||
"stylus-loader": "^1.2.1",
|
||||
"webpack": "^1.10.0",
|
||||
"webpack-dev-server": "^1.10.1"
|
||||
"electron-packager": "^5.1.0",
|
||||
"electron-prebuilt": "^0.33.6",
|
||||
"nib": "^1.1.0",
|
||||
"standard": "^5.3.1",
|
||||
"stylus": "^0.52.4"
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"/browser/ace/"
|
||||
],
|
||||
"global": [
|
||||
"globals": [
|
||||
"localStorage"
|
||||
]
|
||||
}
|
||||
|
||||
1
submodules/ace
Submodule
1
submodules/ace
Submodule
Submodule submodules/ace added at b082bcb4bf
Reference in New Issue
Block a user