mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 02:06:29 +00:00
Compare commits
110 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
1a832c1fc4 | ||
|
|
ee139ca36d | ||
|
|
549ce7f299 | ||
|
|
c72b5449bd | ||
|
|
debfa6323b | ||
|
|
0e6fe35ca4 | ||
|
|
7c2cbfb32e | ||
|
|
25eccacb4c |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
/build
|
||||
/node_modules
|
||||
/electron_build
|
||||
build/
|
||||
node_modules/
|
||||
electron_build/
|
||||
.env
|
||||
dist/
|
||||
vendor/
|
||||
|
||||
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
|
||||
161
Gulpfile.js
161
Gulpfile.js
@@ -1,161 +0,0 @@
|
||||
require('dotenv').load()
|
||||
var env = process.env
|
||||
|
||||
var gulp = require('gulp')
|
||||
var styl = require('gulp-stylus')
|
||||
var autoprefixer = require('gulp-autoprefixer')
|
||||
var templateCache = require('gulp-angular-templatecache')
|
||||
var globby = require('globby')
|
||||
var template = require('gulp-template')
|
||||
var del = require('del')
|
||||
var runSequence = require('run-sequence')
|
||||
var plumber = require('gulp-plumber')
|
||||
var notify = require('gulp-notify')
|
||||
var changed = require('gulp-changed')
|
||||
var livereload = require('gulp-livereload')
|
||||
|
||||
// for Dist
|
||||
var rev = require('gulp-rev')
|
||||
var ngAnnotate = require('gulp-ng-annotate')
|
||||
var uglify = require('gulp-uglify')
|
||||
var minifyCss = require('gulp-minify-css')
|
||||
var merge = require('merge-stream')
|
||||
var concat = require('gulp-concat')
|
||||
var streamqueue = require('streamqueue')
|
||||
var minifyHtml = require('gulp-minify-html')
|
||||
|
||||
var config = require('./build.config.js')
|
||||
|
||||
gulp.task('js', function () {
|
||||
return streamqueue({objectMode: true},
|
||||
gulp.src('tpls/env.js')
|
||||
.pipe(template({
|
||||
apiUrl: env.BUILD_API_URL
|
||||
})),
|
||||
gulp.src(['src/**/*.js'])
|
||||
)
|
||||
.pipe(changed('build'))
|
||||
.pipe(gulp.dest('build'))
|
||||
})
|
||||
|
||||
gulp.task('dist', function () {
|
||||
var js = streamqueue({objectMode: true},
|
||||
gulp.src(['src/**/*.js']),
|
||||
gulp.src('tpls/env.js')
|
||||
.pipe(template({
|
||||
apiUrl: env.DIST_API_URL
|
||||
})),
|
||||
gulp.src('src/**/*.tpl.html')
|
||||
.pipe(templateCache())
|
||||
)
|
||||
.pipe(ngAnnotate())
|
||||
.pipe(uglify())
|
||||
.pipe(concat('app.js'))
|
||||
.pipe(gulp.dest('dist'))
|
||||
|
||||
var css = gulp.src('src/styles/main.styl')
|
||||
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
|
||||
.pipe(styl())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(minifyCss())
|
||||
.pipe(gulp.dest('dist'))
|
||||
|
||||
var index = gulp.src('src/index.html')
|
||||
.pipe(template({
|
||||
scripts: ['app.js'],
|
||||
styles: ['main.css'],
|
||||
env: 'dist'
|
||||
}))
|
||||
.pipe(minifyHtml())
|
||||
.pipe(gulp.dest('dist'))
|
||||
|
||||
return merge(js, css, index)
|
||||
})
|
||||
|
||||
gulp.task('styl', function () {
|
||||
return gulp.src('src/styles/main.styl')
|
||||
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
|
||||
.pipe(styl())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(gulp.dest('build'))
|
||||
.pipe(notify('Stylus!!'))
|
||||
.pipe(livereload())
|
||||
})
|
||||
|
||||
gulp.task('tpls', function () {
|
||||
return gulp.src('src/**/*.tpl.html')
|
||||
.pipe(templateCache())
|
||||
.pipe(notify('Tpls Done!! :)'))
|
||||
.pipe(gulp.dest('build'))
|
||||
})
|
||||
|
||||
gulp.task('index', function () {
|
||||
var files = globby.sync(['build/**/*', '!build/vendor/**/*'])
|
||||
|
||||
var filter = function (files, ext) {
|
||||
return files.filter(function (file) {
|
||||
var reg = new RegExp('.+\.' + ext + '$')
|
||||
return file.match(reg)
|
||||
}).map(function (file) {
|
||||
return file.replace('build/', '')
|
||||
})
|
||||
}
|
||||
var scripts = filter(files, 'js')
|
||||
var styles = filter(files, 'css')
|
||||
|
||||
return gulp.src('src/index.html')
|
||||
.pipe(template({
|
||||
scripts: scripts,
|
||||
styles: styles,
|
||||
env: 'build'
|
||||
}))
|
||||
.pipe(gulp.dest('build'))
|
||||
.pipe(livereload())
|
||||
})
|
||||
|
||||
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('build/vendor'))
|
||||
})
|
||||
|
||||
gulp.task('resources', function () {
|
||||
return gulp.src('resources/**/*')
|
||||
.pipe(changed('build/resources'))
|
||||
.pipe(gulp.dest('build/resources'))
|
||||
})
|
||||
|
||||
gulp.task('build', function (cb) {
|
||||
runSequence(['js', 'styl', 'tpls', 'vendor', 'resources'], 'index', cb)
|
||||
})
|
||||
|
||||
gulp.task('watch', function (cb) {
|
||||
gulp.watch(['.env', 'tpls/env.js', 'src/**/*.js'], ['js'])
|
||||
|
||||
gulp.watch('src/styles/**/*.styl', ['styl'])
|
||||
|
||||
gulp.watch('src/**/*.tpl.html', ['tpls'])
|
||||
|
||||
gulp.watch(['build/**/*.js', 'src/index.html'], ['index'])
|
||||
|
||||
livereload.listen()
|
||||
})
|
||||
|
||||
gulp.task('del', function (cb) {
|
||||
del(['build/**/*'], cb)
|
||||
})
|
||||
|
||||
gulp.task('default', function (cb) {
|
||||
runSequence('del', 'build', 'watch', cb)
|
||||
})
|
||||
|
||||
require('./gulp-electron')(gulp)
|
||||
BIN
app-logo.png
BIN
app-logo.png
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
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 |
62
browser/finder/index.electron.html
Normal file
62
browser/finder/index.electron.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!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">
|
||||
<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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
require('electron-stylus')(__dirname + '/../styles/finder/index.styl')
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script src="../ace/src-min/ace.js"></script>
|
||||
<script>
|
||||
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.length > 50 ? article.description.substring(0, 50) + ' …' : 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>
|
||||
)
|
||||
}
|
||||
})
|
||||
97
browser/main/Components/PlanetCreateModal.jsx
Normal file
97
browser/main/Components/PlanetCreateModal.jsx
Normal file
@@ -0,0 +1,97 @@
|
||||
/* 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
|
||||
}
|
||||
},
|
||||
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({errorMessage: 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 === 403) {
|
||||
this.setState({errorMessage: err.response.body.message})
|
||||
}
|
||||
}.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.errorMessage != null ? (<p className='errorAlert'>{this.state.errorMessage}</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>
|
||||
)
|
||||
}
|
||||
})
|
||||
54
browser/main/Components/PlanetNavigator.jsx
Normal file
54
browser/main/Components/PlanetNavigator.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
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
|
||||
}),
|
||||
search: React.PropTypes.string,
|
||||
toggleCodeFilter: React.PropTypes.func,
|
||||
toggleNoteFilter: React.PropTypes.func
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isLaunchModalOpen: false
|
||||
}
|
||||
},
|
||||
openLaunchModal: function () {
|
||||
this.openModal(LaunchModal, {planet: this.props.planet, transitionTo: this.transitionTo})
|
||||
},
|
||||
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'>
|
||||
<button onClick={this.openLaunchModal} className='launchButton btn-primary btn-block'>
|
||||
<i className='fa fa-rocket fa-fw'/> Launch
|
||||
</button>
|
||||
<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}/>
|
||||
)
|
||||
}
|
||||
})
|
||||
71
browser/main/Components/TeamCreateModal.jsx
Normal file
71
browser/main/Components/TeamCreateModal.jsx
Normal file
@@ -0,0 +1,71 @@
|
||||
/* 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: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
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 () {
|
||||
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)
|
||||
})
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className='TeamCreateModal modal'>
|
||||
<input ref='teamName' valueLink={this.linkState('team.name')} className='nameInput stripInput' placeholder='Create new team'/>
|
||||
|
||||
<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>
|
||||
)
|
||||
}
|
||||
})
|
||||
414
browser/main/Containers/PlanetContainer.jsx
Normal file
414
browser/main/Containers/PlanetContainer.jsx
Normal file
@@ -0,0 +1,414 @@
|
||||
/* 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}/>
|
||||
|
||||
<PlanetArticleList showOnlyWithTag={this.applyTagFilter} ref='list' articles={filteredArticles}/>
|
||||
|
||||
<PlanetArticleDetail
|
||||
ref='detail'
|
||||
article={article}
|
||||
planet={this.state.planet}
|
||||
showOnlyWithTag={this.applyTagFilter}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
136
browser/main/Containers/SignupContainer.jsx
Normal file
136
browser/main/Containers/SignupContainer.jsx
Normal file
@@ -0,0 +1,136 @@
|
||||
/* 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: {},
|
||||
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'>会員登録することで、当サイトの利用規約及びCookieの使用を含むデータに関するポリシーに同意するものとします。</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.name}</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}</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 |
59
browser/main/index.electron.html
Normal file
59
browser/main/index.electron.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
var version = require('remote').getGlobal('version')
|
||||
document.title = 'Boost ' + ((version == null || version.length === 0) ? 'DEV version' : 'v' + version)
|
||||
</script>
|
||||
<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">
|
||||
|
||||
<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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
require('electron-stylus')(__dirname + '/../styles/main/index.styl')
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script src="../ace/src-min/ace.js"></script>
|
||||
<script>
|
||||
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>
|
||||
40
browser/main/index.jsx
Normal file
40
browser/main/index.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
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>
|
||||
)
|
||||
|
||||
ReactRouter.run(routes, ReactRouter.HashLocation, function (Root) {
|
||||
React.render(<Root/>, document.getElementById('content'))
|
||||
})
|
||||
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')
|
||||
81
browser/styles/finder/index.styl
Normal file
81
browser/styles/finder/index.styl
Normal file
@@ -0,0 +1,81 @@
|
||||
@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
|
||||
.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
|
||||
307
browser/styles/main/containers/UserContainer.styl
Normal file
307
browser/styles/main/containers/UserContainer.styl
Normal file
@@ -0,0 +1,307 @@
|
||||
.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
|
||||
.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
|
||||
101
browser/styles/mixins/marked.styl
Normal file
101
browser/styles/mixins/marked.styl
Normal file
@@ -0,0 +1,101 @@
|
||||
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
|
||||
pre
|
||||
padding 5px
|
||||
border solid 1px borderColor
|
||||
border-radius 5px
|
||||
overflow-x auto
|
||||
margin-bottom 15px
|
||||
&>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,44 +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'
|
||||
}
|
||||
]
|
||||
}
|
||||
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|
|
||||
@@ -1,55 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="codexen.popup">
|
||||
<head>
|
||||
<title>
|
||||
CodeXen App
|
||||
</title>
|
||||
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
<meta name="description" content="CodeXen - Short code storage service">
|
||||
|
||||
<link rel="stylesheet" href="../../vendor/css/font-awesome.css" media="screen" title="no title" charset="utf-8">
|
||||
<link rel="stylesheet" href="../../main.css" media="screen" title="no title" charset="utf-8">
|
||||
</head>
|
||||
<body class="popup-body" ng-controller="PopUpController">
|
||||
|
||||
<div class="search-block">
|
||||
<input ng-change="filterList(searchNeedle)" search-input id="search-input" type="text" class="form-control" ng-model="searchNeedle" ng-change="refreshResult">
|
||||
</div>
|
||||
|
||||
<div class="result-block row-fluid">
|
||||
<ul id="result-list" class="result-list left-pane">
|
||||
<li ng-click="selectSnippet($index)" ng-repeat="snippet in filteredSnippets" ng-class="{active:$index == selectIndex}"><a href="#"> <span ng-bind="snippet.callSign"></span> <small ng-bind="snippet.description"></small></a></li>
|
||||
</ul>
|
||||
|
||||
<div class="right-pane">
|
||||
<div class="result-detail-control">
|
||||
<button ng-click="writeCode(selectedItem.content)" id="btnClipboard" type="button" name="button" class="btn btn-default"><i class="fa fa-clipboard"></i></button>
|
||||
<!-- <button ng-click="editSnippet(selectedItem.id)" id="btnEdit" type="button" name="button" class="btn btn-default"><i class="fa fa-edit"></i></button> -->
|
||||
<!-- <button id="btnShare" type="button" name="button" class="btn btn-default"><i class="fa fa-share"></i></button> -->
|
||||
</div>
|
||||
<div id="aceView" class="result-detail-content"
|
||||
ui-ace="{
|
||||
showGutter: false,
|
||||
useWrapMode: true,
|
||||
mode:selectedItem.mode.toLowerCase(),
|
||||
onLoad: aceLoaded,
|
||||
theme: 'solarized_dark'
|
||||
}"
|
||||
|
||||
readonly
|
||||
ng-model="selectedItem.content"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../../vendor/ace.js"></script>
|
||||
<script src="../../vendor/angular.js" charset="utf-8"></script>
|
||||
<script src="../../directives/ui-ace.js"></script>
|
||||
<script src="../../vendor/satellizer.js"></script>
|
||||
<script src="../../vendor/hotkeys.js" charset="utf-8"></script>
|
||||
<script src="popup.js" charset="utf-8"></script>
|
||||
<script src="services/snippet.js" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,190 +0,0 @@
|
||||
/* global angular */
|
||||
|
||||
var remote = require('remote')
|
||||
var ipc = require('ipc')
|
||||
|
||||
var resultList = document.getElementById('result-list')
|
||||
|
||||
angular.module('codexen.popup', [
|
||||
'ui.ace',
|
||||
'satellizer',
|
||||
'cfp.hotkeys'
|
||||
])
|
||||
.controller('PopUpController', function ($scope, Snippet, $auth, $window, hotkeys, $document, $filter) {
|
||||
// Setup Events
|
||||
remote.getCurrentWindow().on('focus', function () {
|
||||
if (!$auth.isAuthenticated()) return hidePopUp()
|
||||
$scope.$apply(focusSearchInput)
|
||||
loadSnippets()
|
||||
})
|
||||
|
||||
hotkeys.bindTo($scope)
|
||||
.add('down', function (e) {
|
||||
nextSnippet()
|
||||
e.preventDefault()
|
||||
})
|
||||
.add('up', function (e) {
|
||||
priorSnippet()
|
||||
e.preventDefault()
|
||||
})
|
||||
.add('right', function (e) {
|
||||
e.preventDefault()
|
||||
})
|
||||
.add('left', function (e) {
|
||||
e.preventDefault()
|
||||
})
|
||||
.add('esc', function (e) {
|
||||
hidePopUp()
|
||||
})
|
||||
.add('shift+tab', function (e) {
|
||||
e.preventDefault()
|
||||
})
|
||||
.add('tab', function (e) {
|
||||
e.preventDefault()
|
||||
})
|
||||
.add('enter', function (e) {
|
||||
writeCode($scope.selectedItem.content)
|
||||
e.preventDefault()
|
||||
})
|
||||
|
||||
$scope.aceLoaded = function (editor) {
|
||||
editor.commands.addCommand({
|
||||
name: 'escape',
|
||||
bindKey: {win: 'esc', mac: 'esc'},
|
||||
exec: function (editor) {
|
||||
editor.blur()
|
||||
$scope.$apply()
|
||||
},
|
||||
readOnly: true
|
||||
})
|
||||
}
|
||||
|
||||
$scope.$on('nextSnippetRequested', function (e) {
|
||||
e.stopPropagation()
|
||||
nextSnippet()
|
||||
})
|
||||
|
||||
$scope.$on('priorSnippetRequested', function (e) {
|
||||
e.stopPropagation()
|
||||
priorSnippet()
|
||||
})
|
||||
|
||||
$scope.$on('snippetSubmitted', function (e) {
|
||||
if ($scope.filteredSnippets.length > 0) ipc.send('writeCode', $scope.selectedItem.content)
|
||||
else console.log('\x07')
|
||||
e.stopPropagation()
|
||||
})
|
||||
|
||||
// Init Data
|
||||
$scope.snippets = []
|
||||
|
||||
Snippet.findMine()
|
||||
.success(function (data) {
|
||||
$scope.snippets = data
|
||||
filterList()
|
||||
})
|
||||
|
||||
// Result Item control
|
||||
$scope.selectIndex = 0
|
||||
|
||||
$scope.selectSnippet = selectSnippet
|
||||
$scope.filterList = filterList
|
||||
$scope.writeCode = writeCode
|
||||
$scope.focusSearchInput = focusSearchInput
|
||||
|
||||
// Search Filter
|
||||
function loadSnippets () {
|
||||
Snippet.findMine()
|
||||
.success(function (data) {
|
||||
$scope.snippets = data
|
||||
filterList()
|
||||
})
|
||||
}
|
||||
|
||||
function filterList (needle) {
|
||||
$scope.filteredSnippets = $filter('filter')($scope.snippets, needle)
|
||||
firstSnippet()
|
||||
}
|
||||
|
||||
function selectSnippet (index) {
|
||||
if (index !== undefined) $scope.selectIndex = index
|
||||
$scope.selectedItem = $scope.filteredSnippets[$scope.selectIndex]
|
||||
}
|
||||
|
||||
function firstSnippet () {
|
||||
$scope.selectIndex = 0
|
||||
selectSnippet($scope.selectIndex)
|
||||
}
|
||||
|
||||
function priorSnippet () {
|
||||
if ($scope.selectIndex > 0) $scope.selectIndex -= 1
|
||||
|
||||
if (resultList.children[$scope.selectIndex].offsetTop < resultList.scrollTop) {
|
||||
resultList.scrollTop -= 33
|
||||
}
|
||||
|
||||
selectSnippet()
|
||||
}
|
||||
|
||||
function nextSnippet () {
|
||||
if ($scope.selectIndex < $scope.filteredSnippets.length - 1) {
|
||||
$scope.selectIndex += 1
|
||||
}
|
||||
|
||||
if (resultList.clientHeight - 33 < resultList.children[$scope.selectIndex].offsetTop - resultList.scrollTop) {
|
||||
resultList.scrollTop += 33
|
||||
}
|
||||
|
||||
selectSnippet()
|
||||
}
|
||||
|
||||
function writeCode (code) {
|
||||
ipc.send('writeCode', code)
|
||||
}
|
||||
|
||||
// Focusing Search Input
|
||||
function focusSearchInput () {
|
||||
document.getElementById('search-input').focus()
|
||||
}
|
||||
|
||||
function hidePopUp () {
|
||||
ipc.send('hidePopUp')
|
||||
}
|
||||
|
||||
})
|
||||
.directive('searchInput', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, el, attr) {
|
||||
el.on('keydown', function (e) {
|
||||
// Down key => Focus on Result list
|
||||
if (e.keyCode === 40) {
|
||||
scope.$emit('nextSnippetRequested')
|
||||
// e.preventDefault()
|
||||
}
|
||||
|
||||
// Up key => Focus on Result list
|
||||
if (e.keyCode === 38) {
|
||||
scope.$emit('priorSnippetRequested')
|
||||
// e.preventDefault()
|
||||
}
|
||||
|
||||
// Up key => Focus on Result list
|
||||
if (e.keyCode === 13) {
|
||||
scope.$emit('snippetSubmitted')
|
||||
}
|
||||
|
||||
// Esc key => Dismiss popup
|
||||
if (e.keyCode === 27) {
|
||||
ipc.send('hidePopUp')
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
// TODO: Tab key => Auto complete
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,62 +0,0 @@
|
||||
/* global angular */
|
||||
angular.module('codexen.popup')
|
||||
.constant('apiUrl', 'http://codexen-server-dev.elasticbeanstalk.com/')
|
||||
.config(function ($authProvider, $httpProvider, apiUrl) {
|
||||
$authProvider.baseUrl = apiUrl
|
||||
|
||||
$httpProvider.defaults.useXDomain = true
|
||||
delete $httpProvider.defaults.headers.common['X-Requested-With']
|
||||
})
|
||||
|
||||
|
||||
angular.module('codexen.popup')
|
||||
.factory('Snippet', function ($http, $auth, apiUrl) {
|
||||
var findByUser = function (user) {
|
||||
var url = apiUrl + 'snippets/search'
|
||||
|
||||
return $http.get(url, {
|
||||
params: {
|
||||
user: user
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var findMine = function (params) {
|
||||
var url = apiUrl + 'snippets/my'
|
||||
|
||||
return $http.get(url, {params: params})
|
||||
}
|
||||
|
||||
var create = function (params) {
|
||||
var url = apiUrl + 'snippets/create'
|
||||
|
||||
return $http.post(url, params)
|
||||
}
|
||||
|
||||
var show = function (id, params) {
|
||||
var url = apiUrl + 'snippets/id/' + id
|
||||
|
||||
return $http.get(url, {params: params})
|
||||
}
|
||||
|
||||
var update = function (id, params) {
|
||||
var url = apiUrl + 'snippets/id/' + id
|
||||
|
||||
return $http.put(url, params)
|
||||
}
|
||||
|
||||
var destroy = function (id) {
|
||||
var url = apiUrl + 'snippets/id/' + id
|
||||
|
||||
return $http.delete(url)
|
||||
}
|
||||
|
||||
return {
|
||||
findByUser: findByUser,
|
||||
findMine: findMine,
|
||||
create: create,
|
||||
show: show,
|
||||
delete: destroy,
|
||||
update: update
|
||||
}
|
||||
})
|
||||
@@ -1,59 +0,0 @@
|
||||
.popup-body
|
||||
.search-block
|
||||
padding: 5px
|
||||
height:44px
|
||||
position:absolute
|
||||
top: 0
|
||||
width: 100%
|
||||
|
||||
.result-block
|
||||
position:absolute
|
||||
top: 44px
|
||||
bottom: 0
|
||||
width: 100%
|
||||
overflow: hidden
|
||||
.left-pane
|
||||
margin: 0
|
||||
position: absolute
|
||||
left: 0
|
||||
top: 0
|
||||
bottom: 0
|
||||
width: 40%
|
||||
overflow-y: auto
|
||||
overflow-x: hidden
|
||||
.result-list
|
||||
list-style:none
|
||||
padding: 0
|
||||
border-right: 1px solid $baseBorderColor
|
||||
li
|
||||
&:nth-child(even)
|
||||
background-color $baseBackgroundColor
|
||||
&:nth-child(odd)
|
||||
background-color lighten($baseBackgroundColor, 2%)
|
||||
&.active
|
||||
color: $textColorSelected
|
||||
background-color: $btnPrimary
|
||||
a
|
||||
display:block
|
||||
padding: 5px 10px
|
||||
border-bottom 1px solid $baseBorderColor
|
||||
|
||||
.right-pane
|
||||
position: absolute
|
||||
left: 40%
|
||||
top: 0
|
||||
bottom: 0
|
||||
width: 60%
|
||||
overflow-y: auto
|
||||
overflow-x: hidden
|
||||
.result-detail-control
|
||||
position: absolute
|
||||
top: 0
|
||||
width: 100%
|
||||
height: 34px
|
||||
|
||||
.result-detail-content
|
||||
position: absolute
|
||||
top: 34px
|
||||
bottom: 0
|
||||
width: 100%
|
||||
@@ -1,13 +0,0 @@
|
||||
@import '../../src/styles/_vars'
|
||||
@import '../../src/styles/mixins/*'
|
||||
|
||||
@import '../../src/styles/_bootstrap'
|
||||
|
||||
@import '../../src/styles/_index'
|
||||
@import '../../src/styles/_shared'
|
||||
|
||||
@import '../../src/styles/modals/*'
|
||||
@import '../../src/styles/directives/*'
|
||||
@import '../../src/styles/states/*'
|
||||
|
||||
@import '_popup'
|
||||
139
gulp-electron.js
139
gulp-electron.js
@@ -1,139 +0,0 @@
|
||||
require('dotenv').load()
|
||||
var env = process.env
|
||||
|
||||
var styl = require('gulp-stylus')
|
||||
var autoprefixer = require('gulp-autoprefixer')
|
||||
var templateCache = require('gulp-angular-templatecache')
|
||||
var globby = require('globby')
|
||||
var template = require('gulp-template')
|
||||
var del = require('del')
|
||||
var runSequence = require('run-sequence')
|
||||
var plumber = require('gulp-plumber')
|
||||
var notify = require('gulp-notify')
|
||||
var changed = require('gulp-changed')
|
||||
var livereload = require('gulp-livereload')
|
||||
var merge = require('merge-stream')
|
||||
|
||||
var config = require('./build.config.js')
|
||||
|
||||
module.exports = function (gulp) {
|
||||
|
||||
gulp.task('elec-env', function () {
|
||||
return gulp.src('tpls/env.js')
|
||||
.pipe(template({
|
||||
apiUrl: env.ELEC_API_URL
|
||||
}))
|
||||
.pipe(gulp.dest('electron_build/config'))
|
||||
})
|
||||
|
||||
gulp.task('elec-js', function () {
|
||||
var main = gulp.src('src/**/*.js')
|
||||
.pipe(changed('electron_build'))
|
||||
.pipe(gulp.dest('electron_build'))
|
||||
|
||||
var electron = gulp.src('electron_src/**/*.js')
|
||||
.pipe(changed('electron_build/electron'))
|
||||
.pipe(gulp.dest('electron_build/electron'))
|
||||
|
||||
return merge(main, electron)
|
||||
})
|
||||
|
||||
gulp.task('elec-styl', function () {
|
||||
return gulp.src('electron_src/styles/main.styl')
|
||||
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
|
||||
.pipe(styl())
|
||||
.pipe(autoprefixer())
|
||||
.pipe(gulp.dest('electron_build'))
|
||||
.pipe(notify('Stylus!!'))
|
||||
.pipe(livereload())
|
||||
})
|
||||
|
||||
gulp.task('elec-tpls', function () {
|
||||
var main = gulp.src('src/**/*.tpl.html')
|
||||
.pipe(templateCache())
|
||||
.pipe(gulp.dest('electron_build'))
|
||||
|
||||
var electron = gulp.src('electron_src/**/*.tpl.html')
|
||||
.pipe(templateCache())
|
||||
.pipe(gulp.dest('electron_build/electron'))
|
||||
|
||||
return merge(main, electron)
|
||||
})
|
||||
|
||||
gulp.task('elec-index', function () {
|
||||
var files = globby.sync(['electron_build/**/*', '!electron_build/vendor/**/*', '!electron_build/electron/**/*'])
|
||||
|
||||
var filter = function (files, ext) {
|
||||
return files.filter(function (file) {
|
||||
var reg = new RegExp('.+\.' + ext + '$')
|
||||
return file.match(reg)
|
||||
}).map(function (file) {
|
||||
return file.replace('electron_build/', '')
|
||||
})
|
||||
}
|
||||
var scripts = filter(files, 'js')
|
||||
var styles = filter(files, 'css')
|
||||
|
||||
var main = gulp.src('src/index.html')
|
||||
.pipe(template({
|
||||
scripts: scripts,
|
||||
styles: styles,
|
||||
env: 'build'
|
||||
}))
|
||||
.pipe(gulp.dest('electron_build'))
|
||||
.pipe(livereload())
|
||||
|
||||
var electron = gulp.src('electron_src/**/index.html')
|
||||
.pipe(gulp.dest('electron_build/electron'))
|
||||
|
||||
return merge(main, electron)
|
||||
})
|
||||
|
||||
gulp.task('elec-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('electron_build/vendor'))
|
||||
})
|
||||
|
||||
gulp.task('elec-resources', function () {
|
||||
return gulp.src('resources/**/*')
|
||||
.pipe(changed('electron_build/resources'))
|
||||
.pipe(gulp.dest('electron_build/resources'))
|
||||
})
|
||||
|
||||
gulp.task('elec-build', function (cb) {
|
||||
runSequence(['elec-env', 'elec-js', 'elec-styl', 'elec-tpls', 'elec-vendor', 'elec-resources'], 'elec-index', cb)
|
||||
})
|
||||
|
||||
gulp.task('elec-watch', function (cb) {
|
||||
gulp.watch(['.env', 'tpls/env.js'], ['elec-env'])
|
||||
|
||||
gulp.watch(['src/**/*.js', 'electron_src/**/*.js'], ['elec-js'])
|
||||
|
||||
gulp.watch(['src/styles/**/*.styl', 'electron_src/styles/**/*.styl'], ['elec-styl'])
|
||||
|
||||
gulp.watch('src/**/*.tpl.html', ['elec-tpls'])
|
||||
|
||||
gulp.watch(['electron_build/**/*.js', 'src/index.html', 'src/index.html', 'electron_src/**/index.html'], ['elec-index'])
|
||||
|
||||
livereload.listen()
|
||||
})
|
||||
|
||||
gulp.task('elec-del', function (cb) {
|
||||
del(['electron_build/**/*'], cb)
|
||||
})
|
||||
|
||||
gulp.task('elec', function (cb) {
|
||||
runSequence('elec-del', 'elec-build', 'elec-watch', cb)
|
||||
})
|
||||
|
||||
}
|
||||
322
main.js
322
main.js
@@ -1,243 +1,186 @@
|
||||
var app = require('app') // Module to control application life.
|
||||
var BrowserWindow = require('browser-window') // Module to create native browser window.
|
||||
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')
|
||||
|
||||
// Report crashes to our server.
|
||||
require('crash-reporter').start()
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the javascript object is GCed.
|
||||
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 clipboard = require('clipboard')
|
||||
var Tray = require('tray')
|
||||
var notifier = require('node-notifier')
|
||||
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')
|
||||
|
||||
var appIcon = null
|
||||
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 () {
|
||||
appIcon = new Tray('./icon.png')
|
||||
appIcon.setToolTip('This is my application.')
|
||||
appIcon.on('clicked', 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('Codexen')
|
||||
|
||||
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()
|
||||
|
||||
var globalShortcut = require('global-shortcut')
|
||||
app.on('activate-with-no-open-windows', function () {
|
||||
if (mainWindow == null) {
|
||||
makeNewMainWindow()
|
||||
return
|
||||
}
|
||||
mainWindow.show()
|
||||
})
|
||||
|
||||
var popUpWindow = new BrowserWindow({
|
||||
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 + '/electron_build/electron/popup/index.html')
|
||||
|
||||
app.on('activate-with-no-open-windows', function () {
|
||||
if (mainWindow == null) {
|
||||
makeNewMainWindow()
|
||||
}
|
||||
mainWindow.show()
|
||||
})
|
||||
popUpWindow.loadUrl('file://' + __dirname + '/browser/finder/index.electron.html')
|
||||
|
||||
popUpWindow.on('blur', function () {
|
||||
popUpWindow.hide()
|
||||
})
|
||||
popUpWindow.setVisibleOnAllWorkspaces(true)
|
||||
|
||||
var hidePopUp = function () {
|
||||
if (fromMain) {
|
||||
var globalShortcut = require('global-shortcut')
|
||||
|
||||
} else {
|
||||
mainWindow ? mainWindow.hide() : null
|
||||
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()
|
||||
}
|
||||
|
||||
var ipc = require('ipc')
|
||||
ipc.on('hidePopUp', function () {
|
||||
hidePopUp()
|
||||
})
|
||||
|
||||
ipc.on('writeCode', function (e, code) {
|
||||
clipboard.writeText(code)
|
||||
notifier.notify({
|
||||
title: 'Write on clipboard!',
|
||||
message: 'Ready to paste',
|
||||
wait: false
|
||||
}, function (err, res) {
|
||||
|
||||
})
|
||||
hidePopUp()
|
||||
})
|
||||
|
||||
var fromMain
|
||||
// Register a 'ctrl+x' shortcut listener.
|
||||
var ret = globalShortcut.register('ctrl+tab+shift', function () {
|
||||
if (popUpWindow.isVisible()) {
|
||||
hidePopUp()
|
||||
return
|
||||
}
|
||||
fromMain = mainWindow ? mainWindow.isFocused() : false
|
||||
popUpWindow.show()
|
||||
|
||||
})
|
||||
if (!ret) console.log('registerion fails')
|
||||
|
||||
// MENU
|
||||
var Menu = require('menu')
|
||||
var template = [
|
||||
{
|
||||
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',
|
||||
click: function () { app.quit() }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
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().reloadIgnoringCache() }
|
||||
},
|
||||
{
|
||||
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: []
|
||||
}
|
||||
]
|
||||
|
||||
var menu = Menu.buildFromTemplate(template)
|
||||
|
||||
Menu.setApplicationMenu(menu)
|
||||
|
||||
function makeNewMainWindow () {
|
||||
console.log('new Window!')
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 920,
|
||||
height: 640,
|
||||
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 + '/electron_build/index.html')
|
||||
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')
|
||||
@@ -246,4 +189,3 @@ app.on('ready', function () {
|
||||
})
|
||||
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: []
|
||||
}
|
||||
]
|
||||
85
package.json
85
package.json
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"name": "codexen-app",
|
||||
"version": "0.0.1",
|
||||
"description": "CodeXen App",
|
||||
"main": "index.js",
|
||||
"name": "boost",
|
||||
"version": "0.2.5",
|
||||
"description": "Boost App",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"install": "gulp build",
|
||||
"start": "http-server build",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"start": "electron ./main.js",
|
||||
"web": "npm run serve | npm run dev",
|
||||
"serve": "./node_modules/.bin/http-server ./browser -p 8080",
|
||||
"dev": "webpack-dev-server --progress --colors --port 8090"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Rokt33r/codexen-app.git"
|
||||
},
|
||||
"keywords": [
|
||||
"codexen",
|
||||
"boost",
|
||||
"b00st",
|
||||
"snippet",
|
||||
"template",
|
||||
"task",
|
||||
"runner",
|
||||
"remote",
|
||||
"automator",
|
||||
"code",
|
||||
"storage",
|
||||
"short code"
|
||||
@@ -31,46 +31,37 @@
|
||||
},
|
||||
"homepage": "https://github.com/Rokt33r/codexen-app#readme",
|
||||
"dependencies": {
|
||||
"@rokt33r/ace-builds": "^1.1.9",
|
||||
"@rokt33r/angular-ui-ace": "^0.2.3",
|
||||
"angular": "^1.3.15",
|
||||
"angular-bootstrap": "^0.12.0",
|
||||
"angular-hotkeys": "^1.4.5",
|
||||
"angular-md5": "^0.1.7",
|
||||
"angular-sanitize": "^1.3.15",
|
||||
"angular-ui-router": "^0.2.15",
|
||||
"bootstrap-sass": "^3.3.4",
|
||||
"bootstrap-styl": "^4.0.4",
|
||||
"del": "^1.2.0",
|
||||
"dotenv": "^1.1.0",
|
||||
"electron-prebuilt": "^0.27.2",
|
||||
"electron-rebuild": "^0.2.1",
|
||||
"electron-stylus": "^0.1.0",
|
||||
"font-awesome": "^4.3.0",
|
||||
"globby": "^2.0.0",
|
||||
"gulp": "^3.8.11",
|
||||
"gulp-angular-templatecache": "^1.6.0",
|
||||
"gulp-autoprefixer": "^2.3.0",
|
||||
"gulp-changed": "^1.2.1",
|
||||
"gulp-livereload": "^3.8.0",
|
||||
"gulp-minify-css": "^1.1.1",
|
||||
"gulp-ng-annotate": "^0.5.3",
|
||||
"gulp-notify": "^2.2.0",
|
||||
"gulp-plumber": "^1.0.1",
|
||||
"gulp-rev": "^4.0.0",
|
||||
"gulp-stylus": "^2.0.3",
|
||||
"gulp-template": "^3.0.0",
|
||||
"gulp-uglify": "^1.2.0",
|
||||
"merge-stream": "^0.1.7",
|
||||
"markdown-it": "^4.3.1",
|
||||
"md5": "^2.0.0",
|
||||
"moment": "^2.10.3",
|
||||
"node-notifier": "^4.2.1",
|
||||
"robotjs": "^0.1.2",
|
||||
"run-sequence": "^1.1.0",
|
||||
"satellizer": "^0.10.1",
|
||||
"ui-select": "^0.11.2"
|
||||
"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": {
|
||||
"gulp-concat": "^2.5.2",
|
||||
"gulp-minify-html": "^1.0.3",
|
||||
"streamqueue": "^1.1.0"
|
||||
"css-loader": "^0.15.1",
|
||||
"http-server": "^0.8.0",
|
||||
"jsx-loader": "^0.13.2",
|
||||
"node-libs-browser": "^0.5.2",
|
||||
"style-loader": "^0.12.3",
|
||||
"stylus-loader": "^1.2.1",
|
||||
"webpack": "^1.10.0",
|
||||
"webpack-dev-server": "^1.10.1"
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"/browser/ace/"
|
||||
],
|
||||
"global": [
|
||||
"localStorage"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# CodeXen app
|
||||
# CodeXen app 0.2.0
|
||||

|
||||
|
||||
Short code(Snippet/Templatefile/Command) storage + boosting service
|
||||
|
||||
11
src/app.js
11
src/app.js
@@ -1,11 +0,0 @@
|
||||
/* global angular */
|
||||
angular.module('codexen', [
|
||||
'ngSanitize',
|
||||
'ui.select',
|
||||
'ui.ace',
|
||||
'ui.router',
|
||||
'ui.bootstrap',
|
||||
'satellizer',
|
||||
'angular-md5',
|
||||
'templates'])
|
||||
angular.module('templates', [])
|
||||
5
src/browser/main/controllers/AppController.js
Normal file
5
src/browser/main/controllers/AppController.js
Normal file
@@ -0,0 +1,5 @@
|
||||
/* global angular */
|
||||
angular.module('codexen')
|
||||
.controller('AppController', function ($scope) {
|
||||
|
||||
})
|
||||
@@ -1,10 +1,13 @@
|
||||
/* global angular */
|
||||
angular.module('codexen')
|
||||
.controller('SideNavController', function ($auth, User, $rootScope, $scope) {
|
||||
.controller('SideNavController', function ($auth, User, $rootScope, $scope, Modal) {
|
||||
var vm = this
|
||||
|
||||
vm.isAuthenticated = $auth.isAuthenticated()
|
||||
|
||||
vm.showPP = Modal.showPP
|
||||
vm.showRegulation = Modal.showRegulation
|
||||
|
||||
var reloadUser = function () {
|
||||
if (vm.isAuthenticated) {
|
||||
User.me().success(function (data) {
|
||||
@@ -16,11 +19,7 @@ angular.module('codexen')
|
||||
reloadUser()
|
||||
|
||||
vm.signOut = function () {
|
||||
$auth.logout()
|
||||
.then(function () {
|
||||
console.log('Sign Out')
|
||||
$rootScope.$broadcast('userSignOut')
|
||||
})
|
||||
Modal.signOut()
|
||||
}
|
||||
|
||||
$scope.$on('userSignIn', function () {
|
||||
@@ -31,6 +30,5 @@ angular.module('codexen')
|
||||
$scope.$on('userSignOut', function () {
|
||||
vm.isAuthenticated = false
|
||||
vm.currentUser = null
|
||||
|
||||
})
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user