1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 18:26:26 +00:00

Compare commits

...

98 Commits

Author SHA1 Message Date
Rokt33r
61cc44cc83 Merge branch 'dev'
* dev:
  0.4.0-beta.2
  tutorial 追加
  ModeSelect
  行動データ, contact form, default articleに英語文追加, Intro fix
  Hotkey settingの時Alertで結果を出す Folder nameが長すぎ雨時のlayout崩れ解決 ArticleNavigatorの余計なスペースをなくす Default articleの誤字直し
  Cmd+Sで保存 debug craetedAt
  fix Title overflow behaviour
  0.4.0-beta.1

Conflicts:
	package.json
2015-11-11 03:09:28 +09:00
Rokt33r
c20cbe7d66 0.4.0-beta.2 2015-11-11 03:01:07 +09:00
Rokt33r
2f4af3223b tutorial 追加 2015-11-11 00:59:14 +09:00
Rokt33r
e4b2c42897 ModeSelect 2015-11-10 04:26:11 +09:00
Rokt33r
746df9277c 行動データ, contact form, default articleに英語文追加, Intro fix 2015-11-09 15:07:17 +09:00
Rokt33r
8428588a4c Hotkey settingの時Alertで結果を出す
Folder nameが長すぎ雨時のlayout崩れ解決
ArticleNavigatorの余計なスペースをなくす
Default articleの誤字直し
2015-11-06 23:45:00 +09:00
Rokt33r
83a8f4b911 Cmd+Sで保存
debug craetedAt
2015-11-06 21:46:29 +09:00
Rokt33r
2736024cb7 fix Title overflow behaviour 2015-11-05 14:47:53 +09:00
Rokt33r
9a32ca893e 0.4.0-beta.1 2015-11-05 10:36:14 +09:00
Rokt33r
59d3c6c94f 0.4.0-beta.1 2015-11-05 10:33:14 +09:00
Rokt33r
388027b731 Merge branch 'dev'
* dev:
  beta - add error alert(folder editing) - debug clear button of search input
2015-11-05 09:50:39 +09:00
Rokt33r
8abdedc11d beta
- add error alert(folder editing)
- debug clear button of search input
2015-11-05 09:50:07 +09:00
Rokt33r
9758f5baa8 release version 0.4.0-alpha.7 2015-11-02 16:13:31 +09:00
Rokt33r
248262a597 Merge branch 'dev'
* dev: (43 commits)
  webpack bugfix, tooltip modified, preview button string changed(toggle Preview -> Preview / Edit)
  tooltip, tutorial追加
  ArticleListに自動scroll機能追加
  Tooltip追加、キー反応改善、Pinch2Zoom使用禁止、Webpack config debug
  編集状態でのMarkdown preview追加
  Key入力の動き改善 - Searchに内容がある時にEscを押すと内容をSearchの内容を削除する - Cmd + Fを押すとSearch inputがfocusされる
  Key入力追加
  prepare alpha.5 (remain work: MD preview, keybind)
  Going LIte
  add Team destroy
  add text:ellipsis to some labels & add member deletion confirm
  bump alpha.3
  add folder create
  add Member setting, Team setting, FolderSetting(80%) bump react-select
  bumpup alpha.2
  addTag search
  add Invalid token handler
  bumpup react (0.13.3->0.14.0), reacthmr work perfectly
  fix updater bug
  Folder indexing
  ...
2015-11-02 16:12:08 +09:00
Rokt33r
cc0f2c7c7f webpack bugfix, tooltip modified, preview button string changed(toggle Preview -> Preview / Edit) 2015-11-01 23:15:22 +09:00
Rokt33r
72f6468d12 tooltip, tutorial追加 2015-11-01 21:59:59 +09:00
Rokt33r
522c0edd90 ArticleListに自動scroll機能追加 2015-11-01 02:37:22 +09:00
Rokt33r
d338f217fe Tooltip追加、キー反応改善、Pinch2Zoom使用禁止、Webpack config debug 2015-11-01 02:24:17 +09:00
Rokt33r
ca79857386 編集状態でのMarkdown preview追加 2015-10-31 19:06:14 +09:00
Rokt33r
60e551e273 Key入力の動き改善
- Searchに内容がある時にEscを押すと内容をSearchの内容を削除する
- Cmd + Fを押すとSearch inputがfocusされる
2015-10-31 18:29:45 +09:00
Rokt33r
954e148be3 Key入力追加 2015-10-31 18:21:42 +09:00
Rokt33r
3d0b79f674 prepare alpha.5 (remain work: MD preview, keybind) 2015-10-31 13:05:22 +09:00
Rokt33r
d9442aa23c Going LIte 2015-10-30 14:53:09 +09:00
Rokt33r
ba0daf4452 add Team destroy 2015-10-26 13:56:43 +09:00
Rokt33r
8d9cd5bbd1 add text:ellipsis to some labels & add member deletion confirm 2015-10-26 13:01:04 +09:00
Rokt33r
186b877c09 bump alpha.3 2015-10-24 21:37:08 +09:00
Rokt33r
5ed2dfccd1 add folder create 2015-10-24 21:32:53 +09:00
Rokt33r
911cfd8642 add Member setting, Team setting, FolderSetting(80%)
bump react-select
2015-10-24 20:52:10 +09:00
Rokt33r
3539bd1e79 bumpup alpha.2 2015-10-22 08:34:09 +09:00
Rokt33r
f56df7c16d addTag search 2015-10-22 08:30:39 +09:00
Rokt33r
c507dfa6c4 add Invalid token handler 2015-10-21 09:25:42 +09:00
Rokt33r
f6d2e898dc bumpup react (0.13.3->0.14.0), reacthmr work perfectly 2015-10-21 02:47:21 +09:00
Rokt33r
326c7a93fb fix updater bug 2015-10-20 01:51:51 +09:00
Rokt33r
58381b8062 Folder indexing 2015-10-20 00:55:18 +09:00
Rokt33r
0899cea4b4 fix build env(redux devtools removed) 2015-10-19 22:10:22 +09:00
Rokt33r
7459e937b5 fix build env 2015-10-19 11:46:58 +09:00
Rokt33r
55db0bebbb exclude finder 2015-10-18 20:55:42 +09:00
Rokt33r
0bdb8142c6 USER auto refresh 2015-10-18 20:18:16 +09:00
Rokt33r
88ee94d4b6 add Preferences modal(30%done) & move some modules (actions, reducer, socket, store -> lib/~) 2015-10-18 17:17:25 +09:00
Rokt33r
1df4ed0fe9 sort by time & add FolderMark 2015-10-17 16:46:10 +09:00
Rokt33r
2a339a2935 article CRUD with socket 2015-10-16 22:12:49 +09:00
Rokt33r
a1810e6023 new folder modal / key indexing for article / login bugfix 2015-10-16 13:32:58 +09:00
Rokt33r
832ca3347c CRUD done 2015-10-15 10:46:22 +09:00
Rokt33r
9d2b64e82b CREATE_MODE(1/2) 2015-10-14 15:20:16 +09:00
Rokt33r
9a5e4b3f54 restructure DONE 2015-10-13 18:07:33 +09:00
Rokt33r
e5e8032ba1 revive articledetail 2015-10-13 16:09:37 +09:00
Rokt33r
5356e68b51 set app status 2015-10-13 02:49:59 +09:00
Rokt33r
cd94c625a7 set style to articlenavigator 2015-10-13 01:28:16 +09:00
Rokt33r
972a3746a1 on making usernavigator 2015-10-12 22:29:24 +09:00
Rokt33r
a9e12e4384 usernavigator done 2015-10-12 15:52:54 +09:00
Rokt33r
1690e6420f set design create team modal 2015-10-11 18:11:08 +09:00
Rokt33r
acdf61f7ab add redirect home -> user(current user), enhance UserNavigation style 2015-10-09 20:45:06 +09:00
Rokt33r
2e4fc557ea use webpack & add some styles 2015-10-09 20:12:01 +09:00
Rokt33r
979dcead49 before applying redux 2015-10-08 20:40:19 +09:00
Rokt33r
116ddf345d remove all submodules 2015-09-23 01:25:42 +09:00
Rokt33r
366805a64f remove submodule 2015-09-22 20:59:30 +09:00
Rokt33r
1fee2a846a - cleanup root directory
- improve window behaviour
2015-09-11 17:20:16 +09:00
Rokt33r
3f54eb52b2 Merge branch 'dev'
* dev:
  リアルタイム(SocketIO)実装 / Markdown style改善

Conflicts:
	package.json
2015-09-08 15:30:02 +09:00
Rokt33r
a3847ce1c9 リアルタイム(SocketIO)実装 / Markdown style改善 2015-09-08 15:28:36 +09:00
Rokt33r
1e7415b692 fix version 0.2.11 2015-09-06 19:48:44 +09:00
Rokt33r
ff950ef28a Merge branch 'dev'
* dev:
  improve markdown style
2015-09-06 12:19:20 +09:00
Rokt33r
51bd12c6cf improve markdown style 2015-09-06 12:18:54 +09:00
Rokt33r
5fa37dbffb Merge branch 'dev'
* dev:
  version 0.2.10 - Hotkeyの設定機能 - Stylus refactor
2015-09-02 01:02:32 +09:00
Rokt33r
c6307e4ad3 version 0.2.10
- Hotkeyの設定機能
- Stylus refactor
2015-09-02 01:02:04 +09:00
Rokt33r
06a54d451c Merge branch 'dev'
* dev:
  0.2.9 API server changed, bump electron version 0.31.0
2015-08-31 01:34:15 +09:00
Rokt33r
e317075815 0.2.9 API server changed, bump electron version 0.31.0 2015-08-31 01:33:32 +09:00
Rokt33r
45541a255b Merge branch 'dev'
* dev:
  - StylusでコンパイルされたCSSをCachingする(ロディングが短くなる) - Planet name changeのときにエラーハンドリング追加 + Bug fix - TeamのMemberを編集する場合、自分を編集することはできない - FinderにMarkdownのリンクがちゃんと外部に飛ぶように - Tray iconがちゃんと表示 - ArticleDetailのCodeアイコンがちゃんと表示されない
2015-08-30 05:31:13 +09:00
Rokt33r
3ab423d695 - StylusでコンパイルされたCSSをCachingする(ロディングが短くなる)
- Planet name changeのときにエラーハンドリング追加 + Bug fix
- TeamのMemberを編集する場合、自分を編集することはできない
- FinderにMarkdownのリンクがちゃんと外部に飛ぶように
- Tray iconがちゃんと表示
- ArticleDetailのCodeアイコンがちゃんと表示されない
2015-08-30 05:30:54 +09:00
Rokt33r
345d7b427a Merge branch 'dev'
* dev:
  Loading font 微調整
2015-08-26 18:27:42 +09:00
Rokt33r
de6d6b692e Loading font 微調整 2015-08-26 18:27:19 +09:00
Rokt33r
b2845e2284 Merge branch 'dev'
* dev:
  version 0.2.7  - Planet, Team作成の時Error message表示  - MarkdownのCode blockの背景を薄い灰色にする  - 権限なしのPlanetには  - SignUpに規約/Privacyの外部リンク追加  - Loading画面追加  - Font 添付(Lato regular)  - UserContainerでのTeam Label変更
2015-08-26 18:23:04 +09:00
Rokt33r
47383c347c version 0.2.7
- Planet, Team作成の時Error message表示
 - MarkdownのCode blockの背景を薄い灰色にする
 - 権限なしのPlanetには
 - SignUpに規約/Privacyの外部リンク追加
 - Loading画面追加
 - Font 添付(Lato regular)
 - UserContainerでのTeam Label変更
2015-08-26 18:22:46 +09:00
Rokt33r
4bda84d69c cleanup old files 2015-08-24 17:42:43 +09:00
Rokt33r
b510aa11f5 Merge branch 'dev'
* dev:
  fix bugs - ArticleList text overflow behaviour in Finder, PlanetContainer - No DevTools
2015-08-24 17:42:00 +09:00
Rokt33r
8dab6d5e04 fix bugs
- ArticleList text overflow behaviour in Finder, PlanetContainer
- No DevTools
2015-08-24 17:41:38 +09:00
Rokt33r
85f833c865 Merge branch 'dev'
* dev:
  v0.2.5 - bugfix - Alert message added(Private planet/Team member)

Conflicts:
	package.json
2015-08-24 13:46:32 +09:00
Rokt33r
15133d00c7 v0.2.5
- bugfix
- Alert message added(Private planet/Team member)
2015-08-24 13:45:28 +09:00
Rokt33r
b93990d10b bump version 2015-08-24 06:23:41 +09:00
Rokt33r
a0bcb8edbe Merge branch 'dev'
* dev:
  実装 - MemberがOwnerではないときにTeam設定ボターンを全部隠す
  v0.2.4 - Minor fix
  fix minor bug
2015-08-24 06:22:32 +09:00
Rokt33r
bfdf691bed 実装 - MemberがOwnerではないときにTeam設定ボターンを全部隠す 2015-08-24 06:22:16 +09:00
Rokt33r
f60856b998 v0.2.4 - Minor fix 2015-08-24 06:14:13 +09:00
Rokt33r
3308eeaf82 fix minor bug 2015-08-23 04:00:18 +09:00
Rokt33r
19930a2472 fix minor bug 2015-08-23 03:49:19 +09:00
Rokt33r
e75d95b1fc Merge branch 'dev'
* dev:
  実装 - PlanetArticleListへのAuto scroll追加
  実装 - tooltip (Refresh, Planet setting, Planet refresh, Edit article, Delete article, Contact)
  #14 改善適用 - アプリが立ち上がったら最初はmy planetが表示されるようにしたい - Noteがスクロールできない - Note行間がなくて読みづらい
  実装 - Hotkey
2015-08-23 03:42:40 +09:00
Rokt33r
4319711dc6 実装 - PlanetArticleListへのAuto scroll追加 2015-08-23 03:42:00 +09:00
Rokt33r
9712be909d 実装 - tooltip (Refresh, Planet setting, Planet refresh, Edit article, Delete article, Contact) 2015-08-23 03:05:30 +09:00
Rokt33r
04060ce252 #14 改善適用
- アプリが立ち上がったら最初はmy planetが表示されるようにしたい
- Noteがスクロールできない
- Note行間がなくて読みづらい
2015-08-23 00:52:03 +09:00
Rokt33r
da066fe694 実装 - Hotkey 2015-08-22 23:57:37 +09:00
Rokt33r
3b7215b36a Merge branch 'dev'
* dev:
  改善 - Finder descriptionのTextoverflow対策, 最初起動の時のFinderの異常振る舞い
2015-08-22 03:46:51 +09:00
Rokt33r
b88d5cfb06 改善 - Finder descriptionのTextoverflow対策, 最初起動の時のFinderの異常振る舞い 2015-08-22 03:46:30 +09:00
Rokt33r
8ab96cf2fb fix version 2015-08-22 03:36:04 +09:00
Rokt33r
63af2fd8b1 fix submodule setting 2015-08-22 03:33:32 +09:00
Rokt33r
fcf26f7844 Merge branch 'dev'
* dev:
  改善 - PlanetArticleList, PlanetArticleDetail
  #14 改善点4つ適用  - Team member 管理画面でのスペルミス  - Member profile imageを丸にする  - Profile popup 改善  - 文字サイズ大きく(UserContainerのみ)
2015-08-22 03:28:56 +09:00
Rokt33r
657ebc99df 改善 - PlanetArticleList, PlanetArticleDetail 2015-08-22 03:27:42 +09:00
Rokt33r
9500f9a8c9 #14 改善点4つ適用
- Team member 管理画面でのスペルミス
 - Member profile imageを丸にする
 - Profile popup 改善
 - 文字サイズ大きく(UserContainerのみ)
2015-08-22 00:27:05 +09:00
Rokt33r
a05bba15e7 Merge branch 'dev'
* dev: (79 commits)
  version 0.2.1
  修正 - Member list, Team listのデザイン修正
  実装 - Team pageとPlanet pageにMember List表示
  実装 - PlanetHeaderにPrivate鍵表示
  Noteから外部Linkを開くときにBrowserを使う
  LogoutModal実装
  Contact Modal追加
  on Refactor... #4
  on Refactor... #3
  on Refactor... #2
  on Refactoring...
  test 0.2.0
  Fix: Personal Settingボターンを右にする。写真からまっすぐProfile pageに入れるようにする
  Add: Refresh button
  Fix: Design changed
  Fix: Disable pinch to zoom
  Fix: Preview button修正
  Fix: PlanetNavigatorのHome削除 & SnippetsとBlueprintsはToggleができるように
  Fix: minor features 設定ボタンアイコンの変更 削除Modalでcmd+enterの使用 検索バーデザイン
  Add: Log in / Sign upの時にエラーが出たらAlertを表示する Debug: Tray Icon, PopUpWindow, Menuがいつの間にか消える
  ...

Conflicts:
	src/browser/main/controllers/AppController.js
	src/browser/main/controllers/directives/SideNavController.js
	src/browser/main/controllers/states/AuthRegisterController.js
	src/browser/main/index.html
	src/browser/main/services/Modal.js
	src/browser/main/styles/app.css
	src/browser/main/styles/app.styl
	src/browser/main/styles/directives/side-nav.styl
	src/browser/main/tpls/directives/side-nav.tpl.html
	src/browser/main/tpls/states/home.tpl.html
2015-08-21 04:58:01 +09:00
Rokt33r
3b907627f7 fix links 2015-07-03 18:35:10 +09:00
Rokt33r
2284fd41b9 add agreement 2015-07-03 16:41:42 +09:00
159 changed files with 8647 additions and 5458 deletions

20
.babelrc Normal file
View File

@@ -0,0 +1,20 @@
{
"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"]
}]
}
}
}
}
}

View File

@@ -1,3 +0,0 @@
{
"directory": "browser/vendor/"
}

10
.gitignore vendored
View File

@@ -1,6 +1,6 @@
build/
node_modules/
electron_build/
.env .env
dist/ node_modules/*
vendor/ !node_modules/boost
Boost-darwin-x64/
backup/
compiled

5
.gitmodules vendored
View File

@@ -1,3 +1,4 @@
[submodule "browser/ace"] [submodule "submodules/ace"]
path = browser/ace path = submodules/ace
url = https://github.com/ajaxorg/ace-builds.git url = https://github.com/ajaxorg/ace-builds.git
branch = master

29
atom-lib/finder-window.js Normal file
View File

@@ -0,0 +1,29 @@
var BrowserWindow = require('browser-window')
var 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

24
atom-lib/main-window.js Normal file
View File

@@ -0,0 +1,24 @@
var BrowserWindow = require('browser-window')
var path = require('path')
var mainWindow = new BrowserWindow({
width: 1080,
height: 720,
'zoom-factor': 1.0,
'web-preferences': {
'overlay-scrollbars': true
},
'standard-window': false
})
var 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

View File

@@ -5,7 +5,7 @@ module.exports = [
label: 'Electron', label: 'Electron',
submenu: [ submenu: [
{ {
label: 'About Electron', label: 'About Boost',
selector: 'orderFrontStandardAboutPanel:' selector: 'orderFrontStandardAboutPanel:'
}, },
{ {
@@ -19,7 +19,7 @@ module.exports = [
type: 'separator' type: 'separator'
}, },
{ {
label: 'Hide Electron', label: 'Hide Boost',
accelerator: 'Command+H', accelerator: 'Command+H',
selector: 'hide:' selector: 'hide:'
}, },

37
atom-lib/updater.js Normal file
View File

@@ -0,0 +1,37 @@
var autoUpdater = require('auto-updater')
var nn = require('node-notifier')
var app = require('app')
var path = require('path')
var version = app.getVersion()
var versionText = (version == null || version.length === 0) ? 'DEV version' : 'v' + version
autoUpdater
.on('error', function (err, message) {
console.error(err)
console.error(message)
nn.notify({
title: 'Error! ' + versionText,
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
message: message
})
})
// .on('checking-for-update', function () {
// // Connecting
// })
.on('update-available', function () {
nn.notify({
title: 'Update is available!! ' + versionText,
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
message: 'Download started.. wait for the update ready.'
})
})
.on('update-not-available', function () {
nn.notify({
title: 'Latest Build!! ' + versionText,
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
message: 'Hope you to enjoy our app :D'
})
})
module.exports = autoUpdater

View File

@@ -1,11 +0,0 @@
{
"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"
}
}

Submodule browser/ace deleted from 0982db4853

View File

@@ -1,43 +0,0 @@
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>
)
}
})

View File

@@ -1,15 +0,0 @@
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>
)
}
})

View File

@@ -1,79 +0,0 @@
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>
)
}
})

View File

@@ -0,0 +1,34 @@
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'>
<ModeIcon mode={activeArticle.mode}/> {activeArticle.title}</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()
}

View File

@@ -0,0 +1,16 @@
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
}

View File

@@ -0,0 +1,71 @@
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
}

33
browser/finder/actions.js Normal file
View File

@@ -0,0 +1,33 @@
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 () {
console.log('refreshing data')
let data = JSON.parse(localStorage.getItem('local'))
if (data == null) return null
let { folders, articles } = data
return {
type: REFRESH_DATA,
data: {
articles,
folders
}
}
}

View File

@@ -1,62 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>CodeXen Popup</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<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>

View File

@@ -0,0 +1,40 @@
<!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" charset="utf-8">
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.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>
require('web-frame').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>

186
browser/finder/index.js Normal file
View File

@@ -0,0 +1,186 @@
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 { selectArticle, searchArticle, refreshData } from './actions'
import _ from 'lodash'
import activityRecord from 'boost/activityRecord'
import remote from 'remote'
var hideFinder = remote.getGlobal('hideFinder')
import clipboard from 'clipboard'
require('../styles/finder/index.styl')
const FOLDER_FILTER = 'FOLDER_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) {
let { activeArticle } = this.props
clipboard.writeText(activeArticle.content)
activityRecord.emit('FINDER_COPY')
hideFinder()
e.preventDefault()
}
if (e.keyCode === 27) {
hideFinder()
e.preventDefault()
}
}
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
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}/>
</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
}
function remap (state) {
let { articles, folders, status } = state
let filters = status.search.split(' ').map(key => key.trim()).filter(key => key.length > 0 && !key.match(/^#$/)).map(key => {
if (key.match(/^in:.+$/)) {
return {type: FOLDER_FILTER, value: key.match(/^in:(.+)$/)[1]}
}
if (key.match(/^#(.+)/)) {
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
}
return {type: TEXT_FILTER, value: key}
})
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)
if (folders != null) {
let targetFolders = folders.filter(folder => {
return _.findWhere(folderFilters, {value: folder.name})
})
status.targetFolders = targetFolders
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]
return {
articles,
activeArticle,
status
}
}
var Finder = connect(remap)(FinderMain)
var store = createStore(reducer)
window.onfocus = e => {
store.dispatch(refreshData())
activityRecord.emit('FINDER_OPEN')
}
ReactDOM.render((
<Provider store={store}>
<Finder/>
</Provider>
), document.getElementById('content'))

View File

@@ -1,134 +0,0 @@
/* 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)
},
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'))

49
browser/finder/reducer.js Normal file
View File

@@ -0,0 +1,49 @@
import { combineReducers } from 'redux'
import { SELECT_ARTICLE, SEARCH_ARTICLE, REFRESH_DATA } from './actions'
let data = JSON.parse(localStorage.getItem('local'))
let initialArticles = data != null ? data.articles : []
let initialFolders = data != null ? data.folders : []
let initialStatus = {
articleKey: null,
search: ''
}
function status (state = initialStatus, action) {
switch (action.type) {
case SELECT_ARTICLE:
state.articleKey = action.data.key
return state
case SEARCH_ARTICLE:
state.search = action.data.input
return 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
})

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<a href="/main">Go Main</a>
<a href="/main">Go Popup</a>
</body>
</html>

View File

@@ -1,32 +0,0 @@
var remote = require('remote')
var version = remote.getGlobal('version')
var React = require('react/addons')
var ExternalLink = require('../Mixins/ExternalLink')
module.exports = React.createClass({
mixins: [ExternalLink],
propTypes: {
close: React.PropTypes.func
},
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>
)
}
})

View File

@@ -1,83 +0,0 @@
var React = require('react/addons')
var Select = require('react-select')
var LinkedState = require('../Mixins/LinkedState')
var Hq = require('../Services/Hq')
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],
propTypes: {
team: React.PropTypes.object,
close: React.PropTypes.func
},
getInitialState: function () {
return {
userName: '',
role: 'member'
}
},
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>
)
}
})

View File

@@ -1,48 +0,0 @@
var React = require('react')
var Hq = require('../Services/Hq')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
propTypes: {
planet: React.PropTypes.object,
code: React.PropTypes.object,
close: React.PropTypes.func
},
componentDidMount: function () {
React.findDOMNode(this).focus()
},
stopPropagation: function (e) {
e.stopPropagation()
},
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>
)
}
})

View File

@@ -1,20 +0,0 @@
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
},
render: function () {
return (
<div className='CodeEditModal modal'>
<div className='modal-header'>
<h1>Edit Code</h1>
</div>
<CodeForm code={this.props.code} planet={this.props.planet} close={this.props.close}/>
</div>
)
}
})

View File

@@ -1,151 +0,0 @@
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 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],
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
}
},
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>
)
}
})

View File

@@ -1,52 +0,0 @@
var React = require('react/addons')
var ace = window.ace
module.exports = React.createClass({
propTypes: {
code: React.PropTypes.string,
mode: 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'></div>
)
}
})

View File

@@ -1,162 +0,0 @@
/* global localStorage */
var React = require('react/addons')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var UserStore = require('../Stores/UserStore')
module.exports = React.createClass({
mixins: [LinkedState],
propTypes: {
user: React.PropTypes.shape({
name: React.PropTypes.string,
profileName: React.PropTypes.string,
email: React.PropTypes.string
})
},
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
}
},
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>
)
}
})

View File

@@ -1,188 +0,0 @@
/* 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})
},
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 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>
<div className='shortCut'>{index + 1}</div>
</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 className='planetList'>
{planets}
</ul>
<button onClick={this.openPlanetCreateModal} className='newPlanet'>
<i className='fa fa-plus'/>
<div className='newPlanetTooltip'>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>
<div className='userSetting'><i className='fa fa-gear'/></div>
</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>
<div className='userSetting'><i className='fa fa-gear'/></div>
</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>
)
}
})

View File

@@ -1,60 +0,0 @@
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 () {
},
stopPropagation: function (e) {
e.stopPropagation()
},
selectCodeTab: function () {
this.setState({currentTab: 'code'})
},
selectNoteTab: function () {
this.setState({currentTab: 'note'})
},
handleKeyDown: function (e) {
if (e.keyCode === 37 && e.metaKey) {
this.selectCodeTab()
}
if (e.keyCode === 39 && e.metaKey) {
this.selectNoteTab()
}
},
render: function () {
var modalBody
if (this.state.currentTab === 'code') {
modalBody = (
<CodeForm planet={this.props.planet} transitionTo={this.props.transitionTo} close={this.props.close}/>
)
} else {
modalBody = (
<NoteForm 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 className={this.state.currentTab === 'code' ? 'btn-primary active' : 'btn-default'} onClick={this.selectCodeTab}>Code</button><button className={this.state.currentTab === 'note' ? 'btn-primary active' : 'btn-default'} onClick={this.selectNoteTab}>Note</button>
</div>
</div>
{modalBody}
</div>
)
}
})

View File

@@ -1,27 +0,0 @@
/* global localStorage */
var React = require('react')
module.exports = React.createClass({
propTypes: {
transitionTo: React.PropTypes.func,
close: React.PropTypes.func
},
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>
)
}
})

View File

@@ -1,43 +0,0 @@
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)}}/>
)
}
})

View File

@@ -1,45 +0,0 @@
var React = require('react')
var Hq = require('../Services/Hq')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
propTypes: {
planet: React.PropTypes.object,
note: React.PropTypes.object,
close: React.PropTypes.func
},
componentDidMount: function () {
React.findDOMNode(this).focus()
},
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>
)
}
})

View File

@@ -1,21 +0,0 @@
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
},
render: function () {
return (
<div className='NoteEditModal modal'>
<div className='modal-header'>
<h1>Edit Note</h1>
</div>
<NoteForm note={this.props.note} planet={this.props.planet} close={this.props.close}/>
</div>
)
}
})

View File

@@ -1,146 +0,0 @@
var React = require('react/addons')
var ReactRouter = require('react-router')
var Select = require('react-select')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var Markdown = require('../Mixins/Markdown')
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],
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
}
},
componentDidMount: function () {
React.findDOMNode(this.refs.title).focus()
},
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>
)
}
})

View File

@@ -1,99 +0,0 @@
var React = require('react/addons')
var moment = require('moment')
var CodeViewer = require('../Components/CodeViewer')
var CodeEditModal = require('../Components/CodeEditModal')
var CodeDeleteModal = require('../Components/CodeDeleteModal')
var NoteEditModal = require('../Components/NoteEditModal')
var NoteDeleteModal = require('../Components/NoteDeleteModal')
var MarkdownPreview = require('../Components/MarkdownPreview')
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 () {
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 () {
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='viewer-header'>
<i className='fa fa-code fa-fw'></i> {article.callSign} <small className='updatedAt'>{moment(article.updatedAt).fromNow()}</small>
<span className='control-group'>
<button onClick={this.openEditModal} className='btn-default btn-square btn-sm'><i className='fa fa-edit fa-fw'></i></button>
<button onClick={this.openDeleteModal} className='btn-default btn-square btn-sm'><i className='fa fa-trash fa-fw'></i></button>
</span>
</div>
<div className='viewer-body'>
<div className='viewer-detail'>
<div className='description'>{article.description}</div>
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
</div>
<div className='content'>
<CodeViewer code={article.content} mode={article.mode}/>
</div>
</div>
</div>
)
}
return (
<div className='PlanetArticleDetail noteDetail'>
<div className='viewer-header'>
<i className='fa fa-file-text-o fa-fw'></i> {article.title} <small className='updatedAt'>{moment(article.updatedAt).fromNow()}</small>
<span className='control-group'>
<button onClick={this.openEditModal} className='btn-default btn-square btn-sm'><i className='fa fa-edit fa-fw'></i></button>
<button onClick={this.openDeleteModal} className='btn-default btn-square btn-sm'><i className='fa fa-trash fa-fw'></i></button>
</span>
</div>
<div className='viewer-body'>
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
<MarkdownPreview className='content' content={article.content}/>
</div>
</div>
)
}
})

View File

@@ -1,94 +0,0 @@
var React = require('react/addons')
var ReactRouter = require('react-router')
var moment = require('moment')
var ForceUpdate = require('../Mixins/ForceUpdate')
var Markdown = require('../Mixins/Markdown')
var ProfileImage = require('../Components/ProfileImage')
module.exports = React.createClass({
mixins: [ReactRouter.Navigation, ReactRouter.State, ForceUpdate(60000), Markdown],
propTypes: {
articles: React.PropTypes.array,
showOnlyWithTag: React.PropTypes.func
},
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
var handleClick
if (article.type === 'code') {
handleClick = function () {
this.transitionTo('codes', {
userName: params.userName,
planetName: params.planetName,
localId: article.localId
})
}.bind(this)
return (
<li onClick={handleClick} 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='updatedAt'>{moment(article.updatedAt).fromNow()}</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>
)
}
handleClick = function () {
this.transitionTo('notes', {
userName: params.userName,
planetName: params.planetName,
localId: article.localId
})
}.bind(this)
return (
<li onClick={handleClick} 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='updatedAt'>{moment(article.updatedAt).fromNow()}</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>
{articles}
</ul>
</div>
)
}
})

View File

@@ -1,84 +0,0 @@
/* global localStorage */
var React = require('react/addons')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
mixins: [LinkedState],
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()
},
onListen: function (res) {
if (res.status === 'planetCreated') {
this.props.close()
}
},
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>
)
}
})

View File

@@ -1,66 +0,0 @@
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 () {
React.findDOMNode(this.refs.search).focus()
},
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='privateTooltip'>Private planet</div>
</div>
)}
<button onClick={this.openPlanetSettingModal} className='menuBtn'>
<i className='fa fa-chevron-down'></i>
</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'/></button>
<a onClick={this.openExternal} href='http://b00st.io' className='logo'>
<img width='44' height='44' src='resources/favicon-230x230.png'/>
</a>
</div>
</div>
)
}
})

View File

@@ -1,58 +0,0 @@
var React = require('react/addons')
var ReactRouter = require('react-router')
var Navigation = ReactRouter.Navigation
var Modal = require('../Mixins/Modal')
var LaunchModal = require('../Components/LaunchModal')
var PlanetNavigator = 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
}
},
submitLaunchModal: function (ret) {
this.setState({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>
)
}
})
module.exports = PlanetNavigator

View File

@@ -1,154 +0,0 @@
var React = require('react/addons')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
mixins: [LinkedState],
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: ''
}
},
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>
)
}
})

View File

@@ -1,15 +0,0 @@
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}/>
)
}
})

View File

@@ -1,57 +0,0 @@
/* global localStorage */
var React = require('react/addons')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var UserStore = require('../Stores/UserStore')
module.exports = React.createClass({
mixins: [LinkedState],
propTypes: {
user: React.PropTypes.shape({
name: React.PropTypes.string
}),
transitionTo: React.PropTypes.func,
close: React.PropTypes.func
},
getInitialState: function () {
return {
team: {
name: ''
}
}
},
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 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>
)
}
})

View File

@@ -1,273 +0,0 @@
/* 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 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],
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
})
},
getInitialState: function () {
var team = this.props.team
return {
currentTab: 'teamInfo',
team: {
profileName: team.profileName
},
userSubmitStatus: null,
member: {
name: '',
role: 'member'
},
updatingMember: false
}
},
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 numbr of members is 5 on Beta version. Please contact us if you want futher use.
</div>
)}
</div>
)
}
})

View File

@@ -1,29 +0,0 @@
/* global localStorage */
var React = require('react/addons')
var ReactRouter = require('react-router')
var RouteHandler = ReactRouter.RouteHandler
var State = ReactRouter.State
var Navigation = ReactRouter.Navigation
var AuthFilter = require('../Mixins/AuthFilter')
var HomeNavigator = require('../Components/HomeNavigator')
module.exports = React.createClass({
mixins: [AuthFilter.OnlyUser, State, Navigation],
componentDidMount: function () {
if (this.isActive('homeEmpty')) {
var user = JSON.parse(localStorage.getItem('currentUser'))
this.transitionTo('userHome', {userName: user.name})
}
},
render: function () {
return (
<div className='HomeContainer'>
<HomeNavigator/>
<RouteHandler/>
</div>
)
}
})

View File

@@ -1,106 +0,0 @@
/* global localStorage */
var React = require('react/addons')
var ReactRouter = require('react-router')
var Link = ReactRouter.Link
var AuthFilter = require('../Mixins/AuthFilter')
var LinkedState = require('../Mixins/LinkedState')
var Hq = require('../Services/Hq')
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>
)
}
})

View File

@@ -1,104 +0,0 @@
/* global localStorage */
var ipc = require('ipc')
var React = require('react/addons')
var ReactRouter = require('react-router')
var RouteHandler = ReactRouter.RouteHandler
var Navigation = ReactRouter.Navigation
var State = ReactRouter.State
var Hq = require('../Services/Hq')
var 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'/></button>
<RouteHandler/>
</div>
)
}
})

View File

@@ -1,373 +0,0 @@
/* 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 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],
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)
})
}
},
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
}
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).querySelector('.PlanetHeader .searchInput input').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)
},
focus: function () {
React.findDOMNode(this).focus()
},
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
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>
)
}
})

View File

@@ -1,136 +0,0 @@
/* global localStorage */
var React = require('react/addons')
var ReactRouter = require('react-router')
var Link = ReactRouter.Link
var AuthFilter = require('../Mixins/AuthFilter')
var LinkedState = require('../Mixins/LinkedState')
var Hq = require('../Services/Hq')
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>
)
}
})

View File

@@ -1,366 +0,0 @@
/* global localStorage */
var React = require('react/addons')
var ReactRouter = require('react-router')
var Navigation = ReactRouter.Navigation
var State = ReactRouter.State
var RouteHandler = ReactRouter.RouteHandler
var Link = ReactRouter.Link
var Reflux = require('reflux')
var LinkedState = require('../Mixins/LinkedState')
var Modal = require('../Mixins/Modal')
var Helper = require('../Mixins/Helper')
var Hq = require('../Services/Hq')
var ProfileImage = require('../Components/ProfileImage')
var EditProfileModal = require('../Components/EditProfileModal')
var TeamSettingsModal = require('../Components/TeamSettingsModal')
var PlanetCreateModal = require('../Components/PlanetCreateModal')
var AddMemberModal = require('../Components/AddMemberModal')
var TeamCreateModal = require('../Components/TeamCreateModal')
var UserStore = require('../Stores/UserStore')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
mixins: [LinkedState, State, Navigation, Modal, Reflux.listenTo(UserStore, 'onUserChange'), Reflux.listenTo(PlanetStore, 'onPlanetChange'), Helper],
propTypes: {
params: React.PropTypes.shape({
userName: React.PropTypes.string,
planetName: React.PropTypes.string
})
},
getInitialState: function () {
return {
user: null
}
},
componentDidMount: function () {
this.fetchUser()
},
componentWillReceiveProps: function (nextProps) {
if (this.state.user == null) {
this.fetchUser(nextProps.params.userName)
return
}
if (nextProps.params.userName !== this.state.user.name) {
this.setState({
user: null
}, function () {
this.fetchUser(nextProps.params.userName)
})
}
},
onUserChange: function (res) {
if (this.state.user == null) return
var member
switch (res.status) {
case 'userUpdated':
if (this.state.user.id === res.data.id) {
this.setState({user: res.data})
}
break
case 'memberAdded':
member = res.data
if (this.state.user.userType === 'team' && member.TeamMember.TeamId === this.state.user.id) {
this.state.user.Members = this.updateItemToTargetArray(member, this.state.user.Members)
this.setState({user: this.state.user})
}
break
case 'memberRemoved':
member = res.data
if (this.state.user.userType === 'team' && member.TeamMember.TeamId === this.state.user.id) {
this.state.user.Members = this.deleteItemFromTargetArray(member, this.state.user.Members)
this.setState({user: this.state.user})
}
break
}
},
onPlanetChange: function (res) {
if (this.state.user == null) return
var currentUser, planet, isOwner, team
switch (res.status) {
case 'updated':
// if state.user is currentUser, planet will be fetched by UserStore
currentUser = JSON.parse(localStorage.getItem('currentUser'))
if (currentUser.id === this.state.user.id) return
planet = res.data
isOwner = planet.Owner.id === this.state.user.id
if (isOwner) {
this.state.user.Planets = this.updateItemToTargetArray(planet, this.state.user.Planets)
this.setState({user: this.state.user})
return
}
// check if team of user has this planet
team = null
this.state.user.userType !== 'team' && this.state.user.Teams.some(function (_team) {
if (planet.Owner.id === _team.id) {
team = _team
return true
}
return false
})
if (team != null) {
team.Planets = this.updateItemToTargetArray(planet, team.Planets)
this.setState({user: this.state.user})
return
}
break
case 'destroyed':
// if state.user is currentUser, planet will be fetched by UserStore
currentUser = JSON.parse(localStorage.getItem('currentUser'))
if (currentUser.id === this.state.user.id) return
planet = res.data
isOwner = planet.Owner.id === this.state.user.id
if (isOwner) {
this.state.user.Planets = this.deleteItemFromTargetArray(planet, this.state.user.Planets)
this.setState({user: this.state.user})
return
}
// check if team of user has this planet
team = null
this.state.user.userType !== 'team' && this.state.user.Teams.some(function (_team) {
if (planet.Owner.id === _team.id) {
team = _team
return true
}
return false
})
if (team != null) {
team.Planets = this.deleteItemFromTargetArray(planet, team.Planets)
this.setState({user: this.state.user})
return
}
break
}
},
fetchUser: function (userName) {
if (userName == null) userName = this.props.params.userName
Hq.fetchUser(userName)
.then(function (res) {
this.setState({user: res.body})
}.bind(this))
.catch(function (err) {
console.error(err)
})
},
openEditProfileModal: function () {
this.openModal(EditProfileModal, {user: this.state.user})
},
openTeamSettingsModal: function () {
this.openModal(TeamSettingsModal, {team: this.state.user})
},
openAddUserModal: function () {
this.openModal(AddMemberModal, {team: this.state.user})
},
openTeamCreateModal: function () {
this.openModal(TeamCreateModal, {user: this.state.user})
},
openPlanetCreateModalWithOwnerName: function (name) {
return function () {
this.openModal(PlanetCreateModal, {ownerName: name})
}.bind(this)
},
render: function () {
var user = this.state.user
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
if (this.isActive('userHome')) {
if (user == null) {
return (
<div className='UserContainer'>
User Loading...
</div>
)
} else if (user.userType === 'team') {
return this.renderTeamHome(currentUser)
} else {
return this.renderUserHome(currentUser)
}
} else if (this.isActive('planet') && user != null && user.userType === 'team') {
console.log(user.Members)
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 = true
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>
&nbsp;{!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>
<button onClick={this.openTeamSettingsModal} className='editProfileButton'>Team settings</button>
</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>
&nbsp;{!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>
&nbsp;{!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>
)
}
})

260
browser/main/HomePage.js Normal file
View File

@@ -0,0 +1,260 @@
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 keygen from 'boost/keygen'
import { isModalOpen, closeModal } from 'boost/modal'
const TEXT_FILTER = 'TEXT_FILTER'
const FOLDER_FILTER = 'FOLDER_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 (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, activeArticle, folders, filters } = this.props
return (
<div className='HomePage'>
<ArticleNavigator
ref='nav'
dispatch={dispatch}
folders={folders}
status={status}
/>
<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}
filters={filters}
/>
</div>
)
}
}
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)
})
// Filter articles
let filters = status.search.split(' ').map(key => key.trim()).filter(key => key.length > 0 && !key.match(/^#$/)).map(key => {
if (key.match(/^in:.+$/)) {
return {type: FOLDER_FILTER, value: key.match(/^in:(.+)$/)[1]}
}
if (key.match(/^#(.+)/)) {
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
}
return {type: TEXT_FILTER, value: key}
})
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)
if (folders != null) {
let targetFolders = folders.filter(folder => {
return _.findWhere(folderFilters, {value: folder.name})
})
status.targetFolders = targetFolders
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]
// remove Unsaved new article if user is not CREATE_MODE
if (status.mode !== CREATE_MODE) {
let targetIndex = _.findIndex(articles, article => article.status === NEW)
if (targetIndex >= 0) articles.splice(targetIndex, 1)
}
// switching CREATE_MODE
// restrict
// 1. team have one folder at least
// or Change IDLE MODE
if (status.mode === CREATE_MODE) {
let newArticle = _.findWhere(articles, {status: 'NEW'})
let FolderKey = folders[0].key
if (folderFilters.length > 0) {
let targetFolder = _.findWhere(folders, {name: folderFilters[0].value})
if (targetFolder != null) FolderKey = targetFolder.key
}
if (newArticle == null) {
newArticle = {
id: null,
key: keygen(),
title: '',
content: '',
mode: 'markdown',
tags: [],
FolderKey: FolderKey,
status: NEW
}
articles.unshift(newArticle)
}
activeArticle = newArticle
} else if (status.mode === CREATE_MODE) {
status.mode = IDLE_MODE
}
return {
folders,
status,
articles,
activeArticle,
filters: {
folder: folderFilters,
tag: tagFilters,
text: textFilters
}
}
}
HomePage.propTypes = {
params: PropTypes.shape({
userId: PropTypes.string
}),
status: PropTypes.shape({
userId: PropTypes.string
}),
articles: 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)

View File

@@ -0,0 +1,400 @@
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, CREATE_MODE, EDIT_MODE, switchMode, switchArticle, switchFolder, clearSearch, 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
}
}
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 !== IDLE_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
})
}
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>&nbsp;
Created : {moment(activeArticle.createdAt).format('YYYY/MM/DD')}&nbsp;
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, filters } = this.props
let article = this.state.article
let newArticle = Object.assign({}, article)
let folder = _.findWhere(folders, {key: article.FolderKey})
if (folder == null) return false
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 (filters.folder.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})
}
handleTagsChange (newTag, tags) {
let article = this.state.article
article.tags = tags
this.setState({article: article})
}
handleModeChange (value) {
let article = this.state.article
article.mode = value
this.setState({
article: article,
previewMode: false
})
}
handleModeSelectBlur () {
if (this.refs.code != null) {
this.refs.code.editor.focus()
}
}
handleContentChange (e, value) {
let article = this.state.article
article.content = value
this.setState({article: article})
}
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 } = 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>
<TagSelect
tags={this.state.article.tags}
onChange={(tags, tag) => this.handleTagsChange(tags, tag)}
/>
{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' valueLink={this.linkState('article.title')}/>
</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 CREATE_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()
}
ArticleDetail.prototype.linkState = linkState

View File

@@ -0,0 +1,118 @@
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
}

View File

@@ -0,0 +1,158 @@
import React, { PropTypes } from 'react'
import { findWhere } from 'lodash'
import { setSearchFilter, switchFolder, switchMode, CREATE_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 remote from '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 } = this.props
dispatch(switchMode(CREATE_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 } = this.props
let { targetFolders } = status
if (targetFolders == null) targetFolders = []
let folderElememts = folders.map((folder, index) => {
let isActive = findWhere(targetFolders, {key: folder.key})
return (
<button onClick={e => this.handleFolderButtonClick(folder.name)(e)} key={'folder-' + folder.key} className={isActive ? 'active' : ''}>
<FolderMark color={folder.color}/> {folder.name}
</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,
status: PropTypes.shape({
folderId: PropTypes.number
}),
dispatch: PropTypes.func
}

View File

@@ -0,0 +1,162 @@
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 : in:{folder_name}\n'}</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 : in:{'{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
})
}

View File

@@ -0,0 +1,52 @@
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
}

View File

93
browser/main/LoginPage.js Normal file
View File

@@ -0,0 +1,93 @@
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
})
}

44
browser/main/MainPage.js Normal file
View File

@@ -0,0 +1,44 @@
import ipc from 'ipc'
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
}

View File

@@ -1,67 +0,0 @@
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
}

View File

@@ -1,27 +0,0 @@
/* 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

View File

@@ -1,8 +0,0 @@
var shell = require('shell')
module.exports = {
openExternal: function (e) {
shell.openExternal(e.currentTarget.href)
e.preventDefault()
}
}

View File

@@ -1,14 +0,0 @@
var ForceUpdate = function (interval) {
return {
componentDidMount: function () {
this.refreshTimer = setInterval(function () {
this.forceUpdate()
}.bind(this), interval)
},
componentWillUnmount: function () {
clearInterval(this.refreshTimer)
}
}
}
module.exports = ForceUpdate

View File

@@ -1,30 +0,0 @@
function deleteItemFromTargetArray (item, 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) {
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
}

View File

@@ -1,13 +0,0 @@
var markdownit = require('markdown-it')
var md = markdownit({
typographer: true,
linkify: true
})
var Markdown = {
markdown: function (content) {
return md.render(content)
}
}
module.exports = Markdown

View File

@@ -1,42 +0,0 @@
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})
}
}

View File

@@ -1,166 +0,0 @@
/* 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)
},
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)
},
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)
}
}

104
browser/main/SignupPage.js Normal file
View File

@@ -0,0 +1,104 @@
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
})
}

View File

@@ -1,131 +0,0 @@
/* global localStorage */
var Reflux = require('reflux')
var request = require('superagent')
var apiUrl = require('../../../config').apiUrl
var AuthStore = Reflux.createStore({
init: function () {
},
// Reflux Store
login: function (input) {
request
.post(apiUrl + 'auth/login')
.send(input)
.set('Accept', 'application/json')
.end(function (err, res) {
if (err) {
console.error(err)
this.trigger({
status: 'failedToLogIn',
data: res
})
return
}
var user = res.body.user
localStorage.setItem('token', res.body.token)
localStorage.setItem('user', JSON.stringify(res.body.user))
this.trigger({
status: 'loggedIn',
data: user
})
}.bind(this))
},
register: function (input) {
request
.post(apiUrl + 'auth/signup')
.send(input)
.set('Accept', 'application/json')
.end(function (err, res) {
if (err) {
console.error(res)
this.trigger({
status: 'failedToRegister',
data: res
})
return
}
var user = res.body.user
localStorage.setItem('token', res.body.token)
localStorage.setItem('user', JSON.stringify(res.body.user))
this.trigger({
status: 'registered',
data: user
})
}.bind(this))
},
refreshUser: function () {
request
.get(apiUrl + 'auth/user')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.end(function (err, res) {
if (err) {
console.error(err)
if (res.status === 401 || res.status === 403) {
AuthActions.logout()
}
return
}
var user = res.body
localStorage.setItem('user', JSON.stringify(user))
this.trigger({
status: 'userRefreshed',
data: user
})
}.bind(this))
},
logout: function () {
localStorage.removeItem('token')
localStorage.removeItem('currentUser')
this.trigger({
status: 'loggedOut'
})
},
updateProfile: function (input) {
request
.put(apiUrl + 'auth/user')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
.end(function (err, res) {
if (err) {
console.error(err)
this.trigger({
status: 'userProfileUpdatingFailed',
data: err
})
return
}
var user = res.body
localStorage.setItem('user', JSON.stringify(user))
this.trigger({
status: 'userProfileUpdated',
data: user
})
}.bind(this))
},
// Methods
check: function () {
if (localStorage.getItem('token')) return true
return false
},
getUser: function () {
var userJSON = localStorage.getItem('currentUser')
if (userJSON == null) return null
return JSON.parse(userJSON)
}
})
module.exports = AuthStore

View File

@@ -1,159 +0,0 @@
/* global localStorage */
var Reflux = require('reflux')
var UserStore = require('./UserStore')
var Helper = require('../Mixins/Helper')
var actions = Reflux.createActions([
'update',
'destroy',
'updateCode',
'destroyCode',
'updateNote',
'destroyNote'
])
module.exports = Reflux.createStore({
mixins: [Helper],
listenables: [actions],
Actions: actions,
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
})
}
})

View File

@@ -1,69 +0,0 @@
/* 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
})

View File

@@ -1,59 +0,0 @@
<!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>

View File

@@ -1,56 +1,68 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>CodeXen</title> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<link rel="stylesheet" href="../vendor/fontawesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8"> <link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
<link rel="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');
}
var to = Object(target); <style>
for (var i = 1; i < arguments.length; i++) { @font-face {
var nextSource = arguments[i]; font-family: 'Lato';
if (nextSource === undefined || nextSource === null) { src: url('../../resources/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
continue; 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;
} }
nextSource = Object(nextSource); #loadingCover{
position: absolute;
var keysArray = Object.keys(Object(nextSource)); top: 0;
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { bottom: 0;
var nextKey = keysArray[nextIndex]; left: 0;
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); right: 0;
if (desc !== undefined && desc.enumerable) { box-sizing: border-box;
to[nextKey] = nextSource[nextKey]; 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;
} }
return to; </style>
}
});
}
</script>
<script src="../vendor/moment/min/moment.min.js"></script>
<script src="../vendor/markdown-it/dist/markdown-it.min.js"></script>
<script src="../vendor/react/react-with-addons.js"></script>
<script src="../vendor/react-router/build/umd/ReactRouter.js"></script>
<script src="../vendor/reflux/dist/reflux.js"></script>
<script src="../ace/src-min/ace.js"></script>
</head> </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 type="text/javascript" src="http://localhost:8090/assets/main.js"></script> <script src="../../submodules/ace/src-min/ace.js"></script>
<script type="text/javascript" src="http://localhost:8090/assets/main-style.js"></script> <script type='text/javascript'>
require('web-frame').setZoomLevelLimits(1, 1)
var version = require('remote').require('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>

40
browser/main/index.js Normal file
View File

@@ -0,0 +1,40 @@
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'
activityRecord.init()
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))
}
})

View File

@@ -1,40 +0,0 @@
var React = require('react/addons')
var ReactRouter = require('react-router')
var Route = ReactRouter.Route
var DefaultRoute = ReactRouter.DefaultRoute
var MainContainer = require('./Containers/MainContainer')
var LoginContainer = require('./Containers/LoginContainer')
var SignupContainer = require('./Containers/SignupContainer')
var HomeContainer = require('./Containers/HomeContainer')
var UserContainer = require('./Containers/UserContainer')
var PlanetContainer = require('./Containers/PlanetContainer')
var routes = (
<Route path='/' handler={MainContainer}>
<DefaultRoute name='root'/>
<Route name='login' path='login' handler={LoginContainer}/>
<Route name='signup' path='signup' handler={SignupContainer}/>
<Route name='home' path='home' handler={HomeContainer}>
<DefaultRoute name='homeEmpty'/>
<Route name='user' path=':userName' handler={UserContainer}>
<DefaultRoute name='userHome'/>
<Route name='planet' path=':planetName' handler={PlanetContainer}>
<DefaultRoute name='planetHome'/>
<Route name='codes' path='codes/:localId'/>
<Route name='notes' path='notes/:localId'/>
</Route>
</Route>
</Route>
</Route>
)
ReactRouter.run(routes, ReactRouter.HashLocation, function (Root) {
React.render(<Root/>, document.getElementById('content'))
})

View File

@@ -1,2 +0,0 @@
require('../styles/main/index.styl')
require('react-select/dist/default.css')

View File

@@ -4,22 +4,27 @@
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
position absolute padding 11px
top 11px
left 11px
right 11px
margin 0 auto margin 0 auto
height 44px height 55px
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%
@@ -29,9 +34,9 @@ body
height 33px height 33px
border-radius 5px border-radius 5px
box-sizing border-box box-sizing border-box
border-radius 16.5px border-radius 5px
&:focus, &.focus &:focus, &.focus
border-color brandBorderColor border-color iptFocusBorderColor
outline none outline none
.FinderList .FinderList
absolute left bottom absolute left bottom
@@ -40,12 +45,16 @@ 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
@@ -57,22 +66,29 @@ 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 44px height 55px
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 44px line-height 55px
font-size 1.3em font-size 18px
white-space nowrap
text-overflow ellipsis
overflow-x hidden
.content .content
.ace_editor, .marked
position absolute position absolute
top 49px top 55px
left 5px padding 10px
right 5px bottom 0
bottom 5px left 0
right 0
box-sizing border-box box-sizing border-box
.marked
marked()
overflow-y auto overflow-y auto
.MarkdownPreview
marked()
.CodeEditor
absolute top bottom left right

View File

@@ -0,0 +1,310 @@
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
white-space nowrap
overflow-x auto
position relative
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
.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

View File

@@ -0,0 +1,74 @@
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

View File

@@ -0,0 +1,166 @@
articleNavBgColor = #353535
.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 brandColor
.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

View File

@@ -0,0 +1,156 @@
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

View File

@@ -0,0 +1,86 @@
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

View File

@@ -0,0 +1,11 @@
@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'

View File

@@ -0,0 +1,70 @@
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 100px auto 25px
&:focus
border-color iptFocusBorderColor
.alert
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
.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

View File

@@ -0,0 +1,199 @@
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

View File

@@ -0,0 +1,570 @@
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
.folderName
float left
width 175px
overflow ellipsis
padding-left 15px
.folderPublic
float left
text-align center
width 100px
.folderControl
float right
width 145px
text-align center
&.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
.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
.folderPublic select
height 33px
border 1px solid borderColor
background-color white
outline none
display block
margin 0 auto
font-size 14px
&:focus
border-color iptFocusBorderColor
.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%)

View File

@@ -0,0 +1,130 @@
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
.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)

View File

@@ -0,0 +1,21 @@
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

View File

@@ -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: 4px; border-bottom-right-radius: 5px;
border-bottom-left-radius: 4px; border-bottom-left-radius: 5px;
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: 4px; border-bottom-right-radius: 5px;
border-bottom-left-radius: 4px; border-bottom-left-radius: 5px;
} }
.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: #f2f9fc; background-color: brandColor;
border-radius: 2px; border-radius: 2px;
border: 1px solid #c9e6f2; // border: 1px solid #c9e6f2;
color: #0088cc; color: white;
display: inline-block; display: inline-block;
font-size: 1em; font-size: 1em;
margin: 2px; margin: 2px;
@@ -216,20 +216,19 @@
padding: 3px 5px; padding: 3px 5px;
} }
.Select-item-label .Select-item-label__a { .Select-item-label .Select-item-label__a {
color: #0088cc; color: white;
cursor: pointer; cursor: white;
} }
.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 #c9e6f2; border-right: 1px solid darken(brandColor, 10%)
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: #ddeff7; background-color: lighten(brandColor, 10%)
color: #0077b3;
} }
.Select-item-icon:active { .Select-item-icon:active {
background-color: #c9e6f2; background-color: #c9e6f2;

View File

@@ -1,6 +1,6 @@
.LoginContainer, .SignupContainer .LoginContainer, .SignupContainer
margin 0 auto margin 0 auto
padding 25px 15px padding 105px 15px
box-sizing border-box box-sizing border-box
color inactiveTextColor color inactiveTextColor
.logo .logo
@@ -58,16 +58,23 @@
.alertInfo, .alertError .alertInfo, .alertError
margin-top 15px margin-top 15px
margin-bottom 15px margin-bottom 15px
height 44px padding 10px
padding 5px border-radius 5px
border-radius 10px line-height 1.6
line-height 44px
text-align center text-align center
.alertInfo .alertInfo
alertInfo() alertInfo()
.alertError .alertError
alertError() alertError()
div.form-group:last-child div.formField
input
stripInput()
height 33px
width 100%
margin-bottom 10px
text-align center
font-size 1.1em
&:last-child
margin-top 15px margin-top 15px
button.logInButton button.logInButton
btnPrimary() btnPrimary()

View File

@@ -10,7 +10,7 @@ articleListWidth= 275px
margin 0 2px margin 0 2px
text-decoration underline text-decoration underline
cursor pointer cursor pointer
font-size 0.9em font-size 0.95em
&.noTag &.noTag
color inactiveTextColor color inactiveTextColor
font-size 0.8em font-size 0.8em
@@ -64,40 +64,35 @@ articleListWidth= 275px
color inactiveColor color inactiveColor
&:hover &:hover
color textColor color textColor
.privateTooltip .tooltip
position fixed tooltip()
z-index popupZIndex
background-color transparentify(invBackgroundColor, 80%)
color invTextColor
padding 10px
font-size 0.9em
line-height 0.9em
border-radius 5px
white-space nowrap
opacity 0
transition 0.1s
pointer-events none
margin-left -30px margin-left -30px
&:hover .privateTooltip &:hover .tooltip
opacity 1 opacity 1
.menuBtn .planetSettingButton
position absolute position absolute
top 12px top 15px
right 5px right 5px
font-size 1em font-size 0.8em
btnDefault() btnDefault()
box-sizing border-box box-sizing border-box
circle() circle()
width 33px width 26px
height 33px height 26px
text-align center text-align center
cursor pointer cursor pointer
transition 0.1s transition 0.1s
transform scale(0.8)
&:focus, &.focus &:focus, &.focus
outline none outline none
.tooltip
tooltip()
margin-top 11px
margin-left -36px
&:hover .tooltip
opacity 1
.headerControl .headerControl
noSelect() noSelect()
absolute top bottom right absolute top bottom right
@@ -118,10 +113,11 @@ articleListWidth= 275px
.refreshButton .refreshButton
display block display block
position absolute position absolute
top 12px top 15px
right 55px right 55px
width 28px width 26px
height 28px height 26px
font-size 0.8em
btnDefault() btnDefault()
circle() circle()
text-align center text-align center
@@ -129,16 +125,28 @@ articleListWidth= 275px
transition 0.1s transition 0.1s
&:focus, &.focus &:focus, &.focus
outline none outline none
.tooltip
tooltip()
margin-top 11px
margin-left -39px
&:hover .tooltip
opacity 1
.logo .logo
display block display block
position absolute position absolute
top 4px top 4px
right 10px right 10px
cursor pointer cursor pointer
img
transition 0.1s transition 0.1s
opacity 0.9 opacity 0.9
&:hover, &.hover &:hover img, &:hover .tooltip
opacity 1 opacity 1
.tooltip
tooltip()
margin-top -5px
margin-left -67px
.PlanetNavigator .PlanetNavigator
absolute bottom left absolute bottom left
@@ -178,33 +186,51 @@ articleListWidth= 275px
overflow-y auto overflow-y auto
li li
.articleItem .articleItem
user-select none noSelect()
border solid 2px transparent border solid 2px transparent
padding 10px position relative
height 94px
width 100%
cursor pointer cursor pointer
transition 0.1s transition 0.1s
clearfix()
.itemLeft .itemLeft
float left position absolute
width 25px top 4px
bottom 4px
width 38px
padding 3px 0 3px 3px
text-align center text-align center
.profileImage .profileImage
margin-bottom 5px margin-bottom 5px
circle()
.fa .fa
line-height 25px line-height 25px
.itemRight .itemRight
float left position absolute
width 225px top 4px
bottom 4px
right 2px
left 40px
overflow-x hidden overflow-x hidden
padding-left 10px padding 3px 10px 3px 3px
.updatedAt .itemInfo
margin-bottom 10px margin 5px 0 13px
color lighten(textColor, 25%) color lighten(textColor, 25%)
font-size 0.7em font-size 0.7em
.userProfileName
color brandColor
font-size 1.2em
.description .description
line-height 120% line-height 120%
margin-bottom 15px margin-bottom 10px
font-size 1em font-size 1em
overflow-x hidden
white-space nowrap
text-overflow ellipsis
.tags
position absolute
bottom 5px
font-size 0.9em
&:hover, &.hover &:hover, &.hover
background-color hoverBackgroundColor background-color hoverBackgroundColor
&:active, &.active &:active, &.active
@@ -218,57 +244,89 @@ articleListWidth= 275px
absolute right bottom absolute right bottom
top 55px top 55px
left navigationWidth + articleListWidth left navigationWidth + articleListWidth
&>.viewer-header .detailHeader
height 44px border solid 2px transparent
line-height 44px position relative
padding 0 15px height 105px
box-sizing border-box width 100%
font-size 1.2em transition 0.1s
small .itemLeft
font-size 0.8em position absolute
top 7px
bottom 4px
width 38px
padding 3px 0 3px 3px
text-align center
.profileImage
margin-bottom 5px
circle()
.fa
line-height 25px
.itemRight
position absolute
top 7px
bottom 4px
right 2px
left 40px
overflow-x hidden
padding 3px 10px 3px 3px
.itemInfo
margin 5px 0 13px
color lighten(textColor, 25%) color lighten(textColor, 25%)
.control-group font-size 0.7em
float right .userProfileName
button color brandColor
margin 10px 3px font-size 1.2em
.viewer-body
absolute bottom right
left 1px
top 44px
&.codeDetail>.viewer-body
.viewer-detail
border-bottom solid 1px borderColor
height 150px
box-sizing border-box
padding 10px
.description .description
height 100px line-height 120%
line-height 1.4em margin-bottom 10px
overflow-y auto font-size 1em
overflow-x auto
white-space nowrap
.tags .tags
position absolute position absolute
left 15px
right 15px
top 120px
.content
.ace_editor
absolute left right
top 155px
bottom 5px bottom 5px
&.noteDetail>.viewer-body font-size 0.9em
.tags .itemControl
absolute top position absolute
left 15px z-index 1
right 15px top 2px
height 24px right 2px
line-height 24px .deleteButton, .editButton
.content btnDefault()
text-align center
width 33px
height 33px
border-radius 16.5px
font-size 15px
margin 0 3px
.tooltip
tooltip()
margin-top 10px
&:hover .tooltip
opacity 1
.editButton .tooltip
margin-left -12px
.deleteButton .tooltip
margin-left -26px
.detailBody
absolute left right bottom absolute left right bottom
top 30px top 105px
.content
position absolute
top 5px
bottom 5px
left 2px
right 2px
box-sizing border-box box-sizing border-box
padding 5px padding 5px
border-top solid 1px borderColor border-top solid 1px borderColor
padding 10px &.noteDetail
.detailBody .content
overflow-x hidden overflow-x hidden
overflow-y auto overflow-y auto
marked() marked()
&.codeDetail
.detailBody .content
.ace_editor
absolute left right top bottom

View File

@@ -1,319 +1,123 @@
.HomeContainer userNavigatorWidth = 200px
.HomeNavigator userNavigatorBgColor = #333
background-color planetNavBgColor userNavigatorColor = #DDD
absolute left top bottom userNavigatorProfileNameColor = brandColor
width 55px userNavigatorBorderColor = #666
text-align center
box-sizing border-box userContentBgColor = #E6E6E6
border-right solid 1px borderColor
.profileButton
display block
width 55px
height 55px
border-bottom solid 1px borderColor
overflow hidden
background-color black
margin 0
padding 0
cursor pointer
box-sizing border-box
border none
img
transition 0.1s
opacity 0.9
&.vivid.active, &.focus, &:focus, &.hover, &:hover
img
opacity 1
.profilePopup
position fixed
left 35px
top 35px
z-index popupZIndex
width 200px
background-color backgroundColor
box-shadow popupShadow
border-radius 10px
padding 10px 0 0px
&.close
display none
.profileGroup
margin-bottom 10px
.profileGroupLabel
text-align left
height 1em
padding 0 15px
span
position absolute
z-index 2
background-color backgroundColor
padding-right 5px
color inactiveTextColor
font-size 0.8em
&::before
content ''
position absolute
display block
z-index 1
height 0.5em
width 175px
border-bottom solid 1px borderColor
.profileGroupList
li
clearfix()
&:hover
background-color hoverBackgroundColor
.userName
float left
width 155px
padding 10px 15px
text-align left
display block
text-decoration none
cursor pointer
.userSetting
float right
display block
padding 10px 0
width 45px
cursor pointer
.createNewTeam
btnStripDefault()
width 100%
padding 10px 20px
font-size 1em
cursor pointer
text-align left
.controlGroup
list-style none
border-top solid 1px borderColor
padding 10px 0
li
&:hover
background-color hoverBackgroundColor
button
btnStripDefault()
width 100%
padding 10px 20px
font-size 1em
cursor pointer
text-align left
ul.planetList>li
margin 15px 0
.shortCut
margin-top 5px
color lighten(textColor, 5%)
font-size 0.8em
&.active
a
background-color planetAnchorActiveBgColor
color planetAnchorActiveColor
a
display block
width 44px
height 44px
margin 0 auto
text-align center
background-color planetAnchorBgColor
text-decoration none
color planetAnchorColor
line-height 44px
font-size 1.1em
cursor pointer
circle()
transition 0.1s
&:hover, &:active
background-color white
.planetTooltip
position absolute
z-index popupZIndex
background-color transparentify(invBackgroundColor, 80%)
color invTextColor
padding 10px
line-height 1em
border-radius 5px
margin-top -41px
margin-left 52px
white-space nowrap
opacity 0
transition 0.1s
pointer-events none
&:hover .planetTooltip
opacity 1
img
circle()
width 55px
height 55px
button.newPlanet
display block
margin 0 auto
width 30px
height 30px
circle()
border solid 1px lightButtonColor
color lightButtonColor
text-align center
font-size 1
background-image none
background-color transparent
box-sizing border-box
absolute left bottom right
bottom 15px
&:hover, &.hover, &:focus, &.focus
border-color darken(lightButtonColor, 50%)
color darken(lightButtonColor, 50%)
&:active, &.active
border-color darken(brandBorderColor, 10%)
background-color brandColor
color white
.newPlanetTooltip
position fixed
z-index 500
background-color transparentify(invBackgroundColor, 80%)
color invTextColor
padding 10px
line-height 1em
border-radius 5px
margin-top -23px
margin-left 33px
white-space nowrap
font-size 1.1em
opacity 0
transition 0.1s
pointer-events none
&:hover .newPlanetTooltip
opacity 1
.UserContainer .UserContainer
absolute top bottom right absolute top bottom right
left 55px left 60px
.memberPopup .content
absolute left absolute top bottom right
top 235px left userNavigatorWidth
z-index popupZIndex background-color userContentBgColor
padding 0 15px 10px .UserNavigator
width 200px absolute left top bottom
.label width userNavigatorWidth
padding 10px 0 background-color userNavigatorBgColor
font-size 0.9em color userNavigatorColor
border-bottom solid 1px borderColor noSelect()
margin-bottom 15px &>.profile
.members height 60px
li padding 10px 15px 0
padding 0 10px box-sizing border-box
margin-bottom 15px position relative
clearfix() border-bottom solid 1px userNavigatorBorderColor
.memberImage cursor pointer
float left &>.profileName
margin-right 7px color userNavigatorProfileNameColor
.memberInfo font-size 22px
float left cursor pointer
.memberProfileName transition 0.1s
margin-bottom 5px &>.name
.memberName padding 5px 10px
margin-left 5px font-size 14px
font-size 0.8em color userNavigatorColor
color inactiveTextColor cursor pointer
a:hover .memberProfileName, a:hover .memberName transition 0.1s
text-decoration underline &>.dropdownIcon
.userProfile position absolute
absolute top left right top 20px
padding 15px right 25px
border-bottom solid 1px borderColor
height 125px
clearfix()
.userPhoto
circle()
float left
margin 5px 15px 15px
.userInfo
float left
margin-top 15px
.userProfileName
font-size 1.5em
color brandColor
margin-bottom 10px
.userName
font-size 1.1em
.editProfileButton
float right float right
btnDefault() width 20px
margin-top 25px height 20px
padding 10px 15px line-height 20px
border-radius 5px font-size 8px
.teamList, .memberList border solid 1px userNavigatorColor
absolute left bottom border-radius 12.5px
top 125px text-align center
width 200px transition 0.1s
padding 15px
border-right solid 1px borderColor
overflow-y auto
.teamLabel, .memberLabel
font-size 1.2em
margin-bottom 15px
.teams
li
padding 0 10px
margin-bottom 15px
clearfix()
.teamInfo
float left
.teamProfileName
margin-bottom 5px
.teamName
margin-left 5px
font-size 0.8em
color inactiveTextColor
a:hover .teamProfileName, a:hover .teamName
text-decoration underline
margin-bottom 10px
font-size 1.1em
.createTeamButton, .addMemberButton
btnStripDefault()
.members
li
padding 0 10px
margin-bottom 15px
clearfix()
.memberImage
float left
margin-right 7px
.memberInfo
float left
.memberProfileName
margin-bottom 5px
.memberRole
font-size 0.8em
color inactiveTextColor
.memberName
margin-left 5px
font-size 0.8em
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 &:hover
text-decoration underline &>.profileName
color lighten(brandColor, 10%)
&>.name
color white
&>.dropdownIcon
border-color white
&:active
&>.dropdownIcon
background-color brandColor
border-color brandColor
&>.control
padding 15px 15px
&>.newPostButton
background-color brandColor
color white
height 44px
width 100%
border none
border-radius 5px
font-size 16px
font-weight 600
transition 0.1s
&:hover
background-color lighten(brandColor, 10%)
&>.menu
absolute left right bottom
top 134px
padding 15px 0
overflow auto
&>.menuGruop
&>.label
border-bottom 1px solid userNavigatorBorderColor
padding 10px 15px
font-size 18px
margin-bottom 10px margin-bottom 10px
.createPlanetButton &>.plusButton
btnStripDefault() float right
width 20px
height 20px
margin-top -2.5px
margin-right -5px
line-height 15px
font-size 8px
border solid 1px userNavigatorColor
border-radius 10px
background-color transparent
text-align center
color userNavigatorColor
&:hover
border-color white
color white
&:active
background-color brandColor
border-color brandColor
&>.folders
.folderButton
padding 10px 25px
width 100%
background-color transparent
border none
font-size 14px
color userNavigatorColor
transition 0.1s
text-align left
&:hover
background-color transparentify(white, 20%)
color white
&.active
background-color brandColor
color white

View File

@@ -5,6 +5,11 @@ 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%
@@ -17,21 +22,12 @@ body
font-size fontSize font-size fontSize
font-weight 400 font-weight 400
button, input, select
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
@@ -50,6 +46,9 @@ button
&:focus, &.focus &:focus, &.focus
outline none outline none
.noSelect
noSelect()
.text-center .text-center
text-align center text-align center
@@ -59,13 +58,6 @@ 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
@@ -105,14 +97,32 @@ textarea.block-input
z-index 2000 z-index 2000
bottom 5px bottom 5px
right 53px right 53px
btnDefault()
padding 10px 15px padding 10px 15px
border none
border-radius 5px border-radius 5px
background-color brandColor
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
btnDefault()
padding 10px 15px padding 10px 15px
border none
border-radius 5px border-radius 5px
background-color brandColor
color white
opacity 0.7
&:hover
opacity 1
background-color lighten(brandColor, 10%)
.tooltip
tooltip()
margin-top -22px
margin-left -107px
&:hover .tooltip
opacity 1

View File

@@ -3,6 +3,7 @@ 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
@@ -11,6 +12,7 @@ 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

View File

@@ -1,31 +1,36 @@
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
margin 0 auto 0.67em border-bottom solid 2px borderColor
margin 0.33em auto 0.67em
h2 h2
font-size 1.5em font-size 1.5em
margin 0 auto 0.83em margin 0.42em auto 0.83em
h3 h3
font-size 1.17em font-size 1.17em
margin 0 auto 1em margin 0.5em auto 1em
h4 h4
font-size 1em font-size 1em
margin 0 auto 1.33em margin 0.67em auto 1.33em
h5 h5
font-size 0.83em font-size 0.83em
margin 0 auto 1.67em margin 0.84em auto 1.67em
h6 h6
font-size 0.67em font-size 0.67em
margin 2.33em auto margin 1.16em auto 2.33em
h1, h2, h3, h4, h5, h6 h1, h2, h3, h4, h5, h6
font-weight font-weight 400 font-weight 700
line-height 1.2em line-height 1.8em
p p
line-height 1.2em line-height 1.8em
margin 15px 0 25px
img img
max-width 100% max-width 100%
strong strong
@@ -36,14 +41,15 @@ marked()
text-decoration line-through text-decoration line-through
blockquote blockquote
border-left solid 4px brandBorderColor border-left solid 4px brandBorderColor
margin 1em 0 margin 15px 0 25px
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
margin 0.5em 0 line-height 1.8em
&>li>ul &>li>ul
list-style-type circle list-style-type circle
&>li>ul &>li>ul
@@ -51,35 +57,41 @@ 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
margin 0.5em 0 line-height 1.8em
code code
font-family 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
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
margin 0.5em 0
overflow-x auto overflow-x auto
margin 15px 0 25px
background-color #F6F6F6
&>code &>code
padding 0 padding 0
border none border none
border-radius 0 border-radius 0
color black
table table
width 100% width 100%
margin 15px 0 margin 15px 0 25px
thead thead
tr tr
background-color tableHeadBgColor background-color tableHeadBgColor
th th
border-style: solid; border-style solid
padding: 5px; padding 15px 5px
border-width: 1px 0 2px 1px; border-width 1px 0 2px 1px
border-color borderColor border-color borderColor
&:last-child &:last-child
border-right solid 1px borderColor border-right solid 1px borderColor
@@ -89,9 +101,9 @@ marked()
tr:nth-child(2n) tr:nth-child(2n)
background-color tableEvenBgColor background-color tableEvenBgColor
td td
border-style: solid; border-style solid
padding: 5px; padding 15px 5px
border-width: 0 0 1px 1px; border-width 0 0 1px 1px
border-color borderColor border-color borderColor
&:last-child &:last-child
border-right solid 1px borderColor border-right solid 1px borderColor

View File

@@ -0,0 +1,14 @@
tooltip()
position fixed
z-index popupZIndex
background-color transparentify(invBackgroundColor, 80%)
color invTextColor
padding 6px 15px
font-size 12px
font-weight normal
line-height 20px
white-space nowrap
opacity 0
transition 0.1s
pointer-events none

View File

@@ -3,5 +3,4 @@ borderBox()
noSelect() noSelect()
-webkit-user-select none -webkit-user-select none
-webkit-app-region drag cursor default

View File

@@ -1,375 +1,390 @@
.ModalBase // .ModalBase
fixed top left bottom right // fixed top left bottom right
z-index modalZIndex // z-index modalZIndex
&.hide // &.hide
display none // display none
.modalBack // .modalBack
absolute top left bottom right // absolute top left bottom right
background-color modalBackColor // background-color modalBackColor
z-index modalZIndex + 1 // z-index modalZIndex + 1
.modal // .modal
position relative // position relative
width 650px // width 650px
margin 50px auto 0 // margin 50px auto 0
z-index modalZIndex + 2 // z-index modalZIndex + 2
box-shadow popupShadow // box-shadow popupShadow
background-color white // background-color white
border-radius 10px // border-radius 10px
padding 15px // padding 15px
.modal-header // .modal-header
border-bottom solid 1px borderColor // border-bottom solid 1px borderColor
margin-bottom 10px // margin-bottom 10px
h1 // h1
padding 10px 0 15px // padding 10px 0 15px
font-size 1.5em // font-size 1.5em
.modal-body // .modal-body
p // p
margin-bottom 10px // margin-bottom 10px
.modal-footer // .modal-footer
clearfix() // clearfix()
border-top solid 1px borderColor // border-top solid 1px borderColor
padding-top 10px // padding-top 10px
.modal-control // .modal-control
float right // float right
//
.tabModal // .sideNavModal
height 500px // height 500px
.leftPane // .leftPane
absolute top bottom left // absolute top bottom left
width 175px // width 175px
padding 20px // padding 20px
border-right solid 1px borderColor // border-right solid 1px borderColor
.tabLabel // .modalLabel
font-size 1.5em // font-size 1.5em
margin-top 25px // margin-top 25px
margin-bottom 35px // margin-bottom 35px
color brandColor // color brandColor
.tabList button // .tabList button
btnStripDefault() // btnStripDefault()
display block // display block
width 100% // width 100%
font-size 1.1em // font-size 1.1em
padding 10px 5px // padding 10px 5px
margin-bottom 15px // margin-bottom 15px
text-align left // text-align left
.rightPane // .rightPane
absolute top bottom right // absolute top bottom right
left 175px // left 175px
padding 15px // padding 15px
overflow-y auto // overflow-y auto
// .tab
.EditProfileModal, .PlanetSettingModal, .TeamSettingsModal // padding-top 45px
.userInfoTab, .passwordTab, .planetProfileTab, .userInfoTab, .membersTab // .formField
padding-top 45px // position relative
.formField // clearfix()
position relative // margin-bottom 15px
clearfix() // label
margin-bottom 15px // width 30%
label // display block
width 30% // line-height 33px
display block // float left
line-height 33px // input
float left // width 70%
input // display block
width 70% // borderInput()
display block // height 33px
borderInput() // font-size 1em
height 33px // border-radius 5px
font-size 1em // float left
border-radius 5px // .formRadioField
float left // margin-bottom 15px
.formRadioField // input
margin-bottom 15px // margin-left 25px
input // .formConfirm
margin-left 25px // position relative
.formConfirm // clearfix()
position relative // margin-bottom 15px
clearfix() // button
margin-bottom 15px // float right
button // btnDefault()
float right // padding 10px 15px
btnDefault() // border-radius 5px
padding 10px 15px // font-size 1em
border-radius 5px // margin-left 5px
font-size 1em // .alertInfo, .alertSuccess, .alertError
margin-left 5px // float right
.alertInfo, .alertSuccess, .alertError // padding 12px 10px
float right // border-radius 5px
padding 12px 10px // width 320px
border-radius 5px // font-size 1em
width 200px // overflow-x hidden
font-size 1em // white-space nowrap
overflow-x hidden // transition 0.1s
white-space nowrap // &.hide
transition 0.1s // width 0
&.hide // padding 12px 0
width 0 // .alertInfo
padding 12px 0 // alertInfo()
.alertInfo // .alertSuccess
alertInfo() // alertSuccess()
.alertSuccess // .alertError
alertSuccess() // alertError()
.alertError // .PreferencesModal
alertError() // .settingsTab
.planetDeleteTab // .categoryLabel
padding-top 65px // font-size 1.5em
p // margin-bottom 25px
margin-bottom 25px // .example
strong // marked()
color brandColor // .aboutTab
font-size 1.1em // padding-top 30px
input // .about1
borderInput() // margin-bottom 25px
margin-right 5px // .logo
height 33px // display block
font-size 1em // margin 0 auto
border-radius 10px // .appInfo
.formConfirm // font-size 1.5em
position relative // text-align center
clearfix() // .about2
margin-bottom 15px // width 200px
button // margin 0 auto
float right // .externalLabel
btnDefault() // font-size 1.2em
padding 10px 15px // margin-bottom 15px
border-radius 5px // .externalList
font-size 1em // li
margin-left 5px // margin-bottom 15px
.alertInfo, .alertSuccess, .alertError // .PlanetSettingModal
float right // .planetDeleteTab
padding 12px 10px // padding-top 65px
border-radius 5px // p
width 200px // margin-bottom 25px
font-size 1em // strong
overflow-x hidden // color brandColor
white-space nowrap // font-size 1.1em
transition 0.1s // input
&.hide // borderInput()
width 0 // margin-right 5px
padding 12px 0 // height 33px
.alertInfo // font-size 1em
alertInfo() // border-radius 10px
.alertSuccess // .formConfirm
alertSuccess() // position relative
.alertError // clearfix()
alertError() // margin-bottom 15px
.membersTab // button
.memberTable // float right
width 100% // btnDefault()
margin-bottom 25px // padding 10px 15px
th // border-radius 5px
border-bottom solid 2px borderColor // font-size 1em
td // margin-left 5px
border-bottom solid 1px borderColor // .alertInfo, .alertSuccess, .alertError
height 38px // float right
button // padding 12px 10px
btnDefault() // border-radius 5px
padding 5px // width 320px
border-radius 5px // font-size 1em
.roleSelect // overflow-x hidden
height 33px // white-space nowrap
border solid 1px borderColor // transition 0.1s
background-color backgroundColor // &.hide
th, td // width 0
padding 5px 0 // padding 12px 0
.addMemberForm // .alertInfo
.formLabel // alertInfo()
margin-bottom 5px // .alertSuccess
.formGroup // alertSuccess()
clearfix() // .alertError
.userNameSelect // alertError()
display block // .TeamSettingsModal
width 200px // .membersTab
margin-right 5px // .memberTable
float left // width 100%
.roleSelect // margin-bottom 25px
display block // th
height 33px // border-bottom solid 2px borderColor
border solid 1px borderColor // td
background-color backgroundColor // border-bottom solid 1px borderColor
float left // height 38px
margin-right 5px // button
.confirmButton // btnDefault()
display block // padding 5px
height 33px // border-radius 5px
btnDefault() // .roleSelect
border-radius 5px // height 33px
float left // border solid 1px borderColor
// background-color backgroundColor
// th, td
.LaunchModal // padding 5px 0
.modal-tab // .addMemberForm
text-align center // .formLabel
margin-bottom 10px // margin-bottom 5px
.btn-primary, .btn-default // .formGroup
margin 0 // clearfix()
border-radius 0 // .userNameSelect
border-width 1px // display block
width 150px // width 200px
border-radius 0 // margin-right 5px
&:nth-child(1) // float left
border-right solid 1px borderColor // .roleSelect
border-top-left-radius 5px // display block
border-bottom-left-radius 5px // height 33px
&:nth-child(2) // border solid 1px borderColor
border-left none // background-color backgroundColor
border-top-right-radius 5px // float left
border-bottom-right-radius 5px // margin-right 5px
.Select // .confirmButton
.Select-control // display block
border-color borderColor // height 33px
&.is-focused // btnDefault()
.Select-control // border-radius 5px
border-color brandBorderColor // float left
.Select-menu-outer //
border-color borderColor // .LaunchModal
.ace_editor // .modal-tab
border-radius 5px // text-align center
border solid 1px borderColor // margin-bottom 10px
.CodeForm, .NoteForm // .btn-primary, .btn-default
.form-group // margin 0
margin-bottom 10px // border-radius 0
.CodeForm // border-width 1px
textarea.codeDescription // width 150px
height 75px // border-radius 0
font-size 0.9em // &:nth-child(1)
margin-bottom 10px // border-right solid 1px borderColor
.modeSelect.Select // border-top-left-radius 5px
display inline-block // border-bottom-left-radius 5px
width 200px // &:nth-child(2)
height 37px // border-left none
.Select-control // border-top-right-radius 5px
height 37px // border-bottom-right-radius 5px
.ace_editor // .Select
height 258px // .Select-control
.NoteForm // border-color borderColor
.ace_editor // &.is-focused
height 358px // .Select-control
.previewMode // border-color brandBorderColor
absolute top right // .Select-menu-outer
font-size 0.8em // border-color borderColor
line-height 24px // .ace_editor
padding 5 15px // border-radius 5px
background-color transparentify(invBackgroundColor, 0.2) // border solid 1px borderColor
color invTextColor // .CodeForm, .NoteForm
border-top-right-radius 5px // .form-group
.marked // margin-bottom 10px
height 360px // .CodeForm
overflow-x hidden // textarea.codeDescription
overflow-y auto // height 75px
box-sizing border-box // font-size 0.9em
padding 5px // margin-bottom 10px
border solid 1px borderColor // .modeSelect.Select
border-radius 5px // display inline-block
marked() // width 200px
// height 37px
.AboutModal // .Select-control
width 320px // height 37px
.about1 // .ace_editor
margin-bottom 25px // height 258px
.logo // .NoteForm
display block // .ace_editor
margin 0 auto // height 358px
.appInfo // .previewMode
font-size 1.5em // absolute top right
text-align center // font-size 0.8em
.about2 // line-height 24px
width 200px // padding 5 15px
margin 0 auto // background-color transparentify(invBackgroundColor, 0.2)
.externalLabel // color invTextColor
font-size 1.2em // border-top-right-radius 5px
margin-bottom 15px // .marked
.externalList // height 360px
li // overflow-x hidden
margin-bottom 15px // overflow-y auto
// box-sizing border-box
.PlanetCreateModal.modal, .TeamCreateModal.modal, .AddMemberModal.modal // padding 5px
padding 60px 0 // border solid 1px borderColor
.nameInput // border-radius 5px
width 80% // marked()
font-size 1.3em //
margin 25px auto 15px //
text-align center // .PlanetCreateModal.modal, .TeamCreateModal.modal, .AddMemberModal.modal
.userNameSelect // padding 60px 0
width 80% // .nameInput
font-size 1.3em // width 80%
margin 35px auto // font-size 1.3em
text-align center // margin 25px auto 15px
.formField // text-align center
text-align center // .userNameSelect
margin 0 auto 25px // width 80%
select // font-size 1.3em
display inline-block // margin 35px auto
width 150px // text-align center
height 33px // .formField
border solid 1px borderColor // text-align center
background-color white // margin 0 auto 25px
padding 0 10px // select
margin 0 15px // display inline-block
.submitButton // width 150px
display block // height 33px
margin 0 auto // border solid 1px borderColor
box-sizing border-box // background-color white
width 55px // padding 0 10px
height 55px // margin 0 15px
circle() // .submitButton
btnPrimary() // display block
// margin 0 auto
.ContactModal // box-sizing border-box
padding 15px // width 55px
.contactForm // height 55px
.formField // circle()
width 100% // btnPrimary()
margin-bottom 10px // .errorAlert
input, textarea // alertError()
display block // padding 12px 10px
width 100% // border-radius 5px
borderInput() // text-align center
border-radius 5px // display block
input // width 360px
height 33px // margin 0 auto 15px
font-size 1em //
textarea // .ContactModal
height 175px // padding 15px
font-size 1em // .contactForm
.formControl // .formField
clearfix() // width 100%
button // margin-bottom 10px
float right // input, textarea
btnDefault() // display block
height 44px // width 100%
padding 0 15px // borderInput()
border-radius 5px // border-radius 5px
margin-left 5px // input
button.sendButton // height 33px
btnPrimary() // font-size 1em
.confirmation // textarea
.confirmationMessage // height 175px
padding 35px 0 // font-size 1em
text-align center // .formControl
font-size 1.1em // clearfix()
.doneButton // button
btnDefault() // float right
height 44px // btnDefault()
padding 0 35px // height 44px
border-radius 5px // padding 0 15px
display block // border-radius 5px
margin 0 auto 25px // margin-left 5px
// font-size 1em
.LogoutModal // button.sendButton
padding 65px 0 45px // btnPrimary()
width 350px // .confirmation
.messageLabel // .confirmationMessage
text-align center // padding 35px 0
font-size 1.1em // text-align center
margin-bottom 35px // font-size 1.1em
.formControl // .doneButton
text-align center // btnDefault()
button // height 44px
btnDefault() // padding 0 35px
border-radius 5px // border-radius 5px
height 44px // display block
margin 15px 5px // margin 0 auto 25px
padding 0 15px //
button.logoutButton // .LogoutModal
btnPrimary() // padding 65px 0 45px
// width 350px
// .messageLabel
// text-align center
// font-size 1.1em
// margin-bottom 35px
// .formControl
// text-align center
// button
// btnDefault()
// border-radius 5px
// height 44px
// margin 15px 5px
// padding 0 15px
// button.logoutButton
// btnPrimary()

Some files were not shown because too many files have changed in this diff Show More