mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
345d7b427a | ||
|
|
de6d6b692e | ||
|
|
b2845e2284 | ||
|
|
47383c347c | ||
|
|
4bda84d69c | ||
|
|
b510aa11f5 | ||
|
|
8dab6d5e04 | ||
|
|
85f833c865 | ||
|
|
15133d00c7 | ||
|
|
b93990d10b | ||
|
|
a0bcb8edbe | ||
|
|
bfdf691bed | ||
|
|
f60856b998 | ||
|
|
3308eeaf82 | ||
|
|
19930a2472 | ||
|
|
e75d95b1fc | ||
|
|
4319711dc6 | ||
|
|
9712be909d | ||
|
|
04060ce252 | ||
|
|
da066fe694 | ||
|
|
3b7215b36a | ||
|
|
b88d5cfb06 | ||
|
|
8ab96cf2fb | ||
|
|
63af2fd8b1 | ||
|
|
fcf26f7844 | ||
|
|
657ebc99df | ||
|
|
9500f9a8c9 | ||
|
|
a05bba15e7 | ||
|
|
d88ad0f6be | ||
|
|
4e34f16e33 | ||
|
|
503a806446 | ||
|
|
caf7606893 | ||
|
|
2fb51fe37c | ||
|
|
fa1c48e480 | ||
|
|
3d9a631786 | ||
|
|
0b4cfd6563 | ||
|
|
484dfe6726 | ||
|
|
d8cb93fb10 | ||
|
|
23b8b49c00 | ||
|
|
932997259f | ||
|
|
1bebb66165 | ||
|
|
ac0d81f9b3 | ||
|
|
45b99d13cd | ||
|
|
920704075e | ||
|
|
9e929f80ad | ||
|
|
9696a6cba1 | ||
|
|
211fd8b28a | ||
|
|
c6ef86cbbe | ||
|
|
90c2ff7480 | ||
|
|
49057810fb | ||
|
|
e5e6e2e1b8 | ||
|
|
c8851ecd2a | ||
|
|
bd2d77fef7 | ||
|
|
43403f8bb1 | ||
|
|
0ac7839f11 | ||
|
|
714b5f7b4b | ||
|
|
51fb43d624 | ||
|
|
a79cbb2d5c | ||
|
|
e88c197f86 | ||
|
|
b1be92e6c9 | ||
|
|
d20f005c5d | ||
|
|
5dbfb24f1c | ||
|
|
4df489bd10 | ||
|
|
8c3510413a | ||
|
|
bea9dfdfc7 | ||
|
|
465b315ae0 | ||
|
|
054daac6db | ||
|
|
867ec25e54 | ||
|
|
2e4aaf7345 | ||
|
|
2f754bbb87 | ||
|
|
cdf6ed47dd | ||
|
|
c31432fe3f | ||
|
|
f0b2e91091 | ||
|
|
72a08e8fec | ||
|
|
864001bdff | ||
|
|
bd2816b2ac | ||
|
|
03918527f6 | ||
|
|
6140e93cc8 | ||
|
|
0f8eaaf750 | ||
|
|
7afad6ac49 | ||
|
|
e9308bdd69 | ||
|
|
16b60ada50 | ||
|
|
aa71251edd | ||
|
|
89cfd35d72 | ||
|
|
9ea16a39df | ||
|
|
4fee2586e4 | ||
|
|
b30511eb51 | ||
|
|
05325e7276 | ||
|
|
0dde2eb20f | ||
|
|
c2fcc72e62 | ||
|
|
ec686c9452 | ||
|
|
863de33f63 | ||
|
|
f56dd10106 | ||
|
|
8b10eb130a | ||
|
|
b0d9895e5e | ||
|
|
1d3e3f3c87 | ||
|
|
36eaebcbc7 | ||
|
|
7870c60ab4 | ||
|
|
e0d52d3578 | ||
|
|
9422825aec | ||
|
|
e467862c29 | ||
|
|
6b03ea2fe5 | ||
|
|
472d79cbf2 | ||
|
|
27701bbe1b | ||
|
|
9e1dcf8b64 | ||
|
|
c74de88ca3 | ||
|
|
361e9c629e | ||
|
|
3b907627f7 | ||
|
|
2284fd41b9 |
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "browser/ace"]
|
||||||
|
path = browser/ace
|
||||||
|
url = https://github.com/ajaxorg/ace-builds.git
|
||||||
129
Gulpfile.js
129
Gulpfile.js
@@ -1,129 +0,0 @@
|
|||||||
var gulp = require('gulp')
|
|
||||||
var styl = require('gulp-stylus')
|
|
||||||
var autoprefixer = require('gulp-autoprefixer')
|
|
||||||
var del = require('del')
|
|
||||||
var runSequence = require('run-sequence')
|
|
||||||
var plumber = require('gulp-plumber')
|
|
||||||
var notify = require('gulp-notify')
|
|
||||||
var rename = require('gulp-rename')
|
|
||||||
var livereload = require('gulp-livereload')
|
|
||||||
var inject = require('gulp-inject')
|
|
||||||
|
|
||||||
// for Dist
|
|
||||||
var rev = require('gulp-rev')
|
|
||||||
var ngAnnotate = require('gulp-ng-annotate')
|
|
||||||
var templateCache = require('gulp-angular-templatecache')
|
|
||||||
var uglify = require('gulp-uglify')
|
|
||||||
var minifyCss = require('gulp-minify-css')
|
|
||||||
var merge = require('merge-stream')
|
|
||||||
var concat = require('gulp-concat')
|
|
||||||
var minifyHtml = require('gulp-minify-html')
|
|
||||||
|
|
||||||
var config = require('./build.config.js')
|
|
||||||
|
|
||||||
gulp.task('build', function () {
|
|
||||||
var tpls = gulp.src(['src/browser/main/**/*.html','!src/browser/main/index.html','!src/browser/main/index.inject.html'])
|
|
||||||
.pipe(templateCache({}))
|
|
||||||
.pipe(concat('tpls.js'))
|
|
||||||
.pipe(ngAnnotate())
|
|
||||||
.pipe(uglify())
|
|
||||||
.pipe(gulp.dest('build'))
|
|
||||||
var js = gulp.src(['src/browser/main/**/*.js', 'src/browser/shared/**/*.js'])
|
|
||||||
.pipe(concat('app.js'))
|
|
||||||
.pipe(ngAnnotate())
|
|
||||||
.pipe(uglify())
|
|
||||||
.pipe(gulp.dest('build'))
|
|
||||||
var css = gulp.src(['src/browser/main/**/*.css', 'src/browser/shared/**/*.css'])
|
|
||||||
.pipe(concat('all.css'))
|
|
||||||
.pipe(minifyCss())
|
|
||||||
.pipe(gulp.dest('build'))
|
|
||||||
return merge(tpls, js, css)
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('vendor', function () {
|
|
||||||
var vendors = config.vendors
|
|
||||||
|
|
||||||
var vendorFiles = vendors.map(function (vendor) {
|
|
||||||
return vendor.src
|
|
||||||
})
|
|
||||||
|
|
||||||
vendorFiles.push('node_modules/font-awesome/**/font-awesome.css')
|
|
||||||
vendorFiles.push('node_modules/font-awesome/**/fontawesome-webfont.*')
|
|
||||||
vendorFiles.push('node_modules/font-awesome/**/FontAwesome.*')
|
|
||||||
|
|
||||||
return gulp.src(vendorFiles)
|
|
||||||
.pipe(gulp.dest('src/browser/vendor'))
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('styl', function () {
|
|
||||||
return gulp.src('src/browser/main/styles/app.styl')
|
|
||||||
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
|
|
||||||
.pipe(styl())
|
|
||||||
.pipe(autoprefixer())
|
|
||||||
.pipe(gulp.dest('src/browser/main/styles/'))
|
|
||||||
.pipe(livereload())
|
|
||||||
.pipe(notify('Stylus!!'))
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('styl-popup', function () {
|
|
||||||
return gulp.src('src/browser/popup/styles/app.styl')
|
|
||||||
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
|
|
||||||
.pipe(styl())
|
|
||||||
.pipe(autoprefixer())
|
|
||||||
.pipe(gulp.dest('src/browser/popup/styles/'))
|
|
||||||
.pipe(livereload())
|
|
||||||
.pipe(notify('Stylus!! @POPUP'))
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('bs', function () {
|
|
||||||
return gulp.src('src/browser/shared/styles/bootstrap.styl')
|
|
||||||
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
|
|
||||||
.pipe(styl())
|
|
||||||
.pipe(autoprefixer())
|
|
||||||
.pipe(gulp.dest('src/browser/shared/styles'))
|
|
||||||
.pipe(notify('Bootstrap compiled!!'))
|
|
||||||
.pipe(livereload())
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('inject', function (cb) {
|
|
||||||
runSequence(['inject-main', 'inject-popup'], cb)
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('inject-main', function () {
|
|
||||||
return gulp.src('src/browser/main/index.inject.html')
|
|
||||||
.pipe(inject(gulp.src(['src/browser/main/**/*.js', 'src/browser/main/**/*.css', 'src/browser/shared/**/*.js', 'src/browser/shared/**/*.css'], {read: false}), {
|
|
||||||
relative: true
|
|
||||||
}))
|
|
||||||
.pipe(rename(function (path) {
|
|
||||||
path.basename = 'index'
|
|
||||||
}))
|
|
||||||
.pipe(gulp.dest('src/browser/main/'))
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('watch-main', function () {
|
|
||||||
gulp.watch(
|
|
||||||
['src/browser/main/index.inject.html', 'src/browser/main/**/*.js', 'src/browser/main/**/*.css', 'src/browser/shared/**/*.js', 'src/browser/shared/**/*.css'], ['inject-main'])
|
|
||||||
|
|
||||||
gulp.watch('src/browser/main/styles/**/*.styl', ['styl'])
|
|
||||||
gulp.watch('src/browser/popup/styles/**/*.styl', ['styl-popup'])
|
|
||||||
gulp.watch('src/browser/shared/styles/**/*.styl', ['bs'])
|
|
||||||
livereload.listen()
|
|
||||||
})
|
|
||||||
gulp.task('inject-popup', function () {
|
|
||||||
return gulp.src('src/browser/popup/index.inject.html')
|
|
||||||
.pipe(inject(gulp.src(['src/browser/popup/**/*.js', 'src/browser/popup/**/*.css', 'src/browser/shared/**/*.js', 'src/browser/shared/**/*.css'], {read: false}), {
|
|
||||||
relative: true
|
|
||||||
}))
|
|
||||||
.pipe(rename(function (path) {
|
|
||||||
path.basename = 'index'
|
|
||||||
}))
|
|
||||||
.pipe(gulp.dest('src/browser/popup/'))
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('del', function (cb) {
|
|
||||||
del(['build/**/*'], cb)
|
|
||||||
})
|
|
||||||
|
|
||||||
gulp.task('default', function (cb) {
|
|
||||||
runSequence('del', 'build', 'watch', cb)
|
|
||||||
})
|
|
||||||
BIN
Lato-Regular.ttf
Normal file
BIN
Lato-Regular.ttf
Normal file
Binary file not shown.
BIN
Lato-Regular.woff
Normal file
BIN
Lato-Regular.woff
Normal file
Binary file not shown.
BIN
Lato-Regular.woff2
Normal file
BIN
Lato-Regular.woff2
Normal file
Binary file not shown.
11
bower.json
Normal file
11
bower.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "codexen-app",
|
||||||
|
"dependencies": {
|
||||||
|
"react": "~0.13.3",
|
||||||
|
"fontawesome": "~4.3.0",
|
||||||
|
"react-router": "~0.13.3",
|
||||||
|
"reflux": "~0.2.8",
|
||||||
|
"moment": "~2.10.3",
|
||||||
|
"markdown-it": "~4.3.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
browser/ace
Submodule
1
browser/ace
Submodule
Submodule browser/ace added at 0982db4853
43
browser/finder/Components/FinderDetail.jsx
Normal file
43
browser/finder/Components/FinderDetail.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
var CodeViewer = require('../../main/Components/CodeViewer')
|
||||||
|
|
||||||
|
var Markdown = require('../../main/Mixins/Markdown')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [Markdown],
|
||||||
|
propTypes: {
|
||||||
|
currentArticle: React.PropTypes.object
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var article = this.props.currentArticle
|
||||||
|
|
||||||
|
if (article != null) {
|
||||||
|
if (article.type === 'code') {
|
||||||
|
return (
|
||||||
|
<div className='FinderDetail'>
|
||||||
|
<div className='header'><i className='fa fa-code fa-fw'/> {article.description}</div>
|
||||||
|
<div className='content'>
|
||||||
|
<CodeViewer code={article.content} mode={article.mode}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else if (article.type === 'note') {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='FinderDetail'>
|
||||||
|
<div className='header'><i className='fa fa-file-text-o fa-fw'/> {article.title}</div>
|
||||||
|
<div className='content'>
|
||||||
|
<div className='marked' dangerouslySetInnerHTML={{__html: ' ' + this.markdown(article.content)}}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className='FinderDetail'>
|
||||||
|
<div className='nothing'>Nothing selected</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
15
browser/finder/Components/FinderInput.jsx
Normal file
15
browser/finder/Components/FinderInput.jsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
onChange: React.PropTypes.func,
|
||||||
|
search: React.PropTypes.string
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className='FinderInput'>
|
||||||
|
<input value={this.props.search} onChange={this.props.onChange} type='text'/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
79
browser/finder/Components/FinderList.jsx
Normal file
79
browser/finder/Components/FinderList.jsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
articles: React.PropTypes.arrayOf,
|
||||||
|
currentArticle: React.PropTypes.shape({
|
||||||
|
id: React.PropTypes.number,
|
||||||
|
type: React.PropTypes.string
|
||||||
|
}),
|
||||||
|
selectArticle: React.PropTypes.func
|
||||||
|
},
|
||||||
|
componentDidUpdate: function () {
|
||||||
|
var index = this.props.articles.indexOf(this.props.currentArticle)
|
||||||
|
var el = React.findDOMNode(this)
|
||||||
|
var li = el.querySelectorAll('li')[index]
|
||||||
|
|
||||||
|
if (li == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var overflowBelow = el.clientHeight + el.scrollTop < li.offsetTop + li.clientHeight
|
||||||
|
if (overflowBelow) {
|
||||||
|
el.scrollTop = li.offsetTop + li.clientHeight - el.clientHeight
|
||||||
|
}
|
||||||
|
var overflowAbove = el.scrollTop > li.offsetTop
|
||||||
|
if (overflowAbove) {
|
||||||
|
el.scrollTop = li.offsetTop
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleArticleClick: function (article) {
|
||||||
|
return function () {
|
||||||
|
this.props.selectArticle(article)
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var list = this.props.articles.map(function (article) {
|
||||||
|
if (article == null) {
|
||||||
|
return (
|
||||||
|
<li className={isActive ? 'active' : ''}>
|
||||||
|
<div className='articleItem'>Undefined</div>
|
||||||
|
<div className='divider'/>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var isActive = this.props.currentArticle != null && (article.type === this.props.currentArticle.type && article.id === this.props.currentArticle.id)
|
||||||
|
if (article.type === 'code') {
|
||||||
|
return (
|
||||||
|
<li onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
|
||||||
|
<div className='articleItem'><i className='fa fa-code fa-fw'/> {article.description}</div>
|
||||||
|
<div className='divider'/>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (article.type === 'note') {
|
||||||
|
return (
|
||||||
|
<li onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
|
||||||
|
<div className='articleItem'><i className='fa fa-file-text-o fa-fw'/> {article.title}</div>
|
||||||
|
<div className='divider'/>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<li className={isActive ? 'active' : ''}>
|
||||||
|
<div className='articleItem'>Undefined</div>
|
||||||
|
<div className='divider'/>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}.bind(this))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='FinderList'>
|
||||||
|
<ul>
|
||||||
|
{list}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
BIN
browser/finder/favicon.ico
Normal file
BIN
browser/finder/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
72
browser/finder/index.electron.html
Normal file
72
browser/finder/index.electron.html
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<!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('../../Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||||
|
url('../../Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||||
|
url('../../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')
|
||||||
|
require('node-jsx').install({ harmony: true, extension: '.jsx' })
|
||||||
|
require('./index.jsx')
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
0
browser/finder/index.html
Normal file
0
browser/finder/index.html
Normal file
135
browser/finder/index.jsx
Normal file
135
browser/finder/index.jsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
/* global localStorage */
|
||||||
|
var remote = require('remote')
|
||||||
|
var hideFinder = remote.getGlobal('hideFinder')
|
||||||
|
var clipboard = require('clipboard')
|
||||||
|
|
||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
var ArticleFilter = require('../main/Mixins/ArticleFilter')
|
||||||
|
|
||||||
|
var FinderInput = require('./Components/FinderInput')
|
||||||
|
var FinderList = require('./Components/FinderList')
|
||||||
|
var FinderDetail = require('./Components/FinderDetail')
|
||||||
|
|
||||||
|
// Filter end
|
||||||
|
|
||||||
|
function fetchArticles () {
|
||||||
|
var user = JSON.parse(localStorage.getItem('currentUser'))
|
||||||
|
if (user == null) {
|
||||||
|
console.log('need to login')
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
var articles = []
|
||||||
|
user.Planets.forEach(function (planet) {
|
||||||
|
var _planet = JSON.parse(localStorage.getItem('planet-' + planet.id))
|
||||||
|
articles = articles.concat(_planet.Codes, _planet.Notes)
|
||||||
|
})
|
||||||
|
user.Teams.forEach(function (team) {
|
||||||
|
team.Planets.forEach(function (planet) {
|
||||||
|
var _planet = JSON.parse(localStorage.getItem('planet-' + planet.id))
|
||||||
|
articles = articles.concat(_planet.Codes, _planet.Notes)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return articles
|
||||||
|
}
|
||||||
|
|
||||||
|
var Finder = React.createClass({
|
||||||
|
mixins: [ArticleFilter],
|
||||||
|
getInitialState: function () {
|
||||||
|
var articles = fetchArticles()
|
||||||
|
return {
|
||||||
|
articles: articles,
|
||||||
|
currentArticle: articles[0],
|
||||||
|
search: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
document.addEventListener('keydown', this.handleKeyDown)
|
||||||
|
document.addEventListener('click', this.handleClick)
|
||||||
|
window.addEventListener('focus', this.handleFinderFocus)
|
||||||
|
this.handleFinderFocus()
|
||||||
|
},
|
||||||
|
componentWillUnmount: function () {
|
||||||
|
document.removeEventListener('keydown', this.handleKeyDown)
|
||||||
|
document.removeEventListener('click', this.handleClick)
|
||||||
|
window.removeEventListener('focus', this.handleFinderFocus)
|
||||||
|
},
|
||||||
|
handleFinderFocus: function () {
|
||||||
|
console.log('focusseeddddd')
|
||||||
|
this.focusInput()
|
||||||
|
var articles = fetchArticles()
|
||||||
|
this.setState({
|
||||||
|
articles: articles,
|
||||||
|
search: ''
|
||||||
|
}, function () {
|
||||||
|
var firstArticle = this.refs.finderList.props.articles[0]
|
||||||
|
if (firstArticle) {
|
||||||
|
this.setState({
|
||||||
|
currentArticle: firstArticle
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleKeyDown: function (e) {
|
||||||
|
if (e.keyCode === 38) {
|
||||||
|
this.selectPrevious()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 40) {
|
||||||
|
this.selectNext()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
var article = this.state.currentArticle
|
||||||
|
clipboard.writeText(article.content)
|
||||||
|
hideFinder()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
hideFinder()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
focusInput: function () {
|
||||||
|
React.findDOMNode(this.refs.finderInput).querySelector('input').focus()
|
||||||
|
},
|
||||||
|
handleClick: function () {
|
||||||
|
this.focusInput()
|
||||||
|
},
|
||||||
|
selectPrevious: function () {
|
||||||
|
var index = this.refs.finderList.props.articles.indexOf(this.state.currentArticle)
|
||||||
|
if (index > 0) {
|
||||||
|
this.setState({currentArticle: this.refs.finderList.props.articles[index - 1]})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectNext: function () {
|
||||||
|
var index = this.refs.finderList.props.articles.indexOf(this.state.currentArticle)
|
||||||
|
if (index > -1 && index < this.refs.finderList.props.articles.length - 1) {
|
||||||
|
this.setState({currentArticle: this.refs.finderList.props.articles[index + 1]})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectArticle: function (article) {
|
||||||
|
this.setState({currentArticle: article})
|
||||||
|
},
|
||||||
|
handleChange: function (e) {
|
||||||
|
this.setState({search: e.target.value}, function () {
|
||||||
|
this.setState({currentArticle: this.refs.finderList.props.articles[0]})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var articles = this.searchArticle(this.state.search, this.state.articles)
|
||||||
|
return (
|
||||||
|
<div className='Finder'>
|
||||||
|
<FinderInput ref='finderInput' onChange={this.handleChange} search={this.state.search}/>
|
||||||
|
<FinderList ref='finderList' currentArticle={this.state.currentArticle} articles={articles} selectArticle={this.selectArticle}/>
|
||||||
|
<FinderDetail currentArticle={this.state.currentArticle}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
React.render(<Finder/>, document.getElementById('content'))
|
||||||
11
browser/index.html
Normal file
11
browser/index.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<!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>
|
||||||
40
browser/main/Components/AboutModal.jsx
Normal file
40
browser/main/Components/AboutModal.jsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
var remote = require('remote')
|
||||||
|
var version = remote.getGlobal('version')
|
||||||
|
|
||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
var ExternalLink = require('../Mixins/ExternalLink')
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [ExternalLink, KeyCaster('aboutModal')],
|
||||||
|
propTypes: {
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className='AboutModal modal'>
|
||||||
|
<div className='about1'>
|
||||||
|
<img className='logo' src='resources/favicon-230x230.png'/>
|
||||||
|
<div className='appInfo'>Boost {version == null || version.length === 0 ? 'DEV version' : 'v' + version}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='about2'>
|
||||||
|
<div className='externalLabel'>External links</div>
|
||||||
|
<ul className='externalList'>
|
||||||
|
<li><a onClick={this.openExternal} href='http://b00st.io'>Boost Homepage <i className='fa fa-external-link'/></a></li>
|
||||||
|
<li><a>Regulation <i className='fa fa-external-link'/></a></li>
|
||||||
|
<li><a>Private policy <i className='fa fa-external-link'/></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
102
browser/main/Components/AddMemberModal.jsx
Normal file
102
browser/main/Components/AddMemberModal.jsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
var Select = require('react-select')
|
||||||
|
|
||||||
|
var LinkedState = require('../Mixins/LinkedState')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var UserStore = require('../Stores/UserStore')
|
||||||
|
|
||||||
|
var getOptions = function (input, callback) {
|
||||||
|
Hq.searchUser(input)
|
||||||
|
.then(function (res) {
|
||||||
|
callback(null, {
|
||||||
|
options: res.body.map(function (user) {
|
||||||
|
return {
|
||||||
|
label: user.name,
|
||||||
|
value: user.name
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
complete: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [LinkedState, KeyCaster('addMemberModal')],
|
||||||
|
propTypes: {
|
||||||
|
team: React.PropTypes.object,
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
userName: '',
|
||||||
|
role: 'member'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
case 'submitAddMemberModal':
|
||||||
|
this.handleSubmit()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSubmit: function () {
|
||||||
|
this.setState({errorMessage: null}, function () {
|
||||||
|
Hq
|
||||||
|
.addMember(this.props.team.name, {
|
||||||
|
userName: this.state.userName,
|
||||||
|
role: this.state.role
|
||||||
|
})
|
||||||
|
.then(function (res) {
|
||||||
|
console.log(res.body)
|
||||||
|
UserStore.Actions.addMember(res.body)
|
||||||
|
this.props.close()
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
if (err.status === 403) {
|
||||||
|
this.setState({errorMessage: err.response.body.message})
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleChange: function (value) {
|
||||||
|
this.setState({userName: value})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className='AddMemberModal modal'>
|
||||||
|
<Select
|
||||||
|
name='userName'
|
||||||
|
value={this.state.userName}
|
||||||
|
placeholder='Username to add'
|
||||||
|
asyncOptions={getOptions}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
className='userNameSelect'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='formField'>
|
||||||
|
Add member as
|
||||||
|
<select valueLink={this.linkState('role')}>
|
||||||
|
<option value={'member'}>Member</option>
|
||||||
|
<option value={'owner'}>Owner</option>
|
||||||
|
</select>
|
||||||
|
role
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{this.state.errorMessage != null ? (<p className='errorAlert'>{this.state.errorMessage}</p>) : null}
|
||||||
|
|
||||||
|
<button onClick={this.handleSubmit} className='submitButton'><i className='fa fa-check'/></button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
55
browser/main/Components/CodeDeleteModal.jsx
Normal file
55
browser/main/Components/CodeDeleteModal.jsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
var React = require('react')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var PlanetStore = require('../Stores/PlanetStore')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [KeyCaster('codeDeleteModal')],
|
||||||
|
propTypes: {
|
||||||
|
planet: React.PropTypes.object,
|
||||||
|
code: React.PropTypes.object,
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'submitCodeDeleteModal':
|
||||||
|
this.submit()
|
||||||
|
break
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit: function () {
|
||||||
|
var planet = this.props.planet
|
||||||
|
Hq.destroyCode(planet.userName, planet.name, this.props.code.localId)
|
||||||
|
.then(function (res) {
|
||||||
|
PlanetStore.Actions.destroyCode(res.body)
|
||||||
|
this.props.close()
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className='CodeDeleteModal modal'>
|
||||||
|
<div className='modal-header'>
|
||||||
|
<h1>Delete Code</h1>
|
||||||
|
</div>
|
||||||
|
<div className='modal-body'>
|
||||||
|
<p>Are you sure to delete it?</p>
|
||||||
|
</div>
|
||||||
|
<div className='modal-footer'>
|
||||||
|
<div className='modal-control'>
|
||||||
|
<button onClick={this.props.close} className='btn-default'>Cancel</button>
|
||||||
|
<button ref='submit' onClick={this.submit} className='btn-primary'>Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
26
browser/main/Components/CodeEditModal.jsx
Normal file
26
browser/main/Components/CodeEditModal.jsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
var React = require('react')
|
||||||
|
var CodeForm = require('./CodeForm')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
close: React.PropTypes.func,
|
||||||
|
code: React.PropTypes.object,
|
||||||
|
planet: React.PropTypes.object
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
// TODO: Hacked!! should fix later
|
||||||
|
setTimeout(function () {
|
||||||
|
React.findDOMNode(this.refs.form.refs.description).focus()
|
||||||
|
}.bind(this), 1)
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className='CodeEditModal modal'>
|
||||||
|
<div className='modal-header'>
|
||||||
|
<h1>Edit Code</h1>
|
||||||
|
</div>
|
||||||
|
<CodeForm ref='form' code={this.props.code} planet={this.props.planet} close={this.props.close}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
59
browser/main/Components/CodeEditor.jsx
Normal file
59
browser/main/Components/CodeEditor.jsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
var ace = window.ace
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
code: React.PropTypes.string,
|
||||||
|
mode: React.PropTypes.string,
|
||||||
|
className: React.PropTypes.string,
|
||||||
|
onChange: React.PropTypes.func
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
var el = React.findDOMNode(this.refs.target)
|
||||||
|
var editor = ace.edit(el)
|
||||||
|
editor.$blockScrolling = Infinity
|
||||||
|
editor.setValue(this.props.code)
|
||||||
|
editor.renderer.setShowGutter(true)
|
||||||
|
editor.setTheme('ace/theme/xcode')
|
||||||
|
editor.clearSelection()
|
||||||
|
|
||||||
|
var session = editor.getSession()
|
||||||
|
if (this.props.mode != null && this.props.mode.length > 0) {
|
||||||
|
session.setMode('ace/mode/' + this.props.mode)
|
||||||
|
} else {
|
||||||
|
session.setMode('ace/mode/text')
|
||||||
|
}
|
||||||
|
session.setUseSoftTabs(true)
|
||||||
|
session.setOption('useWorker', false)
|
||||||
|
session.setUseWrapMode(true)
|
||||||
|
|
||||||
|
session.on('change', function (e) {
|
||||||
|
if (this.props.onChange != null) {
|
||||||
|
var value = editor.getValue()
|
||||||
|
this.props.onChange(e, value)
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
|
||||||
|
this.setState({editor: editor})
|
||||||
|
},
|
||||||
|
componentDidUpdate: function (prevProps) {
|
||||||
|
if (this.state.editor.getValue() !== this.props.code) {
|
||||||
|
this.state.editor.setValue(this.props.code)
|
||||||
|
this.state.editor.clearSelection()
|
||||||
|
}
|
||||||
|
if (prevProps.mode !== this.props.mode) {
|
||||||
|
var session = this.state.editor.getSession()
|
||||||
|
if (this.props.mode != null && this.props.mode.length > 0) {
|
||||||
|
session.setMode('ace/mode/' + this.props.mode)
|
||||||
|
} else {
|
||||||
|
session.setMode('ace/mode/text')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div ref='target' className={this.props.className}></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
162
browser/main/Components/CodeForm.jsx
Normal file
162
browser/main/Components/CodeForm.jsx
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
var CodeEditor = require('./CodeEditor')
|
||||||
|
var Select = require('react-select')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var LinkedState = require('../Mixins/LinkedState')
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var PlanetStore = require('../Stores/PlanetStore')
|
||||||
|
|
||||||
|
var aceModes = require('../../../modules/ace-modes')
|
||||||
|
|
||||||
|
var getOptions = function (input, callback) {
|
||||||
|
Hq.searchTag(input)
|
||||||
|
.then(function (res) {
|
||||||
|
callback(null, {
|
||||||
|
options: res.body.map(function (tag) {
|
||||||
|
return {
|
||||||
|
label: tag.name,
|
||||||
|
value: tag.name
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
complete: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [LinkedState, KeyCaster('codeForm')],
|
||||||
|
propTypes: {
|
||||||
|
planet: React.PropTypes.object,
|
||||||
|
close: React.PropTypes.func,
|
||||||
|
transitionTo: React.PropTypes.func,
|
||||||
|
code: React.PropTypes.object
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
var code = Object.assign({
|
||||||
|
description: '',
|
||||||
|
mode: '',
|
||||||
|
content: '',
|
||||||
|
Tags: []
|
||||||
|
}, this.props.code)
|
||||||
|
|
||||||
|
code.Tags = code.Tags.map(function (tag) {
|
||||||
|
return {
|
||||||
|
label: tag.name,
|
||||||
|
value: tag.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: code
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'submitCodeForm':
|
||||||
|
this.submit()
|
||||||
|
break
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleModeChange: function (selected) {
|
||||||
|
var code = this.state.code
|
||||||
|
code.mode = selected
|
||||||
|
this.setState({code: code})
|
||||||
|
},
|
||||||
|
handleTagsChange: function (selected, all) {
|
||||||
|
var code = this.state.code
|
||||||
|
code.Tags = all
|
||||||
|
this.setState({code: code})
|
||||||
|
},
|
||||||
|
handleContentChange: function (e, value) {
|
||||||
|
var code = this.state.code
|
||||||
|
code.content = value
|
||||||
|
this.setState({code: code})
|
||||||
|
},
|
||||||
|
submit: function () {
|
||||||
|
var planet = this.props.planet
|
||||||
|
var code = this.state.code
|
||||||
|
code.Tags = code.Tags.map(function (tag) {
|
||||||
|
return tag.value
|
||||||
|
})
|
||||||
|
if (this.props.code == null) {
|
||||||
|
Hq.createCode(planet.userName, planet.name, this.state.code)
|
||||||
|
.then(function (res) {
|
||||||
|
var code = res.body
|
||||||
|
PlanetStore.Actions.updateCode(code)
|
||||||
|
this.props.close()
|
||||||
|
this.props.transitionTo('codes', {userName: planet.userName, planetName: planet.name, localId: code.localId})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Hq.updateCode(planet.userName, planet.name, this.props.code.localId, this.state.code)
|
||||||
|
.then(function (res) {
|
||||||
|
var code = res.body
|
||||||
|
PlanetStore.Actions.updateCode(code)
|
||||||
|
this.props.close()
|
||||||
|
}.bind(this))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleKeyDown: function (e) {
|
||||||
|
if (e.keyCode === 13 && e.metaKey) {
|
||||||
|
this.submit()
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var modeOptions = aceModes.map(function (mode) {
|
||||||
|
return {
|
||||||
|
label: mode,
|
||||||
|
value: mode
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div className='CodeForm'>
|
||||||
|
<div className='modal-body'>
|
||||||
|
<div className='form-group'>
|
||||||
|
<textarea ref='description' className='codeDescription block-input' valueLink={this.linkState('code.description')} placeholder='Description'/>
|
||||||
|
</div>
|
||||||
|
<div className='form-group'>
|
||||||
|
<Select
|
||||||
|
name='mode'
|
||||||
|
className='modeSelect'
|
||||||
|
value={this.state.code.mode}
|
||||||
|
placeholder='Select Language'
|
||||||
|
options={modeOptions}
|
||||||
|
onChange={this.handleModeChange}/>
|
||||||
|
</div>
|
||||||
|
<div className='form-group'>
|
||||||
|
<CodeEditor onChange={this.handleContentChange} code={this.state.code.content} mode={this.state.code.mode}/>
|
||||||
|
</div>
|
||||||
|
<div className='form-group'>
|
||||||
|
<Select
|
||||||
|
name='Tags'
|
||||||
|
multi={true}
|
||||||
|
allowCreate={true}
|
||||||
|
value={this.state.code.Tags}
|
||||||
|
placeholder='Tags...'
|
||||||
|
asyncOptions={getOptions}
|
||||||
|
onChange={this.handleTagsChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='modal-footer'>
|
||||||
|
<div className='modal-control'>
|
||||||
|
<button onClick={this.props.close} className='btn-default'>Cancel</button>
|
||||||
|
<button onClick={this.submit} className='btn-primary'>{this.props.code == null ? 'Launch' : 'Relaunch'}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
53
browser/main/Components/CodeViewer.jsx
Normal file
53
browser/main/Components/CodeViewer.jsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
var ace = window.ace
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
code: React.PropTypes.string,
|
||||||
|
mode: React.PropTypes.string,
|
||||||
|
className: React.PropTypes.string
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
var el = React.findDOMNode(this.refs.target)
|
||||||
|
var editor = ace.edit(el)
|
||||||
|
editor.$blockScrolling = Infinity
|
||||||
|
editor.setValue(this.props.code)
|
||||||
|
editor.renderer.setShowGutter(false)
|
||||||
|
editor.setReadOnly(true)
|
||||||
|
editor.setTheme('ace/theme/xcode')
|
||||||
|
editor.setHighlightActiveLine(false)
|
||||||
|
editor.clearSelection()
|
||||||
|
|
||||||
|
var session = editor.getSession()
|
||||||
|
if (this.props.mode != null && this.props.mode.length > 0) {
|
||||||
|
session.setMode('ace/mode/' + this.props.mode)
|
||||||
|
} else {
|
||||||
|
session.setMode('ace/mode/text')
|
||||||
|
}
|
||||||
|
session.setUseSoftTabs(true)
|
||||||
|
session.setOption('useWorker', false)
|
||||||
|
session.setUseWrapMode(true)
|
||||||
|
|
||||||
|
this.setState({editor: editor})
|
||||||
|
},
|
||||||
|
componentDidUpdate: function (prevProps) {
|
||||||
|
if (this.state.editor.getValue() !== this.props.code) {
|
||||||
|
this.state.editor.setValue(this.props.code)
|
||||||
|
this.state.editor.clearSelection()
|
||||||
|
}
|
||||||
|
if (prevProps.mode !== this.props.mode) {
|
||||||
|
var session = this.state.editor.getSession()
|
||||||
|
if (this.props.mode != null && this.props.mode.length > 0) {
|
||||||
|
session.setMode('ace/mode/' + this.props.mode)
|
||||||
|
} else {
|
||||||
|
session.setMode('ace/mode/text')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div ref='target' className={this.props.className}></div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
80
browser/main/Components/ContactModal.jsx
Normal file
80
browser/main/Components/ContactModal.jsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
var React = require('react')
|
||||||
|
|
||||||
|
var LinkedState = require('../Mixins/LinkedState')
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [LinkedState, KeyCaster('contactModal')],
|
||||||
|
propTypes: {
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
isSent: false,
|
||||||
|
mail: {
|
||||||
|
title: '',
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
case 'submitContactModal':
|
||||||
|
if (this.state.isSent) {
|
||||||
|
this.props.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.sendEmail()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
React.findDOMNode(this.refs.title).focus()
|
||||||
|
},
|
||||||
|
sendEmail: function () {
|
||||||
|
Hq.sendEmail(this.state.mail)
|
||||||
|
.then(function (res) {
|
||||||
|
this.setState({isSent: !this.state.isSent})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className='ContactModal modal'>
|
||||||
|
<div className='modal-header'><h1>Contact form</h1></div>
|
||||||
|
|
||||||
|
{!this.state.isSent ? (
|
||||||
|
<div className='contactForm'>
|
||||||
|
<div className='modal-body'>
|
||||||
|
<div className='formField'>
|
||||||
|
<input ref='title' valueLink={this.linkState('mail.title')} placeholder='Title'/>
|
||||||
|
</div>
|
||||||
|
<div className='formField'>
|
||||||
|
<textarea valueLink={this.linkState('mail.content')} placeholder='Content'/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='modal-footer'>
|
||||||
|
<div className='formControl'>
|
||||||
|
<button onClick={this.sendEmail} className='sendButton'>Send</button>
|
||||||
|
<button onClick={this.props.close}>Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='confirmation'>
|
||||||
|
<div className='confirmationMessage'>Thanks for sharing your opinion!</div>
|
||||||
|
<button className='doneButton' onClick={this.props.close}>Done</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
171
browser/main/Components/EditProfileModal.jsx
Normal file
171
browser/main/Components/EditProfileModal.jsx
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
/* global localStorage */
|
||||||
|
|
||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var LinkedState = require('../Mixins/LinkedState')
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var UserStore = require('../Stores/UserStore')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [LinkedState, KeyCaster('editProfileModal')],
|
||||||
|
propTypes: {
|
||||||
|
user: React.PropTypes.shape({
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
profileName: React.PropTypes.string,
|
||||||
|
email: React.PropTypes.string
|
||||||
|
}),
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
var user = this.props.user
|
||||||
|
return {
|
||||||
|
currentTab: 'userInfo',
|
||||||
|
user: {
|
||||||
|
profileName: user.profileName,
|
||||||
|
email: user.email
|
||||||
|
},
|
||||||
|
userSubmitStatus: null,
|
||||||
|
password: {
|
||||||
|
currentPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
passwordConfirmation: ''
|
||||||
|
},
|
||||||
|
passwordSubmitStatus: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectTab: function (tabName) {
|
||||||
|
return function () {
|
||||||
|
this.setState({currentTab: tabName})
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
saveUserInfo: function () {
|
||||||
|
this.setState({
|
||||||
|
userSubmitStatus: 'sending'
|
||||||
|
}, function () {
|
||||||
|
Hq.updateUser(this.props.user.name, this.state.user)
|
||||||
|
.then(function (res) {
|
||||||
|
this.setState({userSubmitStatus: 'done'}, function () {
|
||||||
|
localStorage.setItem('currentUser', JSON.stringify(res.body))
|
||||||
|
UserStore.Actions.update(res.body)
|
||||||
|
})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
this.setState({userSubmitStatus: 'error'})
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
savePassword: function () {
|
||||||
|
this.setState({
|
||||||
|
passwordSubmitStatus: 'sending'
|
||||||
|
}, function () {
|
||||||
|
console.log(this.state.password)
|
||||||
|
Hq.changePassword(this.state.password)
|
||||||
|
.then(function (res) {
|
||||||
|
this.setState({
|
||||||
|
passwordSubmitStatus: 'done',
|
||||||
|
currentPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
passwordConfirmation: ''
|
||||||
|
})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
this.setState({
|
||||||
|
passwordSubmitStatus: 'error',
|
||||||
|
currentPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
passwordConfirmation: ''
|
||||||
|
})
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var content
|
||||||
|
|
||||||
|
switch (this.state.currentTab) {
|
||||||
|
case 'userInfo':
|
||||||
|
content = this.renderUserInfoTab()
|
||||||
|
break
|
||||||
|
case 'password':
|
||||||
|
content = this.renderPasswordTab()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='EditProfileModal modal tabModal'>
|
||||||
|
<div className='leftPane'>
|
||||||
|
<div className='tabLabel'>Edit profile</div>
|
||||||
|
<div className='tabList'>
|
||||||
|
<button className={this.state.currentTab === 'userInfo' ? 'active' : ''} onClick={this.selectTab('userInfo')}><i className='fa fa-user fa-fw'/> User Info</button>
|
||||||
|
<button className={this.state.currentTab === 'password' ? 'active' : ''} onClick={this.selectTab('password')}><i className='fa fa-lock fa-fw'/> Password</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='rightPane'>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderUserInfoTab: function () {
|
||||||
|
return (
|
||||||
|
<div className='userInfoTab'>
|
||||||
|
<div className='formField'>
|
||||||
|
<label>Profile Name</label>
|
||||||
|
<input valueLink={this.linkState('user.profileName')}/>
|
||||||
|
</div>
|
||||||
|
<div className='formField'>
|
||||||
|
<label>E-mail</label>
|
||||||
|
<input valueLink={this.linkState('user.email')}/>
|
||||||
|
</div>
|
||||||
|
<div className='formConfirm'>
|
||||||
|
<button disabled={this.state.userSubmitStatus === 'sending'} onClick={this.saveUserInfo}>Save</button>
|
||||||
|
|
||||||
|
<div className={'alertInfo' + (this.state.userSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
||||||
|
|
||||||
|
<div className={'alertError' + (this.state.userSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
||||||
|
|
||||||
|
<div className={'alertSuccess' + (this.state.userSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderPasswordTab: function () {
|
||||||
|
return (
|
||||||
|
<div className='passwordTab'>
|
||||||
|
<div className='formField'>
|
||||||
|
<label>Current password</label>
|
||||||
|
<input valueLink={this.linkState('password.currentPassword')}/>
|
||||||
|
</div>
|
||||||
|
<div className='formField'>
|
||||||
|
<label>New password</label>
|
||||||
|
<input valueLink={this.linkState('password.newPassword')}/>
|
||||||
|
</div>
|
||||||
|
<div className='formField'>
|
||||||
|
<label>Confirmation</label>
|
||||||
|
<input valueLink={this.linkState('password.passwordConfirmation')}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='formConfirm'>
|
||||||
|
<button disabled={this.state.password.newPassword.length === 0 || this.state.password.newPassword !== this.state.password.passwordConfirmation || this.state.passwordSubmitStatus === 'sending'} onClick={this.savePassword}>Save</button>
|
||||||
|
|
||||||
|
<div className={'alertInfo' + (this.state.passwordSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
||||||
|
|
||||||
|
<div className={'alertError' + (this.state.passwordSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
||||||
|
|
||||||
|
<div className={'alertSuccess' + (this.state.passwordSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
180
browser/main/Components/HomeNavigator.jsx
Normal file
180
browser/main/Components/HomeNavigator.jsx
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/* global localStorage */
|
||||||
|
|
||||||
|
var React = require('react/addons')
|
||||||
|
var ReactRouter = require('react-router')
|
||||||
|
var Navigation = ReactRouter.Navigation
|
||||||
|
var State = ReactRouter.State
|
||||||
|
var Link = ReactRouter.Link
|
||||||
|
var Reflux = require('reflux')
|
||||||
|
|
||||||
|
var Modal = require('../Mixins/Modal')
|
||||||
|
|
||||||
|
var UserStore = require('../Stores/UserStore')
|
||||||
|
|
||||||
|
var AboutModal = require('./AboutModal')
|
||||||
|
var PlanetCreateModal = require('./PlanetCreateModal')
|
||||||
|
var TeamCreateModal = require('./TeamCreateModal')
|
||||||
|
var LogoutModal = require('./LogoutModal')
|
||||||
|
var ProfileImage = require('./ProfileImage')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [Navigation, State, Reflux.listenTo(UserStore, 'onUserChange'), Modal],
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
isPlanetCreateModalOpen: false,
|
||||||
|
currentUser: JSON.parse(localStorage.getItem('currentUser'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUserChange: function (res) {
|
||||||
|
switch (res.status) {
|
||||||
|
case 'userUpdated':
|
||||||
|
var user = res.data
|
||||||
|
var currentUser = this.state.currentUser
|
||||||
|
if (currentUser.id === user.id) {
|
||||||
|
this.setState({currentUser: user})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({currentUser: currentUser})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openTeamCreateModal: function () {
|
||||||
|
this.openModal(TeamCreateModal, {user: this.state.currentUser, transitionTo: this.transitionTo})
|
||||||
|
},
|
||||||
|
openAboutModal: function () {
|
||||||
|
this.openModal(AboutModal)
|
||||||
|
},
|
||||||
|
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})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var params = this.getParams()
|
||||||
|
|
||||||
|
if (this.state.currentUser == null) {
|
||||||
|
return (
|
||||||
|
<div className='HomeNavigator'>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var planets = (this.state.currentUser.Planets.concat(this.state.currentUser.Teams.reduce(function (planets, team) {
|
||||||
|
return team.Planets == null ? planets : planets.concat(team.Planets)
|
||||||
|
}, []))).map(function (planet, 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>
|
||||||
|
</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>
|
||||||
|
<button onClick={this.openPlanetCreateModal} className='newPlanet'>
|
||||||
|
<i className='fa fa-plus'/>
|
||||||
|
<div className='tooltip'>Create new planet</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.openAboutModal}><i className='fa fa-info-circle fa-fw'/> About this app</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button onClick={this.handleLogoutClick}><i className='fa fa-sign-out fa-fw'/> Log out</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
79
browser/main/Components/LaunchModal.jsx
Normal file
79
browser/main/Components/LaunchModal.jsx
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
var CodeForm = require('./CodeForm')
|
||||||
|
var NoteForm = require('./NoteForm')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
planet: React.PropTypes.object,
|
||||||
|
transitionTo: React.PropTypes.func,
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
currentTab: 'code'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
var codeButton = React.findDOMNode(this.refs.codeButton)
|
||||||
|
codeButton.addEventListener('keydown', this.handleKeyDown)
|
||||||
|
React.findDOMNode(this.refs.noteButton).addEventListener('keydown', this.handleKeyDown)
|
||||||
|
codeButton.focus()
|
||||||
|
},
|
||||||
|
componentWillUnmount: function () {
|
||||||
|
React.findDOMNode(this.refs.codeButton).removeEventListener('keydown', this.handleKeyDown)
|
||||||
|
React.findDOMNode(this.refs.noteButton).removeEventListener('keydown', this.handleKeyDown)
|
||||||
|
},
|
||||||
|
handleKeyDown: function (e) {
|
||||||
|
if (e.keyCode === 37 && e.metaKey) {
|
||||||
|
this.selectCodeTab()
|
||||||
|
e.stopPropagation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.keyCode === 39 && e.metaKey) {
|
||||||
|
this.selectNoteTab()
|
||||||
|
e.stopPropagation()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.keyCode === 9) {
|
||||||
|
if (this.state.currentTab === 'code') React.findDOMNode(this.refs.form.refs.description).focus()
|
||||||
|
else React.findDOMNode(this.refs.form.refs.title).focus()
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectCodeTab: function () {
|
||||||
|
this.setState({currentTab: 'code'}, function () {
|
||||||
|
React.findDOMNode(this.refs.codeButton).focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
selectNoteTab: function () {
|
||||||
|
this.setState({currentTab: 'note'}, function () {
|
||||||
|
React.findDOMNode(this.refs.noteButton).focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var modalBody
|
||||||
|
if (this.state.currentTab === 'code') {
|
||||||
|
modalBody = (
|
||||||
|
<CodeForm ref='form' planet={this.props.planet} transitionTo={this.props.transitionTo} close={this.props.close}/>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
modalBody = (
|
||||||
|
<NoteForm ref='form' planet={this.props.planet} transitionTo={this.props.transitionTo} close={this.props.close}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='LaunchModal modal'>
|
||||||
|
<div className='modal-header'>
|
||||||
|
<div className='modal-tab'>
|
||||||
|
<button ref='codeButton' className={this.state.currentTab === 'code' ? 'btn-primary active' : 'btn-default'} onClick={this.selectCodeTab}>Code</button>
|
||||||
|
<button ref='noteButton' className={this.state.currentTab === 'note' ? 'btn-primary active' : 'btn-default'} onClick={this.selectNoteTab}>Note</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{modalBody}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
40
browser/main/Components/LogoutModal.jsx
Normal file
40
browser/main/Components/LogoutModal.jsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/* global localStorage */
|
||||||
|
|
||||||
|
var React = require('react')
|
||||||
|
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [KeyCaster('logoutModal')],
|
||||||
|
propTypes: {
|
||||||
|
transitionTo: React.PropTypes.func,
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
case 'submitLogoutModal':
|
||||||
|
this.logout()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logout: function () {
|
||||||
|
localStorage.removeItem('currentUser')
|
||||||
|
localStorage.removeItem('token')
|
||||||
|
this.props.transitionTo('login')
|
||||||
|
this.props.close()
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className='LogoutModal modal'>
|
||||||
|
<div className='messageLabel'>Are you sure to log out?</div>
|
||||||
|
<div className='formControl'>
|
||||||
|
<button onClick={this.props.close}>Cancel</button>
|
||||||
|
<button className='logoutButton' onClick={this.logout}>Log out</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
43
browser/main/Components/MarkdownPreview.jsx
Normal file
43
browser/main/Components/MarkdownPreview.jsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
var React = require('react')
|
||||||
|
|
||||||
|
var Markdown = require('../Mixins/Markdown')
|
||||||
|
var ExternalLink = require('../Mixins/ExternalLink')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [Markdown, ExternalLink],
|
||||||
|
propTypes: {
|
||||||
|
className: React.PropTypes.string,
|
||||||
|
content: React.PropTypes.string
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
this.addListener()
|
||||||
|
},
|
||||||
|
componentDidUpdate: function () {
|
||||||
|
this.addListener()
|
||||||
|
},
|
||||||
|
componentWillUnmount: function () {
|
||||||
|
this.removeListener()
|
||||||
|
},
|
||||||
|
componentWillUpdate: function () {
|
||||||
|
this.removeListener()
|
||||||
|
},
|
||||||
|
addListener: function () {
|
||||||
|
var anchors = React.findDOMNode(this).querySelectorAll('a')
|
||||||
|
|
||||||
|
for (var i = 0; i < anchors.length; i++) {
|
||||||
|
anchors[i].addEventListener('click', this.openExternal)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeListener: function () {
|
||||||
|
var anchors = React.findDOMNode(this).querySelectorAll('a')
|
||||||
|
|
||||||
|
for (var i = 0; i < anchors.length; i++) {
|
||||||
|
anchors[i].removeEventListener('click', this.openExternal)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className={'MarkdownPreview' + (this.props.className != null ? ' ' + this.props.className : '')} dangerouslySetInnerHTML={{__html: ' ' + this.markdown(this.props.content)}}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
55
browser/main/Components/NoteDeleteModal.jsx
Normal file
55
browser/main/Components/NoteDeleteModal.jsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
var React = require('react')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var PlanetStore = require('../Stores/PlanetStore')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [KeyCaster('noteDeleteModal')],
|
||||||
|
propTypes: {
|
||||||
|
planet: React.PropTypes.object,
|
||||||
|
note: React.PropTypes.object,
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'submitNoteDeleteModal':
|
||||||
|
this.submit()
|
||||||
|
break
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit: function () {
|
||||||
|
var planet = this.props.planet
|
||||||
|
Hq.destroyNote(planet.userName, planet.name, this.props.note.localId)
|
||||||
|
.then(function (res) {
|
||||||
|
PlanetStore.Actions.destroyNote(res.body)
|
||||||
|
this.props.close()
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className='NoteDeleteModal modal'>
|
||||||
|
<div className='modal-header'>
|
||||||
|
<h1>Delete Note</h1>
|
||||||
|
</div>
|
||||||
|
<div className='modal-body'>
|
||||||
|
<p>Are you sure to delete it?</p>
|
||||||
|
</div>
|
||||||
|
<div className='modal-footer'>
|
||||||
|
<div className='modal-control'>
|
||||||
|
<button onClick={this.props.close} className='btn-default'>Cancel</button>
|
||||||
|
<button ref='submit' onClick={this.submit} className='btn-primary'>Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
27
browser/main/Components/NoteEditModal.jsx
Normal file
27
browser/main/Components/NoteEditModal.jsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
var React = require('react')
|
||||||
|
|
||||||
|
var NoteForm = require('./NoteForm')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
close: React.PropTypes.func,
|
||||||
|
note: React.PropTypes.object,
|
||||||
|
planet: React.PropTypes.object
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
// TODO: Hacked!! should fix later
|
||||||
|
setTimeout(function () {
|
||||||
|
React.findDOMNode(this.refs.form.refs.title).focus()
|
||||||
|
}.bind(this), 1)
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className='NoteEditModal modal'>
|
||||||
|
<div className='modal-header'>
|
||||||
|
<h1>Edit Note</h1>
|
||||||
|
</div>
|
||||||
|
<NoteForm ref='form' note={this.props.note} planet={this.props.planet} close={this.props.close}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
153
browser/main/Components/NoteForm.jsx
Normal file
153
browser/main/Components/NoteForm.jsx
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
var Select = require('react-select')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var LinkedState = require('../Mixins/LinkedState')
|
||||||
|
var Markdown = require('../Mixins/Markdown')
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var PlanetStore = require('../Stores/PlanetStore')
|
||||||
|
|
||||||
|
var CodeEditor = require('./CodeEditor')
|
||||||
|
var MarkdownPreview = require('./MarkdownPreview')
|
||||||
|
|
||||||
|
var getOptions = function (input, callback) {
|
||||||
|
Hq.searchTag(input)
|
||||||
|
.then(function (res) {
|
||||||
|
callback(null, {
|
||||||
|
options: res.body.map(function (tag) {
|
||||||
|
return {
|
||||||
|
label: tag.name,
|
||||||
|
value: tag.name
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
complete: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var EDIT_MODE = 0
|
||||||
|
var PREVIEW_MODE = 1
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [LinkedState, Markdown, KeyCaster('noteForm')],
|
||||||
|
propTypes: {
|
||||||
|
planet: React.PropTypes.object,
|
||||||
|
close: React.PropTypes.func,
|
||||||
|
transitionTo: React.PropTypes.func,
|
||||||
|
note: React.PropTypes.object
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
var note = Object.assign({
|
||||||
|
title: '',
|
||||||
|
content: '',
|
||||||
|
Tags: []
|
||||||
|
}, this.props.note)
|
||||||
|
note.Tags = note.Tags.map(function (tag) {
|
||||||
|
return {
|
||||||
|
label: tag.name,
|
||||||
|
value: tag.name
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
note: note,
|
||||||
|
mode: EDIT_MODE
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'submitNoteForm':
|
||||||
|
this.submit()
|
||||||
|
break
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleTagsChange: function (selected, all) {
|
||||||
|
var note = this.state.note
|
||||||
|
note.Tags = all
|
||||||
|
this.setState({note: note})
|
||||||
|
},
|
||||||
|
handleContentChange: function (e, value) {
|
||||||
|
var note = this.state.note
|
||||||
|
note.content = value
|
||||||
|
this.setState({note: note})
|
||||||
|
},
|
||||||
|
togglePreview: function () {
|
||||||
|
this.setState({mode: this.state.mode === EDIT_MODE ? PREVIEW_MODE : EDIT_MODE})
|
||||||
|
},
|
||||||
|
submit: function () {
|
||||||
|
var planet = this.props.planet
|
||||||
|
var note = this.state.note
|
||||||
|
note.Tags = note.Tags.map(function (tag) {
|
||||||
|
return tag.value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.props.note == null) {
|
||||||
|
Hq.createNote(planet.userName, planet.name, this.state.note)
|
||||||
|
.then(function (res) {
|
||||||
|
var note = res.body
|
||||||
|
PlanetStore.Actions.updateNote(note)
|
||||||
|
this.props.close()
|
||||||
|
this.props.transitionTo('notes', {userName: planet.userName, planetName: planet.name, localId: note.localId})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Hq.updateNote(planet.userName, planet.name, this.props.note.localId, this.state.note)
|
||||||
|
.then(function (res) {
|
||||||
|
var note = res.body
|
||||||
|
PlanetStore.Actions.updateNote(note)
|
||||||
|
this.props.close()
|
||||||
|
}.bind(this))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var content = this.state.mode === EDIT_MODE ? (
|
||||||
|
<div className='form-group'>
|
||||||
|
<CodeEditor onChange={this.handleContentChange} code={this.state.note.content} mode={'markdown'}/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className='form-group relative'>
|
||||||
|
<div className='previewMode'>Preview mode</div>
|
||||||
|
<MarkdownPreview className='marked' content={this.state.note.content}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='NoteForm'>
|
||||||
|
<div className='modal-body'>
|
||||||
|
<div className='form-group'>
|
||||||
|
<input ref='title' className='block-input' valueLink={this.linkState('note.title')} placeholder='Title'/>
|
||||||
|
</div>
|
||||||
|
{content}
|
||||||
|
<div className='form-group'>
|
||||||
|
<Select
|
||||||
|
name='Tags'
|
||||||
|
multi={true}
|
||||||
|
allowCreate={true}
|
||||||
|
value={this.state.note.Tags}
|
||||||
|
placeholder='Tags...'
|
||||||
|
asyncOptions={getOptions}
|
||||||
|
onChange={this.handleTagsChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='modal-footer'>
|
||||||
|
<button onClick={this.togglePreview} className={'btn-default' + (this.state.mode === PREVIEW_MODE ? ' active' : '')}>Preview mode</button>
|
||||||
|
<div className='modal-control'>
|
||||||
|
<button onClick={this.props.close} className='btn-default'>Cancel</button>
|
||||||
|
<button onClick={this.submit} className='btn-primary'>Launch</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
126
browser/main/Components/PlanetArticleDetail.jsx
Normal file
126
browser/main/Components/PlanetArticleDetail.jsx
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
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-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.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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
102
browser/main/Components/PlanetArticleList.jsx
Normal file
102
browser/main/Components/PlanetArticleList.jsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
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()
|
||||||
|
|
||||||
|
document.getElementById('articleEditButton').focus()
|
||||||
|
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()
|
||||||
|
|
||||||
|
document.getElementById('articleEditButton').focus()
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
111
browser/main/Components/PlanetCreateModal.jsx
Normal file
111
browser/main/Components/PlanetCreateModal.jsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/* global localStorage */
|
||||||
|
|
||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var LinkedState = require('../Mixins/LinkedState')
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var PlanetStore = require('../Stores/PlanetStore')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [LinkedState, KeyCaster('planetCreateModal')],
|
||||||
|
propTypes: {
|
||||||
|
ownerName: React.PropTypes.string,
|
||||||
|
transitionTo: React.PropTypes.func,
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||||
|
var ownerName = this.props.ownerName != null ? this.props.ownerName : currentUser.name
|
||||||
|
return {
|
||||||
|
user: currentUser,
|
||||||
|
planet: {
|
||||||
|
name: '',
|
||||||
|
public: true
|
||||||
|
},
|
||||||
|
ownerName: ownerName,
|
||||||
|
error: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
React.findDOMNode(this.refs.name).focus()
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
case 'submitPlanetCreateModal':
|
||||||
|
this.handleSubmit()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSubmit: function () {
|
||||||
|
this.setState({error: null}, function () {
|
||||||
|
Hq.createPlanet(this.state.ownerName, this.state.planet)
|
||||||
|
.then(function (res) {
|
||||||
|
var planet = res.body
|
||||||
|
|
||||||
|
PlanetStore.Actions.update(planet)
|
||||||
|
|
||||||
|
if (this.props.transitionTo != null) {
|
||||||
|
this.props.transitionTo('planetHome', {userName: planet.userName, planetName: planet.name})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.close()
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
|
||||||
|
if (err.status == null) return this.setState({error: {message: 'Check your network connection'}})
|
||||||
|
|
||||||
|
switch (err.status) {
|
||||||
|
case 403:
|
||||||
|
this.setState({error: err.response.body})
|
||||||
|
break
|
||||||
|
case 422:
|
||||||
|
this.setState({error: {message: 'Planet name should be Alphanumeric with _, -'}})
|
||||||
|
break
|
||||||
|
case 409:
|
||||||
|
this.setState({error: {message: 'The entered name already in use'}})
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this.setState({error: {message: 'Undefined error please try again'}})
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var teamOptions = this.state.user.Teams.map(function (team) {
|
||||||
|
return (
|
||||||
|
<option key={'user-' + team.id} value={team.name}>{team.profileName} ({team.name})</option>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div className='PlanetCreateModal modal'>
|
||||||
|
<input ref='name' valueLink={this.linkState('planet.name')} className='nameInput stripInput' placeholder='Crate new Planet'/>
|
||||||
|
|
||||||
|
<div className='formField'>
|
||||||
|
of
|
||||||
|
<select valueLink={this.linkState('ownerName')}>
|
||||||
|
<option value={this.state.user.name}>Me({this.state.user.name})</option>
|
||||||
|
{teamOptions}
|
||||||
|
</select>
|
||||||
|
as
|
||||||
|
<select valueLink={this.linkState('planet.public')}>
|
||||||
|
<option value={true}>Public</option>
|
||||||
|
<option value={false}>Private</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{this.state.error != null ? (<p className='errorAlert'>{this.state.error.message != null ? this.state.error.message : 'Error message undefined'}</p>) : null}
|
||||||
|
|
||||||
|
<button onClick={this.handleSubmit} className='submitButton'>
|
||||||
|
<i className='fa fa-check'/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
86
browser/main/Components/PlanetHeader.jsx
Normal file
86
browser/main/Components/PlanetHeader.jsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
var ReactRouter = require('react-router')
|
||||||
|
var Link = ReactRouter.Link
|
||||||
|
|
||||||
|
var Modal = require('../Mixins/Modal')
|
||||||
|
var ExternalLink = require('../Mixins/ExternalLink')
|
||||||
|
|
||||||
|
var PlanetSettingModal = require('./PlanetSettingModal')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [ReactRouter.State, Modal, ExternalLink],
|
||||||
|
propTypes: {
|
||||||
|
search: React.PropTypes.string,
|
||||||
|
fetchPlanet: React.PropTypes.func,
|
||||||
|
onSearchChange: React.PropTypes.func,
|
||||||
|
currentPlanet: React.PropTypes.object
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
search: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
var search = React.findDOMNode(this.refs.search)
|
||||||
|
search.addEventListener('keydown', this.handleSearchKeyDown)
|
||||||
|
},
|
||||||
|
componentWillUnmount: function () {
|
||||||
|
var search = React.findDOMNode(this.refs.search)
|
||||||
|
search.removeEventListener('keydown', this.handleSearchKeyDown)
|
||||||
|
},
|
||||||
|
handleSearchKeyDown: function (e) {
|
||||||
|
if (e.keyCode === 38 || e.keyCode === 40) {
|
||||||
|
var search = React.findDOMNode(this.refs.search)
|
||||||
|
search.blur()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
if (e.keyCode !== 27 && (e.keyCode !== 13 || !e.metaKey)) {
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openPlanetSettingModal: function () {
|
||||||
|
this.openModal(PlanetSettingModal, {planet: this.props.currentPlanet})
|
||||||
|
},
|
||||||
|
refresh: function () {
|
||||||
|
this.props.fetchPlanet()
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var currentPlanetName = this.props.currentPlanet.name
|
||||||
|
var currentUserName = this.props.currentPlanet.userName
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='PlanetHeader'>
|
||||||
|
<div className='headerLabel'>
|
||||||
|
<Link to='userHome' params={{userName: currentUserName}} className='userName'>{currentUserName}</Link>
|
||||||
|
<span className='planetName'>{currentPlanetName}</span>
|
||||||
|
|
||||||
|
{this.props.currentPlanet.public ? null : (
|
||||||
|
<div className='private'>
|
||||||
|
<i className='fa fa-lock'/>
|
||||||
|
<div className='tooltip'>Private planet</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button onClick={this.openPlanetSettingModal} className='planetSettingButton'>
|
||||||
|
<i className='fa fa-chevron-down'></i>
|
||||||
|
<div className='tooltip'>Planet setting</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className='headerControl'>
|
||||||
|
<div className='searchInput'>
|
||||||
|
<i className='fa fa-search'/>
|
||||||
|
<input onChange={this.props.onSearchChange} value={this.props.search} ref='search' type='text' className='inline-input circleInput' placeholder='Search...'/>
|
||||||
|
</div>
|
||||||
|
<button onClick={this.refresh} className='refreshButton'>
|
||||||
|
<i className='fa fa-refresh'/>
|
||||||
|
<div className='tooltip'>Refresh planet</div>
|
||||||
|
</button>
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
76
browser/main/Components/PlanetNavigator.jsx
Normal file
76
browser/main/Components/PlanetNavigator.jsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
var ReactRouter = require('react-router')
|
||||||
|
var Navigation = ReactRouter.Navigation
|
||||||
|
|
||||||
|
var Modal = require('../Mixins/Modal')
|
||||||
|
|
||||||
|
var LaunchModal = require('../Components/LaunchModal')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [Modal, Navigation],
|
||||||
|
propTypes: {
|
||||||
|
planet: React.PropTypes.shape({
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
Owner: React.PropTypes.shape({
|
||||||
|
id: React.PropTypes.number,
|
||||||
|
userType: React.PropTypes.string
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
search: React.PropTypes.string,
|
||||||
|
toggleCodeFilter: React.PropTypes.func,
|
||||||
|
toggleNoteFilter: React.PropTypes.func,
|
||||||
|
currentUser: React.PropTypes.shape({
|
||||||
|
id: React.PropTypes.number,
|
||||||
|
userType: React.PropTypes.string,
|
||||||
|
Teams: React.PropTypes.array
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
isLaunchModalOpen: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openLaunchModal: function () {
|
||||||
|
this.openModal(LaunchModal, {planet: this.props.planet, transitionTo: this.transitionTo})
|
||||||
|
},
|
||||||
|
isMyPlanet: function () {
|
||||||
|
if (this.props.currentUser == null) return false
|
||||||
|
if (this.props.planet.Owner.userType === 'person' && this.props.planet.Owner.id !== this.props.currentUser.id) return false
|
||||||
|
if (this.props.planet.Owner.userType === 'team' && !this.props.currentUser.Teams.some(function (team) {
|
||||||
|
if (team.id === this.props.planet.Owner.id) return true
|
||||||
|
return false
|
||||||
|
}.bind(this))) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var keywords = this.props.search.split(' ')
|
||||||
|
var usingCodeFilter = keywords.some(function (keyword) {
|
||||||
|
if (keyword === '$c') return true
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
var usingNoteFilter = keywords.some(function (keyword) {
|
||||||
|
if (keyword === '$n') return true
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='PlanetNavigator'>
|
||||||
|
{this.isMyPlanet() ? (
|
||||||
|
<button onClick={this.openLaunchModal} className='launchButton btn-primary btn-block'>
|
||||||
|
<i className='fa fa-rocket fa-fw'/> Launch
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
|
<nav className='articleFilters'>
|
||||||
|
<a className={usingCodeFilter && !usingNoteFilter ? 'active' : ''} onClick={this.props.toggleCodeFilter}>
|
||||||
|
<i className='fa fa-code fa-fw'/> Codes
|
||||||
|
</a>
|
||||||
|
<a className={!usingCodeFilter && usingNoteFilter ? 'active' : ''} onClick={this.props.toggleNoteFilter}>
|
||||||
|
<i className='fa fa-file-text-o fa-fw'/> Notes
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
162
browser/main/Components/PlanetSettingModal.jsx
Normal file
162
browser/main/Components/PlanetSettingModal.jsx
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var LinkedState = require('../Mixins/LinkedState')
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var PlanetStore = require('../Stores/PlanetStore')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [LinkedState, KeyCaster('planetSettingModal')],
|
||||||
|
propTypes: {
|
||||||
|
close: React.PropTypes.func,
|
||||||
|
planet: React.PropTypes.shape({
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
public: React.PropTypes.bool,
|
||||||
|
userName: React.PropTypes.string
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
var deleteTextCandidates = [
|
||||||
|
'Confirm',
|
||||||
|
'Exterminatus',
|
||||||
|
'Avada Kedavra'
|
||||||
|
]
|
||||||
|
var random = Math.round(Math.random() * 10) % 10
|
||||||
|
var randomDeleteText = random > 1 ? deleteTextCandidates[0] : random === 1 ? deleteTextCandidates[1] : deleteTextCandidates[2]
|
||||||
|
|
||||||
|
return {
|
||||||
|
currentTab: 'profile',
|
||||||
|
planet: {
|
||||||
|
name: this.props.planet.name,
|
||||||
|
public: this.props.planet.public
|
||||||
|
},
|
||||||
|
randomDeleteText: randomDeleteText,
|
||||||
|
deleteConfirmation: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
activePlanetProfile: function () {
|
||||||
|
this.setState({currentTab: 'profile'})
|
||||||
|
},
|
||||||
|
activePlanetDelete: function () {
|
||||||
|
this.setState({currentTab: 'delete'})
|
||||||
|
},
|
||||||
|
handlePublicChange: function (value) {
|
||||||
|
return function () {
|
||||||
|
this.state.planet.public = value
|
||||||
|
this.setState({planet: this.state.planet})
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
handleSavePlanetProfile: function (e) {
|
||||||
|
var planet = this.props.planet
|
||||||
|
|
||||||
|
this.setState({profileSubmitStatus: 'sending'}, function () {
|
||||||
|
Hq.updatePlanet(planet.userName, planet.name, this.state.planet)
|
||||||
|
.then(function (res) {
|
||||||
|
var planet = res.body
|
||||||
|
|
||||||
|
this.setState({profileSubmitStatus: 'done'})
|
||||||
|
|
||||||
|
PlanetStore.Actions.update(planet)
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
this.setState({profileSubmitStatus: 'error'})
|
||||||
|
console.error(err)
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleDeletePlanetClick: function () {
|
||||||
|
var planet = this.props.planet
|
||||||
|
|
||||||
|
this.setState({deleteSubmitStatus: 'sending'}, function () {
|
||||||
|
Hq.destroyPlanet(planet.userName, planet.name)
|
||||||
|
.then(function (res) {
|
||||||
|
var planet = res.body
|
||||||
|
|
||||||
|
PlanetStore.Actions.destroy(planet)
|
||||||
|
this.setState({deleteSubmitStatus: 'done'}, function () {
|
||||||
|
this.props.close()
|
||||||
|
})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
this.setState({deleteSubmitStatus: 'error'})
|
||||||
|
console.error(err)
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var content
|
||||||
|
|
||||||
|
content = this.state.currentTab === 'profile' ? this.renderPlanetProfileTab() : this.renderPlanetDeleteTab()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='PlanetSettingModal modal tabModal'>
|
||||||
|
<div className='leftPane'>
|
||||||
|
<h1 className='tabLabel'>Planet setting</h1>
|
||||||
|
<nav className='tabList'>
|
||||||
|
<button onClick={this.activePlanetProfile} className={this.state.currentTab === 'profile' ? 'active' : ''}><i className='fa fa-globe fa-fw'/> Planet profile</button>
|
||||||
|
<button onClick={this.activePlanetDelete} className={this.state.currentTab === 'delete' ? 'active' : ''}><i className='fa fa-trash fa-fw'/> Delete Planet</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='rightPane'>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderPlanetProfileTab: function () {
|
||||||
|
return (
|
||||||
|
<div className='planetProfileTab'>
|
||||||
|
<div className='formField'>
|
||||||
|
<label>Planet name </label>
|
||||||
|
<input valueLink={this.linkState('planet.name')}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='formRadioField'>
|
||||||
|
<input id='publicOption' checked={this.state.planet.public} onChange={this.handlePublicChange(true)} name='public' type='radio'/> <label htmlFor='publicOption'>Public</label>
|
||||||
|
|
||||||
|
<input id='privateOption' checked={!this.state.planet.public} onChange={this.handlePublicChange(false)} name='public' type='radio'/> <label htmlFor='privateOption'>Private</label>
|
||||||
|
</div>
|
||||||
|
<div className='formConfirm'>
|
||||||
|
<button onClick={this.handleSavePlanetProfile} className='saveButton btn-primary'>Save</button>
|
||||||
|
|
||||||
|
<div className={'alertInfo' + (this.state.profileSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
||||||
|
|
||||||
|
<div className={'alertError' + (this.state.profileSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
||||||
|
|
||||||
|
<div className={'alertSuccess' + (this.state.profileSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderPlanetDeleteTab: function () {
|
||||||
|
var disabled = !this.state.deleteConfirmation.match(new RegExp('^' + this.props.planet.userName + '/' + this.props.planet.name + '$'))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='planetDeleteTab'>
|
||||||
|
<p>Are you sure to destroy <strong>'{this.props.planet.userName + '/' + this.props.planet.name}'</strong>?</p>
|
||||||
|
<p>If you are sure, write <strong>'{this.props.planet.userName + '/' + this.props.planet.name}'</strong> to input below and click <strong>'{this.state.randomDeleteText}'</strong> button.</p>
|
||||||
|
<input valueLink={this.linkState('deleteConfirmation')} placeholder='userName/planetName'/>
|
||||||
|
<div className='formConfirm'>
|
||||||
|
<button disabled={disabled} onClick={this.handleDeletePlanetClick}>{this.state.randomDeleteText}</button>
|
||||||
|
|
||||||
|
<div className={'alertInfo' + (this.state.deleteSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
||||||
|
|
||||||
|
<div className={'alertError' + (this.state.deleteSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
||||||
|
|
||||||
|
<div className={'alertSuccess' + (this.state.deleteSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
15
browser/main/Components/ProfileImage.jsx
Normal file
15
browser/main/Components/ProfileImage.jsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
var md5 = require('md5')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
propTypes: {
|
||||||
|
email: React.PropTypes.string,
|
||||||
|
size: React.PropTypes.string,
|
||||||
|
className: React.PropTypes.string
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<img className={this.props.className} width={this.props.size} height={this.props.size} src={'http://www.gravatar.com/avatar/' + md5(this.props.email.trim().toLowerCase()) + '?s=' + this.props.size}/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
87
browser/main/Components/TeamCreateModal.jsx
Normal file
87
browser/main/Components/TeamCreateModal.jsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/* global localStorage */
|
||||||
|
|
||||||
|
var React = require('react/addons')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var LinkedState = require('../Mixins/LinkedState')
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var UserStore = require('../Stores/UserStore')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [LinkedState, KeyCaster('teamCreateModal')],
|
||||||
|
propTypes: {
|
||||||
|
user: React.PropTypes.shape({
|
||||||
|
name: React.PropTypes.string
|
||||||
|
}),
|
||||||
|
transitionTo: React.PropTypes.func,
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
team: {
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
error: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
React.findDOMNode(this.refs.teamName).focus()
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
case 'submitTeamCreateModal':
|
||||||
|
this.handleSubmit()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSubmit: function () {
|
||||||
|
this.setState({error: null}, function () {
|
||||||
|
Hq.createTeam(this.props.user.name, this.state.team)
|
||||||
|
.then(function (res) {
|
||||||
|
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||||
|
var team = res.body
|
||||||
|
|
||||||
|
currentUser.Teams.push(team)
|
||||||
|
localStorage.setItem('currentUser', JSON.stringify(currentUser))
|
||||||
|
UserStore.Actions.update(currentUser)
|
||||||
|
|
||||||
|
if (this.props.transitionTo != null) {
|
||||||
|
this.props.transitionTo('userHome', {userName: team.name})
|
||||||
|
}
|
||||||
|
this.props.close()
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
|
||||||
|
if (err.status == null) return this.setState({error: {message: 'Check your network connection'}})
|
||||||
|
|
||||||
|
switch (err.status) {
|
||||||
|
case 422:
|
||||||
|
this.setState({error: {message: 'Team name should be Alphanumeric with _, -'}})
|
||||||
|
break
|
||||||
|
case 409:
|
||||||
|
this.setState({error: {message: 'The entered name already in use'}})
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
this.setState({error: {message: 'Error message undefined'}})
|
||||||
|
}
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
return (
|
||||||
|
<div className='TeamCreateModal modal'>
|
||||||
|
<input ref='teamName' valueLink={this.linkState('team.name')} className='nameInput stripInput' placeholder='Create new team'/>
|
||||||
|
{this.state.error != null ? (<p className='errorAlert'>{this.state.error.message != null ? this.state.error.message : 'Unintended error occured'}</p>) : null}
|
||||||
|
<button onClick={this.handleSubmit} className='submitButton'>
|
||||||
|
<i className='fa fa-check'/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
282
browser/main/Components/TeamSettingsModal.jsx
Normal file
282
browser/main/Components/TeamSettingsModal.jsx
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
/* global localStorage */
|
||||||
|
|
||||||
|
var React = require('react/addons')
|
||||||
|
var Reflux = require('reflux')
|
||||||
|
var Select = require('react-select')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var LinkedState = require('../Mixins/LinkedState')
|
||||||
|
var Helper = require('../Mixins/Helper')
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var UserStore = require('../Stores/UserStore')
|
||||||
|
|
||||||
|
var getOptions = function (input, callback) {
|
||||||
|
Hq.searchUser(input)
|
||||||
|
.then(function (res) {
|
||||||
|
callback(null, {
|
||||||
|
options: res.body.map(function (user) {
|
||||||
|
return {
|
||||||
|
label: user.name,
|
||||||
|
value: user.name
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
complete: false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [LinkedState, Reflux.listenTo(UserStore, 'onUserChange'), Helper, KeyCaster('teamSettingsModal')],
|
||||||
|
propTypes: {
|
||||||
|
team: React.PropTypes.shape({
|
||||||
|
id: React.PropTypes.number,
|
||||||
|
name: React.PropTypes.string,
|
||||||
|
profileName: React.PropTypes.string,
|
||||||
|
email: React.PropTypes.string,
|
||||||
|
Members: React.PropTypes.array
|
||||||
|
}),
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
var team = this.props.team
|
||||||
|
return {
|
||||||
|
currentTab: 'teamInfo',
|
||||||
|
team: {
|
||||||
|
profileName: team.profileName
|
||||||
|
},
|
||||||
|
userSubmitStatus: null,
|
||||||
|
member: {
|
||||||
|
name: '',
|
||||||
|
role: 'member'
|
||||||
|
},
|
||||||
|
updatingMember: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'closeModal':
|
||||||
|
this.props.close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUserChange: function (res) {
|
||||||
|
var member
|
||||||
|
switch (res.status) {
|
||||||
|
case 'memberAdded':
|
||||||
|
member = res.data
|
||||||
|
if (member.TeamMember.TeamId === this.props.team.id) {
|
||||||
|
this.forceUpdate()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'memberRemoved':
|
||||||
|
member = res.data
|
||||||
|
if (member.TeamMember.TeamId === this.props.team.id) {
|
||||||
|
this.forceUpdate()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectTab: function (tabName) {
|
||||||
|
return function () {
|
||||||
|
this.setState({currentTab: tabName})
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
saveUserInfo: function () {
|
||||||
|
this.setState({
|
||||||
|
userSubmitStatus: 'sending'
|
||||||
|
}, function () {
|
||||||
|
Hq.updateUser(this.props.team.name, this.state.team)
|
||||||
|
.then(function (res) {
|
||||||
|
this.setState({userSubmitStatus: 'done'}, function () {
|
||||||
|
UserStore.Actions.update(res.body)
|
||||||
|
this.forceUpdate()
|
||||||
|
})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
this.setState({userSubmitStatus: 'error'})
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
handleMemberNameChange: function (value) {
|
||||||
|
var member = this.state.member
|
||||||
|
member.name = value
|
||||||
|
this.setState({member: member})
|
||||||
|
},
|
||||||
|
addMember: function () {
|
||||||
|
this.setState({updatingMember: true}, function () {
|
||||||
|
Hq
|
||||||
|
.addMember(this.props.team.name, {
|
||||||
|
userName: this.state.member.name,
|
||||||
|
role: this.state.member.role
|
||||||
|
})
|
||||||
|
.then(function (res) {
|
||||||
|
UserStore.Actions.addMember(res.body)
|
||||||
|
this.setState({updatingMember: false})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
this.setState({updatingMember: false})
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
roleChange: function (memberName) {
|
||||||
|
return function (e) {
|
||||||
|
var role = e.target.value
|
||||||
|
this.setState({updatingMember: true}, function () {
|
||||||
|
Hq
|
||||||
|
.addMember(this.props.team.name, {
|
||||||
|
userName: memberName,
|
||||||
|
role: role
|
||||||
|
})
|
||||||
|
.then(function (res) {
|
||||||
|
UserStore.Actions.addMember(res.body)
|
||||||
|
this.setState({updatingMember: false})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
this.setState({updatingMember: false})
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
removeMember: function (memberName) {
|
||||||
|
return function () {
|
||||||
|
this.setState({updatingMember: true}, function () {
|
||||||
|
Hq
|
||||||
|
.removeMember(this.props.team.name, {
|
||||||
|
userName: memberName
|
||||||
|
})
|
||||||
|
.then(function (res) {
|
||||||
|
UserStore.Actions.removeMember(res.body)
|
||||||
|
this.setState({updatingMember: false})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
this.setState({updatingMember: false})
|
||||||
|
}.bind(this))
|
||||||
|
})
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var content
|
||||||
|
|
||||||
|
switch (this.state.currentTab) {
|
||||||
|
case 'teamInfo':
|
||||||
|
content = this.renderTeamInfoTab()
|
||||||
|
break
|
||||||
|
case 'members':
|
||||||
|
content = this.renderMembersTab()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='TeamSettingsModal modal tabModal'>
|
||||||
|
<div className='leftPane'>
|
||||||
|
<div className='tabLabel'>Team settings</div>
|
||||||
|
<div className='tabList'>
|
||||||
|
<button className={this.state.currentTab === 'teamInfo' ? 'active' : ''} onClick={this.selectTab('teamInfo')}><i className='fa fa-info-circle fa-fw'/> Team Info</button>
|
||||||
|
<button className={this.state.currentTab === 'members' ? 'active' : ''} onClick={this.selectTab('members')}><i className='fa fa-users fa-fw'/> Members</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='rightPane'>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderTeamInfoTab: function () {
|
||||||
|
return (
|
||||||
|
<div className='userInfoTab'>
|
||||||
|
<div className='formField'>
|
||||||
|
<label>Profile Name</label>
|
||||||
|
<input valueLink={this.linkState('team.profileName')}/>
|
||||||
|
</div>
|
||||||
|
<div className='formConfirm'>
|
||||||
|
<button disabled={this.state.userSubmitStatus === 'sending'} onClick={this.saveUserInfo}>Save</button>
|
||||||
|
|
||||||
|
<div className={'alertInfo' + (this.state.userSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
||||||
|
|
||||||
|
<div className={'alertError' + (this.state.userSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
||||||
|
|
||||||
|
<div className={'alertSuccess' + (this.state.userSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
renderMembersTab: function () {
|
||||||
|
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||||
|
|
||||||
|
var members = this.props.team.Members.map(function (member) {
|
||||||
|
var isCurrentUser = currentUser.id === member.id
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<td>{member.profileName}({member.name})</td>
|
||||||
|
<td>
|
||||||
|
{isCurrentUser ? (
|
||||||
|
'Owner'
|
||||||
|
) : (
|
||||||
|
<select disabled={this.state.updatingMember} onChange={this.roleChange(member.name)} className='roleSelect' value={member.TeamMember.role}>
|
||||||
|
<option value='owner'>Owner</option>
|
||||||
|
<option value='member'>Member</option>
|
||||||
|
</select>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{isCurrentUser ? '-' : (
|
||||||
|
<button disabled={this.state.updatingMember} onClick={this.removeMember(member.name)}><i className='fa fa-close fa-fw'/></button>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
}.bind(this))
|
||||||
|
|
||||||
|
var belowLimit = members.length < 5
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='membersTab'>
|
||||||
|
<table className='memberTable'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Username</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Control</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{members}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{belowLimit ? (
|
||||||
|
<div className='addMemberForm'>
|
||||||
|
<div className='formLabel'>Add Member</div>
|
||||||
|
<div className='formGroup'>
|
||||||
|
<Select
|
||||||
|
name='userName'
|
||||||
|
value={this.state.member.name}
|
||||||
|
placeholder='Username to add'
|
||||||
|
asyncOptions={getOptions}
|
||||||
|
onChange={this.handleMemberNameChange}
|
||||||
|
className='userNameSelect'
|
||||||
|
/>
|
||||||
|
<select valueLink={this.linkState('member.role')} className='roleSelect'>
|
||||||
|
<option value={'member'}>Member</option>
|
||||||
|
<option value={'owner'}>Owner</option>
|
||||||
|
</select>
|
||||||
|
<button disabled={this.state.updatingMember} onClick={this.addMember} className='confirmButton'>Add Member</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
Maximum number of members is 5 on Beta version. Please contact us if you want futher use.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
41
browser/main/Containers/HomeContainer.jsx
Normal file
41
browser/main/Containers/HomeContainer.jsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/* 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
106
browser/main/Containers/LoginContainer.jsx
Normal file
106
browser/main/Containers/LoginContainer.jsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/* 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')
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
107
browser/main/Containers/MainContainer.jsx
Normal file
107
browser/main/Containers/MainContainer.jsx
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/* 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 Modal = require('../Mixins/Modal')
|
||||||
|
|
||||||
|
var UserStore = require('../Stores/UserStore')
|
||||||
|
|
||||||
|
var ContactModal = require('../Components/ContactModal')
|
||||||
|
|
||||||
|
function fetchPlanet (userName, planetName) {
|
||||||
|
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'
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('planet-' + planet.id + ' fetched!')
|
||||||
|
localStorage.setItem('planet-' + planet.id, JSON.stringify(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')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Hq.getUser()
|
||||||
|
.then(function (res) {
|
||||||
|
var user = res.body
|
||||||
|
localStorage.setItem('currentUser', JSON.stringify(user))
|
||||||
|
UserStore.Actions.update(user)
|
||||||
|
|
||||||
|
user.Planets.forEach(function (planet) {
|
||||||
|
fetchPlanet(planet.userName, planet.name)
|
||||||
|
})
|
||||||
|
user.Teams.forEach(function (team) {
|
||||||
|
team.Planets.forEach(function (planet) {
|
||||||
|
fetchPlanet(planet.userName, 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
415
browser/main/Containers/PlanetContainer.jsx
Normal file
415
browser/main/Containers/PlanetContainer.jsx
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
/* global localStorage*/
|
||||||
|
'strict'
|
||||||
|
var React = require('react/addons')
|
||||||
|
var ReactRouter = require('react-router')
|
||||||
|
var Reflux = require('reflux')
|
||||||
|
|
||||||
|
var PlanetHeader = require('../Components/PlanetHeader')
|
||||||
|
var PlanetNavigator = require('../Components/PlanetNavigator')
|
||||||
|
var PlanetArticleList = require('../Components/PlanetArticleList')
|
||||||
|
var PlanetArticleDetail = require('../Components/PlanetArticleDetail')
|
||||||
|
|
||||||
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
|
var Modal = require('../Mixins/Modal')
|
||||||
|
var ArticleFilter = require('../Mixins/ArticleFilter')
|
||||||
|
var Helper = require('../Mixins/Helper')
|
||||||
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
|
|
||||||
|
var UserStore = require('../Stores/UserStore')
|
||||||
|
var PlanetStore = require('../Stores/PlanetStore')
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
mixins: [ReactRouter.Navigation, ReactRouter.State, Modal, Reflux.listenTo(UserStore, 'onUserChange'), Reflux.listenTo(PlanetStore, 'onPlanetChange'), ArticleFilter, Helper, KeyCaster('planetContainer')],
|
||||||
|
propTypes: {
|
||||||
|
params: React.PropTypes.object,
|
||||||
|
planetName: React.PropTypes.string
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
currentUser: JSON.parse(localStorage.getItem('currentUser')),
|
||||||
|
planet: null,
|
||||||
|
search: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
|
this.fetchPlanet(this.props.params.userName, this.props.params.planetName)
|
||||||
|
},
|
||||||
|
componentDidUpdate: function () {
|
||||||
|
if (this.isActive('planetHome') && this.refs.list != null && this.refs.list.props.articles.length > 0) {
|
||||||
|
var article = this.refs.list.props.articles[0]
|
||||||
|
var planet = this.state.planet
|
||||||
|
switch (article.type) {
|
||||||
|
case 'code':
|
||||||
|
this.transitionTo('codes', {userName: planet.userName, planetName: planet.name, localId: article.localId})
|
||||||
|
break
|
||||||
|
case 'note':
|
||||||
|
this.transitionTo('notes', {userName: planet.userName, planetName: planet.name, localId: article.localId})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
componentWillReceiveProps: function (nextProps) {
|
||||||
|
if (this.state.planet == null) {
|
||||||
|
this.fetchPlanet(nextProps.params.userName, nextProps.params.planetName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextProps.params.userName !== this.state.planet.userName || nextProps.params.planetName !== this.state.planet.name) {
|
||||||
|
this.setState({
|
||||||
|
planet: null
|
||||||
|
}, function () {
|
||||||
|
this.fetchPlanet(nextProps.params.userName, nextProps.params.planetName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
|
switch (e.status) {
|
||||||
|
case 'openLaunchModal':
|
||||||
|
this.refs.navigator.openLaunchModal()
|
||||||
|
break
|
||||||
|
case 'selectNextArticle':
|
||||||
|
this.selectNextArticle()
|
||||||
|
break
|
||||||
|
case 'selectPriorArticle':
|
||||||
|
this.selectPriorArticle()
|
||||||
|
break
|
||||||
|
case 'toggleFocusSearchInput':
|
||||||
|
this.toggleFocusSearchInput()
|
||||||
|
break
|
||||||
|
case 'openEditModal':
|
||||||
|
this.refs.detail.openEditModal()
|
||||||
|
break
|
||||||
|
case 'openDeleteModal':
|
||||||
|
this.refs.detail.openDeleteModal()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPlanetChange: function (res) {
|
||||||
|
if (this.state.planet == null) return
|
||||||
|
|
||||||
|
var planet, code, note, articleIndex, articlesCount
|
||||||
|
switch (res.status) {
|
||||||
|
case 'updated':
|
||||||
|
planet = res.data
|
||||||
|
if (this.state.planet.id === planet.id) {
|
||||||
|
if (this.state.planet.name === planet.name) {
|
||||||
|
this.setState({planet: planet})
|
||||||
|
} else {
|
||||||
|
this.transitionTo('planetHome', {userName: planet.userName, planetName: planet.name})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'destroyed':
|
||||||
|
planet = res.data
|
||||||
|
if (this.state.planet.id === planet.id) {
|
||||||
|
this.transitionTo('userHome', {userName: this.state.planet.userName})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'codeUpdated':
|
||||||
|
code = res.data
|
||||||
|
if (code.PlanetId === this.state.planet.id) {
|
||||||
|
this.state.planet.Codes = this.updateItemToTargetArray(code, this.state.planet.Codes)
|
||||||
|
|
||||||
|
this.setState({planet: this.state.planet})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'noteUpdated':
|
||||||
|
note = res.data
|
||||||
|
if (note.PlanetId === this.state.planet.id) {
|
||||||
|
this.state.planet.Notes = this.updateItemToTargetArray(note, this.state.planet.Notes)
|
||||||
|
|
||||||
|
this.setState({planet: this.state.planet})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'codeDestroyed':
|
||||||
|
code = res.data
|
||||||
|
if (code.PlanetId === this.state.planet.id) {
|
||||||
|
this.state.planet.Codes = this.deleteItemFromTargetArray(code, this.state.planet.Codes)
|
||||||
|
|
||||||
|
if (this.refs.detail.props.article != null && this.refs.detail.props.article.type === code.type && this.refs.detail.props.article.localId === code.localId) {
|
||||||
|
articleIndex = this.getFilteredIndexOfCurrentArticle()
|
||||||
|
articlesCount = this.refs.list.props.articles.length
|
||||||
|
|
||||||
|
this.setState({planet: this.state.planet}, function () {
|
||||||
|
if (articlesCount > 1) {
|
||||||
|
if (articleIndex > 0) {
|
||||||
|
this.selectArticleByListIndex(articleIndex - 1)
|
||||||
|
} else {
|
||||||
|
this.selectArticleByListIndex(articleIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({planet: this.state.planet})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'noteDestroyed':
|
||||||
|
note = res.data
|
||||||
|
if (note.PlanetId === this.state.planet.id) {
|
||||||
|
this.state.planet.Notes = this.deleteItemFromTargetArray(note, this.state.planet.Notes)
|
||||||
|
|
||||||
|
if (this.refs.detail.props.article != null && this.refs.detail.props.article.type === note.type && this.refs.detail.props.article.localId === note.localId) {
|
||||||
|
articleIndex = this.getFilteredIndexOfCurrentArticle()
|
||||||
|
articlesCount = this.refs.list.props.articles.length
|
||||||
|
|
||||||
|
this.setState({planet: this.state.planet}, function () {
|
||||||
|
if (articlesCount > 1) {
|
||||||
|
if (articleIndex > 0) {
|
||||||
|
this.selectArticleByListIndex(articleIndex - 1)
|
||||||
|
} else {
|
||||||
|
this.selectArticleByListIndex(articleIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({planet: this.state.planet})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onUserChange: function () {
|
||||||
|
|
||||||
|
},
|
||||||
|
fetchPlanet: function (userName, planetName) {
|
||||||
|
if (userName == null) userName = this.props.params.userName
|
||||||
|
if (planetName == null) planetName = this.props.params.planetName
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
this.setState({planet: planet})
|
||||||
|
}.bind(this))
|
||||||
|
.catch(function (err) {
|
||||||
|
console.error(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getFilteredIndexOfCurrentArticle: function () {
|
||||||
|
var params = this.props.params
|
||||||
|
var index = 0
|
||||||
|
|
||||||
|
if (this.isActive('codes')) {
|
||||||
|
this.refs.list.props.articles.some(function (_article, _index) {
|
||||||
|
if (_article.type === 'code' && _article.localId === parseInt(params.localId, 10)) {
|
||||||
|
index = _index
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else if (this.isActive('notes')) {
|
||||||
|
this.refs.list.props.articles.some(function (_article, _index) {
|
||||||
|
if (_article.type === 'note' && _article.localId === parseInt(params.localId, 10)) {
|
||||||
|
index = _index
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return index
|
||||||
|
},
|
||||||
|
selectArticleByListIndex: function (index) {
|
||||||
|
var article = this.refs.list.props.articles[index]
|
||||||
|
var params = this.props.params
|
||||||
|
|
||||||
|
if (article == null) {
|
||||||
|
this.transitionTo('planetHome', params)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var listElement = this.refs.list.refs.articles.getDOMNode()
|
||||||
|
var articleElement = listElement.querySelectorAll('li')[index]
|
||||||
|
|
||||||
|
var overflowBelow = listElement.clientHeight + listElement.scrollTop < articleElement.offsetTop + articleElement.clientHeight
|
||||||
|
if (overflowBelow) {
|
||||||
|
listElement.scrollTop = articleElement.offsetTop + articleElement.clientHeight - listElement.clientHeight
|
||||||
|
}
|
||||||
|
var overflowAbove = listElement.scrollTop > articleElement.offsetTop
|
||||||
|
if (overflowAbove) {
|
||||||
|
listElement.scrollTop = articleElement.offsetTop
|
||||||
|
}
|
||||||
|
|
||||||
|
if (article.type === 'code') {
|
||||||
|
params.localId = article.localId
|
||||||
|
this.transitionTo('codes', params)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (article.type === 'note') {
|
||||||
|
params.localId = article.localId
|
||||||
|
this.transitionTo('notes', params)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectNextArticle: function () {
|
||||||
|
if (this.state.planet == null) return
|
||||||
|
|
||||||
|
var index = this.getFilteredIndexOfCurrentArticle()
|
||||||
|
|
||||||
|
if (index < this.refs.list.props.articles.length - 1) {
|
||||||
|
this.selectArticleByListIndex(index + 1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectPriorArticle: function () {
|
||||||
|
if (this.state.planet == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var index = this.getFilteredIndexOfCurrentArticle()
|
||||||
|
|
||||||
|
if (index > 0) {
|
||||||
|
this.selectArticleByListIndex(index - 1)
|
||||||
|
} else {
|
||||||
|
React.findDOMNode(this.refs.header.refs.search).focus()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
toggleFocusSearchInput: function () {
|
||||||
|
var search = React.findDOMNode(this.refs.header.refs.search)
|
||||||
|
if (document.activeElement === search) {
|
||||||
|
React.findDOMNode(this.refs.header.refs.search).blur()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
React.findDOMNode(this.refs.header.refs.search).focus()
|
||||||
|
},
|
||||||
|
handleSearchChange: function (e) {
|
||||||
|
this.setState({search: e.target.value}, function () {
|
||||||
|
this.selectArticleByListIndex(0)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
showAll: function () {
|
||||||
|
this.setState({search: ''})
|
||||||
|
},
|
||||||
|
toggleCodeFilter: function () {
|
||||||
|
var keywords = typeof this.state.search === 'string' ? this.state.search.split(' ') : []
|
||||||
|
|
||||||
|
var usingCodeFilter = false
|
||||||
|
var usingNoteFilter = false
|
||||||
|
keywords = keywords.filter(function (keyword) {
|
||||||
|
if (keyword === '$n') {
|
||||||
|
usingNoteFilter = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (keyword === '$c') usingCodeFilter = true
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (usingCodeFilter && !usingNoteFilter) {
|
||||||
|
keywords = keywords.filter(function (keyword) {
|
||||||
|
return keyword !== '$c'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usingCodeFilter) {
|
||||||
|
keywords.unshift('$c')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({search: keywords.join(' ')}, function () {
|
||||||
|
this.selectArticleByListIndex(0)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
toggleNoteFilter: function () {
|
||||||
|
var keywords = typeof this.state.search === 'string' ? this.state.search.split(' ') : []
|
||||||
|
|
||||||
|
var usingCodeFilter = false
|
||||||
|
var usingNoteFilter = false
|
||||||
|
keywords = keywords.filter(function (keyword) {
|
||||||
|
if (keyword === '$c') {
|
||||||
|
usingCodeFilter = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (keyword === '$n') usingNoteFilter = true
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (usingNoteFilter && !usingCodeFilter) {
|
||||||
|
keywords = keywords.filter(function (keyword) {
|
||||||
|
return keyword !== '$n'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usingNoteFilter) {
|
||||||
|
keywords.unshift('$n')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({search: keywords.join(' ')}, function () {
|
||||||
|
this.selectArticleByListIndex(0)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
applyTagFilter: function (tag) {
|
||||||
|
return function () {
|
||||||
|
this.setState({search: '#' + tag})
|
||||||
|
}.bind(this)
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
if (this.state.planet == null) return (<div/>)
|
||||||
|
|
||||||
|
var localId = parseInt(this.props.params.localId, 10)
|
||||||
|
|
||||||
|
var codes = this.state.planet.Codes
|
||||||
|
var notes = this.state.planet.Notes
|
||||||
|
|
||||||
|
var article
|
||||||
|
if (this.isActive('codes')) {
|
||||||
|
codes.some(function (_article) {
|
||||||
|
if (localId === _article.localId) {
|
||||||
|
article = _article
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
} else if (this.isActive('notes')) {
|
||||||
|
notes.some(function (_article) {
|
||||||
|
if (localId === _article.localId) {
|
||||||
|
article = _article
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var articles = codes.concat(notes)
|
||||||
|
|
||||||
|
var filteredArticles = this.searchArticle(this.state.search, articles)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='PlanetContainer'>
|
||||||
|
<PlanetHeader
|
||||||
|
ref='header'
|
||||||
|
search={this.state.search}
|
||||||
|
fetchPlanet={this.fetchPlanet}
|
||||||
|
onSearchChange={this.handleSearchChange}
|
||||||
|
currentPlanet={this.state.planet}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PlanetNavigator
|
||||||
|
ref='navigator'
|
||||||
|
search={this.state.search}
|
||||||
|
showAll={this.showAll}
|
||||||
|
toggleCodeFilter={this.toggleCodeFilter}
|
||||||
|
toggleNoteFilter={this.toggleNoteFilter}
|
||||||
|
planet={this.state.planet}
|
||||||
|
currentUser={this.state.currentUser}/>
|
||||||
|
|
||||||
|
<PlanetArticleList showOnlyWithTag={this.applyTagFilter} ref='list' articles={filteredArticles}/>
|
||||||
|
|
||||||
|
<PlanetArticleDetail
|
||||||
|
ref='detail'
|
||||||
|
article={article}
|
||||||
|
planet={this.state.planet}
|
||||||
|
showOnlyWithTag={this.applyTagFilter}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
137
browser/main/Containers/SignupContainer.jsx
Normal file
137
browser/main/Containers/SignupContainer.jsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
/* 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')
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
367
browser/main/Containers/UserContainer.jsx
Normal file
367
browser/main/Containers/UserContainer.jsx
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
/* 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: planet.userName, planetName: planet.name}}>{planet.userName}/{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: planet.userName, planetName: planet.name}}>{planet.userName}/{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: planet.userName, planetName: planet.name}}>{planet.userName}/{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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
67
browser/main/Mixins/ArticleFilter.js
Normal file
67
browser/main/Mixins/ArticleFilter.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
function basicFilter (keyword, articles) {
|
||||||
|
if (keyword === '' || keyword == null) return articles
|
||||||
|
var firstFiltered = articles.filter(function (article) {
|
||||||
|
|
||||||
|
var first = article.type === 'code' ? article.description : article.title
|
||||||
|
if (first.match(new RegExp(keyword, 'i'))) return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
var secondFiltered = articles.filter(function (article) {
|
||||||
|
var second = article.type === 'code' ? article.content : article.content
|
||||||
|
if (second.match(new RegExp(keyword, 'i'))) return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return firstFiltered.concat(secondFiltered).filter(function (value, index, self) {
|
||||||
|
return self.indexOf(value) === index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function codeFilter (articles) {
|
||||||
|
return articles.filter(function (article) {
|
||||||
|
return article.type === 'code'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function noteFilter (articles) {
|
||||||
|
return articles.filter(function (article) {
|
||||||
|
return article.type === 'note'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function tagFilter (keyword, articles) {
|
||||||
|
return articles.filter(function (article) {
|
||||||
|
return article.Tags.some(function (tag) {
|
||||||
|
return tag.name.match(new RegExp('^' + keyword, 'i'))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchArticle (search, articles) {
|
||||||
|
var keywords = search.split(' ')
|
||||||
|
|
||||||
|
for (var keyword of keywords) {
|
||||||
|
if (keyword.match(/^\$c/, 'i')) {
|
||||||
|
articles = codeFilter(articles)
|
||||||
|
continue
|
||||||
|
} else if (keyword.match(/^\$n/, 'i')) {
|
||||||
|
articles = noteFilter(articles)
|
||||||
|
continue
|
||||||
|
} else if (keyword.match(/^#[A-Za-z0-9]+/)) {
|
||||||
|
articles = tagFilter(keyword.substring(1, keyword.length), articles)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
articles = basicFilter(keyword, articles)
|
||||||
|
}
|
||||||
|
|
||||||
|
return articles.sort(function (a, b) {
|
||||||
|
return new Date(b.updatedAt) - new Date(a.updatedAt)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
searchArticle: searchArticle
|
||||||
|
}
|
||||||
27
browser/main/Mixins/AuthFilter.js
Normal file
27
browser/main/Mixins/AuthFilter.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* global localStorage*/
|
||||||
|
|
||||||
|
var mixin = {}
|
||||||
|
|
||||||
|
mixin.OnlyGuest = {
|
||||||
|
componentDidMount: function () {
|
||||||
|
var currentUser = localStorage.getItem('currentUser')
|
||||||
|
|
||||||
|
if (currentUser == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.transitionTo('userHome', {userName: currentUser.name})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin.OnlyUser = {
|
||||||
|
componentDidMount: function () {
|
||||||
|
var currentUser = localStorage.getItem('currentUser')
|
||||||
|
|
||||||
|
if (currentUser == null) {
|
||||||
|
this.transitionTo('login')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = mixin
|
||||||
8
browser/main/Mixins/ExternalLink.js
Normal file
8
browser/main/Mixins/ExternalLink.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
var shell = require('shell')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
openExternal: function (e) {
|
||||||
|
shell.openExternal(e.currentTarget.href)
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
14
browser/main/Mixins/ForceUpdate.js
Normal file
14
browser/main/Mixins/ForceUpdate.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
var ForceUpdate = function (interval) {
|
||||||
|
return {
|
||||||
|
componentDidMount: function () {
|
||||||
|
this.refreshTimer = setInterval(function () {
|
||||||
|
this.forceUpdate()
|
||||||
|
}.bind(this), interval)
|
||||||
|
},
|
||||||
|
componentWillUnmount: function () {
|
||||||
|
clearInterval(this.refreshTimer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ForceUpdate
|
||||||
33
browser/main/Mixins/Helper.js
Normal file
33
browser/main/Mixins/Helper.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
function deleteItemFromTargetArray (item, targetArray) {
|
||||||
|
if (targetArray == null) targetArray = []
|
||||||
|
targetArray.some(function (_item, index) {
|
||||||
|
if (_item.id === item.id) {
|
||||||
|
targetArray.splice(index, 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return targetArray
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateItemToTargetArray (item, targetArray) {
|
||||||
|
if (targetArray == null) targetArray = []
|
||||||
|
|
||||||
|
var isNew = !targetArray.some(function (_item, index) {
|
||||||
|
if (_item.id === item.id) {
|
||||||
|
targetArray.splice(index, 1, item)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isNew) targetArray.push(item)
|
||||||
|
|
||||||
|
return targetArray
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
deleteItemFromTargetArray: deleteItemFromTargetArray,
|
||||||
|
updateItemToTargetArray: updateItemToTargetArray
|
||||||
|
}
|
||||||
100
browser/main/Mixins/KeyCaster.js
Normal file
100
browser/main/Mixins/KeyCaster.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
var Reflux = require('reflux')
|
||||||
|
|
||||||
|
var state = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyDown = Reflux.createAction()
|
||||||
|
|
||||||
|
var KeyStore = Reflux.createStore({
|
||||||
|
init: function () {
|
||||||
|
this.listenTo(keyDown, this.onKeyDown)
|
||||||
|
document.addEventListener('keydown', function (e) {
|
||||||
|
keyDown(e)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setState: function (newState, cb) {
|
||||||
|
for (var key in newState) {
|
||||||
|
state[key] = newState[key]
|
||||||
|
}
|
||||||
|
if (typeof cb === 'function') cb()
|
||||||
|
},
|
||||||
|
onKeyDown: function (e) {
|
||||||
|
/*
|
||||||
|
Modals
|
||||||
|
*/
|
||||||
|
if (state.codeForm || state.noteForm || state.noteDeleteModal || state.codeDeleteModal || state.addMemberModal || state.aboutModal || state.editProfileModal || state.contactModal || state.teamCreateModal || state.planetCreateModal || state.planetSettingModal || state.teamSettingsModal || state.logoutModal) {
|
||||||
|
// ESC
|
||||||
|
if (e.keyCode === 27) this.cast('closeModal')
|
||||||
|
|
||||||
|
// Cmd + Enter
|
||||||
|
if (e.keyCode === 13 && e.metaKey) {
|
||||||
|
if (state.codeForm) this.cast('submitCodeForm')
|
||||||
|
if (state.noteForm) this.cast('submitNoteForm')
|
||||||
|
if (state.codeDeleteModal) this.cast('submitCodeDeleteModal')
|
||||||
|
if (state.noteDeleteModal) this.cast('submitNoteDeleteModal')
|
||||||
|
if (state.addMemberModal) this.cast('submitAddMemberModal')
|
||||||
|
if (state.contactModal) this.cast('submitContactModal')
|
||||||
|
if (state.teamCreateModal) this.cast('submitTeamCreateModal')
|
||||||
|
if (state.planetCreateModal) this.cast('submitPlanetCreateModal')
|
||||||
|
if (state.logoutModal) this.cast('submitLogoutModal')
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
PlanetContainer
|
||||||
|
*/
|
||||||
|
if (state.planetContainer) {
|
||||||
|
// Cmd + Enter, A
|
||||||
|
if ((e.keyCode === 13 && e.metaKey) || e.keyCode === 65) this.cast('openLaunchModal')
|
||||||
|
|
||||||
|
// Esc
|
||||||
|
if (e.keyCode === 27) this.cast('toggleFocusSearchInput')
|
||||||
|
|
||||||
|
// Up
|
||||||
|
if (e.keyCode === 38) this.cast('selectPriorArticle')
|
||||||
|
|
||||||
|
// Down
|
||||||
|
if (e.keyCode === 40) this.cast('selectNextArticle')
|
||||||
|
|
||||||
|
// E
|
||||||
|
if (e.keyCode === 69) this.cast('openEditModal')
|
||||||
|
|
||||||
|
// D
|
||||||
|
if (e.keyCode === 68) this.cast('openDeleteModal')
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
HomeContainer
|
||||||
|
*/
|
||||||
|
if (state.homeContainer) {
|
||||||
|
if (e.keyCode > 48 && e.keyCode < 58 && e.metaKey) {
|
||||||
|
this.cast('switchPlanet', e.keyCode - 48)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
cast: function (status, data) {
|
||||||
|
this.trigger({
|
||||||
|
status: status,
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = function (stateKey) {
|
||||||
|
return {
|
||||||
|
mixins: [Reflux.listenTo(KeyStore, 'onKeyCast')],
|
||||||
|
componentDidMount: function () {
|
||||||
|
var newState = {}
|
||||||
|
newState[stateKey] = true
|
||||||
|
KeyStore.setState(newState)
|
||||||
|
},
|
||||||
|
componentWillUnmount: function () {
|
||||||
|
var newState = {}
|
||||||
|
newState[stateKey] = false
|
||||||
|
KeyStore.setState(newState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
browser/main/Mixins/LinkedState.js
Normal file
31
browser/main/Mixins/LinkedState.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
linkState: function (path) {
|
||||||
|
return {
|
||||||
|
value: getIn(this.state, path),
|
||||||
|
requestChange: setPartialState.bind(null, this, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
browser/main/Mixins/Markdown.js
Normal file
13
browser/main/Mixins/Markdown.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
var markdownit = require('markdown-it')
|
||||||
|
var md = markdownit({
|
||||||
|
typographer: true,
|
||||||
|
linkify: true
|
||||||
|
})
|
||||||
|
|
||||||
|
var Markdown = {
|
||||||
|
markdown: function (content) {
|
||||||
|
return md.render(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Markdown
|
||||||
42
browser/main/Mixins/Modal.jsx
Normal file
42
browser/main/Mixins/Modal.jsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
var React = require('react/addons')
|
||||||
|
var ModalBase = React.createClass({
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
|
component: null,
|
||||||
|
componentProps: {},
|
||||||
|
isHidden: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
close: function () {
|
||||||
|
this.setState({component: null, componentProps: null, isHidden: true})
|
||||||
|
},
|
||||||
|
render: function () {
|
||||||
|
var componentProps = this.state.componentProps
|
||||||
|
return (
|
||||||
|
<div className={'ModalBase' + (this.state.isHidden ? ' hide' : '')}>
|
||||||
|
<div onClick={this.close} className='modalBack'/>
|
||||||
|
{this.state.component == null ? null : (
|
||||||
|
<this.state.component {...componentProps} close={this.close}/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var modalBase = null
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
componentDidMount: function () {
|
||||||
|
if (modalBase == null) {
|
||||||
|
var el = document.createElement('div')
|
||||||
|
document.body.appendChild(el)
|
||||||
|
modalBase = React.render(<ModalBase/>, el)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openModal: function (component, props) {
|
||||||
|
modalBase.setState({component: component, componentProps: props, isHidden: false})
|
||||||
|
},
|
||||||
|
closeModal: function () {
|
||||||
|
modalBase.setState({isHidden: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
172
browser/main/Services/Hq.js
Normal file
172
browser/main/Services/Hq.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
/* global localStorage */
|
||||||
|
|
||||||
|
var request = require('superagent-promise')(require('superagent'), Promise)
|
||||||
|
var apiUrl = require('../../../config').apiUrl
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// Auth
|
||||||
|
login: function (input) {
|
||||||
|
return request
|
||||||
|
.post(apiUrl + 'auth')
|
||||||
|
.send(input)
|
||||||
|
},
|
||||||
|
signup: function (input) {
|
||||||
|
return request
|
||||||
|
.post(apiUrl + 'auth/signup')
|
||||||
|
.send(input)
|
||||||
|
},
|
||||||
|
getUser: function () {
|
||||||
|
return request
|
||||||
|
.get(apiUrl + 'auth/user')
|
||||||
|
.set({
|
||||||
|
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
changePassword: function (input) {
|
||||||
|
return request
|
||||||
|
.post(apiUrl + 'auth/password')
|
||||||
|
.set({
|
||||||
|
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||||
|
})
|
||||||
|
.send(input)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Resources
|
||||||
|
fetchUser: function (userName) {
|
||||||
|
return request
|
||||||
|
.get(apiUrl + 'resources/' + userName)
|
||||||
|
.set({
|
||||||
|
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateUser: function (userName, input) {
|
||||||
|
return request
|
||||||
|
.put(apiUrl + 'resources/' + userName)
|
||||||
|
.set({
|
||||||
|
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||||
|
})
|
||||||
|
.send(input)
|
||||||
|
},
|
||||||
|
createTeam: function (userName, input) {
|
||||||
|
return request
|
||||||
|
.post(apiUrl + 'resources/' + userName + '/teams')
|
||||||
|
.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
|
||||||
|
.get(apiUrl + 'search/tags')
|
||||||
|
.query({name: tagName})
|
||||||
|
},
|
||||||
|
searchUser: function (userName) {
|
||||||
|
return request
|
||||||
|
.get(apiUrl + 'search/users')
|
||||||
|
.query({name: userName})
|
||||||
|
},
|
||||||
|
|
||||||
|
// Mail
|
||||||
|
sendEmail: function (input) {
|
||||||
|
return request
|
||||||
|
.post(apiUrl + 'mail')
|
||||||
|
.set({
|
||||||
|
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||||
|
})
|
||||||
|
.send(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
131
browser/main/Stores/AuthStore.js
Normal file
131
browser/main/Stores/AuthStore.js
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
/* 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
|
||||||
159
browser/main/Stores/PlanetStore.js
Normal file
159
browser/main/Stores/PlanetStore.js
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/* 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,
|
||||||
|
onUpdate: function (planet) {
|
||||||
|
// Copy the planet object
|
||||||
|
var aPlanet = Object.assign({}, planet)
|
||||||
|
delete aPlanet.Codes
|
||||||
|
delete aPlanet.Notes
|
||||||
|
|
||||||
|
// Check if the planet should be updated to currentUser
|
||||||
|
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||||
|
|
||||||
|
var ownedByCurrentUser = currentUser.id === aPlanet.OwnerId
|
||||||
|
|
||||||
|
if (ownedByCurrentUser) {
|
||||||
|
currentUser.Planets = this.updateItemToTargetArray(aPlanet, currentUser.Planets)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ownedByCurrentUser) {
|
||||||
|
var team = null
|
||||||
|
currentUser.Teams.some(function (_team) {
|
||||||
|
if (_team.id === aPlanet.OwnerId) {
|
||||||
|
team = _team
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
})
|
||||||
|
|
||||||
|
if (team) {
|
||||||
|
team.Planets = this.updateItemToTargetArray(aPlanet, 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: '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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
69
browser/main/Stores/UserStore.js
Normal file
69
browser/main/Stores/UserStore.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/* global localStorage */
|
||||||
|
|
||||||
|
var Reflux = require('reflux')
|
||||||
|
|
||||||
|
var actions = Reflux.createActions([
|
||||||
|
'update',
|
||||||
|
'destroy',
|
||||||
|
'addMember',
|
||||||
|
'removeMember'
|
||||||
|
])
|
||||||
|
|
||||||
|
module.exports = Reflux.createStore({
|
||||||
|
listenables: [actions],
|
||||||
|
onUpdate: function (user) {
|
||||||
|
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||||
|
|
||||||
|
if (currentUser.id === user.id) {
|
||||||
|
localStorage.setItem('currentUser', JSON.stringify(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
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onAddMember: function (member) {
|
||||||
|
this.trigger({
|
||||||
|
status: 'memberAdded',
|
||||||
|
data: member
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onRemoveMember: function (member) {
|
||||||
|
this.trigger({
|
||||||
|
status: 'memberRemoved',
|
||||||
|
data: member
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Actions: actions
|
||||||
|
})
|
||||||
BIN
browser/main/favicon.ico
Normal file
BIN
browser/main/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
96
browser/main/index.electron.html
Normal file
96
browser/main/index.electron.html
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<!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('../../Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||||
|
url('../../Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||||
|
url('../../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: 0 auto;
|
||||||
|
}
|
||||||
|
#loadingCover .message{
|
||||||
|
font-size: 45px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-weight: 200;
|
||||||
|
color: #404849;
|
||||||
|
}
|
||||||
|
</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').getGlobal('version')
|
||||||
|
document.title = 'Boost ' + ((version == null || version.length === 0) ? 'DEV version' : 'v' + version)
|
||||||
|
require('electron-stylus')(__dirname + '/../styles/main/index.styl')
|
||||||
|
require('node-jsx').install({ harmony: true, extension: '.jsx' })
|
||||||
|
require('./index.jsx')
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
56
browser/main/index.html
Normal file
56
browser/main/index.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>CodeXen</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="../vendor/fontawesome/css/font-awesome.min.css" media="screen" title="no title" 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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<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>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
46
browser/main/index.jsx
Normal file
46
browser/main/index.jsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
BIN
browser/main/resources/favicon-230x230.png
Normal file
BIN
browser/main/resources/favicon-230x230.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
2
browser/main/style.js
Normal file
2
browser/main/style.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
require('../styles/main/index.styl')
|
||||||
|
require('react-select/dist/default.css')
|
||||||
84
browser/styles/finder/index.styl
Normal file
84
browser/styles/finder/index.styl
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
@import '../../../node_modules/nib/lib/nib'
|
||||||
|
@import '../vars'
|
||||||
|
@import '../mixins/*'
|
||||||
|
global-reset()
|
||||||
|
@import '../shared/*'
|
||||||
|
|
||||||
|
body
|
||||||
|
font-family "Lato"
|
||||||
|
color textColor
|
||||||
|
font-size fontSize
|
||||||
|
|
||||||
|
.Finder
|
||||||
|
absolute top bottom left right
|
||||||
|
.FinderInput
|
||||||
|
position absolute
|
||||||
|
top 11px
|
||||||
|
left 11px
|
||||||
|
right 11px
|
||||||
|
margin 0 auto
|
||||||
|
height 44px
|
||||||
|
box-sizing border-box
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
input
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
border solid 1px borderColor
|
||||||
|
padding 0 10px
|
||||||
|
font-size 1em
|
||||||
|
height 33px
|
||||||
|
border-radius 5px
|
||||||
|
box-sizing border-box
|
||||||
|
border-radius 16.5px
|
||||||
|
&:focus, &.focus
|
||||||
|
border-color brandBorderColor
|
||||||
|
outline none
|
||||||
|
.FinderList
|
||||||
|
absolute left bottom
|
||||||
|
top 55px
|
||||||
|
border-right solid 1px borderColor
|
||||||
|
box-sizing border-box
|
||||||
|
width 250px
|
||||||
|
overflow-y auto
|
||||||
|
&>ul>li
|
||||||
|
.articleItem
|
||||||
|
padding 10px
|
||||||
|
border solid 2px transparent
|
||||||
|
box-sizing border-box
|
||||||
|
cursor pointer
|
||||||
|
white-space nowrap
|
||||||
|
overflow-x hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
.divider
|
||||||
|
box-sizing border-box
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
&.active
|
||||||
|
.articleItem
|
||||||
|
border-color brandColor
|
||||||
|
|
||||||
|
.FinderDetail
|
||||||
|
absolute right bottom
|
||||||
|
top 55px
|
||||||
|
left 250px
|
||||||
|
.header
|
||||||
|
absolute top left right
|
||||||
|
height 44px
|
||||||
|
box-sizing border-box
|
||||||
|
padding 0 10px
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
line-height 44px
|
||||||
|
font-size 1.3em
|
||||||
|
white-space nowrap
|
||||||
|
text-overflow ellipsis
|
||||||
|
overflow-x hidden
|
||||||
|
.content
|
||||||
|
.ace_editor, .marked
|
||||||
|
position absolute
|
||||||
|
top 49px
|
||||||
|
left 5px
|
||||||
|
right 5px
|
||||||
|
bottom 5px
|
||||||
|
box-sizing border-box
|
||||||
|
.marked
|
||||||
|
marked()
|
||||||
|
overflow-y auto
|
||||||
260
browser/styles/main/components/Select.styl
Normal file
260
browser/styles/main/components/Select.styl
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
/**
|
||||||
|
* React Select
|
||||||
|
* ============
|
||||||
|
* Created by Jed Watson and Joss Mackison for KeystoneJS, http://www.keystonejs.com/
|
||||||
|
* https://twitter.com/jedwatson https://twitter.com/jossmackison https://twitter.com/keystonejs
|
||||||
|
* MIT License: https://github.com/keystonejs/react-select
|
||||||
|
*/
|
||||||
|
.Select {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.Select-control {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-color: #d9d9d9 #cccccc #b3b3b3;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #333333;
|
||||||
|
cursor: default;
|
||||||
|
outline: none;
|
||||||
|
padding: 8px 52px 8px 10px;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
}
|
||||||
|
.Select-control:hover {
|
||||||
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.is-searchable.is-open > .Select-control {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
.is-open > .Select-control {
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
background: #ffffff;
|
||||||
|
border-color: #b3b3b3 #cccccc #d9d9d9;
|
||||||
|
}
|
||||||
|
.is-open > .Select-control > .Select-arrow {
|
||||||
|
border-color: transparent transparent #999999;
|
||||||
|
border-width: 0 5px 5px;
|
||||||
|
}
|
||||||
|
.is-searchable.is-focused:not(.is-open) > .Select-control {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
.Select-placeholder {
|
||||||
|
color: #aaaaaa;
|
||||||
|
padding: 8px 52px 8px 10px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: -15px;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.has-value > .Select-control > .Select-placeholder {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.Select-input > input {
|
||||||
|
cursor: default;
|
||||||
|
background: none transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
height: auto;
|
||||||
|
border: 0 none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
outline: none;
|
||||||
|
display: inline-block;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
.is-focused .Select-input > input {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
.Select-control:not(.is-searchable) > .Select-input {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.Select-loading {
|
||||||
|
-webkit-animation: Select-animation-spin 400ms infinite linear;
|
||||||
|
-o-animation: Select-animation-spin 400ms infinite linear;
|
||||||
|
animation: Select-animation-spin 400ms infinite linear;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #cccccc;
|
||||||
|
border-right-color: #333333;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
margin-top: -8px;
|
||||||
|
position: absolute;
|
||||||
|
right: 30px;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
.has-value > .Select-control > .Select-loading {
|
||||||
|
right: 46px;
|
||||||
|
}
|
||||||
|
.Select-clear {
|
||||||
|
color: #999999;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
position: absolute;
|
||||||
|
right: 17px;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.Select-clear:hover {
|
||||||
|
color: #c0392b;
|
||||||
|
}
|
||||||
|
.Select-clear > span {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
.Select-arrow-zone {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.Select-arrow {
|
||||||
|
border-color: #999999 transparent transparent;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 5px 5px 0;
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
height: 0;
|
||||||
|
margin-top: -ceil(2.5px);
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
top: 14px;
|
||||||
|
width: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.Select-menu-outer {
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #cccccc;
|
||||||
|
border-top-color: #e6e6e6;
|
||||||
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-top: -1px;
|
||||||
|
max-height: 200px;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
.Select-menu {
|
||||||
|
max-height: 198px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.Select-option {
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #666666;
|
||||||
|
cursor: pointer;
|
||||||
|
display: block;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
.Select-option:last-child {
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
}
|
||||||
|
.Select-option.is-focused {
|
||||||
|
background-color: #f2f9fc;
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
.Select-option.is-disabled {
|
||||||
|
color: #cccccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
.Select-noresults {
|
||||||
|
box-sizing: border-box;
|
||||||
|
color: #999999;
|
||||||
|
cursor: default;
|
||||||
|
display: block;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
.Select.is-multi .Select-control {
|
||||||
|
padding: 2px 52px 2px 3px;
|
||||||
|
}
|
||||||
|
.Select.is-multi .Select-input {
|
||||||
|
vertical-align: middle;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
margin: 2px;
|
||||||
|
padding: 3px 0;
|
||||||
|
}
|
||||||
|
.Select-item {
|
||||||
|
background-color: #f2f9fc;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #c9e6f2;
|
||||||
|
color: #0088cc;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1em;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
.Select-item-icon,
|
||||||
|
.Select-item-label {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.Select-item-label {
|
||||||
|
cursor: default;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
padding: 3px 5px;
|
||||||
|
}
|
||||||
|
.Select-item-label .Select-item-label__a {
|
||||||
|
color: #0088cc;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.Select-item-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom-left-radius: 2px;
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
border-right: 1px solid #c9e6f2;
|
||||||
|
padding: 2px 5px 4px;
|
||||||
|
}
|
||||||
|
.Select-item-icon:hover,
|
||||||
|
.Select-item-icon:focus {
|
||||||
|
background-color: #ddeff7;
|
||||||
|
color: #0077b3;
|
||||||
|
}
|
||||||
|
.Select-item-icon:active {
|
||||||
|
background-color: #c9e6f2;
|
||||||
|
}
|
||||||
|
.Select.is-multi.is-disabled .Select-item {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
color: #888888;
|
||||||
|
}
|
||||||
|
.Select.is-multi.is-disabled .Select-item-icon {
|
||||||
|
cursor: not-allowed;
|
||||||
|
border-right: 1px solid #d9d9d9;
|
||||||
|
}
|
||||||
|
.Select.is-multi.is-disabled .Select-item-icon:hover,
|
||||||
|
.Select.is-multi.is-disabled .Select-item-icon:focus,
|
||||||
|
.Select.is-multi.is-disabled .Select-item-icon:active {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
@keyframes Select-animation-spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(1turn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes Select-animation-spin {
|
||||||
|
to {
|
||||||
|
-webkit-transform: rotate(1turn);
|
||||||
|
}
|
||||||
|
}
|
||||||
82
browser/styles/main/containers/LoginContainer.styl
Normal file
82
browser/styles/main/containers/LoginContainer.styl
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
.LoginContainer, .SignupContainer
|
||||||
|
margin 0 auto
|
||||||
|
padding 25px 15px
|
||||||
|
box-sizing border-box
|
||||||
|
color inactiveTextColor
|
||||||
|
.logo
|
||||||
|
width 150px
|
||||||
|
height 150px
|
||||||
|
display block
|
||||||
|
margin 0 auto
|
||||||
|
.authNavigator
|
||||||
|
margin 15px 0 25px
|
||||||
|
a
|
||||||
|
font-size 1.5em
|
||||||
|
text-decoration none
|
||||||
|
color inactiveTextColor
|
||||||
|
&:hover, &.hover, &:active, &.active
|
||||||
|
color brandColor
|
||||||
|
.socialControl
|
||||||
|
text-align center
|
||||||
|
margin 25px 0
|
||||||
|
p
|
||||||
|
margin-bottom 25px
|
||||||
|
.facebookBtn, .githubBtn
|
||||||
|
margin 0 45px
|
||||||
|
width 50px
|
||||||
|
height 50px
|
||||||
|
line-height 50px
|
||||||
|
font-size 25px
|
||||||
|
text-align center
|
||||||
|
background-image none
|
||||||
|
color white
|
||||||
|
border none
|
||||||
|
border-radius 25px
|
||||||
|
cursor pointer
|
||||||
|
.facebookBtn
|
||||||
|
background-color facebookColor
|
||||||
|
&:hover, &.hover
|
||||||
|
background-color lighten(facebookColor, 25%)
|
||||||
|
.githubBtn
|
||||||
|
background-color githubBtn
|
||||||
|
font-size 30px
|
||||||
|
line-height 30px
|
||||||
|
&:hover, &.hover
|
||||||
|
background-color lighten(githubBtn, 25%)
|
||||||
|
.divider
|
||||||
|
.dividerLabel
|
||||||
|
text-align center
|
||||||
|
position relative
|
||||||
|
top -27px
|
||||||
|
font-size 1.3em
|
||||||
|
background-color backgroundColor
|
||||||
|
margin 0 auto
|
||||||
|
width 50px
|
||||||
|
form
|
||||||
|
width 400px
|
||||||
|
margin 0 auto 45px
|
||||||
|
.alertInfo, .alertError
|
||||||
|
margin-top 15px
|
||||||
|
margin-bottom 15px
|
||||||
|
height 44px
|
||||||
|
padding 5px
|
||||||
|
border-radius 10px
|
||||||
|
line-height 44px
|
||||||
|
text-align center
|
||||||
|
.alertInfo
|
||||||
|
alertInfo()
|
||||||
|
.alertError
|
||||||
|
alertError()
|
||||||
|
div.form-group:last-child
|
||||||
|
margin-top 15px
|
||||||
|
button.logInButton
|
||||||
|
btnPrimary()
|
||||||
|
height 44px
|
||||||
|
border-radius 22px
|
||||||
|
display block
|
||||||
|
width 200px
|
||||||
|
font-size 1em
|
||||||
|
margin 0 auto
|
||||||
|
p.alert
|
||||||
|
text-align center
|
||||||
|
font-size 0.8em
|
||||||
332
browser/styles/main/containers/PlanetContainer.styl
Normal file
332
browser/styles/main/containers/PlanetContainer.styl
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
navigationWidth= 200px
|
||||||
|
articleListWidth= 275px
|
||||||
|
|
||||||
|
.PlanetContainer
|
||||||
|
absolute top bottom right left
|
||||||
|
.tags
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
a
|
||||||
|
margin 0 2px
|
||||||
|
text-decoration underline
|
||||||
|
cursor pointer
|
||||||
|
font-size 0.95em
|
||||||
|
&.noTag
|
||||||
|
color inactiveTextColor
|
||||||
|
font-size 0.8em
|
||||||
|
|
||||||
|
.PlanetHeader
|
||||||
|
absolute left right top
|
||||||
|
overflow-y hidden
|
||||||
|
height 55px
|
||||||
|
background-color white
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
box-sizing border-box
|
||||||
|
padding 5px 15px
|
||||||
|
clearfix()
|
||||||
|
.headerLabel
|
||||||
|
noSelect()
|
||||||
|
absolute top left bottom
|
||||||
|
overflow hidden
|
||||||
|
display inline-block
|
||||||
|
width navigationWidth
|
||||||
|
.userName
|
||||||
|
position absolute
|
||||||
|
left 15px
|
||||||
|
top 30px
|
||||||
|
width 140px
|
||||||
|
font-size 1em
|
||||||
|
color textColor
|
||||||
|
text-decoration none
|
||||||
|
&:hover
|
||||||
|
color darken(lightButtonColor, 50%)
|
||||||
|
text-decoration underline
|
||||||
|
.planetName
|
||||||
|
position absolute
|
||||||
|
top 5px
|
||||||
|
left 10px
|
||||||
|
width 145px
|
||||||
|
font-size 1.6em
|
||||||
|
color brandColor
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
white-space nowrap
|
||||||
|
&:hover
|
||||||
|
color darken(brandBorderColor, 30%)
|
||||||
|
.private
|
||||||
|
position absolute
|
||||||
|
top 12px
|
||||||
|
right 38px
|
||||||
|
width 33px
|
||||||
|
height 33px
|
||||||
|
line-height 33px
|
||||||
|
text-align center
|
||||||
|
color inactiveColor
|
||||||
|
&:hover
|
||||||
|
color textColor
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
margin-left -30px
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
|
||||||
|
.planetSettingButton
|
||||||
|
position absolute
|
||||||
|
top 15px
|
||||||
|
right 5px
|
||||||
|
font-size 0.8em
|
||||||
|
btnDefault()
|
||||||
|
box-sizing border-box
|
||||||
|
circle()
|
||||||
|
width 26px
|
||||||
|
height 26px
|
||||||
|
text-align center
|
||||||
|
cursor pointer
|
||||||
|
transition 0.1s
|
||||||
|
&:focus, &.focus
|
||||||
|
outline none
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
margin-top 11px
|
||||||
|
margin-left -36px
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.headerControl
|
||||||
|
noSelect()
|
||||||
|
absolute top bottom right
|
||||||
|
left navigationWidth
|
||||||
|
.searchInput
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
top 12px
|
||||||
|
left 0
|
||||||
|
input
|
||||||
|
padding-left 32px
|
||||||
|
width 300px
|
||||||
|
.fa
|
||||||
|
position absolute
|
||||||
|
top 8px
|
||||||
|
left 12px
|
||||||
|
color inactiveTextColor
|
||||||
|
.refreshButton
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
top 15px
|
||||||
|
right 55px
|
||||||
|
width 26px
|
||||||
|
height 26px
|
||||||
|
font-size 0.8em
|
||||||
|
btnDefault()
|
||||||
|
circle()
|
||||||
|
text-align center
|
||||||
|
cursor pointer
|
||||||
|
transition 0.1s
|
||||||
|
&:focus, &.focus
|
||||||
|
outline none
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
margin-top 11px
|
||||||
|
margin-left -39px
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
.logo
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
top 4px
|
||||||
|
right 10px
|
||||||
|
cursor pointer
|
||||||
|
img
|
||||||
|
transition 0.1s
|
||||||
|
opacity 0.9
|
||||||
|
&:hover img, &:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
margin-top -5px
|
||||||
|
margin-left -67px
|
||||||
|
|
||||||
|
|
||||||
|
.PlanetNavigator
|
||||||
|
absolute bottom left
|
||||||
|
noSelect()
|
||||||
|
top 55px
|
||||||
|
width navigationWidth
|
||||||
|
border-right solid 1px highlightenBorderColor
|
||||||
|
padding 10px
|
||||||
|
box-sizing border-box
|
||||||
|
.launchButton
|
||||||
|
border-radius 22px
|
||||||
|
font-size 1.1em
|
||||||
|
nav
|
||||||
|
a
|
||||||
|
display block
|
||||||
|
box-sizing border-box
|
||||||
|
padding 15px 15px
|
||||||
|
margin 10px 0
|
||||||
|
border-radius 10px
|
||||||
|
text-decoration none
|
||||||
|
background-color transparent
|
||||||
|
color textColor
|
||||||
|
cursor pointer
|
||||||
|
transition 0.1s
|
||||||
|
btnDefault()
|
||||||
|
border none
|
||||||
|
|
||||||
|
.PlanetArticleList
|
||||||
|
absolute bottom right
|
||||||
|
left navigationWidth
|
||||||
|
top 55px
|
||||||
|
width articleListWidth
|
||||||
|
border-right solid 1px highlightenBorderColor
|
||||||
|
|
||||||
|
&>ul
|
||||||
|
absolute top bottom left right
|
||||||
|
overflow-y auto
|
||||||
|
li
|
||||||
|
.articleItem
|
||||||
|
noSelect()
|
||||||
|
border solid 2px transparent
|
||||||
|
position relative
|
||||||
|
height 94px
|
||||||
|
width 100%
|
||||||
|
cursor pointer
|
||||||
|
transition 0.1s
|
||||||
|
.itemLeft
|
||||||
|
position absolute
|
||||||
|
top 4px
|
||||||
|
bottom 4px
|
||||||
|
width 38px
|
||||||
|
padding 3px 0 3px 3px
|
||||||
|
text-align center
|
||||||
|
.profileImage
|
||||||
|
margin-bottom 5px
|
||||||
|
circle()
|
||||||
|
.fa
|
||||||
|
line-height 25px
|
||||||
|
.itemRight
|
||||||
|
position absolute
|
||||||
|
top 4px
|
||||||
|
bottom 4px
|
||||||
|
right 2px
|
||||||
|
left 40px
|
||||||
|
overflow-x hidden
|
||||||
|
padding 3px 10px 3px 3px
|
||||||
|
.itemInfo
|
||||||
|
margin 5px 0 13px
|
||||||
|
color lighten(textColor, 25%)
|
||||||
|
font-size 0.7em
|
||||||
|
.userProfileName
|
||||||
|
color brandColor
|
||||||
|
font-size 1.2em
|
||||||
|
.description
|
||||||
|
line-height 120%
|
||||||
|
margin-bottom 10px
|
||||||
|
font-size 1em
|
||||||
|
overflow-x hidden
|
||||||
|
white-space nowrap
|
||||||
|
text-overflow ellipsis
|
||||||
|
.tags
|
||||||
|
position absolute
|
||||||
|
bottom 5px
|
||||||
|
font-size 0.9em
|
||||||
|
&:hover, &.hover
|
||||||
|
background-color hoverBackgroundColor
|
||||||
|
&:active, &.active
|
||||||
|
background-color white
|
||||||
|
&:active, &.active
|
||||||
|
border-color brandBorderColor
|
||||||
|
.divider
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
|
||||||
|
.PlanetArticleDetail
|
||||||
|
absolute right bottom
|
||||||
|
top 55px
|
||||||
|
left navigationWidth + articleListWidth
|
||||||
|
.detailHeader
|
||||||
|
border solid 2px transparent
|
||||||
|
position relative
|
||||||
|
height 105px
|
||||||
|
width 100%
|
||||||
|
transition 0.1s
|
||||||
|
.itemLeft
|
||||||
|
position absolute
|
||||||
|
top 7px
|
||||||
|
bottom 4px
|
||||||
|
width 38px
|
||||||
|
padding 3px 0 3px 3px
|
||||||
|
text-align center
|
||||||
|
.profileImage
|
||||||
|
margin-bottom 5px
|
||||||
|
circle()
|
||||||
|
.fa
|
||||||
|
line-height 25px
|
||||||
|
.itemRight
|
||||||
|
position absolute
|
||||||
|
top 7px
|
||||||
|
bottom 4px
|
||||||
|
right 2px
|
||||||
|
left 40px
|
||||||
|
overflow-x hidden
|
||||||
|
padding 3px 10px 3px 3px
|
||||||
|
.itemInfo
|
||||||
|
margin 5px 0 13px
|
||||||
|
color lighten(textColor, 25%)
|
||||||
|
font-size 0.7em
|
||||||
|
.userProfileName
|
||||||
|
color brandColor
|
||||||
|
font-size 1.2em
|
||||||
|
.description
|
||||||
|
line-height 120%
|
||||||
|
margin-bottom 10px
|
||||||
|
font-size 1em
|
||||||
|
overflow-x auto
|
||||||
|
white-space nowrap
|
||||||
|
.tags
|
||||||
|
position absolute
|
||||||
|
bottom 5px
|
||||||
|
font-size 0.9em
|
||||||
|
.itemControl
|
||||||
|
position absolute
|
||||||
|
z-index 1
|
||||||
|
top 2px
|
||||||
|
right 2px
|
||||||
|
.deleteButton, .editButton
|
||||||
|
btnDefault()
|
||||||
|
text-align center
|
||||||
|
width 33px
|
||||||
|
height 33px
|
||||||
|
border-radius 16.5px
|
||||||
|
font-size 15px
|
||||||
|
margin 0 3px
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
margin-top 10px
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
.editButton .tooltip
|
||||||
|
margin-left -12px
|
||||||
|
.deleteButton .tooltip
|
||||||
|
margin-left -26px
|
||||||
|
.detailBody
|
||||||
|
absolute left right bottom
|
||||||
|
top 105px
|
||||||
|
.content
|
||||||
|
position absolute
|
||||||
|
top 5px
|
||||||
|
bottom 5px
|
||||||
|
left 2px
|
||||||
|
right 2px
|
||||||
|
box-sizing border-box
|
||||||
|
padding 5px
|
||||||
|
border-top solid 1px borderColor
|
||||||
|
&.noteDetail
|
||||||
|
.detailBody .content
|
||||||
|
overflow-x hidden
|
||||||
|
overflow-y auto
|
||||||
|
marked()
|
||||||
|
&.codeDetail
|
||||||
|
.detailBody .content
|
||||||
|
.ace_editor
|
||||||
|
absolute left right top bottom
|
||||||
310
browser/styles/main/containers/UserContainer.styl
Normal file
310
browser/styles/main/containers/UserContainer.styl
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
.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
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
&: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
|
||||||
|
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
|
||||||
|
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()
|
||||||
128
browser/styles/main/index.styl
Normal file
128
browser/styles/main/index.styl
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
@import '../../../node_modules/nib/lib/nib'
|
||||||
|
@import '../vars'
|
||||||
|
@import '../mixins/*'
|
||||||
|
global-reset()
|
||||||
|
@import '../shared/*'
|
||||||
|
@import './components/*'
|
||||||
|
@import './containers/*'
|
||||||
|
|
||||||
|
html, body
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
overflow hidden
|
||||||
|
|
||||||
|
body
|
||||||
|
font-family "Lato"
|
||||||
|
color textColor
|
||||||
|
font-size fontSize
|
||||||
|
font-weight 400
|
||||||
|
button
|
||||||
|
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
|
||||||
|
color darken(brandColor, 15%)
|
||||||
|
&:visited
|
||||||
|
color brandColor
|
||||||
|
|
||||||
|
hr
|
||||||
|
border-top none
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
margin 15px 0
|
||||||
|
|
||||||
|
button
|
||||||
|
font-weight 400
|
||||||
|
cursor pointer
|
||||||
|
&:focus, &.focus
|
||||||
|
outline none
|
||||||
|
|
||||||
|
.text-center
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
margin-bottom 15px
|
||||||
|
&>label
|
||||||
|
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
|
||||||
|
font-size 1em
|
||||||
|
height 33px
|
||||||
|
border-radius 5px
|
||||||
|
box-sizing border-box
|
||||||
|
&:focus, &.focus
|
||||||
|
border solid 1px brandBorderColor
|
||||||
|
outline none
|
||||||
|
&.circleInput
|
||||||
|
border-radius 16.5px
|
||||||
|
|
||||||
|
.block-input
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
.inline-input
|
||||||
|
display inline-block
|
||||||
|
margin-right 5px
|
||||||
|
|
||||||
|
.relative
|
||||||
|
position relative
|
||||||
|
|
||||||
|
textarea.block-input
|
||||||
|
resize vertical
|
||||||
|
height 125px
|
||||||
|
border-radius 5px
|
||||||
|
padding 5px 10px
|
||||||
|
|
||||||
|
#content
|
||||||
|
fullsize()
|
||||||
|
|
||||||
|
.Main
|
||||||
|
.appUpdateButton
|
||||||
|
position fixed
|
||||||
|
z-index 2000
|
||||||
|
bottom 5px
|
||||||
|
right 53px
|
||||||
|
btnPrimary()
|
||||||
|
padding 10px 15px
|
||||||
|
border-radius 5px
|
||||||
|
background-color backgroundColor
|
||||||
|
.contactButton
|
||||||
|
position fixed
|
||||||
|
z-index 2000
|
||||||
|
bottom 5px
|
||||||
|
right 5px
|
||||||
|
btnPrimary()
|
||||||
|
padding 10px 15px
|
||||||
|
border-radius 5px
|
||||||
|
background-color backgroundColor
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
margin-top -22px
|
||||||
|
margin-left -97px
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
11
browser/styles/mixins/alert.styl
Normal file
11
browser/styles/mixins/alert.styl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
alertSuccess()
|
||||||
|
background-color successBackgroundColor
|
||||||
|
color successTextColor
|
||||||
|
|
||||||
|
alertError()
|
||||||
|
background-color errorBackgroundColor
|
||||||
|
color errorTextColor
|
||||||
|
|
||||||
|
alertInfo()
|
||||||
|
background-color infoBackgroundColor
|
||||||
|
color infoTextColor
|
||||||
40
browser/styles/mixins/btn.styl
Normal file
40
browser/styles/mixins/btn.styl
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
btnDefault()
|
||||||
|
border-style solid
|
||||||
|
border-width 1px
|
||||||
|
border-color lightButtonColor
|
||||||
|
background-color transparent
|
||||||
|
color lightButtonColor
|
||||||
|
|
||||||
|
&: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
|
||||||
|
&:disabled, &.disabled
|
||||||
|
opacity 0.6
|
||||||
|
|
||||||
|
btnPrimary()
|
||||||
|
border-style solid
|
||||||
|
border-width 1px
|
||||||
|
border-color brandBorderColor
|
||||||
|
background-color transparent
|
||||||
|
color brandColor
|
||||||
|
&:hover, &.hover, &:focus, &.focus
|
||||||
|
border-color darken(brandBorderColor, 30%)
|
||||||
|
color darken(brandColor, 30%)
|
||||||
|
&:active, &.active
|
||||||
|
background-color brandColor
|
||||||
|
color white
|
||||||
|
&:disabled, &.disabled
|
||||||
|
opacity 0.6
|
||||||
|
|
||||||
|
btnStripDefault()
|
||||||
|
border none
|
||||||
|
background-color transparent
|
||||||
|
color lightButtonColor
|
||||||
|
&:hover, &.hover, &:focus, &.focus
|
||||||
|
color darken(lightButtonColor, 50%)
|
||||||
|
&:active, &.active
|
||||||
|
color brandColor
|
||||||
3
browser/styles/mixins/circle.styl
Normal file
3
browser/styles/mixins/circle.styl
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
circle()
|
||||||
|
border-radius 50%
|
||||||
|
overflow hidden
|
||||||
6
browser/styles/mixins/fullsize.styl
Normal file
6
browser/styles/mixins/fullsize.styl
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
fullsize()
|
||||||
|
position absolute
|
||||||
|
top 0
|
||||||
|
left 0
|
||||||
|
right 0
|
||||||
|
bottom 0
|
||||||
16
browser/styles/mixins/input.styl
Normal file
16
browser/styles/mixins/input.styl
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
stripInput()
|
||||||
|
border none
|
||||||
|
border-bottom 1px solid borderColor
|
||||||
|
padding 5px 15px
|
||||||
|
transition 0.1s
|
||||||
|
&:focus, &.focus
|
||||||
|
border-bottom 1px solid brandBorderColor
|
||||||
|
outline none
|
||||||
|
|
||||||
|
borderInput()
|
||||||
|
border solid 1px borderColor
|
||||||
|
padding 5px 15px
|
||||||
|
transition 0.1s
|
||||||
|
&:focus, &.focus
|
||||||
|
border-color brandBorderColor
|
||||||
|
outline none
|
||||||
103
browser/styles/mixins/marked.styl
Normal file
103
browser/styles/mixins/marked.styl
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
marked()
|
||||||
|
hr
|
||||||
|
border-top none
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
margin 15px 0
|
||||||
|
h1
|
||||||
|
font-size 2em
|
||||||
|
margin 0 auto 0.67em
|
||||||
|
h2
|
||||||
|
font-size 1.5em
|
||||||
|
margin 0 auto 0.83em
|
||||||
|
h3
|
||||||
|
font-size 1.17em
|
||||||
|
margin 0 auto 1em
|
||||||
|
h4
|
||||||
|
font-size 1em
|
||||||
|
margin 0 auto 1.33em
|
||||||
|
h5
|
||||||
|
font-size 0.83em
|
||||||
|
margin 0 auto 1.67em
|
||||||
|
h6
|
||||||
|
font-size 0.67em
|
||||||
|
margin 2.33em auto
|
||||||
|
h1, h2, h3, h4, h5, h6
|
||||||
|
font-weight 400
|
||||||
|
line-height 1.4em
|
||||||
|
p
|
||||||
|
line-height 1.4em
|
||||||
|
margin-bottom 15px
|
||||||
|
img
|
||||||
|
max-width 100%
|
||||||
|
strong
|
||||||
|
font-weight bold
|
||||||
|
em
|
||||||
|
font-style italic
|
||||||
|
s
|
||||||
|
text-decoration line-through
|
||||||
|
blockquote
|
||||||
|
border-left solid 4px brandBorderColor
|
||||||
|
margin 15px 0 15px
|
||||||
|
padding 0 25px
|
||||||
|
ul
|
||||||
|
list-style-type disc
|
||||||
|
padding-left 35px
|
||||||
|
li
|
||||||
|
display list-item
|
||||||
|
margin 15px 0
|
||||||
|
&>li>ul
|
||||||
|
list-style-type circle
|
||||||
|
&>li>ul
|
||||||
|
list-style-type square
|
||||||
|
ol
|
||||||
|
list-style-type decimal
|
||||||
|
padding-left 35px
|
||||||
|
li
|
||||||
|
display list-item
|
||||||
|
margin 15px 0
|
||||||
|
code
|
||||||
|
font-family monospace
|
||||||
|
padding 2px 4px
|
||||||
|
border solid 1px borderColor
|
||||||
|
border-radius 4px
|
||||||
|
font-size 0.9em
|
||||||
|
color black
|
||||||
|
text-decoration none
|
||||||
|
background-color #F6F6F6
|
||||||
|
pre
|
||||||
|
padding 5px
|
||||||
|
border solid 1px borderColor
|
||||||
|
border-radius 5px
|
||||||
|
overflow-x auto
|
||||||
|
margin-bottom 15px
|
||||||
|
background-color #F6F6F6
|
||||||
|
&>code
|
||||||
|
padding 0
|
||||||
|
border none
|
||||||
|
border-radius 0
|
||||||
|
color black
|
||||||
|
table
|
||||||
|
width 100%
|
||||||
|
margin 15px 0 25px
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
background-color tableHeadBgColor
|
||||||
|
th
|
||||||
|
border-style solid
|
||||||
|
padding 15px 5px
|
||||||
|
border-width 1px 0 2px 1px
|
||||||
|
border-color borderColor
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px borderColor
|
||||||
|
tbody
|
||||||
|
tr:nth-child(2n + 1)
|
||||||
|
background-color tableOddBgColor
|
||||||
|
tr:nth-child(2n)
|
||||||
|
background-color tableEvenBgColor
|
||||||
|
td
|
||||||
|
border-style solid
|
||||||
|
padding 15px 5px
|
||||||
|
border-width 0 0 1px 1px
|
||||||
|
border-color borderColor
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px borderColor
|
||||||
13
browser/styles/mixins/tooltip.styl
Normal file
13
browser/styles/mixins/tooltip.styl
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
tooltip()
|
||||||
|
position fixed
|
||||||
|
z-index popupZIndex
|
||||||
|
background-color transparentify(invBackgroundColor, 80%)
|
||||||
|
color invTextColor
|
||||||
|
padding 10px
|
||||||
|
font-size 12px
|
||||||
|
line-height 12px
|
||||||
|
border-radius 5px
|
||||||
|
white-space nowrap
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
pointer-events none
|
||||||
7
browser/styles/mixins/util.styl
Normal file
7
browser/styles/mixins/util.styl
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
borderBox()
|
||||||
|
box-sizing border-box
|
||||||
|
|
||||||
|
noSelect()
|
||||||
|
-webkit-user-select none
|
||||||
|
-webkit-app-region drag
|
||||||
|
|
||||||
55
browser/styles/shared/btn.styl
Normal file
55
browser/styles/shared/btn.styl
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
.btn-primary, .btn-default
|
||||||
|
border-style solid
|
||||||
|
border-width 1px
|
||||||
|
background-image none
|
||||||
|
height 44px
|
||||||
|
padding 0 15px
|
||||||
|
border-radius 5px
|
||||||
|
box-sizing border-box
|
||||||
|
font-size 1em
|
||||||
|
font-family 'Lato'
|
||||||
|
font-weight 400
|
||||||
|
transition 0.1s
|
||||||
|
cursor pointer
|
||||||
|
margin 0 5px
|
||||||
|
|
||||||
|
.btn-block
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
margin 0 auto
|
||||||
|
|
||||||
|
.btn-square
|
||||||
|
display inline-block
|
||||||
|
width 44px
|
||||||
|
padding 0
|
||||||
|
border-width 1px
|
||||||
|
|
||||||
|
.btn-sm
|
||||||
|
height 32px
|
||||||
|
border-radius 16px
|
||||||
|
&.btn-square
|
||||||
|
width 32px
|
||||||
|
|
||||||
|
.btn-primary
|
||||||
|
border-color brandBorderColor
|
||||||
|
background-color transparent
|
||||||
|
color brandColor
|
||||||
|
&:hover, &.hover, &:focus, &.focus
|
||||||
|
border-color darken(brandBorderColor, 30%)
|
||||||
|
color darken(brandColor, 30%)
|
||||||
|
&:active, &.active
|
||||||
|
background-color brandColor
|
||||||
|
color white
|
||||||
|
|
||||||
|
.btn-default
|
||||||
|
border-color lightButtonColor
|
||||||
|
background-color transparent
|
||||||
|
color lightButtonColor
|
||||||
|
|
||||||
|
&: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
|
||||||
383
browser/styles/shared/modal.styl
Normal file
383
browser/styles/shared/modal.styl
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
.ModalBase
|
||||||
|
fixed top left bottom right
|
||||||
|
z-index modalZIndex
|
||||||
|
&.hide
|
||||||
|
display none
|
||||||
|
.modalBack
|
||||||
|
absolute top left bottom right
|
||||||
|
background-color modalBackColor
|
||||||
|
z-index modalZIndex + 1
|
||||||
|
.modal
|
||||||
|
position relative
|
||||||
|
width 650px
|
||||||
|
margin 50px auto 0
|
||||||
|
z-index modalZIndex + 2
|
||||||
|
box-shadow popupShadow
|
||||||
|
background-color white
|
||||||
|
border-radius 10px
|
||||||
|
padding 15px
|
||||||
|
.modal-header
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
margin-bottom 10px
|
||||||
|
h1
|
||||||
|
padding 10px 0 15px
|
||||||
|
font-size 1.5em
|
||||||
|
.modal-body
|
||||||
|
p
|
||||||
|
margin-bottom 10px
|
||||||
|
.modal-footer
|
||||||
|
clearfix()
|
||||||
|
border-top solid 1px borderColor
|
||||||
|
padding-top 10px
|
||||||
|
.modal-control
|
||||||
|
float right
|
||||||
|
|
||||||
|
.tabModal
|
||||||
|
height 500px
|
||||||
|
.leftPane
|
||||||
|
absolute top bottom left
|
||||||
|
width 175px
|
||||||
|
padding 20px
|
||||||
|
border-right solid 1px borderColor
|
||||||
|
.tabLabel
|
||||||
|
font-size 1.5em
|
||||||
|
margin-top 25px
|
||||||
|
margin-bottom 35px
|
||||||
|
color brandColor
|
||||||
|
.tabList button
|
||||||
|
btnStripDefault()
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
font-size 1.1em
|
||||||
|
padding 10px 5px
|
||||||
|
margin-bottom 15px
|
||||||
|
text-align left
|
||||||
|
.rightPane
|
||||||
|
absolute top bottom right
|
||||||
|
left 175px
|
||||||
|
padding 15px
|
||||||
|
overflow-y auto
|
||||||
|
|
||||||
|
.EditProfileModal, .PlanetSettingModal, .TeamSettingsModal
|
||||||
|
.userInfoTab, .passwordTab, .planetProfileTab, .userInfoTab, .membersTab
|
||||||
|
padding-top 45px
|
||||||
|
.formField
|
||||||
|
position relative
|
||||||
|
clearfix()
|
||||||
|
margin-bottom 15px
|
||||||
|
label
|
||||||
|
width 30%
|
||||||
|
display block
|
||||||
|
line-height 33px
|
||||||
|
float left
|
||||||
|
input
|
||||||
|
width 70%
|
||||||
|
display block
|
||||||
|
borderInput()
|
||||||
|
height 33px
|
||||||
|
font-size 1em
|
||||||
|
border-radius 5px
|
||||||
|
float left
|
||||||
|
.formRadioField
|
||||||
|
margin-bottom 15px
|
||||||
|
input
|
||||||
|
margin-left 25px
|
||||||
|
.formConfirm
|
||||||
|
position relative
|
||||||
|
clearfix()
|
||||||
|
margin-bottom 15px
|
||||||
|
button
|
||||||
|
float right
|
||||||
|
btnDefault()
|
||||||
|
padding 10px 15px
|
||||||
|
border-radius 5px
|
||||||
|
font-size 1em
|
||||||
|
margin-left 5px
|
||||||
|
.alertInfo, .alertSuccess, .alertError
|
||||||
|
float right
|
||||||
|
padding 12px 10px
|
||||||
|
border-radius 5px
|
||||||
|
width 200px
|
||||||
|
font-size 1em
|
||||||
|
overflow-x hidden
|
||||||
|
white-space nowrap
|
||||||
|
transition 0.1s
|
||||||
|
&.hide
|
||||||
|
width 0
|
||||||
|
padding 12px 0
|
||||||
|
.alertInfo
|
||||||
|
alertInfo()
|
||||||
|
.alertSuccess
|
||||||
|
alertSuccess()
|
||||||
|
.alertError
|
||||||
|
alertError()
|
||||||
|
.planetDeleteTab
|
||||||
|
padding-top 65px
|
||||||
|
p
|
||||||
|
margin-bottom 25px
|
||||||
|
strong
|
||||||
|
color brandColor
|
||||||
|
font-size 1.1em
|
||||||
|
input
|
||||||
|
borderInput()
|
||||||
|
margin-right 5px
|
||||||
|
height 33px
|
||||||
|
font-size 1em
|
||||||
|
border-radius 10px
|
||||||
|
.formConfirm
|
||||||
|
position relative
|
||||||
|
clearfix()
|
||||||
|
margin-bottom 15px
|
||||||
|
button
|
||||||
|
float right
|
||||||
|
btnDefault()
|
||||||
|
padding 10px 15px
|
||||||
|
border-radius 5px
|
||||||
|
font-size 1em
|
||||||
|
margin-left 5px
|
||||||
|
.alertInfo, .alertSuccess, .alertError
|
||||||
|
float right
|
||||||
|
padding 12px 10px
|
||||||
|
border-radius 5px
|
||||||
|
width 200px
|
||||||
|
font-size 1em
|
||||||
|
overflow-x hidden
|
||||||
|
white-space nowrap
|
||||||
|
transition 0.1s
|
||||||
|
&.hide
|
||||||
|
width 0
|
||||||
|
padding 12px 0
|
||||||
|
.alertInfo
|
||||||
|
alertInfo()
|
||||||
|
.alertSuccess
|
||||||
|
alertSuccess()
|
||||||
|
.alertError
|
||||||
|
alertError()
|
||||||
|
.membersTab
|
||||||
|
.memberTable
|
||||||
|
width 100%
|
||||||
|
margin-bottom 25px
|
||||||
|
th
|
||||||
|
border-bottom solid 2px borderColor
|
||||||
|
td
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
height 38px
|
||||||
|
button
|
||||||
|
btnDefault()
|
||||||
|
padding 5px
|
||||||
|
border-radius 5px
|
||||||
|
.roleSelect
|
||||||
|
height 33px
|
||||||
|
border solid 1px borderColor
|
||||||
|
background-color backgroundColor
|
||||||
|
th, td
|
||||||
|
padding 5px 0
|
||||||
|
.addMemberForm
|
||||||
|
.formLabel
|
||||||
|
margin-bottom 5px
|
||||||
|
.formGroup
|
||||||
|
clearfix()
|
||||||
|
.userNameSelect
|
||||||
|
display block
|
||||||
|
width 200px
|
||||||
|
margin-right 5px
|
||||||
|
float left
|
||||||
|
.roleSelect
|
||||||
|
display block
|
||||||
|
height 33px
|
||||||
|
border solid 1px borderColor
|
||||||
|
background-color backgroundColor
|
||||||
|
float left
|
||||||
|
margin-right 5px
|
||||||
|
.confirmButton
|
||||||
|
display block
|
||||||
|
height 33px
|
||||||
|
btnDefault()
|
||||||
|
border-radius 5px
|
||||||
|
float left
|
||||||
|
|
||||||
|
.LaunchModal
|
||||||
|
.modal-tab
|
||||||
|
text-align center
|
||||||
|
margin-bottom 10px
|
||||||
|
.btn-primary, .btn-default
|
||||||
|
margin 0
|
||||||
|
border-radius 0
|
||||||
|
border-width 1px
|
||||||
|
width 150px
|
||||||
|
border-radius 0
|
||||||
|
&:nth-child(1)
|
||||||
|
border-right solid 1px borderColor
|
||||||
|
border-top-left-radius 5px
|
||||||
|
border-bottom-left-radius 5px
|
||||||
|
&:nth-child(2)
|
||||||
|
border-left none
|
||||||
|
border-top-right-radius 5px
|
||||||
|
border-bottom-right-radius 5px
|
||||||
|
.Select
|
||||||
|
.Select-control
|
||||||
|
border-color borderColor
|
||||||
|
&.is-focused
|
||||||
|
.Select-control
|
||||||
|
border-color brandBorderColor
|
||||||
|
.Select-menu-outer
|
||||||
|
border-color borderColor
|
||||||
|
.ace_editor
|
||||||
|
border-radius 5px
|
||||||
|
border solid 1px borderColor
|
||||||
|
.CodeForm, .NoteForm
|
||||||
|
.form-group
|
||||||
|
margin-bottom 10px
|
||||||
|
.CodeForm
|
||||||
|
textarea.codeDescription
|
||||||
|
height 75px
|
||||||
|
font-size 0.9em
|
||||||
|
margin-bottom 10px
|
||||||
|
.modeSelect.Select
|
||||||
|
display inline-block
|
||||||
|
width 200px
|
||||||
|
height 37px
|
||||||
|
.Select-control
|
||||||
|
height 37px
|
||||||
|
.ace_editor
|
||||||
|
height 258px
|
||||||
|
.NoteForm
|
||||||
|
.ace_editor
|
||||||
|
height 358px
|
||||||
|
.previewMode
|
||||||
|
absolute top right
|
||||||
|
font-size 0.8em
|
||||||
|
line-height 24px
|
||||||
|
padding 5 15px
|
||||||
|
background-color transparentify(invBackgroundColor, 0.2)
|
||||||
|
color invTextColor
|
||||||
|
border-top-right-radius 5px
|
||||||
|
.marked
|
||||||
|
height 360px
|
||||||
|
overflow-x hidden
|
||||||
|
overflow-y auto
|
||||||
|
box-sizing border-box
|
||||||
|
padding 5px
|
||||||
|
border solid 1px borderColor
|
||||||
|
border-radius 5px
|
||||||
|
marked()
|
||||||
|
|
||||||
|
.AboutModal
|
||||||
|
width 320px
|
||||||
|
.about1
|
||||||
|
margin-bottom 25px
|
||||||
|
.logo
|
||||||
|
display block
|
||||||
|
margin 0 auto
|
||||||
|
.appInfo
|
||||||
|
font-size 1.5em
|
||||||
|
text-align center
|
||||||
|
.about2
|
||||||
|
width 200px
|
||||||
|
margin 0 auto
|
||||||
|
.externalLabel
|
||||||
|
font-size 1.2em
|
||||||
|
margin-bottom 15px
|
||||||
|
.externalList
|
||||||
|
li
|
||||||
|
margin-bottom 15px
|
||||||
|
|
||||||
|
.PlanetCreateModal.modal, .TeamCreateModal.modal, .AddMemberModal.modal
|
||||||
|
padding 60px 0
|
||||||
|
.nameInput
|
||||||
|
width 80%
|
||||||
|
font-size 1.3em
|
||||||
|
margin 25px auto 15px
|
||||||
|
text-align center
|
||||||
|
.userNameSelect
|
||||||
|
width 80%
|
||||||
|
font-size 1.3em
|
||||||
|
margin 35px auto
|
||||||
|
text-align center
|
||||||
|
.formField
|
||||||
|
text-align center
|
||||||
|
margin 0 auto 25px
|
||||||
|
select
|
||||||
|
display inline-block
|
||||||
|
width 150px
|
||||||
|
height 33px
|
||||||
|
border solid 1px borderColor
|
||||||
|
background-color white
|
||||||
|
padding 0 10px
|
||||||
|
margin 0 15px
|
||||||
|
.submitButton
|
||||||
|
display block
|
||||||
|
margin 0 auto
|
||||||
|
box-sizing border-box
|
||||||
|
width 55px
|
||||||
|
height 55px
|
||||||
|
circle()
|
||||||
|
btnPrimary()
|
||||||
|
.errorAlert
|
||||||
|
alertError()
|
||||||
|
padding 12px 10px
|
||||||
|
border-radius 5px
|
||||||
|
text-align center
|
||||||
|
display block
|
||||||
|
width 360px
|
||||||
|
margin 0 auto 15px
|
||||||
|
|
||||||
|
.ContactModal
|
||||||
|
padding 15px
|
||||||
|
.contactForm
|
||||||
|
.formField
|
||||||
|
width 100%
|
||||||
|
margin-bottom 10px
|
||||||
|
input, textarea
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
borderInput()
|
||||||
|
border-radius 5px
|
||||||
|
input
|
||||||
|
height 33px
|
||||||
|
font-size 1em
|
||||||
|
textarea
|
||||||
|
height 175px
|
||||||
|
font-size 1em
|
||||||
|
.formControl
|
||||||
|
clearfix()
|
||||||
|
button
|
||||||
|
float right
|
||||||
|
btnDefault()
|
||||||
|
height 44px
|
||||||
|
padding 0 15px
|
||||||
|
border-radius 5px
|
||||||
|
margin-left 5px
|
||||||
|
font-size 1em
|
||||||
|
button.sendButton
|
||||||
|
btnPrimary()
|
||||||
|
.confirmation
|
||||||
|
.confirmationMessage
|
||||||
|
padding 35px 0
|
||||||
|
text-align center
|
||||||
|
font-size 1.1em
|
||||||
|
.doneButton
|
||||||
|
btnDefault()
|
||||||
|
height 44px
|
||||||
|
padding 0 35px
|
||||||
|
border-radius 5px
|
||||||
|
display block
|
||||||
|
margin 0 auto 25px
|
||||||
|
|
||||||
|
.LogoutModal
|
||||||
|
padding 65px 0 45px
|
||||||
|
width 350px
|
||||||
|
.messageLabel
|
||||||
|
text-align center
|
||||||
|
font-size 1.1em
|
||||||
|
margin-bottom 35px
|
||||||
|
.formControl
|
||||||
|
text-align center
|
||||||
|
button
|
||||||
|
btnDefault()
|
||||||
|
border-radius 5px
|
||||||
|
height 44px
|
||||||
|
margin 15px 5px
|
||||||
|
padding 0 15px
|
||||||
|
button.logoutButton
|
||||||
|
btnPrimary()
|
||||||
51
browser/styles/vars.styl
Normal file
51
browser/styles/vars.styl
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
borderColor = #E8E8E8
|
||||||
|
highlightenBorderColor = darken(borderColor, 20%)
|
||||||
|
invBorderColor = #404849
|
||||||
|
brandBorderColor = #3FB399
|
||||||
|
|
||||||
|
buttonBorderColor = #4C4C4C
|
||||||
|
|
||||||
|
lightButtonColor = #898989
|
||||||
|
|
||||||
|
hoverBackgroundColor= transparentify(#444, 4%)
|
||||||
|
|
||||||
|
inactiveTextColor = #888
|
||||||
|
textColor = #4D4D4D
|
||||||
|
backgroundColor= white
|
||||||
|
fontSize= 14px
|
||||||
|
|
||||||
|
shadowColor= #C5C5C5
|
||||||
|
|
||||||
|
invBackgroundColor = #4C4C4C
|
||||||
|
invTextColor = white
|
||||||
|
|
||||||
|
btnColor = #888
|
||||||
|
btnHighlightenColor = #000
|
||||||
|
|
||||||
|
brandColor = #2BAC8F
|
||||||
|
|
||||||
|
planetNavBgColor = #ECECEC
|
||||||
|
planetAnchorColor = #979797
|
||||||
|
planetAnchorBgColor = #BEBEBE
|
||||||
|
planetAnchorActiveColor = textColor
|
||||||
|
planetAnchorActiveBgColor = white
|
||||||
|
|
||||||
|
popupShadow = 0 0 5px 0 #888
|
||||||
|
modalBackColor = transparentify(white, 65%)
|
||||||
|
|
||||||
|
tableHeadBgColor = white
|
||||||
|
tableOddBgColor = #F9F9F9
|
||||||
|
tableEvenBgColor = white
|
||||||
|
|
||||||
|
facebookColor= #3b5998
|
||||||
|
githubBtn= #201F1F
|
||||||
|
|
||||||
|
successBackgroundColor= #E0F0D9
|
||||||
|
successTextColor= #3E753F
|
||||||
|
errorBackgroundColor= #F2DEDE
|
||||||
|
errorTextColor= #A64444
|
||||||
|
infoBackgroundColor= #D9EDF7
|
||||||
|
infoTextColor= #34708E
|
||||||
|
|
||||||
|
modalZIndex= 1000
|
||||||
|
popupZIndex= 500
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
vendors: [
|
|
||||||
{
|
|
||||||
name: 'ace',
|
|
||||||
src: 'node_modules/@rokt33r/ace-builds/src/**/*'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'angular',
|
|
||||||
src: 'node_modules/angular/angular.js'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'angular-bootstrap',
|
|
||||||
src: 'node_modules/angular-bootstrap/dist/ui-bootstrap-tpls.js'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'angular-sanitize',
|
|
||||||
src: 'node_modules/angular-sanitize/angular-sanitize.js'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'angular-ui-router',
|
|
||||||
src: 'node_modules/angular-ui-router/build/angular-ui-router.js'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ui-select',
|
|
||||||
src: 'node_modules/ui-select/dist/select.js'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'satellizer',
|
|
||||||
src: 'node_modules/satellizer/satellizer.js'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'angular-md5',
|
|
||||||
src: 'node_modules/angular-md5/angular-md5.js'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'moment',
|
|
||||||
src: 'node_modules/moment/moment.js'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'angular-hotkeys',
|
|
||||||
src: 'node_modules/angular-hotkeys/build/hotkeys.js'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'marked',
|
|
||||||
src: 'node_modules/marked/lib/marked.js'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
4
config.js
Normal file
4
config.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
apiUrl: 'http://codexen-server-dev2.elasticbeanstalk.com/'
|
||||||
|
// apiUrl: 'http://localhost:8000/'
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# Event List
|
|
||||||
|
|
||||||
|name|Description|Delivery|
|
|
||||||
|----|----|----|
|
|
||||||
|userSignIn|a user signed in||
|
|
||||||
|userSignOut|a user signed out||
|
|
||||||
|snippetUpdated|snippet has been updated or created|snippet|
|
|
||||||
191
main.js
Normal file
191
main.js
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
var app = require('app')
|
||||||
|
var BrowserWindow = require('browser-window')
|
||||||
|
var Menu = require('menu')
|
||||||
|
var MenuItem = require('menu-item')
|
||||||
|
var Tray = require('tray')
|
||||||
|
var ipc = require('ipc')
|
||||||
|
|
||||||
|
require('crash-reporter').start()
|
||||||
|
|
||||||
|
var mainWindow = null
|
||||||
|
var appIcon = null
|
||||||
|
var menu = null
|
||||||
|
var popUpWindow = null
|
||||||
|
|
||||||
|
var update = null
|
||||||
|
|
||||||
|
// app.on('window-all-closed', function () {
|
||||||
|
// if (process.platform !== 'darwin') app.quit()
|
||||||
|
// })
|
||||||
|
|
||||||
|
var version = app.getVersion()
|
||||||
|
global.version = version
|
||||||
|
var versionText = (version == null || version.length === 0) ? 'DEV version' : 'v' + version
|
||||||
|
var nn = require('node-notifier')
|
||||||
|
var autoUpdater = require('auto-updater')
|
||||||
|
var path = require('path')
|
||||||
|
|
||||||
|
autoUpdater
|
||||||
|
.on('error', function (err, message) {
|
||||||
|
nn.notify({
|
||||||
|
title: 'Error! ' + versionText,
|
||||||
|
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
|
||||||
|
message: message
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.on('checking-for-update', function () {
|
||||||
|
nn.notify({
|
||||||
|
title: 'Boost launched!! ' + versionText,
|
||||||
|
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
|
||||||
|
message: 'Checking update is available....'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.on('update-available', function () {
|
||||||
|
nn.notify({
|
||||||
|
title: 'Update is available!! ' + versionText,
|
||||||
|
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
|
||||||
|
message: 'Download started.. wait for the update ready.'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.on('update-not-available', function () {
|
||||||
|
nn.notify({
|
||||||
|
title: 'Latest Build!! ' + versionText,
|
||||||
|
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
|
||||||
|
message: 'Hope you to enjoy our app :D'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
|
||||||
|
nn.notify({
|
||||||
|
title: 'Ready to Update!! ' + versionText,
|
||||||
|
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
|
||||||
|
message: 'Click tray icon to update app: ' + releaseName
|
||||||
|
})
|
||||||
|
update = quitAndUpdate
|
||||||
|
|
||||||
|
if (mainWindow != null && !mainWindow.webContents.isLoading()) {
|
||||||
|
mainWindow.webContents.send('update-available', 'Update available!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.on('ready', function () {
|
||||||
|
console.log('Version ' + version)
|
||||||
|
autoUpdater.setFeedUrl('http://orbital.b00st.io/rokt33r/boost/latest?version=' + version)
|
||||||
|
autoUpdater.checkForUpdates()
|
||||||
|
// menu start
|
||||||
|
var template = require('./modules/menu-template')
|
||||||
|
|
||||||
|
ipc.on('update-app', function (event, msg) {
|
||||||
|
if (update != null) {
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
menu = Menu.buildFromTemplate(template)
|
||||||
|
|
||||||
|
Menu.setApplicationMenu(menu)
|
||||||
|
// menu end
|
||||||
|
appIcon = new Tray(__dirname + '/tray-icon.png')
|
||||||
|
appIcon.setToolTip('Boost')
|
||||||
|
|
||||||
|
var trayMenu = new Menu()
|
||||||
|
trayMenu.append(new MenuItem({
|
||||||
|
label: 'Open main window',
|
||||||
|
click: function () {
|
||||||
|
if (mainWindow == null) {
|
||||||
|
makeNewMainWindow()
|
||||||
|
}
|
||||||
|
mainWindow.show()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
trayMenu.append(new MenuItem({
|
||||||
|
label: 'Update App',
|
||||||
|
click: function () {
|
||||||
|
if (update != null) {
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
trayMenu.append(new MenuItem({
|
||||||
|
label: 'Quit',
|
||||||
|
click: function () {
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
appIcon.setContextMenu(trayMenu)
|
||||||
|
|
||||||
|
makeNewMainWindow()
|
||||||
|
|
||||||
|
app.on('activate-with-no-open-windows', function () {
|
||||||
|
if (mainWindow == null) {
|
||||||
|
makeNewMainWindow()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mainWindow.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
popUpWindow = new BrowserWindow({
|
||||||
|
width: 600,
|
||||||
|
height: 400,
|
||||||
|
show: false,
|
||||||
|
frame: false,
|
||||||
|
'zoom-factor': 1.0,
|
||||||
|
'always-on-top': true,
|
||||||
|
'web-preferences': {
|
||||||
|
'overlay-scrollbars': true,
|
||||||
|
'skip-taskbar': true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
popUpWindow.loadUrl('file://' + __dirname + '/browser/finder/index.electron.html')
|
||||||
|
|
||||||
|
popUpWindow.on('blur', function () {
|
||||||
|
popUpWindow.hide()
|
||||||
|
})
|
||||||
|
popUpWindow.setVisibleOnAllWorkspaces(true)
|
||||||
|
|
||||||
|
var globalShortcut = require('global-shortcut')
|
||||||
|
|
||||||
|
globalShortcut.register('ctrl+tab+shift', function () {
|
||||||
|
if (mainWindow != null && !mainWindow.isFocused()) {
|
||||||
|
mainWindow.hide()
|
||||||
|
}
|
||||||
|
popUpWindow.show()
|
||||||
|
})
|
||||||
|
|
||||||
|
global.hideFinder = function () {
|
||||||
|
if (mainWindow == null || !mainWindow.isVisible()) {
|
||||||
|
Menu.sendActionToFirstResponder('hide:')
|
||||||
|
}
|
||||||
|
popUpWindow.hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function makeNewMainWindow () {
|
||||||
|
console.log('new Window!')
|
||||||
|
mainWindow = new BrowserWindow({
|
||||||
|
width: 1080,
|
||||||
|
height: 720,
|
||||||
|
'zoom-factor': 1.0,
|
||||||
|
'web-preferences': {
|
||||||
|
'overlay-scrollbars': true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (update != null) {
|
||||||
|
mainWindow.webContents.on('did-finish-load', function () {
|
||||||
|
mainWindow.webContents.send('update-available', 'whoooooooh!')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mainWindow.loadUrl('file://' + __dirname + '/browser/main/index.electron.html')
|
||||||
|
|
||||||
|
mainWindow.webContents.on('new-window', function (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
|
mainWindow.on('closed', function () {
|
||||||
|
console.log('main closed')
|
||||||
|
mainWindow = null
|
||||||
|
app.dock.hide()
|
||||||
|
})
|
||||||
|
app.dock.show()
|
||||||
|
}
|
||||||
10
modules/ace-modes.js
Normal file
10
modules/ace-modes.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
var fs = require('fs')
|
||||||
|
|
||||||
|
module.exports = fs.readdirSync(__dirname + '/../browser/ace/src-min')
|
||||||
|
.filter(function (file) {
|
||||||
|
return file.match(/^mode-/)
|
||||||
|
})
|
||||||
|
.map(function (file) {
|
||||||
|
var match = file.match(/^mode-([a-z0-9\_]+).js$/)
|
||||||
|
return match[1]
|
||||||
|
})
|
||||||
128
modules/menu-template.js
Normal file
128
modules/menu-template.js
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
var BrowserWindow = require('browser-window')
|
||||||
|
|
||||||
|
module.exports = [
|
||||||
|
{
|
||||||
|
label: 'Electron',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'About Electron',
|
||||||
|
selector: 'orderFrontStandardAboutPanel:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Services',
|
||||||
|
submenu: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hide Electron',
|
||||||
|
accelerator: 'Command+H',
|
||||||
|
selector: 'hide:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Hide Others',
|
||||||
|
accelerator: 'Command+Shift+H',
|
||||||
|
selector: 'hideOtherApplications:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Show All',
|
||||||
|
selector: 'unhideAllApplications:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
accelerator: 'Command+Q',
|
||||||
|
selector: 'terminate:'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Edit',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Undo',
|
||||||
|
accelerator: 'Command+Z',
|
||||||
|
selector: 'undo:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Redo',
|
||||||
|
accelerator: 'Shift+Command+Z',
|
||||||
|
selector: 'redo:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Cut',
|
||||||
|
accelerator: 'Command+X',
|
||||||
|
selector: 'cut:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Copy',
|
||||||
|
accelerator: 'Command+C',
|
||||||
|
selector: 'copy:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Paste',
|
||||||
|
accelerator: 'Command+V',
|
||||||
|
selector: 'paste:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Select All',
|
||||||
|
accelerator: 'Command+A',
|
||||||
|
selector: 'selectAll:'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'View',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Reload',
|
||||||
|
accelerator: 'Command+R',
|
||||||
|
click: function () {
|
||||||
|
BrowserWindow.getFocusedWindow().reload()
|
||||||
|
}
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// label: 'Toggle DevTools',
|
||||||
|
// accelerator: 'Alt+Command+I',
|
||||||
|
// click: function () {
|
||||||
|
// BrowserWindow.getFocusedWindow().toggleDevTools()
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Window',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Minimize',
|
||||||
|
accelerator: 'Command+M',
|
||||||
|
selector: 'performMiniaturize:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Close',
|
||||||
|
accelerator: 'Command+W',
|
||||||
|
selector: 'performClose:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'separator'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Bring All to Front',
|
||||||
|
selector: 'arrangeInFront:'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Help',
|
||||||
|
submenu: []
|
||||||
|
}
|
||||||
|
]
|
||||||
90
package.json
90
package.json
@@ -1,24 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "codexen-app-builder",
|
"name": "boost",
|
||||||
"version": "0.2.0",
|
"version": "0.2.7",
|
||||||
"description": "CodeXen App Builder",
|
"description": "Boost App",
|
||||||
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "gulp build",
|
"start": "electron ./main.js",
|
||||||
"start": "http-server build",
|
"web": "npm run serve | npm run dev",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"serve": "./node_modules/.bin/http-server ./browser -p 8080",
|
||||||
|
"dev": "webpack-dev-server --progress --colors --port 8090"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/Rokt33r/codexen-app.git"
|
"url": "git+https://github.com/Rokt33r/codexen-app.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"codexen",
|
"boost",
|
||||||
|
"b00st",
|
||||||
"snippet",
|
"snippet",
|
||||||
"template",
|
"template",
|
||||||
"task",
|
"task",
|
||||||
"runner",
|
"runner",
|
||||||
"remote",
|
|
||||||
"automator",
|
|
||||||
"code",
|
"code",
|
||||||
"storage",
|
"storage",
|
||||||
"short code"
|
"short code"
|
||||||
@@ -30,48 +31,37 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/Rokt33r/codexen-app#readme",
|
"homepage": "https://github.com/Rokt33r/codexen-app#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^1.1.0",
|
"electron-stylus": "^0.1.0",
|
||||||
"robotjs": "^0.1.2",
|
"font-awesome": "^4.3.0",
|
||||||
"node-notifier": "^4.2.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",
|
||||||
|
"reflux": "^0.2.8",
|
||||||
|
"superagent": "^1.2.0",
|
||||||
|
"superagent-promise": "^1.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rokt33r/ace-builds": "^1.1.9",
|
"css-loader": "^0.15.1",
|
||||||
"@rokt33r/angular-ui-ace": "^0.2.3",
|
"http-server": "^0.8.0",
|
||||||
"angular": "^1.3.15",
|
"jsx-loader": "^0.13.2",
|
||||||
"angular-bootstrap": "^0.12.0",
|
"node-libs-browser": "^0.5.2",
|
||||||
"angular-hotkeys": "^1.4.5",
|
"style-loader": "^0.12.3",
|
||||||
"angular-md5": "^0.1.7",
|
"stylus-loader": "^1.2.1",
|
||||||
"angular-sanitize": "^1.3.15",
|
"webpack": "^1.10.0",
|
||||||
"angular-ui-router": "^0.2.15",
|
"webpack-dev-server": "^1.10.1"
|
||||||
"bootstrap-styl": "^4.0.4",
|
},
|
||||||
"del": "^1.2.0",
|
"standard": {
|
||||||
"font-awesome": "^4.3.0",
|
"ignore": [
|
||||||
"globby": "^2.0.0",
|
"/browser/ace/"
|
||||||
"gulp": "^3.8.11",
|
],
|
||||||
"gulp-angular-templatecache": "^1.6.0",
|
"global": [
|
||||||
"gulp-autoprefixer": "^2.3.0",
|
"localStorage"
|
||||||
"gulp-cached": "^1.1.0",
|
]
|
||||||
"gulp-changed": "^1.2.1",
|
|
||||||
"gulp-concat": "^2.5.2",
|
|
||||||
"gulp-inject": "^1.3.1",
|
|
||||||
"gulp-livereload": "^3.8.0",
|
|
||||||
"gulp-minify-css": "^1.1.1",
|
|
||||||
"gulp-minify-html": "^1.0.3",
|
|
||||||
"gulp-ng-annotate": "^0.5.3",
|
|
||||||
"gulp-notify": "^2.2.0",
|
|
||||||
"gulp-plumber": "^1.0.1",
|
|
||||||
"gulp-remember": "^0.3.0",
|
|
||||||
"gulp-rename": "^1.2.2",
|
|
||||||
"gulp-rev": "^4.0.0",
|
|
||||||
"gulp-stylus": "^2.0.3",
|
|
||||||
"gulp-template": "^3.0.0",
|
|
||||||
"gulp-uglify": "^1.2.0",
|
|
||||||
"marked": "^0.3.3",
|
|
||||||
"merge-stream": "^0.1.7",
|
|
||||||
"moment": "^2.10.3",
|
|
||||||
"run-sequence": "^1.1.0",
|
|
||||||
"satellizer": "^0.10.1",
|
|
||||||
"streamqueue": "^1.1.0",
|
|
||||||
"ui-select": "^0.11.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# CodeXen app
|
# CodeXen app 0.2.0
|
||||||

|

|
||||||
|
|
||||||
Short code(Snippet/Templatefile/Command) storage + boosting service
|
Short code(Snippet/Templatefile/Command) storage + boosting service
|
||||||
|
|||||||
BIN
src/app-logo.png
BIN
src/app-logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
@@ -1,13 +0,0 @@
|
|||||||
/* global angular */
|
|
||||||
angular.module('codexen', [
|
|
||||||
'codexen.shared',
|
|
||||||
'ngSanitize',
|
|
||||||
'ui.select',
|
|
||||||
'ui.ace',
|
|
||||||
'ui.router',
|
|
||||||
'ui.bootstrap',
|
|
||||||
'satellizer',
|
|
||||||
'angular-md5',
|
|
||||||
'templates'])
|
|
||||||
.constant('appName', 'main')
|
|
||||||
angular.module('templates', [])
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
/* global angular */
|
|
||||||
angular.module('codexen')
|
|
||||||
.config(function ($stateProvider, $urlRouterProvider, $httpProvider) {
|
|
||||||
$httpProvider.interceptors.push(function ($q, $injector) {
|
|
||||||
return {
|
|
||||||
responseError: function (res) {
|
|
||||||
switch (res.status) {
|
|
||||||
case 401:
|
|
||||||
var $state = $injector.get('$state')
|
|
||||||
$state.go('auth.signin')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return $q.reject(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
$urlRouterProvider
|
|
||||||
.when('/auth', '/auth/register')
|
|
||||||
.when('/auth/', '/auth/register')
|
|
||||||
.otherwise('/')
|
|
||||||
|
|
||||||
$stateProvider
|
|
||||||
/* Auth */
|
|
||||||
.state('auth', {
|
|
||||||
url: '/auth',
|
|
||||||
views: {
|
|
||||||
'main-view': {
|
|
||||||
templateUrl: 'tpls/states/auth.tpl.html'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.state('auth.register', {
|
|
||||||
url: '/register',
|
|
||||||
templateUrl: 'tpls/states/auth.register.tpl.html',
|
|
||||||
controller: 'AuthRegisterController as vm'
|
|
||||||
})
|
|
||||||
.state('auth.signin', {
|
|
||||||
url: '/signin',
|
|
||||||
templateUrl: 'tpls/states/auth.signin.tpl.html',
|
|
||||||
controller: 'AuthSignInController as vm'
|
|
||||||
})
|
|
||||||
|
|
||||||
.state('settings', {
|
|
||||||
url: '/settings',
|
|
||||||
views: {
|
|
||||||
'main-view': {
|
|
||||||
templateUrl: 'tpls/states/settings.tpl.html',
|
|
||||||
controller: 'SettingsController as vm'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Snippets */
|
|
||||||
.state('snippets', {
|
|
||||||
url: '/snippets',
|
|
||||||
views: {
|
|
||||||
'main-view': {
|
|
||||||
templateUrl: 'tpls/states/snippets.list.tpl.html',
|
|
||||||
controller: 'SnippetsListController as vm'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
mySnippets: function (Snippet) {
|
|
||||||
return Snippet.findMine().then(function (res) {
|
|
||||||
return res.data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.state('snippets.detail', {
|
|
||||||
url: '/:id',
|
|
||||||
templateUrl: 'tpls/states/snippets.detail.tpl.html',
|
|
||||||
controller: 'SnippetsDetailController as vm'
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Home */
|
|
||||||
.state('home', {
|
|
||||||
url: '/',
|
|
||||||
views: {
|
|
||||||
'main-view': {
|
|
||||||
templateUrl: 'tpls/states/home.tpl.html',
|
|
||||||
controller: 'HomeController as vm'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/* Recipes */
|
|
||||||
.state('recipes', {
|
|
||||||
url: '/recipes',
|
|
||||||
views: {
|
|
||||||
'main-view': {
|
|
||||||
templateUrl: 'tpls/states/recipes.list.tpl.html',
|
|
||||||
controller: 'RecipesListController as vm'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
myRecipes: function (Recipe) {
|
|
||||||
return Recipe.findMine().then(function (res) {
|
|
||||||
return res.data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.state('recipes.detail', {
|
|
||||||
url: '/:id',
|
|
||||||
templateUrl: 'tpls/states/recipes.detail.html',
|
|
||||||
controller: 'RecipesDetailController as vm'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
/* global angular */
|
|
||||||
angular.module('codexen')
|
|
||||||
.controller('AppController', function ($scope) {})
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
/* global angular */
|
|
||||||
angular.module('codexen')
|
|
||||||
.controller('SideNavController', function ($auth, User, $rootScope, $scope, Modal) {
|
|
||||||
var vm = this
|
|
||||||
|
|
||||||
vm.isAuthenticated = $auth.isAuthenticated()
|
|
||||||
|
|
||||||
var reloadUser = function () {
|
|
||||||
if (vm.isAuthenticated) {
|
|
||||||
User.me().success(function (data) {
|
|
||||||
console.log('currentUser', data)
|
|
||||||
vm.currentUser = data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reloadUser()
|
|
||||||
|
|
||||||
vm.signOut = function () {
|
|
||||||
Modal.signOut()
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.$on('userSignIn', function () {
|
|
||||||
vm.isAuthenticated = true
|
|
||||||
reloadUser()
|
|
||||||
})
|
|
||||||
|
|
||||||
$scope.$on('userSignOut', function () {
|
|
||||||
vm.isAuthenticated = false
|
|
||||||
vm.currentUser = null
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
/* global angular */
|
|
||||||
angular.module('codexen')
|
|
||||||
.controller('DeleteRecipeModalController', function ($modalInstance, Recipe, recipe) {
|
|
||||||
var vm = this
|
|
||||||
|
|
||||||
vm.submit = function () {
|
|
||||||
Recipe.delete(recipe.id)
|
|
||||||
.success(function (recipe) {
|
|
||||||
$modalInstance.close(recipe)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.cancel = function () {
|
|
||||||
$modalInstance.dismiss()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
/* global angular */
|
|
||||||
angular.module('codexen')
|
|
||||||
.controller('DeleteSnippetModalController', function ($modalInstance, Snippet, snippet) {
|
|
||||||
var vm = this
|
|
||||||
|
|
||||||
vm.submit = function () {
|
|
||||||
Snippet.delete(snippet.id)
|
|
||||||
.success(function (snippet) {
|
|
||||||
$modalInstance.close(snippet)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.cancel = function () {
|
|
||||||
$modalInstance.dismiss()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/* global angular */
|
|
||||||
angular.module('codexen')
|
|
||||||
.controller('EditRecipeModalController', function (Recipe, Tag, $modalInstance, recipe) {
|
|
||||||
var vm = this
|
|
||||||
|
|
||||||
vm.recipe = recipe
|
|
||||||
|
|
||||||
vm.submit = function () {
|
|
||||||
var params = {
|
|
||||||
title: vm.recipe.title,
|
|
||||||
content: vm.recipe.content,
|
|
||||||
Tags: angular.isArray(vm.recipe.Tags) ? vm.recipe.Tags.map(function (tag) { return tag.name }) : []
|
|
||||||
}
|
|
||||||
|
|
||||||
Recipe.update(vm.recipe.id, params)
|
|
||||||
.success(function (data) {
|
|
||||||
$modalInstance.close(data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// vm.tags = []
|
|
||||||
vm.tagCandidates = []
|
|
||||||
vm.refreshTagCandidates = function (tagName) {
|
|
||||||
if (tagName == null || tagName === '') return null
|
|
||||||
return Tag.findByName(tagName)
|
|
||||||
.success(function (data) {
|
|
||||||
console.log('tags fetched!!', data)
|
|
||||||
vm.tagCandidates = data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
vm.transform = function (tagName) {
|
|
||||||
return {
|
|
||||||
id: 0,
|
|
||||||
name: tagName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.cancel = function () {
|
|
||||||
$modalInstance.dismiss()
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/* global angular */
|
|
||||||
angular.module('codexen')
|
|
||||||
.controller('EditSnippetModalController', function ($modalInstance, aceModes, $log, Snippet, $rootScope, Tag, snippet) {
|
|
||||||
var vm = this
|
|
||||||
|
|
||||||
vm.aceModes = aceModes
|
|
||||||
vm.snippet = snippet
|
|
||||||
|
|
||||||
vm.submit = function () {
|
|
||||||
var params = {
|
|
||||||
description: vm.snippet.description,
|
|
||||||
callSign: vm.snippet.callSign,
|
|
||||||
mode: vm.snippet.mode == null ? null : vm.snippet.mode.toLowerCase(),
|
|
||||||
content: vm.snippet.content,
|
|
||||||
Tags: angular.isArray(vm.snippet.Tags) ? vm.snippet.Tags.map(function (tag) { return tag.name }) : []
|
|
||||||
}
|
|
||||||
Snippet.update(vm.snippet.id, params)
|
|
||||||
.success(function (data) {
|
|
||||||
console.log('updated res :', data)
|
|
||||||
$rootScope.$broadcast('snippetUpdated', snippet)
|
|
||||||
$modalInstance.close(data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// vm.tags = []
|
|
||||||
vm.tagCandidates = []
|
|
||||||
vm.refreshTagCandidates = function (tagName) {
|
|
||||||
if (tagName == null || tagName === '') return null
|
|
||||||
return Tag.findByName(tagName)
|
|
||||||
.success(function (data) {
|
|
||||||
console.log('tags fetched!!', data)
|
|
||||||
vm.tagCandidates = data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
vm.transform = function (tagName) {
|
|
||||||
return {
|
|
||||||
id: 0,
|
|
||||||
name: tagName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.cancel = function () {
|
|
||||||
$modalInstance.dismiss()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user