1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 02:06:29 +00:00

Compare commits

...

123 Commits
v0.1 ... 0.2.10

Author SHA1 Message Date
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
d88ad0f6be version 0.2.1 2015-08-21 04:51:26 +09:00
Rokt33r
4e34f16e33 修正 - Member list, Team listのデザイン修正 2015-08-21 02:13:41 +09:00
Rokt33r
503a806446 実装 - Team pageとPlanet pageにMember List表示 2015-08-21 01:49:23 +09:00
Rokt33r
caf7606893 実装 - PlanetHeaderにPrivate鍵表示 2015-08-20 16:41:05 +09:00
Rokt33r
2fb51fe37c Noteから外部Linkを開くときにBrowserを使う 2015-08-20 15:52:20 +09:00
Rokt33r
fa1c48e480 LogoutModal実装 2015-08-20 14:44:57 +09:00
Rokt33r
3d9a631786 Contact Modal追加 2015-08-20 01:30:57 +09:00
Rokt33r
0b4cfd6563 on Refactor... #4 2015-08-19 04:05:38 +09:00
Rokt33r
484dfe6726 on Refactor... #3 2015-08-19 00:54:38 +09:00
Rokt33r
d8cb93fb10 on Refactor... #2 2015-08-18 03:46:09 +09:00
Rokt33r
23b8b49c00 on Refactoring... 2015-08-17 01:10:08 +09:00
Rokt33r
932997259f test 0.2.0 2015-08-04 16:23:25 +09:00
Rokt33r
1bebb66165 Fix: Personal Settingボターンを右にする。写真からまっすぐProfile pageに入れるようにする 2015-07-30 16:34:01 +09:00
Rokt33r
ac0d81f9b3 Add: Refresh button 2015-07-30 14:40:14 +09:00
Rokt33r
45b99d13cd Fix: Design changed 2015-07-30 12:50:12 +09:00
Rokt33r
920704075e Fix: Disable pinch to zoom 2015-07-30 04:11:31 +09:00
Rokt33r
9e929f80ad Fix: Preview button修正 2015-07-30 00:59:52 +09:00
Rokt33r
9696a6cba1 Fix: PlanetNavigatorのHome削除 & SnippetsとBlueprintsはToggleができるように 2015-07-30 00:44:26 +09:00
Rokt33r
211fd8b28a Fix: minor features
設定ボタンアイコンの変更
削除Modalでcmd+enterの使用
検索バーデザイン
2015-07-30 00:00:45 +09:00
Rokt33r
c6ef86cbbe Add: Log in / Sign upの時にエラーが出たらAlertを表示する
Debug: Tray Icon, PopUpWindow, Menuがいつの間にか消える
2015-07-29 22:43:27 +09:00
Rokt33r
90c2ff7480 Debug: Finderから検索して結果が何もなくなってからFinder自体が働かなくなる 2015-07-29 22:41:17 +09:00
Rokt33r
49057810fb 実装:PlanetNavigatorのUser iconを押すとそのUser pageに飛ぶ
改善:Profile Photoに影を追加
2015-07-29 13:17:55 +09:00
Rokt33r
e5e6e2e1b8 実装:Profile Imageとしてgravatar Icon使用 2015-07-29 11:26:28 +09:00
Rokt33r
c8851ecd2a 改善:UserNavatorに活性化されているPlanetがちゃん表示されるした 2015-07-29 10:52:37 +09:00
Rokt33r
bd2d77fef7 改善:PlanetNavigatorに活性化されているLinkを目立つようにする 2015-07-29 03:40:36 +09:00
Rokt33r
43403f8bb1 改善:Aceで利用可能なすべてのLanguage Modeに対応 2015-07-29 03:28:48 +09:00
Rokt33r
0ac7839f11 改善:Login/Register部分のデザイン 2015-07-29 02:40:27 +09:00
Rokt33r
714b5f7b4b 改善:立ち上げたらUserの情報(PlanetのSnippetsなどを含めた)を検証して、データを全部リロードする 2015-07-29 02:18:44 +09:00
Rokt33r
51fb43d624 改善: 新しいAce適用 2015-07-29 01:44:36 +09:00
Rokt33r
a79cbb2d5c Debug: Planet 削除エラー 2015-07-29 01:40:21 +09:00
Rokt33r
e88c197f86 cleanup useless packages 2015-07-29 00:03:01 +09:00
Rokt33r
b1be92e6c9 add Finder & update main.js & cleanup some old files 2015-07-28 23:56:50 +09:00
Rokt33r
d20f005c5d apply electron & cleanup old app files 2015-07-28 23:35:21 +09:00
Rokt33r
5dbfb24f1c add delete planet request 2015-07-25 12:39:09 +09:00
Rokt33r
4df489bd10 add mail sending request & add response alerts for PersonalSettingModal & fix preventDefault bug(PersonalSettingModal) 2015-07-25 12:07:09 +09:00
Rokt33r
8c3510413a add hotkeys for planet switching 2015-07-25 02:34:58 +09:00
Rokt33r
bea9dfdfc7 add planet/user tooltip 2015-07-25 02:23:21 +09:00
Rokt33r
465b315ae0 add Tag filter 2015-07-25 02:04:27 +09:00
Rokt33r
054daac6db add filter 2015-07-24 20:34:10 +09:00
Rokt33r
867ec25e54 add user management for planet setting modal 2015-07-24 19:05:11 +09:00
Rokt33r
2e4aaf7345 add PersonalSettingModal 2015-07-24 15:13:04 +09:00
Rokt33r
2f754bbb87 add name change for planet & fix minor bugs 2015-07-23 07:55:56 +09:00
Rokt33r
cdf6ed47dd add PlanetSettingModal(Only visible things) 2015-07-23 02:41:35 +09:00
Rokt33r
c31432fe3f adding User to planet is available 2015-07-22 14:41:29 +09:00
Rokt33r
f0b2e91091 fix planet switching bugs 2015-07-21 18:16:03 +09:00
Rokt33r
72a08e8fec add planet create modal & switching func 2015-07-21 12:02:40 +09:00
Rokt33r
864001bdff cleanup redirecting 2015-07-21 00:36:01 +09:00
Rokt33r
bd2816b2ac fix search bug 2015-07-21 00:18:00 +09:00
Rokt33r
03918527f6 update new icon and design 2015-07-20 03:19:17 +09:00
Rokt33r
6140e93cc8 add Logout modal & update PlanetHeader 2015-07-20 01:12:45 +09:00
Rokt33r
0f8eaaf750 fix minor design 2015-07-19 22:08:20 +09:00
Rokt33r
7afad6ac49 fix hotkey bugs 2015-07-19 15:10:09 +09:00
Rokt33r
e9308bdd69 refactor hotkeys 2015-07-19 15:06:36 +09:00
Rokt33r
16b60ada50 add hotkey 2015-07-19 03:19:41 +09:00
Rokt33r
aa71251edd update behavior 2015-07-19 02:35:02 +09:00
Rokt33r
89cfd35d72 extract components from PlanetContainer 2015-07-19 00:10:52 +09:00
Rokt33r
9ea16a39df add article indexing(keyinput, store event) 2015-07-18 21:38:22 +09:00
Rokt33r
4fee2586e4 blueprint articles are available in planet list 2015-07-18 16:52:17 +09:00
Rokt33r
b30511eb51 add Markdown 2015-07-18 03:25:02 +09:00
Rokt33r
05325e7276 update design & refresh behavior 2015-07-17 12:12:47 +09:00
Rokt33r
0dde2eb20f add SnippetDeleteModal & fix some bugs 2015-07-17 09:03:49 +09:00
Rokt33r
c2fcc72e62 add SnippetEditModal 2015-07-16 23:55:59 +09:00
Rokt33r
ec686c9452 extract BlueprintForm from LaunchModal & add Tag search 2015-07-16 17:02:46 +09:00
Rokt33r
863de33f63 extract SnippetForm component & add focsuing after form loaded & enhance CSS of Article focusing effect in 2015-07-16 02:14:51 +09:00
Rokt33r
f56dd10106 refactor modal & setup ordering for snippet 2015-07-16 01:34:40 +09:00
Rokt33r
8b10eb130a refactor Actions & add logout action 2015-07-14 01:20:17 +09:00
Rokt33r
b0d9895e5e add Article detail 2015-07-13 13:05:54 +09:00
Rokt33r
1d3e3f3c87 update Login/Register Container style & fix OnlyGuest Mixin 2015-07-12 22:00:19 +09:00
Rokt33r
36eaebcbc7 add OnlyGuest mixin 2015-07-12 19:24:44 +09:00
Rokt33r
7870c60ab4 add login/signup action 2015-07-12 18:13:28 +09:00
Rokt33r
e0d52d3578 refactor Router & add User settings 2015-07-11 10:53:04 +09:00
Rokt33r
9422825aec add Dropdown for planet header menu 2015-07-10 17:52:58 +09:00
Rokt33r
e467862c29 change planet body layout 2015-07-09 23:05:07 +09:00
Rokt33r
6b03ea2fe5 add ignore config for standard & standardize 2015-07-09 02:40:35 +09:00
Rokt33r
472d79cbf2 add ModalBase, LaunchModal & install Reflux 2015-07-09 01:48:49 +09:00
Rokt33r
27701bbe1b add update log 2015-07-08 02:22:32 +09:00
Rokt33r
9e1dcf8b64 add highlighting for an active item in the snippet list 2015-07-07 15:08:57 +09:00
Rokt33r
c74de88ca3 embed ace 2015-07-07 03:55:52 +09:00
Rokt33r
361e9c629e switch Angular -> React(half done) 2015-07-06 02:03:40 +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
Rokt33r
1a832c1fc4 rename api routes 2015-06-25 23:19:45 +09:00
Rokt33r
ee139ca36d update popup and set stage api 2015-06-25 21:01:57 +09:00
Rokt33r
549ce7f299 add How-to & Signout modal 2015-06-25 20:31:38 +09:00
Rokt33r
c72b5449bd clean up recipe 2015-06-25 19:28:36 +09:00
Rokt33r
debfa6323b update recipe states 2015-06-24 20:39:36 +09:00
Rokt33r
0e6fe35ca4 add Recipes(scaffolding) 2015-06-23 19:18:00 +09:00
Rokt33r
7c2cbfb32e fix injector bug & add tray-icon 2015-06-22 12:20:16 +09:00
Rokt33r
25eccacb4c refactor build config 2015-06-21 02:40:06 +09:00
163 changed files with 7459 additions and 3222 deletions

3
.bowerrc Normal file
View File

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

7
.gitignore vendored
View File

@@ -1,5 +1,6 @@
/build build/
/node_modules node_modules/
/electron_build electron_build/
.env .env
dist/ dist/
vendor/

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "browser/ace"]
path = browser/ace
url = https://github.com/ajaxorg/ace-builds.git
[submodule "browser/electron-stylus"]
path = browser/electron-stylus
url = https://github.com/Rokt33r/electron-stylus.git

View File

@@ -1,161 +0,0 @@
require('dotenv').load()
var env = process.env
var gulp = require('gulp')
var styl = require('gulp-stylus')
var autoprefixer = require('gulp-autoprefixer')
var templateCache = require('gulp-angular-templatecache')
var globby = require('globby')
var template = require('gulp-template')
var del = require('del')
var runSequence = require('run-sequence')
var plumber = require('gulp-plumber')
var notify = require('gulp-notify')
var changed = require('gulp-changed')
var livereload = require('gulp-livereload')
// for Dist
var rev = require('gulp-rev')
var ngAnnotate = require('gulp-ng-annotate')
var uglify = require('gulp-uglify')
var minifyCss = require('gulp-minify-css')
var merge = require('merge-stream')
var concat = require('gulp-concat')
var streamqueue = require('streamqueue')
var minifyHtml = require('gulp-minify-html')
var config = require('./build.config.js')
gulp.task('js', function () {
return streamqueue({objectMode: true},
gulp.src('tpls/env.js')
.pipe(template({
apiUrl: env.BUILD_API_URL
})),
gulp.src(['src/**/*.js'])
)
.pipe(changed('build'))
.pipe(gulp.dest('build'))
})
gulp.task('dist', function () {
var js = streamqueue({objectMode: true},
gulp.src(['src/**/*.js']),
gulp.src('tpls/env.js')
.pipe(template({
apiUrl: env.DIST_API_URL
})),
gulp.src('src/**/*.tpl.html')
.pipe(templateCache())
)
.pipe(ngAnnotate())
.pipe(uglify())
.pipe(concat('app.js'))
.pipe(gulp.dest('dist'))
var css = gulp.src('src/styles/main.styl')
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
.pipe(styl())
.pipe(autoprefixer())
.pipe(minifyCss())
.pipe(gulp.dest('dist'))
var index = gulp.src('src/index.html')
.pipe(template({
scripts: ['app.js'],
styles: ['main.css'],
env: 'dist'
}))
.pipe(minifyHtml())
.pipe(gulp.dest('dist'))
return merge(js, css, index)
})
gulp.task('styl', function () {
return gulp.src('src/styles/main.styl')
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
.pipe(styl())
.pipe(autoprefixer())
.pipe(gulp.dest('build'))
.pipe(notify('Stylus!!'))
.pipe(livereload())
})
gulp.task('tpls', function () {
return gulp.src('src/**/*.tpl.html')
.pipe(templateCache())
.pipe(notify('Tpls Done!! :)'))
.pipe(gulp.dest('build'))
})
gulp.task('index', function () {
var files = globby.sync(['build/**/*', '!build/vendor/**/*'])
var filter = function (files, ext) {
return files.filter(function (file) {
var reg = new RegExp('.+\.' + ext + '$')
return file.match(reg)
}).map(function (file) {
return file.replace('build/', '')
})
}
var scripts = filter(files, 'js')
var styles = filter(files, 'css')
return gulp.src('src/index.html')
.pipe(template({
scripts: scripts,
styles: styles,
env: 'build'
}))
.pipe(gulp.dest('build'))
.pipe(livereload())
})
gulp.task('vendor', function () {
var vendors = config.vendors
var vendorFiles = vendors.map(function (vendor) {
return vendor.src
})
vendorFiles.push('node_modules/font-awesome/**/font-awesome.css')
vendorFiles.push('node_modules/font-awesome/**/fontawesome-webfont.*')
vendorFiles.push('node_modules/font-awesome/**/FontAwesome.*')
return gulp.src(vendorFiles)
.pipe(gulp.dest('build/vendor'))
})
gulp.task('resources', function () {
return gulp.src('resources/**/*')
.pipe(changed('build/resources'))
.pipe(gulp.dest('build/resources'))
})
gulp.task('build', function (cb) {
runSequence(['js', 'styl', 'tpls', 'vendor', 'resources'], 'index', cb)
})
gulp.task('watch', function (cb) {
gulp.watch(['.env', 'tpls/env.js', 'src/**/*.js'], ['js'])
gulp.watch('src/styles/**/*.styl', ['styl'])
gulp.watch('src/**/*.tpl.html', ['tpls'])
gulp.watch(['build/**/*.js', 'src/index.html'], ['index'])
livereload.listen()
})
gulp.task('del', function (cb) {
del(['build/**/*'], cb)
})
gulp.task('default', function (cb) {
runSequence('del', 'build', 'watch', cb)
})
require('./gulp-electron')(gulp)

BIN
Lato-Regular.ttf Normal file

Binary file not shown.

BIN
Lato-Regular.woff Normal file

Binary file not shown.

BIN
Lato-Regular.woff2 Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

11
bower.json Normal file
View File

@@ -0,0 +1,11 @@
{
"name": "codexen-app",
"dependencies": {
"react": "~0.13.3",
"fontawesome": "~4.3.0",
"react-router": "~0.13.3",
"reflux": "~0.2.8",
"moment": "~2.10.3",
"markdown-it": "~4.3.1"
}
}

1
browser/ace Submodule

Submodule browser/ace added at 0982db4853

View File

@@ -0,0 +1,42 @@
var React = require('react/addons')
var CodeViewer = require('../../main/Components/CodeViewer')
var MarkdownPreview = require('../../main/Components/MarkdownPreview')
module.exports = React.createClass({
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'>
<MarkdownPreview className='marked' content={article.content}/>
</div>
</div>
)
}
}
return (
<div className='FinderDetail'>
<div className='nothing'>Nothing selected</div>
</div>
)
}
})

View File

@@ -0,0 +1,15 @@
var React = require('react/addons')
module.exports = React.createClass({
propTypes: {
onChange: React.PropTypes.func,
search: React.PropTypes.string
},
render: function () {
return (
<div className='FinderInput'>
<input value={this.props.search} onChange={this.props.onChange} type='text'/>
</div>
)
}
})

View File

@@ -0,0 +1,79 @@
var React = require('react/addons')
module.exports = React.createClass({
propTypes: {
articles: React.PropTypes.arrayOf,
currentArticle: React.PropTypes.shape({
id: React.PropTypes.number,
type: React.PropTypes.string
}),
selectArticle: React.PropTypes.func
},
componentDidUpdate: function () {
var index = this.props.articles.indexOf(this.props.currentArticle)
var el = React.findDOMNode(this)
var li = el.querySelectorAll('li')[index]
if (li == null) {
return
}
var overflowBelow = el.clientHeight + el.scrollTop < li.offsetTop + li.clientHeight
if (overflowBelow) {
el.scrollTop = li.offsetTop + li.clientHeight - el.clientHeight
}
var overflowAbove = el.scrollTop > li.offsetTop
if (overflowAbove) {
el.scrollTop = li.offsetTop
}
},
handleArticleClick: function (article) {
return function () {
this.props.selectArticle(article)
}.bind(this)
},
render: function () {
var list = this.props.articles.map(function (article) {
if (article == null) {
return (
<li className={isActive ? 'active' : ''}>
<div className='articleItem'>Undefined</div>
<div className='divider'/>
</li>
)
}
var isActive = this.props.currentArticle != null && (article.type === this.props.currentArticle.type && article.id === this.props.currentArticle.id)
if (article.type === 'code') {
return (
<li onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
<div className='articleItem'><i className='fa fa-code fa-fw'/> {article.description}</div>
<div className='divider'/>
</li>
)
}
if (article.type === 'note') {
return (
<li onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
<div className='articleItem'><i className='fa fa-file-text-o fa-fw'/> {article.title}</div>
<div className='divider'/>
</li>
)
}
return (
<li className={isActive ? 'active' : ''}>
<div className='articleItem'>Undefined</div>
<div className='divider'/>
</li>
)
}.bind(this))
return (
<div className='FinderList'>
<ul>
{list}
</ul>
</div>
)
}
})

BIN
browser/finder/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html>
<head>
<title>CodeXen Popup</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<style>
@font-face {
font-family: 'Lato';
src: url('../../Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
url('../../Lato-Regular.woff') format('woff'), /* Modern Browsers */
url('../../Lato-Regular.ttf') format('truetype');
font-style: normal;
font-weight: normal;
text-rendering: optimizeLegibility;
}
</style>
<script>
document.addEventListener('mousewheel', function(e) {
if(e.deltaY % 1 !== 0) {
e.preventDefault()
}
})
if (!Object.assign) {
Object.defineProperty(Object, 'assign', {
enumerable: false,
configurable: true,
writable: true,
value: function(target) {
'use strict';
if (target === undefined || target === null) {
throw new TypeError('Cannot convert first argument to object');
}
var to = Object(target);
for (var i = 1; i < arguments.length; i++) {
var nextSource = arguments[i];
if (nextSource === undefined || nextSource === null) {
continue;
}
nextSource = Object(nextSource);
var keysArray = Object.keys(Object(nextSource));
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
to[nextKey] = nextSource[nextKey];
}
}
}
return to;
}
});
}
</script>
</head>
<body>
<div id="content"></div>
<script src="../ace/src-min/ace.js"></script>
<script>
require('../electron-stylus')(__dirname + '/../styles/finder/index.styl', 'finderCss')
require('node-jsx').install({ harmony: true, extension: '.jsx' })
require('./index.jsx')
</script>
</body>
</html>

View File

135
browser/finder/index.jsx Normal file
View File

@@ -0,0 +1,135 @@
/* global localStorage */
var remote = require('remote')
var hideFinder = remote.getGlobal('hideFinder')
var clipboard = require('clipboard')
var React = require('react/addons')
var ArticleFilter = require('../main/Mixins/ArticleFilter')
var FinderInput = require('./Components/FinderInput')
var FinderList = require('./Components/FinderList')
var FinderDetail = require('./Components/FinderDetail')
// Filter end
function fetchArticles () {
var user = JSON.parse(localStorage.getItem('currentUser'))
if (user == null) {
console.log('need to login')
return []
}
var articles = []
user.Planets.forEach(function (planet) {
var _planet = JSON.parse(localStorage.getItem('planet-' + planet.id))
articles = articles.concat(_planet.Codes, _planet.Notes)
})
user.Teams.forEach(function (team) {
team.Planets.forEach(function (planet) {
var _planet = JSON.parse(localStorage.getItem('planet-' + planet.id))
articles = articles.concat(_planet.Codes, _planet.Notes)
})
})
return articles
}
var Finder = React.createClass({
mixins: [ArticleFilter],
getInitialState: function () {
var articles = fetchArticles()
return {
articles: articles,
currentArticle: articles[0],
search: ''
}
},
componentDidMount: function () {
document.addEventListener('keydown', this.handleKeyDown)
document.addEventListener('click', this.handleClick)
window.addEventListener('focus', this.handleFinderFocus)
this.handleFinderFocus()
},
componentWillUnmount: function () {
document.removeEventListener('keydown', this.handleKeyDown)
document.removeEventListener('click', this.handleClick)
window.removeEventListener('focus', this.handleFinderFocus)
},
handleFinderFocus: function () {
console.log('focusseeddddd')
this.focusInput()
var articles = fetchArticles()
this.setState({
articles: articles,
search: ''
}, function () {
var firstArticle = this.refs.finderList.props.articles[0]
if (firstArticle) {
this.setState({
currentArticle: firstArticle
})
}
})
},
handleKeyDown: function (e) {
if (e.keyCode === 38) {
this.selectPrevious()
e.preventDefault()
}
if (e.keyCode === 40) {
this.selectNext()
e.preventDefault()
}
if (e.keyCode === 13) {
var article = this.state.currentArticle
clipboard.writeText(article.content)
hideFinder()
e.preventDefault()
}
if (e.keyCode === 27) {
hideFinder()
e.preventDefault()
}
},
focusInput: function () {
React.findDOMNode(this.refs.finderInput).querySelector('input').focus()
},
handleClick: function () {
this.focusInput()
},
selectPrevious: function () {
var index = this.refs.finderList.props.articles.indexOf(this.state.currentArticle)
if (index > 0) {
this.setState({currentArticle: this.refs.finderList.props.articles[index - 1]})
}
},
selectNext: function () {
var index = this.refs.finderList.props.articles.indexOf(this.state.currentArticle)
if (index > -1 && index < this.refs.finderList.props.articles.length - 1) {
this.setState({currentArticle: this.refs.finderList.props.articles[index + 1]})
}
},
selectArticle: function (article) {
this.setState({currentArticle: article})
},
handleChange: function (e) {
this.setState({search: e.target.value}, function () {
this.setState({currentArticle: this.refs.finderList.props.articles[0]})
})
},
render: function () {
var articles = this.searchArticle(this.state.search, this.state.articles)
return (
<div className='Finder'>
<FinderInput ref='finderInput' onChange={this.handleChange} search={this.state.search}/>
<FinderList ref='finderList' currentArticle={this.state.currentArticle} articles={articles} selectArticle={this.selectArticle}/>
<FinderDetail currentArticle={this.state.currentArticle}/>
</div>
)
}
})
React.render(<Finder/>, document.getElementById('content'))

11
browser/index.html Normal file
View File

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

View File

@@ -0,0 +1,38 @@
var React = require('react/addons')
var ExternalLink = require('../Mixins/ExternalLink')
var KeyCaster = require('../Mixins/KeyCaster')
module.exports = React.createClass({
mixins: [ExternalLink, KeyCaster('aboutModal')],
propTypes: {
close: React.PropTypes.func
},
onKeyCast: function (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
}
},
render: function () {
var version = global.version
return (
<div className='PreferencesModal sideNavModal 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

@@ -0,0 +1,102 @@
var React = require('react/addons')
var Select = require('react-select')
var LinkedState = require('../Mixins/LinkedState')
var Hq = require('../Services/Hq')
var KeyCaster = require('../Mixins/KeyCaster')
var UserStore = require('../Stores/UserStore')
var getOptions = function (input, callback) {
Hq.searchUser(input)
.then(function (res) {
callback(null, {
options: res.body.map(function (user) {
return {
label: user.name,
value: user.name
}
}),
complete: false
})
})
.catch(function (err) {
console.error(err)
})
}
module.exports = React.createClass({
mixins: [LinkedState, KeyCaster('addMemberModal')],
propTypes: {
team: React.PropTypes.object,
close: React.PropTypes.func
},
getInitialState: function () {
return {
userName: '',
role: 'member'
}
},
onKeyCast: function (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
case 'submitAddMemberModal':
this.handleSubmit()
break
}
},
handleSubmit: function () {
this.setState({errorMessage: null}, function () {
Hq
.addMember(this.props.team.name, {
userName: this.state.userName,
role: this.state.role
})
.then(function (res) {
console.log(res.body)
UserStore.Actions.addMember(res.body)
this.props.close()
}.bind(this))
.catch(function (err) {
console.error(err)
if (err.status === 403) {
this.setState({errorMessage: err.response.body.message})
}
}.bind(this))
})
},
handleChange: function (value) {
this.setState({userName: value})
},
render: function () {
return (
<div className='AddMemberModal modal'>
<Select
name='userName'
value={this.state.userName}
placeholder='Username to add'
asyncOptions={getOptions}
onChange={this.handleChange}
className='userNameSelect'
/>
<div className='formField'>
Add member as
<select valueLink={this.linkState('role')}>
<option value={'member'}>Member</option>
<option value={'owner'}>Owner</option>
</select>
role
</div>
{this.state.errorMessage != null ? (<p className='errorAlert'>{this.state.errorMessage}</p>) : null}
<button onClick={this.handleSubmit} className='submitButton'><i className='fa fa-check'/></button>
</div>
)
}
})

View File

@@ -0,0 +1,55 @@
var React = require('react')
var Hq = require('../Services/Hq')
var KeyCaster = require('../Mixins/KeyCaster')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
mixins: [KeyCaster('codeDeleteModal')],
propTypes: {
planet: React.PropTypes.object,
code: React.PropTypes.object,
close: React.PropTypes.func
},
onKeyCast: function (e) {
switch (e.status) {
case 'submitCodeDeleteModal':
this.submit()
break
case 'closeModal':
this.props.close()
break
}
},
submit: function () {
var planet = this.props.planet
Hq.destroyCode(planet.userName, planet.name, this.props.code.localId)
.then(function (res) {
PlanetStore.Actions.destroyCode(res.body)
this.props.close()
}.bind(this))
.catch(function (err) {
console.error(err)
})
},
render: function () {
return (
<div className='CodeDeleteModal modal'>
<div className='modal-header'>
<h1>Delete Code</h1>
</div>
<div className='modal-body'>
<p>Are you sure to delete it?</p>
</div>
<div className='modal-footer'>
<div className='modal-control'>
<button onClick={this.props.close} className='btn-default'>Cancel</button>
<button ref='submit' onClick={this.submit} className='btn-primary'>Delete</button>
</div>
</div>
</div>
)
}
})

View File

@@ -0,0 +1,26 @@
var React = require('react')
var CodeForm = require('./CodeForm')
module.exports = React.createClass({
propTypes: {
close: React.PropTypes.func,
code: React.PropTypes.object,
planet: React.PropTypes.object
},
componentDidMount: function () {
// TODO: Hacked!! should fix later
setTimeout(function () {
React.findDOMNode(this.refs.form.refs.description).focus()
}.bind(this), 1)
},
render: function () {
return (
<div className='CodeEditModal modal'>
<div className='modal-header'>
<h1>Edit Code</h1>
</div>
<CodeForm ref='form' code={this.props.code} planet={this.props.planet} close={this.props.close}/>
</div>
)
}
})

View File

@@ -0,0 +1,59 @@
var React = require('react/addons')
var ace = window.ace
module.exports = React.createClass({
propTypes: {
code: React.PropTypes.string,
mode: React.PropTypes.string,
className: React.PropTypes.string,
onChange: React.PropTypes.func
},
componentDidMount: function () {
var el = React.findDOMNode(this.refs.target)
var editor = ace.edit(el)
editor.$blockScrolling = Infinity
editor.setValue(this.props.code)
editor.renderer.setShowGutter(true)
editor.setTheme('ace/theme/xcode')
editor.clearSelection()
var session = editor.getSession()
if (this.props.mode != null && this.props.mode.length > 0) {
session.setMode('ace/mode/' + this.props.mode)
} else {
session.setMode('ace/mode/text')
}
session.setUseSoftTabs(true)
session.setOption('useWorker', false)
session.setUseWrapMode(true)
session.on('change', function (e) {
if (this.props.onChange != null) {
var value = editor.getValue()
this.props.onChange(e, value)
}
}.bind(this))
this.setState({editor: editor})
},
componentDidUpdate: function (prevProps) {
if (this.state.editor.getValue() !== this.props.code) {
this.state.editor.setValue(this.props.code)
this.state.editor.clearSelection()
}
if (prevProps.mode !== this.props.mode) {
var session = this.state.editor.getSession()
if (this.props.mode != null && this.props.mode.length > 0) {
session.setMode('ace/mode/' + this.props.mode)
} else {
session.setMode('ace/mode/text')
}
}
},
render: function () {
return (
<div ref='target' className={this.props.className}></div>
)
}
})

View File

@@ -0,0 +1,162 @@
var React = require('react/addons')
var CodeEditor = require('./CodeEditor')
var Select = require('react-select')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var KeyCaster = require('../Mixins/KeyCaster')
var PlanetStore = require('../Stores/PlanetStore')
var aceModes = require('../../../modules/ace-modes')
var getOptions = function (input, callback) {
Hq.searchTag(input)
.then(function (res) {
callback(null, {
options: res.body.map(function (tag) {
return {
label: tag.name,
value: tag.name
}
}),
complete: false
})
})
.catch(function (err) {
console.log(err)
})
}
module.exports = React.createClass({
mixins: [LinkedState, KeyCaster('codeForm')],
propTypes: {
planet: React.PropTypes.object,
close: React.PropTypes.func,
transitionTo: React.PropTypes.func,
code: React.PropTypes.object
},
getInitialState: function () {
var code = Object.assign({
description: '',
mode: '',
content: '',
Tags: []
}, this.props.code)
code.Tags = code.Tags.map(function (tag) {
return {
label: tag.name,
value: tag.name
}
})
return {
code: code
}
},
onKeyCast: function (e) {
switch (e.status) {
case 'submitCodeForm':
this.submit()
break
case 'closeModal':
this.props.close()
break
}
},
handleModeChange: function (selected) {
var code = this.state.code
code.mode = selected
this.setState({code: code})
},
handleTagsChange: function (selected, all) {
var code = this.state.code
code.Tags = all
this.setState({code: code})
},
handleContentChange: function (e, value) {
var code = this.state.code
code.content = value
this.setState({code: code})
},
submit: function () {
var planet = this.props.planet
var code = this.state.code
code.Tags = code.Tags.map(function (tag) {
return tag.value
})
if (this.props.code == null) {
Hq.createCode(planet.userName, planet.name, this.state.code)
.then(function (res) {
var code = res.body
PlanetStore.Actions.updateCode(code)
this.props.close()
this.props.transitionTo('codes', {userName: planet.userName, planetName: planet.name, localId: code.localId})
}.bind(this))
.catch(function (err) {
console.error(err)
})
} else {
Hq.updateCode(planet.userName, planet.name, this.props.code.localId, this.state.code)
.then(function (res) {
var code = res.body
PlanetStore.Actions.updateCode(code)
this.props.close()
}.bind(this))
}
},
handleKeyDown: function (e) {
if (e.keyCode === 13 && e.metaKey) {
this.submit()
e.stopPropagation()
}
},
render: function () {
var modeOptions = aceModes.map(function (mode) {
return {
label: mode,
value: mode
}
})
return (
<div className='CodeForm'>
<div className='modal-body'>
<div className='form-group'>
<textarea ref='description' className='codeDescription block-input' valueLink={this.linkState('code.description')} placeholder='Description'/>
</div>
<div className='form-group'>
<Select
name='mode'
className='modeSelect'
value={this.state.code.mode}
placeholder='Select Language'
options={modeOptions}
onChange={this.handleModeChange}/>
</div>
<div className='form-group'>
<CodeEditor onChange={this.handleContentChange} code={this.state.code.content} mode={this.state.code.mode}/>
</div>
<div className='form-group'>
<Select
name='Tags'
multi={true}
allowCreate={true}
value={this.state.code.Tags}
placeholder='Tags...'
asyncOptions={getOptions}
onChange={this.handleTagsChange}
/>
</div>
</div>
<div className='modal-footer'>
<div className='modal-control'>
<button onClick={this.props.close} className='btn-default'>Cancel</button>
<button onClick={this.submit} className='btn-primary'>{this.props.code == null ? 'Launch' : 'Relaunch'}</button>
</div>
</div>
</div>
)
}
})

View File

@@ -0,0 +1,53 @@
var React = require('react/addons')
var ace = window.ace
module.exports = React.createClass({
propTypes: {
code: React.PropTypes.string,
mode: React.PropTypes.string,
className: React.PropTypes.string
},
componentDidMount: function () {
var el = React.findDOMNode(this.refs.target)
var editor = ace.edit(el)
editor.$blockScrolling = Infinity
editor.setValue(this.props.code)
editor.renderer.setShowGutter(false)
editor.setReadOnly(true)
editor.setTheme('ace/theme/xcode')
editor.setHighlightActiveLine(false)
editor.clearSelection()
var session = editor.getSession()
if (this.props.mode != null && this.props.mode.length > 0) {
session.setMode('ace/mode/' + this.props.mode)
} else {
session.setMode('ace/mode/text')
}
session.setUseSoftTabs(true)
session.setOption('useWorker', false)
session.setUseWrapMode(true)
this.setState({editor: editor})
},
componentDidUpdate: function (prevProps) {
if (this.state.editor.getValue() !== this.props.code) {
this.state.editor.setValue(this.props.code)
this.state.editor.clearSelection()
}
if (prevProps.mode !== this.props.mode) {
var session = this.state.editor.getSession()
if (this.props.mode != null && this.props.mode.length > 0) {
session.setMode('ace/mode/' + this.props.mode)
} else {
session.setMode('ace/mode/text')
}
}
},
render: function () {
return (
<div ref='target' className={this.props.className}></div>
)
}
})

View File

@@ -0,0 +1,80 @@
var React = require('react')
var LinkedState = require('../Mixins/LinkedState')
var KeyCaster = require('../Mixins/KeyCaster')
var Hq = require('../Services/Hq')
module.exports = React.createClass({
mixins: [LinkedState, KeyCaster('contactModal')],
propTypes: {
close: React.PropTypes.func
},
getInitialState: function () {
return {
isSent: false,
mail: {
title: '',
content: ''
}
}
},
onKeyCast: function (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
case 'submitContactModal':
if (this.state.isSent) {
this.props.close()
return
}
this.sendEmail()
break
}
},
componentDidMount: function () {
React.findDOMNode(this.refs.title).focus()
},
sendEmail: function () {
Hq.sendEmail(this.state.mail)
.then(function (res) {
this.setState({isSent: !this.state.isSent})
}.bind(this))
.catch(function (err) {
console.error(err)
})
},
render: function () {
return (
<div className='ContactModal modal'>
<div className='modal-header'><h1>Contact form</h1></div>
{!this.state.isSent ? (
<div className='contactForm'>
<div className='modal-body'>
<div className='formField'>
<input ref='title' valueLink={this.linkState('mail.title')} placeholder='Title'/>
</div>
<div className='formField'>
<textarea valueLink={this.linkState('mail.content')} placeholder='Content'/>
</div>
</div>
<div className='modal-footer'>
<div className='formControl'>
<button onClick={this.sendEmail} className='sendButton'>Send</button>
<button onClick={this.props.close}>Cancel</button>
</div>
</div>
</div>
) : (
<div className='confirmation'>
<div className='confirmationMessage'>Thanks for sharing your opinion!</div>
<button className='doneButton' onClick={this.props.close}>Done</button>
</div>
)}
</div>
)
}
})

View File

@@ -0,0 +1,170 @@
/* global localStorage */
var React = require('react/addons')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var KeyCaster = require('../Mixins/KeyCaster')
var UserStore = require('../Stores/UserStore')
module.exports = React.createClass({
mixins: [LinkedState, KeyCaster('editProfileModal')],
propTypes: {
user: React.PropTypes.shape({
name: React.PropTypes.string,
profileName: React.PropTypes.string,
email: React.PropTypes.string
}),
close: React.PropTypes.func
},
getInitialState: function () {
var user = this.props.user
return {
currentTab: 'userInfo',
user: {
profileName: user.profileName,
email: user.email
},
userSubmitStatus: null,
password: {
currentPassword: '',
newPassword: '',
passwordConfirmation: ''
},
passwordSubmitStatus: null
}
},
onKeyCast: function (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
}
},
selectTab: function (tabName) {
return function () {
this.setState({currentTab: tabName})
}.bind(this)
},
saveUserInfo: function () {
this.setState({
userSubmitStatus: 'sending'
}, function () {
Hq.updateUser(this.props.user.name, this.state.user)
.then(function (res) {
this.setState({userSubmitStatus: 'done'}, function () {
localStorage.setItem('currentUser', JSON.stringify(res.body))
UserStore.Actions.update(res.body)
})
}.bind(this))
.catch(function (err) {
console.error(err)
this.setState({userSubmitStatus: 'error'})
}.bind(this))
})
},
savePassword: function () {
this.setState({
passwordSubmitStatus: 'sending'
}, function () {
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 sideNavModal modal'>
<div className='leftPane'>
<div className='modalLabel'>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 tab'>
<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 tab'>
<div className='formField'>
<label>Current password</label>
<input type='password' valueLink={this.linkState('password.currentPassword')}/>
</div>
<div className='formField'>
<label>New password</label>
<input type='password' valueLink={this.linkState('password.newPassword')}/>
</div>
<div className='formField'>
<label>Confirmation</label>
<input type='password' 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

@@ -0,0 +1,180 @@
/* global localStorage */
var React = require('react/addons')
var ReactRouter = require('react-router')
var Navigation = ReactRouter.Navigation
var State = ReactRouter.State
var Link = ReactRouter.Link
var Reflux = require('reflux')
var Modal = require('../Mixins/Modal')
var UserStore = require('../Stores/UserStore')
var PreferencesModal = require('./PreferencesModal')
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})
},
openPreferencesModal: function () {
this.openModal(PreferencesModal)
},
openPlanetCreateModal: function () {
this.openModal(PlanetCreateModal, {transitionTo: this.transitionTo})
},
toggleProfilePopup: function () {
this.openProfilePopup()
},
openProfilePopup: function () {
this.setState({isProfilePopupOpen: true}, function () {
document.addEventListener('click', this.closeProfilePopup)
})
},
closeProfilePopup: function () {
document.removeEventListener('click', this.closeProfilePopup)
this.setState({isProfilePopupOpen: false})
},
handleLogoutClick: function () {
this.openModal(LogoutModal, {transitionTo: this.transitionTo})
},
switchPlanetByIndex: function (index) {
var planetProps = this.refs.planets.props.children[index - 1].props
this.transitionTo('planet', {userName: planetProps.userName, planetName: planetProps.planetName})
},
render: function () {
var params = this.getParams()
if (this.state.currentUser == null) {
return (
<div className='HomeNavigator'>
</div>
)
}
var planets = (this.state.currentUser.Planets.concat(this.state.currentUser.Teams.reduce(function (planets, team) {
return team.Planets == null ? planets : planets.concat(team.Planets)
}, []))).map(function (planet, index) {
return (
<li userName={planet.userName} planetName={planet.name} key={planet.id} className={params.userName === planet.userName && params.planetName === planet.name ? 'active' : ''}>
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>
{planet.name[0]}
<div className='planetTooltip'>{planet.userName}/{planet.name}</div>
</Link>
{index < 9 ? (<div className='shortCut'>{index + 1}</div>) : null}
</li>
)
})
var popup = this.renderPopup()
return (
<div className='HomeNavigator'>
<button onClick={this.toggleProfilePopup} className='profileButton'>
<ProfileImage size='55' email={this.state.currentUser.email}/>
</button>
{popup}
<ul ref='planets' className='planetList'>
{planets}
</ul>
<button onClick={this.openPlanetCreateModal} className='newPlanet'>
<i className='fa fa-plus'/>
<div className='tooltip'>Create new planet</div>
</button>
</div>
)
},
renderPopup: function () {
var teams = this.state.currentUser.Teams == null ? [] : this.state.currentUser.Teams.map(function (team) {
return (
<li key={'user-' + team.id}>
<Link to='userHome' params={{userName: team.name}} className='userName'>{team.profileName} ({team.name})</Link>
</li>
)
})
return (
<div ref='profilePopup' className={'profilePopup' + (this.state.isProfilePopupOpen ? '' : ' close')}>
<div className='profileGroup'>
<div className='profileGroupLabel'>
<span>You</span>
</div>
<ul className='profileGroupList'>
<li>
<Link to='userHome' params={{userName: this.state.currentUser.name}} className='userName'>Profile ({this.state.currentUser.name})</Link>
</li>
</ul>
</div>
<div className='profileGroup'>
<div className='profileGroupLabel'>
<span>Team</span>
</div>
<ul className='profileGroupList'>
{teams}
<li>
<button onClick={this.openTeamCreateModal} className='createNewTeam'><i className='fa fa-plus-square-o'/> create new team</button>
</li>
</ul>
</div>
<ul className='controlGroup'>
<li>
<button onClick={this.openPreferencesModal}><i className='fa fa-gears fa-fw'/> Preferences</button>
</li>
<li>
<button onClick={this.handleLogoutClick}><i className='fa fa-sign-out fa-fw'/> Log out</button>
</li>
</ul>
</div>
)
}
})

View File

@@ -0,0 +1,79 @@
var React = require('react/addons')
var CodeForm = require('./CodeForm')
var NoteForm = require('./NoteForm')
module.exports = React.createClass({
propTypes: {
planet: React.PropTypes.object,
transitionTo: React.PropTypes.func,
close: React.PropTypes.func
},
getInitialState: function () {
return {
currentTab: 'code'
}
},
componentDidMount: function () {
var codeButton = React.findDOMNode(this.refs.codeButton)
codeButton.addEventListener('keydown', this.handleKeyDown)
React.findDOMNode(this.refs.noteButton).addEventListener('keydown', this.handleKeyDown)
codeButton.focus()
},
componentWillUnmount: function () {
React.findDOMNode(this.refs.codeButton).removeEventListener('keydown', this.handleKeyDown)
React.findDOMNode(this.refs.noteButton).removeEventListener('keydown', this.handleKeyDown)
},
handleKeyDown: function (e) {
if (e.keyCode === 37 && e.metaKey) {
this.selectCodeTab()
e.stopPropagation()
return
}
if (e.keyCode === 39 && e.metaKey) {
this.selectNoteTab()
e.stopPropagation()
return
}
if (e.keyCode === 9) {
if (this.state.currentTab === 'code') React.findDOMNode(this.refs.form.refs.description).focus()
else React.findDOMNode(this.refs.form.refs.title).focus()
e.preventDefault()
}
},
selectCodeTab: function () {
this.setState({currentTab: 'code'}, function () {
React.findDOMNode(this.refs.codeButton).focus()
})
},
selectNoteTab: function () {
this.setState({currentTab: 'note'}, function () {
React.findDOMNode(this.refs.noteButton).focus()
})
},
render: function () {
var modalBody
if (this.state.currentTab === 'code') {
modalBody = (
<CodeForm ref='form' planet={this.props.planet} transitionTo={this.props.transitionTo} close={this.props.close}/>
)
} else {
modalBody = (
<NoteForm ref='form' planet={this.props.planet} transitionTo={this.props.transitionTo} close={this.props.close}/>
)
}
return (
<div className='LaunchModal modal'>
<div className='modal-header'>
<div className='modal-tab'>
<button ref='codeButton' className={this.state.currentTab === 'code' ? 'btn-primary active' : 'btn-default'} onClick={this.selectCodeTab}>Code</button>
<button ref='noteButton' className={this.state.currentTab === 'note' ? 'btn-primary active' : 'btn-default'} onClick={this.selectNoteTab}>Note</button>
</div>
</div>
{modalBody}
</div>
)
}
})

View File

@@ -0,0 +1,40 @@
/* global localStorage */
var React = require('react')
var KeyCaster = require('../Mixins/KeyCaster')
module.exports = React.createClass({
mixins: [KeyCaster('logoutModal')],
propTypes: {
transitionTo: React.PropTypes.func,
close: React.PropTypes.func
},
onKeyCast: function (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
case 'submitLogoutModal':
this.logout()
break
}
},
logout: function () {
localStorage.removeItem('currentUser')
localStorage.removeItem('token')
this.props.transitionTo('login')
this.props.close()
},
render: function () {
return (
<div className='LogoutModal modal'>
<div className='messageLabel'>Are you sure to log out?</div>
<div className='formControl'>
<button onClick={this.props.close}>Cancel</button>
<button className='logoutButton' onClick={this.logout}>Log out</button>
</div>
</div>
)
}
})

View File

@@ -0,0 +1,43 @@
var React = require('react')
var Markdown = require('../Mixins/Markdown')
var ExternalLink = require('../Mixins/ExternalLink')
module.exports = React.createClass({
mixins: [Markdown, ExternalLink],
propTypes: {
className: React.PropTypes.string,
content: React.PropTypes.string
},
componentDidMount: function () {
this.addListener()
},
componentDidUpdate: function () {
this.addListener()
},
componentWillUnmount: function () {
this.removeListener()
},
componentWillUpdate: function () {
this.removeListener()
},
addListener: function () {
var anchors = React.findDOMNode(this).querySelectorAll('a')
for (var i = 0; i < anchors.length; i++) {
anchors[i].addEventListener('click', this.openExternal)
}
},
removeListener: function () {
var anchors = React.findDOMNode(this).querySelectorAll('a')
for (var i = 0; i < anchors.length; i++) {
anchors[i].removeEventListener('click', this.openExternal)
}
},
render: function () {
return (
<div className={'MarkdownPreview' + (this.props.className != null ? ' ' + this.props.className : '')} dangerouslySetInnerHTML={{__html: ' ' + this.markdown(this.props.content)}}/>
)
}
})

View File

@@ -0,0 +1,55 @@
var React = require('react')
var Hq = require('../Services/Hq')
var KeyCaster = require('../Mixins/KeyCaster')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
mixins: [KeyCaster('noteDeleteModal')],
propTypes: {
planet: React.PropTypes.object,
note: React.PropTypes.object,
close: React.PropTypes.func
},
onKeyCast: function (e) {
switch (e.status) {
case 'submitNoteDeleteModal':
this.submit()
break
case 'closeModal':
this.props.close()
break
}
},
submit: function () {
var planet = this.props.planet
Hq.destroyNote(planet.userName, planet.name, this.props.note.localId)
.then(function (res) {
PlanetStore.Actions.destroyNote(res.body)
this.props.close()
}.bind(this))
.catch(function (err) {
console.error(err)
})
},
render: function () {
return (
<div className='NoteDeleteModal modal'>
<div className='modal-header'>
<h1>Delete Note</h1>
</div>
<div className='modal-body'>
<p>Are you sure to delete it?</p>
</div>
<div className='modal-footer'>
<div className='modal-control'>
<button onClick={this.props.close} className='btn-default'>Cancel</button>
<button ref='submit' onClick={this.submit} className='btn-primary'>Delete</button>
</div>
</div>
</div>
)
}
})

View File

@@ -0,0 +1,27 @@
var React = require('react')
var NoteForm = require('./NoteForm')
module.exports = React.createClass({
propTypes: {
close: React.PropTypes.func,
note: React.PropTypes.object,
planet: React.PropTypes.object
},
componentDidMount: function () {
// TODO: Hacked!! should fix later
setTimeout(function () {
React.findDOMNode(this.refs.form.refs.title).focus()
}.bind(this), 1)
},
render: function () {
return (
<div className='NoteEditModal modal'>
<div className='modal-header'>
<h1>Edit Note</h1>
</div>
<NoteForm ref='form' note={this.props.note} planet={this.props.planet} close={this.props.close}/>
</div>
)
}
})

View File

@@ -0,0 +1,153 @@
var React = require('react/addons')
var Select = require('react-select')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var Markdown = require('../Mixins/Markdown')
var KeyCaster = require('../Mixins/KeyCaster')
var PlanetStore = require('../Stores/PlanetStore')
var CodeEditor = require('./CodeEditor')
var MarkdownPreview = require('./MarkdownPreview')
var getOptions = function (input, callback) {
Hq.searchTag(input)
.then(function (res) {
callback(null, {
options: res.body.map(function (tag) {
return {
label: tag.name,
value: tag.name
}
}),
complete: false
})
})
.catch(function (err) {
console.log(err)
})
}
var EDIT_MODE = 0
var PREVIEW_MODE = 1
module.exports = React.createClass({
mixins: [LinkedState, Markdown, KeyCaster('noteForm')],
propTypes: {
planet: React.PropTypes.object,
close: React.PropTypes.func,
transitionTo: React.PropTypes.func,
note: React.PropTypes.object
},
getInitialState: function () {
var note = Object.assign({
title: '',
content: '',
Tags: []
}, this.props.note)
note.Tags = note.Tags.map(function (tag) {
return {
label: tag.name,
value: tag.name
}
})
return {
note: note,
mode: EDIT_MODE
}
},
onKeyCast: function (e) {
switch (e.status) {
case 'submitNoteForm':
this.submit()
break
case 'closeModal':
this.props.close()
break
}
},
handleTagsChange: function (selected, all) {
var note = this.state.note
note.Tags = all
this.setState({note: note})
},
handleContentChange: function (e, value) {
var note = this.state.note
note.content = value
this.setState({note: note})
},
togglePreview: function () {
this.setState({mode: this.state.mode === EDIT_MODE ? PREVIEW_MODE : EDIT_MODE})
},
submit: function () {
var planet = this.props.planet
var note = this.state.note
note.Tags = note.Tags.map(function (tag) {
return tag.value
})
if (this.props.note == null) {
Hq.createNote(planet.userName, planet.name, this.state.note)
.then(function (res) {
var note = res.body
PlanetStore.Actions.updateNote(note)
this.props.close()
this.props.transitionTo('notes', {userName: planet.userName, planetName: planet.name, localId: note.localId})
}.bind(this))
.catch(function (err) {
console.error(err)
})
} else {
Hq.updateNote(planet.userName, planet.name, this.props.note.localId, this.state.note)
.then(function (res) {
var note = res.body
PlanetStore.Actions.updateNote(note)
this.props.close()
}.bind(this))
}
},
render: function () {
var content = this.state.mode === EDIT_MODE ? (
<div className='form-group'>
<CodeEditor onChange={this.handleContentChange} code={this.state.note.content} mode={'markdown'}/>
</div>
) : (
<div className='form-group relative'>
<div className='previewMode'>Preview mode</div>
<MarkdownPreview className='marked' content={this.state.note.content}/>
</div>
)
return (
<div className='NoteForm'>
<div className='modal-body'>
<div className='form-group'>
<input ref='title' className='block-input' valueLink={this.linkState('note.title')} placeholder='Title'/>
</div>
{content}
<div className='form-group'>
<Select
name='Tags'
multi={true}
allowCreate={true}
value={this.state.note.Tags}
placeholder='Tags...'
asyncOptions={getOptions}
onChange={this.handleTagsChange}
/>
</div>
</div>
<div className='modal-footer'>
<button onClick={this.togglePreview} className={'btn-default' + (this.state.mode === PREVIEW_MODE ? ' active' : '')}>Preview mode</button>
<div className='modal-control'>
<button onClick={this.props.close} className='btn-default'>Cancel</button>
<button onClick={this.submit} className='btn-primary'>Launch</button>
</div>
</div>
</div>
)
}
})

View File

@@ -0,0 +1,126 @@
var React = require('react/addons')
var moment = require('moment')
var CodeViewer = require('./CodeViewer')
var CodeEditModal = require('./CodeEditModal')
var CodeDeleteModal = require('./CodeDeleteModal')
var NoteEditModal = require('./NoteEditModal')
var NoteDeleteModal = require('./NoteDeleteModal')
var MarkdownPreview = require('./MarkdownPreview')
var ProfileImage = require('./ProfileImage')
var Modal = require('../Mixins/Modal')
var ForceUpdate = require('../Mixins/ForceUpdate')
module.exports = React.createClass({
mixins: [ForceUpdate(60000), Modal],
propTypes: {
article: React.PropTypes.object,
showOnlyWithTag: React.PropTypes.func,
planet: React.PropTypes.object
},
getInitialState: function () {
return {
isEditModalOpen: false
}
},
openEditModal: function () {
if (this.props.article == null) return
switch (this.props.article.type) {
case 'code' :
this.openModal(CodeEditModal, {code: this.props.article, planet: this.props.planet})
break
case 'note' :
this.openModal(NoteEditModal, {note: this.props.article, planet: this.props.planet})
}
},
openDeleteModal: function () {
if (this.props.article == null) return
switch (this.props.article.type) {
case 'code' :
this.openModal(CodeDeleteModal, {code: this.props.article, planet: this.props.planet})
break
case 'note' :
this.openModal(NoteDeleteModal, {note: this.props.article, planet: this.props.planet})
}
},
render: function () {
var article = this.props.article
if (article == null) {
return (
<div className='PlanetArticleDetail'>
Nothing selected
</div>
)
}
var tags = article.Tags.length > 0 ? article.Tags.map(function (tag) {
return (
<a onClick={this.props.showOnlyWithTag(tag.name)} key={tag.id}>#{tag.name}</a>
)
}.bind(this)) : (
<a className='noTag'>Not tagged yet</a>
)
if (article.type === 'code') {
return (
<div className='PlanetArticleDetail codeDetail'>
<div className='detailHeader'>
<div className='itemLeft'>
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
<i className='fa fa-code fa-fw'></i>
</div>
<div className='itemRight'>
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
<div className='description'>{article.description}</div>
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
</div>
<span className='itemControl'>
<button id='articleEditButton' onClick={this.openEditModal} className='editButton'>
<i className='fa fa-edit fa-fw'></i>
<div className='tooltip'>Edit</div>
</button>
<button onClick={this.openDeleteModal} className='deleteButton'>
<i className='fa fa-trash fa-fw'></i>
<div className='tooltip'>Delete</div>
</button>
</span>
</div>
<div className='detailBody'>
<CodeViewer className='content' code={article.content} mode={article.mode}/>
</div>
</div>
)
}
return (
<div className='PlanetArticleDetail noteDetail'>
<div className='detailHeader'>
<div className='itemLeft'>
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
<i className='fa fa-file-text-o fa-fw'></i>
</div>
<div className='itemRight'>
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
<div className='description'>{article.title}</div>
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
</div>
<span className='itemControl'>
<button id='articleEditButton' onClick={this.openEditModal} className='editButton'>
<i className='fa fa-edit fa-fw'></i>
<div className='tooltip'>Edit</div>
</button>
<button onClick={this.openDeleteModal} className='deleteButton'>
<i className='fa fa-trash fa-fw'></i>
<div className='tooltip'>Delete</div>
</button>
</span>
</div>
<div className='detailBody'>
<MarkdownPreview className='content' content={article.content}/>
</div>
</div>
)
}
})

View File

@@ -0,0 +1,102 @@
var React = require('react/addons')
var ReactRouter = require('react-router')
var moment = require('moment')
var ForceUpdate = require('../Mixins/ForceUpdate')
var Markdown = require('../Mixins/Markdown')
var ProfileImage = require('../Components/ProfileImage')
module.exports = React.createClass({
mixins: [ReactRouter.Navigation, ReactRouter.State, ForceUpdate(60000), Markdown],
propTypes: {
articles: React.PropTypes.array,
showOnlyWithTag: React.PropTypes.func
},
handleArticleClikck: function (article) {
if (article.type === 'code') {
return function (e) {
var params = this.getParams()
document.getElementById('articleEditButton').focus()
this.transitionTo('codes', {
userName: params.userName,
planetName: params.planetName,
localId: article.localId
})
}.bind(this)
}
if (article.type === 'note') {
return function (e) {
var params = this.getParams()
document.getElementById('articleEditButton').focus()
this.transitionTo('notes', {
userName: params.userName,
planetName: params.planetName,
localId: article.localId
})
}.bind(this)
}
},
render: function () {
var articles = this.props.articles.map(function (article) {
var tags = article.Tags.length > 0 ? article.Tags.map(function (tag) {
return (
<a onClick={this.props.showOnlyWithTag(tag.name)} key={tag.id}>#{tag.name}</a>
)
}.bind(this)) : (
<a className='noTag'>Not tagged yet</a>
)
var params = this.getParams()
var isActive = article.type === 'code' ? this.isActive('codes') && parseInt(params.localId, 10) === article.localId : this.isActive('notes') && parseInt(params.localId, 10) === article.localId
if (article.type === 'code') {
return (
<li onClick={this.handleArticleClikck(article)} key={'code-' + article.id}>
<div className={'articleItem' + (isActive ? ' active' : '')}>
<div className='itemLeft'>
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
<i className='fa fa-code fa-fw'></i>
</div>
<div className='itemRight'>
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
<div className='description'>{article.description}</div>
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
</div>
</div>
<div className='divider'></div>
</li>
)
}
return (
<li onClick={this.handleArticleClikck(article)} key={'note-' + article.id}>
<div className={'articleItem blueprintItem' + (isActive ? ' active' : '')}>
<div className='itemLeft'>
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
<i className='fa fa-file-text-o fa-fw'></i>
</div>
<div className='itemRight'>
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
<div className='description'>{article.title}</div>
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
</div>
</div>
<div className='divider'></div>
</li>
)
}.bind(this))
return (
<div className='PlanetArticleList'>
<ul ref='articles'>
{articles}
</ul>
</div>
)
}
})

View File

@@ -0,0 +1,111 @@
/* global localStorage */
var React = require('react/addons')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var KeyCaster = require('../Mixins/KeyCaster')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
mixins: [LinkedState, KeyCaster('planetCreateModal')],
propTypes: {
ownerName: React.PropTypes.string,
transitionTo: React.PropTypes.func,
close: React.PropTypes.func
},
getInitialState: function () {
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
var ownerName = this.props.ownerName != null ? this.props.ownerName : currentUser.name
return {
user: currentUser,
planet: {
name: '',
public: true
},
ownerName: ownerName,
error: null
}
},
componentDidMount: function () {
React.findDOMNode(this.refs.name).focus()
},
onKeyCast: function (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
case 'submitPlanetCreateModal':
this.handleSubmit()
break
}
},
handleSubmit: function () {
this.setState({error: null}, function () {
Hq.createPlanet(this.state.ownerName, this.state.planet)
.then(function (res) {
var planet = res.body
PlanetStore.Actions.update(planet)
if (this.props.transitionTo != null) {
this.props.transitionTo('planetHome', {userName: planet.userName, planetName: planet.name})
}
this.props.close()
}.bind(this))
.catch(function (err) {
console.error(err)
if (err.status == null) return this.setState({error: {message: 'Check your network connection'}})
switch (err.status) {
case 403:
this.setState({error: err.response.body})
break
case 422:
this.setState({error: {message: 'Planet name should be Alphanumeric with _, -'}})
break
case 409:
this.setState({error: {message: 'The entered name already in use'}})
break
default:
this.setState({error: {message: 'Unexpected error occured! please try again'}})
}
}.bind(this))
})
},
render: function () {
var teamOptions = this.state.user.Teams.map(function (team) {
return (
<option key={'user-' + team.id} value={team.name}>{team.profileName} ({team.name})</option>
)
})
return (
<div className='PlanetCreateModal modal'>
<input ref='name' valueLink={this.linkState('planet.name')} className='nameInput stripInput' placeholder='Crate new Planet'/>
<div className='formField'>
of
<select valueLink={this.linkState('ownerName')}>
<option value={this.state.user.name}>Me({this.state.user.name})</option>
{teamOptions}
</select>
as
<select valueLink={this.linkState('planet.public')}>
<option value={true}>Public</option>
<option value={false}>Private</option>
</select>
</div>
{this.state.error != null ? (<p className='errorAlert'>{this.state.error.message != null ? this.state.error.message : 'Error message undefined'}</p>) : null}
<button onClick={this.handleSubmit} className='submitButton'>
<i className='fa fa-check'/>
</button>
</div>
)
}
})

View File

@@ -0,0 +1,86 @@
var React = require('react/addons')
var ReactRouter = require('react-router')
var Link = ReactRouter.Link
var Modal = require('../Mixins/Modal')
var ExternalLink = require('../Mixins/ExternalLink')
var PlanetSettingModal = require('./PlanetSettingModal')
module.exports = React.createClass({
mixins: [ReactRouter.State, Modal, ExternalLink],
propTypes: {
search: React.PropTypes.string,
fetchPlanet: React.PropTypes.func,
onSearchChange: React.PropTypes.func,
currentPlanet: React.PropTypes.object
},
getInitialState: function () {
return {
search: ''
}
},
componentDidMount: function () {
var search = React.findDOMNode(this.refs.search)
search.addEventListener('keydown', this.handleSearchKeyDown)
},
componentWillUnmount: function () {
var search = React.findDOMNode(this.refs.search)
search.removeEventListener('keydown', this.handleSearchKeyDown)
},
handleSearchKeyDown: function (e) {
if (e.keyCode === 38 || e.keyCode === 40) {
var search = React.findDOMNode(this.refs.search)
search.blur()
e.preventDefault()
}
if (e.keyCode !== 27 && (e.keyCode !== 13 || !e.metaKey)) {
e.stopPropagation()
}
},
openPlanetSettingModal: function () {
this.openModal(PlanetSettingModal, {planet: this.props.currentPlanet})
},
refresh: function () {
this.props.fetchPlanet()
},
render: function () {
var currentPlanetName = this.props.currentPlanet.name
var currentUserName = this.props.currentPlanet.userName
return (
<div className='PlanetHeader'>
<div className='headerLabel'>
<Link to='userHome' params={{userName: currentUserName}} className='userName'>{currentUserName}</Link>
<span className='planetName'>{currentPlanetName}</span>
{this.props.currentPlanet.public ? null : (
<div className='private'>
<i className='fa fa-lock'/>
<div className='tooltip'>Private planet</div>
</div>
)}
<button onClick={this.openPlanetSettingModal} className='planetSettingButton'>
<i className='fa fa-chevron-down'></i>
<div className='tooltip'>Planet setting</div>
</button>
</div>
<div className='headerControl'>
<div className='searchInput'>
<i className='fa fa-search'/>
<input onChange={this.props.onSearchChange} value={this.props.search} ref='search' type='text' className='inline-input circleInput' placeholder='Search...'/>
</div>
<button onClick={this.refresh} className='refreshButton'>
<i className='fa fa-refresh'/>
<div className='tooltip'>Refresh planet</div>
</button>
<a onClick={this.openExternal} href='http://b00st.io' className='logo'>
<img width='44' height='44' src='resources/favicon-230x230.png'/>
<div className='tooltip'>Boost official page</div>
</a>
</div>
</div>
)
}
})

View File

@@ -0,0 +1,76 @@
var React = require('react/addons')
var ReactRouter = require('react-router')
var Navigation = ReactRouter.Navigation
var Modal = require('../Mixins/Modal')
var LaunchModal = require('../Components/LaunchModal')
module.exports = React.createClass({
mixins: [Modal, Navigation],
propTypes: {
planet: React.PropTypes.shape({
name: React.PropTypes.string,
Owner: React.PropTypes.shape({
id: React.PropTypes.number,
userType: React.PropTypes.string
})
}),
search: React.PropTypes.string,
toggleCodeFilter: React.PropTypes.func,
toggleNoteFilter: React.PropTypes.func,
currentUser: React.PropTypes.shape({
id: React.PropTypes.number,
userType: React.PropTypes.string,
Teams: React.PropTypes.array
})
},
getInitialState: function () {
return {
isLaunchModalOpen: false
}
},
openLaunchModal: function () {
this.openModal(LaunchModal, {planet: this.props.planet, transitionTo: this.transitionTo})
},
isMyPlanet: function () {
if (this.props.currentUser == null) return false
if (this.props.planet.Owner.userType === 'person' && this.props.planet.Owner.id !== this.props.currentUser.id) return false
if (this.props.planet.Owner.userType === 'team' && !this.props.currentUser.Teams.some(function (team) {
if (team.id === this.props.planet.Owner.id) return true
return false
}.bind(this))) return false
return true
},
render: function () {
var keywords = this.props.search.split(' ')
var usingCodeFilter = keywords.some(function (keyword) {
if (keyword === '$c') return true
return false
})
var usingNoteFilter = keywords.some(function (keyword) {
if (keyword === '$n') return true
return false
})
return (
<div className='PlanetNavigator'>
{this.isMyPlanet() ? (
<button onClick={this.openLaunchModal} className='launchButton btn-primary btn-block'>
<i className='fa fa-rocket fa-fw'/> Launch
</button>
) : null}
<nav className='articleFilters'>
<a className={usingCodeFilter && !usingNoteFilter ? 'active' : ''} onClick={this.props.toggleCodeFilter}>
<i className='fa fa-code fa-fw'/> Codes
</a>
<a className={!usingCodeFilter && usingNoteFilter ? 'active' : ''} onClick={this.props.toggleNoteFilter}>
<i className='fa fa-file-text-o fa-fw'/> Notes
</a>
</nav>
</div>
)
}
})

View File

@@ -0,0 +1,188 @@
var React = require('react/addons')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var KeyCaster = require('../Mixins/KeyCaster')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
mixins: [LinkedState, KeyCaster('planetSettingModal')],
propTypes: {
close: React.PropTypes.func,
planet: React.PropTypes.shape({
name: React.PropTypes.string,
public: React.PropTypes.bool,
userName: React.PropTypes.string
})
},
getInitialState: function () {
var deleteTextCandidates = [
'Confirm',
'Exterminatus',
'Avada Kedavra'
]
var random = Math.round(Math.random() * 10) % 10
var randomDeleteText = random > 1 ? deleteTextCandidates[0] : random === 1 ? deleteTextCandidates[1] : deleteTextCandidates[2]
return {
currentTab: 'profile',
planet: {
name: this.props.planet.name,
public: this.props.planet.public
},
randomDeleteText: randomDeleteText,
deleteConfirmation: ''
}
},
onKeyCast: function (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
}
},
activePlanetProfile: function () {
this.setState({currentTab: 'profile'})
},
activePlanetDelete: function () {
this.setState({currentTab: 'delete'})
},
handlePublicChange: function (value) {
return function () {
this.state.planet.public = value
this.setState({planet: this.state.planet})
}.bind(this)
},
handleSavePlanetProfile: function (e) {
var planet = this.props.planet
this.setState({profileFormStatus: 'sending', profileFormError: null}, function () {
Hq.updatePlanet(planet.userName, planet.name, this.state.planet)
.then(function (res) {
var planet = res.body
console.log(planet)
this.setState({profileFormStatus: 'done'})
PlanetStore.Actions.update(planet)
this.props.close()
}.bind(this))
.catch(function (err) {
console.error(err)
var newState = {
profileFormStatus: 'error'
}
if (err.status == null) {
newState.profileFormError = {message: 'Check your network connection'}
return this.setState(newState)
}
switch (err.status) {
case 403:
newState.profileFormError = err.response.body
this.setState(newState)
break
case 422:
newState.profileFormError = {message: 'Planet name should be Alphanumeric with _, -'}
this.setState(newState)
break
case 409:
newState.profileFormError = {message: 'The entered name already in use'}
this.setState(newState)
break
default:
newState.profileFormError = {message: 'Undefined error please try again'}
this.setState(newState)
}
}.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 sideNavModal modal'>
<div className='leftPane'>
<h1 className='modalLabel'>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 tab'>
<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.profileFormStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
<div className={'alertError' + (this.state.profileFormStatus === 'error' ? '' : ' hide')}>{this.state.profileFormError != null ? this.state.profileFormError.message : 'Unexpected error occured! please try again'}</div>
<div className={'alertSuccess' + (this.state.profileFormStatus === '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 tab'>
<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

@@ -0,0 +1,112 @@
var ipc = require('ipc')
var remote = require('remote')
var React = require('react/addons')
var LinkedState = require('../Mixins/LinkedState')
var ExternalLink = require('../Mixins/ExternalLink')
var KeyCaster = require('../Mixins/KeyCaster')
module.exports = React.createClass({
mixins: [LinkedState, ExternalLink, KeyCaster('aboutModal')],
propTypes: {
close: React.PropTypes.func
},
getInitialState: function () {
var keymap = remote.getGlobal('keymap')
console.log(keymap)
return {
currentTab: 'settings',
keymap: keymap
}
},
onKeyCast: function (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
}
},
activeSettings: function () {
this.setState({currentTab: 'settings'})
},
activeAbout: function () {
this.setState({currentTab: 'about'})
},
saveKeymap: function () {
ipc.send('hotkeyUpdated', JSON.stringify(this.state.keymap))
},
render: function () {
var content = this.state.currentTab === 'settings' ? this.renderSettingsTab() : this.renderAboutTab()
return (
<div className='PreferencesModal sideNavModal modal'>
<div className='leftPane'>
<h1 className='modalLabel'>Preferences</h1>
<nav className='tabList'>
<button onClick={this.activeSettings} className={this.state.currentTab === 'settings' ? 'active' : ''}><i className='fa fa-gear fa-fw'/> Settings</button>
<button onClick={this.activeAbout} className={this.state.currentTab === 'about' ? 'active' : ''}><i className='fa fa-info-circle fa-fw'/> About this app</button>
</nav>
</div>
<div className='rightPane'>
{content}
</div>
</div>
)
},
renderSettingsTab: function () {
return (
<div className='settingsTab tab'>
<div className='categoryLabel'>Hotkey</div>
<div className='formField'>
<label>Toggle finder</label>
<input valueLink={this.linkState('keymap.toggleFinder')}/>
</div>
<div className='formConfirm'>
<button onClick={this.saveKeymap}>Save</button>
</div>
<div className='example'>
<h3>Example</h3>
<ul>
<li><code>0</code> to <code>9</code></li>
<li><code>A</code> to <code>Z</code></li>
<li><code>F1</code> to <code>F24</code></li>
<li>Punctuations like <code>~</code>, <code>!</code>, <code>@</code>, <code>#</code>, <code>$</code>, etc.</li>
<li><code>Plus</code></li>
<li><code>Space</code></li>
<li><code>Backspace</code></li>
<li><code>Delete</code></li>
<li><code>Insert</code></li>
<li><code>Return</code> (or <code>Enter</code> as alias)</li>
<li><code>Up</code>, <code>Down</code>, <code>Left</code> and <code>Right</code></li>
<li><code>Home</code> and <code>End</code></li>
<li><code>PageUp</code> and <code>PageDown</code></li>
<li><code>Escape</code> (or <code>Esc</code> for short)</li>
<li><code>VolumeUp</code>, <code>VolumeDown</code> and <code>VolumeMute</code></li>
<li><code>MediaNextTrack</code>, <code>MediaPreviousTrack</code>, <code>MediaStop</code> and <code>MediaPlayPause</code></li>
</ul>
</div>
</div>
)
},
renderAboutTab: function () {
var version = global.version
return (
<div className='aboutTab tab'>
<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 onClick={this.openExternal} href='http://boostio.github.io/regulations.html'>Regulation <i className='fa fa-external-link'/></a></li>
<li><a onClick={this.openExternal} href='http://boostio.github.io/privacypolicies.html'>Private policy <i className='fa fa-external-link'/></a></li>
</ul>
</div>
</div>
)
}
})

View File

@@ -0,0 +1,15 @@
var React = require('react/addons')
var md5 = require('md5')
module.exports = React.createClass({
propTypes: {
email: React.PropTypes.string,
size: React.PropTypes.string,
className: React.PropTypes.string
},
render: function () {
return (
<img className={this.props.className} width={this.props.size} height={this.props.size} src={'http://www.gravatar.com/avatar/' + md5(this.props.email.trim().toLowerCase()) + '?s=' + this.props.size}/>
)
}
})

View File

@@ -0,0 +1,87 @@
/* global localStorage */
var React = require('react/addons')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var KeyCaster = require('../Mixins/KeyCaster')
var UserStore = require('../Stores/UserStore')
module.exports = React.createClass({
mixins: [LinkedState, KeyCaster('teamCreateModal')],
propTypes: {
user: React.PropTypes.shape({
name: React.PropTypes.string
}),
transitionTo: React.PropTypes.func,
close: React.PropTypes.func
},
getInitialState: function () {
return {
team: {
name: ''
},
error: null
}
},
componentDidMount: function () {
React.findDOMNode(this.refs.teamName).focus()
},
onKeyCast: function (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
case 'submitTeamCreateModal':
this.handleSubmit()
break
}
},
handleSubmit: function () {
this.setState({error: null}, function () {
Hq.createTeam(this.props.user.name, this.state.team)
.then(function (res) {
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
var team = res.body
currentUser.Teams.push(team)
localStorage.setItem('currentUser', JSON.stringify(currentUser))
UserStore.Actions.update(currentUser)
if (this.props.transitionTo != null) {
this.props.transitionTo('userHome', {userName: team.name})
}
this.props.close()
}.bind(this))
.catch(function (err) {
console.error(err)
if (err.status == null) return this.setState({error: {message: 'Check your network connection'}})
switch (err.status) {
case 422:
this.setState({error: {message: 'Team name should be Alphanumeric with _, -'}})
break
case 409:
this.setState({error: {message: 'The entered name already in use'}})
break
default:
this.setState({error: {message: 'Error message undefined'}})
}
}.bind(this))
})
},
render: function () {
return (
<div className='TeamCreateModal modal'>
<input ref='teamName' valueLink={this.linkState('team.name')} className='nameInput stripInput' placeholder='Create new team'/>
{this.state.error != null ? (<p className='errorAlert'>{this.state.error.message != null ? this.state.error.message : 'Unintended error occured'}</p>) : null}
<button onClick={this.handleSubmit} className='submitButton'>
<i className='fa fa-check'/>
</button>
</div>
)
}
})

View File

@@ -0,0 +1,282 @@
/* global localStorage */
var React = require('react/addons')
var Reflux = require('reflux')
var Select = require('react-select')
var Hq = require('../Services/Hq')
var LinkedState = require('../Mixins/LinkedState')
var Helper = require('../Mixins/Helper')
var KeyCaster = require('../Mixins/KeyCaster')
var UserStore = require('../Stores/UserStore')
var getOptions = function (input, callback) {
Hq.searchUser(input)
.then(function (res) {
callback(null, {
options: res.body.map(function (user) {
return {
label: user.name,
value: user.name
}
}),
complete: false
})
})
.catch(function (err) {
console.error(err)
})
}
module.exports = React.createClass({
mixins: [LinkedState, Reflux.listenTo(UserStore, 'onUserChange'), Helper, KeyCaster('teamSettingsModal')],
propTypes: {
team: React.PropTypes.shape({
id: React.PropTypes.number,
name: React.PropTypes.string,
profileName: React.PropTypes.string,
email: React.PropTypes.string,
Members: React.PropTypes.array
}),
close: React.PropTypes.func
},
getInitialState: function () {
var team = this.props.team
return {
currentTab: 'teamInfo',
team: {
profileName: team.profileName
},
userSubmitStatus: null,
member: {
name: '',
role: 'member'
},
updatingMember: false
}
},
onKeyCast: function (e) {
switch (e.status) {
case 'closeModal':
this.props.close()
break
}
},
onUserChange: function (res) {
var member
switch (res.status) {
case 'memberAdded':
member = res.data
if (member.TeamMember.TeamId === this.props.team.id) {
this.forceUpdate()
}
break
case 'memberRemoved':
member = res.data
if (member.TeamMember.TeamId === this.props.team.id) {
this.forceUpdate()
}
break
}
},
selectTab: function (tabName) {
return function () {
this.setState({currentTab: tabName})
}.bind(this)
},
saveUserInfo: function () {
this.setState({
userSubmitStatus: 'sending'
}, function () {
Hq.updateUser(this.props.team.name, this.state.team)
.then(function (res) {
this.setState({userSubmitStatus: 'done'}, function () {
UserStore.Actions.update(res.body)
this.forceUpdate()
})
}.bind(this))
.catch(function (err) {
console.error(err)
this.setState({userSubmitStatus: 'error'})
}.bind(this))
})
},
handleMemberNameChange: function (value) {
var member = this.state.member
member.name = value
this.setState({member: member})
},
addMember: function () {
this.setState({updatingMember: true}, function () {
Hq
.addMember(this.props.team.name, {
userName: this.state.member.name,
role: this.state.member.role
})
.then(function (res) {
UserStore.Actions.addMember(res.body)
this.setState({updatingMember: false})
}.bind(this))
.catch(function (err) {
console.error(err)
this.setState({updatingMember: false})
}.bind(this))
})
},
roleChange: function (memberName) {
return function (e) {
var role = e.target.value
this.setState({updatingMember: true}, function () {
Hq
.addMember(this.props.team.name, {
userName: memberName,
role: role
})
.then(function (res) {
UserStore.Actions.addMember(res.body)
this.setState({updatingMember: false})
}.bind(this))
.catch(function (err) {
console.error(err)
this.setState({updatingMember: false})
}.bind(this))
})
}.bind(this)
},
removeMember: function (memberName) {
return function () {
this.setState({updatingMember: true}, function () {
Hq
.removeMember(this.props.team.name, {
userName: memberName
})
.then(function (res) {
UserStore.Actions.removeMember(res.body)
this.setState({updatingMember: false})
}.bind(this))
.catch(function (err) {
console.error(err)
this.setState({updatingMember: false})
}.bind(this))
})
}.bind(this)
},
render: function () {
var content
switch (this.state.currentTab) {
case 'teamInfo':
content = this.renderTeamInfoTab()
break
case 'members':
content = this.renderMembersTab()
break
}
return (
<div className='TeamSettingsModal sideNavModal modal'>
<div className='leftPane'>
<div className='modalLabel'>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 tab'>
<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 tab'>
<table className='memberTable'>
<thead>
<tr>
<th>Username</th>
<th>Role</th>
<th>Control</th>
</tr>
</thead>
<tbody>
{members}
</tbody>
</table>
{belowLimit ? (
<div className='addMemberForm'>
<div className='formLabel'>Add Member</div>
<div className='formGroup'>
<Select
name='userName'
value={this.state.member.name}
placeholder='Username to add'
asyncOptions={getOptions}
onChange={this.handleMemberNameChange}
className='userNameSelect'
/>
<select valueLink={this.linkState('member.role')} className='roleSelect'>
<option value={'member'}>Member</option>
<option value={'owner'}>Owner</option>
</select>
<button disabled={this.state.updatingMember} onClick={this.addMember} className='confirmButton'>Add Member</button>
</div>
</div>
) : (
<div>
Maximum number of members is 5 on Beta version. Please contact us if you want futher use.
</div>
)}
</div>
)
}
})

View File

@@ -0,0 +1,41 @@
/* global localStorage */
var React = require('react/addons')
var ReactRouter = require('react-router')
var RouteHandler = ReactRouter.RouteHandler
var State = ReactRouter.State
var Navigation = ReactRouter.Navigation
var AuthFilter = require('../Mixins/AuthFilter')
var KeyCaster = require('../Mixins/KeyCaster')
var HomeNavigator = require('../Components/HomeNavigator')
module.exports = React.createClass({
mixins: [AuthFilter.OnlyUser, State, Navigation, KeyCaster('homeContainer')],
componentDidMount: function () {
if (this.isActive('homeEmpty')) {
var user = JSON.parse(localStorage.getItem('currentUser'))
if (user.Planets != null && user.Planets.length > 0) {
this.transitionTo('planet', {userName: user.name, planetName: user.Planets[0].name})
return
}
this.transitionTo('userHome', {userName: user.name})
}
},
onKeyCast: function (e) {
switch (e.status) {
case 'switchPlanet':
this.refs.navigator.switchPlanetByIndex(e.data)
break
}
},
render: function () {
return (
<div className='HomeContainer'>
<HomeNavigator ref='navigator'/>
<RouteHandler/>
</div>
)
}
})

View File

@@ -0,0 +1,106 @@
/* global localStorage */
var React = require('react/addons')
var ReactRouter = require('react-router')
var Link = ReactRouter.Link
var AuthFilter = require('../Mixins/AuthFilter')
var LinkedState = require('../Mixins/LinkedState')
var Hq = require('../Services/Hq')
module.exports = React.createClass({
mixins: [LinkedState, ReactRouter.Navigation, AuthFilter.OnlyGuest],
getInitialState: function () {
return {
user: {},
authenticationFailed: false,
connectionFailed: false,
isSending: false
}
},
onListen: function (res) {
if (res.status === 'failedToLogIn') {
if (res.data.status === 401) {
// Wrong E-mail or Password
this.setState({
authenticationFailed: true,
connectionFailed: false,
isSending: false
})
return
}
// Connection Failed or Whatever
this.setState({
authenticationFailed: false,
connectionFailed: true,
isSending: false
})
return
}
},
handleSubmit: function (e) {
this.setState({
authenticationFailed: false,
connectionFailed: false,
isSending: true
}, function () {
Hq.login(this.state.user)
.then(function (res) {
localStorage.setItem('token', res.body.token)
localStorage.setItem('currentUser', JSON.stringify(res.body.user))
this.transitionTo('userHome', {userName: res.body.user.name})
}.bind(this))
.catch(function (err) {
if (err.status === 401) {
this.setState({
authenticationFailed: true,
connectionFailed: false,
isSending: false
})
return
}
this.setState({
authenticationFailed: false,
connectionFailed: true,
isSending: false
})
}.bind(this))
})
e.preventDefault()
},
render: function () {
return (
<div className='LoginContainer'>
<img className='logo' src='resources/favicon-230x230.png'/>
<nav className='authNavigator text-center'><Link to='login'>Log In</Link> / <Link to='signup'>Sign Up</Link></nav>
<form onSubmit={this.handleSubmit}>
<div className='form-group'>
<input className='stripInput' valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
</div>
<div className='form-group'>
<input className='stripInput' valueLink={this.linkState('user.password')} onChange={this.handleChange} type='password' placeholder='Password'/>
</div>
{this.state.isSending ? (
<p className='alertInfo'>Logging in...</p>
) : null}
{this.state.connectionFailed ? (
<p className='alertError'>Please try again.</p>
) : null}
{this.state.authenticationFailed ? (
<p className='alertError'>Wrong E-mail or Password.</p>
) : null}
<div className='form-group'>
<button className='logInButton' type='submit'>Log In</button>
</div>
</form>
</div>
)
}
})

View File

@@ -0,0 +1,106 @@
/* 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')
}
}
Hq.getUser()
.then(function (res) {
var user = res.body
localStorage.setItem('currentUser', JSON.stringify(user))
UserStore.Actions.update(user)
user.Planets.forEach(function (planet) {
fetchPlanet(planet.userName, planet.name)
})
user.Teams.forEach(function (team) {
team.Planets.forEach(function (planet) {
fetchPlanet(planet.userName, planet.name)
})
})
})
.catch(function (err) {
if (err.status === 401) {
console.log('Not logged in yet')
localStorage.removeItem('currentUser')
this.transitionTo('login')
return
}
console.error(err)
}.bind(this))
},
updateApp: function () {
ipc.send('update-app', 'Deal with it.')
},
openContactModal: function () {
this.openModal(ContactModal)
},
render: function () {
return (
<div className='Main'>
{this.state.updateAvailable ? (
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
) : null}
<button onClick={this.openContactModal} className='contactButton'>
<i className='fa fa-paper-plane-o'/>
<div className='tooltip'>Contact us</div>
</button>
<RouteHandler/>
</div>
)
}
})

View File

@@ -0,0 +1,415 @@
/* global localStorage*/
'strict'
var React = require('react/addons')
var ReactRouter = require('react-router')
var Reflux = require('reflux')
var PlanetHeader = require('../Components/PlanetHeader')
var PlanetNavigator = require('../Components/PlanetNavigator')
var PlanetArticleList = require('../Components/PlanetArticleList')
var PlanetArticleDetail = require('../Components/PlanetArticleDetail')
var Hq = require('../Services/Hq')
var Modal = require('../Mixins/Modal')
var ArticleFilter = require('../Mixins/ArticleFilter')
var Helper = require('../Mixins/Helper')
var KeyCaster = require('../Mixins/KeyCaster')
var UserStore = require('../Stores/UserStore')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
mixins: [ReactRouter.Navigation, ReactRouter.State, Modal, Reflux.listenTo(UserStore, 'onUserChange'), Reflux.listenTo(PlanetStore, 'onPlanetChange'), ArticleFilter, Helper, KeyCaster('planetContainer')],
propTypes: {
params: React.PropTypes.object,
planetName: React.PropTypes.string
},
getInitialState: function () {
return {
currentUser: JSON.parse(localStorage.getItem('currentUser')),
planet: null,
search: ''
}
},
componentDidMount: function () {
this.fetchPlanet(this.props.params.userName, this.props.params.planetName)
},
componentDidUpdate: function () {
if (this.isActive('planetHome') && this.refs.list != null && this.refs.list.props.articles.length > 0) {
var article = this.refs.list.props.articles[0]
var planet = this.state.planet
switch (article.type) {
case 'code':
this.transitionTo('codes', {userName: planet.userName, planetName: planet.name, localId: article.localId})
break
case 'note':
this.transitionTo('notes', {userName: planet.userName, planetName: planet.name, localId: article.localId})
break
}
}
},
componentWillReceiveProps: function (nextProps) {
if (this.state.planet == null) {
this.fetchPlanet(nextProps.params.userName, nextProps.params.planetName)
return
}
if (nextProps.params.userName !== this.state.planet.userName || nextProps.params.planetName !== this.state.planet.name) {
this.setState({
planet: null
}, function () {
this.fetchPlanet(nextProps.params.userName, nextProps.params.planetName)
})
}
},
onKeyCast: function (e) {
switch (e.status) {
case 'openLaunchModal':
this.refs.navigator.openLaunchModal()
break
case 'selectNextArticle':
this.selectNextArticle()
break
case 'selectPriorArticle':
this.selectPriorArticle()
break
case 'toggleFocusSearchInput':
this.toggleFocusSearchInput()
break
case 'openEditModal':
this.refs.detail.openEditModal()
break
case 'openDeleteModal':
this.refs.detail.openDeleteModal()
break
}
},
onPlanetChange: function (res) {
if (this.state.planet == null) return
var planet, code, note, articleIndex, articlesCount
switch (res.status) {
case 'updated':
planet = res.data
if (this.state.planet.id === planet.id) {
if (this.state.planet.name === planet.name) {
this.setState({planet: planet})
} else {
this.transitionTo('planetHome', {userName: planet.userName, planetName: planet.name})
}
}
break
case 'destroyed':
planet = res.data
if (this.state.planet.id === planet.id) {
this.transitionTo('userHome', {userName: this.state.planet.userName})
}
break
case 'codeUpdated':
code = res.data
if (code.PlanetId === this.state.planet.id) {
this.state.planet.Codes = this.updateItemToTargetArray(code, this.state.planet.Codes)
this.setState({planet: this.state.planet})
}
break
case 'noteUpdated':
note = res.data
if (note.PlanetId === this.state.planet.id) {
this.state.planet.Notes = this.updateItemToTargetArray(note, this.state.planet.Notes)
this.setState({planet: this.state.planet})
}
break
case 'codeDestroyed':
code = res.data
if (code.PlanetId === this.state.planet.id) {
this.state.planet.Codes = this.deleteItemFromTargetArray(code, this.state.planet.Codes)
if (this.refs.detail.props.article != null && this.refs.detail.props.article.type === code.type && this.refs.detail.props.article.localId === code.localId) {
articleIndex = this.getFilteredIndexOfCurrentArticle()
articlesCount = this.refs.list.props.articles.length
this.setState({planet: this.state.planet}, function () {
if (articlesCount > 1) {
if (articleIndex > 0) {
this.selectArticleByListIndex(articleIndex - 1)
} else {
this.selectArticleByListIndex(articleIndex)
}
}
})
return
}
this.setState({planet: this.state.planet})
}
break
case 'noteDestroyed':
note = res.data
if (note.PlanetId === this.state.planet.id) {
this.state.planet.Notes = this.deleteItemFromTargetArray(note, this.state.planet.Notes)
if (this.refs.detail.props.article != null && this.refs.detail.props.article.type === note.type && this.refs.detail.props.article.localId === note.localId) {
articleIndex = this.getFilteredIndexOfCurrentArticle()
articlesCount = this.refs.list.props.articles.length
this.setState({planet: this.state.planet}, function () {
if (articlesCount > 1) {
if (articleIndex > 0) {
this.selectArticleByListIndex(articleIndex - 1)
} else {
this.selectArticleByListIndex(articleIndex)
}
}
})
return
}
this.setState({planet: this.state.planet})
}
break
}
},
onUserChange: function () {
},
fetchPlanet: function (userName, planetName) {
if (userName == null) userName = this.props.params.userName
if (planetName == null) planetName = this.props.params.planetName
Hq.fetchPlanet(userName, planetName)
.then(function (res) {
var planet = res.body
planet.Codes.forEach(function (code) {
code.type = 'code'
})
planet.Notes.forEach(function (note) {
note.type = 'note'
})
localStorage.setItem('planet-' + planet.id, JSON.stringify(planet))
this.setState({planet: planet})
}.bind(this))
.catch(function (err) {
console.error(err)
})
},
getFilteredIndexOfCurrentArticle: function () {
var params = this.props.params
var index = 0
if (this.isActive('codes')) {
this.refs.list.props.articles.some(function (_article, _index) {
if (_article.type === 'code' && _article.localId === parseInt(params.localId, 10)) {
index = _index
}
})
} else if (this.isActive('notes')) {
this.refs.list.props.articles.some(function (_article, _index) {
if (_article.type === 'note' && _article.localId === parseInt(params.localId, 10)) {
index = _index
return true
}
return false
})
}
return index
},
selectArticleByListIndex: function (index) {
var article = this.refs.list.props.articles[index]
var params = this.props.params
if (article == null) {
this.transitionTo('planetHome', params)
return
}
var listElement = this.refs.list.refs.articles.getDOMNode()
var articleElement = listElement.querySelectorAll('li')[index]
var overflowBelow = listElement.clientHeight + listElement.scrollTop < articleElement.offsetTop + articleElement.clientHeight
if (overflowBelow) {
listElement.scrollTop = articleElement.offsetTop + articleElement.clientHeight - listElement.clientHeight
}
var overflowAbove = listElement.scrollTop > articleElement.offsetTop
if (overflowAbove) {
listElement.scrollTop = articleElement.offsetTop
}
if (article.type === 'code') {
params.localId = article.localId
this.transitionTo('codes', params)
return
}
if (article.type === 'note') {
params.localId = article.localId
this.transitionTo('notes', params)
return
}
},
selectNextArticle: function () {
if (this.state.planet == null) return
var index = this.getFilteredIndexOfCurrentArticle()
if (index < this.refs.list.props.articles.length - 1) {
this.selectArticleByListIndex(index + 1)
}
},
selectPriorArticle: function () {
if (this.state.planet == null) {
return
}
var index = this.getFilteredIndexOfCurrentArticle()
if (index > 0) {
this.selectArticleByListIndex(index - 1)
} else {
React.findDOMNode(this.refs.header.refs.search).focus()
}
},
toggleFocusSearchInput: function () {
var search = React.findDOMNode(this.refs.header.refs.search)
if (document.activeElement === search) {
React.findDOMNode(this.refs.header.refs.search).blur()
return
}
React.findDOMNode(this.refs.header.refs.search).focus()
},
handleSearchChange: function (e) {
this.setState({search: e.target.value}, function () {
this.selectArticleByListIndex(0)
})
},
showAll: function () {
this.setState({search: ''})
},
toggleCodeFilter: function () {
var keywords = typeof this.state.search === 'string' ? this.state.search.split(' ') : []
var usingCodeFilter = false
var usingNoteFilter = false
keywords = keywords.filter(function (keyword) {
if (keyword === '$n') {
usingNoteFilter = true
return false
}
if (keyword === '$c') usingCodeFilter = true
return true
})
if (usingCodeFilter && !usingNoteFilter) {
keywords = keywords.filter(function (keyword) {
return keyword !== '$c'
})
}
if (!usingCodeFilter) {
keywords.unshift('$c')
}
this.setState({search: keywords.join(' ')}, function () {
this.selectArticleByListIndex(0)
})
},
toggleNoteFilter: function () {
var keywords = typeof this.state.search === 'string' ? this.state.search.split(' ') : []
var usingCodeFilter = false
var usingNoteFilter = false
keywords = keywords.filter(function (keyword) {
if (keyword === '$c') {
usingCodeFilter = true
return false
}
if (keyword === '$n') usingNoteFilter = true
return true
})
if (usingNoteFilter && !usingCodeFilter) {
keywords = keywords.filter(function (keyword) {
return keyword !== '$n'
})
}
if (!usingNoteFilter) {
keywords.unshift('$n')
}
this.setState({search: keywords.join(' ')}, function () {
this.selectArticleByListIndex(0)
})
},
applyTagFilter: function (tag) {
return function () {
this.setState({search: '#' + tag})
}.bind(this)
},
render: function () {
if (this.state.planet == null) return (<div/>)
var localId = parseInt(this.props.params.localId, 10)
var codes = this.state.planet.Codes
var notes = this.state.planet.Notes
var article
if (this.isActive('codes')) {
codes.some(function (_article) {
if (localId === _article.localId) {
article = _article
return true
}
return false
})
} else if (this.isActive('notes')) {
notes.some(function (_article) {
if (localId === _article.localId) {
article = _article
return true
}
return false
})
}
var articles = codes.concat(notes)
var filteredArticles = this.searchArticle(this.state.search, articles)
return (
<div className='PlanetContainer'>
<PlanetHeader
ref='header'
search={this.state.search}
fetchPlanet={this.fetchPlanet}
onSearchChange={this.handleSearchChange}
currentPlanet={this.state.planet}
/>
<PlanetNavigator
ref='navigator'
search={this.state.search}
showAll={this.showAll}
toggleCodeFilter={this.toggleCodeFilter}
toggleNoteFilter={this.toggleNoteFilter}
planet={this.state.planet}
currentUser={this.state.currentUser}/>
<PlanetArticleList showOnlyWithTag={this.applyTagFilter} ref='list' articles={filteredArticles}/>
<PlanetArticleDetail
ref='detail'
article={article}
planet={this.state.planet}
showOnlyWithTag={this.applyTagFilter}/>
</div>
)
}
})

View File

@@ -0,0 +1,137 @@
/* global localStorage */
var React = require('react/addons')
var ReactRouter = require('react-router')
var Link = ReactRouter.Link
var AuthFilter = require('../Mixins/AuthFilter')
var LinkedState = require('../Mixins/LinkedState')
var ExternalLink = require('../Mixins/ExternalLink')
var Hq = require('../Services/Hq')
module.exports = React.createClass({
mixins: [LinkedState, ReactRouter.Navigation, AuthFilter.OnlyGuest, ExternalLink],
getInitialState: function () {
return {
user: {},
connectionFailed: false,
emailConflicted: false,
nameConflicted: false,
validationFailed: false,
isSending: false
}
},
handleSubmit: function (e) {
this.setState({
connectionFailed: false,
emailConflicted: false,
nameConflicted: false,
validationFailed: false,
isSending: true
}, function () {
Hq.signup(this.state.user)
.then(function (res) {
localStorage.setItem('token', res.body.token)
localStorage.setItem('currentUser', JSON.stringify(res.body.user))
this.transitionTo('userHome', {userName: res.body.user.name})
}.bind(this))
.catch(function (err) {
console.error(err)
var res = err.response
if (err.status === 409) {
// Confliction
var emailConflicted = res.body.errors[0].path === 'email'
var nameConflicted = res.body.errors[0].path === 'name'
this.setState({
connectionFailed: false,
emailConflicted: emailConflicted,
nameConflicted: nameConflicted,
validationFailed: false,
isSending: false
})
return
}
if (err.status === 422) {
// Validation Failed
this.setState({
connectionFailed: false,
emailConflicted: false,
nameConflicted: false,
validationFailed: {
errors: res.body.errors.map(function (error) {
return error.path
})
},
isSending: false
})
return
}
// Connection Failed or Whatever
this.setState({
connectionFailed: true,
emailConflicted: false,
nameConflicted: false,
validationFailed: false,
isSending: false
})
return
}.bind(this))
})
e.preventDefault()
},
render: function () {
return (
<div className='SignupContainer'>
<img className='logo' src='resources/favicon-230x230.png'/>
<nav className='authNavigator text-center'><Link to='login'>Log In</Link> / <Link to='signup'>Sign Up</Link></nav>
<form onSubmit={this.handleSubmit}>
<div className='form-group'>
<input className='stripInput' valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
</div>
<div className='form-group'>
<input className='stripInput' valueLink={this.linkState('user.password')} type='password' placeholder='Password'/>
</div>
<div className='form-group'>
<input className='stripInput' valueLink={this.linkState('user.name')} type='text' placeholder='name'/>
</div>
<div className='form-group'>
<input className='stripInput' valueLink={this.linkState('user.profileName')} type='text' placeholder='Profile name'/>
</div>
{this.state.isSending ? (
<p className='alertInfo'>Signing up...</p>
) : null}
{this.state.connectionFailed ? (
<p className='alertError'>Please try again.</p>
) : null}
{this.state.emailConflicted ? (
<p className='alertError'>E-mail already exists.</p>
) : null}
{this.state.nameConflicted ? (
<p className='alertError'>Username already exists.</p>
) : null}
{this.state.validationFailed ? (
<p className='alertError'>Please fill every field correctly: {this.state.validationFailed.errors.join(', ')}</p>
) : null}
<div className='form-group'>
<button className='logInButton' type='submit'>Sign Up</button>
</div>
</form>
<p className='alert'>会員登録することで<a onClick={this.openExternal} href='http://boostio.github.io/regulations.html'>当サイトの利用規約</a>及び<a onClick={this.openExternal} href='http://boostio.github.io/privacypolicies.html'>Cookieの使用を含むデータに関するポリシー</a>に同意するものとします</p>
</div>
)
}
})

View File

@@ -0,0 +1,367 @@
/* global localStorage */
var React = require('react/addons')
var ReactRouter = require('react-router')
var Navigation = ReactRouter.Navigation
var State = ReactRouter.State
var RouteHandler = ReactRouter.RouteHandler
var Link = ReactRouter.Link
var Reflux = require('reflux')
var LinkedState = require('../Mixins/LinkedState')
var Modal = require('../Mixins/Modal')
var Helper = require('../Mixins/Helper')
var Hq = require('../Services/Hq')
var ProfileImage = require('../Components/ProfileImage')
var EditProfileModal = require('../Components/EditProfileModal')
var TeamSettingsModal = require('../Components/TeamSettingsModal')
var PlanetCreateModal = require('../Components/PlanetCreateModal')
var AddMemberModal = require('../Components/AddMemberModal')
var TeamCreateModal = require('../Components/TeamCreateModal')
var UserStore = require('../Stores/UserStore')
var PlanetStore = require('../Stores/PlanetStore')
module.exports = React.createClass({
mixins: [LinkedState, State, Navigation, Modal, Reflux.listenTo(UserStore, 'onUserChange'), Reflux.listenTo(PlanetStore, 'onPlanetChange'), Helper],
propTypes: {
params: React.PropTypes.shape({
userName: React.PropTypes.string,
planetName: React.PropTypes.string
})
},
getInitialState: function () {
return {
user: null
}
},
componentDidMount: function () {
this.fetchUser()
},
componentWillReceiveProps: function (nextProps) {
if (this.state.user == null) {
this.fetchUser(nextProps.params.userName)
return
}
if (nextProps.params.userName !== this.state.user.name) {
this.setState({
user: null
}, function () {
this.fetchUser(nextProps.params.userName)
})
}
},
onUserChange: function (res) {
if (this.state.user == null) return
var member
switch (res.status) {
case 'userUpdated':
if (this.state.user.id === res.data.id) {
this.setState({user: res.data})
}
break
case 'memberAdded':
member = res.data
if (this.state.user.userType === 'team' && member.TeamMember.TeamId === this.state.user.id) {
this.state.user.Members = this.updateItemToTargetArray(member, this.state.user.Members)
this.setState({user: this.state.user})
}
break
case 'memberRemoved':
member = res.data
if (this.state.user.userType === 'team' && member.TeamMember.TeamId === this.state.user.id) {
this.state.user.Members = this.deleteItemFromTargetArray(member, this.state.user.Members)
this.setState({user: this.state.user})
}
break
}
},
onPlanetChange: function (res) {
if (this.state.user == null) return
var currentUser, planet, isOwner, team
switch (res.status) {
case 'updated':
// if state.user is currentUser, planet will be fetched by UserStore
currentUser = JSON.parse(localStorage.getItem('currentUser'))
if (currentUser.id === this.state.user.id) return
planet = res.data
isOwner = planet.Owner.id === this.state.user.id
if (isOwner) {
this.state.user.Planets = this.updateItemToTargetArray(planet, this.state.user.Planets)
this.setState({user: this.state.user})
return
}
// check if team of user has this planet
team = null
this.state.user.userType !== 'team' && this.state.user.Teams.some(function (_team) {
if (planet.Owner.id === _team.id) {
team = _team
return true
}
return false
})
if (team != null) {
team.Planets = this.updateItemToTargetArray(planet, team.Planets)
this.setState({user: this.state.user})
return
}
break
case 'destroyed':
// if state.user is currentUser, planet will be fetched by UserStore
currentUser = JSON.parse(localStorage.getItem('currentUser'))
if (currentUser.id === this.state.user.id) return
planet = res.data
isOwner = planet.Owner.id === this.state.user.id
if (isOwner) {
this.state.user.Planets = this.deleteItemFromTargetArray(planet, this.state.user.Planets)
this.setState({user: this.state.user})
return
}
// check if team of user has this planet
team = null
this.state.user.userType !== 'team' && this.state.user.Teams.some(function (_team) {
if (planet.Owner.id === _team.id) {
team = _team
return true
}
return false
})
if (team != null) {
team.Planets = this.deleteItemFromTargetArray(planet, team.Planets)
this.setState({user: this.state.user})
return
}
break
}
},
fetchUser: function (userName) {
if (userName == null) userName = this.props.params.userName
Hq.fetchUser(userName)
.then(function (res) {
this.setState({user: res.body})
}.bind(this))
.catch(function (err) {
console.error(err)
})
},
openEditProfileModal: function () {
this.openModal(EditProfileModal, {user: this.state.user})
},
openTeamSettingsModal: function () {
this.openModal(TeamSettingsModal, {team: this.state.user})
},
openAddUserModal: function () {
this.openModal(AddMemberModal, {team: this.state.user})
},
openTeamCreateModal: function () {
this.openModal(TeamCreateModal, {user: this.state.user})
},
openPlanetCreateModalWithOwnerName: function (name) {
return function () {
this.openModal(PlanetCreateModal, {ownerName: name})
}.bind(this)
},
render: function () {
var user = this.state.user
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
if (this.isActive('userHome')) {
if (user == null) {
return (
<div className='UserContainer'>
User Loading...
</div>
)
} else if (user.userType === 'team') {
return this.renderTeamHome(currentUser)
} else {
return this.renderUserHome(currentUser)
}
} else if (this.isActive('planet') && user != null && user.userType === 'team') {
var members = user.Members.map(function (member) {
return (
<li key={'user-' + member.id}><Link to='userHome' params={{userName: member.name}}>
<ProfileImage className='memberImage' size='22' email={member.email}/>
<div className='memberInfo'>
<div className='memberProfileName'>{member.profileName}</div>
<div className='memberName'>@{member.name}</div>
</div>
</Link></li>
)
})
return (
<div className='UserContainer'>
<RouteHandler/>
<div className='memberPopup'>
<div className='label'>Members</div>
<ul className='members'>
{members}
</ul>
</div>
</div>
)
} else {
return (
<div className='UserContainer'>
<RouteHandler/>
</div>
)
}
},
renderTeamHome: function (currentUser) {
var user = this.state.user
var isOwner = user.Members == null ? false : user.Members.some(function (member) {
return member.id === currentUser.id && member.TeamMember.role === 'owner'
})
var userPlanets = user.Planets.map(function (planet) {
return (
<li key={'planet-' + planet.id}>
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>{planet.userName}/{planet.name}</Link>
&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>
{isOwner ? (<button onClick={this.openTeamSettingsModal} className='editProfileButton'>Team settings</button>) : null}
</div>
<div className='memberList'>
<div className='memberLabel'>{members.length} {members.length > 1 ? 'Members' : 'Member'}</div>
<ul className='members'>
{members}
{isOwner ? (<li><button onClick={this.openAddUserModal} className='addMemberButton'><i className='fa fa-plus-square-o'/> add Member</button></li>) : null}
</ul>
</div>
<div className='planetList'>
<div className='planetLabel'>{userPlanets.length} {userPlanets.length > 0 ? 'Planets' : 'Planet'}</div>
<div className='planetGroup'>
<ul className='planets'>
{userPlanets}
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(user.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
</ul>
</div>
</div>
</div>
)
},
renderUserHome: function (currentUser) {
var user = this.state.user
var isOwner = currentUser.id === user.id
var userPlanets = user.Planets.map(function (planet) {
return (
<li key={'planet-' + planet.id}>
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>{planet.userName}/{planet.name}</Link>
&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.profileName} <small>@{team.name}</small></div>
<ul className='planets'>
{planets}
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(team.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
</ul>
</div>
)
}.bind(this))
var planetCount = userPlanets.length + user.Teams.reduce(function (sum, team) {
return sum + (team.Planets != null ? team.Planets.length : 0)
}, 0)
return (
<div className='UserContainer'>
<div className='userProfile'>
<ProfileImage className='userPhoto' size='75' email={user.email}/>
<div className='userInfo'>
<div className='userProfileName'>{user.profileName}</div>
<div className='userName'>{user.name}</div>
</div>
{isOwner ? (
<button onClick={this.openEditProfileModal} className='editProfileButton'>Edit profile</button>) : null}
</div>
<div className='teamList'>
<div className='teamLabel'>{teams.length} {teams.length > 1 ? 'Teams' : 'Team'}</div>
<ul className='teams'>
{teams}
{isOwner ? (<li><button onClick={this.openTeamCreateModal} className='createTeamButton'><i className='fa fa-plus-square-o'/> Create new team</button></li>) : null}
</ul>
</div>
<div className='planetList'>
<div className='planetLabel'>{planetCount} {planetCount > 1 ? 'Planets' : 'Planet'}</div>
<div className='planetGroup'>
<div className='planetGroupLabel'>{user.profileName} <small>@{user.name}</small></div>
<ul className='planets'>
{userPlanets}
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(user.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
</ul>
</div>
{teamPlanets}
</div>
</div>
)
}
})

View File

@@ -0,0 +1,67 @@
function basicFilter (keyword, articles) {
if (keyword === '' || keyword == null) return articles
var firstFiltered = articles.filter(function (article) {
var first = article.type === 'code' ? article.description : article.title
if (first.match(new RegExp(keyword, 'i'))) return true
return false
})
var secondFiltered = articles.filter(function (article) {
var second = article.type === 'code' ? article.content : article.content
if (second.match(new RegExp(keyword, 'i'))) return true
return false
})
return firstFiltered.concat(secondFiltered).filter(function (value, index, self) {
return self.indexOf(value) === index
})
}
function codeFilter (articles) {
return articles.filter(function (article) {
return article.type === 'code'
})
}
function noteFilter (articles) {
return articles.filter(function (article) {
return article.type === 'note'
})
}
function tagFilter (keyword, articles) {
return articles.filter(function (article) {
return article.Tags.some(function (tag) {
return tag.name.match(new RegExp('^' + keyword, 'i'))
})
})
}
function searchArticle (search, articles) {
var keywords = search.split(' ')
for (var keyword of keywords) {
if (keyword.match(/^\$c/, 'i')) {
articles = codeFilter(articles)
continue
} else if (keyword.match(/^\$n/, 'i')) {
articles = noteFilter(articles)
continue
} else if (keyword.match(/^#[A-Za-z0-9]+/)) {
articles = tagFilter(keyword.substring(1, keyword.length), articles)
continue
}
articles = basicFilter(keyword, articles)
}
return articles.sort(function (a, b) {
return new Date(b.updatedAt) - new Date(a.updatedAt)
})
}
module.exports = {
searchArticle: searchArticle
}

View File

@@ -0,0 +1,27 @@
/* global localStorage*/
var mixin = {}
mixin.OnlyGuest = {
componentDidMount: function () {
var currentUser = localStorage.getItem('currentUser')
if (currentUser == null) {
return
}
this.transitionTo('userHome', {userName: currentUser.name})
}
}
mixin.OnlyUser = {
componentDidMount: function () {
var currentUser = localStorage.getItem('currentUser')
if (currentUser == null) {
this.transitionTo('login')
return
}
}
}
module.exports = mixin

View File

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

View File

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

View File

@@ -0,0 +1,33 @@
function deleteItemFromTargetArray (item, targetArray) {
if (targetArray == null) targetArray = []
targetArray.some(function (_item, index) {
if (_item.id === item.id) {
targetArray.splice(index, 1)
return true
}
return false
})
return targetArray
}
function updateItemToTargetArray (item, targetArray) {
if (targetArray == null) targetArray = []
var isNew = !targetArray.some(function (_item, index) {
if (_item.id === item.id) {
targetArray.splice(index, 1, item)
return true
}
return false
})
if (isNew) targetArray.push(item)
return targetArray
}
module.exports = {
deleteItemFromTargetArray: deleteItemFromTargetArray,
updateItemToTargetArray: updateItemToTargetArray
}

View File

@@ -0,0 +1,100 @@
var Reflux = require('reflux')
var state = {
}
var keyDown = Reflux.createAction()
var KeyStore = Reflux.createStore({
init: function () {
this.listenTo(keyDown, this.onKeyDown)
document.addEventListener('keydown', function (e) {
keyDown(e)
})
},
setState: function (newState, cb) {
for (var key in newState) {
state[key] = newState[key]
}
if (typeof cb === 'function') cb()
},
onKeyDown: function (e) {
/*
Modals
*/
if (state.codeForm || state.noteForm || state.noteDeleteModal || state.codeDeleteModal || state.addMemberModal || state.aboutModal || state.editProfileModal || state.contactModal || state.teamCreateModal || state.planetCreateModal || state.planetSettingModal || state.teamSettingsModal || state.logoutModal) {
// ESC
if (e.keyCode === 27) this.cast('closeModal')
// Cmd + Enter
if (e.keyCode === 13 && e.metaKey) {
if (state.codeForm) this.cast('submitCodeForm')
if (state.noteForm) this.cast('submitNoteForm')
if (state.codeDeleteModal) this.cast('submitCodeDeleteModal')
if (state.noteDeleteModal) this.cast('submitNoteDeleteModal')
if (state.addMemberModal) this.cast('submitAddMemberModal')
if (state.contactModal) this.cast('submitContactModal')
if (state.teamCreateModal) this.cast('submitTeamCreateModal')
if (state.planetCreateModal) this.cast('submitPlanetCreateModal')
if (state.logoutModal) this.cast('submitLogoutModal')
}
return
}
/*
PlanetContainer
*/
if (state.planetContainer) {
// Cmd + Enter, A
if ((e.keyCode === 13 && e.metaKey) || e.keyCode === 65) this.cast('openLaunchModal')
// Esc
if (e.keyCode === 27) this.cast('toggleFocusSearchInput')
// Up
if (e.keyCode === 38) this.cast('selectPriorArticle')
// Down
if (e.keyCode === 40) this.cast('selectNextArticle')
// E
if (e.keyCode === 69) this.cast('openEditModal')
// D
if (e.keyCode === 68) this.cast('openDeleteModal')
}
/*
HomeContainer
*/
if (state.homeContainer) {
if (e.keyCode > 48 && e.keyCode < 58 && e.metaKey) {
this.cast('switchPlanet', e.keyCode - 48)
}
}
},
cast: function (status, data) {
this.trigger({
status: status,
data: data
})
}
})
module.exports = function (stateKey) {
return {
mixins: [Reflux.listenTo(KeyStore, 'onKeyCast')],
componentDidMount: function () {
var newState = {}
newState[stateKey] = true
KeyStore.setState(newState)
},
componentWillUnmount: function () {
var newState = {}
newState[stateKey] = false
KeyStore.setState(newState)
}
}
}

View File

@@ -0,0 +1,31 @@
function getIn (object, path) {
var stack = path.split('.')
while (stack.length > 1) {
object = object[stack.shift()]
}
return object[stack.shift()]
}
function updateIn (object, path, value) {
var current = object
var stack = path.split('.')
while (stack.length > 1) {
current = current[stack.shift()]
}
current[stack.shift()] = value
return object
}
function setPartialState (component, path, value) {
component.setState(
updateIn(component.state, path, value))
}
module.exports = {
linkState: function (path) {
return {
value: getIn(this.state, path),
requestChange: setPartialState.bind(null, this, path)
}
}
}

View File

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

View File

@@ -0,0 +1,42 @@
var React = require('react/addons')
var ModalBase = React.createClass({
getInitialState: function () {
return {
component: null,
componentProps: {},
isHidden: true
}
},
close: function () {
this.setState({component: null, componentProps: null, isHidden: true})
},
render: function () {
var componentProps = this.state.componentProps
return (
<div className={'ModalBase' + (this.state.isHidden ? ' hide' : '')}>
<div onClick={this.close} className='modalBack'/>
{this.state.component == null ? null : (
<this.state.component {...componentProps} close={this.close}/>
)}
</div>
)
}
})
var modalBase = null
module.exports = {
componentDidMount: function () {
if (modalBase == null) {
var el = document.createElement('div')
document.body.appendChild(el)
modalBase = React.render(<ModalBase/>, el)
}
},
openModal: function (component, props) {
modalBase.setState({component: component, componentProps: props, isHidden: false})
},
closeModal: function () {
modalBase.setState({isHidden: true})
}
}

172
browser/main/Services/Hq.js Normal file
View File

@@ -0,0 +1,172 @@
/* global localStorage */
var request = require('superagent-promise')(require('superagent'), Promise)
var apiUrl = require('../../../config').apiUrl
module.exports = {
// Auth
login: function (input) {
return request
.post(apiUrl + 'auth')
.send(input)
},
signup: function (input) {
return request
.post(apiUrl + 'auth/signup')
.send(input)
},
getUser: function () {
return request
.get(apiUrl + 'auth/user')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
},
changePassword: function (input) {
return request
.post(apiUrl + 'auth/password')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
// Resources
fetchUser: function (userName) {
return request
.get(apiUrl + 'resources/' + userName)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
},
updateUser: function (userName, input) {
return request
.put(apiUrl + 'resources/' + userName)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
createTeam: function (userName, input) {
return request
.post(apiUrl + 'resources/' + userName + '/teams')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
addMember: function (userName, input) {
return request
.post(apiUrl + 'resources/' + userName + '/members')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
removeMember: function (userName, input) {
return request
.del(apiUrl + 'resources/' + userName + '/members')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
createPlanet: function (userName, input) {
return request
.post(apiUrl + 'resources/' + userName + '/planets')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
fetchPlanet: function (userName, planetName) {
return request
.get(apiUrl + 'resources/' + userName + '/planets/' + planetName)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
},
updatePlanet: function (userName, planetName, input) {
return request
.put(apiUrl + 'resources/' + userName + '/planets/' + planetName)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
destroyPlanet: function (userName, planetName) {
return request
.del(apiUrl + 'resources/' + userName + '/planets/' + planetName)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
},
createCode: function (userName, planetName, input) {
return request
.post(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/codes')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
updateCode: function (userName, planetName, localId, input) {
return request
.put(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/codes/' + localId)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
destroyCode: function (userName, planetName, localId) {
return request
.del(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/codes/' + localId)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
},
createNote: function (userName, planetName, input) {
return request
.post(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/notes')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
updateNote: function (userName, planetName, localId, input) {
return request
.put(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/notes/' + localId)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
},
destroyNote: function (userName, planetName, localId) {
return request
.del(apiUrl + 'resources/' + userName + '/planets/' + planetName + '/notes/' + localId)
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
},
// Search
searchTag: function (tagName) {
return request
.get(apiUrl + 'search/tags')
.query({name: tagName})
},
searchUser: function (userName) {
return request
.get(apiUrl + 'search/users')
.query({name: userName})
},
// Mail
sendEmail: function (input) {
return request
.post(apiUrl + 'mail')
.set({
Authorization: 'Bearer ' + localStorage.getItem('token')
})
.send(input)
}
}

View File

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

View File

@@ -0,0 +1,167 @@
/* 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)
planet.Codes.forEach(function (code) {
code.type = 'code'
})
planet.Notes.forEach(function (note) {
note.type = 'note'
})
// Update the planet
localStorage.setItem('planet-' + planet.id, JSON.stringify(planet))
this.trigger({
status: 'updated',
data: planet
})
},
onDestroy: function (planet) {
// Check if the planet should be updated to currentUser
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
var ownedByCurrentUser = currentUser.id === planet.OwnerId
if (ownedByCurrentUser) {
currentUser.Planets = this.deleteItemFromTargetArray(planet, currentUser.Planets)
}
if (!ownedByCurrentUser) {
var team = null
currentUser.Teams.some(function (_team) {
if (_team.id === planet.OwnerId) {
team = _team
return true
}
return
})
if (team) {
team.Planets = this.deleteItemFromTargetArray(planet, team.Planets)
}
}
// Update currentUser
localStorage.setItem('currentUser', JSON.stringify(currentUser))
UserStore.Actions.update(currentUser)
// Update the planet
localStorage.setItem('planet-' + planet.id, JSON.stringify(planet))
this.trigger({
status: 'destroyed',
data: planet
})
},
onUpdateCode: function (code) {
code.type = 'code'
var planet = JSON.parse(localStorage.getItem('planet-' + code.PlanetId))
if (planet != null) {
planet.Codes = this.updateItemToTargetArray(code, planet.Codes)
localStorage.setItem('planet-' + code.PlanetId, JSON.stringify(planet))
}
this.trigger({
status: 'codeUpdated',
data: code
})
},
onDestroyCode: function (code) {
var planet = JSON.parse(localStorage.getItem('planet-' + code.PlanetId))
if (planet != null) {
planet.Codes = this.deleteItemFromTargetArray(code, planet.Codes)
localStorage.setItem('planet-' + code.PlanetId, JSON.stringify(planet))
}
code.type = 'code'
this.trigger({
status: 'codeDestroyed',
data: code
})
},
onUpdateNote: function (note) {
note.type = 'note'
var planet = JSON.parse(localStorage.getItem('planet-' + note.PlanetId))
if (planet != null) {
planet.Notes = this.updateItemToTargetArray(note, planet.Notes)
localStorage.setItem('planet-' + note.PlanetId, JSON.stringify(planet))
}
this.trigger({
status: 'noteUpdated',
data: note
})
},
onDestroyNote: function (note) {
var planet = JSON.parse(localStorage.getItem('planet-' + note.PlanetId))
if (planet != null) {
planet.Notes = this.deleteItemFromTargetArray(note, planet.Notes)
localStorage.setItem('planet-' + note.PlanetId, JSON.stringify(planet))
}
note.type = 'note'
this.trigger({
status: 'noteDestroyed',
data: note
})
}
})

View File

@@ -0,0 +1,69 @@
/* global localStorage */
var Reflux = require('reflux')
var actions = Reflux.createActions([
'update',
'destroy',
'addMember',
'removeMember'
])
module.exports = Reflux.createStore({
listenables: [actions],
onUpdate: function (user) {
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
if (currentUser.id === user.id) {
localStorage.setItem('currentUser', JSON.stringify(user))
}
if (user.userType === 'team') {
var isMyTeam = user.Members.some(function (member) {
if (currentUser.id === member.id) {
return true
}
return false
})
if (isMyTeam) {
var isNew = !currentUser.Teams.some(function (team, index) {
if (user.id === team.id) {
currentUser.Teams.splice(index, 1, user)
return true
}
return false
})
if (isNew) {
currentUser.Teams.push(user)
}
localStorage.setItem('currentUser', JSON.stringify(currentUser))
}
}
this.trigger({
status: 'userUpdated',
data: user
})
},
onDestroy: function (user) {
this.trigger({
status: 'userDestroyed',
data: user
})
},
onAddMember: function (member) {
this.trigger({
status: 'memberAdded',
data: member
})
},
onRemoveMember: function (member) {
this.trigger({
status: 'memberRemoved',
data: member
})
},
Actions: actions
})

BIN
browser/main/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,99 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<style>
@font-face {
font-family: 'Lato';
src: url('../../Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
url('../../Lato-Regular.woff') format('woff'), /* Modern Browsers */
url('../../Lato-Regular.ttf') format('truetype');
font-style: normal;
font-weight: normal;
text-rendering: optimizeLegibility;
}
#loadingCover{
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
box-sizing: border-box;
padding: 65px 0;
font-family: sans-serif;
}
#loadingCover img{
display: block;
margin: 75px auto 5px;
width: 160px;
height: 160px;
}
#loadingCover .message{
font-size: 30px;
text-align: center;
line-height: 1.6;
font-weight: 100;
color: #888;
}
</style>
</head>
<body>
<div id="loadingCover">
<img src="resources/favicon-230x230.png">
<div class='message'>Loading...</div>
</div>
<div id="content"></div>
<script>
if (!Object.assign) {
Object.defineProperty(Object, 'assign', {
enumerable: false,
configurable: true,
writable: true,
value: function(target) {
'use strict';
if (target === undefined || target === null) {
throw new TypeError('Cannot convert first argument to object');
}
var to = Object(target);
for (var i = 1; i < arguments.length; i++) {
var nextSource = arguments[i];
if (nextSource === undefined || nextSource === null) {
continue;
}
nextSource = Object(nextSource);
var keysArray = Object.keys(Object(nextSource));
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
to[nextKey] = nextSource[nextKey];
}
}
}
return to;
}
});
}
</script>
<script src="../ace/src-min/ace.js"></script>
<script>
var version = require('remote').require('app').getVersion()
global.version = version
document.title = 'Boost ' + ((version == null || version.length === 0) ? 'DEV version' : 'v' + version)
require('../electron-stylus')(__dirname + '/../styles/main/index.styl', 'mainCss')
// require('../electron-stylus')(__dirname + '/../styles/main/index.styl')
require('node-jsx').install({ harmony: true, extension: '.jsx' })
require('./index.jsx')
</script>
</body>
</html>

56
browser/main/index.html Normal file
View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>CodeXen</title>
<link rel="stylesheet" href="../vendor/fontawesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
<link rel="shortcut icon" href="favicon.ico">
<script>
if (!Object.assign) {
Object.defineProperty(Object, 'assign', {
enumerable: false,
configurable: true,
writable: true,
value: function(target) {
'use strict';
if (target === undefined || target === null) {
throw new TypeError('Cannot convert first argument to object');
}
var to = Object(target);
for (var i = 1; i < arguments.length; i++) {
var nextSource = arguments[i];
if (nextSource === undefined || nextSource === null) {
continue;
}
nextSource = Object(nextSource);
var keysArray = Object.keys(Object(nextSource));
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
to[nextKey] = nextSource[nextKey];
}
}
}
return to;
}
});
}
</script>
<script src="../vendor/moment/min/moment.min.js"></script>
<script src="../vendor/markdown-it/dist/markdown-it.min.js"></script>
<script src="../vendor/react/react-with-addons.js"></script>
<script src="../vendor/react-router/build/umd/ReactRouter.js"></script>
<script src="../vendor/reflux/dist/reflux.js"></script>
<script src="../ace/src-min/ace.js"></script>
</head>
<body>
<div id="content"></div>
<script src="http://localhost:8090/webpack-dev-server.js"></script>
<script type="text/javascript" src="http://localhost:8090/assets/main.js"></script>
<script type="text/javascript" src="http://localhost:8090/assets/main-style.js"></script>
</body>
</html>

46
browser/main/index.jsx Normal file
View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

2
browser/main/style.js Normal file
View File

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

View File

@@ -0,0 +1,84 @@
@import '../../../node_modules/nib/lib/nib'
@import '../vars'
@import '../mixins/*'
global-reset()
@import '../shared/*'
body
font-family "Lato"
color textColor
font-size fontSize
.Finder
absolute top bottom left right
.FinderInput
position absolute
top 11px
left 11px
right 11px
margin 0 auto
height 44px
box-sizing border-box
border-bottom solid 1px borderColor
input
display block
width 100%
border solid 1px borderColor
padding 0 10px
font-size 1em
height 33px
border-radius 5px
box-sizing border-box
border-radius 16.5px
&:focus, &.focus
border-color brandBorderColor
outline none
.FinderList
absolute left bottom
top 55px
border-right solid 1px borderColor
box-sizing border-box
width 250px
overflow-y auto
&>ul>li
.articleItem
padding 10px
border solid 2px transparent
box-sizing border-box
cursor pointer
white-space nowrap
overflow-x hidden
text-overflow ellipsis
.divider
box-sizing border-box
border-bottom solid 1px borderColor
&.active
.articleItem
border-color brandColor
.FinderDetail
absolute right bottom
top 55px
left 250px
.header
absolute top left right
height 44px
box-sizing border-box
padding 0 10px
border-bottom solid 1px borderColor
line-height 44px
font-size 1.3em
white-space nowrap
text-overflow ellipsis
overflow-x hidden
.content
.ace_editor, .marked
position absolute
top 49px
left 5px
right 5px
bottom 5px
box-sizing border-box
.marked
marked()
overflow-y auto

View File

@@ -0,0 +1,260 @@
/**
* React Select
* ============
* Created by Jed Watson and Joss Mackison for KeystoneJS, http://www.keystonejs.com/
* https://twitter.com/jedwatson https://twitter.com/jossmackison https://twitter.com/keystonejs
* MIT License: https://github.com/keystonejs/react-select
*/
.Select {
position: relative;
}
.Select-control {
position: relative;
overflow: hidden;
background-color: #ffffff;
border: 1px solid #cccccc;
border-color: #d9d9d9 #cccccc #b3b3b3;
border-radius: 4px;
box-sizing: border-box;
color: #333333;
cursor: default;
outline: none;
padding: 8px 52px 8px 10px;
transition: all 200ms ease;
}
.Select-control:hover {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
}
.is-searchable.is-open > .Select-control {
cursor: text;
}
.is-open > .Select-control {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
background: #ffffff;
border-color: #b3b3b3 #cccccc #d9d9d9;
}
.is-open > .Select-control > .Select-arrow {
border-color: transparent transparent #999999;
border-width: 0 5px 5px;
}
.is-searchable.is-focused:not(.is-open) > .Select-control {
cursor: text;
}
.is-focused:not(.is-open) > .Select-control {
border-color: #0088cc #0099e6 #0099e6;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 5px -1px rgba(0, 136, 204, 0.5);
}
.Select-placeholder {
color: #aaaaaa;
padding: 8px 52px 8px 10px;
position: absolute;
top: 0;
left: 0;
right: -15px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.has-value > .Select-control > .Select-placeholder {
color: #333333;
}
.Select-input > input {
cursor: default;
background: none transparent;
box-shadow: none;
height: auto;
border: 0 none;
font-family: inherit;
font-size: inherit;
margin: 0;
padding: 0;
outline: none;
display: inline-block;
-webkit-appearance: none;
}
.is-focused .Select-input > input {
cursor: text;
}
.Select-control:not(.is-searchable) > .Select-input {
outline: none;
}
.Select-loading {
-webkit-animation: Select-animation-spin 400ms infinite linear;
-o-animation: Select-animation-spin 400ms infinite linear;
animation: Select-animation-spin 400ms infinite linear;
width: 16px;
height: 16px;
box-sizing: border-box;
border-radius: 50%;
border: 2px solid #cccccc;
border-right-color: #333333;
display: inline-block;
position: relative;
margin-top: -8px;
position: absolute;
right: 30px;
top: 50%;
}
.has-value > .Select-control > .Select-loading {
right: 46px;
}
.Select-clear {
color: #999999;
cursor: pointer;
display: inline-block;
font-size: 16px;
padding: 6px 10px;
position: absolute;
right: 17px;
top: 0;
}
.Select-clear:hover {
color: #c0392b;
}
.Select-clear > span {
font-size: 1.1em;
}
.Select-arrow-zone {
content: " ";
display: block;
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 30px;
cursor: pointer;
}
.Select-arrow {
border-color: #999999 transparent transparent;
border-style: solid;
border-width: 5px 5px 0;
content: " ";
display: block;
height: 0;
margin-top: -ceil(2.5px);
position: absolute;
right: 10px;
top: 14px;
width: 0;
cursor: pointer;
}
.Select-menu-outer {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
background-color: #ffffff;
border: 1px solid #cccccc;
border-top-color: #e6e6e6;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
box-sizing: border-box;
margin-top: -1px;
max-height: 200px;
position: absolute;
top: 100%;
width: 100%;
z-index: 1000;
-webkit-overflow-scrolling: touch;
}
.Select-menu {
max-height: 198px;
overflow-y: auto;
}
.Select-option {
box-sizing: border-box;
color: #666666;
cursor: pointer;
display: block;
padding: 8px 10px;
}
.Select-option:last-child {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
.Select-option.is-focused {
background-color: #f2f9fc;
color: #333333;
}
.Select-option.is-disabled {
color: #cccccc;
cursor: not-allowed;
}
.Select-noresults {
box-sizing: border-box;
color: #999999;
cursor: default;
display: block;
padding: 8px 10px;
}
.Select.is-multi .Select-control {
padding: 2px 52px 2px 3px;
}
.Select.is-multi .Select-input {
vertical-align: middle;
border: 1px solid transparent;
margin: 2px;
padding: 3px 0;
}
.Select-item {
background-color: #f2f9fc;
border-radius: 2px;
border: 1px solid #c9e6f2;
color: #0088cc;
display: inline-block;
font-size: 1em;
margin: 2px;
}
.Select-item-icon,
.Select-item-label {
display: inline-block;
vertical-align: middle;
}
.Select-item-label {
cursor: default;
border-bottom-right-radius: 2px;
border-top-right-radius: 2px;
padding: 3px 5px;
}
.Select-item-label .Select-item-label__a {
color: #0088cc;
cursor: pointer;
}
.Select-item-icon {
cursor: pointer;
border-bottom-left-radius: 2px;
border-top-left-radius: 2px;
border-right: 1px solid #c9e6f2;
padding: 2px 5px 4px;
}
.Select-item-icon:hover,
.Select-item-icon:focus {
background-color: #ddeff7;
color: #0077b3;
}
.Select-item-icon:active {
background-color: #c9e6f2;
}
.Select.is-multi.is-disabled .Select-item {
background-color: #f2f2f2;
border: 1px solid #d9d9d9;
color: #888888;
}
.Select.is-multi.is-disabled .Select-item-icon {
cursor: not-allowed;
border-right: 1px solid #d9d9d9;
}
.Select.is-multi.is-disabled .Select-item-icon:hover,
.Select.is-multi.is-disabled .Select-item-icon:focus,
.Select.is-multi.is-disabled .Select-item-icon:active {
background-color: #f2f2f2;
}
@keyframes Select-animation-spin {
to {
transform: rotate(1turn);
}
}
@-webkit-keyframes Select-animation-spin {
to {
-webkit-transform: rotate(1turn);
}
}

View File

@@ -0,0 +1,82 @@
.LoginContainer, .SignupContainer
margin 0 auto
padding 25px 15px
box-sizing border-box
color inactiveTextColor
.logo
width 150px
height 150px
display block
margin 0 auto
.authNavigator
margin 15px 0 25px
a
font-size 1.5em
text-decoration none
color inactiveTextColor
&:hover, &.hover, &:active, &.active
color brandColor
.socialControl
text-align center
margin 25px 0
p
margin-bottom 25px
.facebookBtn, .githubBtn
margin 0 45px
width 50px
height 50px
line-height 50px
font-size 25px
text-align center
background-image none
color white
border none
border-radius 25px
cursor pointer
.facebookBtn
background-color facebookColor
&:hover, &.hover
background-color lighten(facebookColor, 25%)
.githubBtn
background-color githubBtn
font-size 30px
line-height 30px
&:hover, &.hover
background-color lighten(githubBtn, 25%)
.divider
.dividerLabel
text-align center
position relative
top -27px
font-size 1.3em
background-color backgroundColor
margin 0 auto
width 50px
form
width 400px
margin 0 auto 45px
.alertInfo, .alertError
margin-top 15px
margin-bottom 15px
height 44px
padding 5px
border-radius 10px
line-height 44px
text-align center
.alertInfo
alertInfo()
.alertError
alertError()
div.form-group:last-child
margin-top 15px
button.logInButton
btnPrimary()
height 44px
border-radius 22px
display block
width 200px
font-size 1em
margin 0 auto
p.alert
text-align center
font-size 0.8em

View File

@@ -0,0 +1,332 @@
navigationWidth= 200px
articleListWidth= 275px
.PlanetContainer
absolute top bottom right left
.tags
white-space: nowrap;
overflow-x: auto;
a
margin 0 2px
text-decoration underline
cursor pointer
font-size 0.95em
&.noTag
color inactiveTextColor
font-size 0.8em
.PlanetHeader
absolute left right top
overflow-y hidden
height 55px
background-color white
border-bottom solid 1px borderColor
box-sizing border-box
padding 5px 15px
clearfix()
.headerLabel
noSelect()
absolute top left bottom
overflow hidden
display inline-block
width navigationWidth
.userName
position absolute
left 15px
top 30px
width 140px
font-size 1em
color textColor
text-decoration none
&:hover
color darken(lightButtonColor, 50%)
text-decoration underline
.planetName
position absolute
top 5px
left 10px
width 145px
font-size 1.6em
color brandColor
overflow hidden
text-overflow ellipsis
white-space nowrap
&:hover
color darken(brandBorderColor, 30%)
.private
position absolute
top 12px
right 38px
width 33px
height 33px
line-height 33px
text-align center
color inactiveColor
&:hover
color textColor
.tooltip
tooltip()
margin-left -30px
&:hover .tooltip
opacity 1
.planetSettingButton
position absolute
top 15px
right 5px
font-size 0.8em
btnDefault()
box-sizing border-box
circle()
width 26px
height 26px
text-align center
cursor pointer
transition 0.1s
&:focus, &.focus
outline none
.tooltip
tooltip()
margin-top 11px
margin-left -36px
&:hover .tooltip
opacity 1
.headerControl
noSelect()
absolute top bottom right
left navigationWidth
.searchInput
display block
position absolute
top 12px
left 0
input
padding-left 32px
width 300px
.fa
position absolute
top 8px
left 12px
color inactiveTextColor
.refreshButton
display block
position absolute
top 15px
right 55px
width 26px
height 26px
font-size 0.8em
btnDefault()
circle()
text-align center
cursor pointer
transition 0.1s
&:focus, &.focus
outline none
.tooltip
tooltip()
margin-top 11px
margin-left -39px
&:hover .tooltip
opacity 1
.logo
display block
position absolute
top 4px
right 10px
cursor pointer
img
transition 0.1s
opacity 0.9
&:hover img, &:hover .tooltip
opacity 1
.tooltip
tooltip()
margin-top -5px
margin-left -67px
.PlanetNavigator
absolute bottom left
noSelect()
top 55px
width navigationWidth
border-right solid 1px highlightenBorderColor
padding 10px
box-sizing border-box
.launchButton
border-radius 22px
font-size 1.1em
nav
a
display block
box-sizing border-box
padding 15px 15px
margin 10px 0
border-radius 10px
text-decoration none
background-color transparent
color textColor
cursor pointer
transition 0.1s
btnDefault()
border none
.PlanetArticleList
absolute bottom right
left navigationWidth
top 55px
width articleListWidth
border-right solid 1px highlightenBorderColor
&>ul
absolute top bottom left right
overflow-y auto
li
.articleItem
noSelect()
border solid 2px transparent
position relative
height 94px
width 100%
cursor pointer
transition 0.1s
.itemLeft
position absolute
top 4px
bottom 4px
width 38px
padding 3px 0 3px 3px
text-align center
.profileImage
margin-bottom 5px
circle()
.fa
line-height 25px
.itemRight
position absolute
top 4px
bottom 4px
right 2px
left 40px
overflow-x hidden
padding 3px 10px 3px 3px
.itemInfo
margin 5px 0 13px
color lighten(textColor, 25%)
font-size 0.7em
.userProfileName
color brandColor
font-size 1.2em
.description
line-height 120%
margin-bottom 10px
font-size 1em
overflow-x hidden
white-space nowrap
text-overflow ellipsis
.tags
position absolute
bottom 5px
font-size 0.9em
&:hover, &.hover
background-color hoverBackgroundColor
&:active, &.active
background-color white
&:active, &.active
border-color brandBorderColor
.divider
border-bottom solid 1px borderColor
.PlanetArticleDetail
absolute right bottom
top 55px
left navigationWidth + articleListWidth
.detailHeader
border solid 2px transparent
position relative
height 105px
width 100%
transition 0.1s
.itemLeft
position absolute
top 7px
bottom 4px
width 38px
padding 3px 0 3px 3px
text-align center
.profileImage
margin-bottom 5px
circle()
.fa
line-height 25px
.itemRight
position absolute
top 7px
bottom 4px
right 2px
left 40px
overflow-x hidden
padding 3px 10px 3px 3px
.itemInfo
margin 5px 0 13px
color lighten(textColor, 25%)
font-size 0.7em
.userProfileName
color brandColor
font-size 1.2em
.description
line-height 120%
margin-bottom 10px
font-size 1em
overflow-x auto
white-space nowrap
.tags
position absolute
bottom 5px
font-size 0.9em
.itemControl
position absolute
z-index 1
top 2px
right 2px
.deleteButton, .editButton
btnDefault()
text-align center
width 33px
height 33px
border-radius 16.5px
font-size 15px
margin 0 3px
.tooltip
tooltip()
margin-top 10px
&:hover .tooltip
opacity 1
.editButton .tooltip
margin-left -12px
.deleteButton .tooltip
margin-left -26px
.detailBody
absolute left right bottom
top 105px
.content
position absolute
top 5px
bottom 5px
left 2px
right 2px
box-sizing border-box
padding 5px
border-top solid 1px borderColor
&.noteDetail
.detailBody .content
overflow-x hidden
overflow-y auto
marked()
&.codeDetail
.detailBody .content
.ace_editor
absolute left right top bottom

View File

@@ -0,0 +1,310 @@
.HomeContainer
.HomeNavigator
noSelect()
background-color planetNavBgColor
absolute left top bottom
width 55px
text-align center
box-sizing border-box
border-right solid 1px borderColor
.profileButton
display block
width 55px
height 55px
border-bottom solid 1px borderColor
overflow hidden
background-color black
margin 0
padding 0
cursor pointer
box-sizing border-box
border none
img
transition 0.1s
opacity 0.9
&.vivid.active, &.focus, &:focus, &.hover, &:hover
img
opacity 1
.profilePopup
position fixed
left 35px
top 35px
z-index popupZIndex
width 200px
background-color backgroundColor
box-shadow popupShadow
border-radius 10px
padding 10px 0 0px
&.close
display none
.profileGroup
margin-bottom 10px
.profileGroupLabel
text-align left
height 1em
padding 0 15px
span
position absolute
z-index 2
background-color backgroundColor
padding-right 5px
color inactiveTextColor
font-size 0.8em
&::before
content ''
position absolute
display block
z-index 1
height 0.5em
width 175px
border-bottom solid 1px borderColor
.profileGroupList
li
clearfix()
&:hover
background-color hoverBackgroundColor
.userName
width 155px
padding 10px 15px
text-align left
display block
text-decoration none
cursor pointer
.createNewTeam
btnStripDefault()
width 100%
padding 10px 20px
font-size 1em
cursor pointer
text-align left
.controlGroup
list-style none
border-top solid 1px borderColor
padding 10px 0
li
&:hover
background-color hoverBackgroundColor
button
btnStripDefault()
width 100%
padding 10px 20px
font-size 1em
cursor pointer
text-align left
ul.planetList>li
margin 15px 0
.shortCut
margin-top 5px
color lighten(textColor, 5%)
font-size 0.8em
&.active
a
background-color planetAnchorActiveBgColor
color planetAnchorActiveColor
a
display block
width 44px
height 44px
margin 0 auto
text-align center
background-color planetAnchorBgColor
text-decoration none
color planetAnchorColor
line-height 44px
font-size 1.1em
cursor pointer
circle()
transition 0.1s
&:hover, &:active
background-color white
.planetTooltip
position absolute
z-index popupZIndex
background-color transparentify(invBackgroundColor, 80%)
color invTextColor
padding 10px
line-height 1em
border-radius 5px
margin-top -41px
margin-left 52px
white-space nowrap
opacity 0
transition 0.1s
pointer-events none
&:hover .planetTooltip
opacity 1
img
circle()
width 55px
height 55px
button.newPlanet
display block
margin 0 auto
width 30px
height 30px
circle()
border solid 1px lightButtonColor
color lightButtonColor
text-align center
font-size 1
background-image none
background-color transparent
box-sizing border-box
absolute left bottom right
bottom 15px
&:hover, &.hover, &:focus, &.focus
border-color darken(lightButtonColor, 50%)
color darken(lightButtonColor, 50%)
&:active, &.active
border-color darken(brandBorderColor, 10%)
background-color brandColor
color white
.tooltip
tooltip()
margin-top -22px
margin-left 33px
&:hover .tooltip
opacity 1
.UserContainer
absolute top bottom right
left 55px
.memberPopup
absolute left
top 235px
z-index 1
padding 0 15px 10px
width 200px
.label
padding 10px 0
font-size 0.9em
border-bottom solid 1px borderColor
margin-bottom 15px
.members
li
padding 0 10px
margin-bottom 15px
clearfix()
.memberImage
float left
margin-right 7px
circle()
.memberInfo
float left
.memberProfileName
margin-bottom 5px
font-size 1.05em
.memberName
margin-left 5px
font-size 0.9em
color inactiveTextColor
a:hover .memberProfileName, a:hover .memberName
text-decoration underline
.userProfile
absolute top left right
padding 15px
border-bottom solid 1px borderColor
height 125px
clearfix()
.userPhoto
circle()
float left
margin 5px 15px 15px
.userInfo
float left
margin-top 15px
.userProfileName
font-size 1.5em
color brandColor
margin-bottom 10px
.userName
font-size 1.1em
.editProfileButton
float right
btnDefault()
margin-top 25px
padding 10px 15px
border-radius 5px
.teamList, .memberList
absolute left bottom
top 125px
width 200px
padding 15px
border-right solid 1px borderColor
overflow-y auto
.teamLabel, .memberLabel
font-size 1.2em
margin-bottom 15px
.teams
li
padding 0 10px
margin-bottom 15px
clearfix()
.teamInfo
float left
.teamProfileName
margin-bottom 5px
font-size 1.05em
.teamName
margin-left 5px
font-size 0.9em
color inactiveTextColor
a:hover .teamProfileName, a:hover .teamName
text-decoration underline
margin-bottom 10px
font-size 1.1em
.createTeamButton, .addMemberButton
btnStripDefault()
.members
li
padding 0 10px
margin-bottom 15px
clearfix()
.memberImage
float left
margin-right 7px
circle()
.memberInfo
float left
.memberProfileName
margin-bottom 5px
font-size 1.05em
.memberRole
font-size 0.9em
color inactiveTextColor
.memberName
margin-left 5px
font-size 0.9em
color inactiveTextColor
.createTeamButton, .addMemberButton
btnStripDefault()
a:hover .memberProfileName, a:hover .memberName
text-decoration underline
.planetList
absolute right bottom
top 125px
left 200px
padding 15px
overflow-y auto
.planetLabel
font-size 1.2em
margin-bottom 15px
.planetGroup
margin-left 15px
.planetGroupLabel
font-size 1.1em
margin-bottom 15px
small
font-size 0.8em
color inactiveTextColor
.planets
margin-left 15px
li
a
font-size 1.1em
text-decoration none
&:hover
text-decoration underline
margin-bottom 10px
.createPlanetButton
btnStripDefault()

View File

@@ -0,0 +1,128 @@
@import '../../../node_modules/nib/lib/nib'
@import '../vars'
@import '../mixins/*'
global-reset()
@import '../shared/*'
@import './components/*'
@import './containers/*'
html, body
width 100%
height 100%
overflow hidden
body
font-family "Lato"
color textColor
font-size fontSize
font-weight 400
button
font-family "Lato"
div, span, a, button, input, textarea
box-sizing border-box
h1
font-size 2em
h2
font-size 1.5em
h3
font-size 1.17em
h4
font-size 1em
h5
font-size 0.83em
h6
font-size 0.67em
a
color brandColor
&:hover
color darken(brandColor, 15%)
&:visited
color brandColor
hr
border-top none
border-bottom solid 1px borderColor
margin 15px 0
button
font-weight 400
cursor pointer
&:focus, &.focus
outline none
.text-center
text-align center
.form-group
margin-bottom 15px
&>label
display block
margin-bottom 5px
.stripInput
stripInput()
display block
width 100%
font-size 1em
height 33px
.block-input, .inline-input
border solid 1px borderColor
padding 0 10px
font-size 1em
height 33px
border-radius 5px
box-sizing border-box
&:focus, &.focus
border solid 1px brandBorderColor
outline none
&.circleInput
border-radius 16.5px
.block-input
display block
width 100%
.inline-input
display inline-block
margin-right 5px
.relative
position relative
textarea.block-input
resize vertical
height 125px
border-radius 5px
padding 5px 10px
#content
fullsize()
.Main
.appUpdateButton
position fixed
z-index 2000
bottom 5px
right 53px
btnPrimary()
padding 10px 15px
border-radius 5px
background-color backgroundColor
.contactButton
position fixed
z-index 2000
bottom 5px
right 5px
btnPrimary()
padding 10px 15px
border-radius 5px
background-color backgroundColor
.tooltip
tooltip()
margin-top -22px
margin-left -97px
&:hover .tooltip
opacity 1

View File

@@ -0,0 +1,11 @@
alertSuccess()
background-color successBackgroundColor
color successTextColor
alertError()
background-color errorBackgroundColor
color errorTextColor
alertInfo()
background-color infoBackgroundColor
color infoTextColor

View File

@@ -0,0 +1,40 @@
btnDefault()
border-style solid
border-width 1px
border-color lightButtonColor
background-color transparent
color lightButtonColor
&:hover, &.hover, &:focus, &.focus
border-color darken(lightButtonColor, 50%)
color darken(lightButtonColor, 50%)
&:active, &.active
border-color darken(brandBorderColor, 10%)
background-color brandColor
color white
&:disabled, &.disabled
opacity 0.6
btnPrimary()
border-style solid
border-width 1px
border-color brandBorderColor
background-color transparent
color brandColor
&:hover, &.hover, &:focus, &.focus
border-color darken(brandBorderColor, 30%)
color darken(brandColor, 30%)
&:active, &.active
background-color brandColor
color white
&:disabled, &.disabled
opacity 0.6
btnStripDefault()
border none
background-color transparent
color lightButtonColor
&:hover, &.hover, &:focus, &.focus
color darken(lightButtonColor, 50%)
&:active, &.active
color brandColor

View File

@@ -0,0 +1,3 @@
circle()
border-radius 50%
overflow hidden

View File

@@ -0,0 +1,6 @@
fullsize()
position absolute
top 0
left 0
right 0
bottom 0

View File

@@ -0,0 +1,16 @@
stripInput()
border none
border-bottom 1px solid borderColor
padding 5px 15px
transition 0.1s
&:focus, &.focus
border-bottom 1px solid brandBorderColor
outline none
borderInput()
border solid 1px borderColor
padding 5px 15px
transition 0.1s
&:focus, &.focus
border-color brandBorderColor
outline none

View File

@@ -0,0 +1,103 @@
marked()
hr
border-top none
border-bottom solid 1px borderColor
margin 15px 0
h1
font-size 2em
margin 0 auto 0.67em
h2
font-size 1.5em
margin 0 auto 0.83em
h3
font-size 1.17em
margin 0 auto 1em
h4
font-size 1em
margin 0 auto 1.33em
h5
font-size 0.83em
margin 0 auto 1.67em
h6
font-size 0.67em
margin 2.33em auto
h1, h2, h3, h4, h5, h6
font-weight 400
line-height 1.4em
p
line-height 1.4em
margin-bottom 15px
img
max-width 100%
strong
font-weight bold
em
font-style italic
s
text-decoration line-through
blockquote
border-left solid 4px brandBorderColor
margin 15px 0 15px
padding 0 25px
ul
list-style-type disc
padding-left 35px
li
display list-item
margin 15px 0
&>li>ul
list-style-type circle
&>li>ul
list-style-type square
ol
list-style-type decimal
padding-left 35px
li
display list-item
margin 15px 0
code
font-family monospace
padding 2px 4px
border solid 1px borderColor
border-radius 4px
font-size 0.9em
color black
text-decoration none
background-color #F6F6F6
pre
padding 5px
border solid 1px borderColor
border-radius 5px
overflow-x auto
margin-bottom 15px
background-color #F6F6F6
&>code
padding 0
border none
border-radius 0
color black
table
width 100%
margin 15px 0 25px
thead
tr
background-color tableHeadBgColor
th
border-style solid
padding 15px 5px
border-width 1px 0 2px 1px
border-color borderColor
&:last-child
border-right solid 1px borderColor
tbody
tr:nth-child(2n + 1)
background-color tableOddBgColor
tr:nth-child(2n)
background-color tableEvenBgColor
td
border-style solid
padding 15px 5px
border-width 0 0 1px 1px
border-color borderColor
&:last-child
border-right solid 1px borderColor

View File

@@ -0,0 +1,13 @@
tooltip()
position fixed
z-index popupZIndex
background-color transparentify(invBackgroundColor, 80%)
color invTextColor
padding 10px
font-size 12px
line-height 12px
border-radius 5px
white-space nowrap
opacity 0
transition 0.1s
pointer-events none

View File

@@ -0,0 +1,7 @@
borderBox()
box-sizing border-box
noSelect()
-webkit-user-select none
-webkit-app-region drag

View File

@@ -0,0 +1,55 @@
.btn-primary, .btn-default
border-style solid
border-width 1px
background-image none
height 44px
padding 0 15px
border-radius 5px
box-sizing border-box
font-size 1em
font-family 'Lato'
font-weight 400
transition 0.1s
cursor pointer
margin 0 5px
.btn-block
display block
width 100%
margin 0 auto
.btn-square
display inline-block
width 44px
padding 0
border-width 1px
.btn-sm
height 32px
border-radius 16px
&.btn-square
width 32px
.btn-primary
border-color brandBorderColor
background-color transparent
color brandColor
&:hover, &.hover, &:focus, &.focus
border-color darken(brandBorderColor, 30%)
color darken(brandColor, 30%)
&:active, &.active
background-color brandColor
color white
.btn-default
border-color lightButtonColor
background-color transparent
color lightButtonColor
&:hover, &.hover, &:focus, &.focus
border-color darken(lightButtonColor, 50%)
color darken(lightButtonColor, 50%)
&:active, &.active
border-color darken(brandBorderColor, 10%)
background-color brandColor
color white

View File

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

51
browser/styles/vars.styl Normal file
View File

@@ -0,0 +1,51 @@
borderColor = #E8E8E8
highlightenBorderColor = darken(borderColor, 20%)
invBorderColor = #404849
brandBorderColor = #3FB399
buttonBorderColor = #4C4C4C
lightButtonColor = #898989
hoverBackgroundColor= transparentify(#444, 4%)
inactiveTextColor = #888
textColor = #4D4D4D
backgroundColor= white
fontSize= 14px
shadowColor= #C5C5C5
invBackgroundColor = #4C4C4C
invTextColor = white
btnColor = #888
btnHighlightenColor = #000
brandColor = #2BAC8F
planetNavBgColor = #ECECEC
planetAnchorColor = #979797
planetAnchorBgColor = #BEBEBE
planetAnchorActiveColor = textColor
planetAnchorActiveBgColor = white
popupShadow = 0 0 5px 0 #888
modalBackColor = transparentify(white, 65%)
tableHeadBgColor = white
tableOddBgColor = #F9F9F9
tableEvenBgColor = white
facebookColor= #3b5998
githubBtn= #201F1F
successBackgroundColor= #E0F0D9
successTextColor= #3E753F
errorBackgroundColor= #F2DEDE
errorTextColor= #A64444
infoBackgroundColor= #D9EDF7
infoTextColor= #34708E
modalZIndex= 1000
popupZIndex= 500

View File

@@ -1,44 +0,0 @@
module.exports = {
vendors: [
{
name: 'ace',
src: 'node_modules/@rokt33r/ace-builds/src/**/*'
},
{
name: 'angular',
src: 'node_modules/angular/angular.js'
},
{
name: 'angular-bootstrap',
src: 'node_modules/angular-bootstrap/dist/ui-bootstrap-tpls.js'
},
{
name: 'angular-sanitize',
src: 'node_modules/angular-sanitize/angular-sanitize.js'
},
{
name: 'angular-ui-router',
src: 'node_modules/angular-ui-router/build/angular-ui-router.js'
},
{
name: 'ui-select',
src: 'node_modules/ui-select/dist/select.js'
},
{
name: 'satellizer',
src: 'node_modules/satellizer/satellizer.js'
},
{
name: 'angular-md5',
src: 'node_modules/angular-md5/angular-md5.js'
},
{
name: 'moment',
src: 'node_modules/moment/moment.js'
},
{
name: 'angular-hotkeys',
src: 'node_modules/angular-hotkeys/build/hotkeys.js'
}
]
}

4
config.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
apiUrl: 'https://api.b00st.io/'
// apiUrl: 'http://localhost:8000/'
}

View File

@@ -1,7 +0,0 @@
# Event List
|name|Description|Delivery|
|----|----|----|
|userSignIn|a user signed in||
|userSignOut|a user signed out||
|snippetUpdated|snippet has been updated or created|snippet|

View File

@@ -1,55 +0,0 @@
<!DOCTYPE html>
<html ng-app="codexen.popup">
<head>
<title>
CodeXen App
</title>
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<meta name="description" content="CodeXen - Short code storage service">
<link rel="stylesheet" href="../../vendor/css/font-awesome.css" media="screen" title="no title" charset="utf-8">
<link rel="stylesheet" href="../../main.css" media="screen" title="no title" charset="utf-8">
</head>
<body class="popup-body" ng-controller="PopUpController">
<div class="search-block">
<input ng-change="filterList(searchNeedle)" search-input id="search-input" type="text" class="form-control" ng-model="searchNeedle" ng-change="refreshResult">
</div>
<div class="result-block row-fluid">
<ul id="result-list" class="result-list left-pane">
<li ng-click="selectSnippet($index)" ng-repeat="snippet in filteredSnippets" ng-class="{active:$index == selectIndex}"><a href="#"> <span ng-bind="snippet.callSign"></span> <small ng-bind="snippet.description"></small></a></li>
</ul>
<div class="right-pane">
<div class="result-detail-control">
<button ng-click="writeCode(selectedItem.content)" id="btnClipboard" type="button" name="button" class="btn btn-default"><i class="fa fa-clipboard"></i></button>
<!-- <button ng-click="editSnippet(selectedItem.id)" id="btnEdit" type="button" name="button" class="btn btn-default"><i class="fa fa-edit"></i></button> -->
<!-- <button id="btnShare" type="button" name="button" class="btn btn-default"><i class="fa fa-share"></i></button> -->
</div>
<div id="aceView" class="result-detail-content"
ui-ace="{
showGutter: false,
useWrapMode: true,
mode:selectedItem.mode.toLowerCase(),
onLoad: aceLoaded,
theme: 'solarized_dark'
}"
readonly
ng-model="selectedItem.content"
></div>
</div>
</div>
<script src="../../vendor/ace.js"></script>
<script src="../../vendor/angular.js" charset="utf-8"></script>
<script src="../../directives/ui-ace.js"></script>
<script src="../../vendor/satellizer.js"></script>
<script src="../../vendor/hotkeys.js" charset="utf-8"></script>
<script src="popup.js" charset="utf-8"></script>
<script src="services/snippet.js" charset="utf-8"></script>
</body>
</html>

View File

@@ -1,190 +0,0 @@
/* global angular */
var remote = require('remote')
var ipc = require('ipc')
var resultList = document.getElementById('result-list')
angular.module('codexen.popup', [
'ui.ace',
'satellizer',
'cfp.hotkeys'
])
.controller('PopUpController', function ($scope, Snippet, $auth, $window, hotkeys, $document, $filter) {
// Setup Events
remote.getCurrentWindow().on('focus', function () {
if (!$auth.isAuthenticated()) return hidePopUp()
$scope.$apply(focusSearchInput)
loadSnippets()
})
hotkeys.bindTo($scope)
.add('down', function (e) {
nextSnippet()
e.preventDefault()
})
.add('up', function (e) {
priorSnippet()
e.preventDefault()
})
.add('right', function (e) {
e.preventDefault()
})
.add('left', function (e) {
e.preventDefault()
})
.add('esc', function (e) {
hidePopUp()
})
.add('shift+tab', function (e) {
e.preventDefault()
})
.add('tab', function (e) {
e.preventDefault()
})
.add('enter', function (e) {
writeCode($scope.selectedItem.content)
e.preventDefault()
})
$scope.aceLoaded = function (editor) {
editor.commands.addCommand({
name: 'escape',
bindKey: {win: 'esc', mac: 'esc'},
exec: function (editor) {
editor.blur()
$scope.$apply()
},
readOnly: true
})
}
$scope.$on('nextSnippetRequested', function (e) {
e.stopPropagation()
nextSnippet()
})
$scope.$on('priorSnippetRequested', function (e) {
e.stopPropagation()
priorSnippet()
})
$scope.$on('snippetSubmitted', function (e) {
if ($scope.filteredSnippets.length > 0) ipc.send('writeCode', $scope.selectedItem.content)
else console.log('\x07')
e.stopPropagation()
})
// Init Data
$scope.snippets = []
Snippet.findMine()
.success(function (data) {
$scope.snippets = data
filterList()
})
// Result Item control
$scope.selectIndex = 0
$scope.selectSnippet = selectSnippet
$scope.filterList = filterList
$scope.writeCode = writeCode
$scope.focusSearchInput = focusSearchInput
// Search Filter
function loadSnippets () {
Snippet.findMine()
.success(function (data) {
$scope.snippets = data
filterList()
})
}
function filterList (needle) {
$scope.filteredSnippets = $filter('filter')($scope.snippets, needle)
firstSnippet()
}
function selectSnippet (index) {
if (index !== undefined) $scope.selectIndex = index
$scope.selectedItem = $scope.filteredSnippets[$scope.selectIndex]
}
function firstSnippet () {
$scope.selectIndex = 0
selectSnippet($scope.selectIndex)
}
function priorSnippet () {
if ($scope.selectIndex > 0) $scope.selectIndex -= 1
if (resultList.children[$scope.selectIndex].offsetTop < resultList.scrollTop) {
resultList.scrollTop -= 33
}
selectSnippet()
}
function nextSnippet () {
if ($scope.selectIndex < $scope.filteredSnippets.length - 1) {
$scope.selectIndex += 1
}
if (resultList.clientHeight - 33 < resultList.children[$scope.selectIndex].offsetTop - resultList.scrollTop) {
resultList.scrollTop += 33
}
selectSnippet()
}
function writeCode (code) {
ipc.send('writeCode', code)
}
// Focusing Search Input
function focusSearchInput () {
document.getElementById('search-input').focus()
}
function hidePopUp () {
ipc.send('hidePopUp')
}
})
.directive('searchInput', function () {
return {
restrict: 'A',
link: function (scope, el, attr) {
el.on('keydown', function (e) {
// Down key => Focus on Result list
if (e.keyCode === 40) {
scope.$emit('nextSnippetRequested')
// e.preventDefault()
}
// Up key => Focus on Result list
if (e.keyCode === 38) {
scope.$emit('priorSnippetRequested')
// e.preventDefault()
}
// Up key => Focus on Result list
if (e.keyCode === 13) {
scope.$emit('snippetSubmitted')
}
// Esc key => Dismiss popup
if (e.keyCode === 27) {
ipc.send('hidePopUp')
e.preventDefault()
}
// TODO: Tab key => Auto complete
if (e.keyCode === 9) {
e.preventDefault()
}
})
}
}
})

View File

@@ -1,62 +0,0 @@
/* global angular */
angular.module('codexen.popup')
.constant('apiUrl', 'http://codexen-server-dev.elasticbeanstalk.com/')
.config(function ($authProvider, $httpProvider, apiUrl) {
$authProvider.baseUrl = apiUrl
$httpProvider.defaults.useXDomain = true
delete $httpProvider.defaults.headers.common['X-Requested-With']
})
angular.module('codexen.popup')
.factory('Snippet', function ($http, $auth, apiUrl) {
var findByUser = function (user) {
var url = apiUrl + 'snippets/search'
return $http.get(url, {
params: {
user: user
}
})
}
var findMine = function (params) {
var url = apiUrl + 'snippets/my'
return $http.get(url, {params: params})
}
var create = function (params) {
var url = apiUrl + 'snippets/create'
return $http.post(url, params)
}
var show = function (id, params) {
var url = apiUrl + 'snippets/id/' + id
return $http.get(url, {params: params})
}
var update = function (id, params) {
var url = apiUrl + 'snippets/id/' + id
return $http.put(url, params)
}
var destroy = function (id) {
var url = apiUrl + 'snippets/id/' + id
return $http.delete(url)
}
return {
findByUser: findByUser,
findMine: findMine,
create: create,
show: show,
delete: destroy,
update: update
}
})

View File

@@ -1,59 +0,0 @@
.popup-body
.search-block
padding: 5px
height:44px
position:absolute
top: 0
width: 100%
.result-block
position:absolute
top: 44px
bottom: 0
width: 100%
overflow: hidden
.left-pane
margin: 0
position: absolute
left: 0
top: 0
bottom: 0
width: 40%
overflow-y: auto
overflow-x: hidden
.result-list
list-style:none
padding: 0
border-right: 1px solid $baseBorderColor
li
&:nth-child(even)
background-color $baseBackgroundColor
&:nth-child(odd)
background-color lighten($baseBackgroundColor, 2%)
&.active
color: $textColorSelected
background-color: $btnPrimary
a
display:block
padding: 5px 10px
border-bottom 1px solid $baseBorderColor
.right-pane
position: absolute
left: 40%
top: 0
bottom: 0
width: 60%
overflow-y: auto
overflow-x: hidden
.result-detail-control
position: absolute
top: 0
width: 100%
height: 34px
.result-detail-content
position: absolute
top: 34px
bottom: 0
width: 100%

View File

@@ -1,13 +0,0 @@
@import '../../src/styles/_vars'
@import '../../src/styles/mixins/*'
@import '../../src/styles/_bootstrap'
@import '../../src/styles/_index'
@import '../../src/styles/_shared'
@import '../../src/styles/modals/*'
@import '../../src/styles/directives/*'
@import '../../src/styles/states/*'
@import '_popup'

View File

@@ -1,139 +0,0 @@
require('dotenv').load()
var env = process.env
var styl = require('gulp-stylus')
var autoprefixer = require('gulp-autoprefixer')
var templateCache = require('gulp-angular-templatecache')
var globby = require('globby')
var template = require('gulp-template')
var del = require('del')
var runSequence = require('run-sequence')
var plumber = require('gulp-plumber')
var notify = require('gulp-notify')
var changed = require('gulp-changed')
var livereload = require('gulp-livereload')
var merge = require('merge-stream')
var config = require('./build.config.js')
module.exports = function (gulp) {
gulp.task('elec-env', function () {
return gulp.src('tpls/env.js')
.pipe(template({
apiUrl: env.ELEC_API_URL
}))
.pipe(gulp.dest('electron_build/config'))
})
gulp.task('elec-js', function () {
var main = gulp.src('src/**/*.js')
.pipe(changed('electron_build'))
.pipe(gulp.dest('electron_build'))
var electron = gulp.src('electron_src/**/*.js')
.pipe(changed('electron_build/electron'))
.pipe(gulp.dest('electron_build/electron'))
return merge(main, electron)
})
gulp.task('elec-styl', function () {
return gulp.src('electron_src/styles/main.styl')
.pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
.pipe(styl())
.pipe(autoprefixer())
.pipe(gulp.dest('electron_build'))
.pipe(notify('Stylus!!'))
.pipe(livereload())
})
gulp.task('elec-tpls', function () {
var main = gulp.src('src/**/*.tpl.html')
.pipe(templateCache())
.pipe(gulp.dest('electron_build'))
var electron = gulp.src('electron_src/**/*.tpl.html')
.pipe(templateCache())
.pipe(gulp.dest('electron_build/electron'))
return merge(main, electron)
})
gulp.task('elec-index', function () {
var files = globby.sync(['electron_build/**/*', '!electron_build/vendor/**/*', '!electron_build/electron/**/*'])
var filter = function (files, ext) {
return files.filter(function (file) {
var reg = new RegExp('.+\.' + ext + '$')
return file.match(reg)
}).map(function (file) {
return file.replace('electron_build/', '')
})
}
var scripts = filter(files, 'js')
var styles = filter(files, 'css')
var main = gulp.src('src/index.html')
.pipe(template({
scripts: scripts,
styles: styles,
env: 'build'
}))
.pipe(gulp.dest('electron_build'))
.pipe(livereload())
var electron = gulp.src('electron_src/**/index.html')
.pipe(gulp.dest('electron_build/electron'))
return merge(main, electron)
})
gulp.task('elec-vendor', function () {
var vendors = config.vendors
var vendorFiles = vendors.map(function (vendor) {
return vendor.src
})
vendorFiles.push('node_modules/font-awesome/**/font-awesome.css')
vendorFiles.push('node_modules/font-awesome/**/fontawesome-webfont.*')
vendorFiles.push('node_modules/font-awesome/**/FontAwesome.*')
return gulp.src(vendorFiles)
.pipe(gulp.dest('electron_build/vendor'))
})
gulp.task('elec-resources', function () {
return gulp.src('resources/**/*')
.pipe(changed('electron_build/resources'))
.pipe(gulp.dest('electron_build/resources'))
})
gulp.task('elec-build', function (cb) {
runSequence(['elec-env', 'elec-js', 'elec-styl', 'elec-tpls', 'elec-vendor', 'elec-resources'], 'elec-index', cb)
})
gulp.task('elec-watch', function (cb) {
gulp.watch(['.env', 'tpls/env.js'], ['elec-env'])
gulp.watch(['src/**/*.js', 'electron_src/**/*.js'], ['elec-js'])
gulp.watch(['src/styles/**/*.styl', 'electron_src/styles/**/*.styl'], ['elec-styl'])
gulp.watch('src/**/*.tpl.html', ['elec-tpls'])
gulp.watch(['electron_build/**/*.js', 'src/index.html', 'src/index.html', 'electron_src/**/index.html'], ['elec-index'])
livereload.listen()
})
gulp.task('elec-del', function (cb) {
del(['electron_build/**/*'], cb)
})
gulp.task('elec', function (cb) {
runSequence('elec-del', 'elec-build', 'elec-watch', cb)
})
}

BIN
icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 367 B

407
main.js
View File

@@ -1,249 +1,228 @@
var app = require('app') // Module to control application life. var app = require('app')
var BrowserWindow = require('browser-window') // Module to create native browser window. var BrowserWindow = require('browser-window')
var Menu = require('menu')
var MenuItem = require('menu-item')
var Tray = require('tray')
var ipc = require('ipc')
var jetpack = require('fs-jetpack')
// Report crashes to our server.
require('crash-reporter').start() require('crash-reporter').start()
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the javascript object is GCed.
var mainWindow = null var mainWindow = null
var appIcon = null
var menu = null
var popUpWindow = null
var update = null
// app.on('window-all-closed', function () { // app.on('window-all-closed', function () {
// if (process.platform !== 'darwin') app.quit() // if (process.platform !== 'darwin') app.quit()
// }) // })
var clipboard = require('clipboard') var version = app.getVersion()
var Tray = require('tray') var versionText = (version == null || version.length === 0) ? 'DEV version' : 'v' + version
var notifier = require('node-notifier') var nn = require('node-notifier')
var autoUpdater = require('auto-updater')
var path = require('path')
var appIcon = null autoUpdater
.on('error', function (err, message) {
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'
})
})
.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
nn.notify({
title: 'Ready to Update!! ' + versionText,
icon: path.join(__dirname, 'browser/main/resources/favicon-230x230.png'),
message: 'Click tray icon to update app: ' + releaseName
})
update = quitAndUpdate
if (mainWindow != null && !mainWindow.webContents.isLoading()) {
mainWindow.webContents.send('update-available', 'Update available!')
}
})
app.on('ready', function () { app.on('ready', function () {
appIcon = new Tray('./icon.png') console.log('Version ' + version)
appIcon.setToolTip('This is my application.') autoUpdater.setFeedUrl('http://orbital.b00st.io/rokt33r/boost/latest?version=' + version)
appIcon.on('clicked', function () { autoUpdater.checkForUpdates()
if (mainWindow == null) { // menu start
makeNewMainWindow() var template = require('./modules/menu-template')
ipc.on('update-app', function (event, msg) {
if (update != null) {
update()
} }
mainWindow.show()
}) })
menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
// menu end
appIcon = new Tray(__dirname + '/tray-icon.png')
appIcon.setToolTip('Boost')
var trayMenu = new Menu()
trayMenu.append(new MenuItem({
label: 'Open main window',
click: function () {
if (mainWindow == null) {
makeNewMainWindow()
}
mainWindow.show()
}
}))
trayMenu.append(new MenuItem({
label: 'Update App',
click: function () {
if (update != null) {
update()
}
}
}))
trayMenu.append(new MenuItem({
label: 'Quit',
click: function () {
app.quit()
}
}))
appIcon.setContextMenu(trayMenu)
makeNewMainWindow() makeNewMainWindow()
var globalShortcut = require('global-shortcut')
var popUpWindow = new BrowserWindow({
width: 600,
height: 400,
show: false,
frame: false,
'web-preferences': {
'overlay-scrollbars': true,
'skip-taskbar': true
}
})
popUpWindow.loadUrl('file://' + __dirname + '/electron_build/electron/popup/index.html')
app.on('activate-with-no-open-windows', function () { app.on('activate-with-no-open-windows', function () {
if (mainWindow == null) { if (mainWindow == null) {
makeNewMainWindow() makeNewMainWindow()
return
} }
mainWindow.show() mainWindow.show()
}) })
popUpWindow = new BrowserWindow({
width: 600,
height: 400,
show: false,
frame: false,
'zoom-factor': 1.0,
'always-on-top': true,
'web-preferences': {
'overlay-scrollbars': true,
'skip-taskbar': true
},
'standard-window': false
})
popUpWindow.loadUrl('file://' + __dirname + '/browser/finder/index.electron.html')
popUpWindow.on('blur', function () { popUpWindow.on('blur', function () {
popUpWindow.hide() popUpWindow.hide()
}) })
popUpWindow.setVisibleOnAllWorkspaces(true) popUpWindow.setVisibleOnAllWorkspaces(true)
var hidePopUp = function () { var globalShortcut = require('global-shortcut')
if (fromMain) { console.log('jetpack launch')
var userDataPath = app.getPath('userData')
if (!jetpack.cwd(userDataPath).exists('keymap.json')) {
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
}
try {
global.keymap = JSON.parse(jetpack.cwd(userDataPath).read('keymap.json', 'utf-8'))
} catch (err) {
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
global.keymap = {}
}
if (global.keymap.toggleFinder == null) global.keymap.toggleFinder = 'ctrl+tab+shift'
var toggleFinderKey = global.keymap.toggleFinder
} else { try {
mainWindow ? mainWindow.hide() : null globalShortcut.register(toggleFinderKey, function () {
if (mainWindow != null && !mainWindow.isFocused()) {
mainWindow.hide()
}
popUpWindow.show()
})
} catch (err) {
console.log(err.name)
}
ipc.on('hotkeyUpdated', function (event, newKeymap) {
console.log('got new keymap')
console.log(newKeymap)
globalShortcut.unregisterAll()
global.keymap = JSON.parse(newKeymap)
jetpack.cwd(userDataPath).file('keymap.json', {content: JSON.stringify(global.keymap)})
var toggleFinderKey = global.keymap.toggleFinder != null ? global.keymap.toggleFinder : 'ctrl+tab+shift'
try {
globalShortcut.register(toggleFinderKey, function () {
if (mainWindow != null && !mainWindow.isFocused()) {
mainWindow.hide()
}
popUpWindow.show()
})
} catch (err) {
console.log(err.name)
}
})
global.hideFinder = function () {
if (mainWindow == null || !mainWindow.isVisible()) {
Menu.sendActionToFirstResponder('hide:') Menu.sendActionToFirstResponder('hide:')
} }
popUpWindow.hide() popUpWindow.hide()
} }
var ipc = require('ipc')
ipc.on('hidePopUp', function () {
hidePopUp()
})
ipc.on('writeCode', function (e, code) {
clipboard.writeText(code)
notifier.notify({
title: 'Write on clipboard!',
message: 'Ready to paste',
wait: false
}, function (err, res) {
})
hidePopUp()
})
var fromMain
// Register a 'ctrl+x' shortcut listener.
var ret = globalShortcut.register('ctrl+tab+shift', function () {
if (popUpWindow.isVisible()) {
hidePopUp()
return
}
fromMain = mainWindow ? mainWindow.isFocused() : false
popUpWindow.show()
})
if (!ret) console.log('registerion fails')
// MENU
var Menu = require('menu')
var template = [
{
label: 'Electron',
submenu: [
{
label: 'About Electron',
selector: 'orderFrontStandardAboutPanel:'
},
{
type: 'separator'
},
{
label: 'Services',
submenu: []
},
{
type: 'separator'
},
{
label: 'Hide Electron',
accelerator: 'Command+H',
selector: 'hide:'
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
selector: 'hideOtherApplications:'
},
{
label: 'Show All',
selector: 'unhideAllApplications:'
},
{
type: 'separator'
},
{
label: 'Quit',
accelerator: 'Command+Q',
click: function () { app.quit() }
}
]
},
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'Command+Z',
selector: 'undo:'
},
{
label: 'Redo',
accelerator: 'Shift+Command+Z',
selector: 'redo:'
},
{
type: 'separator'
},
{
label: 'Cut',
accelerator: 'Command+X',
selector: 'cut:'
},
{
label: 'Copy',
accelerator: 'Command+C',
selector: 'copy:'
},
{
label: 'Paste',
accelerator: 'Command+V',
selector: 'paste:'
},
{
label: 'Select All',
accelerator: 'Command+A',
selector: 'selectAll:'
}
]
},
{
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: 'Command+R',
click: function () { BrowserWindow.getFocusedWindow().reloadIgnoringCache() }
},
{
label: 'Toggle DevTools',
accelerator: 'Alt+Command+I',
click: function () { BrowserWindow.getFocusedWindow().toggleDevTools() }
}
]
},
{
label: 'Window',
submenu: [
{
label: 'Minimize',
accelerator: 'Command+M',
selector: 'performMiniaturize:'
},
{
label: 'Close',
accelerator: 'Command+W',
selector: 'performClose:'
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
selector: 'arrangeInFront:'
}
]
},
{
label: 'Help',
submenu: []
}
]
var menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
function makeNewMainWindow () {
console.log('new Window!')
mainWindow = new BrowserWindow({
width: 920,
height: 640,
'web-preferences': {
'overlay-scrollbars': true
}
})
mainWindow.loadUrl('file://' + __dirname + '/electron_build/index.html')
mainWindow.on('closed', function () {
console.log('main closed')
mainWindow = null
app.dock.hide()
})
app.dock.show()
}
}) })
function makeNewMainWindow () {
console.log('new Window!')
mainWindow = new BrowserWindow({
width: 1080,
height: 720,
'zoom-factor': 1.0,
'web-preferences': {
'overlay-scrollbars': true
},
'standard-window': false
})
if (update != null) {
mainWindow.webContents.on('did-finish-load', function () {
mainWindow.webContents.send('update-available', 'whoooooooh!')
})
}
mainWindow.loadUrl('file://' + __dirname + '/browser/main/index.electron.html')
mainWindow.webContents.on('new-window', function (e) {
e.preventDefault()
})
mainWindow.on('closed', function () {
console.log('main closed')
mainWindow = null
app.dock.hide()
})
app.dock.show()
}

10
modules/ace-modes.js Normal file
View File

@@ -0,0 +1,10 @@
var fs = require('fs')
module.exports = fs.readdirSync(__dirname + '/../browser/ace/src-min')
.filter(function (file) {
return file.match(/^mode-/)
})
.map(function (file) {
var match = file.match(/^mode-([a-z0-9\_]+).js$/)
return match[1]
})

128
modules/menu-template.js Normal file
View File

@@ -0,0 +1,128 @@
var BrowserWindow = require('browser-window')
module.exports = [
{
label: 'Electron',
submenu: [
{
label: 'About Boost',
selector: 'orderFrontStandardAboutPanel:'
},
{
type: 'separator'
},
{
label: 'Services',
submenu: []
},
{
type: 'separator'
},
{
label: 'Hide Boost',
accelerator: 'Command+H',
selector: 'hide:'
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
selector: 'hideOtherApplications:'
},
{
label: 'Show All',
selector: 'unhideAllApplications:'
},
{
type: 'separator'
},
{
label: 'Quit',
accelerator: 'Command+Q',
selector: 'terminate:'
}
]
},
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'Command+Z',
selector: 'undo:'
},
{
label: 'Redo',
accelerator: 'Shift+Command+Z',
selector: 'redo:'
},
{
type: 'separator'
},
{
label: 'Cut',
accelerator: 'Command+X',
selector: 'cut:'
},
{
label: 'Copy',
accelerator: 'Command+C',
selector: 'copy:'
},
{
label: 'Paste',
accelerator: 'Command+V',
selector: 'paste:'
},
{
label: 'Select All',
accelerator: 'Command+A',
selector: 'selectAll:'
}
]
},
{
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: 'Command+R',
click: function () {
BrowserWindow.getFocusedWindow().reload()
}
},
{
label: 'Toggle DevTools',
accelerator: 'Alt+Command+I',
click: function () {
BrowserWindow.getFocusedWindow().toggleDevTools()
}
}
]
},
{
label: 'Window',
submenu: [
{
label: 'Minimize',
accelerator: 'Command+M',
selector: 'performMiniaturize:'
},
{
label: 'Close',
accelerator: 'Command+W',
selector: 'performClose:'
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
selector: 'arrangeInFront:'
}
]
},
{
label: 'Help',
submenu: []
}
]

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