mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
11 Commits
0.4.2-rc.0
...
0.2.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b93990d10b | ||
|
|
a0bcb8edbe | ||
|
|
19930a2472 | ||
|
|
e75d95b1fc | ||
|
|
3b7215b36a | ||
|
|
8ab96cf2fb | ||
|
|
63af2fd8b1 | ||
|
|
fcf26f7844 | ||
|
|
a05bba15e7 | ||
|
|
3b907627f7 | ||
|
|
2284fd41b9 |
20
.babelrc
20
.babelrc
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"stage": 0,
|
|
||||||
"env": {
|
|
||||||
"development": {
|
|
||||||
"plugins": ["react-transform"],
|
|
||||||
"extra": {
|
|
||||||
"react-transform": {
|
|
||||||
"transforms": [{
|
|
||||||
"transform": "react-transform-hmr",
|
|
||||||
"imports": ["react"],
|
|
||||||
"locals": ["module"]
|
|
||||||
}, {
|
|
||||||
"transform": "react-transform-catch-errors",
|
|
||||||
"imports": ["react", "redbox-react"]
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
|
build/
|
||||||
|
node_modules/
|
||||||
|
electron_build/
|
||||||
.env
|
.env
|
||||||
node_modules/*
|
dist/
|
||||||
!node_modules/boost
|
vendor/
|
||||||
Boost-darwin-x64/
|
|
||||||
backup/
|
|
||||||
compiled
|
|
||||||
|
|||||||
5
.gitmodules
vendored
5
.gitmodules
vendored
@@ -1,4 +1,3 @@
|
|||||||
[submodule "submodules/ace"]
|
[submodule "browser/ace"]
|
||||||
path = submodules/ace
|
path = browser/ace
|
||||||
url = https://github.com/ajaxorg/ace-builds.git
|
url = https://github.com/ajaxorg/ace-builds.git
|
||||||
branch = master
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
const electron = require('electron')
|
|
||||||
const BrowserWindow = electron.BrowserWindow
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
var finderWindow = new BrowserWindow({
|
|
||||||
width: 640,
|
|
||||||
height: 400,
|
|
||||||
show: false,
|
|
||||||
frame: false,
|
|
||||||
resizable: false,
|
|
||||||
'zoom-factor': 1.0,
|
|
||||||
'always-on-top': true,
|
|
||||||
'web-preferences': {
|
|
||||||
'overlay-scrollbars': true,
|
|
||||||
'skip-taskbar': true
|
|
||||||
},
|
|
||||||
'standard-window': false
|
|
||||||
})
|
|
||||||
|
|
||||||
var url = path.resolve(__dirname, '../browser/finder/index.html')
|
|
||||||
|
|
||||||
finderWindow.loadURL('file://' + url)
|
|
||||||
|
|
||||||
finderWindow.on('blur', function () {
|
|
||||||
finderWindow.hide()
|
|
||||||
})
|
|
||||||
|
|
||||||
finderWindow.setVisibleOnAllWorkspaces(true)
|
|
||||||
|
|
||||||
module.exports = finderWindow
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
const electron = require('electron')
|
|
||||||
const BrowserWindow = electron.BrowserWindow
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
var mainWindow = new BrowserWindow({
|
|
||||||
width: 1080,
|
|
||||||
height: 720,
|
|
||||||
'zoom-factor': 1.0,
|
|
||||||
'web-preferences': {
|
|
||||||
'overlay-scrollbars': true
|
|
||||||
},
|
|
||||||
'standard-window': false
|
|
||||||
})
|
|
||||||
|
|
||||||
const url = path.resolve(__dirname, '../browser/main/index.html')
|
|
||||||
|
|
||||||
mainWindow.loadURL('file://' + url)
|
|
||||||
|
|
||||||
mainWindow.setVisibleOnAllWorkspaces(true)
|
|
||||||
|
|
||||||
mainWindow.webContents.on('new-window', function (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = mainWindow
|
|
||||||
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import CodeEditor from 'boost/components/CodeEditor'
|
|
||||||
import MarkdownPreview from 'boost/components/MarkdownPreview'
|
|
||||||
import ModeIcon from 'boost/components/ModeIcon'
|
|
||||||
|
|
||||||
export default class FinderDetail extends React.Component {
|
|
||||||
render () {
|
|
||||||
let { activeArticle } = this.props
|
|
||||||
|
|
||||||
if (activeArticle != null) {
|
|
||||||
return (
|
|
||||||
<div className='FinderDetail'>
|
|
||||||
<div className='header'>
|
|
||||||
<div className='left'>
|
|
||||||
<ModeIcon mode={activeArticle.mode}/> {activeArticle.title}
|
|
||||||
</div>
|
|
||||||
<div className='right'>
|
|
||||||
<button onClick={this.props.saveToClipboard} className='clipboardBtn'>
|
|
||||||
<i className='fa fa-clipboard fa-fw'/>
|
|
||||||
<span className='tooltip'>Copy to clipboard (Enter)</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='content'>
|
|
||||||
{activeArticle.mode === 'markdown'
|
|
||||||
? <MarkdownPreview content={activeArticle.content}/>
|
|
||||||
: <CodeEditor readOnly mode={activeArticle.mode} code={activeArticle.content}/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className='FinderDetail'>
|
|
||||||
<div className='nothing'>Nothing selected</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderDetail.propTypes = {
|
|
||||||
activeArticle: PropTypes.shape(),
|
|
||||||
saveToClipboard: PropTypes.func
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
|
|
||||||
export default class FinderInput extends React.Component {
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className='FinderInput'>
|
|
||||||
<input ref='input' value={this.props.value} onChange={this.props.handleSearchChange} type='text'/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderInput.propTypes = {
|
|
||||||
handleSearchChange: PropTypes.func,
|
|
||||||
value: PropTypes.string
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import ModeIcon from 'boost/components/ModeIcon'
|
|
||||||
import { selectArticle } from './actions'
|
|
||||||
|
|
||||||
export default class FinderList extends React.Component {
|
|
||||||
componentDidUpdate () {
|
|
||||||
var index = this.props.articles.indexOf(this.props.activeArticle)
|
|
||||||
var el = ReactDOM.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 (article) {
|
|
||||||
return (e) => {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
dispatch(selectArticle(article.key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let articleElements = 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.activeArticle != null && (article.key === this.props.activeArticle.key)
|
|
||||||
return (
|
|
||||||
<li key={'article-' + article.key} onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
|
|
||||||
<div className='articleItem'>
|
|
||||||
<ModeIcon mode={article.mode}/> {article.title}</div>
|
|
||||||
<div className='divider'/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}.bind(this))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='FinderList'>
|
|
||||||
<ul>
|
|
||||||
{articleElements}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderList.propTypes = {
|
|
||||||
articles: PropTypes.array,
|
|
||||||
activeArticle: PropTypes.shape({
|
|
||||||
type: PropTypes.string,
|
|
||||||
key: PropTypes.string
|
|
||||||
}),
|
|
||||||
dispatch: PropTypes.func
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
export const SELECT_ARTICLE = 'SELECT_ARTICLE'
|
|
||||||
export const SEARCH_ARTICLE = 'SEARCH_ARTICLE'
|
|
||||||
export const REFRESH_DATA = 'REFRESH_DATA'
|
|
||||||
|
|
||||||
export function selectArticle (key) {
|
|
||||||
return {
|
|
||||||
type: SELECT_ARTICLE,
|
|
||||||
data: { key }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function searchArticle (input) {
|
|
||||||
return {
|
|
||||||
type: SEARCH_ARTICLE,
|
|
||||||
data: { input }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshData (data) {
|
|
||||||
console.log('refreshing data')
|
|
||||||
let { folders, articles } = data
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: REFRESH_DATA,
|
|
||||||
data: {
|
|
||||||
articles,
|
|
||||||
folders
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
SELECT_ARTICLE,
|
|
||||||
SEARCH_ARTICLE,
|
|
||||||
REFRESH_DATA,
|
|
||||||
selectArticle,
|
|
||||||
searchArticle,
|
|
||||||
refreshData
|
|
||||||
}
|
|
||||||
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>
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
|
|
||||||
<title>Boost Finder</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" charset="utf-8">
|
|
||||||
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
|
|
||||||
<link rel="stylesheet" href="../../node_modules/highlight.js/styles/xcode.css">
|
|
||||||
<link rel="shortcut icon" href="favicon.ico">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Lato';
|
|
||||||
src: url('../../resources/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
|
||||||
url('../../resources/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
|
||||||
url('../../resources/Lato-Regular.ttf') format('truetype');
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="content"></div>
|
|
||||||
<script src="../../submodules/ace/src-min/ace.js"></script>
|
|
||||||
<script>
|
|
||||||
const electron = require('electron')
|
|
||||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
|
||||||
var scriptUrl = process.env.BOOST_ENV === 'development'
|
|
||||||
? 'http://localhost:8080/assets/finder.js'
|
|
||||||
: '../../compiled/finder.js'
|
|
||||||
var scriptEl=document.createElement('script')
|
|
||||||
scriptEl.setAttribute("type","text/javascript")
|
|
||||||
scriptEl.setAttribute("src", scriptUrl)
|
|
||||||
document.getElementsByTagName("head")[0].appendChild(scriptEl)
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -1,230 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import { connect, Provider } from 'react-redux'
|
|
||||||
import reducer from './reducer'
|
|
||||||
import { createStore } from 'redux'
|
|
||||||
import FinderInput from './FinderInput'
|
|
||||||
import FinderList from './FinderList'
|
|
||||||
import FinderDetail from './FinderDetail'
|
|
||||||
import actions, { selectArticle, searchArticle } from './actions'
|
|
||||||
import _ from 'lodash'
|
|
||||||
import dataStore from 'boost/dataStore'
|
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const { remote, clipboard } = electron
|
|
||||||
|
|
||||||
var hideFinder = remote.getGlobal('hideFinder')
|
|
||||||
|
|
||||||
function notify (...args) {
|
|
||||||
return new window.Notification(...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
require('../styles/finder/index.styl')
|
|
||||||
|
|
||||||
const FOLDER_FILTER = 'FOLDER_FILTER'
|
|
||||||
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
|
|
||||||
const TEXT_FILTER = 'TEXT_FILTER'
|
|
||||||
const TAG_FILTER = 'TAG_FILTER'
|
|
||||||
|
|
||||||
class FinderMain extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick (e) {
|
|
||||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown (e) {
|
|
||||||
if (e.keyCode === 38) {
|
|
||||||
this.selectPrevious()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 40) {
|
|
||||||
this.selectNext()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
this.saveToClipboard()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
if (e.keyCode === 27) {
|
|
||||||
hideFinder()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToClipboard () {
|
|
||||||
let { activeArticle } = this.props
|
|
||||||
clipboard.writeText(activeArticle.content)
|
|
||||||
|
|
||||||
notify('Saved to Clipboard!', {
|
|
||||||
body: 'Paste it wherever you want!'
|
|
||||||
})
|
|
||||||
hideFinder()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearchChange (e) {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
|
|
||||||
dispatch(searchArticle(e.target.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
selectArticle (article) {
|
|
||||||
this.setState({currentArticle: article})
|
|
||||||
}
|
|
||||||
|
|
||||||
selectPrevious () {
|
|
||||||
let { activeArticle, dispatch } = this.props
|
|
||||||
let index = this.refs.finderList.props.articles.indexOf(activeArticle)
|
|
||||||
let previousArticle = this.refs.finderList.props.articles[index - 1]
|
|
||||||
if (previousArticle != null) dispatch(selectArticle(previousArticle.key))
|
|
||||||
}
|
|
||||||
|
|
||||||
selectNext () {
|
|
||||||
let { activeArticle, dispatch } = this.props
|
|
||||||
let index = this.refs.finderList.props.articles.indexOf(activeArticle)
|
|
||||||
let previousArticle = this.refs.finderList.props.articles[index + 1]
|
|
||||||
if (previousArticle != null) dispatch(selectArticle(previousArticle.key))
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { articles, activeArticle, status, dispatch } = this.props
|
|
||||||
let saveToClipboard = () => this.saveToClipboard()
|
|
||||||
return (
|
|
||||||
<div onClick={e => this.handleClick(e)} onKeyDown={e => this.handleKeyDown(e)} className='Finder'>
|
|
||||||
<FinderInput
|
|
||||||
handleSearchChange={e => this.handleSearchChange(e)}
|
|
||||||
ref='finderInput'
|
|
||||||
onChange={this.handleChange}
|
|
||||||
value={status.search}
|
|
||||||
/>
|
|
||||||
<FinderList
|
|
||||||
ref='finderList'
|
|
||||||
activeArticle={activeArticle}
|
|
||||||
articles={articles}
|
|
||||||
dispatch={dispatch}
|
|
||||||
selectArticle={article => this.selectArticle(article)}
|
|
||||||
/>
|
|
||||||
<FinderDetail
|
|
||||||
activeArticle={activeArticle}
|
|
||||||
saveToClipboard={saveToClipboard}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderMain.propTypes = {
|
|
||||||
articles: PropTypes.array,
|
|
||||||
activeArticle: PropTypes.shape({
|
|
||||||
key: PropTypes.string,
|
|
||||||
tags: PropTypes.array,
|
|
||||||
title: PropTypes.string,
|
|
||||||
content: PropTypes.string
|
|
||||||
}),
|
|
||||||
status: PropTypes.shape(),
|
|
||||||
dispatch: PropTypes.func
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore invalid key
|
|
||||||
function ignoreInvalidKey (key) {
|
|
||||||
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build filter object by key
|
|
||||||
function buildFilter (key) {
|
|
||||||
if (key.match(/^\/\/.+/)) {
|
|
||||||
return {type: FOLDER_EXACT_FILTER, value: key.match(/^\/\/(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
if (key.match(/^\/.+/)) {
|
|
||||||
return {type: FOLDER_FILTER, value: key.match(/^\/(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
if (key.match(/^#(.+)/)) {
|
|
||||||
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
return {type: TEXT_FILTER, value: key}
|
|
||||||
}
|
|
||||||
|
|
||||||
function remap (state) {
|
|
||||||
let { articles, folders, status } = state
|
|
||||||
|
|
||||||
let filters = status.search.split(' ')
|
|
||||||
.map(key => key.trim())
|
|
||||||
.filter(ignoreInvalidKey)
|
|
||||||
.map(buildFilter)
|
|
||||||
|
|
||||||
let folderExactFilters = filters.filter(filter => filter.type === FOLDER_EXACT_FILTER)
|
|
||||||
let folderFilters = filters.filter(filter => filter.type === FOLDER_FILTER)
|
|
||||||
let textFilters = filters.filter(filter => filter.type === TEXT_FILTER)
|
|
||||||
let tagFilters = filters.filter(filter => filter.type === TAG_FILTER)
|
|
||||||
|
|
||||||
let targetFolders
|
|
||||||
if (folders != null) {
|
|
||||||
let exactTargetFolders = folders.filter(folder => {
|
|
||||||
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
|
|
||||||
})
|
|
||||||
let fuzzyTargetFolders = folders.filter(folder => {
|
|
||||||
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
|
|
||||||
})
|
|
||||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
|
||||||
|
|
||||||
if (targetFolders.length > 0) {
|
|
||||||
articles = articles.filter(article => {
|
|
||||||
return _.findWhere(targetFolders, {key: article.FolderKey})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textFilters.length > 0) {
|
|
||||||
articles = textFilters.reduce((articles, textFilter) => {
|
|
||||||
return articles.filter(article => {
|
|
||||||
return article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
|
|
||||||
})
|
|
||||||
}, articles)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tagFilters.length > 0) {
|
|
||||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
|
||||||
return articles.filter(article => {
|
|
||||||
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
|
|
||||||
})
|
|
||||||
}, articles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
|
||||||
if (activeArticle == null) activeArticle = articles[0]
|
|
||||||
|
|
||||||
console.log(status.search)
|
|
||||||
return {
|
|
||||||
articles,
|
|
||||||
activeArticle,
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var Finder = connect(remap)(FinderMain)
|
|
||||||
var store = createStore(reducer)
|
|
||||||
|
|
||||||
function refreshData () {
|
|
||||||
let data = dataStore.getData()
|
|
||||||
store.dispatch(actions.refreshData(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onfocus = e => {
|
|
||||||
refreshData()
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactDOM.render((
|
|
||||||
<Provider store={store}>
|
|
||||||
<Finder/>
|
|
||||||
</Provider>
|
|
||||||
), document.getElementById('content'), function () {
|
|
||||||
refreshData()
|
|
||||||
})
|
|
||||||
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'))
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { combineReducers } from 'redux'
|
|
||||||
import { SELECT_ARTICLE, SEARCH_ARTICLE, REFRESH_DATA } from './actions'
|
|
||||||
|
|
||||||
let initialArticles = []
|
|
||||||
let initialFolders = []
|
|
||||||
let initialStatus = {
|
|
||||||
articleKey: null,
|
|
||||||
search: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function status (state = initialStatus, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case SELECT_ARTICLE:
|
|
||||||
state.articleKey = action.data.key
|
|
||||||
return Object.assign({}, state)
|
|
||||||
case SEARCH_ARTICLE:
|
|
||||||
state.search = action.data.input
|
|
||||||
return Object.assign({}, state)
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function articles (state = initialArticles, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case REFRESH_DATA:
|
|
||||||
return action.data.articles
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function folders (state = initialFolders, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case REFRESH_DATA:
|
|
||||||
console.log(action)
|
|
||||||
return action.data.folders
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default combineReducers({
|
|
||||||
status,
|
|
||||||
folders,
|
|
||||||
articles
|
|
||||||
})
|
|
||||||
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
95
browser/main/Components/AddMemberModal.jsx
Normal file
95
browser/main/Components/AddMemberModal.jsx
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
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 () {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
import React from 'react'
|
var React = require('react/addons')
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import modes from 'boost/vars/modes'
|
|
||||||
import _ from 'lodash'
|
|
||||||
var ace = window.ace
|
var ace = window.ace
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
@@ -9,37 +7,23 @@ module.exports = React.createClass({
|
|||||||
code: React.PropTypes.string,
|
code: React.PropTypes.string,
|
||||||
mode: React.PropTypes.string,
|
mode: React.PropTypes.string,
|
||||||
className: React.PropTypes.string,
|
className: React.PropTypes.string,
|
||||||
onChange: React.PropTypes.func,
|
onChange: React.PropTypes.func
|
||||||
readOnly: React.PropTypes.bool
|
|
||||||
},
|
|
||||||
getDefaultProps: function () {
|
|
||||||
return {
|
|
||||||
readOnly: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentWillReceiveProps: function (nextProps) {
|
|
||||||
if (nextProps.readOnly !== this.props.readOnly) {
|
|
||||||
this.editor.setReadOnly(!!nextProps.readOnly)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
var el = ReactDOM.findDOMNode(this.refs.target)
|
var el = React.findDOMNode(this.refs.target)
|
||||||
var editor = this.editor = ace.edit(el)
|
var editor = ace.edit(el)
|
||||||
editor.$blockScrolling = Infinity
|
editor.$blockScrolling = Infinity
|
||||||
editor.setValue(this.props.code)
|
editor.setValue(this.props.code)
|
||||||
editor.renderer.setShowGutter(true)
|
editor.renderer.setShowGutter(true)
|
||||||
editor.setTheme('ace/theme/xcode')
|
editor.setTheme('ace/theme/xcode')
|
||||||
editor.clearSelection()
|
editor.clearSelection()
|
||||||
|
|
||||||
editor.setReadOnly(!!this.props.readOnly)
|
|
||||||
|
|
||||||
var session = editor.getSession()
|
var session = editor.getSession()
|
||||||
let mode = _.findWhere(modes, {name: this.props.mode})
|
if (this.props.mode != null && this.props.mode.length > 0) {
|
||||||
let syntaxMode = mode != null
|
session.setMode('ace/mode/' + this.props.mode)
|
||||||
? mode.mode
|
} else {
|
||||||
: 'text'
|
session.setMode('ace/mode/text')
|
||||||
session.setMode('ace/mode/' + syntaxMode)
|
}
|
||||||
|
|
||||||
session.setUseSoftTabs(true)
|
session.setUseSoftTabs(true)
|
||||||
session.setOption('useWorker', false)
|
session.setOption('useWorker', false)
|
||||||
session.setUseWrapMode(true)
|
session.setUseWrapMode(true)
|
||||||
@@ -60,16 +44,16 @@ module.exports = React.createClass({
|
|||||||
}
|
}
|
||||||
if (prevProps.mode !== this.props.mode) {
|
if (prevProps.mode !== this.props.mode) {
|
||||||
var session = this.state.editor.getSession()
|
var session = this.state.editor.getSession()
|
||||||
let mode = _.findWhere(modes, {name: this.props.mode})
|
if (this.props.mode != null && this.props.mode.length > 0) {
|
||||||
let syntaxMode = mode != null
|
session.setMode('ace/mode/' + this.props.mode)
|
||||||
? mode.mode
|
} else {
|
||||||
: 'text'
|
session.setMode('ace/mode/text')
|
||||||
session.setMode('ace/mode/' + syntaxMode)
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
return (
|
return (
|
||||||
<div ref='target' className={this.props.className == null ? 'CodeEditor' : 'CodeEditor ' + this.props.className}></div>
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
import React, { PropTypes, findDOMNode } from 'react'
|
var React = require('react')
|
||||||
import linkState from 'boost/linkState'
|
|
||||||
import { sendEmail } from 'boost/api'
|
|
||||||
|
|
||||||
export default class ContactModal extends React.Component {
|
var LinkedState = require('../Mixins/LinkedState')
|
||||||
constructor (props) {
|
var KeyCaster = require('../Mixins/KeyCaster')
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.linkState = linkState
|
var Hq = require('../Services/Hq')
|
||||||
|
|
||||||
this.state = {
|
module.exports = React.createClass({
|
||||||
|
mixins: [LinkedState, KeyCaster('contactModal')],
|
||||||
|
propTypes: {
|
||||||
|
close: React.PropTypes.func
|
||||||
|
},
|
||||||
|
getInitialState: function () {
|
||||||
|
return {
|
||||||
isSent: false,
|
isSent: false,
|
||||||
mail: {
|
mail: {
|
||||||
title: '',
|
title: '',
|
||||||
content: ''
|
content: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
onKeyCast: function (e) {
|
||||||
onKeyCast (e) {
|
|
||||||
switch (e.status) {
|
switch (e.status) {
|
||||||
case 'closeModal':
|
case 'closeModal':
|
||||||
this.props.close()
|
this.props.close()
|
||||||
@@ -30,23 +32,20 @@ export default class ContactModal extends React.Component {
|
|||||||
this.sendEmail()
|
this.sendEmail()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
componentDidMount: function () {
|
||||||
componentDidMount () {
|
React.findDOMNode(this.refs.title).focus()
|
||||||
findDOMNode(this.refs.title).focus()
|
},
|
||||||
}
|
sendEmail: function () {
|
||||||
|
Hq.sendEmail(this.state.mail)
|
||||||
sendEmail () {
|
|
||||||
sendEmail(this.state.mail)
|
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
this.setState({isSent: !this.state.isSent})
|
this.setState({isSent: !this.state.isSent})
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
|
render: function () {
|
||||||
render () {
|
|
||||||
return (
|
return (
|
||||||
<div className='ContactModal modal'>
|
<div className='ContactModal modal'>
|
||||||
<div className='modal-header'><h1>Contact form</h1></div>
|
<div className='modal-header'><h1>Contact form</h1></div>
|
||||||
@@ -78,8 +77,4 @@ export default class ContactModal extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
ContactModal.propTypes = {
|
|
||||||
close: PropTypes.func
|
|
||||||
}
|
|
||||||
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
190
browser/main/Components/HomeNavigator.jsx
Normal file
190
browser/main/Components/HomeNavigator.jsx
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
/* 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})
|
||||||
|
},
|
||||||
|
handleKeyDown: function (e) {
|
||||||
|
if (this.state.currentUser == null) return
|
||||||
|
if (e.metaKey && e.keyCode > 48 && e.keyCode < 58) {
|
||||||
|
var planet = this.state.currentUser.Planets[e.keyCode - 49]
|
||||||
|
if (planet != null) {
|
||||||
|
this.transitionTo('planet', {userName: planet.userName, planetName: planet.name})
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
90
browser/main/Components/PlanetCreateModal.jsx
Normal file
90
browser/main/Components/PlanetCreateModal.jsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/* 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 () {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -1,263 +0,0 @@
|
|||||||
import React, { PropTypes} from 'react'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { CREATE_MODE, EDIT_MODE, IDLE_MODE, NEW, toggleTutorial } from 'boost/actions'
|
|
||||||
// import UserNavigator from './HomePage/UserNavigator'
|
|
||||||
import ArticleNavigator from './HomePage/ArticleNavigator'
|
|
||||||
import ArticleTopBar from './HomePage/ArticleTopBar'
|
|
||||||
import ArticleList from './HomePage/ArticleList'
|
|
||||||
import ArticleDetail from './HomePage/ArticleDetail'
|
|
||||||
import _ from 'lodash'
|
|
||||||
import { isModalOpen, closeModal } from 'boost/modal'
|
|
||||||
const electron = require('electron')
|
|
||||||
const BrowserWindow = electron.remote.BrowserWindow
|
|
||||||
|
|
||||||
const TEXT_FILTER = 'TEXT_FILTER'
|
|
||||||
const FOLDER_FILTER = 'FOLDER_FILTER'
|
|
||||||
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
|
|
||||||
const TAG_FILTER = 'TAG_FILTER'
|
|
||||||
|
|
||||||
class HomePage extends React.Component {
|
|
||||||
componentDidMount () {
|
|
||||||
// React自体のKey入力はfocusされていないElementからは動かないため、
|
|
||||||
// `window`に直接かける
|
|
||||||
this.keyHandler = e => this.handleKeyDown(e)
|
|
||||||
window.addEventListener('keydown', this.keyHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
window.removeEventListener('keydown', this.keyHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown (e) {
|
|
||||||
if (process.env.BOOST_ENV === 'development' && e.keyCode === 73 && e.metaKey && e.altKey) {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
BrowserWindow.getFocusedWindow().toggleDevTools()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isModalOpen()) {
|
|
||||||
if (e.keyCode === 27) closeModal()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let { status, dispatch } = this.props
|
|
||||||
let { nav, top, list, detail } = this.refs
|
|
||||||
|
|
||||||
if (status.isTutorialOpen) {
|
|
||||||
dispatch(toggleTutorial())
|
|
||||||
e.preventDefault()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search inputがfocusされていたら大体のキー入力は無視される。
|
|
||||||
if (top.isInputFocused() && !e.metaKey) {
|
|
||||||
if (e.keyCode === 13 || e.keyCode === 27) top.escape()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (status.mode) {
|
|
||||||
case CREATE_MODE:
|
|
||||||
case EDIT_MODE:
|
|
||||||
if (e.keyCode === 27) {
|
|
||||||
detail.handleCancelButtonClick()
|
|
||||||
}
|
|
||||||
if ((e.keyCode === 13 && e.metaKey) || (e.keyCode === 83 && e.metaKey)) {
|
|
||||||
detail.handleSaveButtonClick()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case IDLE_MODE:
|
|
||||||
if (e.keyCode === 69) {
|
|
||||||
detail.handleEditButtonClick()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
if (e.keyCode === 68) {
|
|
||||||
detail.handleDeleteButtonClick()
|
|
||||||
}
|
|
||||||
|
|
||||||
// `detail`の`openDeleteConfirmMenu`の時。
|
|
||||||
if (detail.state.openDeleteConfirmMenu) {
|
|
||||||
if (e.keyCode === 27) {
|
|
||||||
detail.handleDeleteCancelButtonClick()
|
|
||||||
}
|
|
||||||
if (e.keyCode === 13 && e.metaKey) {
|
|
||||||
detail.handleDeleteConfirmButtonClick()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// `detail`の`openDeleteConfirmMenu`が`true`なら呼ばれない。
|
|
||||||
if (e.keyCode === 27 || (e.keyCode === 70 && e.metaKey)) {
|
|
||||||
top.focusInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 38) {
|
|
||||||
list.selectPriorArticle()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 40) {
|
|
||||||
list.selectNextArticle()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 65 || e.keyCode === 13 && e.metaKey) {
|
|
||||||
nav.handleNewPostButtonClick()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { dispatch, status, articles, allArticles, activeArticle, folders, tags, filters } = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='HomePage'>
|
|
||||||
<ArticleNavigator
|
|
||||||
ref='nav'
|
|
||||||
dispatch={dispatch}
|
|
||||||
folders={folders}
|
|
||||||
status={status}
|
|
||||||
allArticles={allArticles}
|
|
||||||
/>
|
|
||||||
<ArticleTopBar
|
|
||||||
ref='top'
|
|
||||||
dispatch={dispatch}
|
|
||||||
status={status}
|
|
||||||
/>
|
|
||||||
<ArticleList
|
|
||||||
ref='list'
|
|
||||||
dispatch={dispatch}
|
|
||||||
folders={folders}
|
|
||||||
articles={articles}
|
|
||||||
status={status}
|
|
||||||
activeArticle={activeArticle}
|
|
||||||
/>
|
|
||||||
<ArticleDetail
|
|
||||||
ref='detail'
|
|
||||||
dispatch={dispatch}
|
|
||||||
activeArticle={activeArticle}
|
|
||||||
folders={folders}
|
|
||||||
status={status}
|
|
||||||
tags={tags}
|
|
||||||
filters={filters}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore invalid key
|
|
||||||
function ignoreInvalidKey (key) {
|
|
||||||
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build filter object by key
|
|
||||||
function buildFilter (key) {
|
|
||||||
if (key.match(/^\/\/.+/)) {
|
|
||||||
return {type: FOLDER_EXACT_FILTER, value: key.match(/^\/\/(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
if (key.match(/^\/.+/)) {
|
|
||||||
return {type: FOLDER_FILTER, value: key.match(/^\/(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
if (key.match(/^#(.+)/)) {
|
|
||||||
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
return {type: TEXT_FILTER, value: key}
|
|
||||||
}
|
|
||||||
|
|
||||||
function remap (state) {
|
|
||||||
let { folders, articles, status } = state
|
|
||||||
|
|
||||||
if (articles == null) articles = []
|
|
||||||
articles.sort((a, b) => {
|
|
||||||
return new Date(b.updatedAt) - new Date(a.updatedAt)
|
|
||||||
})
|
|
||||||
let allArticles = articles.slice()
|
|
||||||
|
|
||||||
let tags = _.uniq(allArticles.reduce((sum, article) => {
|
|
||||||
if (!_.isArray(article.tags)) return sum
|
|
||||||
return sum.concat(article.tags)
|
|
||||||
}, []))
|
|
||||||
|
|
||||||
// Filter articles
|
|
||||||
let filters = status.search.split(' ')
|
|
||||||
.map(key => key.trim())
|
|
||||||
.filter(ignoreInvalidKey)
|
|
||||||
.map(buildFilter)
|
|
||||||
|
|
||||||
let folderExactFilters = filters.filter(filter => filter.type === FOLDER_EXACT_FILTER)
|
|
||||||
let folderFilters = filters.filter(filter => filter.type === FOLDER_FILTER)
|
|
||||||
let textFilters = filters.filter(filter => filter.type === TEXT_FILTER)
|
|
||||||
let tagFilters = filters.filter(filter => filter.type === TAG_FILTER)
|
|
||||||
|
|
||||||
let targetFolders
|
|
||||||
if (folders != null) {
|
|
||||||
let exactTargetFolders = folders.filter(folder => {
|
|
||||||
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
|
|
||||||
})
|
|
||||||
let fuzzyTargetFolders = folders.filter(folder => {
|
|
||||||
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
|
|
||||||
})
|
|
||||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
|
||||||
|
|
||||||
if (targetFolders.length > 0) {
|
|
||||||
articles = articles.filter(article => {
|
|
||||||
return _.findWhere(targetFolders, {key: article.FolderKey})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textFilters.length > 0) {
|
|
||||||
articles = textFilters.reduce((articles, textFilter) => {
|
|
||||||
return articles.filter(article => {
|
|
||||||
return article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
|
|
||||||
})
|
|
||||||
}, articles)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tagFilters.length > 0) {
|
|
||||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
|
||||||
return articles.filter(article => {
|
|
||||||
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
|
|
||||||
})
|
|
||||||
}, articles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab active article
|
|
||||||
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
|
||||||
if (activeArticle == null) activeArticle = articles[0]
|
|
||||||
|
|
||||||
return {
|
|
||||||
folders,
|
|
||||||
status,
|
|
||||||
allArticles,
|
|
||||||
articles,
|
|
||||||
activeArticle,
|
|
||||||
tags,
|
|
||||||
filters: {
|
|
||||||
folder: folderFilters,
|
|
||||||
tag: tagFilters,
|
|
||||||
text: textFilters
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HomePage.propTypes = {
|
|
||||||
params: PropTypes.shape({
|
|
||||||
userId: PropTypes.string
|
|
||||||
}),
|
|
||||||
status: PropTypes.shape({
|
|
||||||
userId: PropTypes.string
|
|
||||||
}),
|
|
||||||
articles: PropTypes.array,
|
|
||||||
allArticles: PropTypes.array,
|
|
||||||
activeArticle: PropTypes.shape(),
|
|
||||||
dispatch: PropTypes.func,
|
|
||||||
folders: PropTypes.array,
|
|
||||||
filters: PropTypes.shape({
|
|
||||||
folder: PropTypes.array,
|
|
||||||
tag: PropTypes.array,
|
|
||||||
text: PropTypes.array
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(remap)(HomePage)
|
|
||||||
@@ -1,518 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import moment from 'moment'
|
|
||||||
import _ from 'lodash'
|
|
||||||
import ModeIcon from 'boost/components/ModeIcon'
|
|
||||||
import MarkdownPreview from 'boost/components/MarkdownPreview'
|
|
||||||
import CodeEditor from 'boost/components/CodeEditor'
|
|
||||||
import {
|
|
||||||
IDLE_MODE,
|
|
||||||
EDIT_MODE,
|
|
||||||
switchMode,
|
|
||||||
switchArticle,
|
|
||||||
switchFolder,
|
|
||||||
clearSearch,
|
|
||||||
lockStatus,
|
|
||||||
unlockStatus,
|
|
||||||
updateArticle,
|
|
||||||
destroyArticle,
|
|
||||||
NEW
|
|
||||||
} from 'boost/actions'
|
|
||||||
import linkState from 'boost/linkState'
|
|
||||||
import FolderMark from 'boost/components/FolderMark'
|
|
||||||
import TagLink from 'boost/components/TagLink'
|
|
||||||
import TagSelect from 'boost/components/TagSelect'
|
|
||||||
import ModeSelect from 'boost/components/ModeSelect'
|
|
||||||
import activityRecord from 'boost/activityRecord'
|
|
||||||
|
|
||||||
const BRAND_COLOR = '#18AF90'
|
|
||||||
|
|
||||||
const editDeleteTutorialElement = (
|
|
||||||
<svg width='300' height='500' className='tutorial'>
|
|
||||||
<text x='50' y='220' fill={BRAND_COLOR} fontSize='24'>Edit / Delete a post</text>
|
|
||||||
<text x='90' y='245' fill={BRAND_COLOR} fontSize='18'>press `e`/`d`</text>
|
|
||||||
|
|
||||||
<svg x='150' y='35'>
|
|
||||||
<path fill='white' d='M87.5,93.6c-16.3-5.7-30.6-16.7-39.9-31.4c-5.5-8.7-9-19.1-3.4-28.7c4.8-8.2,13.6-12.8,22.4-15.3
|
|
||||||
c15.7-4.5,34.4-6.2,49.7,0.4c17.3,7.4,25.6,26.3,25.7,44.4c0.1,10.4-3.4,20.9-13.1,26c-8.6,4.5-19,4.1-28.4,3.7
|
|
||||||
c-1.9-0.1-1.9,2.9,0,3c9.3,0.4,19.2,0.6,27.9-3.2c8.5-3.7,13.8-11.2,15.7-20.2c3.6-17.9-2.9-40.2-17.7-51.4
|
|
||||||
C110.8,9.1,89,9.9,70.8,14c-17.9,4-37.4,16.8-31.3,37.9C45.6,73,66.7,89.5,86.7,96.5C88.6,97.1,89.4,94.2,87.5,93.6L87.5,93.6z'/>
|
|
||||||
<path fill='white' d='M11.9,89.7c14.8-3.4,29.7-6,44.8-7.9c-0.5-0.6-1-1.3-1.4-1.9c-2.6,6.3-2.8,12.7-0.7,19.2
|
|
||||||
c0.6,1.8,3.5,1,2.9-0.8c-1.9-6-1.7-11.8,0.7-17.6c0.3-0.8-0.5-2-1.4-1.9c-15.3,1.9-30.6,4.5-45.6,8C9.3,87.3,10.1,90.2,11.9,89.7
|
|
||||||
L11.9,89.7z'/>
|
|
||||||
<path fill='white' d='M48.6,81.5c-9.4,10.4-17,22.3-22.2,35.3c-5.5,13.6-9.3,28.9-6,43.4c0.4,1.9,3.3,1.1,2.9-0.8
|
|
||||||
c-3.2-14,0.7-28.8,6-41.8c5.1-12.5,12.4-24,21.5-34C52,82.2,49.9,80,48.6,81.5L48.6,81.5z'/>
|
|
||||||
</svg>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
const tagSelectTutorialElement = (
|
|
||||||
<svg width='500' height='500' className='tutorial'>
|
|
||||||
<text x='155' y='50' fill={BRAND_COLOR} fontSize='24'>Attach some tags here!</text>
|
|
||||||
|
|
||||||
<svg x='0' y='-15'>
|
|
||||||
<path fill='white' d='M15.5,22.2c77.8-0.7,155.6-1.3,233.5-2c22.2-0.2,44.4-0.4,66.6-0.6c1.9,0,1.9-3,0-3
|
|
||||||
c-77.8,0.7-155.6,1.3-233.5,2c-22.2,0.2-44.4,0.4-66.6,0.6C13.6,19.2,13.6,22.2,15.5,22.2L15.5,22.2z'/>
|
|
||||||
<path fill='white' d='M130.8,25c-5.4,6.8-10.3,14-14.6,21.5c-0.8,1.4,1.2,3.2,2.4,1.8c1-1.2,2-2.4,3.1-3.7c1.2-1.5-0.9-3.6-2.1-2.1
|
|
||||||
c-1,1.2-2,2.4-3.1,3.7c0.8,0.6,1.6,1.2,2.4,1.8c4.2-7.3,8.9-14.3,14.2-20.9C134.1,25.6,132,23.4,130.8,25L130.8,25z'/>
|
|
||||||
<path fill='white' d='M132.6,22.1c8.4,5.9,16.8,11.9,25.2,17.8c1.6,1.1,3.1-1.5,1.5-2.6c-8.4-5.9-16.8-11.9-25.2-17.8
|
|
||||||
C132.5,18.4,131,21,132.6,22.1L132.6,22.1z'/>
|
|
||||||
<path fill='white' d='M132.9,18.6c0.4,6.7-0.7,13.3-3.5,19.3c-1.5,3.1-3.9,6.4-3.1,10c0.7,3.1,3.4,4.4,6.2,5.5
|
|
||||||
c5.1,2.1,10.5,3.1,16.1,3.2c1.9,0,1.9-3,0-3c-4.7-0.1-9.2-0.8-13.6-2.4c-3-1.1-6.2-1.9-5.4-6.6c0.4-2,2-4.1,2.8-5.9
|
|
||||||
c2.9-6.3,4-13.1,3.6-20.1C135.8,16.7,132.8,16.7,132.9,18.6L132.9,18.6z'/>
|
|
||||||
</svg>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
const modeSelectTutorialElement = (
|
|
||||||
<svg width='500' height='500' className='tutorial'>
|
|
||||||
<text x='195' y='130' fill={BRAND_COLOR} fontSize='24'>Select code syntax!!</text>
|
|
||||||
|
|
||||||
<svg x='300' y='0'>
|
|
||||||
<path fill='white' d='M99.9,58.8c-14.5-0.5-29-2.2-43.1-5.6c-12.3-2.9-27.9-6.4-37.1-15.5C7.9,26,28.2,18.9,37,16.7
|
|
||||||
c13.8-3.5,28.3-4.7,42.4-5.8c29.6-2.2,59.3-1.7,89-1c3,0.1,7.5-0.6,10.2,0.6c3.1,1.4,3.1,5.3,3.3,8.1c0.3,5.2-0.2,10.7-2.4,15.4
|
|
||||||
c-4.4,9.6-18.4,14.7-27.5,18.1c-27.1,10.1-56.7,12.8-85.3,15.6c-1.9,0.2-1.9,3.2,0,3c29.3-2.9,59.8-5.6,87.5-16.2
|
|
||||||
c9.6-3.7,22.8-8.7,27.7-18.4c2.3-4.6,3.2-9.9,3.2-15c0-3.6,0-9.4-2.9-12c-1.9-1.7-4.7-1.8-7.1-2c-4.8-0.2-9.6-0.2-14.4-0.3
|
|
||||||
c-8.7-0.2-17.5-0.3-26.2-0.4C116.7,6.3,99,6.5,81.3,7.8c-15.8,1.1-32.1,2.3-47.4,6.6c-7.7,2.2-22.1,6.9-20.9,17.4
|
|
||||||
c0.6,5.4,5.6,9.4,9.9,12.1c6.7,4.3,14.4,6.9,22,9.2c17.8,5.4,36.4,8,54.9,8.6C101.8,61.8,101.8,58.8,99.9,58.8L99.9,58.8z'/>
|
|
||||||
<path fill='white' d='M11.1,67.8c9.2-6.1,18.6-11.9,28.2-17.2c-0.7-0.3-1.5-0.6-2.2-0.9c0.9,5.3,0.7,10.3-0.5,15.5
|
|
||||||
c-0.4,1.9,2.4,2.7,2.9,0.8c1.4-5.7,1.5-11.3,0.5-17.1c-0.2-1-1.4-1.3-2.2-0.9c-9.7,5.3-19.1,11.1-28.2,17.2
|
|
||||||
C8,66.3,9.5,68.9,11.1,67.8L11.1,67.8z'/>
|
|
||||||
<path fill='white' d='M31.5,52.8C23.4,68.9,0.2,83.2,7.9,104c0.7,1.8,3.6,1,2.9-0.8C3.6,83.7,26.4,69.7,34.1,54.3
|
|
||||||
C35,52.6,32.4,51.1,31.5,52.8L31.5,52.8z'/>
|
|
||||||
</svg>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
function makeInstantArticle (article) {
|
|
||||||
return Object.assign({}, article)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class ArticleDetail extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
article: makeInstantArticle(props.activeArticle),
|
|
||||||
previewMode: false,
|
|
||||||
isArticleEdited: false,
|
|
||||||
isTagChanged: false,
|
|
||||||
isTitleChanged: false,
|
|
||||||
isContentChanged: false,
|
|
||||||
isModeChanged: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
clearInterval(this.refreshTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
|
||||||
let isModeChanged = prevProps.status.mode !== this.props.status.mode
|
|
||||||
if (isModeChanged && this.props.status.mode === EDIT_MODE) {
|
|
||||||
ReactDOM.findDOMNode(this.refs.title).focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
let nextState = {}
|
|
||||||
|
|
||||||
let isArticleChanged = nextProps.activeArticle != null && (nextProps.activeArticle.key !== this.state.article.key)
|
|
||||||
let isModeChanged = nextProps.status.mode !== this.props.status.mode
|
|
||||||
|
|
||||||
// Reset article input
|
|
||||||
if (isArticleChanged || (isModeChanged && nextProps.status.mode !== IDLE_MODE)) {
|
|
||||||
Object.assign(nextState, {
|
|
||||||
article: makeInstantArticle(nextProps.activeArticle)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean state
|
|
||||||
if (isModeChanged) {
|
|
||||||
Object.assign(nextState, {
|
|
||||||
openDeleteConfirmMenu: false,
|
|
||||||
previewMode: false,
|
|
||||||
isArticleEdited: false,
|
|
||||||
isTagChanged: false,
|
|
||||||
isTitleChanged: false,
|
|
||||||
isContentChanged: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(nextState)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderEmpty () {
|
|
||||||
return (
|
|
||||||
<div className='ArticleDetail empty'>
|
|
||||||
Command(⌘) + Enter to create a new post
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEditButtonClick (e) {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
dispatch(switchMode(EDIT_MODE))
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteButtonClick (e) {
|
|
||||||
this.setState({openDeleteConfirmMenu: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteConfirmButtonClick (e) {
|
|
||||||
let { dispatch, activeArticle } = this.props
|
|
||||||
|
|
||||||
dispatch(destroyArticle(activeArticle.key))
|
|
||||||
activityRecord.emit('ARTICLE_DESTROY')
|
|
||||||
this.setState({openDeleteConfirmMenu: false})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteCancelButtonClick (e) {
|
|
||||||
this.setState({openDeleteConfirmMenu: false})
|
|
||||||
}
|
|
||||||
|
|
||||||
renderIdle () {
|
|
||||||
let { status, activeArticle, folders } = this.props
|
|
||||||
|
|
||||||
let tags = activeArticle.tags != null ? activeArticle.tags.length > 0
|
|
||||||
? activeArticle.tags.map(tag => {
|
|
||||||
return (<TagLink key={tag} tag={tag}/>)
|
|
||||||
})
|
|
||||||
: (
|
|
||||||
<span className='noTags'>Not tagged yet</span>
|
|
||||||
) : null
|
|
||||||
let folder = _.findWhere(folders, {key: activeArticle.FolderKey})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='ArticleDetail idle'>
|
|
||||||
{this.state.openDeleteConfirmMenu
|
|
||||||
? (
|
|
||||||
<div className='deleteConfirm'>
|
|
||||||
<div className='right'>
|
|
||||||
Are you sure to delete this article?
|
|
||||||
<button onClick={e => this.handleDeleteConfirmButtonClick(e)} className='primary'>
|
|
||||||
<i className='fa fa-fw fa-check'/> Sure
|
|
||||||
</button>
|
|
||||||
<button onClick={e => this.handleDeleteCancelButtonClick(e)}>
|
|
||||||
<i className='fa fa-fw fa-times'/> Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
: (
|
|
||||||
<div className='detailInfo'>
|
|
||||||
<div className='left'>
|
|
||||||
<div className='info'>
|
|
||||||
<FolderMark color={folder.color}/> <span className='folderName'>{folder.name}</span>
|
|
||||||
Created : {moment(activeArticle.createdAt).format('YYYY/MM/DD')}
|
|
||||||
Updated : {moment(activeArticle.updatedAt).format('YYYY/MM/DD')}
|
|
||||||
</div>
|
|
||||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
|
|
||||||
</div>
|
|
||||||
<div className='right'>
|
|
||||||
<button onClick={e => this.handleEditButtonClick(e)} className='editBtn'>
|
|
||||||
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Edit (e)</span>
|
|
||||||
</button>
|
|
||||||
<button onClick={e => this.handleDeleteButtonClick(e)} className='deleteBtn'>
|
|
||||||
<i className='fa fa-fw fa-trash'/><span className='tooltip'>Delete (d)</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{status.isTutorialOpen ? editDeleteTutorialElement : null}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className='detailBody'>
|
|
||||||
<div className='detailPanel'>
|
|
||||||
<div className='header'>
|
|
||||||
<ModeIcon className='mode' mode={activeArticle.mode}/>
|
|
||||||
<div className='title'>{activeArticle.title}</div>
|
|
||||||
</div>
|
|
||||||
{activeArticle.mode === 'markdown'
|
|
||||||
? <MarkdownPreview content={activeArticle.content}/>
|
|
||||||
: <CodeEditor readOnly onChange={(e, value) => this.handleContentChange(e, value)} mode={activeArticle.mode} code={activeArticle.content}/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCancelButtonClick (e) {
|
|
||||||
let { activeArticle, dispatch } = this.props
|
|
||||||
|
|
||||||
if (activeArticle.status === NEW) {
|
|
||||||
dispatch(switchArticle(null))
|
|
||||||
}
|
|
||||||
dispatch(switchMode(IDLE_MODE))
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSaveButtonClick (e) {
|
|
||||||
let { dispatch, folders, status } = this.props
|
|
||||||
let article = this.state.article
|
|
||||||
let newArticle = Object.assign({}, article)
|
|
||||||
|
|
||||||
let folder = _.findWhere(folders, {key: article.FolderKey})
|
|
||||||
if (folder == null) return false
|
|
||||||
|
|
||||||
dispatch(unlockStatus())
|
|
||||||
|
|
||||||
delete newArticle.status
|
|
||||||
newArticle.updatedAt = new Date()
|
|
||||||
if (newArticle.createdAt == null) {
|
|
||||||
newArticle.createdAt = new Date()
|
|
||||||
activityRecord.emit('ARTICLE_CREATE')
|
|
||||||
} else {
|
|
||||||
activityRecord.emit('ARTICLE_UPDATE')
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(updateArticle(newArticle))
|
|
||||||
dispatch(switchMode(IDLE_MODE))
|
|
||||||
// Folder filterがかかっている時に、
|
|
||||||
// Searchを初期化し、更新先のFolder filterをかける
|
|
||||||
// かかれていない時に
|
|
||||||
// Searchを初期化する
|
|
||||||
if (status.targetFolders.length > 0) dispatch(switchFolder(folder.name))
|
|
||||||
else dispatch(clearSearch())
|
|
||||||
dispatch(switchArticle(newArticle.key))
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFolderKeyChange (e) {
|
|
||||||
let article = this.state.article
|
|
||||||
article.FolderKey = e.target.value
|
|
||||||
|
|
||||||
this.setState({article: article})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTitleChange (e) {
|
|
||||||
let { article } = this.state
|
|
||||||
article.title = e.target.value
|
|
||||||
let _isTitleChanged = article.title !== this.props.activeArticle.title
|
|
||||||
|
|
||||||
let { isTagChanged, isContentChanged, isArticleEdited, isModeChanged } = this.state
|
|
||||||
let _isArticleEdited = _isTitleChanged || isTagChanged || isContentChanged || isModeChanged
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
article,
|
|
||||||
isTitleChanged: _isTitleChanged,
|
|
||||||
isArticleEdited: _isArticleEdited
|
|
||||||
}, () => {
|
|
||||||
if (isArticleEdited !== _isArticleEdited) {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
if (_isArticleEdited) {
|
|
||||||
console.log('lockit')
|
|
||||||
dispatch(lockStatus())
|
|
||||||
} else {
|
|
||||||
console.log('unlockit')
|
|
||||||
dispatch(unlockStatus())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTagsChange (newTag, tags) {
|
|
||||||
let article = this.state.article
|
|
||||||
article.tags = tags
|
|
||||||
|
|
||||||
let _isTagChanged = _.difference(article.tags, this.props.activeArticle.tags).length > 0 || _.difference(this.props.activeArticle.tags, article.tags).length > 0
|
|
||||||
|
|
||||||
let { isTitleChanged, isContentChanged, isArticleEdited, isModeChanged } = this.state
|
|
||||||
let _isArticleEdited = _isTagChanged || isTitleChanged || isContentChanged || isModeChanged
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
article,
|
|
||||||
isTagChanged: _isTagChanged,
|
|
||||||
isArticleEdited: _isArticleEdited
|
|
||||||
}, () => {
|
|
||||||
if (isArticleEdited !== _isArticleEdited) {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
if (_isArticleEdited) {
|
|
||||||
console.log('lockit')
|
|
||||||
dispatch(lockStatus())
|
|
||||||
} else {
|
|
||||||
console.log('unlockit')
|
|
||||||
dispatch(unlockStatus())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleModeChange (value) {
|
|
||||||
let { article } = this.state
|
|
||||||
article.mode = value
|
|
||||||
let _isModeChanged = article.mode !== this.props.activeArticle.mode
|
|
||||||
|
|
||||||
let { isTagChanged, isContentChanged, isArticleEdited, isTitleChanged } = this.state
|
|
||||||
let _isArticleEdited = _isModeChanged || isTagChanged || isContentChanged || isTitleChanged
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
article,
|
|
||||||
previewMode: false,
|
|
||||||
isModeChanged: _isModeChanged,
|
|
||||||
isArticleEdited: _isArticleEdited
|
|
||||||
}, () => {
|
|
||||||
if (isArticleEdited !== _isArticleEdited) {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
if (_isArticleEdited) {
|
|
||||||
console.log('lockit')
|
|
||||||
dispatch(lockStatus())
|
|
||||||
} else {
|
|
||||||
console.log('unlockit')
|
|
||||||
dispatch(unlockStatus())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleModeSelectBlur () {
|
|
||||||
if (this.refs.code != null) {
|
|
||||||
this.refs.code.editor.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleContentChange (e, value) {
|
|
||||||
let { status } = this.props
|
|
||||||
if (status.mode === IDLE_MODE) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let { article } = this.state
|
|
||||||
article.content = value
|
|
||||||
let _isContentChanged = article.content !== this.props.activeArticle.content
|
|
||||||
|
|
||||||
let { isTagChanged, isModeChanged, isArticleEdited, isTitleChanged } = this.state
|
|
||||||
let _isArticleEdited = _isContentChanged || isTagChanged || isModeChanged || isTitleChanged
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
article,
|
|
||||||
isContentChanged: _isContentChanged,
|
|
||||||
isArticleEdited: _isArticleEdited
|
|
||||||
}, () => {
|
|
||||||
if (isArticleEdited !== _isArticleEdited) {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
if (_isArticleEdited) {
|
|
||||||
console.log('lockit')
|
|
||||||
dispatch(lockStatus())
|
|
||||||
} else {
|
|
||||||
console.log('unlockit')
|
|
||||||
dispatch(unlockStatus())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTogglePreviewButtonClick (e) {
|
|
||||||
this.setState({previewMode: !this.state.previewMode})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTitleKeyDown (e) {
|
|
||||||
if (e.keyCode === 9 && !e.shiftKey) {
|
|
||||||
e.preventDefault()
|
|
||||||
this.refs.mode.handleIdleSelectClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderEdit () {
|
|
||||||
let { folders, status, tags } = this.props
|
|
||||||
|
|
||||||
let folderOptions = folders.map(folder => {
|
|
||||||
return (
|
|
||||||
<option key={folder.key} value={folder.key}>{folder.name}</option>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='ArticleDetail edit'>
|
|
||||||
<div className='detailInfo'>
|
|
||||||
<div className='left'>
|
|
||||||
<select
|
|
||||||
className='folder'
|
|
||||||
value={this.state.article.FolderKey}
|
|
||||||
onChange={e => this.handleFolderKeyChange(e)}
|
|
||||||
>
|
|
||||||
{folderOptions}
|
|
||||||
</select>
|
|
||||||
{this.state.isArticleEdited ? ' (edited)' : ''}
|
|
||||||
|
|
||||||
<TagSelect
|
|
||||||
tags={this.state.article.tags}
|
|
||||||
onChange={(tags, tag) => this.handleTagsChange(tags, tag)}
|
|
||||||
suggestTags={tags}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{status.isTutorialOpen ? tagSelectTutorialElement : null}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='right'>
|
|
||||||
{
|
|
||||||
this.state.article.mode === 'markdown'
|
|
||||||
? (<button className='preview' onClick={e => this.handleTogglePreviewButtonClick(e)}>{!this.state.previewMode ? 'Preview' : 'Edit'}</button>)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
<button onClick={e => this.handleCancelButtonClick(e)}>Cancel</button>
|
|
||||||
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='detailBody'>
|
|
||||||
<div className='detailPanel'>
|
|
||||||
<div className='header'>
|
|
||||||
<div className='title'>
|
|
||||||
<input onKeyDown={e => this.handleTitleKeyDown(e)} placeholder='Title' ref='title' value={this.state.article.title} onChange={e => this.handleTitleChange(e)}/>
|
|
||||||
</div>
|
|
||||||
<ModeSelect
|
|
||||||
ref='mode'
|
|
||||||
onChange={e => this.handleModeChange(e)}
|
|
||||||
value={this.state.article.mode}
|
|
||||||
className='mode'
|
|
||||||
onBlur={() => this.handleModeSelectBlur()}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{status.isTutorialOpen ? modeSelectTutorialElement : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.state.previewMode
|
|
||||||
? <MarkdownPreview content={this.state.article.content}/>
|
|
||||||
: (<CodeEditor
|
|
||||||
ref='code'
|
|
||||||
onChange={(e, value) => this.handleContentChange(e, value)}
|
|
||||||
readOnly={false}
|
|
||||||
mode={this.state.article.mode}
|
|
||||||
code={this.state.article.content}
|
|
||||||
/>)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { status, activeArticle } = this.props
|
|
||||||
|
|
||||||
if (activeArticle == null) return this.renderEmpty()
|
|
||||||
|
|
||||||
switch (status.mode) {
|
|
||||||
case EDIT_MODE:
|
|
||||||
return this.renderEdit()
|
|
||||||
case IDLE_MODE:
|
|
||||||
default:
|
|
||||||
return this.renderIdle()
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArticleDetail.propTypes = {
|
|
||||||
status: PropTypes.shape(),
|
|
||||||
activeArticle: PropTypes.shape(),
|
|
||||||
activeUser: PropTypes.shape(),
|
|
||||||
dispatch: PropTypes.func
|
|
||||||
}
|
|
||||||
ArticleDetail.prototype.linkState = linkState
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import ModeIcon from 'boost/components/ModeIcon'
|
|
||||||
import moment from 'moment'
|
|
||||||
import { switchArticle, NEW } from 'boost/actions'
|
|
||||||
import FolderMark from 'boost/components/FolderMark'
|
|
||||||
import TagLink from 'boost/components/TagLink'
|
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
export default class ArticleList extends React.Component {
|
|
||||||
componentDidMount () {
|
|
||||||
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
clearInterval(this.refreshTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate () {
|
|
||||||
let { articles, activeArticle } = this.props
|
|
||||||
var index = articles.indexOf(activeArticle)
|
|
||||||
var el = ReactDOM.findDOMNode(this)
|
|
||||||
var li = el.querySelectorAll('.ArticleList>div')[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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移動ができなかったらfalseを返す:
|
|
||||||
selectPriorArticle () {
|
|
||||||
let { articles, activeArticle, dispatch } = this.props
|
|
||||||
let targetIndex = articles.indexOf(activeArticle) - 1
|
|
||||||
let targetArticle = articles[targetIndex]
|
|
||||||
|
|
||||||
if (targetArticle != null) {
|
|
||||||
dispatch(switchArticle(targetArticle.key))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
selectNextArticle () {
|
|
||||||
let { articles, activeArticle, dispatch } = this.props
|
|
||||||
let targetIndex = articles.indexOf(activeArticle) + 1
|
|
||||||
let targetArticle = articles[targetIndex]
|
|
||||||
|
|
||||||
if (targetArticle != null) {
|
|
||||||
dispatch(switchArticle(targetArticle.key))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
handleArticleClick (article) {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
return function (e) {
|
|
||||||
if (article.status === NEW) return null
|
|
||||||
dispatch(switchArticle(article.key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { articles, activeArticle, folders } = this.props
|
|
||||||
|
|
||||||
let articleElements = articles.map(article => {
|
|
||||||
let tagElements = Array.isArray(article.tags) && article.tags.length > 0
|
|
||||||
? article.tags.map(tag => {
|
|
||||||
return (<TagLink key={tag} tag={tag}/>)
|
|
||||||
})
|
|
||||||
: (<span>Not tagged yet</span>)
|
|
||||||
let folder = _.findWhere(folders, {key: article.FolderKey})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div key={'article-' + article.key}>
|
|
||||||
<div onClick={e => this.handleArticleClick(article)(e)} className={'articleItem' + (activeArticle.key === article.key ? ' active' : '')}>
|
|
||||||
<div className='top'>
|
|
||||||
{folder != null
|
|
||||||
? <span className='folderName'><FolderMark color={folder.color}/>{folder.name}</span>
|
|
||||||
: <span><FolderMark color={-1}/>Unknown</span>
|
|
||||||
}
|
|
||||||
<span className='updatedAt'>{article.status != null ? article.status : moment(article.updatedAt).fromNow()}</span>
|
|
||||||
</div>
|
|
||||||
<div className='middle'>
|
|
||||||
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{article.status !== NEW ? article.title : '(New article)'}</div>
|
|
||||||
</div>
|
|
||||||
<div className='bottom'>
|
|
||||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tagElements}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='divider'></div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='ArticleList'>
|
|
||||||
{articleElements}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArticleList.propTypes = {
|
|
||||||
folders: PropTypes.array,
|
|
||||||
articles: PropTypes.array,
|
|
||||||
activeArticle: PropTypes.shape(),
|
|
||||||
dispatch: PropTypes.func
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import { findWhere } from 'lodash'
|
|
||||||
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, EDIT_MODE } from 'boost/actions'
|
|
||||||
import { openModal } from 'boost/modal'
|
|
||||||
import FolderMark from 'boost/components/FolderMark'
|
|
||||||
import Preferences from 'boost/components/modal/Preferences'
|
|
||||||
import CreateNewFolder from 'boost/components/modal/CreateNewFolder'
|
|
||||||
import keygen from 'boost/keygen'
|
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const remote = electron.remote
|
|
||||||
let userName = remote.getGlobal('process').env.USER
|
|
||||||
|
|
||||||
const BRAND_COLOR = '#18AF90'
|
|
||||||
|
|
||||||
const preferenceTutorialElement = (
|
|
||||||
<svg width='300' height='300' className='tutorial'>
|
|
||||||
<text x='15' y='30' fill={BRAND_COLOR} fontSize='24'>Preference</text>
|
|
||||||
<svg x='-30' y='-270' width='400' height='400'>
|
|
||||||
<path fill='white' d='M165.9,297c5.3,0,10.6,0.1,15.8,0.1c3.3,0,7.7,0.8,10.7-1c2.3-1.4,3.1-4,4.5-6.2c3.5-5.5,9.6-5.2,14.6-1.9
|
|
||||||
c4.6,3.1,8.7,8,8.4,13.8c-0.3,5.2-3.3,10.1-6.1,14.3c-3.1,4.7-6.6,7-12.2,7.9c-5.2,0.8-11.7,1.6-15.4-3
|
|
||||||
c-6.6-8.2,2.1-20.5,7.4-27.1c6.5-8.1,20.1-14,26.4-2.1c5.4,10.3-3.1,21.7-13,24.8c-5.7,1.8-11,0.9-16.2-1.9c-2-1.1-5-2.6-6.6-4.4
|
|
||||||
c-3.9-4.3-0.3-8.2,2.5-11.2c1.3-1.4-0.8-3.6-2.1-2.1c-2.7,2.9-5.8,6.6-5.1,10.9c0.7,4.4,5.6,6.9,9,8.9c8.6,5.1,18.7,4.8,26.8-1.2
|
|
||||||
c7.3-5.4,11.6-15,8-23.7c-3.3-8.1-11.7-11.8-20-9c-12.5,4.1-33.7,33.5-15.9,43.1c6.8,3.7,19.8,1.8,25.3-3.6
|
|
||||||
c6.1-5.8,12.1-17.2,9.5-25.7c-2.6-8.4-13.7-17-22.6-13.3c-1.6,0.7-3,1.7-4.1,3c-1.6,1.9-2.2,5.1-4.1,6.6c-3.1,2.4-10.1,1-13.7,1
|
|
||||||
c-4,0-7.9,0-11.9-0.1C164,294,164,297,165.9,297L165.9,297z'/>
|
|
||||||
</svg>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
const newPostTutorialElement = (
|
|
||||||
<svg width='900' height='900' className='tutorial'>
|
|
||||||
<text x='290' y='155' fill={BRAND_COLOR} fontSize='24'>Create a new post!!</text>
|
|
||||||
<text x='300' y='180' fill={BRAND_COLOR} fontSize='16'>press `⌘ + Enter` or `a`</text>
|
|
||||||
<svg x='130' y='-20' width='400' height='400'>
|
|
||||||
<path fill='white' d='M56.2,132.5c11.7-2.9,23.9-6,36.1-4.1c8.7,1.4,16.6,5.5,23.7,10.5c13.3,9.4,24.5,21.5,40.2,27
|
|
||||||
c1.8,0.6,2.6-2.3,0.8-2.9c-17.1-6-28.9-20.3-44-29.7c-7-4.4-14.8-7.4-23-8.2c-11.7-1.1-23.3,1.7-34.5,4.5
|
|
||||||
C53.6,130.1,54.4,133,56.2,132.5L56.2,132.5 z'/>
|
|
||||||
</svg>
|
|
||||||
<svg x='130' y='-120' width='400' height='400'>
|
|
||||||
<path fill='white' d='M82.6,218c-7.7,4.5-15.3,9.3-22.7,14.3c-1,0.7-0.9,2.4,0.4,2.7c6.2,1.8,11.5,4.8,16.2,9.2
|
|
||||||
c1.4,1.3,3.5-0.8,2.1-2.1c-5.1-4.8-10.9-8.1-17.6-10c0.1,0.9,0.2,1.8,0.4,2.7c7.4-5,15-9.8,22.7-14.3
|
|
||||||
C85.7,219.7,84.2,217.1,82.6,218L82.6,218z'/>
|
|
||||||
</svg>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
const newFolderTutorialElement = (
|
|
||||||
<svg width='800' height='500' className='tutorial'>
|
|
||||||
<text x='145' y='110' fill={BRAND_COLOR} fontSize='24'>Create a new folder!!</text>
|
|
||||||
<svg x='115' y='-10' width='300' height='400'>
|
|
||||||
<path fill='white' d='M36.6,3.7C28.8,8.2,21.3,13,13.9,18c-1,0.7-0.9,2.4,0.4,2.7c6.2,1.8,11.5,4.8,16.2,9.2
|
|
||||||
c1.4,1.3,3.5-0.8,2.1-2.1c-5.1-4.8-10.9-8.1-17.6-10c0.1,0.9,0.2,1.8,0.4,2.7c7.4-5,15-9.8,22.7-14.3C39.7,5.3,38.2,2.7,36.6,3.7
|
|
||||||
L36.6,3.7z'/>
|
|
||||||
<path fill='white' d='M16.8,21.5c13.3-6.9,29.5-7,42.6,0.6c5.6,3.2,10.4,7.7,14.1,13c3.8,5.4,10.3,16.2,2.2,20.6
|
|
||||||
c-1.2,0.7-2.5,1.2-3.9,1.6c-1.1,0.4-2.3,0.5-3.4,0.5c-1.3-1.4-2.6-2.8-3.9-4.2c-0.2-4.6,7.5-6,10.5-5.8
|
|
||||||
c7.4,0.7,13.7,6.2,18.4,11.6c9.4,10.7,14.7,24.3,15.6,38.5c0.1,1.9,3.1,1.9,3,0c-0.9-15.5-6.9-30.4-17.5-41.8
|
|
||||||
c-6.8-7.3-25.8-19.1-32.3-4.8c-1.9,4.1,0.3,8.5,4.8,9.4c4.6,0.8,11.6-1.8,14.3-5.7c3.6-5.3-0.1-12.8-2.8-17.6
|
|
||||||
c-3.4-6.1-8.2-11.3-13.8-15.4C50.2,11.6,31,10.9,15.3,19C13.6,19.8,15.1,22.4,16.8,21.5L16.8,21.5z'/>
|
|
||||||
</svg>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default class ArticleNavigator extends React.Component {
|
|
||||||
handlePreferencesButtonClick (e) {
|
|
||||||
openModal(Preferences)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNewPostButtonClick (e) {
|
|
||||||
let { dispatch, folders, status } = this.props
|
|
||||||
let { targetFolders } = status
|
|
||||||
|
|
||||||
let FolderKey = targetFolders.length > 0
|
|
||||||
? targetFolders[0].key
|
|
||||||
: folders[0].key
|
|
||||||
|
|
||||||
let newArticle = {
|
|
||||||
id: null,
|
|
||||||
key: keygen(),
|
|
||||||
title: '',
|
|
||||||
content: '',
|
|
||||||
mode: 'markdown',
|
|
||||||
tags: [],
|
|
||||||
FolderKey: FolderKey,
|
|
||||||
status: 'NEW'
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(updateArticle(newArticle))
|
|
||||||
dispatch(switchArticle(newArticle.key, true))
|
|
||||||
dispatch(switchMode(EDIT_MODE))
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNewFolderButton (e) {
|
|
||||||
let { activeUser } = this.props
|
|
||||||
openModal(CreateNewFolder, {user: activeUser})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFolderButtonClick (name) {
|
|
||||||
return e => {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
dispatch(switchFolder(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAllFoldersButtonClick (e) {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
dispatch(setSearchFilter(''))
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { status, folders, allArticles } = this.props
|
|
||||||
let { targetFolders } = status
|
|
||||||
if (targetFolders == null) targetFolders = []
|
|
||||||
|
|
||||||
let folderElememts = folders.map((folder, index) => {
|
|
||||||
let isActive = findWhere(targetFolders, {key: folder.key})
|
|
||||||
let articleCount = allArticles.filter(article => article.FolderKey === folder.key && article.status !== 'NEW').length
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button onClick={e => this.handleFolderButtonClick(folder.name)(e)} key={'folder-' + folder.key} className={isActive ? 'active' : ''}>
|
|
||||||
<FolderMark color={folder.color}/> {folder.name} <span className='articleCount'>{articleCount}</span>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='ArticleNavigator'>
|
|
||||||
<div className='userInfo'>
|
|
||||||
<div className='userProfileName'>{userName}</div>
|
|
||||||
<div className='userName'>localStorage</div>
|
|
||||||
<button onClick={e => this.handlePreferencesButtonClick(e)} className='settingBtn'>
|
|
||||||
<i className='fa fa-fw fa-chevron-down'/>
|
|
||||||
<span className='tooltip'>Preferences</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{status.isTutorialOpen ? preferenceTutorialElement : null}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='controlSection'>
|
|
||||||
<button onClick={e => this.handleNewPostButtonClick(e)} className='newPostBtn'>
|
|
||||||
New Post
|
|
||||||
<span className='tooltip'>Create a new Post (⌘ + Enter or a)</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{status.isTutorialOpen ? newPostTutorialElement : null}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='folders'>
|
|
||||||
<div className='header'>
|
|
||||||
<div className='title'>Folders</div>
|
|
||||||
<button onClick={e => this.handleNewFolderButton(e)} className='addBtn'>
|
|
||||||
<i className='fa fa-fw fa-plus'/>
|
|
||||||
<span className='tooltip'>Create a new folder</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{status.isTutorialOpen ? newFolderTutorialElement : null}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className='folderList'>
|
|
||||||
<button onClick={e => this.handleAllFoldersButtonClick(e)} className={targetFolders.length === 0 ? 'active' : ''}>All folders</button>
|
|
||||||
{folderElememts}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArticleNavigator.propTypes = {
|
|
||||||
activeUser: PropTypes.object,
|
|
||||||
folders: PropTypes.array,
|
|
||||||
allArticles: PropTypes.array,
|
|
||||||
status: PropTypes.shape({
|
|
||||||
folderId: PropTypes.number
|
|
||||||
}),
|
|
||||||
dispatch: PropTypes.func
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import ExternalLink from 'boost/components/ExternalLink'
|
|
||||||
import { setSearchFilter, clearSearch, toggleTutorial } from 'boost/actions'
|
|
||||||
|
|
||||||
const BRAND_COLOR = '#18AF90'
|
|
||||||
|
|
||||||
const searchTutorialElement = (
|
|
||||||
<svg width='750' height='120' className='tutorial'>
|
|
||||||
<text x='450' y='33' fill={BRAND_COLOR} fontSize='24'>Search some posts!!</text>
|
|
||||||
<text x='450' y='60' fill={BRAND_COLOR} fontSize='18'>{'- Search by tag : #{string}'}</text>
|
|
||||||
<text x='450' y='85' fill={BRAND_COLOR} fontSize='18'>
|
|
||||||
{'- Search by folder : /{folder_name}\n'}</text>
|
|
||||||
<text x='465' y='105' fill={BRAND_COLOR} fontSize='14'>
|
|
||||||
{'exact match : //{folder_name}'}</text>
|
|
||||||
|
|
||||||
<svg width='500' height='300'>
|
|
||||||
<path fill='white' d='M54.5,51.5c-12.4,3.3-27.3-1.4-38.4-7C11.2,42,5,38.1,5.6,31.8c0.7-6.9,8.1-11.2,13.8-13.7
|
|
||||||
c12.3-5.4,26.4-6.8,39.7-7.7C72.4,9.6,85.7,9.7,99,9.8c55.2,0.3,110.4,2.2,165.5-1.5C291,6.5,317.7,3.8,344.1,7
|
|
||||||
c12.8,1.6,25.8,4.4,37.5,10c1.2,0.6,2.4,1.1,3.5,1.8c2.4,1.4,3.2,1.5,3.3,4.5c0.1,3.6-2.3,5.9-4.8,8.3c-3.9,3.8-8.6,6.8-13.5,9.2
|
|
||||||
c-12.6,6-26.5,7.2-40.3,7.7c-13.7,0.5-27.5,0.6-41.2,1.1c-27.7,0.9-55.3,2.2-82.9,4c-30.8,2-61.6,4.5-92.3,7.6
|
|
||||||
c-15.4,1.5-30.8,3.7-46.3,4.9c-13.6,1.1-30.7,1.5-41.8-7.8c-1.5-1.2-3.6,0.9-2.1,2.1c8.9,7.5,21.4,9.2,32.7,9.2
|
|
||||||
c15.3,0,30.6-2.6,45.8-4.2c31.3-3.3,62.7-6,94.2-8.1c30.9-2.1,61.8-3.7,92.8-4.7c15.7-0.5,31.4-0.5,47-1.3
|
|
||||||
c13.1-0.7,26.3-2.7,38.1-8.9c4.4-2.3,8.5-5.1,12-8.6c2.8-2.8,7.3-7.3,6.4-11.7c-0.8-4.3-6.4-6.3-9.8-7.9
|
|
||||||
c-5.6-2.6-11.4-4.6-17.3-6.2c-28.3-7.5-58.1-5.6-87-3.6c-62.3,4.4-124.5,2.6-187,2.4c-16.4,0-32.8,0-49,2.4
|
|
||||||
C29.9,11,13.4,13.8,5.5,24.6c-7.3,10,0.7,18.4,9.8,22.9c11.9,5.8,26.9,10.4,40,7C57.2,53.9,56.4,51,54.5,51.5L54.5,51.5z'/>
|
|
||||||
<path fill='white' d='M446.5,21.4c-9.1-1.6-18.1-3.5-27.4-3.5c-10.2,0-20.4,1.4-30.5,2.8c-1.9,0.3-1.9,3.3,0,3
|
|
||||||
c9.5-1.3,19.1-2.6,28.8-2.7c9.6-0.2,18.9,1.7,28.3,3.4C447.6,24.7,448.4,21.8,446.5,21.4L446.5,21.4z'/>
|
|
||||||
</svg>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default class ArticleTopBar extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isTooltipHidden: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.searchInput = ReactDOM.findDOMNode(this.refs.searchInput)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
this.searchInput.removeEventListener('keydown', this.showTooltip)
|
|
||||||
this.searchInput.removeEventListener('focus', this.showTooltip)
|
|
||||||
this.searchInput.removeEventListener('blur', this.showTooltip)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTooltipRequest (e) {
|
|
||||||
if (this.searchInput.value.length === 0 && (document.activeElement === this.searchInput)) {
|
|
||||||
this.setState({isTooltipHidden: false})
|
|
||||||
} else {
|
|
||||||
this.setState({isTooltipHidden: true})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isInputFocused () {
|
|
||||||
return document.activeElement === ReactDOM.findDOMNode(this.refs.searchInput)
|
|
||||||
}
|
|
||||||
|
|
||||||
escape () {
|
|
||||||
let { status, dispatch } = this.props
|
|
||||||
if (status.search.length > 0) {
|
|
||||||
dispatch(clearSearch())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.blurInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
focusInput () {
|
|
||||||
this.searchInput.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
blurInput () {
|
|
||||||
this.searchInput.blur()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearchChange (e) {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
|
|
||||||
dispatch(setSearchFilter(e.target.value))
|
|
||||||
this.handleTooltipRequest()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearchClearButton (e) {
|
|
||||||
this.searchInput.value = ''
|
|
||||||
this.focusInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTutorialButtonClick (e) {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
|
|
||||||
dispatch(toggleTutorial())
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { status } = this.props
|
|
||||||
return (
|
|
||||||
<div className='ArticleTopBar'>
|
|
||||||
<div className='left'>
|
|
||||||
<div className='search'>
|
|
||||||
<i className='fa fa-search fa-fw' />
|
|
||||||
<input
|
|
||||||
ref='searchInput'
|
|
||||||
onFocus={e => this.handleSearchChange(e)}
|
|
||||||
onBlur={e => this.handleSearchChange(e)}
|
|
||||||
value={this.props.status.search}
|
|
||||||
onChange={e => this.handleSearchChange(e)}
|
|
||||||
placeholder='Search'
|
|
||||||
type='text'
|
|
||||||
/>
|
|
||||||
{
|
|
||||||
this.props.status.search != null && this.props.status.search.length > 0
|
|
||||||
? <button onClick={e => this.handleSearchClearButton(e)} className='searchClearBtn'><i className='fa fa-times'/></button>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
|
|
||||||
- Search by tag : #{'{string}'}<br/>
|
|
||||||
- Search by folder : /{'{folder_name}'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{status.isTutorialOpen ? searchTutorialElement : null}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className='right'>
|
|
||||||
<button onClick={e => this.handleTutorialButtonClick(e)}>?<span className='tooltip'>How to use</span>
|
|
||||||
</button>
|
|
||||||
<ExternalLink className='logo' href='http://b00st.io'>
|
|
||||||
<img src='../../resources/favicon-230x230.png' width='44' height='44'/>
|
|
||||||
<span className='tooltip'>Boost official page</span>
|
|
||||||
</ExternalLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{status.isTutorialOpen ? (
|
|
||||||
<div className='tutorial'>
|
|
||||||
<div onClick={e => this.handleTutorialButtonClick(e)} className='clickJammer'/>
|
|
||||||
<svg width='500' height='250' className='finder'>
|
|
||||||
<text x='100' y='25' fontSize='32' fill={BRAND_COLOR}>Also, you can open Finder!!</text>
|
|
||||||
<text x='120' y='55' fontSize='18' fill={BRAND_COLOR}>with pressing `Control` + `shift` + `tab`</text>
|
|
||||||
</svg>
|
|
||||||
<svg width='450' className='global'>
|
|
||||||
<text x='100' y='45' fontSize='24' fill={BRAND_COLOR}>Hope you to enjoy our app :D</text>
|
|
||||||
<text x='50' y='75' fontSize='18' fill={BRAND_COLOR}>Press any key or click to escape tutorial mode</text>
|
|
||||||
</svg>
|
|
||||||
<div className='back'></div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArticleTopBar.propTypes = {
|
|
||||||
search: PropTypes.string,
|
|
||||||
dispatch: PropTypes.func,
|
|
||||||
status: PropTypes.shape({
|
|
||||||
search: PropTypes.string
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import React, { Component, PropTypes } from 'react'
|
|
||||||
import { Link } from 'react-router'
|
|
||||||
import ProfileImage from 'boost/components/ProfileImage'
|
|
||||||
import { openModal } from 'boost/modal'
|
|
||||||
import CreateNewTeam from 'boost/components/modal/CreateNewTeam'
|
|
||||||
|
|
||||||
export default class UserNavigator extends Component {
|
|
||||||
handleClick (e) {
|
|
||||||
openModal(CreateNewTeam)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for dev
|
|
||||||
componentDidMount () {
|
|
||||||
// openModal(CreateNewTeam)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderUserList () {
|
|
||||||
if (this.props.users == null) return null
|
|
||||||
|
|
||||||
var users = this.props.users.map((user, index) => (
|
|
||||||
<li key={'user-' + user.id}>
|
|
||||||
<Link to={'/users/' + user.id} activeClassName='active'>
|
|
||||||
<ProfileImage email={user.email} size='44'/>
|
|
||||||
<div className='userTooltip'>{user.name}</div>
|
|
||||||
{index < 9 ? <div className='keyLabel'>{'⌘' + (index + 1)}</div> : null}
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ul className='userList'>
|
|
||||||
{users}
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className='UserNavigator'>
|
|
||||||
{this.renderUserList()}
|
|
||||||
<button className='createTeamBtn' onClick={e => this.handleClick(e)}>
|
|
||||||
+
|
|
||||||
<div className='tooltip'>Create a new team</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UserNavigator.propTypes = {
|
|
||||||
users: PropTypes.array
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import { Link } from 'react-router'
|
|
||||||
import linkState from 'boost/linkState'
|
|
||||||
import { login } from 'boost/api'
|
|
||||||
import auth from 'boost/auth'
|
|
||||||
|
|
||||||
export default class LoginPage extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
user: {},
|
|
||||||
isSending: false,
|
|
||||||
error: null
|
|
||||||
}
|
|
||||||
this.linkState = linkState
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
this.setState({
|
|
||||||
isSending: true,
|
|
||||||
error: null
|
|
||||||
}, function () {
|
|
||||||
login(this.state.user)
|
|
||||||
.then(res => {
|
|
||||||
let { user, token } = res.body
|
|
||||||
auth.user(user, token)
|
|
||||||
|
|
||||||
this.props.history.pushState('home')
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err)
|
|
||||||
if (err.code === 'ECONNREFUSED') {
|
|
||||||
return this.setState({
|
|
||||||
error: {
|
|
||||||
name: 'CunnectionRefused',
|
|
||||||
message: 'Can\'t cznnect to API server.'
|
|
||||||
},
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
} else if (err.status != null) {
|
|
||||||
return this.setState({
|
|
||||||
error: {
|
|
||||||
name: err.response.body.name,
|
|
||||||
message: err.response.body.message
|
|
||||||
},
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else throw err
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className='LoginContainer'>
|
|
||||||
<img className='logo' src='../../resources/favicon-230x230.png'/>
|
|
||||||
|
|
||||||
<nav className='authNavigator text-center'>
|
|
||||||
<Link to='/login' activeClassName='active'>Log In</Link> / <Link to='/signup' activeClassName='active'>Sign Up</Link>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<form onSubmit={e => this.handleSubmit(e)}>
|
|
||||||
<div className='formField'>
|
|
||||||
<input valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
|
||||||
</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<input valueLink={this.linkState('user.password')} onChange={this.handleChange} type='password' placeholder='Password'/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.state.isSending
|
|
||||||
? (
|
|
||||||
<p className='alertInfo'>Logging in...</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{this.state.error != null ? <p className='alertError'>{this.state.error.message}</p> : null}
|
|
||||||
|
|
||||||
<div className='formField'>
|
|
||||||
<button className='logInButton' type='submit'>Log In</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LoginPage.propTypes = {
|
|
||||||
history: PropTypes.shape({
|
|
||||||
pushState: PropTypes.func
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
const electron = require('electron')
|
|
||||||
const ipc = electron.ipcRenderer
|
|
||||||
import React, { PropTypes } from 'react'
|
|
||||||
|
|
||||||
var ContactModal = require('boost/components/modal/ContactModal')
|
|
||||||
|
|
||||||
export default class MainContainer extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
this.state = {updateAvailable: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
ipc.on('update-available', function (message) {
|
|
||||||
this.setState({updateAvailable: true})
|
|
||||||
}.bind(this))
|
|
||||||
}
|
|
||||||
|
|
||||||
updateApp () {
|
|
||||||
ipc.send('update-app', 'Deal with it.')
|
|
||||||
}
|
|
||||||
|
|
||||||
openContactModal () {
|
|
||||||
this.openModal(ContactModal)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className='Main'>
|
|
||||||
{this.state.updateAvailable ? (
|
|
||||||
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
|
|
||||||
) : null}
|
|
||||||
{/* <button onClick={this.openContactModal} className='contactButton'>
|
|
||||||
<i className='fa fa-paper-plane-o'/>
|
|
||||||
<div className='tooltip'>Contact us</div>
|
|
||||||
</button> */}
|
|
||||||
{this.props.children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MainContainer.propTypes = {
|
|
||||||
children: PropTypes.element
|
|
||||||
}
|
|
||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
function getIn (object, path) {
|
function getIn (object, path) {
|
||||||
let stack = path.split('.')
|
var stack = path.split('.')
|
||||||
while (stack.length > 1) {
|
while (stack.length > 1) {
|
||||||
object = object[stack.shift()]
|
object = object[stack.shift()]
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,8 @@ function getIn (object, path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateIn (object, path, value) {
|
function updateIn (object, path, value) {
|
||||||
let current = object
|
var current = object
|
||||||
let stack = path.split('.')
|
var stack = path.split('.')
|
||||||
while (stack.length > 1) {
|
while (stack.length > 1) {
|
||||||
current = current[stack.shift()]
|
current = current[stack.shift()]
|
||||||
}
|
}
|
||||||
@@ -21,16 +21,11 @@ function setPartialState (component, path, value) {
|
|||||||
updateIn(component.state, path, value))
|
updateIn(component.state, path, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function linkState (path) {
|
module.exports = {
|
||||||
return {
|
linkState: function (path) {
|
||||||
value: getIn(this.state, path),
|
return {
|
||||||
requestChange: setPartialState.bind(null, this, path)
|
value: getIn(this.state, path),
|
||||||
}
|
requestChange: setPartialState.bind(null, this, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function linkState2 (el, path) {
|
|
||||||
return {
|
|
||||||
value: getIn(el.state, path),
|
|
||||||
requestChange: setPartialState.bind(null, el, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import { Link } from 'react-router'
|
|
||||||
import linkState from 'boost/linkState'
|
|
||||||
import openExternal from 'boost/openExternal'
|
|
||||||
import { signup } from 'boost/api'
|
|
||||||
import auth from 'boost/auth'
|
|
||||||
|
|
||||||
export default class SignupContainer extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
this.state = {
|
|
||||||
user: {},
|
|
||||||
connectionFailed: false,
|
|
||||||
emailConflicted: false,
|
|
||||||
nameConflicted: false,
|
|
||||||
validationFailed: false,
|
|
||||||
isSending: false,
|
|
||||||
error: null
|
|
||||||
}
|
|
||||||
this.linkState = linkState
|
|
||||||
this.openExternal = openExternal
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit (e) {
|
|
||||||
this.setState({
|
|
||||||
isSending: true,
|
|
||||||
error: null
|
|
||||||
}, function () {
|
|
||||||
signup(this.state.user)
|
|
||||||
.then(res => {
|
|
||||||
let { user, token } = res.body
|
|
||||||
auth.user(user, token)
|
|
||||||
|
|
||||||
this.props.history.pushState('home')
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err)
|
|
||||||
if (err.code === 'ECONNREFUSED') {
|
|
||||||
return this.setState({
|
|
||||||
error: {
|
|
||||||
name: 'CunnectionRefused',
|
|
||||||
message: 'Can\'t connect to API server.'
|
|
||||||
},
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
} else if (err.status != null) {
|
|
||||||
return this.setState({
|
|
||||||
error: {
|
|
||||||
name: err.response.body.name,
|
|
||||||
message: err.response.body.message
|
|
||||||
},
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else throw err
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className='SignupContainer'>
|
|
||||||
<img className='logo' src='../../resources/favicon-230x230.png'/>
|
|
||||||
|
|
||||||
<nav className='authNavigator text-center'><Link to='/login' activeClassName='active'>Log In</Link> / <Link to='/signup' activeClassName='active'>Sign Up</Link></nav>
|
|
||||||
|
|
||||||
<form onSubmit={e => this.handleSubmit(e)}>
|
|
||||||
<div className='formField'>
|
|
||||||
<input valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
|
||||||
</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<input valueLink={this.linkState('user.password')} type='password' placeholder='Password'/>
|
|
||||||
</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<input valueLink={this.linkState('user.name')} type='text' placeholder='name'/>
|
|
||||||
</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<input valueLink={this.linkState('user.profileName')} type='text' placeholder='Profile name'/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.state.isSending ? (
|
|
||||||
<p className='alertInfo'>Signing up...</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{this.state.error != null ? <p className='alertError'>{this.state.error.message}</p> : null}
|
|
||||||
|
|
||||||
<div className='formField'>
|
|
||||||
<button className='logInButton' type='submit'>Sign Up</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p className='alert'>会員登録することで、<a onClick={this.openExternal} href='http://boostio.github.io/regulations.html'>当サイトの利用規約</a>及び<a onClick={this.openExternal} href='http://boostio.github.io/privacypolicies.html'>Cookieの使用を含むデータに関するポリシー</a>に同意するものとします。</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SignupContainer.propTypes = {
|
|
||||||
history: PropTypes.shape({
|
|
||||||
pushState: PropTypes.func
|
|
||||||
})
|
|
||||||
}
|
|
||||||
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
|
||||||
|
})
|
||||||
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>
|
||||||
@@ -1,70 +1,56 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<title>CodeXen</title>
|
||||||
<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" charset="utf-8">
|
<link rel="stylesheet" href="../vendor/fontawesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
|
||||||
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
|
|
||||||
<link rel="stylesheet" href="../../node_modules/highlight.js/styles/xcode.css">
|
|
||||||
<link rel="shortcut icon" href="favicon.ico">
|
<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');
|
||||||
|
}
|
||||||
|
|
||||||
<style>
|
var to = Object(target);
|
||||||
@font-face {
|
for (var i = 1; i < arguments.length; i++) {
|
||||||
font-family: 'Lato';
|
var nextSource = arguments[i];
|
||||||
src: url('../../resources/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
if (nextSource === undefined || nextSource === null) {
|
||||||
url('../../resources/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
continue;
|
||||||
url('../../resources/Lato-Regular.ttf') format('truetype');
|
}
|
||||||
font-style: normal;
|
nextSource = Object(nextSource);
|
||||||
font-weight: normal;
|
|
||||||
text-rendering: optimizeLegibility;
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
#loadingCover{
|
</script>
|
||||||
position: absolute;
|
<script src="../vendor/moment/min/moment.min.js"></script>
|
||||||
top: 0;
|
<script src="../vendor/markdown-it/dist/markdown-it.min.js"></script>
|
||||||
bottom: 0;
|
<script src="../vendor/react/react-with-addons.js"></script>
|
||||||
left: 0;
|
<script src="../vendor/react-router/build/umd/ReactRouter.js"></script>
|
||||||
right: 0;
|
<script src="../vendor/reflux/dist/reflux.js"></script>
|
||||||
box-sizing: border-box;
|
<script src="../ace/src-min/ace.js"></script>
|
||||||
padding: 65px 0;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
#loadingCover img{
|
|
||||||
display: block;
|
|
||||||
margin: 75px auto 5px;
|
|
||||||
width: 160px;
|
|
||||||
height: 160px;
|
|
||||||
}
|
|
||||||
#loadingCover .message{
|
|
||||||
font-size: 30px;
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-weight: 100;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="loadingCover">
|
|
||||||
<img src="../../resources/favicon-230x230.png">
|
|
||||||
<div class='message'>Loading...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="content"></div>
|
<div id="content"></div>
|
||||||
|
<script src="http://localhost:8090/webpack-dev-server.js"></script>
|
||||||
<script src="../../submodules/ace/src-min/ace.js"></script>
|
<script type="text/javascript" src="http://localhost:8090/assets/main.js"></script>
|
||||||
<script type='text/javascript'>
|
<script type="text/javascript" src="http://localhost:8090/assets/main-style.js"></script>
|
||||||
const electron = require('electron')
|
|
||||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
|
||||||
var version = electron.remote.app.getVersion()
|
|
||||||
document.title = 'Boost' + ((version == null || version.length === 0) ? ' DEV' : '')
|
|
||||||
var scriptUrl = process.env.BOOST_ENV === 'development'
|
|
||||||
? 'http://localhost:8080/assets/main.js'
|
|
||||||
: '../../compiled/main.js'
|
|
||||||
var scriptEl=document.createElement('script')
|
|
||||||
scriptEl.setAttribute("type","text/javascript")
|
|
||||||
scriptEl.setAttribute("src", scriptUrl)
|
|
||||||
document.getElementsByTagName("head")[0].appendChild(scriptEl)
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Provider } from 'react-redux'
|
|
||||||
// import { updateUser } from 'boost/actions'
|
|
||||||
import { Router, Route, IndexRoute } from 'react-router'
|
|
||||||
import MainPage from './MainPage'
|
|
||||||
import HomePage from './HomePage'
|
|
||||||
// import auth from 'boost/auth'
|
|
||||||
import store from 'boost/store'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
require('../styles/main/index.styl')
|
|
||||||
import { openModal } from 'boost/modal'
|
|
||||||
import Tutorial from 'boost/components/modal/Tutorial'
|
|
||||||
import activityRecord from 'boost/activityRecord'
|
|
||||||
const electron = require('electron')
|
|
||||||
const ipc = electron.ipcRenderer
|
|
||||||
|
|
||||||
activityRecord.init()
|
|
||||||
window.addEventListener('online', function () {
|
|
||||||
ipc.send('check-update', 'check-update')
|
|
||||||
})
|
|
||||||
|
|
||||||
function notify (...args) {
|
|
||||||
return new window.Notification(...args)
|
|
||||||
}
|
|
||||||
|
|
||||||
ipc.on('notify', function (e, payload) {
|
|
||||||
notify(payload.title, {
|
|
||||||
body: payload.body
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
let routes = (
|
|
||||||
<Route path='/' component={MainPage}>
|
|
||||||
<IndexRoute name='home' component={HomePage}/>
|
|
||||||
</Route>
|
|
||||||
)
|
|
||||||
|
|
||||||
let el = document.getElementById('content')
|
|
||||||
ReactDOM.render((
|
|
||||||
<div>
|
|
||||||
<Provider store={store}>
|
|
||||||
<Router>{routes}</Router>
|
|
||||||
</Provider>
|
|
||||||
</div>
|
|
||||||
), el, function () {
|
|
||||||
let loadingCover = document.getElementById('loadingCover')
|
|
||||||
loadingCover.parentNode.removeChild(loadingCover)
|
|
||||||
let status = JSON.parse(localStorage.getItem('status'))
|
|
||||||
if (status == null) status = {}
|
|
||||||
if (!status.introWatched) {
|
|
||||||
openModal(Tutorial)
|
|
||||||
status.introWatched = true
|
|
||||||
localStorage.setItem('status', JSON.stringify(status))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
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'))
|
||||||
|
})
|
||||||
|
Before Width: | Height: | Size: 5.5 KiB 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')
|
||||||
@@ -4,27 +4,22 @@
|
|||||||
global-reset()
|
global-reset()
|
||||||
@import '../shared/*'
|
@import '../shared/*'
|
||||||
|
|
||||||
iptBgColor = #E6E6E6
|
|
||||||
iptFocusBorderColor = #369DCD
|
|
||||||
|
|
||||||
body
|
body
|
||||||
font-family "Lato"
|
font-family "Lato"
|
||||||
color textColor
|
color textColor
|
||||||
font-size fontSize
|
font-size fontSize
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
overflow hidden
|
|
||||||
|
|
||||||
.Finder
|
.Finder
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
.FinderInput
|
.FinderInput
|
||||||
padding 11px
|
position absolute
|
||||||
|
top 11px
|
||||||
|
left 11px
|
||||||
|
right 11px
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
height 55px
|
height 44px
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
background-color iptBgColor
|
|
||||||
z-index 200
|
|
||||||
input
|
input
|
||||||
display block
|
display block
|
||||||
width 100%
|
width 100%
|
||||||
@@ -34,9 +29,9 @@ body
|
|||||||
height 33px
|
height 33px
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
border-radius 5px
|
border-radius 16.5px
|
||||||
&:focus, &.focus
|
&:focus, &.focus
|
||||||
border-color iptFocusBorderColor
|
border-color brandBorderColor
|
||||||
outline none
|
outline none
|
||||||
.FinderList
|
.FinderList
|
||||||
absolute left bottom
|
absolute left bottom
|
||||||
@@ -45,16 +40,12 @@ body
|
|||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
width 250px
|
width 250px
|
||||||
overflow-y auto
|
overflow-y auto
|
||||||
z-index 0
|
|
||||||
&>ul>li
|
&>ul>li
|
||||||
.articleItem
|
.articleItem
|
||||||
padding 10px
|
padding 10px
|
||||||
border solid 2px transparent
|
border solid 2px transparent
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
cursor pointer
|
cursor pointer
|
||||||
white-space nowrap
|
|
||||||
overflow-x hidden
|
|
||||||
text-overflow ellipsis
|
|
||||||
.divider
|
.divider
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
@@ -66,54 +57,25 @@ body
|
|||||||
absolute right bottom
|
absolute right bottom
|
||||||
top 55px
|
top 55px
|
||||||
left 250px
|
left 250px
|
||||||
box-shadow 0px 0px 10px 0 #CCC
|
|
||||||
z-index 100
|
|
||||||
.header
|
.header
|
||||||
absolute top left right
|
absolute top left right
|
||||||
height 55px
|
height 44px
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
line-height 55px
|
line-height 44px
|
||||||
font-size 18px
|
font-size 1.3em
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
text-overflow ellipsis
|
text-overflow ellipsis
|
||||||
overflow-x hidden
|
overflow-x hidden
|
||||||
clearfix()
|
|
||||||
.left
|
|
||||||
float left
|
|
||||||
.right
|
|
||||||
float right
|
|
||||||
button
|
|
||||||
border-radius 16.5px
|
|
||||||
cursor pointer
|
|
||||||
height 33px
|
|
||||||
width 33px
|
|
||||||
border none
|
|
||||||
margin-right 5px
|
|
||||||
font-size 18px
|
|
||||||
color inactiveTextColor
|
|
||||||
background-color transparent
|
|
||||||
padding 0
|
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
&.clipboardBtn .tooltip
|
|
||||||
margin-left -160px
|
|
||||||
margin-top 25px
|
|
||||||
&:hover
|
|
||||||
color textColor
|
|
||||||
.tooltip
|
|
||||||
opacity 1
|
|
||||||
.content
|
.content
|
||||||
position absolute
|
.ace_editor, .marked
|
||||||
top 55px
|
position absolute
|
||||||
padding 10px
|
top 49px
|
||||||
bottom 0
|
left 5px
|
||||||
left 0
|
right 5px
|
||||||
right 0
|
bottom 5px
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
overflow-y auto
|
.marked
|
||||||
.MarkdownPreview
|
|
||||||
marked()
|
marked()
|
||||||
.CodeEditor
|
overflow-y auto
|
||||||
absolute top bottom left right
|
|
||||||
|
|||||||
@@ -1,329 +0,0 @@
|
|||||||
noTagsColor = #999
|
|
||||||
iptFocusBorderColor = #369DCD
|
|
||||||
|
|
||||||
.ArticleDetail
|
|
||||||
absolute right bottom
|
|
||||||
top 60px
|
|
||||||
left 450px
|
|
||||||
padding 10px
|
|
||||||
background-color #E6E6E6
|
|
||||||
border-top 1px solid borderColor
|
|
||||||
border-left 1px solid borderColor
|
|
||||||
*
|
|
||||||
-webkit-user-select all
|
|
||||||
.deleteConfirm
|
|
||||||
width 100%
|
|
||||||
height 70px
|
|
||||||
.right
|
|
||||||
float right
|
|
||||||
button
|
|
||||||
cursor pointer
|
|
||||||
height 33px
|
|
||||||
padding 0 10px
|
|
||||||
margin-left 5px
|
|
||||||
font-size 14px
|
|
||||||
color inactiveTextColor
|
|
||||||
background-color darken(white, 5%)
|
|
||||||
border solid 1px borderColor
|
|
||||||
border-radius 5px
|
|
||||||
&:hover
|
|
||||||
background-color white
|
|
||||||
&.primary
|
|
||||||
border none
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
&:hover
|
|
||||||
color white
|
|
||||||
background-color lighten(brandColor, 10%)
|
|
||||||
|
|
||||||
.detailInfo
|
|
||||||
height 70px
|
|
||||||
width 100%
|
|
||||||
font-size 12px
|
|
||||||
position relative
|
|
||||||
.left
|
|
||||||
absolute top left bottom
|
|
||||||
right 120px
|
|
||||||
.folderName
|
|
||||||
display inline-block
|
|
||||||
max-width 100px
|
|
||||||
overflow ellipsis
|
|
||||||
height 10px
|
|
||||||
.right
|
|
||||||
absolute top right
|
|
||||||
.detailBody
|
|
||||||
absolute left right bottom
|
|
||||||
top 70px
|
|
||||||
overflow-x hidden
|
|
||||||
overflow-y auto
|
|
||||||
.detailPanel
|
|
||||||
absolute top
|
|
||||||
left 10px
|
|
||||||
right 10px
|
|
||||||
bottom 10px
|
|
||||||
background-color white
|
|
||||||
border-radius 5px
|
|
||||||
border solid 1px borderColor
|
|
||||||
&>.header
|
|
||||||
absolute top left right
|
|
||||||
height 60px
|
|
||||||
.MarkdownPreview
|
|
||||||
absolute left right bottom
|
|
||||||
top 60px
|
|
||||||
marked()
|
|
||||||
box-sizing border-box
|
|
||||||
padding 5px 15px
|
|
||||||
border-top solid 1px borderColor
|
|
||||||
overflow-y auto
|
|
||||||
.CodeEditor
|
|
||||||
absolute left right bottom
|
|
||||||
top 60px
|
|
||||||
border-top solid 1px borderColor
|
|
||||||
min-height 300px
|
|
||||||
border-bottom-left-radius 5px
|
|
||||||
border-bottom-right-radius 5px
|
|
||||||
&.edit
|
|
||||||
.detailInfo
|
|
||||||
.left
|
|
||||||
&>.tutorial
|
|
||||||
position fixed
|
|
||||||
z-index 35
|
|
||||||
font-style italic
|
|
||||||
.folder
|
|
||||||
border none
|
|
||||||
width 150px
|
|
||||||
height 27px
|
|
||||||
outline none
|
|
||||||
background-color darken(white, 5%)
|
|
||||||
&:hover
|
|
||||||
background-color white
|
|
||||||
.TagSelect
|
|
||||||
.tags
|
|
||||||
white-space nowrap
|
|
||||||
overflow-x auto
|
|
||||||
position relative
|
|
||||||
max-width 350px
|
|
||||||
margin-top 5px
|
|
||||||
noSelect()
|
|
||||||
z-index 30
|
|
||||||
background-color #E6E6E6
|
|
||||||
.tagItem
|
|
||||||
background-color brandColor
|
|
||||||
border-radius 2px
|
|
||||||
color white
|
|
||||||
margin 0 2px
|
|
||||||
padding 0
|
|
||||||
border 1px solid darken(brandColor, 10%)
|
|
||||||
button.tagRemoveBtn
|
|
||||||
color white
|
|
||||||
border-radius 2px
|
|
||||||
border none
|
|
||||||
background-color transparent
|
|
||||||
padding 4px 2px
|
|
||||||
border-right 1px solid #E6E6E6
|
|
||||||
font-size 8px
|
|
||||||
line-height 12px
|
|
||||||
transition 0.1s
|
|
||||||
&:hover
|
|
||||||
background-color lighten(brandColor, 10%)
|
|
||||||
.tagLabel
|
|
||||||
padding 4px 4px
|
|
||||||
font-size 12px
|
|
||||||
line-height 12px
|
|
||||||
input.tagInput
|
|
||||||
background-color transparent
|
|
||||||
outline none
|
|
||||||
margin 0 2px
|
|
||||||
border-radius 2px
|
|
||||||
border none
|
|
||||||
transition 0.1s
|
|
||||||
height 18px
|
|
||||||
.suggestTags
|
|
||||||
position fixed
|
|
||||||
width 150px
|
|
||||||
max-height 150px
|
|
||||||
background-color white
|
|
||||||
z-index 5
|
|
||||||
border 1px solid borderColor
|
|
||||||
border-radius 5px
|
|
||||||
button
|
|
||||||
width 100%
|
|
||||||
display block
|
|
||||||
padding 0 15px
|
|
||||||
height 33px
|
|
||||||
line-height 33px
|
|
||||||
background-color transparent
|
|
||||||
border none
|
|
||||||
text-align left
|
|
||||||
font-size 14px
|
|
||||||
&:hover
|
|
||||||
background-color darken(white, 10%)
|
|
||||||
.right
|
|
||||||
button
|
|
||||||
cursor pointer
|
|
||||||
height 33px
|
|
||||||
width 55px
|
|
||||||
margin-left 5px
|
|
||||||
font-size 14px
|
|
||||||
color inactiveTextColor
|
|
||||||
background-color darken(white, 5%)
|
|
||||||
border solid 1px borderColor
|
|
||||||
border-radius 5px
|
|
||||||
&.preview
|
|
||||||
width inherit
|
|
||||||
&:hover
|
|
||||||
background-color white
|
|
||||||
&.primary
|
|
||||||
border none
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
&:hover
|
|
||||||
color white
|
|
||||||
background-color lighten(brandColor, 10%)
|
|
||||||
.detailBody
|
|
||||||
.detailPanel
|
|
||||||
&>.header
|
|
||||||
&>.tutorial
|
|
||||||
fixed right
|
|
||||||
z-index 35
|
|
||||||
font-style italic
|
|
||||||
.mode
|
|
||||||
position relative
|
|
||||||
z-index 30
|
|
||||||
absolute top bottom right
|
|
||||||
display block
|
|
||||||
height 33px
|
|
||||||
margin-top 12px
|
|
||||||
width 150px
|
|
||||||
margin-right 15px
|
|
||||||
border-radius 5px
|
|
||||||
border solid 1px borderColor
|
|
||||||
transition 0.1s
|
|
||||||
&.idle
|
|
||||||
background-color darken(white, 5%)
|
|
||||||
cursor pointer
|
|
||||||
&:hover
|
|
||||||
background-color white
|
|
||||||
.ModeIcon
|
|
||||||
float left
|
|
||||||
width 25px
|
|
||||||
line-height 33px
|
|
||||||
text-align center
|
|
||||||
.modeLabel
|
|
||||||
line-height 30px
|
|
||||||
&.edit
|
|
||||||
background-color white
|
|
||||||
input
|
|
||||||
width 150px
|
|
||||||
line-height 30px
|
|
||||||
padding 0 10px
|
|
||||||
border none
|
|
||||||
outline none
|
|
||||||
background-color transparent
|
|
||||||
font-size 14px
|
|
||||||
.modeOptions
|
|
||||||
position fixed
|
|
||||||
width 150px
|
|
||||||
z-index 10
|
|
||||||
margin-top 5px
|
|
||||||
border 1px solid borderColor
|
|
||||||
border-radius 5px
|
|
||||||
background-color white
|
|
||||||
max-height 250px
|
|
||||||
overflow-y auto
|
|
||||||
.option
|
|
||||||
height 33px
|
|
||||||
line-height 33px
|
|
||||||
cursor pointer
|
|
||||||
&.active, &:hover.active
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
.ModeIcon
|
|
||||||
width 30px
|
|
||||||
text-align center
|
|
||||||
display inline-block
|
|
||||||
&:hover
|
|
||||||
background-color darken(white, 10%)
|
|
||||||
.title
|
|
||||||
absolute left top bottom
|
|
||||||
right 150px
|
|
||||||
padding 0 15px
|
|
||||||
input
|
|
||||||
width 100%
|
|
||||||
border none
|
|
||||||
background-color transparent
|
|
||||||
line-height 60px
|
|
||||||
font-size 24px
|
|
||||||
outline none
|
|
||||||
&.idle
|
|
||||||
.detailInfo
|
|
||||||
&>.tutorial
|
|
||||||
fixed top right
|
|
||||||
z-index 35
|
|
||||||
font-style italic
|
|
||||||
.left
|
|
||||||
right 99px
|
|
||||||
.info
|
|
||||||
padding 5px
|
|
||||||
overflow ellipsis
|
|
||||||
.tags
|
|
||||||
padding 10px 10px 5px
|
|
||||||
color articleItemColor
|
|
||||||
a
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
border-radius 2px
|
|
||||||
padding 1.5px 5px
|
|
||||||
margin 2px
|
|
||||||
font-size 10px
|
|
||||||
opacity 0.8
|
|
||||||
cursor pointer
|
|
||||||
&:hover
|
|
||||||
opacity 1
|
|
||||||
span.noTags
|
|
||||||
color noTagsColor
|
|
||||||
.right
|
|
||||||
z-index 30
|
|
||||||
button
|
|
||||||
border-radius 16.5px
|
|
||||||
cursor pointer
|
|
||||||
height 33px
|
|
||||||
width 33px
|
|
||||||
border none
|
|
||||||
margin-right 5px
|
|
||||||
font-size 18px
|
|
||||||
color inactiveTextColor
|
|
||||||
background-color darken(white, 5%)
|
|
||||||
padding 0
|
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
&.editBtn .tooltip
|
|
||||||
margin-top 25px
|
|
||||||
margin-left -45px
|
|
||||||
&.deleteBtn .tooltip
|
|
||||||
margin-top 25px
|
|
||||||
margin-left -73px
|
|
||||||
&:hover
|
|
||||||
color textColor
|
|
||||||
.tooltip
|
|
||||||
opacity 1
|
|
||||||
.detailBody
|
|
||||||
.detailPanel
|
|
||||||
&>.header
|
|
||||||
.mode
|
|
||||||
display block
|
|
||||||
line-height 60px
|
|
||||||
width 45px
|
|
||||||
height 60px
|
|
||||||
font-size 18px
|
|
||||||
text-align center
|
|
||||||
.title
|
|
||||||
absolute top bottom
|
|
||||||
left 45px
|
|
||||||
right 15px
|
|
||||||
font-size 24px
|
|
||||||
line-height 60px
|
|
||||||
|
|
||||||
white-space nowrap
|
|
||||||
overflow-x auto
|
|
||||||
overflow-y hidden
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
articleItemHoverBgColor = darken(white, 5%)
|
|
||||||
articleItemColor = #777
|
|
||||||
|
|
||||||
.ArticleList
|
|
||||||
absolute bottom
|
|
||||||
top 60px
|
|
||||||
left 200px
|
|
||||||
width 250px
|
|
||||||
border-top 1px solid borderColor
|
|
||||||
border-right 1px solid borderColor
|
|
||||||
overflow-y auto
|
|
||||||
noSelect()
|
|
||||||
&>div
|
|
||||||
.articleItem
|
|
||||||
border solid 2px transparent
|
|
||||||
position relative
|
|
||||||
height 88px
|
|
||||||
width 100%
|
|
||||||
cursor pointer
|
|
||||||
transition 0.1s
|
|
||||||
background-color white
|
|
||||||
padding 0 10px
|
|
||||||
font-size 12px
|
|
||||||
.top
|
|
||||||
clearfix()
|
|
||||||
line-height 20px
|
|
||||||
padding 5px 0
|
|
||||||
color articleItemColor
|
|
||||||
.folderName
|
|
||||||
overflow ellipsis
|
|
||||||
display inline-block
|
|
||||||
width 120px
|
|
||||||
.updatedAt
|
|
||||||
float right
|
|
||||||
line-height 20px
|
|
||||||
.middle
|
|
||||||
padding 3px 0 7px
|
|
||||||
font-size 16px
|
|
||||||
position relative
|
|
||||||
height 26px
|
|
||||||
.mode
|
|
||||||
position absolute
|
|
||||||
left 0
|
|
||||||
font-size 12px
|
|
||||||
line-height 16px
|
|
||||||
.title
|
|
||||||
position absolute
|
|
||||||
left 19px
|
|
||||||
right 0
|
|
||||||
overflow ellipsis
|
|
||||||
.bottom
|
|
||||||
padding 5px 0
|
|
||||||
overflow-x auto
|
|
||||||
white-space nowrap
|
|
||||||
.tags
|
|
||||||
color articleItemColor
|
|
||||||
a
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
border-radius 2px
|
|
||||||
padding 1.5px 5px
|
|
||||||
margin 2px
|
|
||||||
font-size 10px
|
|
||||||
opacity 0.8
|
|
||||||
&:hover
|
|
||||||
opacity 1
|
|
||||||
&:hover, &.hover
|
|
||||||
background-color articleItemHoverBgColor
|
|
||||||
&:active, &.active
|
|
||||||
background-color white
|
|
||||||
&:active, &.active
|
|
||||||
border-color brandBorderColor
|
|
||||||
.divider
|
|
||||||
border-bottom solid 1px borderColor
|
|
||||||
@@ -1,170 +0,0 @@
|
|||||||
articleNavBgColor = #353535
|
|
||||||
articleCount = #999
|
|
||||||
|
|
||||||
.ArticleNavigator
|
|
||||||
background-color articleNavBgColor
|
|
||||||
absolute top bottom left
|
|
||||||
width 200px
|
|
||||||
border-right 1px solid borderColor
|
|
||||||
color white
|
|
||||||
.userInfo
|
|
||||||
height 60px
|
|
||||||
display block
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
.userProfileName
|
|
||||||
color brandColor
|
|
||||||
font-size 28px
|
|
||||||
padding 6px 0 0 10px
|
|
||||||
white-space nowrap
|
|
||||||
text-overflow ellipsis
|
|
||||||
overflow hidden
|
|
||||||
.userName
|
|
||||||
color white
|
|
||||||
padding-left 20px
|
|
||||||
margin-top 3px
|
|
||||||
.tutorial
|
|
||||||
position fixed
|
|
||||||
z-index 35
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
pointer-event none
|
|
||||||
font-style italic
|
|
||||||
transition 0.1s
|
|
||||||
&.hide
|
|
||||||
opacity 0
|
|
||||||
.settingBtn
|
|
||||||
width 22px
|
|
||||||
height 22px
|
|
||||||
line-height 22px
|
|
||||||
border-radius 11px
|
|
||||||
position absolute
|
|
||||||
top 19px
|
|
||||||
right 14px
|
|
||||||
color white
|
|
||||||
padding 0
|
|
||||||
background-color transparent
|
|
||||||
border 1px solid white
|
|
||||||
z-index 31
|
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
margin-top -5px
|
|
||||||
margin-left 10px
|
|
||||||
&:hover
|
|
||||||
.tooltip
|
|
||||||
opacity 1
|
|
||||||
&:active
|
|
||||||
background-color brandColor
|
|
||||||
border-color brandColor
|
|
||||||
.controlSection
|
|
||||||
height 88px
|
|
||||||
padding 22px 15px
|
|
||||||
margin-bottom 44px
|
|
||||||
.tutorial
|
|
||||||
fixed top left
|
|
||||||
z-index 35
|
|
||||||
pointer-event none
|
|
||||||
font-style italic
|
|
||||||
transition 0.1s
|
|
||||||
&.hide
|
|
||||||
opacity 0
|
|
||||||
.newPostBtn
|
|
||||||
position relative
|
|
||||||
border none
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
height 44px
|
|
||||||
width 170px
|
|
||||||
border-radius 5px
|
|
||||||
font-size 20px
|
|
||||||
transition 0.1s
|
|
||||||
z-index 30
|
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
margin-left 48px
|
|
||||||
margin-top -3px
|
|
||||||
&:hover
|
|
||||||
background-color lighten(brandColor, 7%)
|
|
||||||
.tooltip
|
|
||||||
opacity 1
|
|
||||||
.folders, .members
|
|
||||||
.header
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
padding-bottom 5px
|
|
||||||
margin-bottom 10px
|
|
||||||
clearfix()
|
|
||||||
position relative
|
|
||||||
z-index 30
|
|
||||||
.title
|
|
||||||
float left
|
|
||||||
padding-left 10px
|
|
||||||
font-size 18px
|
|
||||||
line-height 22px
|
|
||||||
.addBtn
|
|
||||||
float right
|
|
||||||
margin-right 15px
|
|
||||||
width 22px
|
|
||||||
height 22px
|
|
||||||
font-size 10px
|
|
||||||
padding 0
|
|
||||||
line-height 22px
|
|
||||||
border 1px solid white
|
|
||||||
border-radius 11px
|
|
||||||
background-color transparent
|
|
||||||
color white
|
|
||||||
padding 0
|
|
||||||
font-weight bold
|
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
margin-top -6px
|
|
||||||
margin-left 11px
|
|
||||||
&:hover
|
|
||||||
.tooltip
|
|
||||||
opacity 1
|
|
||||||
&:active
|
|
||||||
background-color brandColor
|
|
||||||
border-color brandColor
|
|
||||||
.folders
|
|
||||||
absolute bottom
|
|
||||||
top 200px
|
|
||||||
width 100%
|
|
||||||
.header
|
|
||||||
.tutorial
|
|
||||||
position fixed
|
|
||||||
z-index 35px
|
|
||||||
top 200px
|
|
||||||
font-style italic
|
|
||||||
.folderList
|
|
||||||
absolute bottom
|
|
||||||
top 38px
|
|
||||||
overflow-y auto
|
|
||||||
.folderList button
|
|
||||||
height 33px
|
|
||||||
width 199px
|
|
||||||
border none
|
|
||||||
text-align left
|
|
||||||
font-size 14px
|
|
||||||
background-color transparent
|
|
||||||
color white
|
|
||||||
padding-left 15px
|
|
||||||
overflow ellipsis
|
|
||||||
&:hover
|
|
||||||
background-color transparentify(white, 5%)
|
|
||||||
&.active, &:active
|
|
||||||
background-color transparentify(lighten(brandColor, 25%), 70%)
|
|
||||||
.articleCount
|
|
||||||
color articleCount
|
|
||||||
font-size 12px
|
|
||||||
.members
|
|
||||||
.memberList>div
|
|
||||||
height 33px
|
|
||||||
width 200px
|
|
||||||
margin-bottom 5px
|
|
||||||
padding-left 15px
|
|
||||||
.memberImage
|
|
||||||
float left
|
|
||||||
margin-top 5.5px
|
|
||||||
border-radius 11px
|
|
||||||
.memberProfileName
|
|
||||||
float left
|
|
||||||
line-height 33px
|
|
||||||
margin-left 7px
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
bgColor = #E6E6E6
|
|
||||||
inputBgColor = white
|
|
||||||
iptFocusBorderColor = #369DCD
|
|
||||||
|
|
||||||
topBarBtnColor = #B3B3B3
|
|
||||||
topBarBtnBgColor = #B3B3B3
|
|
||||||
topBarBtnBgActiveColor = #3A3A3A
|
|
||||||
|
|
||||||
infoBtnColor = bgColor
|
|
||||||
infoBtnBgColor = #B3B3B3
|
|
||||||
infoBtnActiveBgColor = #3A3A3A
|
|
||||||
|
|
||||||
.ArticleTopBar
|
|
||||||
absolute top right
|
|
||||||
left 200px
|
|
||||||
height 60px
|
|
||||||
background-color bgColor
|
|
||||||
&>.tutorial
|
|
||||||
.clickJammer
|
|
||||||
fixed top left bottom right
|
|
||||||
z-index 40
|
|
||||||
background transparent
|
|
||||||
.global
|
|
||||||
fixed bottom right
|
|
||||||
height 100px
|
|
||||||
z-index 35
|
|
||||||
font-style italic
|
|
||||||
.finder
|
|
||||||
fixed bottom right
|
|
||||||
height 250px
|
|
||||||
left 50%
|
|
||||||
margin-left -250px
|
|
||||||
z-index 35
|
|
||||||
font-style italic
|
|
||||||
.back
|
|
||||||
fixed top left bottom right
|
|
||||||
z-index 20
|
|
||||||
background-color transparentify(black, 80%)
|
|
||||||
&>.left
|
|
||||||
float left
|
|
||||||
&>.tutorial
|
|
||||||
fixed top
|
|
||||||
left 200px
|
|
||||||
z-index 36
|
|
||||||
font-style italic
|
|
||||||
&>.search
|
|
||||||
position relative
|
|
||||||
float left
|
|
||||||
height 33px
|
|
||||||
margin-top 13.5px
|
|
||||||
margin-left 15px
|
|
||||||
width 350px
|
|
||||||
padding 5px 15px
|
|
||||||
transition 0.1s
|
|
||||||
font-size 16px
|
|
||||||
border 1px solid transparent
|
|
||||||
z-index 30
|
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
margin-left -24px
|
|
||||||
margin-top 35px
|
|
||||||
opacity 1
|
|
||||||
&.hide
|
|
||||||
opacity 0
|
|
||||||
input
|
|
||||||
absolute top left
|
|
||||||
width 350px
|
|
||||||
border-radius 16.5px
|
|
||||||
background-color inputBgColor
|
|
||||||
border 1px solid transparent
|
|
||||||
padding-left 35px
|
|
||||||
outline none
|
|
||||||
font-size 14px
|
|
||||||
height 33px
|
|
||||||
line-height 33px
|
|
||||||
z-index 0
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
i.fa.fa-search
|
|
||||||
position absolute
|
|
||||||
display block
|
|
||||||
top 0
|
|
||||||
left 10px
|
|
||||||
line-height 33px
|
|
||||||
z-index 1
|
|
||||||
pointer-events none
|
|
||||||
.searchClearBtn
|
|
||||||
position absolute
|
|
||||||
top 6px
|
|
||||||
right 10px
|
|
||||||
width 20px
|
|
||||||
height 20px
|
|
||||||
border-radius 10px
|
|
||||||
border none
|
|
||||||
background-color transparent
|
|
||||||
color topBarBtnColor
|
|
||||||
transition 0.1s
|
|
||||||
line-height 20px
|
|
||||||
text-align center
|
|
||||||
padding 0
|
|
||||||
&:hover
|
|
||||||
color white
|
|
||||||
background-color topBarBtnBgColor
|
|
||||||
&>.refreshBtn
|
|
||||||
float left
|
|
||||||
width 33px
|
|
||||||
height 33px
|
|
||||||
margin-top 13.5px
|
|
||||||
margin-left 15px
|
|
||||||
border none
|
|
||||||
color refreshBtColor
|
|
||||||
background transparent
|
|
||||||
font-size 18px
|
|
||||||
line-height 18px
|
|
||||||
transition 0.1s
|
|
||||||
&:hover
|
|
||||||
color refreshBtnActiveColor
|
|
||||||
&>.right
|
|
||||||
float right
|
|
||||||
&>button
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
right 74px
|
|
||||||
top 20px
|
|
||||||
width 20px
|
|
||||||
height 20px
|
|
||||||
font-size 14px
|
|
||||||
line-height 14px
|
|
||||||
background-color infoBtnBgColor
|
|
||||||
color bgColor
|
|
||||||
border-radius 11px
|
|
||||||
border none
|
|
||||||
transition 0.1s
|
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
margin-left -50px
|
|
||||||
margin-top 29px
|
|
||||||
&:hover
|
|
||||||
background-color infoBtnActiveBgColor
|
|
||||||
.tooltip
|
|
||||||
opacity 1
|
|
||||||
|
|
||||||
&>.logo
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
top 8px
|
|
||||||
right 15px
|
|
||||||
opacity 0.7
|
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
margin-top 44px
|
|
||||||
margin-left -120px
|
|
||||||
&:hover
|
|
||||||
opacity 1
|
|
||||||
.tooltip
|
|
||||||
opacity 1
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
userNavigatorBgColor = #1B1C1C
|
|
||||||
userNavigatorColor = #DDD
|
|
||||||
userAnchorColor = #979797
|
|
||||||
userAnchorBgColor = #BEBEBE
|
|
||||||
userAnchorActiveColor = textColor
|
|
||||||
userAnchorActiveBgColor = white
|
|
||||||
|
|
||||||
.UserNavigator
|
|
||||||
noSelect()
|
|
||||||
background-color userNavigatorBgColor
|
|
||||||
absolute left top bottom
|
|
||||||
width 60px
|
|
||||||
text-align center
|
|
||||||
box-sizing border-box
|
|
||||||
ul.userList
|
|
||||||
position absolute
|
|
||||||
top 25px
|
|
||||||
left 0
|
|
||||||
right 0
|
|
||||||
bottom 70px
|
|
||||||
// overflow-y auto
|
|
||||||
&>li
|
|
||||||
a
|
|
||||||
display block
|
|
||||||
width 38px
|
|
||||||
height 64px
|
|
||||||
margin 0 auto 10px
|
|
||||||
text-align center
|
|
||||||
text-decoration none
|
|
||||||
color userAnchorColor
|
|
||||||
line-height 44px
|
|
||||||
font-size 1.1em
|
|
||||||
cursor pointer
|
|
||||||
transition 0.1s
|
|
||||||
|
|
||||||
img.ProfileImage
|
|
||||||
width 38px
|
|
||||||
height 38px
|
|
||||||
border-radius 22px
|
|
||||||
opacity 0.7
|
|
||||||
&:hover
|
|
||||||
img.ProfileImage
|
|
||||||
opacity 1
|
|
||||||
.userTooltip
|
|
||||||
opacity 1
|
|
||||||
&.active
|
|
||||||
img.ProfileImage
|
|
||||||
opacity 1
|
|
||||||
.userTooltip
|
|
||||||
tooltip()
|
|
||||||
position absolute
|
|
||||||
margin-top -52px
|
|
||||||
margin-left 44px
|
|
||||||
.keyLabel
|
|
||||||
margin-top -25px
|
|
||||||
font-size 0.8em
|
|
||||||
color userNavigatorColor
|
|
||||||
button.createTeamBtn
|
|
||||||
display block
|
|
||||||
margin 0 auto
|
|
||||||
width 30px
|
|
||||||
height 30px
|
|
||||||
border-radius 15px
|
|
||||||
border 2px solid darken(white, 5%)
|
|
||||||
color darken(white, 5%)
|
|
||||||
text-align center
|
|
||||||
background-image none
|
|
||||||
background-color transparent
|
|
||||||
box-sizing border-box
|
|
||||||
absolute left right
|
|
||||||
bottom 15px
|
|
||||||
font-size 22px
|
|
||||||
line-height 22px
|
|
||||||
transition 0.1s
|
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
margin-top -26px
|
|
||||||
margin-left 30px
|
|
||||||
&:hover, &.hover, &:focus, &.focus
|
|
||||||
color white
|
|
||||||
border-color white
|
|
||||||
.tooltip
|
|
||||||
opacity 1
|
|
||||||
&:active
|
|
||||||
background-color brandColor
|
|
||||||
border-color brandColor
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
@require './components/UserNavigator'
|
|
||||||
@require './components/ArticleNavigator'
|
|
||||||
@require './components/ArticleTopBar'
|
|
||||||
@require './components/ArticleList'
|
|
||||||
@require './components/ArticleDetail'
|
|
||||||
|
|
||||||
@require './lib/modal'
|
|
||||||
@require './lib/CreateNewTeam'
|
|
||||||
@require './lib/CreateNewFolder'
|
|
||||||
@require './lib/Preferences'
|
|
||||||
@require './lib/Tutorial'
|
|
||||||
@require './lib/EditedAlert'
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
tabNavColor = #999999
|
|
||||||
iptFocusBorderColor = #369DCD
|
|
||||||
|
|
||||||
.CreateNewFolder.modal
|
|
||||||
width 600px
|
|
||||||
height 450px
|
|
||||||
.closeBtn
|
|
||||||
position absolute
|
|
||||||
top 15px
|
|
||||||
right 15px
|
|
||||||
width 33px
|
|
||||||
height 33px
|
|
||||||
font-size 18px
|
|
||||||
line-height 33px
|
|
||||||
padding 0
|
|
||||||
text-align center
|
|
||||||
background-color transparent
|
|
||||||
border none
|
|
||||||
color stripBtnColor
|
|
||||||
&:hover
|
|
||||||
color stripHoverBtnColor
|
|
||||||
.title
|
|
||||||
font-size 32px
|
|
||||||
text-align center
|
|
||||||
font-weight bold
|
|
||||||
margin-top 25px
|
|
||||||
.ipt
|
|
||||||
display block
|
|
||||||
width 330px
|
|
||||||
font-size 14px
|
|
||||||
height 44px
|
|
||||||
line-height 44px
|
|
||||||
padding 0 15px
|
|
||||||
border-radius 5px
|
|
||||||
border solid 1px borderColor
|
|
||||||
outline none
|
|
||||||
margin 75px auto 20px
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.colorSelect
|
|
||||||
text-align center
|
|
||||||
.option
|
|
||||||
cursor pointer
|
|
||||||
font-size 22px
|
|
||||||
height 48px
|
|
||||||
width 48px
|
|
||||||
margin 0 2px
|
|
||||||
border 1px solid transparent
|
|
||||||
border-radius 5px
|
|
||||||
overflow hidden
|
|
||||||
line-height 45px
|
|
||||||
text-align center
|
|
||||||
transition 0.1s
|
|
||||||
display inline-block
|
|
||||||
&:hover
|
|
||||||
border-color borderColor
|
|
||||||
font-size 28px
|
|
||||||
&.active
|
|
||||||
font-size 28px
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.alert
|
|
||||||
color infoTextColor
|
|
||||||
background-color infoBackgroundColor
|
|
||||||
font-size 14px
|
|
||||||
padding 15px 15px
|
|
||||||
width 330px
|
|
||||||
border-radius 5px
|
|
||||||
margin 15px auto 0
|
|
||||||
&.error
|
|
||||||
color errorTextColor
|
|
||||||
background-color errorBackgroundColor
|
|
||||||
.confirmBtn
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
left 180px
|
|
||||||
bottom 44px
|
|
||||||
width 240px
|
|
||||||
font-size 24px
|
|
||||||
height 44px
|
|
||||||
line-height 24px
|
|
||||||
font-weight bold
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
border none
|
|
||||||
border-radius 5px
|
|
||||||
margin 0 auto
|
|
||||||
transition 0.1s
|
|
||||||
&:hover
|
|
||||||
transform scale(1.1)
|
|
||||||
&:disabled
|
|
||||||
opacity 0.7
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
tabNavColor = #999999
|
|
||||||
iptFocusBorderColor = #369DCD
|
|
||||||
stripHoverBtnColor = #333
|
|
||||||
stripBtnColor = lighten(stripHoverBtnColor, 35%)
|
|
||||||
|
|
||||||
.CreateNewTeam.modal
|
|
||||||
width 600px
|
|
||||||
height 450px
|
|
||||||
.closeBtn
|
|
||||||
position absolute
|
|
||||||
top 15px
|
|
||||||
right 15px
|
|
||||||
width 33px
|
|
||||||
height 33px
|
|
||||||
font-size 18px
|
|
||||||
line-height 33px
|
|
||||||
padding 0
|
|
||||||
text-align center
|
|
||||||
background-color transparent
|
|
||||||
border none
|
|
||||||
color stripBtnColor
|
|
||||||
&:hover
|
|
||||||
color stripHoverBtnColor
|
|
||||||
.title
|
|
||||||
font-size 32px
|
|
||||||
text-align center
|
|
||||||
font-weight bold
|
|
||||||
margin-top 25px
|
|
||||||
.ipt
|
|
||||||
display block
|
|
||||||
width 330px
|
|
||||||
font-size 14px
|
|
||||||
height 44px
|
|
||||||
line-height 44px
|
|
||||||
padding 0 15px
|
|
||||||
border-radius 5px
|
|
||||||
border solid 1px borderColor
|
|
||||||
outline none
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.alert
|
|
||||||
padding 0 15px
|
|
||||||
height 44px
|
|
||||||
line-height 44px
|
|
||||||
width 300px
|
|
||||||
margin 0 auto
|
|
||||||
border-radius 5px
|
|
||||||
color infoTextColor
|
|
||||||
background-color infoBackgroundColor
|
|
||||||
white-space nowrap
|
|
||||||
overflow-x auto
|
|
||||||
&.error
|
|
||||||
color errorTextColor
|
|
||||||
background-color errorBackgroundColor
|
|
||||||
.confirmBtn
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
left 180px
|
|
||||||
bottom 44px
|
|
||||||
width 240px
|
|
||||||
font-size 24px
|
|
||||||
height 44px
|
|
||||||
line-height 24px
|
|
||||||
font-weight bold
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
border none
|
|
||||||
border-radius 5px
|
|
||||||
margin 0 auto
|
|
||||||
transition 0.1s
|
|
||||||
&:hover
|
|
||||||
transform scale(1.1)
|
|
||||||
&:disabled
|
|
||||||
opacity 0.7
|
|
||||||
.tabNav
|
|
||||||
absolute left right
|
|
||||||
bottom 15px
|
|
||||||
height 33px
|
|
||||||
line-height 33px
|
|
||||||
width 150px
|
|
||||||
text-align center
|
|
||||||
font-size 12px
|
|
||||||
color tabNavColor
|
|
||||||
margin 0 auto
|
|
||||||
transition 0.1s
|
|
||||||
i.active
|
|
||||||
color brandColor
|
|
||||||
.createTab
|
|
||||||
.ipt
|
|
||||||
margin 105px auto 15px
|
|
||||||
.selectTab
|
|
||||||
.memberForm
|
|
||||||
display block
|
|
||||||
margin 25px auto 15px
|
|
||||||
width 330px
|
|
||||||
clearfix()
|
|
||||||
padding 0
|
|
||||||
font-size 14px
|
|
||||||
height 44px
|
|
||||||
line-height 44px
|
|
||||||
outline none
|
|
||||||
.Select.memberName
|
|
||||||
display block
|
|
||||||
margin 0
|
|
||||||
float left
|
|
||||||
width 280px
|
|
||||||
height 44px
|
|
||||||
font-size 14px
|
|
||||||
border none
|
|
||||||
line-height 44px
|
|
||||||
background-color transparent
|
|
||||||
outline none
|
|
||||||
&.is-focus
|
|
||||||
.Select-control
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.Select-control
|
|
||||||
height 44px
|
|
||||||
line-height 44px
|
|
||||||
padding 0 0 0 15px
|
|
||||||
border-radius 5px 0 0 5px
|
|
||||||
border 1px solid borderColor
|
|
||||||
border-right none
|
|
||||||
.Select-placeholder
|
|
||||||
padding 0 0 0 15px
|
|
||||||
.Seleect-arrow
|
|
||||||
top 21px
|
|
||||||
.Select-clear
|
|
||||||
padding 0 10px
|
|
||||||
.Select-noresults, .Select-option
|
|
||||||
line-height 44px
|
|
||||||
padding 0 0 0 15px
|
|
||||||
|
|
||||||
&:focus, &.focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
button
|
|
||||||
font-weight 400
|
|
||||||
height 44px
|
|
||||||
cursor pointer
|
|
||||||
margin 0
|
|
||||||
padding 0
|
|
||||||
width 50px
|
|
||||||
float right
|
|
||||||
border none
|
|
||||||
background-color brandColor
|
|
||||||
border-top-right-radius 5px
|
|
||||||
border-bottom-right-radius 5px
|
|
||||||
color white
|
|
||||||
font-size 14px
|
|
||||||
.memberList
|
|
||||||
width 480px
|
|
||||||
margin 0 auto
|
|
||||||
height 190px
|
|
||||||
overflow scroll
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
&>li
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
height 44px
|
|
||||||
padding 0 25px
|
|
||||||
clearfix()
|
|
||||||
&:nth-last-child(1)
|
|
||||||
border-bottom-color transparent
|
|
||||||
.userPhoto
|
|
||||||
width 30px
|
|
||||||
height 30px
|
|
||||||
float left
|
|
||||||
margin-top 7px
|
|
||||||
margin-right 15px
|
|
||||||
border-radius 15px
|
|
||||||
.userInfo
|
|
||||||
float left
|
|
||||||
margin-top 7px
|
|
||||||
.userName
|
|
||||||
font-size 16px
|
|
||||||
margin-bottom 2px
|
|
||||||
.userEmail
|
|
||||||
font-size 12px
|
|
||||||
.userControl
|
|
||||||
float right
|
|
||||||
.userRole
|
|
||||||
float left
|
|
||||||
height 30px
|
|
||||||
background-color transparent
|
|
||||||
border 1px solid transparent
|
|
||||||
margin-top 7px
|
|
||||||
margin-right 35px
|
|
||||||
outline none
|
|
||||||
cursor pointer
|
|
||||||
&:hover
|
|
||||||
border-color borderColor
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
button
|
|
||||||
border none
|
|
||||||
height 30px
|
|
||||||
margin-top 7px
|
|
||||||
background-color transparent
|
|
||||||
color stripBtnColor
|
|
||||||
&:hover
|
|
||||||
color stripHoverBtnColor
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
.EditedAlert.modal
|
|
||||||
width 350px
|
|
||||||
top 100px
|
|
||||||
.title
|
|
||||||
font-size 24px
|
|
||||||
margin-bottom 15px
|
|
||||||
.message
|
|
||||||
font-size 14px
|
|
||||||
margin-bottom 15px
|
|
||||||
.control
|
|
||||||
text-align right
|
|
||||||
button
|
|
||||||
border-radius 5px
|
|
||||||
height 33px
|
|
||||||
padding 0 15px
|
|
||||||
font-size 14px
|
|
||||||
background-color white
|
|
||||||
border 1px solid borderColor
|
|
||||||
border-radius 5px
|
|
||||||
margin-left 5px
|
|
||||||
&:hover
|
|
||||||
background-color darken(white, 10%)
|
|
||||||
&.primary
|
|
||||||
border-color brandColor
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
&:hover
|
|
||||||
background-color lighten(brandColor, 10%)
|
|
||||||
@@ -1,633 +0,0 @@
|
|||||||
menuColor = #808080
|
|
||||||
menuBgColor = #E6E6E6
|
|
||||||
closeBtnBgColor = #1790C6
|
|
||||||
iptFocusBorderColor = #369DCD
|
|
||||||
|
|
||||||
.Preferences.modal
|
|
||||||
padding 0
|
|
||||||
border-radius 5px
|
|
||||||
overflow hidden
|
|
||||||
width 720px
|
|
||||||
height 450px
|
|
||||||
&>.header
|
|
||||||
absolute top left right
|
|
||||||
height 50px
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
background-color menuBgColor
|
|
||||||
&>.title
|
|
||||||
font-size 22px
|
|
||||||
font-weight bold
|
|
||||||
float left
|
|
||||||
padding-left 30px
|
|
||||||
line-height 50px
|
|
||||||
&>.closeBtn
|
|
||||||
float right
|
|
||||||
font-size 14px
|
|
||||||
background-color closeBtnBgColor
|
|
||||||
color white
|
|
||||||
padding 0 15px
|
|
||||||
height 33px
|
|
||||||
margin-top 9px
|
|
||||||
margin-right 15px
|
|
||||||
border none
|
|
||||||
border-radius 5px
|
|
||||||
&:hover
|
|
||||||
background-color lighten(closeBtnBgColor, 10%)
|
|
||||||
&>.nav
|
|
||||||
absolute left bottom
|
|
||||||
top 50px
|
|
||||||
width 180px
|
|
||||||
background-color menuBgColor
|
|
||||||
border-right 1px solid borderColor
|
|
||||||
&>button
|
|
||||||
width 100%
|
|
||||||
height 44px
|
|
||||||
font-size 18px
|
|
||||||
color menuColor
|
|
||||||
border none
|
|
||||||
background-color transparent
|
|
||||||
transition 0.1s
|
|
||||||
text-align left
|
|
||||||
padding-left 15px
|
|
||||||
&:hover
|
|
||||||
background-color darken(menuBgColor, 10%)
|
|
||||||
&.active, &:active
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
&>.content
|
|
||||||
absolute right bottom
|
|
||||||
top 50px
|
|
||||||
left 180px
|
|
||||||
overflow-y auto
|
|
||||||
&>.section
|
|
||||||
padding 10px
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
overflow-y auto
|
|
||||||
&:nth-last-child(1)
|
|
||||||
border-bottom none
|
|
||||||
&>.sectionTitle
|
|
||||||
font-size 18px
|
|
||||||
margin 10px 0 5px
|
|
||||||
color brandColor
|
|
||||||
&>.sectionInput
|
|
||||||
height 33px
|
|
||||||
margin-bottom 5px
|
|
||||||
clearfix()
|
|
||||||
label
|
|
||||||
width 180px
|
|
||||||
padding-left 15px
|
|
||||||
float left
|
|
||||||
line-height 33px
|
|
||||||
input
|
|
||||||
width 300px
|
|
||||||
float left
|
|
||||||
height 33px
|
|
||||||
border-radius 5px
|
|
||||||
border 1px solid borderColor
|
|
||||||
padding 0 10px
|
|
||||||
font-size 14px
|
|
||||||
outline none
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
&>.sectionConfirm
|
|
||||||
clearfix()
|
|
||||||
padding 5px 15px
|
|
||||||
button
|
|
||||||
float right
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
border none
|
|
||||||
border-radius 5px
|
|
||||||
height 33px
|
|
||||||
padding 0 15px
|
|
||||||
font-size 14px
|
|
||||||
&:hover
|
|
||||||
background-color lighten(brandColor, 10%)
|
|
||||||
.alert
|
|
||||||
float right
|
|
||||||
width 250px
|
|
||||||
padding 10px 15px
|
|
||||||
margin 0 10px 0
|
|
||||||
.alert
|
|
||||||
color infoTextColor
|
|
||||||
background-color infoBackgroundColor
|
|
||||||
font-size 14px
|
|
||||||
padding 15px 15px
|
|
||||||
width 330px
|
|
||||||
border-radius 5px
|
|
||||||
margin 10px auto
|
|
||||||
&.error
|
|
||||||
color errorTextColor
|
|
||||||
background-color errorBackgroundColor
|
|
||||||
&.ContactTab
|
|
||||||
&.done
|
|
||||||
.message
|
|
||||||
margin-top 75px
|
|
||||||
margin-bottom 15px
|
|
||||||
text-align center
|
|
||||||
font-size 22px
|
|
||||||
.checkIcon
|
|
||||||
margin-bottom 15px
|
|
||||||
font-size 144px
|
|
||||||
color brandColor
|
|
||||||
text-align center
|
|
||||||
.control
|
|
||||||
text-align center
|
|
||||||
button
|
|
||||||
border solid 1px borderColor
|
|
||||||
border-radius 5px
|
|
||||||
background-color white
|
|
||||||
padding 15px 15px
|
|
||||||
font-size 14px
|
|
||||||
&:hover
|
|
||||||
background-color darken(white, 10%)
|
|
||||||
&.form
|
|
||||||
padding 10px
|
|
||||||
.title
|
|
||||||
font-size 18px
|
|
||||||
color brandColor
|
|
||||||
margin-top 10px
|
|
||||||
margin-bottom 10px
|
|
||||||
.description
|
|
||||||
margin-bottom 15px
|
|
||||||
.iptGroup
|
|
||||||
margin-bottom 10px
|
|
||||||
input, textarea
|
|
||||||
border-radius 5px
|
|
||||||
border 1px solid borderColor
|
|
||||||
font-size 14px
|
|
||||||
outline none
|
|
||||||
padding 10px 15px
|
|
||||||
width 100%
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
textarea
|
|
||||||
resize vertical
|
|
||||||
min-height 150px
|
|
||||||
.formControl
|
|
||||||
clearfix()
|
|
||||||
.alert
|
|
||||||
float right
|
|
||||||
padding 10px 15px
|
|
||||||
margin 0 5px 0
|
|
||||||
font-size 14px
|
|
||||||
line-height normal
|
|
||||||
button
|
|
||||||
padding 10px 15px
|
|
||||||
background-color brandColor
|
|
||||||
color white
|
|
||||||
font-size 14px
|
|
||||||
border-radius 5px
|
|
||||||
border none
|
|
||||||
float right
|
|
||||||
&:hover
|
|
||||||
background-color lighten(brandColor, 10%)
|
|
||||||
|
|
||||||
&.AppSettingTab
|
|
||||||
.description
|
|
||||||
marked()
|
|
||||||
&.TeamSettingTab
|
|
||||||
.header
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
padding 10px
|
|
||||||
font-size 18px
|
|
||||||
color brandColor
|
|
||||||
line-height 33px
|
|
||||||
.teamSelect
|
|
||||||
border 1px solid borderColor
|
|
||||||
height 33px
|
|
||||||
width 200px
|
|
||||||
margin 0 10px
|
|
||||||
outline none
|
|
||||||
font-size 14px
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.teamDeleteConfirm
|
|
||||||
|
|
||||||
label
|
|
||||||
line-height 33px
|
|
||||||
font-size 14px
|
|
||||||
.teamDelete
|
|
||||||
label
|
|
||||||
line-height 33px
|
|
||||||
font-size 18px
|
|
||||||
color brandColor
|
|
||||||
.teamDelete, .teamDeleteConfirm
|
|
||||||
padding 15px 20px 15px 15px
|
|
||||||
button
|
|
||||||
background-color white
|
|
||||||
height 33px
|
|
||||||
font-size 14px
|
|
||||||
padding 0 15px
|
|
||||||
border 1px solid borderColor
|
|
||||||
float right
|
|
||||||
margin 0 5px
|
|
||||||
border-radius 5px
|
|
||||||
&:hover
|
|
||||||
background-color darken(white, 10%)
|
|
||||||
button.deleteBtn
|
|
||||||
background-color brandColor
|
|
||||||
border none
|
|
||||||
color white
|
|
||||||
&:hover
|
|
||||||
background-color lighten(brandColor, 10%)
|
|
||||||
&.MemberSettingTab
|
|
||||||
&>.header
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
padding 10px
|
|
||||||
font-size 18px
|
|
||||||
color brandColor
|
|
||||||
line-height 33px
|
|
||||||
.teamSelect
|
|
||||||
border 1px solid borderColor
|
|
||||||
height 33px
|
|
||||||
width 200px
|
|
||||||
margin 0 10px
|
|
||||||
outline none
|
|
||||||
font-size 14px
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.membersTableSection
|
|
||||||
.addMember
|
|
||||||
clearfix()
|
|
||||||
padding 10px
|
|
||||||
.addMemberLabel
|
|
||||||
font-size 14px
|
|
||||||
line-height 33px
|
|
||||||
float left
|
|
||||||
.addMemberControl
|
|
||||||
width 330px
|
|
||||||
float left
|
|
||||||
margin-left 25px
|
|
||||||
.Select
|
|
||||||
display block
|
|
||||||
margin 0
|
|
||||||
float left
|
|
||||||
width 280px
|
|
||||||
height 33px
|
|
||||||
font-size 14px
|
|
||||||
border none
|
|
||||||
line-height 33px
|
|
||||||
background-color transparent
|
|
||||||
outline none
|
|
||||||
&.is-focus
|
|
||||||
.Select-control
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.Select-control
|
|
||||||
height 33px
|
|
||||||
line-height 33px
|
|
||||||
padding 0 0 0 15px
|
|
||||||
border-radius 5px 0 0 5px
|
|
||||||
border 1px solid borderColor
|
|
||||||
border-right none
|
|
||||||
.Select-placeholder
|
|
||||||
padding 0 0 0 15px
|
|
||||||
.Seleect-arrow
|
|
||||||
top 21px
|
|
||||||
.Select-clear
|
|
||||||
padding 0 10px
|
|
||||||
.Select-noresults, .Select-option
|
|
||||||
line-height 33px
|
|
||||||
padding 0 0 0 15px
|
|
||||||
button
|
|
||||||
font-weight 400
|
|
||||||
height 33px
|
|
||||||
cursor pointer
|
|
||||||
margin 0
|
|
||||||
padding 0
|
|
||||||
width 50px
|
|
||||||
float right
|
|
||||||
border none
|
|
||||||
background-color brandColor
|
|
||||||
border-top-right-radius 5px
|
|
||||||
border-bottom-right-radius 5px
|
|
||||||
color white
|
|
||||||
font-size 14px
|
|
||||||
.memberList
|
|
||||||
&>.header
|
|
||||||
clearfix()
|
|
||||||
&>.userName
|
|
||||||
float left
|
|
||||||
&>.role
|
|
||||||
float left
|
|
||||||
&>.control
|
|
||||||
float right
|
|
||||||
&>li
|
|
||||||
&.edit
|
|
||||||
.colDescription
|
|
||||||
font-size 14px
|
|
||||||
line-height 33px
|
|
||||||
padding-left 15px
|
|
||||||
float left
|
|
||||||
strong
|
|
||||||
font-size 16px
|
|
||||||
color brandColor
|
|
||||||
.colDeleteConfirm
|
|
||||||
float right
|
|
||||||
margin-right 15px
|
|
||||||
button
|
|
||||||
border none
|
|
||||||
height 30px
|
|
||||||
width 60px
|
|
||||||
margin-top 1.5px
|
|
||||||
font-size 14px
|
|
||||||
background-color transparent
|
|
||||||
color stripBtnColor
|
|
||||||
&:hover
|
|
||||||
color stripHoverBtnColor
|
|
||||||
&:disabled
|
|
||||||
color lighten(stripBtnColor, 10%)
|
|
||||||
cursor not-allowed
|
|
||||||
&.primary
|
|
||||||
color brandColor
|
|
||||||
&:hover
|
|
||||||
color lighten(brandColor, 10%)
|
|
||||||
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
height 44px
|
|
||||||
padding 0 25px
|
|
||||||
width 420px
|
|
||||||
margin 0 auto
|
|
||||||
clearfix()
|
|
||||||
&:nth-last-child(1)
|
|
||||||
border-bottom-color transparent
|
|
||||||
.colUserName
|
|
||||||
float left
|
|
||||||
width 250px
|
|
||||||
clearfix()
|
|
||||||
.userPhoto
|
|
||||||
width 30px
|
|
||||||
height 30px
|
|
||||||
float left
|
|
||||||
margin-top 7px
|
|
||||||
margin-right 15px
|
|
||||||
border-radius 15px
|
|
||||||
.userInfo
|
|
||||||
float left
|
|
||||||
margin-top 7px
|
|
||||||
width 205px
|
|
||||||
.userName
|
|
||||||
font-size 16px
|
|
||||||
margin-bottom 2px
|
|
||||||
overflow ellipsis
|
|
||||||
.userEmail
|
|
||||||
font-size 12px
|
|
||||||
overflow ellipsis
|
|
||||||
.colRole
|
|
||||||
float left
|
|
||||||
width 75px
|
|
||||||
.userRole
|
|
||||||
height 30px
|
|
||||||
background-color transparent
|
|
||||||
border 1px solid transparent
|
|
||||||
margin-top 7px
|
|
||||||
margin-right 35px
|
|
||||||
outline none
|
|
||||||
cursor pointer
|
|
||||||
&:hover
|
|
||||||
border-color borderColor
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
&:disabled
|
|
||||||
border-color transparent
|
|
||||||
cursor not-allowed
|
|
||||||
.colDelete
|
|
||||||
width 45px
|
|
||||||
float right
|
|
||||||
text-align center
|
|
||||||
button.deleteButton
|
|
||||||
border none
|
|
||||||
height 30px
|
|
||||||
width 30px
|
|
||||||
margin-top 7px
|
|
||||||
background-color transparent
|
|
||||||
color stripBtnColor
|
|
||||||
&:hover
|
|
||||||
color stripHoverBtnColor
|
|
||||||
&:disabled
|
|
||||||
color lighten(stripBtnColor, 10%)
|
|
||||||
cursor not-allowed
|
|
||||||
&.header
|
|
||||||
.colRole, .colDelete
|
|
||||||
text-align center
|
|
||||||
.colUserName, .colRole, .colDelete
|
|
||||||
line-height 44px
|
|
||||||
&.FolderSettingTab
|
|
||||||
&>.header
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
padding 10px
|
|
||||||
font-size 18px
|
|
||||||
color brandColor
|
|
||||||
line-height 33px
|
|
||||||
.teamSelect
|
|
||||||
border 1px solid borderColor
|
|
||||||
height 33px
|
|
||||||
width 200px
|
|
||||||
margin 0 10px
|
|
||||||
outline none
|
|
||||||
font-size 14px
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.section
|
|
||||||
.folderTable
|
|
||||||
width 420px
|
|
||||||
margin 15px auto
|
|
||||||
&>div
|
|
||||||
border-bottom 1px solid borderColor
|
|
||||||
clearfix()
|
|
||||||
height 43px
|
|
||||||
line-height 33px
|
|
||||||
padding 5px 0
|
|
||||||
&:last-child
|
|
||||||
border-color transparent
|
|
||||||
.folderColor
|
|
||||||
float left
|
|
||||||
margin-left 10px
|
|
||||||
text-align center
|
|
||||||
width 44px
|
|
||||||
.folderName
|
|
||||||
float left
|
|
||||||
width 175px
|
|
||||||
overflow ellipsis
|
|
||||||
.folderControl
|
|
||||||
float right
|
|
||||||
width 125px
|
|
||||||
text-align center
|
|
||||||
&.folderHeader
|
|
||||||
.folderName
|
|
||||||
padding-left 25px
|
|
||||||
&.newFolder
|
|
||||||
.alert
|
|
||||||
display block
|
|
||||||
color infoTextColor
|
|
||||||
background-color infoBackgroundColor
|
|
||||||
font-size 14px
|
|
||||||
padding 15px 15px
|
|
||||||
width 330px
|
|
||||||
border-radius 5px
|
|
||||||
margin 0 auto
|
|
||||||
&.error
|
|
||||||
color errorTextColor
|
|
||||||
background-color errorBackgroundColor
|
|
||||||
.folderName input
|
|
||||||
height 33px
|
|
||||||
border 1px solid transparent
|
|
||||||
border-radius 5px
|
|
||||||
padding 0 10px
|
|
||||||
font-size 14px
|
|
||||||
outline none
|
|
||||||
width 150px
|
|
||||||
overflow ellipsis
|
|
||||||
&:hover
|
|
||||||
border-color borderColor
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.folderPublic select
|
|
||||||
height 33px
|
|
||||||
border 1px solid transparent
|
|
||||||
background-color white
|
|
||||||
outline none
|
|
||||||
display block
|
|
||||||
margin 0 auto
|
|
||||||
font-size 14px
|
|
||||||
&:hover
|
|
||||||
border-color borderColor
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.folderControl
|
|
||||||
button
|
|
||||||
border none
|
|
||||||
height 30px
|
|
||||||
margin-top 1.5px
|
|
||||||
font-size 14px
|
|
||||||
background-color transparent
|
|
||||||
color brandColor
|
|
||||||
&:hover
|
|
||||||
color lighten(brandColor, 10%)
|
|
||||||
&.FolderRow
|
|
||||||
.sortBtns
|
|
||||||
float left
|
|
||||||
display block
|
|
||||||
height 30px
|
|
||||||
width 30px
|
|
||||||
margin-top 1.5px
|
|
||||||
position absolute
|
|
||||||
button
|
|
||||||
absolute left
|
|
||||||
background-color transparent
|
|
||||||
border none
|
|
||||||
height 15px
|
|
||||||
padding 0
|
|
||||||
margin 0
|
|
||||||
color stripBtnColor
|
|
||||||
&:first-child
|
|
||||||
top 0
|
|
||||||
&:last-child
|
|
||||||
top 15px
|
|
||||||
&:hover
|
|
||||||
color stripHoverBtnColor
|
|
||||||
&:disabled
|
|
||||||
color lighten(stripBtnColor, 10%)
|
|
||||||
cursor not-allowed
|
|
||||||
.folderName input
|
|
||||||
height 33px
|
|
||||||
border 1px solid borderColor
|
|
||||||
border-radius 5px
|
|
||||||
padding 0 10px
|
|
||||||
font-size 14px
|
|
||||||
outline none
|
|
||||||
width 150px
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.folderColor
|
|
||||||
.select
|
|
||||||
height 33px
|
|
||||||
width 33px
|
|
||||||
border 1px solid borderColor
|
|
||||||
background-color white
|
|
||||||
outline none
|
|
||||||
display block
|
|
||||||
margin 0 auto
|
|
||||||
font-size 14px
|
|
||||||
border-radius 5px
|
|
||||||
&:focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.options
|
|
||||||
position absolute
|
|
||||||
background-color white
|
|
||||||
text-align left
|
|
||||||
border 1px solid borderColor
|
|
||||||
border-radius 5px
|
|
||||||
padding 0 5px 5px
|
|
||||||
margin-left 5px
|
|
||||||
margin-top -34px
|
|
||||||
clearfix()
|
|
||||||
.label
|
|
||||||
margin-left 5px
|
|
||||||
line-height 22px
|
|
||||||
font-size 12px
|
|
||||||
button
|
|
||||||
float left
|
|
||||||
border none
|
|
||||||
width 33px
|
|
||||||
height 33px
|
|
||||||
margin-right 5px
|
|
||||||
border 1px solid transparent
|
|
||||||
line-height 29px
|
|
||||||
overflow hidden
|
|
||||||
border-radius 5px
|
|
||||||
background-color transparent
|
|
||||||
outline none
|
|
||||||
transition 0.1s
|
|
||||||
&:hover
|
|
||||||
border-color borderColor
|
|
||||||
&.active
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
.FolderMark
|
|
||||||
transform scale(1.4)
|
|
||||||
.folderControl
|
|
||||||
button
|
|
||||||
border none
|
|
||||||
height 30px
|
|
||||||
width 30px
|
|
||||||
margin-top 1.5px
|
|
||||||
font-size 14px
|
|
||||||
background-color transparent
|
|
||||||
color stripBtnColor
|
|
||||||
&:hover
|
|
||||||
color stripHoverBtnColor
|
|
||||||
&:disabled
|
|
||||||
color lighten(stripBtnColor, 10%)
|
|
||||||
cursor not-allowed
|
|
||||||
&.edit
|
|
||||||
.folderControl
|
|
||||||
button
|
|
||||||
width 60px
|
|
||||||
&.primary
|
|
||||||
color brandColor
|
|
||||||
&:hover
|
|
||||||
color lighten(brandColor, 10%)
|
|
||||||
&.delete
|
|
||||||
.folderDeleteLabel
|
|
||||||
float left
|
|
||||||
height 33px
|
|
||||||
width 250px
|
|
||||||
padding-left 15px
|
|
||||||
overflow ellipsis
|
|
||||||
strong
|
|
||||||
font-size 16px
|
|
||||||
color brandColor
|
|
||||||
.folderControl
|
|
||||||
button
|
|
||||||
width 60px
|
|
||||||
&.primary
|
|
||||||
color brandColor
|
|
||||||
&:hover
|
|
||||||
color lighten(brandColor, 10%)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
|
|
||||||
slideBgColor0 = #2BAC8F
|
|
||||||
slideBgColor1 = #F68F92
|
|
||||||
slideBgColor2 = #D6AD56
|
|
||||||
slideBgColor3 = #26969B
|
|
||||||
slideBgColor4 = #00B493
|
|
||||||
|
|
||||||
.Tutorial.modal
|
|
||||||
background-color slideBgColor0
|
|
||||||
color white
|
|
||||||
width 720px
|
|
||||||
height 480px
|
|
||||||
margin-top 75px
|
|
||||||
border-radius 5px
|
|
||||||
overflow hidden
|
|
||||||
|
|
||||||
.priorBtn, .nextBtn
|
|
||||||
font-size 72px
|
|
||||||
position absolute
|
|
||||||
background-color transparent
|
|
||||||
color transparentify(white, 50%)
|
|
||||||
transition 0.1s
|
|
||||||
border none
|
|
||||||
line-height 72px
|
|
||||||
padding 0
|
|
||||||
width 93px
|
|
||||||
height 72px
|
|
||||||
z-index 2
|
|
||||||
top 189px
|
|
||||||
&:hover
|
|
||||||
color white
|
|
||||||
&.hide
|
|
||||||
opacity 0
|
|
||||||
.priorBtn
|
|
||||||
left 15px
|
|
||||||
.nextBtn
|
|
||||||
right 15px
|
|
||||||
.title
|
|
||||||
text-align center
|
|
||||||
font-size 54px
|
|
||||||
margin 40px 0
|
|
||||||
.content
|
|
||||||
text-align center
|
|
||||||
font-size 22px
|
|
||||||
line-height 1.8
|
|
||||||
.dots
|
|
||||||
position absolute
|
|
||||||
left 0
|
|
||||||
right 0
|
|
||||||
bottom 25px
|
|
||||||
margin 0 auto
|
|
||||||
color gray
|
|
||||||
text-align center
|
|
||||||
z-index 2
|
|
||||||
&>i
|
|
||||||
transition 0.3s
|
|
||||||
&.active
|
|
||||||
color white
|
|
||||||
.slide
|
|
||||||
absolute top bottom left right
|
|
||||||
z-index 1
|
|
||||||
.slide0
|
|
||||||
background-color slideBgColor0
|
|
||||||
.content
|
|
||||||
margin-top 100px
|
|
||||||
.slide1
|
|
||||||
background-color slideBgColor1
|
|
||||||
.content
|
|
||||||
.markdown
|
|
||||||
background-color white
|
|
||||||
color textColor
|
|
||||||
width 480px
|
|
||||||
height 140px
|
|
||||||
margin 45px auto 0
|
|
||||||
clearfix()
|
|
||||||
text-align left
|
|
||||||
border-radius 5px
|
|
||||||
overflow hidden
|
|
||||||
.left
|
|
||||||
float left
|
|
||||||
width 240px
|
|
||||||
height 140px
|
|
||||||
box-sizing border-box
|
|
||||||
font-size 0.5em
|
|
||||||
padding 30px
|
|
||||||
border-right 1px solid borderColor
|
|
||||||
.right
|
|
||||||
width 240px
|
|
||||||
height 140px
|
|
||||||
float right
|
|
||||||
box-sizing border-box
|
|
||||||
padding: 28px 0 0 10px
|
|
||||||
font-size 0.45em
|
|
||||||
marked()
|
|
||||||
ul
|
|
||||||
padding-left 20px
|
|
||||||
.slide2
|
|
||||||
background-color slideBgColor2
|
|
||||||
.code
|
|
||||||
border-radius 5px
|
|
||||||
overflow hidden
|
|
||||||
text-align left
|
|
||||||
width 480px
|
|
||||||
heght 140px
|
|
||||||
margin 45px auto 0
|
|
||||||
font-size 14px
|
|
||||||
.ace_editor
|
|
||||||
height 140px
|
|
||||||
.slide3
|
|
||||||
background-color slideBgColor3
|
|
||||||
.title
|
|
||||||
margin-bottom 15px
|
|
||||||
.content
|
|
||||||
font-size 18px
|
|
||||||
&>img
|
|
||||||
margin-top 25px
|
|
||||||
.slide4
|
|
||||||
background-color slideBgColor4
|
|
||||||
.content
|
|
||||||
&>button
|
|
||||||
background-color white
|
|
||||||
color brandColor
|
|
||||||
font-size 60px
|
|
||||||
width 250px
|
|
||||||
height 250px
|
|
||||||
border-radius 125px
|
|
||||||
border none
|
|
||||||
transition 0.1s
|
|
||||||
&:hover
|
|
||||||
transform scale(1.2)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
modalZIndex= 1000
|
|
||||||
modalBackColor = transparentify(black, 65%)
|
|
||||||
|
|
||||||
.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
|
|
||||||
background-color white
|
|
||||||
padding 15px
|
|
||||||
color #666666
|
|
||||||
border-radius 5px
|
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
transition: all 200ms ease;
|
transition: all 200ms ease;
|
||||||
}
|
}
|
||||||
.Select-control:hover {
|
.Select-control:hover {
|
||||||
// box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
|
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
|
||||||
}
|
}
|
||||||
.is-searchable.is-open > .Select-control {
|
.is-searchable.is-open > .Select-control {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
.is-focused:not(.is-open) > .Select-control {
|
.is-focused:not(.is-open) > .Select-control {
|
||||||
// border-color: #0088cc #0099e6 #0099e6;
|
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);
|
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 5px -1px rgba(0, 136, 204, 0.5);
|
||||||
}
|
}
|
||||||
.Select-placeholder {
|
.Select-placeholder {
|
||||||
color: #aaaaaa;
|
color: #aaaaaa;
|
||||||
@@ -141,8 +141,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.Select-menu-outer {
|
.Select-menu-outer {
|
||||||
border-bottom-right-radius: 5px;
|
border-bottom-right-radius: 4px;
|
||||||
border-bottom-left-radius: 5px;
|
border-bottom-left-radius: 4px;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border: 1px solid #cccccc;
|
border: 1px solid #cccccc;
|
||||||
border-top-color: #e6e6e6;
|
border-top-color: #e6e6e6;
|
||||||
@@ -168,8 +168,8 @@
|
|||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
}
|
}
|
||||||
.Select-option:last-child {
|
.Select-option:last-child {
|
||||||
border-bottom-right-radius: 5px;
|
border-bottom-right-radius: 4px;
|
||||||
border-bottom-left-radius: 5px;
|
border-bottom-left-radius: 4px;
|
||||||
}
|
}
|
||||||
.Select-option.is-focused {
|
.Select-option.is-focused {
|
||||||
background-color: #f2f9fc;
|
background-color: #f2f9fc;
|
||||||
@@ -196,10 +196,10 @@
|
|||||||
padding: 3px 0;
|
padding: 3px 0;
|
||||||
}
|
}
|
||||||
.Select-item {
|
.Select-item {
|
||||||
background-color: brandColor;
|
background-color: #f2f9fc;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
// border: 1px solid #c9e6f2;
|
border: 1px solid #c9e6f2;
|
||||||
color: white;
|
color: #0088cc;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
margin: 2px;
|
margin: 2px;
|
||||||
@@ -216,19 +216,20 @@
|
|||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
}
|
}
|
||||||
.Select-item-label .Select-item-label__a {
|
.Select-item-label .Select-item-label__a {
|
||||||
color: white;
|
color: #0088cc;
|
||||||
cursor: white;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.Select-item-icon {
|
.Select-item-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-bottom-left-radius: 2px;
|
border-bottom-left-radius: 2px;
|
||||||
border-top-left-radius: 2px;
|
border-top-left-radius: 2px;
|
||||||
border-right: 1px solid darken(brandColor, 10%)
|
border-right: 1px solid #c9e6f2;
|
||||||
padding: 2px 5px 4px;
|
padding: 2px 5px 4px;
|
||||||
}
|
}
|
||||||
.Select-item-icon:hover,
|
.Select-item-icon:hover,
|
||||||
.Select-item-icon:focus {
|
.Select-item-icon:focus {
|
||||||
background-color: lighten(brandColor, 10%)
|
background-color: #ddeff7;
|
||||||
|
color: #0077b3;
|
||||||
}
|
}
|
||||||
.Select-item-icon:active {
|
.Select-item-icon:active {
|
||||||
background-color: #c9e6f2;
|
background-color: #c9e6f2;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.LoginContainer, .SignupContainer
|
.LoginContainer, .SignupContainer
|
||||||
margin 0 auto
|
margin 0 auto
|
||||||
padding 105px 15px
|
padding 25px 15px
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
color inactiveTextColor
|
color inactiveTextColor
|
||||||
.logo
|
.logo
|
||||||
@@ -58,24 +58,17 @@
|
|||||||
.alertInfo, .alertError
|
.alertInfo, .alertError
|
||||||
margin-top 15px
|
margin-top 15px
|
||||||
margin-bottom 15px
|
margin-bottom 15px
|
||||||
padding 10px
|
height 44px
|
||||||
border-radius 5px
|
padding 5px
|
||||||
line-height 1.6
|
border-radius 10px
|
||||||
|
line-height 44px
|
||||||
text-align center
|
text-align center
|
||||||
.alertInfo
|
.alertInfo
|
||||||
alertInfo()
|
alertInfo()
|
||||||
.alertError
|
.alertError
|
||||||
alertError()
|
alertError()
|
||||||
div.formField
|
div.form-group:last-child
|
||||||
input
|
margin-top 15px
|
||||||
stripInput()
|
|
||||||
height 33px
|
|
||||||
width 100%
|
|
||||||
margin-bottom 10px
|
|
||||||
text-align center
|
|
||||||
font-size 1.1em
|
|
||||||
&:last-child
|
|
||||||
margin-top 15px
|
|
||||||
button.logInButton
|
button.logInButton
|
||||||
btnPrimary()
|
btnPrimary()
|
||||||
height 44px
|
height 44px
|
||||||
|
|||||||
@@ -1,123 +1,307 @@
|
|||||||
userNavigatorWidth = 200px
|
.HomeContainer
|
||||||
userNavigatorBgColor = #333
|
.HomeNavigator
|
||||||
userNavigatorColor = #DDD
|
|
||||||
userNavigatorProfileNameColor = brandColor
|
|
||||||
userNavigatorBorderColor = #666
|
|
||||||
|
|
||||||
userContentBgColor = #E6E6E6
|
|
||||||
|
|
||||||
.UserContainer
|
|
||||||
absolute top bottom right
|
|
||||||
left 60px
|
|
||||||
.content
|
|
||||||
absolute top bottom right
|
|
||||||
left userNavigatorWidth
|
|
||||||
background-color userContentBgColor
|
|
||||||
.UserNavigator
|
|
||||||
absolute left top bottom
|
|
||||||
width userNavigatorWidth
|
|
||||||
background-color userNavigatorBgColor
|
|
||||||
color userNavigatorColor
|
|
||||||
noSelect()
|
noSelect()
|
||||||
&>.profile
|
background-color planetNavBgColor
|
||||||
height 60px
|
absolute left top bottom
|
||||||
padding 10px 15px 0
|
width 55px
|
||||||
box-sizing border-box
|
text-align center
|
||||||
position relative
|
box-sizing border-box
|
||||||
border-bottom solid 1px userNavigatorBorderColor
|
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
|
cursor pointer
|
||||||
&>.profileName
|
box-sizing border-box
|
||||||
color userNavigatorProfileNameColor
|
border none
|
||||||
font-size 22px
|
img
|
||||||
cursor pointer
|
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
&>.name
|
opacity 0.9
|
||||||
padding 5px 10px
|
&.vivid.active, &.focus, &:focus, &.hover, &:hover
|
||||||
font-size 14px
|
img
|
||||||
color userNavigatorColor
|
opacity 1
|
||||||
cursor pointer
|
.profilePopup
|
||||||
transition 0.1s
|
position fixed
|
||||||
&>.dropdownIcon
|
left 35px
|
||||||
position absolute
|
top 35px
|
||||||
top 20px
|
z-index popupZIndex
|
||||||
right 25px
|
width 200px
|
||||||
float right
|
background-color backgroundColor
|
||||||
width 20px
|
box-shadow popupShadow
|
||||||
height 20px
|
border-radius 10px
|
||||||
line-height 20px
|
padding 10px 0 0px
|
||||||
font-size 8px
|
&.close
|
||||||
border solid 1px userNavigatorColor
|
display none
|
||||||
border-radius 12.5px
|
.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
|
text-align center
|
||||||
|
background-color planetAnchorBgColor
|
||||||
|
text-decoration none
|
||||||
|
color planetAnchorColor
|
||||||
|
line-height 44px
|
||||||
|
font-size 1.1em
|
||||||
|
cursor pointer
|
||||||
|
circle()
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
&:hover
|
&:hover, &:active
|
||||||
&>.profileName
|
background-color white
|
||||||
color lighten(brandColor, 10%)
|
.planetTooltip
|
||||||
&>.name
|
position absolute
|
||||||
color white
|
z-index popupZIndex
|
||||||
&>.dropdownIcon
|
background-color transparentify(invBackgroundColor, 80%)
|
||||||
border-color white
|
color invTextColor
|
||||||
&:active
|
padding 10px
|
||||||
&>.dropdownIcon
|
line-height 1em
|
||||||
background-color brandColor
|
border-radius 5px
|
||||||
border-color brandColor
|
margin-top -41px
|
||||||
&>.control
|
margin-left 52px
|
||||||
padding 15px 15px
|
white-space nowrap
|
||||||
&>.newPostButton
|
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
|
background-color brandColor
|
||||||
color white
|
color white
|
||||||
height 44px
|
.tooltip
|
||||||
width 100%
|
tooltip()
|
||||||
border none
|
margin-top -22px
|
||||||
border-radius 5px
|
margin-left 33px
|
||||||
font-size 16px
|
&:hover .tooltip
|
||||||
font-weight 600
|
opacity 1
|
||||||
transition 0.1s
|
.UserContainer
|
||||||
&:hover
|
absolute top bottom right
|
||||||
background-color lighten(brandColor, 10%)
|
left 55px
|
||||||
&>.menu
|
.memberPopup
|
||||||
absolute left right bottom
|
absolute left
|
||||||
top 134px
|
top 235px
|
||||||
padding 15px 0
|
z-index 1
|
||||||
overflow auto
|
padding 0 15px 10px
|
||||||
&>.menuGruop
|
width 200px
|
||||||
&>.label
|
.label
|
||||||
border-bottom 1px solid userNavigatorBorderColor
|
padding 10px 0
|
||||||
padding 10px 15px
|
font-size 0.9em
|
||||||
font-size 18px
|
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
|
margin-bottom 10px
|
||||||
&>.plusButton
|
.userName
|
||||||
float right
|
font-size 1.1em
|
||||||
width 20px
|
.editProfileButton
|
||||||
height 20px
|
float right
|
||||||
margin-top -2.5px
|
btnDefault()
|
||||||
margin-right -5px
|
margin-top 25px
|
||||||
line-height 15px
|
padding 10px 15px
|
||||||
font-size 8px
|
border-radius 5px
|
||||||
border solid 1px userNavigatorColor
|
.teamList, .memberList
|
||||||
border-radius 10px
|
absolute left bottom
|
||||||
background-color transparent
|
top 125px
|
||||||
text-align center
|
width 200px
|
||||||
color userNavigatorColor
|
padding 15px
|
||||||
&:hover
|
border-right solid 1px borderColor
|
||||||
border-color white
|
overflow-y auto
|
||||||
color white
|
.teamLabel, .memberLabel
|
||||||
&:active
|
font-size 1.2em
|
||||||
background-color brandColor
|
margin-bottom 15px
|
||||||
border-color brandColor
|
.teams
|
||||||
&>.folders
|
li
|
||||||
.folderButton
|
padding 0 10px
|
||||||
padding 10px 25px
|
margin-bottom 15px
|
||||||
width 100%
|
clearfix()
|
||||||
background-color transparent
|
.teamInfo
|
||||||
border none
|
float left
|
||||||
font-size 14px
|
.teamProfileName
|
||||||
color userNavigatorColor
|
margin-bottom 5px
|
||||||
transition 0.1s
|
font-size 1.05em
|
||||||
text-align left
|
.teamName
|
||||||
&:hover
|
margin-left 5px
|
||||||
background-color transparentify(white, 20%)
|
font-size 0.9em
|
||||||
color white
|
color inactiveTextColor
|
||||||
&.active
|
a:hover .teamProfileName, a:hover .teamName
|
||||||
background-color brandColor
|
text-decoration underline
|
||||||
color white
|
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()
|
||||||
|
|||||||
@@ -5,29 +5,35 @@ global-reset()
|
|||||||
@import '../shared/*'
|
@import '../shared/*'
|
||||||
@import './components/*'
|
@import './components/*'
|
||||||
@import './containers/*'
|
@import './containers/*'
|
||||||
@import './HomeContainer'
|
|
||||||
|
|
||||||
*
|
|
||||||
-webkit-app-region no-drag
|
|
||||||
-webkit-user-select none
|
|
||||||
|
|
||||||
html, body
|
html, body
|
||||||
width 100%
|
width 100%
|
||||||
height 100%
|
height 100%
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
body
|
body
|
||||||
font-family "Lato"
|
font-family "Lato"
|
||||||
color textColor
|
color textColor
|
||||||
font-size fontSize
|
font-size fontSize
|
||||||
font-weight 400
|
font-weight 400
|
||||||
|
button
|
||||||
button, input, select
|
|
||||||
font-family "Lato"
|
font-family "Lato"
|
||||||
|
|
||||||
div, span, a, button, input, textarea
|
div, span, a, button, input, textarea
|
||||||
box-sizing border-box
|
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
|
a
|
||||||
color brandColor
|
color brandColor
|
||||||
&:hover
|
&:hover
|
||||||
@@ -46,9 +52,6 @@ button
|
|||||||
&:focus, &.focus
|
&:focus, &.focus
|
||||||
outline none
|
outline none
|
||||||
|
|
||||||
.noSelect
|
|
||||||
noSelect()
|
|
||||||
|
|
||||||
.text-center
|
.text-center
|
||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
@@ -58,6 +61,13 @@ button
|
|||||||
display block
|
display block
|
||||||
margin-bottom 5px
|
margin-bottom 5px
|
||||||
|
|
||||||
|
.stripInput
|
||||||
|
stripInput()
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
font-size 1em
|
||||||
|
height 33px
|
||||||
|
|
||||||
.block-input, .inline-input
|
.block-input, .inline-input
|
||||||
border solid 1px borderColor
|
border solid 1px borderColor
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
@@ -97,32 +107,22 @@ textarea.block-input
|
|||||||
z-index 2000
|
z-index 2000
|
||||||
bottom 5px
|
bottom 5px
|
||||||
right 53px
|
right 53px
|
||||||
|
btnPrimary()
|
||||||
padding 10px 15px
|
padding 10px 15px
|
||||||
border none
|
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
background-color brandColor
|
background-color backgroundColor
|
||||||
color white
|
|
||||||
opacity 0.7
|
|
||||||
&:hover
|
|
||||||
opacity 1
|
|
||||||
background-color lighten(brandColor, 10%)
|
|
||||||
.contactButton
|
.contactButton
|
||||||
position fixed
|
position fixed
|
||||||
z-index 2000
|
z-index 2000
|
||||||
bottom 5px
|
bottom 5px
|
||||||
right 5px
|
right 5px
|
||||||
|
btnPrimary()
|
||||||
padding 10px 15px
|
padding 10px 15px
|
||||||
border none
|
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
background-color brandColor
|
background-color backgroundColor
|
||||||
color white
|
|
||||||
opacity 0.7
|
|
||||||
&:hover
|
|
||||||
opacity 1
|
|
||||||
background-color lighten(brandColor, 10%)
|
|
||||||
.tooltip
|
.tooltip
|
||||||
tooltip()
|
tooltip()
|
||||||
margin-top -22px
|
margin-top -22px
|
||||||
margin-left -107px
|
margin-left -97px
|
||||||
&:hover .tooltip
|
&:hover .tooltip
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ stripInput()
|
|||||||
border-bottom 1px solid borderColor
|
border-bottom 1px solid borderColor
|
||||||
padding 5px 15px
|
padding 5px 15px
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
font-size 14px
|
|
||||||
&:focus, &.focus
|
&:focus, &.focus
|
||||||
border-bottom 1px solid brandBorderColor
|
border-bottom 1px solid brandBorderColor
|
||||||
outline none
|
outline none
|
||||||
@@ -12,7 +11,6 @@ borderInput()
|
|||||||
border solid 1px borderColor
|
border solid 1px borderColor
|
||||||
padding 5px 15px
|
padding 5px 15px
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
font-size 14px
|
|
||||||
&:focus, &.focus
|
&:focus, &.focus
|
||||||
border-color brandBorderColor
|
border-color brandBorderColor
|
||||||
outline none
|
outline none
|
||||||
|
|||||||
@@ -1,36 +1,32 @@
|
|||||||
marked()
|
marked()
|
||||||
h1, h2, h3, h4, h5, h6, p
|
|
||||||
&:first-child
|
|
||||||
margin-top 0
|
|
||||||
hr
|
hr
|
||||||
border-top none
|
border-top none
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
margin 15px 0
|
margin 15px 0
|
||||||
h1
|
h1
|
||||||
font-size 2em
|
font-size 2em
|
||||||
border-bottom solid 2px borderColor
|
margin 0 auto 0.67em
|
||||||
margin 0.33em auto 0.67em
|
|
||||||
h2
|
h2
|
||||||
font-size 1.5em
|
font-size 1.5em
|
||||||
margin 0.42em auto 0.83em
|
margin 0 auto 0.83em
|
||||||
h3
|
h3
|
||||||
font-size 1.17em
|
font-size 1.17em
|
||||||
margin 0.5em auto 1em
|
margin 0 auto 1em
|
||||||
h4
|
h4
|
||||||
font-size 1em
|
font-size 1em
|
||||||
margin 0.67em auto 1.33em
|
margin 0 auto 1.33em
|
||||||
h5
|
h5
|
||||||
font-size 0.83em
|
font-size 0.83em
|
||||||
margin 0.84em auto 1.67em
|
margin 0 auto 1.67em
|
||||||
h6
|
h6
|
||||||
font-size 0.67em
|
font-size 0.67em
|
||||||
margin 1.16em auto 2.33em
|
margin 2.33em auto
|
||||||
h1, h2, h3, h4, h5, h6
|
h1, h2, h3, h4, h5, h6
|
||||||
font-weight 700
|
font-weight 400
|
||||||
line-height 1.8em
|
line-height 1.4em
|
||||||
p
|
p
|
||||||
line-height 1.8em
|
line-height 1.4em
|
||||||
margin 15px 0 25px
|
margin-bottom 15px
|
||||||
img
|
img
|
||||||
max-width 100%
|
max-width 100%
|
||||||
strong
|
strong
|
||||||
@@ -41,15 +37,14 @@ marked()
|
|||||||
text-decoration line-through
|
text-decoration line-through
|
||||||
blockquote
|
blockquote
|
||||||
border-left solid 4px brandBorderColor
|
border-left solid 4px brandBorderColor
|
||||||
margin 15px 0 25px
|
margin 15px 0 15px
|
||||||
padding 0 25px
|
padding 0 25px
|
||||||
ul
|
ul
|
||||||
list-style-type disc
|
list-style-type disc
|
||||||
padding-left 35px
|
padding-left 35px
|
||||||
margin-bottom 35px
|
|
||||||
li
|
li
|
||||||
display list-item
|
display list-item
|
||||||
line-height 1.8em
|
margin 15px 0
|
||||||
&>li>ul
|
&>li>ul
|
||||||
list-style-type circle
|
list-style-type circle
|
||||||
&>li>ul
|
&>li>ul
|
||||||
@@ -57,27 +52,23 @@ marked()
|
|||||||
ol
|
ol
|
||||||
list-style-type decimal
|
list-style-type decimal
|
||||||
padding-left 35px
|
padding-left 35px
|
||||||
margin-bottom 35px
|
|
||||||
li
|
li
|
||||||
display list-item
|
display list-item
|
||||||
line-height 1.8em
|
margin 15px 0
|
||||||
code
|
code
|
||||||
font-family Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace;
|
font-family monospace
|
||||||
padding 2px 4px
|
padding 2px 4px
|
||||||
border solid 1px borderColor
|
border solid 1px borderColor
|
||||||
border-radius 4px
|
border-radius 4px
|
||||||
font-size 0.9em
|
font-size 0.9em
|
||||||
color black
|
color black
|
||||||
text-decoration none
|
text-decoration none
|
||||||
background-color #F6F6F6
|
|
||||||
pre
|
pre
|
||||||
padding 5px
|
padding 5px
|
||||||
border solid 1px borderColor
|
border solid 1px borderColor
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
overflow-x auto
|
overflow-x auto
|
||||||
margin 15px 0 25px
|
margin-bottom 15px
|
||||||
background-color #F6F6F6
|
|
||||||
line-height 1.35em
|
|
||||||
&>code
|
&>code
|
||||||
padding 0
|
padding 0
|
||||||
border none
|
border none
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ tooltip()
|
|||||||
z-index popupZIndex
|
z-index popupZIndex
|
||||||
background-color transparentify(invBackgroundColor, 80%)
|
background-color transparentify(invBackgroundColor, 80%)
|
||||||
color invTextColor
|
color invTextColor
|
||||||
padding 6px 15px
|
padding 10px
|
||||||
font-size 12px
|
font-size 12px
|
||||||
font-weight normal
|
line-height 12px
|
||||||
line-height 20px
|
border-radius 5px
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
pointer-events none
|
pointer-events none
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user