mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7a499a2b1 | ||
|
|
b646313b58 | ||
|
|
f3ce4ca803 | ||
|
|
93d99c0c47 | ||
|
|
ae1fc7572a | ||
|
|
1a527cca10 | ||
|
|
c625513924 | ||
|
|
3f58302a14 | ||
|
|
63b199c9c2 | ||
|
|
fc64c565db | ||
|
|
91e60fa82b | ||
|
|
0cc52c2206 | ||
|
|
2ffe4ba70b | ||
|
|
2afd7e3687 | ||
|
|
a0f8d13c4f | ||
|
|
2571ea021a | ||
|
|
6950e05b6a | ||
|
|
7eb767a268 | ||
|
|
8e64abc4bc | ||
|
|
52df793a74 | ||
|
|
8e44a421a2 | ||
|
|
7f4ccdcac8 | ||
|
|
03e8de2f62 | ||
|
|
8b04eecc90 | ||
|
|
16bcd86792 | ||
|
|
be3c519a57 | ||
|
|
8776cb1cea | ||
|
|
4c94503f9a | ||
|
|
48f57376d3 | ||
|
|
958469f526 | ||
|
|
2a774a7bb6 | ||
|
|
a872ad9d8b | ||
|
|
2499a05473 | ||
|
|
6b66893ea4 | ||
|
|
529c27aed5 | ||
|
|
70fc0afbc4 | ||
|
|
09f81fd0d6 | ||
|
|
af7f2d4d5e | ||
|
|
3bd5d6b9f6 | ||
|
|
57912b5a5a | ||
|
|
a05f5b9737 | ||
|
|
1963b586ac | ||
|
|
3b9ad59849 | ||
|
|
79e0e5668d | ||
|
|
0e8edf0c72 | ||
|
|
24e2544544 | ||
|
|
f3732c76ea | ||
|
|
a4c72a9a86 | ||
|
|
455610e586 | ||
|
|
634d58b3ca | ||
|
|
27bbd77e8c | ||
|
|
d8ae77ded7 | ||
|
|
0648c04728 | ||
|
|
57c26e3b4a | ||
|
|
b03afff994 | ||
|
|
77f9e60177 | ||
|
|
35bb792496 | ||
|
|
8a87304800 | ||
|
|
64bbe053f8 | ||
|
|
d3f420bf6d | ||
|
|
7fcaaa297a | ||
|
|
7c2d2044a9 | ||
|
|
aa32f59dc6 | ||
|
|
182af99e7c | ||
|
|
5b520a7a81 | ||
|
|
364917c910 | ||
|
|
ca7b9c786a | ||
|
|
15c2363098 | ||
|
|
1a11095121 | ||
|
|
2b384b1d15 | ||
|
|
a1d61edb9c | ||
|
|
96a8687896 | ||
|
|
0448773682 | ||
|
|
57998ba727 | ||
|
|
de83447cb3 | ||
|
|
eba19468d5 | ||
|
|
65c78df671 | ||
|
|
a7096aa89f | ||
|
|
15a50ef452 | ||
|
|
04036e5c87 | ||
|
|
2bbb5ef74e | ||
|
|
91eb7feb3c | ||
|
|
978d77142c | ||
|
|
e36478b9ac | ||
|
|
e1fe4dd693 | ||
|
|
b1ee949b1c | ||
|
|
a0e5f8e97e | ||
|
|
e9cfb2c4ee | ||
|
|
190b6edfb1 | ||
|
|
80a0c59f87 | ||
|
|
823fdec705 | ||
|
|
fe87dcced7 | ||
|
|
137eb44516 | ||
|
|
f60d957102 | ||
|
|
8f0b04504f | ||
|
|
2c39d8b1c8 | ||
|
|
d4d1c32288 | ||
|
|
e4f39d2b6a | ||
|
|
e5a2bfbcbd | ||
|
|
de3b76b31d | ||
|
|
53455496bf | ||
|
|
cc2a2f6dfb | ||
|
|
ee4ac7371c | ||
|
|
d5265407b9 | ||
|
|
954b3e9fc5 | ||
|
|
7d9894bef7 | ||
|
|
3b34698e8b | ||
|
|
263cb581c4 | ||
|
|
1c9cb4516c | ||
|
|
ac4ceccb4f | ||
|
|
e731b7882d | ||
|
|
84e0728ff3 | ||
|
|
666bc18e91 | ||
|
|
8f83124a0d | ||
|
|
ee91daad7e | ||
|
|
ee78c0d33b | ||
|
|
1318abd37e | ||
|
|
76a031a8c9 | ||
|
|
09482ebcf3 | ||
|
|
67424f2d3a | ||
|
|
51f530ffbe | ||
|
|
013f96a754 | ||
|
|
df6a018fb6 | ||
|
|
409eaf54c1 | ||
|
|
7e04fd342c | ||
|
|
1fe15bc6a5 | ||
|
|
ff1bffbb55 | ||
|
|
b28b18a19a | ||
|
|
bbc3c85212 | ||
|
|
26a08fac06 | ||
|
|
da9d7a4336 | ||
|
|
46c6555f94 | ||
|
|
3e980fd2d4 | ||
|
|
fb1462f669 | ||
|
|
41e1630aac | ||
|
|
ef84c4e3da | ||
|
|
61cc44cc83 | ||
|
|
c20cbe7d66 | ||
|
|
2f4af3223b | ||
|
|
e4b2c42897 | ||
|
|
746df9277c | ||
|
|
8428588a4c | ||
|
|
83a8f4b911 | ||
|
|
2736024cb7 | ||
|
|
9a32ca893e | ||
|
|
59d3c6c94f | ||
|
|
388027b731 | ||
|
|
8abdedc11d | ||
|
|
9758f5baa8 | ||
|
|
248262a597 | ||
|
|
cc0f2c7c7f | ||
|
|
72f6468d12 | ||
|
|
522c0edd90 | ||
|
|
d338f217fe | ||
|
|
ca79857386 | ||
|
|
60e551e273 | ||
|
|
954e148be3 | ||
|
|
3d0b79f674 | ||
|
|
d9442aa23c | ||
|
|
ba0daf4452 | ||
|
|
8d9cd5bbd1 | ||
|
|
186b877c09 | ||
|
|
5ed2dfccd1 | ||
|
|
911cfd8642 | ||
|
|
3539bd1e79 | ||
|
|
f56df7c16d | ||
|
|
c507dfa6c4 | ||
|
|
f6d2e898dc | ||
|
|
326c7a93fb | ||
|
|
58381b8062 | ||
|
|
0899cea4b4 | ||
|
|
7459e937b5 | ||
|
|
55db0bebbb | ||
|
|
0bdb8142c6 | ||
|
|
88ee94d4b6 | ||
|
|
1df4ed0fe9 | ||
|
|
2a339a2935 | ||
|
|
a1810e6023 | ||
|
|
832ca3347c | ||
|
|
9d2b64e82b | ||
|
|
9a5e4b3f54 | ||
|
|
e5e8032ba1 | ||
|
|
5356e68b51 | ||
|
|
cd94c625a7 | ||
|
|
972a3746a1 | ||
|
|
a9e12e4384 | ||
|
|
1690e6420f | ||
|
|
acdf61f7ab | ||
|
|
2e4fc557ea | ||
|
|
979dcead49 | ||
|
|
116ddf345d | ||
|
|
366805a64f | ||
|
|
1fee2a846a | ||
|
|
3f54eb52b2 | ||
|
|
a3847ce1c9 | ||
|
|
1e7415b692 | ||
|
|
ff950ef28a | ||
|
|
51bd12c6cf | ||
|
|
5fa37dbffb | ||
|
|
c6307e4ad3 | ||
|
|
06a54d451c | ||
|
|
e317075815 |
20
.babelrc
Normal file
20
.babelrc
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"stage": 0,
|
||||
"env": {
|
||||
"development": {
|
||||
"plugins": ["react-transform"],
|
||||
"extra": {
|
||||
"react-transform": {
|
||||
"transforms": [{
|
||||
"transform": "react-transform-hmr",
|
||||
"imports": ["react"],
|
||||
"locals": ["module"]
|
||||
}, {
|
||||
"transform": "react-transform-catch-errors",
|
||||
"imports": ["react", "redbox-react"]
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
build/
|
||||
node_modules/
|
||||
electron_build/
|
||||
.env
|
||||
node_modules/*
|
||||
!node_modules/boost
|
||||
dist/
|
||||
vendor/
|
||||
compiled
|
||||
/secret
|
||||
|
||||
8
.gitmodules
vendored
8
.gitmodules
vendored
@@ -1,6 +1,4 @@
|
||||
[submodule "browser/ace"]
|
||||
path = browser/ace
|
||||
[submodule "submodules/ace"]
|
||||
path = submodules/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
|
||||
branch = master
|
||||
|
||||
2
LICENSE
Normal file
2
LICENSE
Normal file
@@ -0,0 +1,2 @@
|
||||
本製品をインストール、または使用することによって、お客様は利用規約(
|
||||
https://b00st.io/regulations.html)より拘束されることに承諾されたものとします。利用規約に同意されない場合、Boostnoteは、お客様に本製品のインストール、使用のいずれも許諾できません。
|
||||
161
atom-lib/finder-window.js
Normal file
161
atom-lib/finder-window.js
Normal file
@@ -0,0 +1,161 @@
|
||||
const electron = require('electron')
|
||||
const BrowserWindow = electron.BrowserWindow
|
||||
const Menu = electron.Menu
|
||||
const MenuItem = electron.MenuItem
|
||||
const app = electron.app
|
||||
const ipcMain = electron.ipcMain
|
||||
const Tray = electron.Tray
|
||||
const path = require('path')
|
||||
const nodeIpc = require('@rokt33r/node-ipc')
|
||||
|
||||
var appQuit = false
|
||||
var isFinderLoaded = false
|
||||
|
||||
nodeIpc.config.id = 'finder'
|
||||
nodeIpc.config.retry = 1500
|
||||
nodeIpc.config.silent = true
|
||||
|
||||
nodeIpc.connectTo(
|
||||
'main',
|
||||
path.join(app.getPath('userData'), 'boost.service'),
|
||||
function () {
|
||||
nodeIpc.of.main.on(
|
||||
'error',
|
||||
function (err) {
|
||||
nodeIpc.log('<< ## err ##'.rainbow, nodeIpc.config.delay)
|
||||
nodeIpc.log(err)
|
||||
}
|
||||
)
|
||||
nodeIpc.of.main.on(
|
||||
'connect',
|
||||
function () {
|
||||
nodeIpc.log('<< ## connected to world ##'.rainbow, nodeIpc.config.delay)
|
||||
}
|
||||
)
|
||||
nodeIpc.of.main.on(
|
||||
'disconnect',
|
||||
function () {
|
||||
nodeIpc.log('<< disconnected from main'.notice)
|
||||
if (process.platform === 'darwin') {
|
||||
appQuit = true
|
||||
app.quit()
|
||||
}
|
||||
}
|
||||
)
|
||||
nodeIpc.of.main.on(
|
||||
'message',
|
||||
function (payload) {
|
||||
switch (payload.type) {
|
||||
case 'open-finder':
|
||||
if (isFinderLoaded) openFinder()
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
function emit (type, data) {
|
||||
var payload = {
|
||||
type: type,
|
||||
data: data
|
||||
}
|
||||
nodeIpc.of.main.emit('message', payload)
|
||||
}
|
||||
|
||||
var config = {
|
||||
width: 640,
|
||||
height: 400,
|
||||
show: false,
|
||||
frame: false,
|
||||
resizable: false,
|
||||
'zoom-factor': 1.0,
|
||||
'web-preferences': {
|
||||
'overlay-scrollbars': true,
|
||||
'skip-taskbar': true
|
||||
},
|
||||
'standard-window': false
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
config['always-on-top'] = true
|
||||
}
|
||||
|
||||
var finderWindow = new BrowserWindow(config)
|
||||
|
||||
var url = path.resolve(__dirname, '../browser/finder/index.html')
|
||||
|
||||
finderWindow.loadURL('file://' + url)
|
||||
finderWindow.setSkipTaskbar(true)
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
finderWindow.setVisibleOnAllWorkspaces(true)
|
||||
}
|
||||
|
||||
finderWindow.on('blur', function () {
|
||||
hideFinder()
|
||||
})
|
||||
|
||||
finderWindow.on('close', function (e) {
|
||||
if (process.platform === 'darwin') {
|
||||
if (appQuit) return true
|
||||
e.preventDefault()
|
||||
finderWindow.hide()
|
||||
}
|
||||
})
|
||||
|
||||
var appIcon = new Tray(path.join(__dirname, '../resources/tray-icon.png'))
|
||||
appIcon.setToolTip('Boost')
|
||||
|
||||
var trayMenu = new Menu()
|
||||
trayMenu.append(new MenuItem({
|
||||
label: 'Open Main window',
|
||||
click: function () {
|
||||
emit('show-main-window')
|
||||
}
|
||||
}))
|
||||
trayMenu.append(new MenuItem({
|
||||
label: 'Open Finder window',
|
||||
click: function () {
|
||||
openFinder()
|
||||
}
|
||||
}))
|
||||
trayMenu.append(new MenuItem({
|
||||
label: 'Quit',
|
||||
click: function () {
|
||||
emit('quit-app')
|
||||
}
|
||||
}))
|
||||
|
||||
appIcon.setContextMenu(trayMenu)
|
||||
appIcon.on('click', function (e) {
|
||||
e.preventDefault()
|
||||
appIcon.popUpContextMenu(trayMenu)
|
||||
})
|
||||
|
||||
ipcMain.on('copy-finder', function () {
|
||||
emit('copy-finder')
|
||||
})
|
||||
|
||||
ipcMain.on('hide-finder', function () {
|
||||
hideFinder()
|
||||
})
|
||||
|
||||
finderWindow.webContents.on('did-finish-load', function () {
|
||||
isFinderLoaded = true
|
||||
})
|
||||
|
||||
function openFinder () {
|
||||
if (isFinderLoaded) finderWindow.show()
|
||||
}
|
||||
function hideFinder () {
|
||||
if (process.platform === 'win32') {
|
||||
finderWindow.minimize()
|
||||
return
|
||||
}
|
||||
if (process.platform === 'darwin') {
|
||||
Menu.sendActionToFirstResponder('hide:')
|
||||
}
|
||||
finderWindow.hide()
|
||||
}
|
||||
module.exports = finderWindow
|
||||
29
atom-lib/main-window.js
Normal file
29
atom-lib/main-window.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const electron = require('electron')
|
||||
const app = electron.app
|
||||
const BrowserWindow = electron.BrowserWindow
|
||||
const path = require('path')
|
||||
|
||||
var mainWindow = new BrowserWindow({
|
||||
width: 1080,
|
||||
height: 720,
|
||||
'zoom-factor': 1.0,
|
||||
'web-preferences': {
|
||||
'overlay-scrollbars': true
|
||||
},
|
||||
'standard-window': false
|
||||
})
|
||||
|
||||
const url = path.resolve(__dirname, '../browser/main/index.html')
|
||||
|
||||
mainWindow.loadURL('file://' + url)
|
||||
|
||||
mainWindow.webContents.on('new-window', function (e) {
|
||||
e.preventDefault()
|
||||
})
|
||||
|
||||
app.on('activate', function () {
|
||||
if (mainWindow == null) return null
|
||||
mainWindow.show()
|
||||
})
|
||||
|
||||
module.exports = mainWindow
|
||||
160
atom-lib/menu-template.js
Normal file
160
atom-lib/menu-template.js
Normal file
@@ -0,0 +1,160 @@
|
||||
const electron = require('electron')
|
||||
const BrowserWindow = electron.BrowserWindow
|
||||
const shell = electron.shell
|
||||
|
||||
var boost = {
|
||||
label: 'Boostnote',
|
||||
submenu: [
|
||||
{
|
||||
label: 'About Boostnote',
|
||||
selector: 'orderFrontStandardAboutPanel:'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Services',
|
||||
submenu: []
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Hide Boostnote',
|
||||
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:'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
var edit = {
|
||||
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:'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
var view = {
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: (function () {
|
||||
if (process.platform === 'darwin') return 'Command+R'
|
||||
else return 'Ctrl+R'
|
||||
})(),
|
||||
click: function () {
|
||||
BrowserWindow.getFocusedWindow().reload()
|
||||
}
|
||||
},
|
||||
// {
|
||||
// label: 'Toggle Developer Tools',
|
||||
// accelerator: (function () {
|
||||
// if (process.platform === 'darwin') return 'Alt+Command+I'
|
||||
// else return 'Ctrl+Shift+I'
|
||||
// })(),
|
||||
// click: function (item, focusedWindow) {
|
||||
// if (focusedWindow) BrowserWindow.getFocusedWindow().toggleDevTools()
|
||||
// }
|
||||
// }
|
||||
]
|
||||
}
|
||||
|
||||
var window = {
|
||||
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:'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
var help = {
|
||||
label: 'Help',
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Boostnote official site',
|
||||
click: function () { shell.openExternal('https://b00st.io/') }
|
||||
},
|
||||
{
|
||||
label: 'Tutorial page',
|
||||
click: function () { shell.openExternal('https://b00st.io/tutorial.html') }
|
||||
},
|
||||
{
|
||||
label: 'Discussions',
|
||||
click: function () { shell.openExternal('https://github.com/BoostIO/boost-app-discussions/issues') }
|
||||
},
|
||||
{
|
||||
label: 'Changelog',
|
||||
click: function () { shell.openExternal('https://github.com/BoostIO/boost-releases/blob/master/changelog.md') }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
module.exports = process.platform === 'darwin'
|
||||
? [boost, edit, view, window, help]
|
||||
: [view, help]
|
||||
11
bower.json
11
bower.json
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "codexen-app",
|
||||
"dependencies": {
|
||||
"react": "~0.13.3",
|
||||
"fontawesome": "~4.3.0",
|
||||
"react-router": "~0.13.3",
|
||||
"reflux": "~0.2.8",
|
||||
"moment": "~2.10.3",
|
||||
"markdown-it": "~4.3.1"
|
||||
}
|
||||
}
|
||||
Submodule browser/ace deleted from 0982db4853
Submodule browser/electron-stylus deleted from 6d86de7c66
@@ -1,42 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,15 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
onChange: React.PropTypes.func,
|
||||
search: React.PropTypes.string
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className='FinderInput'>
|
||||
<input value={this.props.search} onChange={this.props.onChange} type='text'/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,79 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
articles: React.PropTypes.arrayOf,
|
||||
currentArticle: React.PropTypes.shape({
|
||||
id: React.PropTypes.number,
|
||||
type: React.PropTypes.string
|
||||
}),
|
||||
selectArticle: React.PropTypes.func
|
||||
},
|
||||
componentDidUpdate: function () {
|
||||
var index = this.props.articles.indexOf(this.props.currentArticle)
|
||||
var el = React.findDOMNode(this)
|
||||
var li = el.querySelectorAll('li')[index]
|
||||
|
||||
if (li == null) {
|
||||
return
|
||||
}
|
||||
|
||||
var overflowBelow = el.clientHeight + el.scrollTop < li.offsetTop + li.clientHeight
|
||||
if (overflowBelow) {
|
||||
el.scrollTop = li.offsetTop + li.clientHeight - el.clientHeight
|
||||
}
|
||||
var overflowAbove = el.scrollTop > li.offsetTop
|
||||
if (overflowAbove) {
|
||||
el.scrollTop = li.offsetTop
|
||||
}
|
||||
},
|
||||
handleArticleClick: function (article) {
|
||||
return function () {
|
||||
this.props.selectArticle(article)
|
||||
}.bind(this)
|
||||
},
|
||||
render: function () {
|
||||
var list = this.props.articles.map(function (article) {
|
||||
if (article == null) {
|
||||
return (
|
||||
<li className={isActive ? 'active' : ''}>
|
||||
<div className='articleItem'>Undefined</div>
|
||||
<div className='divider'/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
var isActive = this.props.currentArticle != null && (article.type === this.props.currentArticle.type && article.id === this.props.currentArticle.id)
|
||||
if (article.type === 'code') {
|
||||
return (
|
||||
<li onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
|
||||
<div className='articleItem'><i className='fa fa-code fa-fw'/> {article.description}</div>
|
||||
<div className='divider'/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
if (article.type === 'note') {
|
||||
return (
|
||||
<li onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
|
||||
<div className='articleItem'><i className='fa fa-file-text-o fa-fw'/> {article.title}</div>
|
||||
<div className='divider'/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<li className={isActive ? 'active' : ''}>
|
||||
<div className='articleItem'>Undefined</div>
|
||||
<div className='divider'/>
|
||||
</li>
|
||||
)
|
||||
}.bind(this))
|
||||
|
||||
return (
|
||||
<div className='FinderList'>
|
||||
<ul>
|
||||
{list}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
44
browser/finder/FinderDetail.js
Normal file
44
browser/finder/FinderDetail.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import CodeEditor from 'boost/components/CodeEditor'
|
||||
import MarkdownPreview from 'boost/components/MarkdownPreview'
|
||||
import ModeIcon from 'boost/components/ModeIcon'
|
||||
|
||||
export default class FinderDetail extends React.Component {
|
||||
render () {
|
||||
let { activeArticle } = this.props
|
||||
|
||||
if (activeArticle != null) {
|
||||
return (
|
||||
<div className='FinderDetail'>
|
||||
<div className='header'>
|
||||
<div className='left'>
|
||||
<ModeIcon mode={activeArticle.mode}/> {activeArticle.title}
|
||||
</div>
|
||||
<div className='right'>
|
||||
<button onClick={this.props.saveToClipboard} className='clipboardBtn'>
|
||||
<i className='fa fa-clipboard fa-fw'/>
|
||||
<span className='tooltip'>Copy to clipboard (Enter)</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='content'>
|
||||
{activeArticle.mode === 'markdown'
|
||||
? <MarkdownPreview content={activeArticle.content}/>
|
||||
: <CodeEditor readOnly mode={activeArticle.mode} code={activeArticle.content}/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className='FinderDetail'>
|
||||
<div className='nothing'>Nothing selected</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FinderDetail.propTypes = {
|
||||
activeArticle: PropTypes.shape(),
|
||||
saveToClipboard: PropTypes.func
|
||||
}
|
||||
16
browser/finder/FinderInput.js
Normal file
16
browser/finder/FinderInput.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
export default class FinderInput extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<div className='FinderInput'>
|
||||
<input ref='input' value={this.props.value} onChange={this.props.handleSearchChange} type='text'/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FinderInput.propTypes = {
|
||||
handleSearchChange: PropTypes.func,
|
||||
value: PropTypes.string
|
||||
}
|
||||
71
browser/finder/FinderList.js
Normal file
71
browser/finder/FinderList.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ModeIcon from 'boost/components/ModeIcon'
|
||||
import { selectArticle } from './actions'
|
||||
|
||||
export default class FinderList extends React.Component {
|
||||
componentDidUpdate () {
|
||||
var index = this.props.articles.indexOf(this.props.activeArticle)
|
||||
var el = ReactDOM.findDOMNode(this)
|
||||
var li = el.querySelectorAll('li')[index]
|
||||
|
||||
if (li == null) {
|
||||
return
|
||||
}
|
||||
|
||||
var overflowBelow = el.clientHeight + el.scrollTop < li.offsetTop + li.clientHeight
|
||||
if (overflowBelow) {
|
||||
el.scrollTop = li.offsetTop + li.clientHeight - el.clientHeight
|
||||
}
|
||||
var overflowAbove = el.scrollTop > li.offsetTop
|
||||
if (overflowAbove) {
|
||||
el.scrollTop = li.offsetTop
|
||||
}
|
||||
}
|
||||
|
||||
handleArticleClick (article) {
|
||||
return (e) => {
|
||||
let { dispatch } = this.props
|
||||
dispatch(selectArticle(article.key))
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
let articleElements = this.props.articles.map(function (article) {
|
||||
if (article == null) {
|
||||
return (
|
||||
<li className={isActive ? 'active' : ''}>
|
||||
<div className='articleItem'>Undefined</div>
|
||||
<div className='divider'/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
var isActive = this.props.activeArticle != null && (article.key === this.props.activeArticle.key)
|
||||
return (
|
||||
<li key={'article-' + article.key} onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
|
||||
<div className='articleItem'>
|
||||
<ModeIcon mode={article.mode}/> {article.title}</div>
|
||||
<div className='divider'/>
|
||||
</li>
|
||||
)
|
||||
}.bind(this))
|
||||
|
||||
return (
|
||||
<div className='FinderList'>
|
||||
<ul>
|
||||
{articleElements}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FinderList.propTypes = {
|
||||
articles: PropTypes.array,
|
||||
activeArticle: PropTypes.shape({
|
||||
type: PropTypes.string,
|
||||
key: PropTypes.string
|
||||
}),
|
||||
dispatch: PropTypes.func
|
||||
}
|
||||
39
browser/finder/actions.js
Normal file
39
browser/finder/actions.js
Normal file
@@ -0,0 +1,39 @@
|
||||
export const SELECT_ARTICLE = 'SELECT_ARTICLE'
|
||||
export const SEARCH_ARTICLE = 'SEARCH_ARTICLE'
|
||||
export const REFRESH_DATA = 'REFRESH_DATA'
|
||||
|
||||
export function selectArticle (key) {
|
||||
return {
|
||||
type: SELECT_ARTICLE,
|
||||
data: { key }
|
||||
}
|
||||
}
|
||||
|
||||
export function searchArticle (input) {
|
||||
return {
|
||||
type: SEARCH_ARTICLE,
|
||||
data: { input }
|
||||
}
|
||||
}
|
||||
|
||||
export function refreshData (data) {
|
||||
console.log('refreshing data')
|
||||
let { folders, articles } = data
|
||||
|
||||
return {
|
||||
type: REFRESH_DATA,
|
||||
data: {
|
||||
articles,
|
||||
folders
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
SELECT_ARTICLE,
|
||||
SEARCH_ARTICLE,
|
||||
REFRESH_DATA,
|
||||
selectArticle,
|
||||
searchArticle,
|
||||
refreshData
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<title>CodeXen Popup</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<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>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<title>Boostnote Finder</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
|
||||
|
||||
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
|
||||
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
|
||||
<link rel="stylesheet" href="../../node_modules/highlight.js/styles/xcode.css">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('../../resources/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="content"></div>
|
||||
<script src="../../submodules/ace/src-min/ace.js"></script>
|
||||
<script>
|
||||
const electron = require('electron')
|
||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
||||
const _ = require('lodash')
|
||||
var scriptUrl = _.find(electron.remote.process.argv, a => a === '--hot')
|
||||
? 'http://localhost:8080/assets/finder.js'
|
||||
: '../../compiled/finder.js'
|
||||
var scriptEl=document.createElement('script')
|
||||
scriptEl.setAttribute("type","text/javascript")
|
||||
scriptEl.setAttribute("src", scriptUrl)
|
||||
document.getElementsByTagName("head")[0].appendChild(scriptEl)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
259
browser/finder/index.js
Normal file
259
browser/finder/index.js
Normal file
@@ -0,0 +1,259 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { connect, Provider } from 'react-redux'
|
||||
import reducer from './reducer'
|
||||
import { createStore } from 'redux'
|
||||
import FinderInput from './FinderInput'
|
||||
import FinderList from './FinderList'
|
||||
import FinderDetail from './FinderDetail'
|
||||
import actions, { selectArticle, searchArticle } from './actions'
|
||||
import _ from 'lodash'
|
||||
import dataStore from 'boost/dataStore'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote, clipboard, ipcRenderer } = electron
|
||||
const path = require('path')
|
||||
|
||||
function hideFinder () {
|
||||
ipcRenderer.send('hide-finder')
|
||||
}
|
||||
|
||||
function notify (title, options) {
|
||||
if (process.platform === 'win32') {
|
||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
||||
options.silent = false
|
||||
}
|
||||
return new window.Notification(title, options)
|
||||
}
|
||||
|
||||
require('../styles/finder/index.styl')
|
||||
|
||||
const FOLDER_FILTER = 'FOLDER_FILTER'
|
||||
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
|
||||
const TEXT_FILTER = 'TEXT_FILTER'
|
||||
const TAG_FILTER = 'TAG_FILTER'
|
||||
|
||||
class FinderMain extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.keyDownHandler = e => this.handleKeyDown(e)
|
||||
document.addEventListener('keydown', this.keyDownHandler)
|
||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
||||
this.focusHandler = e => {
|
||||
let { dispatch } = this.props
|
||||
|
||||
dispatch(searchArticle(''))
|
||||
}
|
||||
window.addEventListener('focus', this.focusHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('keydown', this.keyDownHandler)
|
||||
window.removeEventListener('focus', this.focusHandler)
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
if (e.keyCode === 38) {
|
||||
this.selectPrevious()
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
if (e.keyCode === 40) {
|
||||
this.selectNext()
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
if (e.keyCode === 13) {
|
||||
this.saveToClipboard()
|
||||
e.preventDefault()
|
||||
}
|
||||
if (e.keyCode === 27) {
|
||||
hideFinder()
|
||||
e.preventDefault()
|
||||
}
|
||||
if (e.keyCode === 91 || e.metaKey) {
|
||||
return
|
||||
}
|
||||
|
||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
||||
}
|
||||
|
||||
saveToClipboard () {
|
||||
let { activeArticle } = this.props
|
||||
clipboard.writeText(activeArticle.content)
|
||||
|
||||
ipcRenderer.send('copy-finder')
|
||||
notify('Saved to Clipboard!', {
|
||||
body: 'Paste it wherever you want!'
|
||||
})
|
||||
hideFinder()
|
||||
}
|
||||
|
||||
handleSearchChange (e) {
|
||||
let { dispatch } = this.props
|
||||
|
||||
dispatch(searchArticle(e.target.value))
|
||||
}
|
||||
|
||||
selectArticle (article) {
|
||||
this.setState({currentArticle: article})
|
||||
}
|
||||
|
||||
selectPrevious () {
|
||||
let { activeArticle, dispatch } = this.props
|
||||
let index = this.refs.finderList.props.articles.indexOf(activeArticle)
|
||||
let previousArticle = this.refs.finderList.props.articles[index - 1]
|
||||
if (previousArticle != null) dispatch(selectArticle(previousArticle.key))
|
||||
}
|
||||
|
||||
selectNext () {
|
||||
let { activeArticle, dispatch } = this.props
|
||||
let index = this.refs.finderList.props.articles.indexOf(activeArticle)
|
||||
let previousArticle = this.refs.finderList.props.articles[index + 1]
|
||||
if (previousArticle != null) dispatch(selectArticle(previousArticle.key))
|
||||
}
|
||||
|
||||
render () {
|
||||
let { articles, activeArticle, status, dispatch } = this.props
|
||||
let saveToClipboard = () => this.saveToClipboard()
|
||||
return (
|
||||
<div onClick={e => this.handleClick(e)} className='Finder'>
|
||||
<FinderInput
|
||||
handleSearchChange={e => this.handleSearchChange(e)}
|
||||
ref='finderInput'
|
||||
onChange={this.handleChange}
|
||||
value={status.search}
|
||||
/>
|
||||
<FinderList
|
||||
ref='finderList'
|
||||
activeArticle={activeArticle}
|
||||
articles={articles}
|
||||
dispatch={dispatch}
|
||||
selectArticle={article => this.selectArticle(article)}
|
||||
/>
|
||||
<FinderDetail
|
||||
activeArticle={activeArticle}
|
||||
saveToClipboard={saveToClipboard}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FinderMain.propTypes = {
|
||||
articles: PropTypes.array,
|
||||
activeArticle: PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
tags: PropTypes.array,
|
||||
title: PropTypes.string,
|
||||
content: PropTypes.string
|
||||
}),
|
||||
status: PropTypes.shape(),
|
||||
dispatch: PropTypes.func
|
||||
}
|
||||
|
||||
// Ignore invalid key
|
||||
function ignoreInvalidKey (key) {
|
||||
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/)
|
||||
}
|
||||
|
||||
// Build filter object by key
|
||||
function buildFilter (key) {
|
||||
if (key.match(/^\/\/.+/)) {
|
||||
return {type: FOLDER_EXACT_FILTER, value: key.match(/^\/\/(.+)$/)[1]}
|
||||
}
|
||||
if (key.match(/^\/.+/)) {
|
||||
return {type: FOLDER_FILTER, value: key.match(/^\/(.+)$/)[1]}
|
||||
}
|
||||
if (key.match(/^#(.+)/)) {
|
||||
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
|
||||
}
|
||||
return {type: TEXT_FILTER, value: key}
|
||||
}
|
||||
|
||||
function isContaining (target, needle) {
|
||||
return target.match(new RegExp(_.escapeRegExp(needle)))
|
||||
}
|
||||
|
||||
function startsWith (target, needle) {
|
||||
return target.match(new RegExp('^' + _.escapeRegExp(needle)))
|
||||
}
|
||||
|
||||
function remap (state) {
|
||||
let { articles, folders, status } = state
|
||||
|
||||
let filters = status.search.split(' ')
|
||||
.map(key => key.trim())
|
||||
.filter(ignoreInvalidKey)
|
||||
.map(buildFilter)
|
||||
|
||||
let folderExactFilters = filters.filter(filter => filter.type === FOLDER_EXACT_FILTER)
|
||||
let folderFilters = filters.filter(filter => filter.type === FOLDER_FILTER)
|
||||
let textFilters = filters.filter(filter => filter.type === TEXT_FILTER)
|
||||
let tagFilters = filters.filter(filter => filter.type === TAG_FILTER)
|
||||
|
||||
let targetFolders
|
||||
if (folders != null) {
|
||||
let exactTargetFolders = folders.filter(folder => {
|
||||
return _.find(folderExactFilters, filter => isContaining(folder.name, filter.value))
|
||||
})
|
||||
let fuzzyTargetFolders = folders.filter(folder => {
|
||||
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
|
||||
})
|
||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
||||
|
||||
if (targetFolders.length > 0) {
|
||||
articles = articles.filter(article => {
|
||||
return _.findWhere(targetFolders, {key: article.FolderKey})
|
||||
})
|
||||
}
|
||||
|
||||
if (textFilters.length > 0) {
|
||||
articles = textFilters.reduce((articles, textFilter) => {
|
||||
return articles.filter(article => {
|
||||
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
|
||||
})
|
||||
}, articles)
|
||||
}
|
||||
|
||||
if (tagFilters.length > 0) {
|
||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
||||
return articles.filter(article => {
|
||||
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
|
||||
})
|
||||
}, articles)
|
||||
}
|
||||
}
|
||||
|
||||
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
||||
if (activeArticle == null) activeArticle = articles[0]
|
||||
|
||||
return {
|
||||
articles,
|
||||
activeArticle,
|
||||
status
|
||||
}
|
||||
}
|
||||
|
||||
var Finder = connect(remap)(FinderMain)
|
||||
var store = createStore(reducer)
|
||||
|
||||
function refreshData () {
|
||||
let data = dataStore.getData()
|
||||
store.dispatch(actions.refreshData(data))
|
||||
}
|
||||
|
||||
window.onfocus = e => {
|
||||
refreshData()
|
||||
}
|
||||
|
||||
ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<Finder/>
|
||||
</Provider>
|
||||
), document.getElementById('content'), function () {
|
||||
refreshData()
|
||||
})
|
||||
@@ -1,135 +0,0 @@
|
||||
/* global localStorage */
|
||||
var remote = require('remote')
|
||||
var hideFinder = remote.getGlobal('hideFinder')
|
||||
var clipboard = require('clipboard')
|
||||
|
||||
var React = require('react/addons')
|
||||
|
||||
var ArticleFilter = require('../main/Mixins/ArticleFilter')
|
||||
|
||||
var FinderInput = require('./Components/FinderInput')
|
||||
var FinderList = require('./Components/FinderList')
|
||||
var FinderDetail = require('./Components/FinderDetail')
|
||||
|
||||
// Filter end
|
||||
|
||||
function fetchArticles () {
|
||||
var user = JSON.parse(localStorage.getItem('currentUser'))
|
||||
if (user == null) {
|
||||
console.log('need to login')
|
||||
return []
|
||||
}
|
||||
|
||||
var articles = []
|
||||
user.Planets.forEach(function (planet) {
|
||||
var _planet = JSON.parse(localStorage.getItem('planet-' + planet.id))
|
||||
articles = articles.concat(_planet.Codes, _planet.Notes)
|
||||
})
|
||||
user.Teams.forEach(function (team) {
|
||||
team.Planets.forEach(function (planet) {
|
||||
var _planet = JSON.parse(localStorage.getItem('planet-' + planet.id))
|
||||
articles = articles.concat(_planet.Codes, _planet.Notes)
|
||||
})
|
||||
})
|
||||
|
||||
return articles
|
||||
}
|
||||
|
||||
var Finder = React.createClass({
|
||||
mixins: [ArticleFilter],
|
||||
getInitialState: function () {
|
||||
var articles = fetchArticles()
|
||||
return {
|
||||
articles: articles,
|
||||
currentArticle: articles[0],
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
document.addEventListener('keydown', this.handleKeyDown)
|
||||
document.addEventListener('click', this.handleClick)
|
||||
window.addEventListener('focus', this.handleFinderFocus)
|
||||
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'))
|
||||
47
browser/finder/reducer.js
Normal file
47
browser/finder/reducer.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { combineReducers } from 'redux'
|
||||
import { SELECT_ARTICLE, SEARCH_ARTICLE, REFRESH_DATA } from './actions'
|
||||
|
||||
let initialArticles = []
|
||||
let initialFolders = []
|
||||
let initialStatus = {
|
||||
articleKey: null,
|
||||
search: ''
|
||||
}
|
||||
|
||||
function status (state = initialStatus, action) {
|
||||
switch (action.type) {
|
||||
case SELECT_ARTICLE:
|
||||
state.articleKey = action.data.key
|
||||
return Object.assign({}, state)
|
||||
case SEARCH_ARTICLE:
|
||||
state.search = action.data.input
|
||||
return Object.assign({}, state)
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function articles (state = initialArticles, action) {
|
||||
switch (action.type) {
|
||||
case REFRESH_DATA:
|
||||
return action.data.articles
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function folders (state = initialFolders, action) {
|
||||
switch (action.type) {
|
||||
case REFRESH_DATA:
|
||||
console.log(action)
|
||||
return action.data.folders
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export default combineReducers({
|
||||
status,
|
||||
folders,
|
||||
articles
|
||||
})
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/main">Go Main</a>
|
||||
<a href="/main">Go Popup</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,40 +0,0 @@
|
||||
var remote = require('remote')
|
||||
var version = remote.getGlobal('version')
|
||||
|
||||
var React = require('react/addons')
|
||||
|
||||
var ExternalLink = require('../Mixins/ExternalLink')
|
||||
var KeyCaster = require('../Mixins/KeyCaster')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [ExternalLink, KeyCaster('aboutModal')],
|
||||
propTypes: {
|
||||
close: React.PropTypes.func
|
||||
},
|
||||
onKeyCast: function (e) {
|
||||
switch (e.status) {
|
||||
case 'closeModal':
|
||||
this.props.close()
|
||||
break
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className='AboutModal modal'>
|
||||
<div className='about1'>
|
||||
<img className='logo' src='resources/favicon-230x230.png'/>
|
||||
<div className='appInfo'>Boost {version == null || version.length === 0 ? 'DEV version' : 'v' + version}</div>
|
||||
</div>
|
||||
|
||||
<div className='about2'>
|
||||
<div className='externalLabel'>External links</div>
|
||||
<ul className='externalList'>
|
||||
<li><a onClick={this.openExternal} href='http://b00st.io'>Boost Homepage <i className='fa fa-external-link'/></a></li>
|
||||
<li><a>Regulation <i className='fa fa-external-link'/></a></li>
|
||||
<li><a>Private policy <i className='fa fa-external-link'/></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,102 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,55 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,26 +0,0 @@
|
||||
var React = require('react')
|
||||
var CodeForm = require('./CodeForm')
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
close: React.PropTypes.func,
|
||||
code: React.PropTypes.object,
|
||||
planet: React.PropTypes.object
|
||||
},
|
||||
componentDidMount: function () {
|
||||
// TODO: Hacked!! should fix later
|
||||
setTimeout(function () {
|
||||
React.findDOMNode(this.refs.form.refs.description).focus()
|
||||
}.bind(this), 1)
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className='CodeEditModal modal'>
|
||||
<div className='modal-header'>
|
||||
<h1>Edit Code</h1>
|
||||
</div>
|
||||
<CodeForm ref='form' code={this.props.code} planet={this.props.planet} close={this.props.close}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,59 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,162 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
var CodeEditor = require('./CodeEditor')
|
||||
var Select = require('react-select')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var 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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,53 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
|
||||
var ace = window.ace
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
code: React.PropTypes.string,
|
||||
mode: React.PropTypes.string,
|
||||
className: React.PropTypes.string
|
||||
},
|
||||
componentDidMount: function () {
|
||||
var el = React.findDOMNode(this.refs.target)
|
||||
var editor = ace.edit(el)
|
||||
editor.$blockScrolling = Infinity
|
||||
editor.setValue(this.props.code)
|
||||
editor.renderer.setShowGutter(false)
|
||||
editor.setReadOnly(true)
|
||||
editor.setTheme('ace/theme/xcode')
|
||||
editor.setHighlightActiveLine(false)
|
||||
editor.clearSelection()
|
||||
|
||||
var session = editor.getSession()
|
||||
if (this.props.mode != null && this.props.mode.length > 0) {
|
||||
session.setMode('ace/mode/' + this.props.mode)
|
||||
} else {
|
||||
session.setMode('ace/mode/text')
|
||||
}
|
||||
session.setUseSoftTabs(true)
|
||||
session.setOption('useWorker', false)
|
||||
session.setUseWrapMode(true)
|
||||
|
||||
this.setState({editor: editor})
|
||||
},
|
||||
componentDidUpdate: function (prevProps) {
|
||||
if (this.state.editor.getValue() !== this.props.code) {
|
||||
this.state.editor.setValue(this.props.code)
|
||||
this.state.editor.clearSelection()
|
||||
}
|
||||
if (prevProps.mode !== this.props.mode) {
|
||||
var session = this.state.editor.getSession()
|
||||
if (this.props.mode != null && this.props.mode.length > 0) {
|
||||
session.setMode('ace/mode/' + this.props.mode)
|
||||
} else {
|
||||
session.setMode('ace/mode/text')
|
||||
}
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div ref='target' className={this.props.className}></div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,80 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,171 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var KeyCaster = require('../Mixins/KeyCaster')
|
||||
|
||||
var UserStore = require('../Stores/UserStore')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [LinkedState, KeyCaster('editProfileModal')],
|
||||
propTypes: {
|
||||
user: React.PropTypes.shape({
|
||||
name: React.PropTypes.string,
|
||||
profileName: React.PropTypes.string,
|
||||
email: React.PropTypes.string
|
||||
}),
|
||||
close: React.PropTypes.func
|
||||
},
|
||||
getInitialState: function () {
|
||||
var user = this.props.user
|
||||
return {
|
||||
currentTab: 'userInfo',
|
||||
user: {
|
||||
profileName: user.profileName,
|
||||
email: user.email
|
||||
},
|
||||
userSubmitStatus: null,
|
||||
password: {
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
passwordConfirmation: ''
|
||||
},
|
||||
passwordSubmitStatus: null
|
||||
}
|
||||
},
|
||||
onKeyCast: function (e) {
|
||||
switch (e.status) {
|
||||
case 'closeModal':
|
||||
this.props.close()
|
||||
break
|
||||
}
|
||||
},
|
||||
selectTab: function (tabName) {
|
||||
return function () {
|
||||
this.setState({currentTab: tabName})
|
||||
}.bind(this)
|
||||
},
|
||||
saveUserInfo: function () {
|
||||
this.setState({
|
||||
userSubmitStatus: 'sending'
|
||||
}, function () {
|
||||
Hq.updateUser(this.props.user.name, this.state.user)
|
||||
.then(function (res) {
|
||||
this.setState({userSubmitStatus: 'done'}, function () {
|
||||
localStorage.setItem('currentUser', JSON.stringify(res.body))
|
||||
UserStore.Actions.update(res.body)
|
||||
})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
this.setState({userSubmitStatus: 'error'})
|
||||
}.bind(this))
|
||||
})
|
||||
},
|
||||
savePassword: function () {
|
||||
this.setState({
|
||||
passwordSubmitStatus: 'sending'
|
||||
}, function () {
|
||||
console.log(this.state.password)
|
||||
Hq.changePassword(this.state.password)
|
||||
.then(function (res) {
|
||||
this.setState({
|
||||
passwordSubmitStatus: 'done',
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
passwordConfirmation: ''
|
||||
})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
this.setState({
|
||||
passwordSubmitStatus: 'error',
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
passwordConfirmation: ''
|
||||
})
|
||||
}.bind(this))
|
||||
})
|
||||
},
|
||||
render: function () {
|
||||
var content
|
||||
|
||||
switch (this.state.currentTab) {
|
||||
case 'userInfo':
|
||||
content = this.renderUserInfoTab()
|
||||
break
|
||||
case 'password':
|
||||
content = this.renderPasswordTab()
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='EditProfileModal modal tabModal'>
|
||||
<div className='leftPane'>
|
||||
<div className='tabLabel'>Edit profile</div>
|
||||
<div className='tabList'>
|
||||
<button className={this.state.currentTab === 'userInfo' ? 'active' : ''} onClick={this.selectTab('userInfo')}><i className='fa fa-user fa-fw'/> User Info</button>
|
||||
<button className={this.state.currentTab === 'password' ? 'active' : ''} onClick={this.selectTab('password')}><i className='fa fa-lock fa-fw'/> Password</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='rightPane'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
renderUserInfoTab: function () {
|
||||
return (
|
||||
<div className='userInfoTab'>
|
||||
<div className='formField'>
|
||||
<label>Profile Name</label>
|
||||
<input valueLink={this.linkState('user.profileName')}/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<label>E-mail</label>
|
||||
<input valueLink={this.linkState('user.email')}/>
|
||||
</div>
|
||||
<div className='formConfirm'>
|
||||
<button disabled={this.state.userSubmitStatus === 'sending'} onClick={this.saveUserInfo}>Save</button>
|
||||
|
||||
<div className={'alertInfo' + (this.state.userSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
||||
|
||||
<div className={'alertError' + (this.state.userSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
||||
|
||||
<div className={'alertSuccess' + (this.state.userSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
renderPasswordTab: function () {
|
||||
return (
|
||||
<div className='passwordTab'>
|
||||
<div className='formField'>
|
||||
<label>Current password</label>
|
||||
<input valueLink={this.linkState('password.currentPassword')}/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<label>New password</label>
|
||||
<input valueLink={this.linkState('password.newPassword')}/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<label>Confirmation</label>
|
||||
<input valueLink={this.linkState('password.passwordConfirmation')}/>
|
||||
</div>
|
||||
|
||||
<div className='formConfirm'>
|
||||
<button disabled={this.state.password.newPassword.length === 0 || this.state.password.newPassword !== this.state.password.passwordConfirmation || this.state.passwordSubmitStatus === 'sending'} onClick={this.savePassword}>Save</button>
|
||||
|
||||
<div className={'alertInfo' + (this.state.passwordSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
||||
|
||||
<div className={'alertError' + (this.state.passwordSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
||||
|
||||
<div className={'alertSuccess' + (this.state.passwordSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,180 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var Navigation = ReactRouter.Navigation
|
||||
var State = ReactRouter.State
|
||||
var Link = ReactRouter.Link
|
||||
var Reflux = require('reflux')
|
||||
|
||||
var Modal = require('../Mixins/Modal')
|
||||
|
||||
var UserStore = require('../Stores/UserStore')
|
||||
|
||||
var AboutModal = require('./AboutModal')
|
||||
var PlanetCreateModal = require('./PlanetCreateModal')
|
||||
var TeamCreateModal = require('./TeamCreateModal')
|
||||
var LogoutModal = require('./LogoutModal')
|
||||
var ProfileImage = require('./ProfileImage')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [Navigation, State, Reflux.listenTo(UserStore, 'onUserChange'), Modal],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
isPlanetCreateModalOpen: false,
|
||||
currentUser: JSON.parse(localStorage.getItem('currentUser'))
|
||||
}
|
||||
},
|
||||
onUserChange: function (res) {
|
||||
switch (res.status) {
|
||||
case 'userUpdated':
|
||||
var user = res.data
|
||||
var currentUser = this.state.currentUser
|
||||
if (currentUser.id === user.id) {
|
||||
this.setState({currentUser: user})
|
||||
return
|
||||
}
|
||||
|
||||
if (user.userType === 'team') {
|
||||
var isMyTeam = user.Members.some(function (member) {
|
||||
if (currentUser.id === member.id) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (isMyTeam) {
|
||||
var isNew = !currentUser.Teams.some(function (team, index) {
|
||||
if (user.id === team.id) {
|
||||
currentUser.Teams.splice(index, 1, user)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (isNew) {
|
||||
currentUser.Teams.push(user)
|
||||
}
|
||||
|
||||
this.setState({currentUser: currentUser})
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
openTeamCreateModal: function () {
|
||||
this.openModal(TeamCreateModal, {user: this.state.currentUser, transitionTo: this.transitionTo})
|
||||
},
|
||||
openAboutModal: function () {
|
||||
this.openModal(AboutModal)
|
||||
},
|
||||
openPlanetCreateModal: function () {
|
||||
this.openModal(PlanetCreateModal, {transitionTo: this.transitionTo})
|
||||
},
|
||||
toggleProfilePopup: function () {
|
||||
this.openProfilePopup()
|
||||
},
|
||||
openProfilePopup: function () {
|
||||
this.setState({isProfilePopupOpen: true}, function () {
|
||||
document.addEventListener('click', this.closeProfilePopup)
|
||||
})
|
||||
},
|
||||
closeProfilePopup: function () {
|
||||
document.removeEventListener('click', this.closeProfilePopup)
|
||||
this.setState({isProfilePopupOpen: false})
|
||||
},
|
||||
handleLogoutClick: function () {
|
||||
this.openModal(LogoutModal, {transitionTo: this.transitionTo})
|
||||
},
|
||||
switchPlanetByIndex: function (index) {
|
||||
var planetProps = this.refs.planets.props.children[index - 1].props
|
||||
this.transitionTo('planet', {userName: planetProps.userName, planetName: planetProps.planetName})
|
||||
},
|
||||
render: function () {
|
||||
var params = this.getParams()
|
||||
|
||||
if (this.state.currentUser == null) {
|
||||
return (
|
||||
<div className='HomeNavigator'>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
var planets = (this.state.currentUser.Planets.concat(this.state.currentUser.Teams.reduce(function (planets, team) {
|
||||
return team.Planets == null ? planets : planets.concat(team.Planets)
|
||||
}, []))).map(function (planet, index) {
|
||||
return (
|
||||
<li userName={planet.userName} planetName={planet.name} key={planet.id} className={params.userName === planet.userName && params.planetName === planet.name ? 'active' : ''}>
|
||||
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>
|
||||
{planet.name[0]}
|
||||
<div className='planetTooltip'>{planet.userName}/{planet.name}</div>
|
||||
</Link>
|
||||
{index < 9 ? (<div className='shortCut'>⌘{index + 1}</div>) : null}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
var popup = this.renderPopup()
|
||||
|
||||
return (
|
||||
<div className='HomeNavigator'>
|
||||
<button onClick={this.toggleProfilePopup} className='profileButton'>
|
||||
<ProfileImage size='55' email={this.state.currentUser.email}/>
|
||||
</button>
|
||||
{popup}
|
||||
<ul ref='planets' className='planetList'>
|
||||
{planets}
|
||||
</ul>
|
||||
<button onClick={this.openPlanetCreateModal} className='newPlanet'>
|
||||
<i className='fa fa-plus'/>
|
||||
<div className='tooltip'>Create new planet</div>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
renderPopup: function () {
|
||||
var teams = this.state.currentUser.Teams == null ? [] : this.state.currentUser.Teams.map(function (team) {
|
||||
return (
|
||||
<li key={'user-' + team.id}>
|
||||
<Link to='userHome' params={{userName: team.name}} className='userName'>{team.profileName} ({team.name})</Link>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div ref='profilePopup' className={'profilePopup' + (this.state.isProfilePopupOpen ? '' : ' close')}>
|
||||
<div className='profileGroup'>
|
||||
<div className='profileGroupLabel'>
|
||||
<span>You</span>
|
||||
</div>
|
||||
<ul className='profileGroupList'>
|
||||
<li>
|
||||
<Link to='userHome' params={{userName: this.state.currentUser.name}} className='userName'>Profile ({this.state.currentUser.name})</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className='profileGroup'>
|
||||
<div className='profileGroupLabel'>
|
||||
<span>Team</span>
|
||||
</div>
|
||||
<ul className='profileGroupList'>
|
||||
{teams}
|
||||
<li>
|
||||
<button onClick={this.openTeamCreateModal} className='createNewTeam'><i className='fa fa-plus-square-o'/> create new team</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ul className='controlGroup'>
|
||||
<li>
|
||||
<button onClick={this.openAboutModal}><i className='fa fa-info-circle fa-fw'/> About this app</button>
|
||||
</li>
|
||||
<li>
|
||||
<button onClick={this.handleLogoutClick}><i className='fa fa-sign-out fa-fw'/> Log out</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,79 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
|
||||
var CodeForm = require('./CodeForm')
|
||||
var NoteForm = require('./NoteForm')
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
planet: React.PropTypes.object,
|
||||
transitionTo: React.PropTypes.func,
|
||||
close: React.PropTypes.func
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
currentTab: 'code'
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,40 +0,0 @@
|
||||
/* 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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,43 +0,0 @@
|
||||
var React = require('react')
|
||||
|
||||
var Markdown = require('../Mixins/Markdown')
|
||||
var ExternalLink = require('../Mixins/ExternalLink')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [Markdown, ExternalLink],
|
||||
propTypes: {
|
||||
className: React.PropTypes.string,
|
||||
content: React.PropTypes.string
|
||||
},
|
||||
componentDidMount: function () {
|
||||
this.addListener()
|
||||
},
|
||||
componentDidUpdate: function () {
|
||||
this.addListener()
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
this.removeListener()
|
||||
},
|
||||
componentWillUpdate: function () {
|
||||
this.removeListener()
|
||||
},
|
||||
addListener: function () {
|
||||
var anchors = React.findDOMNode(this).querySelectorAll('a')
|
||||
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].addEventListener('click', this.openExternal)
|
||||
}
|
||||
},
|
||||
removeListener: function () {
|
||||
var anchors = React.findDOMNode(this).querySelectorAll('a')
|
||||
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].removeEventListener('click', this.openExternal)
|
||||
}
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className={'MarkdownPreview' + (this.props.className != null ? ' ' + this.props.className : '')} dangerouslySetInnerHTML={{__html: ' ' + this.markdown(this.props.content)}}/>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,55 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,27 +0,0 @@
|
||||
var React = require('react')
|
||||
|
||||
var NoteForm = require('./NoteForm')
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
close: React.PropTypes.func,
|
||||
note: React.PropTypes.object,
|
||||
planet: React.PropTypes.object
|
||||
},
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,153 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,126 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,102 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var moment = require('moment')
|
||||
|
||||
var ForceUpdate = require('../Mixins/ForceUpdate')
|
||||
var Markdown = require('../Mixins/Markdown')
|
||||
|
||||
var ProfileImage = require('../Components/ProfileImage')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [ReactRouter.Navigation, ReactRouter.State, ForceUpdate(60000), Markdown],
|
||||
propTypes: {
|
||||
articles: React.PropTypes.array,
|
||||
showOnlyWithTag: React.PropTypes.func
|
||||
},
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,111 +0,0 @@
|
||||
/* 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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,86 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var Link = ReactRouter.Link
|
||||
|
||||
var Modal = require('../Mixins/Modal')
|
||||
var ExternalLink = require('../Mixins/ExternalLink')
|
||||
|
||||
var PlanetSettingModal = require('./PlanetSettingModal')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [ReactRouter.State, Modal, ExternalLink],
|
||||
propTypes: {
|
||||
search: React.PropTypes.string,
|
||||
fetchPlanet: React.PropTypes.func,
|
||||
onSearchChange: React.PropTypes.func,
|
||||
currentPlanet: React.PropTypes.object
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
search: ''
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,76 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var Navigation = ReactRouter.Navigation
|
||||
|
||||
var Modal = require('../Mixins/Modal')
|
||||
|
||||
var LaunchModal = require('../Components/LaunchModal')
|
||||
|
||||
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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,188 +0,0 @@
|
||||
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 modal tabModal'>
|
||||
<div className='leftPane'>
|
||||
<h1 className='tabLabel'>Planet setting</h1>
|
||||
<nav className='tabList'>
|
||||
<button onClick={this.activePlanetProfile} className={this.state.currentTab === 'profile' ? 'active' : ''}><i className='fa fa-globe fa-fw'/> Planet profile</button>
|
||||
<button onClick={this.activePlanetDelete} className={this.state.currentTab === 'delete' ? 'active' : ''}><i className='fa fa-trash fa-fw'/> Delete Planet</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className='rightPane'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
renderPlanetProfileTab: function () {
|
||||
return (
|
||||
<div className='planetProfileTab'>
|
||||
<div className='formField'>
|
||||
<label>Planet name </label>
|
||||
<input valueLink={this.linkState('planet.name')}/>
|
||||
</div>
|
||||
|
||||
<div className='formRadioField'>
|
||||
<input id='publicOption' checked={this.state.planet.public} onChange={this.handlePublicChange(true)} name='public' type='radio'/> <label htmlFor='publicOption'>Public</label>
|
||||
|
||||
<input id='privateOption' checked={!this.state.planet.public} onChange={this.handlePublicChange(false)} name='public' type='radio'/> <label htmlFor='privateOption'>Private</label>
|
||||
</div>
|
||||
<div className='formConfirm'>
|
||||
<button onClick={this.handleSavePlanetProfile} className='saveButton btn-primary'>Save</button>
|
||||
|
||||
<div className={'alertInfo' + (this.state.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'>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,15 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
var md5 = require('md5')
|
||||
|
||||
module.exports = React.createClass({
|
||||
propTypes: {
|
||||
email: React.PropTypes.string,
|
||||
size: React.PropTypes.string,
|
||||
className: React.PropTypes.string
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<img className={this.props.className} width={this.props.size} height={this.props.size} src={'http://www.gravatar.com/avatar/' + md5(this.props.email.trim().toLowerCase()) + '?s=' + this.props.size}/>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,87 +0,0 @@
|
||||
/* 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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,282 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var Reflux = require('reflux')
|
||||
var Select = require('react-select')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var Helper = require('../Mixins/Helper')
|
||||
var KeyCaster = require('../Mixins/KeyCaster')
|
||||
|
||||
var UserStore = require('../Stores/UserStore')
|
||||
|
||||
var getOptions = function (input, callback) {
|
||||
Hq.searchUser(input)
|
||||
.then(function (res) {
|
||||
callback(null, {
|
||||
options: res.body.map(function (user) {
|
||||
return {
|
||||
label: user.name,
|
||||
value: user.name
|
||||
}
|
||||
}),
|
||||
complete: false
|
||||
})
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [LinkedState, Reflux.listenTo(UserStore, 'onUserChange'), Helper, KeyCaster('teamSettingsModal')],
|
||||
propTypes: {
|
||||
team: React.PropTypes.shape({
|
||||
id: React.PropTypes.number,
|
||||
name: React.PropTypes.string,
|
||||
profileName: React.PropTypes.string,
|
||||
email: React.PropTypes.string,
|
||||
Members: React.PropTypes.array
|
||||
}),
|
||||
close: React.PropTypes.func
|
||||
},
|
||||
getInitialState: function () {
|
||||
var team = this.props.team
|
||||
return {
|
||||
currentTab: 'teamInfo',
|
||||
team: {
|
||||
profileName: team.profileName
|
||||
},
|
||||
userSubmitStatus: null,
|
||||
member: {
|
||||
name: '',
|
||||
role: 'member'
|
||||
},
|
||||
updatingMember: false
|
||||
}
|
||||
},
|
||||
onKeyCast: function (e) {
|
||||
switch (e.status) {
|
||||
case 'closeModal':
|
||||
this.props.close()
|
||||
break
|
||||
}
|
||||
},
|
||||
onUserChange: function (res) {
|
||||
var member
|
||||
switch (res.status) {
|
||||
case 'memberAdded':
|
||||
member = res.data
|
||||
if (member.TeamMember.TeamId === this.props.team.id) {
|
||||
this.forceUpdate()
|
||||
}
|
||||
break
|
||||
case 'memberRemoved':
|
||||
member = res.data
|
||||
if (member.TeamMember.TeamId === this.props.team.id) {
|
||||
this.forceUpdate()
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
selectTab: function (tabName) {
|
||||
return function () {
|
||||
this.setState({currentTab: tabName})
|
||||
}.bind(this)
|
||||
},
|
||||
saveUserInfo: function () {
|
||||
this.setState({
|
||||
userSubmitStatus: 'sending'
|
||||
}, function () {
|
||||
Hq.updateUser(this.props.team.name, this.state.team)
|
||||
.then(function (res) {
|
||||
this.setState({userSubmitStatus: 'done'}, function () {
|
||||
UserStore.Actions.update(res.body)
|
||||
this.forceUpdate()
|
||||
})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
this.setState({userSubmitStatus: 'error'})
|
||||
}.bind(this))
|
||||
})
|
||||
},
|
||||
handleMemberNameChange: function (value) {
|
||||
var member = this.state.member
|
||||
member.name = value
|
||||
this.setState({member: member})
|
||||
},
|
||||
addMember: function () {
|
||||
this.setState({updatingMember: true}, function () {
|
||||
Hq
|
||||
.addMember(this.props.team.name, {
|
||||
userName: this.state.member.name,
|
||||
role: this.state.member.role
|
||||
})
|
||||
.then(function (res) {
|
||||
UserStore.Actions.addMember(res.body)
|
||||
this.setState({updatingMember: false})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
this.setState({updatingMember: false})
|
||||
}.bind(this))
|
||||
})
|
||||
},
|
||||
roleChange: function (memberName) {
|
||||
return function (e) {
|
||||
var role = e.target.value
|
||||
this.setState({updatingMember: true}, function () {
|
||||
Hq
|
||||
.addMember(this.props.team.name, {
|
||||
userName: memberName,
|
||||
role: role
|
||||
})
|
||||
.then(function (res) {
|
||||
UserStore.Actions.addMember(res.body)
|
||||
this.setState({updatingMember: false})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
this.setState({updatingMember: false})
|
||||
}.bind(this))
|
||||
})
|
||||
}.bind(this)
|
||||
},
|
||||
removeMember: function (memberName) {
|
||||
return function () {
|
||||
this.setState({updatingMember: true}, function () {
|
||||
Hq
|
||||
.removeMember(this.props.team.name, {
|
||||
userName: memberName
|
||||
})
|
||||
.then(function (res) {
|
||||
UserStore.Actions.removeMember(res.body)
|
||||
this.setState({updatingMember: false})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
this.setState({updatingMember: false})
|
||||
}.bind(this))
|
||||
})
|
||||
}.bind(this)
|
||||
},
|
||||
render: function () {
|
||||
var content
|
||||
|
||||
switch (this.state.currentTab) {
|
||||
case 'teamInfo':
|
||||
content = this.renderTeamInfoTab()
|
||||
break
|
||||
case 'members':
|
||||
content = this.renderMembersTab()
|
||||
break
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='TeamSettingsModal modal tabModal'>
|
||||
<div className='leftPane'>
|
||||
<div className='tabLabel'>Team settings</div>
|
||||
<div className='tabList'>
|
||||
<button className={this.state.currentTab === 'teamInfo' ? 'active' : ''} onClick={this.selectTab('teamInfo')}><i className='fa fa-info-circle fa-fw'/> Team Info</button>
|
||||
<button className={this.state.currentTab === 'members' ? 'active' : ''} onClick={this.selectTab('members')}><i className='fa fa-users fa-fw'/> Members</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='rightPane'>
|
||||
{content}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
renderTeamInfoTab: function () {
|
||||
return (
|
||||
<div className='userInfoTab'>
|
||||
<div className='formField'>
|
||||
<label>Profile Name</label>
|
||||
<input valueLink={this.linkState('team.profileName')}/>
|
||||
</div>
|
||||
<div className='formConfirm'>
|
||||
<button disabled={this.state.userSubmitStatus === 'sending'} onClick={this.saveUserInfo}>Save</button>
|
||||
|
||||
<div className={'alertInfo' + (this.state.userSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
||||
|
||||
<div className={'alertError' + (this.state.userSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
||||
|
||||
<div className={'alertSuccess' + (this.state.userSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
renderMembersTab: function () {
|
||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
|
||||
var members = this.props.team.Members.map(function (member) {
|
||||
var isCurrentUser = currentUser.id === member.id
|
||||
return (
|
||||
<tr>
|
||||
<td>{member.profileName}({member.name})</td>
|
||||
<td>
|
||||
{isCurrentUser ? (
|
||||
'Owner'
|
||||
) : (
|
||||
<select disabled={this.state.updatingMember} onChange={this.roleChange(member.name)} className='roleSelect' value={member.TeamMember.role}>
|
||||
<option value='owner'>Owner</option>
|
||||
<option value='member'>Member</option>
|
||||
</select>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
{isCurrentUser ? '-' : (
|
||||
<button disabled={this.state.updatingMember} onClick={this.removeMember(member.name)}><i className='fa fa-close fa-fw'/></button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}.bind(this))
|
||||
|
||||
var belowLimit = members.length < 5
|
||||
|
||||
return (
|
||||
<div className='membersTab'>
|
||||
<table className='memberTable'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<th>Role</th>
|
||||
<th>Control</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{members}
|
||||
</tbody>
|
||||
</table>
|
||||
{belowLimit ? (
|
||||
<div className='addMemberForm'>
|
||||
<div className='formLabel'>Add Member</div>
|
||||
<div className='formGroup'>
|
||||
<Select
|
||||
name='userName'
|
||||
value={this.state.member.name}
|
||||
placeholder='Username to add'
|
||||
asyncOptions={getOptions}
|
||||
onChange={this.handleMemberNameChange}
|
||||
className='userNameSelect'
|
||||
/>
|
||||
<select valueLink={this.linkState('member.role')} className='roleSelect'>
|
||||
<option value={'member'}>Member</option>
|
||||
<option value={'owner'}>Owner</option>
|
||||
</select>
|
||||
<button disabled={this.state.updatingMember} onClick={this.addMember} className='confirmButton'>Add Member</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
Maximum number of members is 5 on Beta version. Please contact us if you want futher use.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var RouteHandler = ReactRouter.RouteHandler
|
||||
var State = ReactRouter.State
|
||||
var Navigation = ReactRouter.Navigation
|
||||
|
||||
var AuthFilter = require('../Mixins/AuthFilter')
|
||||
var 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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,106 +0,0 @@
|
||||
/* global localStorage */
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var Link = ReactRouter.Link
|
||||
|
||||
var AuthFilter = require('../Mixins/AuthFilter')
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [LinkedState, ReactRouter.Navigation, AuthFilter.OnlyGuest],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
user: {},
|
||||
authenticationFailed: false,
|
||||
connectionFailed: false,
|
||||
isSending: false
|
||||
}
|
||||
},
|
||||
onListen: function (res) {
|
||||
if (res.status === 'failedToLogIn') {
|
||||
if (res.data.status === 401) {
|
||||
// Wrong E-mail or Password
|
||||
this.setState({
|
||||
authenticationFailed: true,
|
||||
connectionFailed: false,
|
||||
isSending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
// Connection Failed or Whatever
|
||||
this.setState({
|
||||
authenticationFailed: false,
|
||||
connectionFailed: true,
|
||||
isSending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
},
|
||||
handleSubmit: function (e) {
|
||||
this.setState({
|
||||
authenticationFailed: false,
|
||||
connectionFailed: false,
|
||||
isSending: true
|
||||
}, function () {
|
||||
Hq.login(this.state.user)
|
||||
.then(function (res) {
|
||||
localStorage.setItem('token', res.body.token)
|
||||
localStorage.setItem('currentUser', JSON.stringify(res.body.user))
|
||||
|
||||
this.transitionTo('userHome', {userName: res.body.user.name})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
if (err.status === 401) {
|
||||
this.setState({
|
||||
authenticationFailed: true,
|
||||
connectionFailed: false,
|
||||
isSending: false
|
||||
})
|
||||
return
|
||||
}
|
||||
this.setState({
|
||||
authenticationFailed: false,
|
||||
connectionFailed: true,
|
||||
isSending: false
|
||||
})
|
||||
}.bind(this))
|
||||
})
|
||||
|
||||
e.preventDefault()
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className='LoginContainer'>
|
||||
<img className='logo' src='resources/favicon-230x230.png'/>
|
||||
|
||||
<nav className='authNavigator text-center'><Link to='login'>Log In</Link> / <Link to='signup'>Sign Up</Link></nav>
|
||||
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<div className='form-group'>
|
||||
<input className='stripInput' valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<input className='stripInput' valueLink={this.linkState('user.password')} onChange={this.handleChange} type='password' placeholder='Password'/>
|
||||
</div>
|
||||
|
||||
{this.state.isSending ? (
|
||||
<p className='alertInfo'>Logging in...</p>
|
||||
) : null}
|
||||
|
||||
{this.state.connectionFailed ? (
|
||||
<p className='alertError'>Please try again.</p>
|
||||
) : null}
|
||||
|
||||
{this.state.authenticationFailed ? (
|
||||
<p className='alertError'>Wrong E-mail or Password.</p>
|
||||
) : null}
|
||||
|
||||
<div className='form-group'>
|
||||
<button className='logInButton' type='submit'>Log In</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,107 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var ipc = require('ipc')
|
||||
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var RouteHandler = ReactRouter.RouteHandler
|
||||
var Navigation = ReactRouter.Navigation
|
||||
var State = ReactRouter.State
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
var Modal = require('../Mixins/Modal')
|
||||
|
||||
var UserStore = require('../Stores/UserStore')
|
||||
|
||||
var ContactModal = require('../Components/ContactModal')
|
||||
|
||||
function fetchPlanet (userName, planetName) {
|
||||
Hq.fetchPlanet(userName, planetName)
|
||||
.then(function (res) {
|
||||
var planet = res.body
|
||||
|
||||
planet.Codes.forEach(function (code) {
|
||||
code.type = 'code'
|
||||
})
|
||||
|
||||
planet.Notes.forEach(function (note) {
|
||||
note.type = 'note'
|
||||
})
|
||||
|
||||
console.log('planet-' + planet.id + ' fetched!')
|
||||
localStorage.setItem('planet-' + planet.id, JSON.stringify(planet))
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [State, Navigation, Modal],
|
||||
getInitialState: function () {
|
||||
return {
|
||||
updateAvailable: false
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
ipc.on('update-available', function (message) {
|
||||
this.setState({updateAvailable: true})
|
||||
}.bind(this))
|
||||
|
||||
if (this.isActive('root')) {
|
||||
if (localStorage.getItem('currentUser') == null) {
|
||||
this.transitionTo('login')
|
||||
return
|
||||
} else {
|
||||
this.transitionTo('home')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Hq.getUser()
|
||||
.then(function (res) {
|
||||
var user = res.body
|
||||
localStorage.setItem('currentUser', JSON.stringify(user))
|
||||
UserStore.Actions.update(user)
|
||||
|
||||
user.Planets.forEach(function (planet) {
|
||||
fetchPlanet(planet.userName, planet.name)
|
||||
})
|
||||
user.Teams.forEach(function (team) {
|
||||
team.Planets.forEach(function (planet) {
|
||||
fetchPlanet(planet.userName, planet.name)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (err.status === 401) {
|
||||
console.log('Not logged in yet')
|
||||
localStorage.removeItem('currentUser')
|
||||
this.transitionTo('login')
|
||||
return
|
||||
}
|
||||
console.error(err)
|
||||
}.bind(this))
|
||||
},
|
||||
updateApp: function () {
|
||||
ipc.send('update-app', 'Deal with it.')
|
||||
},
|
||||
openContactModal: function () {
|
||||
this.openModal(ContactModal)
|
||||
},
|
||||
render: function () {
|
||||
return (
|
||||
<div className='Main'>
|
||||
{this.state.updateAvailable ? (
|
||||
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
|
||||
) : null}
|
||||
<button onClick={this.openContactModal} className='contactButton'>
|
||||
<i className='fa fa-paper-plane-o'/>
|
||||
<div className='tooltip'>Contact us</div>
|
||||
</button>
|
||||
<RouteHandler/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,415 +0,0 @@
|
||||
/* global localStorage*/
|
||||
'strict'
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var Reflux = require('reflux')
|
||||
|
||||
var PlanetHeader = require('../Components/PlanetHeader')
|
||||
var PlanetNavigator = require('../Components/PlanetNavigator')
|
||||
var PlanetArticleList = require('../Components/PlanetArticleList')
|
||||
var PlanetArticleDetail = require('../Components/PlanetArticleDetail')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
var Modal = require('../Mixins/Modal')
|
||||
var ArticleFilter = require('../Mixins/ArticleFilter')
|
||||
var Helper = require('../Mixins/Helper')
|
||||
var 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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,137 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var Link = ReactRouter.Link
|
||||
|
||||
var AuthFilter = require('../Mixins/AuthFilter')
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var 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>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,367 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var React = require('react/addons')
|
||||
var ReactRouter = require('react-router')
|
||||
var Navigation = ReactRouter.Navigation
|
||||
var State = ReactRouter.State
|
||||
var RouteHandler = ReactRouter.RouteHandler
|
||||
var Link = ReactRouter.Link
|
||||
var Reflux = require('reflux')
|
||||
|
||||
var LinkedState = require('../Mixins/LinkedState')
|
||||
var Modal = require('../Mixins/Modal')
|
||||
var Helper = require('../Mixins/Helper')
|
||||
|
||||
var Hq = require('../Services/Hq')
|
||||
|
||||
var ProfileImage = require('../Components/ProfileImage')
|
||||
var EditProfileModal = require('../Components/EditProfileModal')
|
||||
var TeamSettingsModal = require('../Components/TeamSettingsModal')
|
||||
var PlanetCreateModal = require('../Components/PlanetCreateModal')
|
||||
var AddMemberModal = require('../Components/AddMemberModal')
|
||||
var TeamCreateModal = require('../Components/TeamCreateModal')
|
||||
|
||||
var UserStore = require('../Stores/UserStore')
|
||||
var PlanetStore = require('../Stores/PlanetStore')
|
||||
|
||||
module.exports = React.createClass({
|
||||
mixins: [LinkedState, State, Navigation, Modal, Reflux.listenTo(UserStore, 'onUserChange'), Reflux.listenTo(PlanetStore, 'onPlanetChange'), Helper],
|
||||
propTypes: {
|
||||
params: React.PropTypes.shape({
|
||||
userName: React.PropTypes.string,
|
||||
planetName: React.PropTypes.string
|
||||
})
|
||||
},
|
||||
getInitialState: function () {
|
||||
return {
|
||||
user: null
|
||||
}
|
||||
},
|
||||
componentDidMount: function () {
|
||||
this.fetchUser()
|
||||
},
|
||||
componentWillReceiveProps: function (nextProps) {
|
||||
if (this.state.user == null) {
|
||||
this.fetchUser(nextProps.params.userName)
|
||||
return
|
||||
}
|
||||
|
||||
if (nextProps.params.userName !== this.state.user.name) {
|
||||
this.setState({
|
||||
user: null
|
||||
}, function () {
|
||||
this.fetchUser(nextProps.params.userName)
|
||||
})
|
||||
}
|
||||
},
|
||||
onUserChange: function (res) {
|
||||
if (this.state.user == null) return
|
||||
|
||||
var member
|
||||
switch (res.status) {
|
||||
case 'userUpdated':
|
||||
if (this.state.user.id === res.data.id) {
|
||||
this.setState({user: res.data})
|
||||
}
|
||||
break
|
||||
case 'memberAdded':
|
||||
member = res.data
|
||||
if (this.state.user.userType === 'team' && member.TeamMember.TeamId === this.state.user.id) {
|
||||
this.state.user.Members = this.updateItemToTargetArray(member, this.state.user.Members)
|
||||
|
||||
this.setState({user: this.state.user})
|
||||
}
|
||||
break
|
||||
case 'memberRemoved':
|
||||
member = res.data
|
||||
if (this.state.user.userType === 'team' && member.TeamMember.TeamId === this.state.user.id) {
|
||||
this.state.user.Members = this.deleteItemFromTargetArray(member, this.state.user.Members)
|
||||
|
||||
this.setState({user: this.state.user})
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
onPlanetChange: function (res) {
|
||||
if (this.state.user == null) return
|
||||
|
||||
var currentUser, planet, isOwner, team
|
||||
switch (res.status) {
|
||||
case 'updated':
|
||||
// if state.user is currentUser, planet will be fetched by UserStore
|
||||
currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
if (currentUser.id === this.state.user.id) return
|
||||
|
||||
planet = res.data
|
||||
isOwner = planet.Owner.id === this.state.user.id
|
||||
if (isOwner) {
|
||||
this.state.user.Planets = this.updateItemToTargetArray(planet, this.state.user.Planets)
|
||||
this.setState({user: this.state.user})
|
||||
return
|
||||
}
|
||||
// check if team of user has this planet
|
||||
team = null
|
||||
this.state.user.userType !== 'team' && this.state.user.Teams.some(function (_team) {
|
||||
if (planet.Owner.id === _team.id) {
|
||||
team = _team
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (team != null) {
|
||||
team.Planets = this.updateItemToTargetArray(planet, team.Planets)
|
||||
this.setState({user: this.state.user})
|
||||
return
|
||||
}
|
||||
|
||||
break
|
||||
case 'destroyed':
|
||||
// if state.user is currentUser, planet will be fetched by UserStore
|
||||
currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
if (currentUser.id === this.state.user.id) return
|
||||
|
||||
planet = res.data
|
||||
isOwner = planet.Owner.id === this.state.user.id
|
||||
if (isOwner) {
|
||||
this.state.user.Planets = this.deleteItemFromTargetArray(planet, this.state.user.Planets)
|
||||
this.setState({user: this.state.user})
|
||||
return
|
||||
}
|
||||
// check if team of user has this planet
|
||||
team = null
|
||||
this.state.user.userType !== 'team' && this.state.user.Teams.some(function (_team) {
|
||||
if (planet.Owner.id === _team.id) {
|
||||
team = _team
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (team != null) {
|
||||
team.Planets = this.deleteItemFromTargetArray(planet, team.Planets)
|
||||
this.setState({user: this.state.user})
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
},
|
||||
fetchUser: function (userName) {
|
||||
if (userName == null) userName = this.props.params.userName
|
||||
|
||||
Hq.fetchUser(userName)
|
||||
.then(function (res) {
|
||||
this.setState({user: res.body})
|
||||
}.bind(this))
|
||||
.catch(function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
},
|
||||
openEditProfileModal: function () {
|
||||
this.openModal(EditProfileModal, {user: this.state.user})
|
||||
},
|
||||
openTeamSettingsModal: function () {
|
||||
this.openModal(TeamSettingsModal, {team: this.state.user})
|
||||
},
|
||||
openAddUserModal: function () {
|
||||
this.openModal(AddMemberModal, {team: this.state.user})
|
||||
},
|
||||
openTeamCreateModal: function () {
|
||||
this.openModal(TeamCreateModal, {user: this.state.user})
|
||||
},
|
||||
openPlanetCreateModalWithOwnerName: function (name) {
|
||||
return function () {
|
||||
this.openModal(PlanetCreateModal, {ownerName: name})
|
||||
}.bind(this)
|
||||
},
|
||||
render: function () {
|
||||
var user = this.state.user
|
||||
|
||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
|
||||
if (this.isActive('userHome')) {
|
||||
if (user == null) {
|
||||
return (
|
||||
<div className='UserContainer'>
|
||||
User Loading...
|
||||
</div>
|
||||
)
|
||||
} else if (user.userType === 'team') {
|
||||
return this.renderTeamHome(currentUser)
|
||||
} else {
|
||||
return this.renderUserHome(currentUser)
|
||||
}
|
||||
} else if (this.isActive('planet') && user != null && user.userType === 'team') {
|
||||
var members = user.Members.map(function (member) {
|
||||
return (
|
||||
<li key={'user-' + member.id}><Link to='userHome' params={{userName: member.name}}>
|
||||
<ProfileImage className='memberImage' size='22' email={member.email}/>
|
||||
<div className='memberInfo'>
|
||||
<div className='memberProfileName'>{member.profileName}</div>
|
||||
<div className='memberName'>@{member.name}</div>
|
||||
</div>
|
||||
</Link></li>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div className='UserContainer'>
|
||||
<RouteHandler/>
|
||||
<div className='memberPopup'>
|
||||
<div className='label'>Members</div>
|
||||
<ul className='members'>
|
||||
{members}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div className='UserContainer'>
|
||||
<RouteHandler/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
renderTeamHome: function (currentUser) {
|
||||
var user = this.state.user
|
||||
|
||||
var isOwner = user.Members == null ? false : user.Members.some(function (member) {
|
||||
return member.id === currentUser.id && member.TeamMember.role === 'owner'
|
||||
})
|
||||
|
||||
var userPlanets = user.Planets.map(function (planet) {
|
||||
return (
|
||||
<li key={'planet-' + planet.id}>
|
||||
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>{planet.userName}/{planet.name}</Link>
|
||||
{!planet.public ? (<i className='fa fa-lock'/>) : null}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
var members = user.Members == null ? [] : user.Members.map(function (member) {
|
||||
return (
|
||||
<li key={'user-' + member.id}>
|
||||
<Link to='userHome' params={{userName: member.name}}>
|
||||
<ProfileImage size='22' className='memberImage' email={member.email}/>
|
||||
<div className='memberInfo'>
|
||||
<div className='memberProfileName'>{member.profileName} <span className='memberRole'>({member.TeamMember.role})</span></div>
|
||||
<div className='memberName'>@{member.name}</div>
|
||||
</div>
|
||||
</Link>
|
||||
<div className='role'></div>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div className='UserContainer'>
|
||||
<div className='userProfile'>
|
||||
<ProfileImage className='userPhoto' size='75' email={user.email}/>
|
||||
<div className='userInfo'>
|
||||
<div className='userProfileName'>{user.profileName}</div>
|
||||
<div className='userName'>{user.name}</div>
|
||||
</div>
|
||||
|
||||
{isOwner ? (<button onClick={this.openTeamSettingsModal} className='editProfileButton'>Team settings</button>) : null}
|
||||
</div>
|
||||
<div className='memberList'>
|
||||
<div className='memberLabel'>{members.length} {members.length > 1 ? 'Members' : 'Member'}</div>
|
||||
<ul className='members'>
|
||||
{members}
|
||||
{isOwner ? (<li><button onClick={this.openAddUserModal} className='addMemberButton'><i className='fa fa-plus-square-o'/> add Member</button></li>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
<div className='planetList'>
|
||||
<div className='planetLabel'>{userPlanets.length} {userPlanets.length > 0 ? 'Planets' : 'Planet'}</div>
|
||||
<div className='planetGroup'>
|
||||
<ul className='planets'>
|
||||
{userPlanets}
|
||||
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(user.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
renderUserHome: function (currentUser) {
|
||||
var user = this.state.user
|
||||
|
||||
var isOwner = currentUser.id === user.id
|
||||
|
||||
var userPlanets = user.Planets.map(function (planet) {
|
||||
return (
|
||||
<li key={'planet-' + planet.id}>
|
||||
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>{planet.userName}/{planet.name}</Link>
|
||||
{!planet.public ? (<i className='fa fa-lock'/>) : null}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
var teams = user.Teams == null ? [] : user.Teams.map(function (team) {
|
||||
return (
|
||||
<li key={'user-' + team.id}>
|
||||
<Link to='userHome' params={{userName: team.name}}>
|
||||
<div className='teamInfo'>
|
||||
<div className='teamProfileName'>{team.profileName}</div>
|
||||
<div className='teamName'>@{team.name}</div>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
|
||||
var teamPlanets = user.Teams == null ? [] : user.Teams.map(function (team) {
|
||||
var planets = (team.Planets == null ? [] : team.Planets).map(function (planet) {
|
||||
return (
|
||||
<li key={'planet-' + planet.id}>
|
||||
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>{planet.userName}/{planet.name}</Link>
|
||||
{!planet.public ? (<i className='fa fa-lock'/>) : null}
|
||||
</li>
|
||||
)
|
||||
})
|
||||
return (
|
||||
<div key={'user-' + team.id} className='planetGroup'>
|
||||
<div className='planetGroupLabel'>{team.profileName} <small>@{team.name}</small></div>
|
||||
<ul className='planets'>
|
||||
{planets}
|
||||
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(team.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}.bind(this))
|
||||
|
||||
var planetCount = userPlanets.length + user.Teams.reduce(function (sum, team) {
|
||||
return sum + (team.Planets != null ? team.Planets.length : 0)
|
||||
}, 0)
|
||||
|
||||
return (
|
||||
<div className='UserContainer'>
|
||||
<div className='userProfile'>
|
||||
<ProfileImage className='userPhoto' size='75' email={user.email}/>
|
||||
<div className='userInfo'>
|
||||
<div className='userProfileName'>{user.profileName}</div>
|
||||
<div className='userName'>{user.name}</div>
|
||||
</div>
|
||||
|
||||
{isOwner ? (
|
||||
<button onClick={this.openEditProfileModal} className='editProfileButton'>Edit profile</button>) : null}
|
||||
</div>
|
||||
<div className='teamList'>
|
||||
<div className='teamLabel'>{teams.length} {teams.length > 1 ? 'Teams' : 'Team'}</div>
|
||||
<ul className='teams'>
|
||||
{teams}
|
||||
{isOwner ? (<li><button onClick={this.openTeamCreateModal} className='createTeamButton'><i className='fa fa-plus-square-o'/> Create new team</button></li>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
<div className='planetList'>
|
||||
<div className='planetLabel'>{planetCount} {planetCount > 1 ? 'Planets' : 'Planet'}</div>
|
||||
<div className='planetGroup'>
|
||||
<div className='planetGroupLabel'>{user.profileName} <small>@{user.name}</small></div>
|
||||
<ul className='planets'>
|
||||
{userPlanets}
|
||||
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(user.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
|
||||
</ul>
|
||||
</div>
|
||||
{teamPlanets}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
275
browser/main/HomePage.js
Normal file
275
browser/main/HomePage.js
Normal file
@@ -0,0 +1,275 @@
|
||||
import React, { PropTypes} from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { EDIT_MODE, IDLE_MODE, toggleTutorial } from 'boost/actions'
|
||||
import ArticleNavigator from './HomePage/ArticleNavigator'
|
||||
import ArticleTopBar from './HomePage/ArticleTopBar'
|
||||
import ArticleList from './HomePage/ArticleList'
|
||||
import ArticleDetail from './HomePage/ArticleDetail'
|
||||
import _ from 'lodash'
|
||||
import { isModalOpen, closeModal } from 'boost/modal'
|
||||
|
||||
const electron = require('electron')
|
||||
const remote = electron.remote
|
||||
|
||||
const TEXT_FILTER = 'TEXT_FILTER'
|
||||
const FOLDER_FILTER = 'FOLDER_FILTER'
|
||||
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
|
||||
const TAG_FILTER = 'TAG_FILTER'
|
||||
|
||||
class HomePage extends React.Component {
|
||||
componentDidMount () {
|
||||
// React自体のKey入力はfocusされていないElementからは動かないため、
|
||||
// `window`に直接かける
|
||||
this.keyHandler = e => this.handleKeyDown(e)
|
||||
window.addEventListener('keydown', this.keyHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('keydown', this.keyHandler)
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
let cmdOrCtrl = process.platform === 'darwin' ? e.metaKey : e.ctrlKey
|
||||
|
||||
if (isModalOpen()) {
|
||||
if (e.keyCode === 27) closeModal()
|
||||
return
|
||||
}
|
||||
|
||||
let { status, dispatch } = this.props
|
||||
let { nav, top, list, detail } = this.refs
|
||||
|
||||
if (status.isTutorialOpen) {
|
||||
dispatch(toggleTutorial())
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
// Search inputがfocusされていたら大体のキー入力は無視される。
|
||||
if (top.isInputFocused() && !(e.metaKey || e.ctrlKey)) {
|
||||
if (e.keyCode === 13 || e.keyCode === 27) top.escape()
|
||||
return
|
||||
}
|
||||
|
||||
switch (status.mode) {
|
||||
|
||||
case EDIT_MODE:
|
||||
if (e.keyCode === 27) {
|
||||
detail.handleCancelButtonClick()
|
||||
}
|
||||
if ((e.keyCode === 13 && (cmdOrCtrl)) || (e.keyCode === 83 && (cmdOrCtrl))) {
|
||||
detail.handleSaveButtonClick()
|
||||
}
|
||||
if (e.keyCode === 80 && cmdOrCtrl) {
|
||||
detail.handleTogglePreviewButtonClick()
|
||||
}
|
||||
if (e.keyCode === 78 && cmdOrCtrl) {
|
||||
nav.handleNewPostButtonClick()
|
||||
e.preventDefault()
|
||||
}
|
||||
break
|
||||
case IDLE_MODE:
|
||||
if (e.keyCode === 69) {
|
||||
detail.handleEditButtonClick()
|
||||
e.preventDefault()
|
||||
}
|
||||
if (e.keyCode === 68) {
|
||||
detail.handleDeleteButtonClick()
|
||||
}
|
||||
|
||||
// `detail`の`openDeleteConfirmMenu`の時。
|
||||
if (detail.state.openDeleteConfirmMenu) {
|
||||
if (e.keyCode === 27) {
|
||||
detail.handleDeleteCancelButtonClick()
|
||||
}
|
||||
if (e.keyCode === 13 && cmdOrCtrl) {
|
||||
detail.handleDeleteConfirmButtonClick()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// `detail`の`openDeleteConfirmMenu`が`true`なら呼ばれない。
|
||||
if (e.keyCode === 27 || (e.keyCode === 70 && cmdOrCtrl)) {
|
||||
top.focusInput()
|
||||
}
|
||||
|
||||
if (e.keyCode === 38) {
|
||||
list.selectPriorArticle()
|
||||
}
|
||||
|
||||
if (e.keyCode === 40) {
|
||||
list.selectNextArticle()
|
||||
}
|
||||
|
||||
if ((e.keyCode === 65 && !e.metaKey && !e.ctrlKey) || (e.keyCode === 13 && cmdOrCtrl) || (e.keyCode === 78 && cmdOrCtrl)) {
|
||||
nav.handleNewPostButtonClick()
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
let { dispatch, status, user, articles, allArticles, activeArticle, folders, tags, filters } = this.props
|
||||
|
||||
return (
|
||||
<div className='HomePage'>
|
||||
<ArticleNavigator
|
||||
ref='nav'
|
||||
dispatch={dispatch}
|
||||
user={user}
|
||||
folders={folders}
|
||||
status={status}
|
||||
allArticles={allArticles}
|
||||
/>
|
||||
<ArticleTopBar
|
||||
ref='top'
|
||||
dispatch={dispatch}
|
||||
status={status}
|
||||
/>
|
||||
<ArticleList
|
||||
ref='list'
|
||||
dispatch={dispatch}
|
||||
folders={folders}
|
||||
articles={articles}
|
||||
status={status}
|
||||
activeArticle={activeArticle}
|
||||
/>
|
||||
<ArticleDetail
|
||||
ref='detail'
|
||||
dispatch={dispatch}
|
||||
user={user}
|
||||
activeArticle={activeArticle}
|
||||
folders={folders}
|
||||
status={status}
|
||||
tags={tags}
|
||||
filters={filters}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore invalid key
|
||||
function ignoreInvalidKey (key) {
|
||||
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/)
|
||||
}
|
||||
|
||||
// Build filter object by key
|
||||
function buildFilter (key) {
|
||||
if (key.match(/^\/\/.+/)) {
|
||||
return {type: FOLDER_EXACT_FILTER, value: key.match(/^\/\/(.+)$/)[1]}
|
||||
}
|
||||
if (key.match(/^\/.+/)) {
|
||||
return {type: FOLDER_FILTER, value: key.match(/^\/(.+)$/)[1]}
|
||||
}
|
||||
if (key.match(/^#(.+)/)) {
|
||||
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
|
||||
}
|
||||
return {type: TEXT_FILTER, value: key}
|
||||
}
|
||||
|
||||
function isContaining (target, needle) {
|
||||
return target.match(new RegExp(_.escapeRegExp(needle)))
|
||||
}
|
||||
|
||||
function startsWith (target, needle) {
|
||||
return target.match(new RegExp('^' + _.escapeRegExp(needle)))
|
||||
}
|
||||
|
||||
function remap (state) {
|
||||
let { user, folders, articles, status } = state
|
||||
|
||||
if (articles == null) articles = []
|
||||
articles.sort((a, b) => {
|
||||
return new Date(b.updatedAt) - new Date(a.updatedAt)
|
||||
})
|
||||
let allArticles = articles.slice()
|
||||
|
||||
let tags = _.uniq(allArticles.reduce((sum, article) => {
|
||||
if (!_.isArray(article.tags)) return sum
|
||||
return sum.concat(article.tags)
|
||||
}, []))
|
||||
|
||||
// Filter articles
|
||||
let filters = status.search.split(' ')
|
||||
.map(key => key.trim())
|
||||
.filter(ignoreInvalidKey)
|
||||
.map(buildFilter)
|
||||
|
||||
let folderExactFilters = filters.filter(filter => filter.type === FOLDER_EXACT_FILTER)
|
||||
let folderFilters = filters.filter(filter => filter.type === FOLDER_FILTER)
|
||||
let textFilters = filters.filter(filter => filter.type === TEXT_FILTER)
|
||||
let tagFilters = filters.filter(filter => filter.type === TAG_FILTER)
|
||||
|
||||
let targetFolders
|
||||
if (folders != null) {
|
||||
let exactTargetFolders = folders.filter(folder => {
|
||||
return _.findWhere(folderExactFilters, {value: folder.name})
|
||||
})
|
||||
let fuzzyTargetFolders = folders.filter(folder => {
|
||||
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
|
||||
})
|
||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
||||
|
||||
if (targetFolders.length > 0) {
|
||||
articles = articles.filter(article => {
|
||||
return _.findWhere(targetFolders, {key: article.FolderKey})
|
||||
})
|
||||
}
|
||||
|
||||
if (textFilters.length > 0) {
|
||||
articles = textFilters.reduce((articles, textFilter) => {
|
||||
return articles.filter(article => {
|
||||
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
|
||||
})
|
||||
}, articles)
|
||||
}
|
||||
|
||||
if (tagFilters.length > 0) {
|
||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
||||
return articles.filter(article => {
|
||||
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
|
||||
})
|
||||
}, articles)
|
||||
}
|
||||
}
|
||||
|
||||
// Grab active article
|
||||
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
||||
if (activeArticle == null) activeArticle = articles[0]
|
||||
|
||||
return {
|
||||
user,
|
||||
folders,
|
||||
status,
|
||||
allArticles,
|
||||
articles,
|
||||
activeArticle,
|
||||
tags,
|
||||
filters: {
|
||||
folder: folderFilters,
|
||||
tag: tagFilters,
|
||||
text: textFilters
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HomePage.propTypes = {
|
||||
status: PropTypes.shape(),
|
||||
user: PropTypes.shape({
|
||||
name: PropTypes.string
|
||||
}),
|
||||
articles: PropTypes.array,
|
||||
allArticles: PropTypes.array,
|
||||
activeArticle: PropTypes.shape(),
|
||||
dispatch: PropTypes.func,
|
||||
folders: PropTypes.array,
|
||||
filters: PropTypes.shape({
|
||||
folder: PropTypes.array,
|
||||
tag: PropTypes.array,
|
||||
text: PropTypes.array
|
||||
}),
|
||||
tags: PropTypes.array
|
||||
}
|
||||
|
||||
export default connect(remap)(HomePage)
|
||||
151
browser/main/HomePage/ArticleDetail/ShareButton.js
Normal file
151
browser/main/HomePage/ArticleDetail/ShareButton.js
Normal file
@@ -0,0 +1,151 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import api from 'boost/api'
|
||||
import clientKey from 'boost/clientKey'
|
||||
import activityRecord from 'boost/activityRecord'
|
||||
const clipboard = require('electron').clipboard
|
||||
|
||||
function getDefault () {
|
||||
return {
|
||||
openDropdown: false,
|
||||
isSharing: false,
|
||||
// Fetched url
|
||||
url: null,
|
||||
// for tooltip Copy -> Copied!
|
||||
copied: false,
|
||||
failed: false
|
||||
}
|
||||
}
|
||||
|
||||
export default class ShareButton extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = getDefault()
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.setState(getDefault())
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.dropdownInterceptor = e => {
|
||||
this.dropdownClicked = true
|
||||
}
|
||||
ReactDOM.findDOMNode(this.refs.dropdown).addEventListener('click', this.dropdownInterceptor)
|
||||
this.shareViaPublicURLHandler = e => {
|
||||
this.handleShareViaPublicURLClick(e)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('click', this.dropdownHandler)
|
||||
ReactDOM.findDOMNode(this.refs.dropdown).removeEventListener('click', this.dropdownInterceptor)
|
||||
}
|
||||
|
||||
handleOpenButtonClick (e) {
|
||||
this.openDropdown()
|
||||
if (this.dropdownHandler == null) {
|
||||
this.dropdownHandler = e => {
|
||||
if (!this.dropdownClicked) {
|
||||
this.closeDropdown()
|
||||
} else {
|
||||
this.dropdownClicked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
document.removeEventListener('click', this.dropdownHandler)
|
||||
document.addEventListener('click', this.dropdownHandler)
|
||||
}
|
||||
|
||||
openDropdown () {
|
||||
this.setState({openDropdown: true})
|
||||
}
|
||||
|
||||
closeDropdown () {
|
||||
document.removeEventListener('click', this.dropdownHandler)
|
||||
this.setState({openDropdown: false})
|
||||
}
|
||||
|
||||
handleShareViaPublicURLClick (e) {
|
||||
let { user } = this.props
|
||||
let input = Object.assign({}, this.props.article, {
|
||||
clientKey: clientKey.get(),
|
||||
writerName: user.name
|
||||
})
|
||||
this.setState({
|
||||
isSharing: true,
|
||||
failed: false
|
||||
}, () => {
|
||||
api.shareViaPublicURL(input)
|
||||
.then(res => {
|
||||
let url = res.body.url
|
||||
this.setState({url: url, isSharing: false})
|
||||
activityRecord.emit('ARTICLE_SHARE')
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
this.setState({isSharing: false, failed: true})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
handleCopyURLClick () {
|
||||
clipboard.writeText(this.state.url)
|
||||
this.setState({copied: true})
|
||||
}
|
||||
|
||||
// Restore copy url tooltip
|
||||
handleCopyURLMouseLeave () {
|
||||
this.setState({copied: false})
|
||||
}
|
||||
|
||||
render () {
|
||||
let hasPublicURL = this.state.url != null
|
||||
return (
|
||||
<div className='ShareButton'>
|
||||
<button ref='openButton' onClick={e => this.handleOpenButtonClick(e)} className='ShareButton-open-button'>
|
||||
<i className='fa fa-fw fa-share-alt'/>
|
||||
{
|
||||
this.state.openDropdown ? null : (
|
||||
<span className='tooltip'>Share</span>
|
||||
)
|
||||
}
|
||||
</button>
|
||||
<div ref='dropdown' className={'share-dropdown' + (this.state.openDropdown ? '' : ' hide')}>
|
||||
{
|
||||
!hasPublicURL ? (
|
||||
<button
|
||||
onClick={e => this.shareViaPublicURLHandler(e)}
|
||||
ref='sharePublicURL'
|
||||
disabled={this.state.isSharing}>
|
||||
<i className='fa fa-fw fa-external-link'/> {this.state.failed ? 'Failed : Click to Try again' : !this.state.isSharing ? 'Share via public URL' : 'Sharing...'}
|
||||
</button>
|
||||
) : (
|
||||
<div className='ShareButton-url'>
|
||||
<input className='ShareButton-url-input' value={this.state.url} readOnly/>
|
||||
<button
|
||||
onClick={e => this.handleCopyURLClick(e)}
|
||||
className='ShareButton-url-button'
|
||||
onMouseLeave={e => this.handleCopyURLMouseLeave(e)}
|
||||
>
|
||||
<i className='fa fa-fw fa-clipboard'/>
|
||||
<div className='ShareButton-url-button-tooltip'>{this.state.copied ? 'Copied!' : 'Copy URL'}</div>
|
||||
</button>
|
||||
<div className='ShareButton-url-alert'>This url is valid for 7 days.</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ShareButton.propTypes = {
|
||||
article: PropTypes.shape({
|
||||
publicURL: PropTypes.string
|
||||
}),
|
||||
user: PropTypes.shape({
|
||||
name: PropTypes.string
|
||||
})
|
||||
}
|
||||
608
browser/main/HomePage/ArticleDetail/index.js
Normal file
608
browser/main/HomePage/ArticleDetail/index.js
Normal file
@@ -0,0 +1,608 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import moment from 'moment'
|
||||
import _ from 'lodash'
|
||||
import ModeIcon from 'boost/components/ModeIcon'
|
||||
import MarkdownPreview from 'boost/components/MarkdownPreview'
|
||||
import CodeEditor from 'boost/components/CodeEditor'
|
||||
import {
|
||||
IDLE_MODE,
|
||||
EDIT_MODE,
|
||||
switchMode,
|
||||
switchArticle,
|
||||
switchFolder,
|
||||
clearSearch,
|
||||
lockStatus,
|
||||
unlockStatus,
|
||||
updateArticle,
|
||||
destroyArticle,
|
||||
NEW
|
||||
} from 'boost/actions'
|
||||
import linkState from 'boost/linkState'
|
||||
import FolderMark from 'boost/components/FolderMark'
|
||||
import TagLink from 'boost/components/TagLink'
|
||||
import TagSelect from 'boost/components/TagSelect'
|
||||
import ModeSelect from 'boost/components/ModeSelect'
|
||||
import activityRecord from 'boost/activityRecord'
|
||||
import ShareButton from './ShareButton'
|
||||
|
||||
const electron = require('electron')
|
||||
const clipboard = electron.clipboard
|
||||
|
||||
const BRAND_COLOR = '#18AF90'
|
||||
|
||||
const editDeleteTutorialElement = (
|
||||
<svg width='300' height='500' className='tutorial'>
|
||||
<text x='50' y='220' fill={BRAND_COLOR} fontSize='24'>Edit / Delete a post</text>
|
||||
<text x='90' y='245' fill={BRAND_COLOR} fontSize='18'>press `e`/`d`</text>
|
||||
|
||||
<svg x='150' y='35'>
|
||||
<path fill='white' d='M87.5,93.6c-16.3-5.7-30.6-16.7-39.9-31.4c-5.5-8.7-9-19.1-3.4-28.7c4.8-8.2,13.6-12.8,22.4-15.3
|
||||
c15.7-4.5,34.4-6.2,49.7,0.4c17.3,7.4,25.6,26.3,25.7,44.4c0.1,10.4-3.4,20.9-13.1,26c-8.6,4.5-19,4.1-28.4,3.7
|
||||
c-1.9-0.1-1.9,2.9,0,3c9.3,0.4,19.2,0.6,27.9-3.2c8.5-3.7,13.8-11.2,15.7-20.2c3.6-17.9-2.9-40.2-17.7-51.4
|
||||
C110.8,9.1,89,9.9,70.8,14c-17.9,4-37.4,16.8-31.3,37.9C45.6,73,66.7,89.5,86.7,96.5C88.6,97.1,89.4,94.2,87.5,93.6L87.5,93.6z'/>
|
||||
<path fill='white' d='M11.9,89.7c14.8-3.4,29.7-6,44.8-7.9c-0.5-0.6-1-1.3-1.4-1.9c-2.6,6.3-2.8,12.7-0.7,19.2
|
||||
c0.6,1.8,3.5,1,2.9-0.8c-1.9-6-1.7-11.8,0.7-17.6c0.3-0.8-0.5-2-1.4-1.9c-15.3,1.9-30.6,4.5-45.6,8C9.3,87.3,10.1,90.2,11.9,89.7
|
||||
L11.9,89.7z'/>
|
||||
<path fill='white' d='M48.6,81.5c-9.4,10.4-17,22.3-22.2,35.3c-5.5,13.6-9.3,28.9-6,43.4c0.4,1.9,3.3,1.1,2.9-0.8
|
||||
c-3.2-14,0.7-28.8,6-41.8c5.1-12.5,12.4-24,21.5-34C52,82.2,49.9,80,48.6,81.5L48.6,81.5z'/>
|
||||
</svg>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const tagSelectTutorialElement = (
|
||||
<svg width='500' height='500' className='tutorial'>
|
||||
<text x='155' y='50' fill={BRAND_COLOR} fontSize='24'>Attach some tags here!</text>
|
||||
|
||||
<svg x='0' y='-15'>
|
||||
<path fill='white' d='M15.5,22.2c77.8-0.7,155.6-1.3,233.5-2c22.2-0.2,44.4-0.4,66.6-0.6c1.9,0,1.9-3,0-3
|
||||
c-77.8,0.7-155.6,1.3-233.5,2c-22.2,0.2-44.4,0.4-66.6,0.6C13.6,19.2,13.6,22.2,15.5,22.2L15.5,22.2z'/>
|
||||
<path fill='white' d='M130.8,25c-5.4,6.8-10.3,14-14.6,21.5c-0.8,1.4,1.2,3.2,2.4,1.8c1-1.2,2-2.4,3.1-3.7c1.2-1.5-0.9-3.6-2.1-2.1
|
||||
c-1,1.2-2,2.4-3.1,3.7c0.8,0.6,1.6,1.2,2.4,1.8c4.2-7.3,8.9-14.3,14.2-20.9C134.1,25.6,132,23.4,130.8,25L130.8,25z'/>
|
||||
<path fill='white' d='M132.6,22.1c8.4,5.9,16.8,11.9,25.2,17.8c1.6,1.1,3.1-1.5,1.5-2.6c-8.4-5.9-16.8-11.9-25.2-17.8
|
||||
C132.5,18.4,131,21,132.6,22.1L132.6,22.1z'/>
|
||||
<path fill='white' d='M132.9,18.6c0.4,6.7-0.7,13.3-3.5,19.3c-1.5,3.1-3.9,6.4-3.1,10c0.7,3.1,3.4,4.4,6.2,5.5
|
||||
c5.1,2.1,10.5,3.1,16.1,3.2c1.9,0,1.9-3,0-3c-4.7-0.1-9.2-0.8-13.6-2.4c-3-1.1-6.2-1.9-5.4-6.6c0.4-2,2-4.1,2.8-5.9
|
||||
c2.9-6.3,4-13.1,3.6-20.1C135.8,16.7,132.8,16.7,132.9,18.6L132.9,18.6z'/>
|
||||
</svg>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const modeSelectTutorialElement = (
|
||||
<svg width='500' height='500' className='tutorial'>
|
||||
<text x='195' y='130' fill={BRAND_COLOR} fontSize='24'>Select code syntax!!</text>
|
||||
|
||||
<svg x='300' y='0'>
|
||||
<path fill='white' d='M99.9,58.8c-14.5-0.5-29-2.2-43.1-5.6c-12.3-2.9-27.9-6.4-37.1-15.5C7.9,26,28.2,18.9,37,16.7
|
||||
c13.8-3.5,28.3-4.7,42.4-5.8c29.6-2.2,59.3-1.7,89-1c3,0.1,7.5-0.6,10.2,0.6c3.1,1.4,3.1,5.3,3.3,8.1c0.3,5.2-0.2,10.7-2.4,15.4
|
||||
c-4.4,9.6-18.4,14.7-27.5,18.1c-27.1,10.1-56.7,12.8-85.3,15.6c-1.9,0.2-1.9,3.2,0,3c29.3-2.9,59.8-5.6,87.5-16.2
|
||||
c9.6-3.7,22.8-8.7,27.7-18.4c2.3-4.6,3.2-9.9,3.2-15c0-3.6,0-9.4-2.9-12c-1.9-1.7-4.7-1.8-7.1-2c-4.8-0.2-9.6-0.2-14.4-0.3
|
||||
c-8.7-0.2-17.5-0.3-26.2-0.4C116.7,6.3,99,6.5,81.3,7.8c-15.8,1.1-32.1,2.3-47.4,6.6c-7.7,2.2-22.1,6.9-20.9,17.4
|
||||
c0.6,5.4,5.6,9.4,9.9,12.1c6.7,4.3,14.4,6.9,22,9.2c17.8,5.4,36.4,8,54.9,8.6C101.8,61.8,101.8,58.8,99.9,58.8L99.9,58.8z'/>
|
||||
<path fill='white' d='M11.1,67.8c9.2-6.1,18.6-11.9,28.2-17.2c-0.7-0.3-1.5-0.6-2.2-0.9c0.9,5.3,0.7,10.3-0.5,15.5
|
||||
c-0.4,1.9,2.4,2.7,2.9,0.8c1.4-5.7,1.5-11.3,0.5-17.1c-0.2-1-1.4-1.3-2.2-0.9c-9.7,5.3-19.1,11.1-28.2,17.2
|
||||
C8,66.3,9.5,68.9,11.1,67.8L11.1,67.8z'/>
|
||||
<path fill='white' d='M31.5,52.8C23.4,68.9,0.2,83.2,7.9,104c0.7,1.8,3.6,1,2.9-0.8C3.6,83.7,26.4,69.7,34.1,54.3
|
||||
C35,52.6,32.4,51.1,31.5,52.8L31.5,52.8z'/>
|
||||
</svg>
|
||||
</svg>
|
||||
)
|
||||
|
||||
function notify (...args) {
|
||||
return new window.Notification(...args)
|
||||
}
|
||||
|
||||
function makeInstantArticle (article) {
|
||||
return Object.assign({}, article)
|
||||
}
|
||||
|
||||
export default class ArticleDetail extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
article: makeInstantArticle(props.activeArticle),
|
||||
previewMode: false,
|
||||
isArticleEdited: false,
|
||||
isTagChanged: false,
|
||||
isTitleChanged: false,
|
||||
isContentChanged: false,
|
||||
isModeChanged: false,
|
||||
openShareDropdown: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
|
||||
this.shareDropdownInterceptor = e => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
clearInterval(this.refreshTimer)
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
let isModeChanged = prevProps.status.mode !== this.props.status.mode
|
||||
if (isModeChanged && this.props.status.mode === EDIT_MODE) {
|
||||
ReactDOM.findDOMNode(this.refs.title).focus()
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
let nextState = {}
|
||||
|
||||
let isArticleChanged = nextProps.activeArticle != null && (nextProps.activeArticle.key !== this.state.article.key)
|
||||
let isModeChanged = nextProps.status.mode !== this.props.status.mode
|
||||
|
||||
// Reset article input
|
||||
if (isArticleChanged || (isModeChanged && nextProps.status.mode !== IDLE_MODE)) {
|
||||
Object.assign(nextState, {
|
||||
article: makeInstantArticle(nextProps.activeArticle)
|
||||
})
|
||||
}
|
||||
|
||||
// Clean state
|
||||
if (isModeChanged) {
|
||||
Object.assign(nextState, {
|
||||
openDeleteConfirmMenu: false,
|
||||
previewMode: false,
|
||||
isArticleEdited: false,
|
||||
isTagChanged: false,
|
||||
isTitleChanged: false,
|
||||
isContentChanged: false
|
||||
})
|
||||
}
|
||||
|
||||
this.setState(nextState)
|
||||
}
|
||||
|
||||
renderEmpty () {
|
||||
return (
|
||||
<div className='ArticleDetail empty'>
|
||||
Command(⌘) + Enter to create a new post
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
handleClipboardButtonClick (e) {
|
||||
activityRecord.emit('MAIN_DETAIL_COPY')
|
||||
clipboard.writeText(this.props.activeArticle.content)
|
||||
notify('Saved to Clipboard!', {
|
||||
body: 'Paste it wherever you want!'
|
||||
})
|
||||
}
|
||||
|
||||
handleEditButtonClick (e) {
|
||||
let { dispatch } = this.props
|
||||
dispatch(switchMode(EDIT_MODE))
|
||||
}
|
||||
|
||||
handleDeleteButtonClick (e) {
|
||||
this.setState({openDeleteConfirmMenu: true})
|
||||
}
|
||||
|
||||
handleDeleteConfirmButtonClick (e) {
|
||||
let { dispatch, activeArticle } = this.props
|
||||
|
||||
dispatch(destroyArticle(activeArticle.key))
|
||||
activityRecord.emit('ARTICLE_DESTROY')
|
||||
this.setState({openDeleteConfirmMenu: false})
|
||||
}
|
||||
|
||||
handleDeleteCancelButtonClick (e) {
|
||||
this.setState({openDeleteConfirmMenu: false})
|
||||
}
|
||||
|
||||
renderIdle () {
|
||||
let { status, activeArticle, folders, user } = this.props
|
||||
|
||||
let tags = activeArticle.tags != null ? activeArticle.tags.length > 0
|
||||
? activeArticle.tags.map(tag => {
|
||||
return (<TagLink key={tag} tag={tag}/>)
|
||||
})
|
||||
: (
|
||||
<span className='noTags'>Not tagged yet</span>
|
||||
) : null
|
||||
|
||||
let folder = _.findWhere(folders, {key: activeArticle.FolderKey})
|
||||
|
||||
let title = activeArticle.title.trim().length === 0
|
||||
? <small>(Untitled)</small>
|
||||
: activeArticle.title
|
||||
|
||||
return (
|
||||
<div className='ArticleDetail idle'>
|
||||
{this.state.openDeleteConfirmMenu
|
||||
? (
|
||||
<div className='deleteConfirm'>
|
||||
<div className='right'>
|
||||
Are you sure to delete this article?
|
||||
<button onClick={e => this.handleDeleteConfirmButtonClick(e)} className='primary'>
|
||||
<i className='fa fa-fw fa-check'/> Sure
|
||||
</button>
|
||||
<button onClick={e => this.handleDeleteCancelButtonClick(e)}>
|
||||
<i className='fa fa-fw fa-times'/> Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className='detailInfo'>
|
||||
<div className='left'>
|
||||
<div className='info'>
|
||||
<FolderMark color={folder.color}/> <span className='folderName'>{folder.name}</span>
|
||||
Created : {moment(activeArticle.createdAt).format('YYYY/MM/DD')}
|
||||
Updated : {moment(activeArticle.updatedAt).format('YYYY/MM/DD')}
|
||||
</div>
|
||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
|
||||
</div>
|
||||
<div className='right'>
|
||||
<ShareButton
|
||||
article={activeArticle}
|
||||
user={user}
|
||||
/>
|
||||
|
||||
<button onClick={e => this.handleClipboardButtonClick(e)} className='editBtn'>
|
||||
<i className='fa fa-fw fa-clipboard'/><span className='tooltip'>Copy to clipboard</span>
|
||||
</button>
|
||||
|
||||
<button onClick={e => this.handleEditButtonClick(e)} className='editBtn'>
|
||||
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Edit (e)</span>
|
||||
</button>
|
||||
<button onClick={e => this.handleDeleteButtonClick(e)} className='deleteBtn'>
|
||||
<i className='fa fa-fw fa-trash'/><span className='tooltip'>Delete (d)</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{status.isTutorialOpen ? editDeleteTutorialElement : null}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div className='detailBody'>
|
||||
<div className='detailPanel'>
|
||||
<div className='header'>
|
||||
<ModeIcon className='mode' mode={activeArticle.mode}/>
|
||||
<div className='title'>{title}</div>
|
||||
</div>
|
||||
{activeArticle.mode === 'markdown'
|
||||
? <MarkdownPreview content={activeArticle.content}/>
|
||||
: <CodeEditor readOnly onChange={(e, value) => this.handleContentChange(e, value)} mode={activeArticle.mode} code={activeArticle.content}/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
handleCancelButtonClick (e) {
|
||||
let { activeArticle, dispatch } = this.props
|
||||
|
||||
if (activeArticle.status === NEW) {
|
||||
dispatch(switchArticle(null))
|
||||
}
|
||||
dispatch(switchMode(IDLE_MODE))
|
||||
}
|
||||
|
||||
handleSaveButtonClick (e) {
|
||||
let { dispatch, folders, status } = this.props
|
||||
let article = this.state.article
|
||||
let newArticle = Object.assign({}, article)
|
||||
|
||||
let folder = _.findWhere(folders, {key: article.FolderKey})
|
||||
if (folder == null) return false
|
||||
|
||||
dispatch(unlockStatus())
|
||||
|
||||
newArticle.status = null
|
||||
newArticle.updatedAt = new Date()
|
||||
newArticle.title = newArticle.title.trim()
|
||||
if (newArticle.createdAt == null) {
|
||||
newArticle.createdAt = new Date()
|
||||
if (newArticle.title.length === 0) {
|
||||
newArticle.title = `Created at ${moment(newArticle.createdAt).format('YYYY/MM/DD HH:mm')}`
|
||||
}
|
||||
activityRecord.emit('ARTICLE_CREATE', {mode: newArticle.mode})
|
||||
} else {
|
||||
activityRecord.emit('ARTICLE_UPDATE', {mode: newArticle.mode})
|
||||
}
|
||||
|
||||
dispatch(updateArticle(newArticle))
|
||||
dispatch(switchMode(IDLE_MODE))
|
||||
// Folder filterがかかっている時に、
|
||||
// Searchを初期化し、更新先のFolder filterをかける
|
||||
// かかれていない時に
|
||||
// Searchを初期化する
|
||||
if (status.targetFolders.length > 0) dispatch(switchFolder(folder.name))
|
||||
else dispatch(clearSearch())
|
||||
dispatch(switchArticle(newArticle.key))
|
||||
}
|
||||
|
||||
handleFolderKeyChange (e) {
|
||||
let article = this.state.article
|
||||
article.FolderKey = e.target.value
|
||||
|
||||
this.setState({article: article})
|
||||
}
|
||||
|
||||
handleTitleChange (e) {
|
||||
let { article } = this.state
|
||||
article.title = e.target.value
|
||||
let _isTitleChanged = article.title !== this.props.activeArticle.title
|
||||
|
||||
let { isTagChanged, isContentChanged, isArticleEdited, isModeChanged } = this.state
|
||||
let _isArticleEdited = _isTitleChanged || isTagChanged || isContentChanged || isModeChanged
|
||||
|
||||
this.setState({
|
||||
article,
|
||||
isTitleChanged: _isTitleChanged,
|
||||
isArticleEdited: _isArticleEdited
|
||||
}, () => {
|
||||
if (isArticleEdited !== _isArticleEdited) {
|
||||
let { dispatch } = this.props
|
||||
if (_isArticleEdited) {
|
||||
console.log('lockit')
|
||||
dispatch(lockStatus())
|
||||
} else {
|
||||
console.log('unlockit')
|
||||
dispatch(unlockStatus())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleTagsChange (newTag, tags) {
|
||||
let article = this.state.article
|
||||
article.tags = tags
|
||||
|
||||
let _isTagChanged = _.difference(article.tags, this.props.activeArticle.tags).length > 0 || _.difference(this.props.activeArticle.tags, article.tags).length > 0
|
||||
|
||||
let { isTitleChanged, isContentChanged, isArticleEdited, isModeChanged } = this.state
|
||||
let _isArticleEdited = _isTagChanged || isTitleChanged || isContentChanged || isModeChanged
|
||||
|
||||
this.setState({
|
||||
article,
|
||||
isTagChanged: _isTagChanged,
|
||||
isArticleEdited: _isArticleEdited
|
||||
}, () => {
|
||||
if (isArticleEdited !== _isArticleEdited) {
|
||||
let { dispatch } = this.props
|
||||
if (_isArticleEdited) {
|
||||
console.log('lockit')
|
||||
dispatch(lockStatus())
|
||||
} else {
|
||||
console.log('unlockit')
|
||||
dispatch(unlockStatus())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleModeChange (value) {
|
||||
let { article } = this.state
|
||||
article.mode = value
|
||||
let _isModeChanged = article.mode !== this.props.activeArticle.mode
|
||||
|
||||
let { isTagChanged, isContentChanged, isArticleEdited, isTitleChanged } = this.state
|
||||
let _isArticleEdited = _isModeChanged || isTagChanged || isContentChanged || isTitleChanged
|
||||
|
||||
this.setState({
|
||||
article,
|
||||
previewMode: false,
|
||||
isModeChanged: _isModeChanged,
|
||||
isArticleEdited: _isArticleEdited
|
||||
}, () => {
|
||||
if (isArticleEdited !== _isArticleEdited) {
|
||||
let { dispatch } = this.props
|
||||
if (_isArticleEdited) {
|
||||
console.log('lockit')
|
||||
dispatch(lockStatus())
|
||||
} else {
|
||||
console.log('unlockit')
|
||||
dispatch(unlockStatus())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleModeSelectBlur () {
|
||||
if (this.refs.code != null) {
|
||||
this.refs.code.editor.focus()
|
||||
}
|
||||
}
|
||||
|
||||
handleContentChange (e, value) {
|
||||
let { status } = this.props
|
||||
if (status.mode === IDLE_MODE) {
|
||||
return
|
||||
}
|
||||
|
||||
let { article } = this.state
|
||||
article.content = value
|
||||
let _isContentChanged = article.content !== this.props.activeArticle.content
|
||||
|
||||
let { isTagChanged, isModeChanged, isArticleEdited, isTitleChanged } = this.state
|
||||
let _isArticleEdited = _isContentChanged || isTagChanged || isModeChanged || isTitleChanged
|
||||
|
||||
this.setState({
|
||||
article,
|
||||
isContentChanged: _isContentChanged,
|
||||
isArticleEdited: _isArticleEdited
|
||||
}, () => {
|
||||
if (isArticleEdited !== _isArticleEdited) {
|
||||
let { dispatch } = this.props
|
||||
if (_isArticleEdited) {
|
||||
console.log('lockit')
|
||||
dispatch(lockStatus())
|
||||
} else {
|
||||
console.log('unlockit')
|
||||
dispatch(unlockStatus())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleTogglePreviewButtonClick (e) {
|
||||
if (this.state.article.mode === 'markdown') {
|
||||
if (!this.state.previewMode) {
|
||||
let cursorPosition = this.refs.code.getCursorPosition()
|
||||
let firstVisibleRow = this.refs.code.getFirstVisibleRow()
|
||||
this.setState({
|
||||
previewMode: true,
|
||||
cursorPosition,
|
||||
firstVisibleRow
|
||||
}, function () {
|
||||
let previewEl = ReactDOM.findDOMNode(this.refs.preview)
|
||||
let anchors = previewEl.querySelectorAll('.lineAnchor')
|
||||
for (let i = 0; i < anchors.length; i++) {
|
||||
if (parseInt(anchors[i].dataset.key, 10) > cursorPosition.row || i === anchors.length - 1) {
|
||||
var targetAnchor = anchors[i > 0 ? i - 1 : 0]
|
||||
previewEl.scrollTop = targetAnchor.offsetTop - 100
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
previewMode: false
|
||||
}, function () {
|
||||
console.log(this.state.cursorPosition)
|
||||
this.refs.code.moveCursorTo(this.state.cursorPosition.row, this.state.cursorPosition.column)
|
||||
this.refs.code.scrollToLine(this.state.firstVisibleRow)
|
||||
this.refs.code.editor.focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleTitleKeyDown (e) {
|
||||
if (e.keyCode === 9 && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
this.refs.mode.handleIdleSelectClick()
|
||||
}
|
||||
}
|
||||
|
||||
renderEdit () {
|
||||
let { folders, status, tags } = this.props
|
||||
|
||||
let folderOptions = folders.map(folder => {
|
||||
return (
|
||||
<option key={folder.key} value={folder.key}>{folder.name}</option>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='ArticleDetail edit'>
|
||||
<div className='detailInfo'>
|
||||
<div className='left'>
|
||||
<select
|
||||
className='folder'
|
||||
value={this.state.article.FolderKey}
|
||||
onChange={e => this.handleFolderKeyChange(e)}
|
||||
>
|
||||
{folderOptions}
|
||||
</select>
|
||||
{this.state.isArticleEdited ? ' (edited)' : ''}
|
||||
|
||||
<TagSelect
|
||||
tags={this.state.article.tags}
|
||||
onChange={(tags, tag) => this.handleTagsChange(tags, tag)}
|
||||
suggestTags={tags}
|
||||
/>
|
||||
|
||||
{status.isTutorialOpen ? tagSelectTutorialElement : null}
|
||||
|
||||
</div>
|
||||
|
||||
<div className='right'>
|
||||
{
|
||||
this.state.article.mode === 'markdown'
|
||||
? (<button className='preview' onClick={e => this.handleTogglePreviewButtonClick(e)}>
|
||||
{
|
||||
!this.state.previewMode
|
||||
? 'Preview'
|
||||
: 'Edit'
|
||||
}
|
||||
<span className='tooltip'>{process.platform === 'darwin' ? '⌘' : '^'} + p</span>
|
||||
</button>)
|
||||
: null
|
||||
}
|
||||
<button onClick={e => this.handleCancelButtonClick(e)} className='cancelBtn'>
|
||||
Cancel
|
||||
<span className='tooltip'>Esc</span>
|
||||
</button>
|
||||
<button onClick={e => this.handleSaveButtonClick(e)} className='saveBtn'>
|
||||
Save
|
||||
<span className='tooltip'>{process.platform === 'darwin' ? '⌘' : '^'} + s</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className='detailBody'>
|
||||
<div className='detailPanel'>
|
||||
<div className='header'>
|
||||
<div className='title'>
|
||||
<input
|
||||
onKeyDown={e => this.handleTitleKeyDown(e)}
|
||||
placeholder={this.state.article.createdAt == null
|
||||
? `Created at ${moment().format('YYYY/MM/DD HH:mm')}`
|
||||
: 'Title'}
|
||||
ref='title'
|
||||
value={this.state.article.title}
|
||||
onChange={e => this.handleTitleChange(e)}
|
||||
/>
|
||||
</div>
|
||||
<ModeSelect
|
||||
ref='mode'
|
||||
onChange={e => this.handleModeChange(e)}
|
||||
value={this.state.article.mode}
|
||||
className='mode'
|
||||
onBlur={() => this.handleModeSelectBlur()}
|
||||
/>
|
||||
|
||||
{status.isTutorialOpen ? modeSelectTutorialElement : null}
|
||||
</div>
|
||||
|
||||
{this.state.previewMode
|
||||
? <MarkdownPreview ref='preview' content={this.state.article.content}/>
|
||||
: (<CodeEditor
|
||||
ref='code'
|
||||
onChange={(e, value) => this.handleContentChange(e, value)}
|
||||
readOnly={false}
|
||||
mode={this.state.article.mode}
|
||||
code={this.state.article.content}
|
||||
/>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
let { status, activeArticle } = this.props
|
||||
|
||||
if (activeArticle == null) return this.renderEmpty()
|
||||
|
||||
switch (status.mode) {
|
||||
case EDIT_MODE:
|
||||
return this.renderEdit()
|
||||
case IDLE_MODE:
|
||||
default:
|
||||
return this.renderIdle()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ArticleDetail.propTypes = {
|
||||
status: PropTypes.shape(),
|
||||
activeArticle: PropTypes.shape(),
|
||||
user: PropTypes.shape(),
|
||||
folders: PropTypes.array,
|
||||
tags: PropTypes.array,
|
||||
dispatch: PropTypes.func
|
||||
}
|
||||
ArticleDetail.prototype.linkState = linkState
|
||||
124
browser/main/HomePage/ArticleList.js
Normal file
124
browser/main/HomePage/ArticleList.js
Normal file
@@ -0,0 +1,124 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ModeIcon from 'boost/components/ModeIcon'
|
||||
import moment from 'moment'
|
||||
import { switchArticle, NEW } from 'boost/actions'
|
||||
import FolderMark from 'boost/components/FolderMark'
|
||||
import TagLink from 'boost/components/TagLink'
|
||||
import _ from 'lodash'
|
||||
|
||||
export default class ArticleList extends React.Component {
|
||||
componentDidMount () {
|
||||
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
clearInterval(this.refreshTimer)
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
let { articles, activeArticle } = this.props
|
||||
var index = articles.indexOf(activeArticle)
|
||||
var el = ReactDOM.findDOMNode(this)
|
||||
var li = el.querySelectorAll('.ArticleList>div')[index]
|
||||
|
||||
if (li == null) {
|
||||
return
|
||||
}
|
||||
|
||||
var overflowBelow = el.clientHeight + el.scrollTop < li.offsetTop + li.clientHeight
|
||||
if (overflowBelow) {
|
||||
el.scrollTop = li.offsetTop + li.clientHeight - el.clientHeight
|
||||
}
|
||||
var overflowAbove = el.scrollTop > li.offsetTop
|
||||
if (overflowAbove) {
|
||||
el.scrollTop = li.offsetTop
|
||||
}
|
||||
}
|
||||
|
||||
// 移動ができなかったらfalseを返す:
|
||||
selectPriorArticle () {
|
||||
let { articles, activeArticle, dispatch } = this.props
|
||||
let targetIndex = articles.indexOf(activeArticle) - 1
|
||||
let targetArticle = articles[targetIndex]
|
||||
|
||||
if (targetArticle != null) {
|
||||
dispatch(switchArticle(targetArticle.key))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
selectNextArticle () {
|
||||
let { articles, activeArticle, dispatch } = this.props
|
||||
let targetIndex = articles.indexOf(activeArticle) + 1
|
||||
let targetArticle = articles[targetIndex]
|
||||
|
||||
if (targetArticle != null) {
|
||||
dispatch(switchArticle(targetArticle.key))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
handleArticleClick (article) {
|
||||
let { dispatch } = this.props
|
||||
return function (e) {
|
||||
if (article.status === NEW) return null
|
||||
dispatch(switchArticle(article.key))
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
let { articles, activeArticle, folders } = this.props
|
||||
|
||||
let articleElements = articles.map(article => {
|
||||
let tagElements = Array.isArray(article.tags) && article.tags.length > 0
|
||||
? article.tags.map(tag => {
|
||||
return (<TagLink key={tag} tag={tag}/>)
|
||||
})
|
||||
: (<span>Not tagged yet</span>)
|
||||
let folder = _.findWhere(folders, {key: article.FolderKey})
|
||||
|
||||
let title = article.status !== NEW
|
||||
? article.title.trim().length === 0
|
||||
? <small>(Untitled)</small>
|
||||
: article.title
|
||||
: '(New article)'
|
||||
|
||||
return (
|
||||
<div key={'article-' + article.key}>
|
||||
<div onClick={e => this.handleArticleClick(article)(e)} className={'articleItem' + (activeArticle.key === article.key ? ' active' : '')}>
|
||||
<div className='top'>
|
||||
{folder != null
|
||||
? <span className='folderName'><FolderMark color={folder.color}/>{folder.name}</span>
|
||||
: <span><FolderMark color={-1}/>Unknown</span>
|
||||
}
|
||||
<span className='updatedAt'>{article.status != null ? article.status : moment(article.updatedAt).fromNow()}</span>
|
||||
</div>
|
||||
<div className='middle'>
|
||||
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{title}</div>
|
||||
</div>
|
||||
<div className='bottom'>
|
||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tagElements}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='divider'></div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='ArticleList'>
|
||||
{articleElements}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ArticleList.propTypes = {
|
||||
folders: PropTypes.array,
|
||||
articles: PropTypes.array,
|
||||
activeArticle: PropTypes.shape(),
|
||||
dispatch: PropTypes.func
|
||||
}
|
||||
177
browser/main/HomePage/ArticleNavigator.js
Normal file
177
browser/main/HomePage/ArticleNavigator.js
Normal file
@@ -0,0 +1,177 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { findWhere } from 'lodash'
|
||||
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, clearNewArticle, EDIT_MODE } from 'boost/actions'
|
||||
import { openModal } from 'boost/modal'
|
||||
import FolderMark from 'boost/components/FolderMark'
|
||||
import Preferences from 'boost/components/modal/Preferences'
|
||||
import CreateNewFolder from 'boost/components/modal/CreateNewFolder'
|
||||
import keygen from 'boost/keygen'
|
||||
|
||||
const BRAND_COLOR = '#18AF90'
|
||||
|
||||
const preferenceTutorialElement = (
|
||||
<svg width='300' height='300' className='tutorial'>
|
||||
<text x='15' y='30' fill={BRAND_COLOR} fontSize='24'>Preference</text>
|
||||
<svg x='-30' y='-270' width='400' height='400'>
|
||||
<path fill='white' d='M165.9,297c5.3,0,10.6,0.1,15.8,0.1c3.3,0,7.7,0.8,10.7-1c2.3-1.4,3.1-4,4.5-6.2c3.5-5.5,9.6-5.2,14.6-1.9
|
||||
c4.6,3.1,8.7,8,8.4,13.8c-0.3,5.2-3.3,10.1-6.1,14.3c-3.1,4.7-6.6,7-12.2,7.9c-5.2,0.8-11.7,1.6-15.4-3
|
||||
c-6.6-8.2,2.1-20.5,7.4-27.1c6.5-8.1,20.1-14,26.4-2.1c5.4,10.3-3.1,21.7-13,24.8c-5.7,1.8-11,0.9-16.2-1.9c-2-1.1-5-2.6-6.6-4.4
|
||||
c-3.9-4.3-0.3-8.2,2.5-11.2c1.3-1.4-0.8-3.6-2.1-2.1c-2.7,2.9-5.8,6.6-5.1,10.9c0.7,4.4,5.6,6.9,9,8.9c8.6,5.1,18.7,4.8,26.8-1.2
|
||||
c7.3-5.4,11.6-15,8-23.7c-3.3-8.1-11.7-11.8-20-9c-12.5,4.1-33.7,33.5-15.9,43.1c6.8,3.7,19.8,1.8,25.3-3.6
|
||||
c6.1-5.8,12.1-17.2,9.5-25.7c-2.6-8.4-13.7-17-22.6-13.3c-1.6,0.7-3,1.7-4.1,3c-1.6,1.9-2.2,5.1-4.1,6.6c-3.1,2.4-10.1,1-13.7,1
|
||||
c-4,0-7.9,0-11.9-0.1C164,294,164,297,165.9,297L165.9,297z'/>
|
||||
</svg>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const newPostTutorialElement = (
|
||||
<svg width='900' height='900' className='tutorial'>
|
||||
<text x='290' y='155' fill={BRAND_COLOR} fontSize='24'>Create a new post!!</text>
|
||||
<text x='300' y='180' fill={BRAND_COLOR} fontSize='16' children={`press \`${process.platform === 'darwin' ? '⌘' : '^'} + Enter\` or \`a\``}/>
|
||||
<svg x='130' y='-20' width='400' height='400'>
|
||||
<path fill='white' d='M56.2,132.5c11.7-2.9,23.9-6,36.1-4.1c8.7,1.4,16.6,5.5,23.7,10.5c13.3,9.4,24.5,21.5,40.2,27
|
||||
c1.8,0.6,2.6-2.3,0.8-2.9c-17.1-6-28.9-20.3-44-29.7c-7-4.4-14.8-7.4-23-8.2c-11.7-1.1-23.3,1.7-34.5,4.5
|
||||
C53.6,130.1,54.4,133,56.2,132.5L56.2,132.5 z'/>
|
||||
</svg>
|
||||
<svg x='130' y='-120' width='400' height='400'>
|
||||
<path fill='white' d='M82.6,218c-7.7,4.5-15.3,9.3-22.7,14.3c-1,0.7-0.9,2.4,0.4,2.7c6.2,1.8,11.5,4.8,16.2,9.2
|
||||
c1.4,1.3,3.5-0.8,2.1-2.1c-5.1-4.8-10.9-8.1-17.6-10c0.1,0.9,0.2,1.8,0.4,2.7c7.4-5,15-9.8,22.7-14.3
|
||||
C85.7,219.7,84.2,217.1,82.6,218L82.6,218z'/>
|
||||
</svg>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const newFolderTutorialElement = (
|
||||
<svg width='800' height='500' className='tutorial'>
|
||||
<text x='145' y='110' fill={BRAND_COLOR} fontSize='24'>Create a new folder!!</text>
|
||||
<svg x='115' y='-10' width='300' height='400'>
|
||||
<path fill='white' d='M36.6,3.7C28.8,8.2,21.3,13,13.9,18c-1,0.7-0.9,2.4,0.4,2.7c6.2,1.8,11.5,4.8,16.2,9.2
|
||||
c1.4,1.3,3.5-0.8,2.1-2.1c-5.1-4.8-10.9-8.1-17.6-10c0.1,0.9,0.2,1.8,0.4,2.7c7.4-5,15-9.8,22.7-14.3C39.7,5.3,38.2,2.7,36.6,3.7
|
||||
L36.6,3.7z'/>
|
||||
<path fill='white' d='M16.8,21.5c13.3-6.9,29.5-7,42.6,0.6c5.6,3.2,10.4,7.7,14.1,13c3.8,5.4,10.3,16.2,2.2,20.6
|
||||
c-1.2,0.7-2.5,1.2-3.9,1.6c-1.1,0.4-2.3,0.5-3.4,0.5c-1.3-1.4-2.6-2.8-3.9-4.2c-0.2-4.6,7.5-6,10.5-5.8
|
||||
c7.4,0.7,13.7,6.2,18.4,11.6c9.4,10.7,14.7,24.3,15.6,38.5c0.1,1.9,3.1,1.9,3,0c-0.9-15.5-6.9-30.4-17.5-41.8
|
||||
c-6.8-7.3-25.8-19.1-32.3-4.8c-1.9,4.1,0.3,8.5,4.8,9.4c4.6,0.8,11.6-1.8,14.3-5.7c3.6-5.3-0.1-12.8-2.8-17.6
|
||||
c-3.4-6.1-8.2-11.3-13.8-15.4C50.2,11.6,31,10.9,15.3,19C13.6,19.8,15.1,22.4,16.8,21.5L16.8,21.5z'/>
|
||||
</svg>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default class ArticleNavigator extends React.Component {
|
||||
handlePreferencesButtonClick (e) {
|
||||
openModal(Preferences)
|
||||
}
|
||||
|
||||
handleNewPostButtonClick (e) {
|
||||
let { dispatch, folders, status } = this.props
|
||||
let { targetFolders } = status
|
||||
|
||||
let FolderKey = targetFolders.length > 0
|
||||
? targetFolders[0].key
|
||||
: folders[0].key
|
||||
|
||||
let newArticle = {
|
||||
id: null,
|
||||
key: keygen(),
|
||||
title: '',
|
||||
content: '',
|
||||
mode: 'markdown',
|
||||
tags: [],
|
||||
FolderKey: FolderKey,
|
||||
status: 'NEW'
|
||||
}
|
||||
|
||||
dispatch(clearNewArticle())
|
||||
dispatch(updateArticle(newArticle))
|
||||
dispatch(switchArticle(newArticle.key, true))
|
||||
dispatch(switchMode(EDIT_MODE))
|
||||
}
|
||||
|
||||
handleNewFolderButton (e) {
|
||||
let { activeUser } = this.props
|
||||
openModal(CreateNewFolder, {user: activeUser})
|
||||
}
|
||||
|
||||
handleFolderButtonClick (name) {
|
||||
return e => {
|
||||
let { dispatch } = this.props
|
||||
dispatch(switchFolder(name))
|
||||
}
|
||||
}
|
||||
|
||||
handleAllFoldersButtonClick (e) {
|
||||
let { dispatch } = this.props
|
||||
dispatch(setSearchFilter(''))
|
||||
}
|
||||
|
||||
render () {
|
||||
let { status, user, folders, allArticles } = this.props
|
||||
let { targetFolders } = status
|
||||
if (targetFolders == null) targetFolders = []
|
||||
|
||||
let folderElememts = folders.map((folder, index) => {
|
||||
let isActive = findWhere(targetFolders, {key: folder.key})
|
||||
let articleCount = allArticles.filter(article => article.FolderKey === folder.key && article.status !== 'NEW').length
|
||||
|
||||
return (
|
||||
<button onClick={e => this.handleFolderButtonClick(folder.name)(e)} key={'folder-' + folder.key} className={isActive ? 'active' : ''}>
|
||||
<FolderMark color={folder.color}/> {folder.name} <span className='articleCount'>{articleCount}</span>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='ArticleNavigator'>
|
||||
<div className='userInfo'>
|
||||
<div className='userProfileName'>{user.name}</div>
|
||||
<div className='userName'>localStorage</div>
|
||||
<button onClick={e => this.handlePreferencesButtonClick(e)} className='settingBtn'>
|
||||
<i className='fa fa-fw fa-chevron-down'/>
|
||||
<span className='tooltip'>Preferences</span>
|
||||
</button>
|
||||
|
||||
{status.isTutorialOpen ? preferenceTutorialElement : null}
|
||||
|
||||
</div>
|
||||
|
||||
<div className='controlSection'>
|
||||
<button onClick={e => this.handleNewPostButtonClick(e)} className='newPostBtn'>
|
||||
New Post
|
||||
<span className='tooltip'>Create a new Post ({process.platform === 'darwin' ? '⌘' : '^'} + Enter or a)</span>
|
||||
</button>
|
||||
|
||||
{status.isTutorialOpen ? newPostTutorialElement : null}
|
||||
|
||||
</div>
|
||||
|
||||
<div className='folders'>
|
||||
<div className='header'>
|
||||
<div className='title'>Folders</div>
|
||||
<button onClick={e => this.handleNewFolderButton(e)} className='addBtn'>
|
||||
<i className='fa fa-fw fa-plus'/>
|
||||
<span className='tooltip'>Create a new folder</span>
|
||||
</button>
|
||||
|
||||
{status.isTutorialOpen ? newFolderTutorialElement : null}
|
||||
|
||||
</div>
|
||||
<div className='folderList'>
|
||||
<button onClick={e => this.handleAllFoldersButtonClick(e)} className={targetFolders.length === 0 ? 'active' : ''}>All folders</button>
|
||||
{folderElememts}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ArticleNavigator.propTypes = {
|
||||
activeUser: PropTypes.object,
|
||||
folders: PropTypes.array,
|
||||
allArticles: PropTypes.array,
|
||||
status: PropTypes.shape({
|
||||
folderId: PropTypes.number
|
||||
}),
|
||||
dispatch: PropTypes.func
|
||||
}
|
||||
|
||||
195
browser/main/HomePage/ArticleTopBar.js
Normal file
195
browser/main/HomePage/ArticleTopBar.js
Normal file
@@ -0,0 +1,195 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import ExternalLink from 'boost/components/ExternalLink'
|
||||
import { setSearchFilter, clearSearch, toggleTutorial } from 'boost/actions'
|
||||
|
||||
const BRAND_COLOR = '#18AF90'
|
||||
|
||||
const searchTutorialElement = (
|
||||
<svg width='750' height='120' className='tutorial'>
|
||||
<text x='450' y='33' fill={BRAND_COLOR} fontSize='24'>Search some posts!!</text>
|
||||
<text x='450' y='60' fill={BRAND_COLOR} fontSize='18'>{'- Search by tag : #{string}'}</text>
|
||||
<text x='450' y='85' fill={BRAND_COLOR} fontSize='18'>
|
||||
{'- Search by folder : /{folder_name}\n'}</text>
|
||||
<text x='465' y='105' fill={BRAND_COLOR} fontSize='14'>
|
||||
{'exact match : //{folder_name}'}</text>
|
||||
|
||||
<svg width='500' height='300'>
|
||||
<path fill='white' d='M54.5,51.5c-12.4,3.3-27.3-1.4-38.4-7C11.2,42,5,38.1,5.6,31.8c0.7-6.9,8.1-11.2,13.8-13.7
|
||||
c12.3-5.4,26.4-6.8,39.7-7.7C72.4,9.6,85.7,9.7,99,9.8c55.2,0.3,110.4,2.2,165.5-1.5C291,6.5,317.7,3.8,344.1,7
|
||||
c12.8,1.6,25.8,4.4,37.5,10c1.2,0.6,2.4,1.1,3.5,1.8c2.4,1.4,3.2,1.5,3.3,4.5c0.1,3.6-2.3,5.9-4.8,8.3c-3.9,3.8-8.6,6.8-13.5,9.2
|
||||
c-12.6,6-26.5,7.2-40.3,7.7c-13.7,0.5-27.5,0.6-41.2,1.1c-27.7,0.9-55.3,2.2-82.9,4c-30.8,2-61.6,4.5-92.3,7.6
|
||||
c-15.4,1.5-30.8,3.7-46.3,4.9c-13.6,1.1-30.7,1.5-41.8-7.8c-1.5-1.2-3.6,0.9-2.1,2.1c8.9,7.5,21.4,9.2,32.7,9.2
|
||||
c15.3,0,30.6-2.6,45.8-4.2c31.3-3.3,62.7-6,94.2-8.1c30.9-2.1,61.8-3.7,92.8-4.7c15.7-0.5,31.4-0.5,47-1.3
|
||||
c13.1-0.7,26.3-2.7,38.1-8.9c4.4-2.3,8.5-5.1,12-8.6c2.8-2.8,7.3-7.3,6.4-11.7c-0.8-4.3-6.4-6.3-9.8-7.9
|
||||
c-5.6-2.6-11.4-4.6-17.3-6.2c-28.3-7.5-58.1-5.6-87-3.6c-62.3,4.4-124.5,2.6-187,2.4c-16.4,0-32.8,0-49,2.4
|
||||
C29.9,11,13.4,13.8,5.5,24.6c-7.3,10,0.7,18.4,9.8,22.9c11.9,5.8,26.9,10.4,40,7C57.2,53.9,56.4,51,54.5,51.5L54.5,51.5z'/>
|
||||
<path fill='white' d='M446.5,21.4c-9.1-1.6-18.1-3.5-27.4-3.5c-10.2,0-20.4,1.4-30.5,2.8c-1.9,0.3-1.9,3.3,0,3
|
||||
c9.5-1.3,19.1-2.6,28.8-2.7c9.6-0.2,18.9,1.7,28.3,3.4C447.6,24.7,448.4,21.8,446.5,21.4L446.5,21.4z'/>
|
||||
</svg>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export default class ArticleTopBar extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
isTooltipHidden: true,
|
||||
isLinksDropdownOpen: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.searchInput = ReactDOM.findDOMNode(this.refs.searchInput)
|
||||
this.linksButton = ReactDOM.findDOMNode(this.refs.links)
|
||||
this.showLinksDropdown = e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (!this.state.isLinksDropdownOpen) {
|
||||
this.setState({isLinksDropdownOpen: true})
|
||||
}
|
||||
}
|
||||
this.linksButton.addEventListener('click', this.showLinksDropdown)
|
||||
this.hideLinksDropdown = e => {
|
||||
if (this.state.isLinksDropdownOpen) {
|
||||
this.setState({isLinksDropdownOpen: false})
|
||||
}
|
||||
}
|
||||
document.addEventListener('click', this.hideLinksDropdown)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('click', this.hideLinksDropdown)
|
||||
this.linksButton.removeEventListener('click', this.showLinksDropdown())
|
||||
}
|
||||
|
||||
handleTooltipRequest (e) {
|
||||
if (this.searchInput.value.length === 0 && (document.activeElement === this.searchInput)) {
|
||||
this.setState({isTooltipHidden: false})
|
||||
} else {
|
||||
this.setState({isTooltipHidden: true})
|
||||
}
|
||||
}
|
||||
|
||||
isInputFocused () {
|
||||
return document.activeElement === ReactDOM.findDOMNode(this.refs.searchInput)
|
||||
}
|
||||
|
||||
escape () {
|
||||
let { status, dispatch } = this.props
|
||||
if (status.search.length > 0) {
|
||||
dispatch(clearSearch())
|
||||
return
|
||||
}
|
||||
this.blurInput()
|
||||
}
|
||||
|
||||
focusInput () {
|
||||
this.searchInput.focus()
|
||||
}
|
||||
|
||||
blurInput () {
|
||||
this.searchInput.blur()
|
||||
}
|
||||
|
||||
handleSearchChange (e) {
|
||||
let { dispatch } = this.props
|
||||
|
||||
dispatch(setSearchFilter(e.target.value))
|
||||
this.handleTooltipRequest()
|
||||
}
|
||||
|
||||
handleSearchClearButton (e) {
|
||||
this.searchInput.value = ''
|
||||
this.focusInput()
|
||||
}
|
||||
|
||||
handleTutorialButtonClick (e) {
|
||||
let { dispatch } = this.props
|
||||
|
||||
dispatch(toggleTutorial())
|
||||
}
|
||||
|
||||
render () {
|
||||
let { status } = this.props
|
||||
return (
|
||||
<div className='ArticleTopBar'>
|
||||
<div className='left'>
|
||||
<div className='search'>
|
||||
<i className='fa fa-search fa-fw' />
|
||||
<input
|
||||
ref='searchInput'
|
||||
onFocus={e => this.handleSearchChange(e)}
|
||||
onBlur={e => this.handleSearchChange(e)}
|
||||
value={this.props.status.search}
|
||||
onChange={e => this.handleSearchChange(e)}
|
||||
placeholder='Search'
|
||||
type='text'
|
||||
/>
|
||||
{
|
||||
this.props.status.search != null && this.props.status.search.length > 0
|
||||
? <button onClick={e => this.handleSearchClearButton(e)} className='searchClearBtn'><i className='fa fa-times'/></button>
|
||||
: null
|
||||
}
|
||||
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
|
||||
<ul>
|
||||
<li>- Search by tag : #{'{string}'}</li>
|
||||
<li>- Search by folder : /{'{folder_name}'}</li>
|
||||
<li><small>exact match : //{'{folder_name}'}</small></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{status.isTutorialOpen ? searchTutorialElement : null}
|
||||
|
||||
</div>
|
||||
<div className='right'>
|
||||
<button onClick={e => this.handleTutorialButtonClick(e)}>?<span className='tooltip'>How to use</span>
|
||||
</button>
|
||||
<a ref='links' className='linksBtn' href>
|
||||
<img src='../../resources/app.png' width='44' height='44'/>
|
||||
</a>
|
||||
{
|
||||
this.state.isLinksDropdownOpen
|
||||
? (
|
||||
<div className='links-dropdown'>
|
||||
<ExternalLink className='links-item' href='https://b00st.io'>
|
||||
<i className='fa fa-fw fa-home'/>Boost official page
|
||||
</ExternalLink>
|
||||
<ExternalLink className='links-item' href='https://github.com/BoostIO/boost-app-discussions/issues'>
|
||||
<i className='fa fa-fw fa-bullhorn'/> Discuss
|
||||
</ExternalLink>
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
|
||||
{status.isTutorialOpen ? (
|
||||
<div className='tutorial'>
|
||||
<div onClick={e => this.handleTutorialButtonClick(e)} className='clickJammer'/>
|
||||
<svg width='500' height='250' className='finder'>
|
||||
<text x='100' y='25' fontSize='32' fill={BRAND_COLOR}>Also, you can open Finder!!</text>
|
||||
<text x='120' y='55' fontSize='18' fill={BRAND_COLOR}>with pressing `Control` + `shift` + `tab`</text>
|
||||
</svg>
|
||||
<svg width='450' className='global'>
|
||||
<text x='100' y='45' fontSize='24' fill={BRAND_COLOR}>Hope you to enjoy our app :D</text>
|
||||
<text x='50' y='75' fontSize='18' fill={BRAND_COLOR}>Press any key or click to escape tutorial mode</text>
|
||||
</svg>
|
||||
<div className='back'></div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ArticleTopBar.propTypes = {
|
||||
search: PropTypes.string,
|
||||
dispatch: PropTypes.func,
|
||||
status: PropTypes.shape({
|
||||
search: PropTypes.string
|
||||
})
|
||||
}
|
||||
52
browser/main/HomePage/UserNavigator.js
Normal file
52
browser/main/HomePage/UserNavigator.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React, { Component, PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import ProfileImage from 'boost/components/ProfileImage'
|
||||
import { openModal } from 'boost/modal'
|
||||
import CreateNewTeam from 'boost/components/modal/CreateNewTeam'
|
||||
|
||||
export default class UserNavigator extends Component {
|
||||
handleClick (e) {
|
||||
openModal(CreateNewTeam)
|
||||
}
|
||||
|
||||
// for dev
|
||||
componentDidMount () {
|
||||
// openModal(CreateNewTeam)
|
||||
}
|
||||
|
||||
renderUserList () {
|
||||
if (this.props.users == null) return null
|
||||
|
||||
var users = this.props.users.map((user, index) => (
|
||||
<li key={'user-' + user.id}>
|
||||
<Link to={'/users/' + user.id} activeClassName='active'>
|
||||
<ProfileImage email={user.email} size='44'/>
|
||||
<div className='userTooltip'>{user.name}</div>
|
||||
{index < 9 ? <div className='keyLabel'>{'⌘' + (index + 1)}</div> : null}
|
||||
</Link>
|
||||
</li>
|
||||
))
|
||||
|
||||
return (
|
||||
<ul className='userList'>
|
||||
{users}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='UserNavigator'>
|
||||
{this.renderUserList()}
|
||||
<button className='createTeamBtn' onClick={e => this.handleClick(e)}>
|
||||
+
|
||||
<div className='tooltip'>Create a new team</div>
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
UserNavigator.propTypes = {
|
||||
users: PropTypes.array
|
||||
}
|
||||
93
browser/main/LoginPage.js
Normal file
93
browser/main/LoginPage.js
Normal file
@@ -0,0 +1,93 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import linkState from 'boost/linkState'
|
||||
import { login } from 'boost/api'
|
||||
import auth from 'boost/auth'
|
||||
|
||||
export default class LoginPage extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
user: {},
|
||||
isSending: false,
|
||||
error: null
|
||||
}
|
||||
this.linkState = linkState
|
||||
}
|
||||
|
||||
handleSubmit (e) {
|
||||
e.preventDefault()
|
||||
this.setState({
|
||||
isSending: true,
|
||||
error: null
|
||||
}, function () {
|
||||
login(this.state.user)
|
||||
.then(res => {
|
||||
let { user, token } = res.body
|
||||
auth.user(user, token)
|
||||
|
||||
this.props.history.pushState('home')
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
return this.setState({
|
||||
error: {
|
||||
name: 'CunnectionRefused',
|
||||
message: 'Can\'t cznnect to API server.'
|
||||
},
|
||||
isSending: false
|
||||
})
|
||||
} else if (err.status != null) {
|
||||
return this.setState({
|
||||
error: {
|
||||
name: err.response.body.name,
|
||||
message: err.response.body.message
|
||||
},
|
||||
isSending: false
|
||||
})
|
||||
}
|
||||
else throw err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='LoginContainer'>
|
||||
<img className='logo' src='../../resources/favicon-230x230.png'/>
|
||||
|
||||
<nav className='authNavigator text-center'>
|
||||
<Link to='/login' activeClassName='active'>Log In</Link> / <Link to='/signup' activeClassName='active'>Sign Up</Link>
|
||||
</nav>
|
||||
|
||||
<form onSubmit={e => this.handleSubmit(e)}>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.password')} onChange={this.handleChange} type='password' placeholder='Password'/>
|
||||
</div>
|
||||
|
||||
{this.state.isSending
|
||||
? (
|
||||
<p className='alertInfo'>Logging in...</p>
|
||||
) : null}
|
||||
|
||||
{this.state.error != null ? <p className='alertError'>{this.state.error.message}</p> : null}
|
||||
|
||||
<div className='formField'>
|
||||
<button className='logInButton' type='submit'>Log In</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LoginPage.propTypes = {
|
||||
history: PropTypes.shape({
|
||||
pushState: PropTypes.func
|
||||
})
|
||||
}
|
||||
35
browser/main/MainPage.js
Normal file
35
browser/main/MainPage.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
import React, { PropTypes } from 'react'
|
||||
|
||||
export default class MainContainer extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {updateAvailable: false}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
ipc.on('update-available', function (message) {
|
||||
this.setState({updateAvailable: true})
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
updateApp () {
|
||||
ipc.send('update-app', 'Deal with it.')
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='Main'>
|
||||
{this.state.updateAvailable ? (
|
||||
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
|
||||
) : null}
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MainContainer.propTypes = {
|
||||
children: PropTypes.element
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
function basicFilter (keyword, articles) {
|
||||
if (keyword === '' || keyword == null) return articles
|
||||
var firstFiltered = articles.filter(function (article) {
|
||||
|
||||
var first = article.type === 'code' ? article.description : article.title
|
||||
if (first.match(new RegExp(keyword, 'i'))) return true
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
var secondFiltered = articles.filter(function (article) {
|
||||
var second = article.type === 'code' ? article.content : article.content
|
||||
if (second.match(new RegExp(keyword, 'i'))) return true
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
return firstFiltered.concat(secondFiltered).filter(function (value, index, self) {
|
||||
return self.indexOf(value) === index
|
||||
})
|
||||
}
|
||||
|
||||
function codeFilter (articles) {
|
||||
return articles.filter(function (article) {
|
||||
return article.type === 'code'
|
||||
})
|
||||
}
|
||||
|
||||
function noteFilter (articles) {
|
||||
return articles.filter(function (article) {
|
||||
return article.type === 'note'
|
||||
})
|
||||
}
|
||||
|
||||
function tagFilter (keyword, articles) {
|
||||
return articles.filter(function (article) {
|
||||
return article.Tags.some(function (tag) {
|
||||
return tag.name.match(new RegExp('^' + keyword, 'i'))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function searchArticle (search, articles) {
|
||||
var keywords = search.split(' ')
|
||||
|
||||
for (var keyword of keywords) {
|
||||
if (keyword.match(/^\$c/, 'i')) {
|
||||
articles = codeFilter(articles)
|
||||
continue
|
||||
} else if (keyword.match(/^\$n/, 'i')) {
|
||||
articles = noteFilter(articles)
|
||||
continue
|
||||
} else if (keyword.match(/^#[A-Za-z0-9]+/)) {
|
||||
articles = tagFilter(keyword.substring(1, keyword.length), articles)
|
||||
continue
|
||||
}
|
||||
articles = basicFilter(keyword, articles)
|
||||
}
|
||||
|
||||
return articles.sort(function (a, b) {
|
||||
return new Date(b.updatedAt) - new Date(a.updatedAt)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
searchArticle: searchArticle
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/* global localStorage*/
|
||||
|
||||
var mixin = {}
|
||||
|
||||
mixin.OnlyGuest = {
|
||||
componentDidMount: function () {
|
||||
var currentUser = localStorage.getItem('currentUser')
|
||||
|
||||
if (currentUser == null) {
|
||||
return
|
||||
}
|
||||
this.transitionTo('userHome', {userName: currentUser.name})
|
||||
}
|
||||
}
|
||||
|
||||
mixin.OnlyUser = {
|
||||
componentDidMount: function () {
|
||||
var currentUser = localStorage.getItem('currentUser')
|
||||
|
||||
if (currentUser == null) {
|
||||
this.transitionTo('login')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = mixin
|
||||
@@ -1,8 +0,0 @@
|
||||
var shell = require('shell')
|
||||
|
||||
module.exports = {
|
||||
openExternal: function (e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
var ForceUpdate = function (interval) {
|
||||
return {
|
||||
componentDidMount: function () {
|
||||
this.refreshTimer = setInterval(function () {
|
||||
this.forceUpdate()
|
||||
}.bind(this), interval)
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
clearInterval(this.refreshTimer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ForceUpdate
|
||||
@@ -1,33 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
var Reflux = require('reflux')
|
||||
|
||||
var state = {
|
||||
|
||||
}
|
||||
|
||||
var keyDown = Reflux.createAction()
|
||||
|
||||
var KeyStore = Reflux.createStore({
|
||||
init: function () {
|
||||
this.listenTo(keyDown, this.onKeyDown)
|
||||
document.addEventListener('keydown', function (e) {
|
||||
keyDown(e)
|
||||
})
|
||||
},
|
||||
setState: function (newState, cb) {
|
||||
for (var key in newState) {
|
||||
state[key] = newState[key]
|
||||
}
|
||||
if (typeof cb === 'function') cb()
|
||||
},
|
||||
onKeyDown: function (e) {
|
||||
/*
|
||||
Modals
|
||||
*/
|
||||
if (state.codeForm || state.noteForm || state.noteDeleteModal || state.codeDeleteModal || state.addMemberModal || state.aboutModal || state.editProfileModal || state.contactModal || state.teamCreateModal || state.planetCreateModal || state.planetSettingModal || state.teamSettingsModal || state.logoutModal) {
|
||||
// ESC
|
||||
if (e.keyCode === 27) this.cast('closeModal')
|
||||
|
||||
// Cmd + Enter
|
||||
if (e.keyCode === 13 && e.metaKey) {
|
||||
if (state.codeForm) this.cast('submitCodeForm')
|
||||
if (state.noteForm) this.cast('submitNoteForm')
|
||||
if (state.codeDeleteModal) this.cast('submitCodeDeleteModal')
|
||||
if (state.noteDeleteModal) this.cast('submitNoteDeleteModal')
|
||||
if (state.addMemberModal) this.cast('submitAddMemberModal')
|
||||
if (state.contactModal) this.cast('submitContactModal')
|
||||
if (state.teamCreateModal) this.cast('submitTeamCreateModal')
|
||||
if (state.planetCreateModal) this.cast('submitPlanetCreateModal')
|
||||
if (state.logoutModal) this.cast('submitLogoutModal')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
PlanetContainer
|
||||
*/
|
||||
if (state.planetContainer) {
|
||||
// Cmd + Enter, A
|
||||
if ((e.keyCode === 13 && e.metaKey) || e.keyCode === 65) this.cast('openLaunchModal')
|
||||
|
||||
// Esc
|
||||
if (e.keyCode === 27) this.cast('toggleFocusSearchInput')
|
||||
|
||||
// Up
|
||||
if (e.keyCode === 38) this.cast('selectPriorArticle')
|
||||
|
||||
// Down
|
||||
if (e.keyCode === 40) this.cast('selectNextArticle')
|
||||
|
||||
// E
|
||||
if (e.keyCode === 69) this.cast('openEditModal')
|
||||
|
||||
// D
|
||||
if (e.keyCode === 68) this.cast('openDeleteModal')
|
||||
}
|
||||
|
||||
/*
|
||||
HomeContainer
|
||||
*/
|
||||
if (state.homeContainer) {
|
||||
if (e.keyCode > 48 && e.keyCode < 58 && e.metaKey) {
|
||||
this.cast('switchPlanet', e.keyCode - 48)
|
||||
}
|
||||
}
|
||||
},
|
||||
cast: function (status, data) {
|
||||
this.trigger({
|
||||
status: status,
|
||||
data: data
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = function (stateKey) {
|
||||
return {
|
||||
mixins: [Reflux.listenTo(KeyStore, 'onKeyCast')],
|
||||
componentDidMount: function () {
|
||||
var newState = {}
|
||||
newState[stateKey] = true
|
||||
KeyStore.setState(newState)
|
||||
},
|
||||
componentWillUnmount: function () {
|
||||
var newState = {}
|
||||
newState[stateKey] = false
|
||||
KeyStore.setState(newState)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
var markdownit = require('markdown-it')
|
||||
var md = markdownit({
|
||||
typographer: true,
|
||||
linkify: true
|
||||
})
|
||||
|
||||
var Markdown = {
|
||||
markdown: function (content) {
|
||||
return md.render(content)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Markdown
|
||||
@@ -1,42 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
var ModalBase = React.createClass({
|
||||
getInitialState: function () {
|
||||
return {
|
||||
component: null,
|
||||
componentProps: {},
|
||||
isHidden: true
|
||||
}
|
||||
},
|
||||
close: function () {
|
||||
this.setState({component: null, componentProps: null, isHidden: true})
|
||||
},
|
||||
render: function () {
|
||||
var componentProps = this.state.componentProps
|
||||
return (
|
||||
<div className={'ModalBase' + (this.state.isHidden ? ' hide' : '')}>
|
||||
<div onClick={this.close} className='modalBack'/>
|
||||
{this.state.component == null ? null : (
|
||||
<this.state.component {...componentProps} close={this.close}/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
var modalBase = null
|
||||
|
||||
module.exports = {
|
||||
componentDidMount: function () {
|
||||
if (modalBase == null) {
|
||||
var el = document.createElement('div')
|
||||
document.body.appendChild(el)
|
||||
modalBase = React.render(<ModalBase/>, el)
|
||||
}
|
||||
},
|
||||
openModal: function (component, props) {
|
||||
modalBase.setState({component: component, componentProps: props, isHidden: false})
|
||||
},
|
||||
closeModal: function () {
|
||||
modalBase.setState({isHidden: true})
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var request = require('superagent-promise')(require('superagent'), Promise)
|
||||
var apiUrl = require('../../../config').apiUrl
|
||||
|
||||
module.exports = {
|
||||
// Auth
|
||||
login: function (input) {
|
||||
return request
|
||||
.post(apiUrl + 'auth')
|
||||
.send(input)
|
||||
},
|
||||
signup: function (input) {
|
||||
return request
|
||||
.post(apiUrl + 'auth/signup')
|
||||
.send(input)
|
||||
},
|
||||
getUser: function () {
|
||||
return request
|
||||
.get(apiUrl + 'auth/user')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
},
|
||||
changePassword: function (input) {
|
||||
return request
|
||||
.post(apiUrl + 'auth/password')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
},
|
||||
|
||||
// Resources
|
||||
fetchUser: function (userName) {
|
||||
return request
|
||||
.get(apiUrl + 'resources/' + userName)
|
||||
.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)
|
||||
}
|
||||
}
|
||||
104
browser/main/SignupPage.js
Normal file
104
browser/main/SignupPage.js
Normal file
@@ -0,0 +1,104 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import { Link } from 'react-router'
|
||||
import linkState from 'boost/linkState'
|
||||
import openExternal from 'boost/openExternal'
|
||||
import { signup } from 'boost/api'
|
||||
import auth from 'boost/auth'
|
||||
|
||||
export default class SignupContainer extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
user: {},
|
||||
connectionFailed: false,
|
||||
emailConflicted: false,
|
||||
nameConflicted: false,
|
||||
validationFailed: false,
|
||||
isSending: false,
|
||||
error: null
|
||||
}
|
||||
this.linkState = linkState
|
||||
this.openExternal = openExternal
|
||||
}
|
||||
|
||||
handleSubmit (e) {
|
||||
this.setState({
|
||||
isSending: true,
|
||||
error: null
|
||||
}, function () {
|
||||
signup(this.state.user)
|
||||
.then(res => {
|
||||
let { user, token } = res.body
|
||||
auth.user(user, token)
|
||||
|
||||
this.props.history.pushState('home')
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
if (err.code === 'ECONNREFUSED') {
|
||||
return this.setState({
|
||||
error: {
|
||||
name: 'CunnectionRefused',
|
||||
message: 'Can\'t connect to API server.'
|
||||
},
|
||||
isSending: false
|
||||
})
|
||||
} else if (err.status != null) {
|
||||
return this.setState({
|
||||
error: {
|
||||
name: err.response.body.name,
|
||||
message: err.response.body.message
|
||||
},
|
||||
isSending: false
|
||||
})
|
||||
}
|
||||
else throw err
|
||||
})
|
||||
})
|
||||
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='SignupContainer'>
|
||||
<img className='logo' src='../../resources/favicon-230x230.png'/>
|
||||
|
||||
<nav className='authNavigator text-center'><Link to='/login' activeClassName='active'>Log In</Link> / <Link to='/signup' activeClassName='active'>Sign Up</Link></nav>
|
||||
|
||||
<form onSubmit={e => this.handleSubmit(e)}>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.password')} type='password' placeholder='Password'/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.name')} type='text' placeholder='name'/>
|
||||
</div>
|
||||
<div className='formField'>
|
||||
<input valueLink={this.linkState('user.profileName')} type='text' placeholder='Profile name'/>
|
||||
</div>
|
||||
|
||||
{this.state.isSending ? (
|
||||
<p className='alertInfo'>Signing up...</p>
|
||||
) : null}
|
||||
|
||||
{this.state.error != null ? <p className='alertError'>{this.state.error.message}</p> : null}
|
||||
|
||||
<div className='formField'>
|
||||
<button className='logInButton' type='submit'>Sign Up</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p className='alert'>会員登録することで、<a onClick={this.openExternal} href='http://boostio.github.io/regulations.html'>当サイトの利用規約</a>及び<a onClick={this.openExternal} href='http://boostio.github.io/privacypolicies.html'>Cookieの使用を含むデータに関するポリシー</a>に同意するものとします。</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SignupContainer.propTypes = {
|
||||
history: PropTypes.shape({
|
||||
pushState: PropTypes.func
|
||||
})
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/* global localStorage */
|
||||
var Reflux = require('reflux')
|
||||
var request = require('superagent')
|
||||
|
||||
var apiUrl = require('../../../config').apiUrl
|
||||
|
||||
var AuthStore = Reflux.createStore({
|
||||
init: function () {
|
||||
},
|
||||
// Reflux Store
|
||||
login: function (input) {
|
||||
request
|
||||
.post(apiUrl + 'auth/login')
|
||||
.send(input)
|
||||
.set('Accept', 'application/json')
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
this.trigger({
|
||||
status: 'failedToLogIn',
|
||||
data: res
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user = res.body.user
|
||||
localStorage.setItem('token', res.body.token)
|
||||
localStorage.setItem('user', JSON.stringify(res.body.user))
|
||||
|
||||
this.trigger({
|
||||
status: 'loggedIn',
|
||||
data: user
|
||||
})
|
||||
}.bind(this))
|
||||
},
|
||||
register: function (input) {
|
||||
request
|
||||
.post(apiUrl + 'auth/signup')
|
||||
.send(input)
|
||||
.set('Accept', 'application/json')
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
console.error(res)
|
||||
this.trigger({
|
||||
status: 'failedToRegister',
|
||||
data: res
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user = res.body.user
|
||||
localStorage.setItem('token', res.body.token)
|
||||
localStorage.setItem('user', JSON.stringify(res.body.user))
|
||||
|
||||
this.trigger({
|
||||
status: 'registered',
|
||||
data: user
|
||||
})
|
||||
}.bind(this))
|
||||
},
|
||||
refreshUser: function () {
|
||||
request
|
||||
.get(apiUrl + 'auth/user')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
if (res.status === 401 || res.status === 403) {
|
||||
AuthActions.logout()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var user = res.body
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
|
||||
this.trigger({
|
||||
status: 'userRefreshed',
|
||||
data: user
|
||||
})
|
||||
}.bind(this))
|
||||
},
|
||||
logout: function () {
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('currentUser')
|
||||
|
||||
this.trigger({
|
||||
status: 'loggedOut'
|
||||
})
|
||||
},
|
||||
updateProfile: function (input) {
|
||||
request
|
||||
.put(apiUrl + 'auth/user')
|
||||
.set({
|
||||
Authorization: 'Bearer ' + localStorage.getItem('token')
|
||||
})
|
||||
.send(input)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
this.trigger({
|
||||
status: 'userProfileUpdatingFailed',
|
||||
data: err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var user = res.body
|
||||
localStorage.setItem('user', JSON.stringify(user))
|
||||
|
||||
this.trigger({
|
||||
status: 'userProfileUpdated',
|
||||
data: user
|
||||
})
|
||||
}.bind(this))
|
||||
},
|
||||
// Methods
|
||||
check: function () {
|
||||
if (localStorage.getItem('token')) return true
|
||||
return false
|
||||
},
|
||||
getUser: function () {
|
||||
var userJSON = localStorage.getItem('currentUser')
|
||||
if (userJSON == null) return null
|
||||
return JSON.parse(userJSON)
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = AuthStore
|
||||
@@ -1,167 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var Reflux = require('reflux')
|
||||
|
||||
var UserStore = require('./UserStore')
|
||||
|
||||
var Helper = require('../Mixins/Helper')
|
||||
|
||||
var actions = Reflux.createActions([
|
||||
'update',
|
||||
'destroy',
|
||||
'updateCode',
|
||||
'destroyCode',
|
||||
'updateNote',
|
||||
'destroyNote'
|
||||
])
|
||||
|
||||
module.exports = Reflux.createStore({
|
||||
mixins: [Helper],
|
||||
listenables: [actions],
|
||||
Actions: actions,
|
||||
onUpdate: function (planet) {
|
||||
// Copy the planet object
|
||||
var aPlanet = Object.assign({}, planet)
|
||||
delete aPlanet.Codes
|
||||
delete aPlanet.Notes
|
||||
|
||||
// Check if the planet should be updated to currentUser
|
||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
|
||||
var ownedByCurrentUser = currentUser.id === aPlanet.OwnerId
|
||||
|
||||
if (ownedByCurrentUser) {
|
||||
currentUser.Planets = this.updateItemToTargetArray(aPlanet, currentUser.Planets)
|
||||
}
|
||||
|
||||
if (!ownedByCurrentUser) {
|
||||
var team = null
|
||||
currentUser.Teams.some(function (_team) {
|
||||
if (_team.id === aPlanet.OwnerId) {
|
||||
team = _team
|
||||
return true
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
if (team) {
|
||||
team.Planets = this.updateItemToTargetArray(aPlanet, team.Planets)
|
||||
}
|
||||
}
|
||||
|
||||
// Update currentUser
|
||||
localStorage.setItem('currentUser', JSON.stringify(currentUser))
|
||||
UserStore.Actions.update(currentUser)
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -1,69 +0,0 @@
|
||||
/* global localStorage */
|
||||
|
||||
var Reflux = require('reflux')
|
||||
|
||||
var actions = Reflux.createActions([
|
||||
'update',
|
||||
'destroy',
|
||||
'addMember',
|
||||
'removeMember'
|
||||
])
|
||||
|
||||
module.exports = Reflux.createStore({
|
||||
listenables: [actions],
|
||||
onUpdate: function (user) {
|
||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
||||
|
||||
if (currentUser.id === user.id) {
|
||||
localStorage.setItem('currentUser', JSON.stringify(user))
|
||||
}
|
||||
|
||||
if (user.userType === 'team') {
|
||||
var isMyTeam = user.Members.some(function (member) {
|
||||
if (currentUser.id === member.id) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (isMyTeam) {
|
||||
var isNew = !currentUser.Teams.some(function (team, index) {
|
||||
if (user.id === team.id) {
|
||||
currentUser.Teams.splice(index, 1, user)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (isNew) {
|
||||
currentUser.Teams.push(user)
|
||||
}
|
||||
localStorage.setItem('currentUser', JSON.stringify(currentUser))
|
||||
}
|
||||
}
|
||||
|
||||
this.trigger({
|
||||
status: 'userUpdated',
|
||||
data: user
|
||||
})
|
||||
},
|
||||
onDestroy: function (user) {
|
||||
this.trigger({
|
||||
status: 'userDestroyed',
|
||||
data: user
|
||||
})
|
||||
},
|
||||
onAddMember: function (member) {
|
||||
this.trigger({
|
||||
status: 'memberAdded',
|
||||
data: member
|
||||
})
|
||||
},
|
||||
onRemoveMember: function (member) {
|
||||
this.trigger({
|
||||
status: 'memberRemoved',
|
||||
data: member
|
||||
})
|
||||
},
|
||||
Actions: actions
|
||||
})
|
||||
@@ -1,98 +0,0 @@
|
||||
<!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').getGlobal('version')
|
||||
document.title = 'Boost ' + ((version == null || version.length === 0) ? 'DEV version' : 'v' + version)
|
||||
require('../electron-stylus')(__dirname + '/../styles/main/index.styl', 'mainCss')
|
||||
require('node-jsx').install({ harmony: true, extension: '.jsx' })
|
||||
require('./index.jsx')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,56 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>CodeXen</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
|
||||
<link rel="stylesheet" href="../vendor/fontawesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
|
||||
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
|
||||
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
|
||||
<link rel="stylesheet" href="../../node_modules/highlight.js/styles/xcode.css">
|
||||
<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');
|
||||
}
|
||||
<title>Boostnote</title>
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('../../resources/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('../../resources/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
</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>
|
||||
|
||||
#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/app.png">
|
||||
<div class='message'>Loading...</div>
|
||||
</div>
|
||||
|
||||
<div id="content"></div>
|
||||
<script src="http://localhost:8090/webpack-dev-server.js"></script>
|
||||
<script type="text/javascript" src="http://localhost:8090/assets/main.js"></script>
|
||||
<script type="text/javascript" src="http://localhost:8090/assets/main-style.js"></script>
|
||||
|
||||
<script src="../../submodules/ace/src-min/ace.js"></script>
|
||||
<script type='text/javascript'>
|
||||
const electron = require('electron')
|
||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
||||
var version = electron.remote.app.getVersion()
|
||||
const _ = require('lodash')
|
||||
var scriptUrl = _.find(electron.remote.process.argv, a => a === '--hot')
|
||||
? 'http://localhost:8080/assets/main.js'
|
||||
: '../../compiled/main.js'
|
||||
var scriptEl = document.createElement('script')
|
||||
scriptEl.setAttribute("type","text/javascript")
|
||||
scriptEl.setAttribute("src", scriptUrl)
|
||||
document.getElementsByTagName("head")[0].appendChild(scriptEl)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
68
browser/main/index.js
Normal file
68
browser/main/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Provider } from 'react-redux'
|
||||
// import { updateUser } from 'boost/actions'
|
||||
import { Router, Route, IndexRoute } from 'react-router'
|
||||
import MainPage from './MainPage'
|
||||
import HomePage from './HomePage'
|
||||
// import auth from 'boost/auth'
|
||||
import store from 'boost/store'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
require('../styles/main/index.styl')
|
||||
import { openModal } from 'boost/modal'
|
||||
import Tutorial from 'boost/components/modal/Tutorial'
|
||||
import activityRecord from 'boost/activityRecord'
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
const path = require('path')
|
||||
|
||||
activityRecord.init()
|
||||
window.addEventListener('online', function () {
|
||||
ipc.send('check-update', 'check-update')
|
||||
})
|
||||
|
||||
function notify (title, options) {
|
||||
if (process.platform === 'win32') {
|
||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
||||
options.silent = false
|
||||
}
|
||||
console.log(options)
|
||||
return new window.Notification(title, options)
|
||||
}
|
||||
|
||||
ipc.on('notify', function (e, payload) {
|
||||
notify(payload.title, {
|
||||
body: payload.body
|
||||
})
|
||||
})
|
||||
|
||||
ipc.on('copy-finder', function () {
|
||||
activityRecord.emit('FINDER_COPY')
|
||||
})
|
||||
ipc.on('open-finder', function () {
|
||||
activityRecord.emit('FINDER_OPEN')
|
||||
})
|
||||
|
||||
let routes = (
|
||||
<Route path='/' component={MainPage}>
|
||||
<IndexRoute name='home' component={HomePage}/>
|
||||
</Route>
|
||||
)
|
||||
|
||||
let el = document.getElementById('content')
|
||||
ReactDOM.render((
|
||||
<div>
|
||||
<Provider store={store}>
|
||||
<Router>{routes}</Router>
|
||||
</Provider>
|
||||
</div>
|
||||
), el, function () {
|
||||
let loadingCover = document.getElementById('loadingCover')
|
||||
loadingCover.parentNode.removeChild(loadingCover)
|
||||
let status = JSON.parse(localStorage.getItem('status'))
|
||||
if (status == null) status = {}
|
||||
if (!status.introWatched) {
|
||||
openModal(Tutorial)
|
||||
status.introWatched = true
|
||||
localStorage.setItem('status', JSON.stringify(status))
|
||||
}
|
||||
})
|
||||
@@ -1,46 +0,0 @@
|
||||
var React = require('react/addons')
|
||||
|
||||
var ReactRouter = require('react-router')
|
||||
var Route = ReactRouter.Route
|
||||
var DefaultRoute = ReactRouter.DefaultRoute
|
||||
|
||||
var MainContainer = require('./Containers/MainContainer')
|
||||
|
||||
var LoginContainer = require('./Containers/LoginContainer')
|
||||
var SignupContainer = require('./Containers/SignupContainer')
|
||||
|
||||
var HomeContainer = require('./Containers/HomeContainer')
|
||||
var UserContainer = require('./Containers/UserContainer')
|
||||
|
||||
var PlanetContainer = require('./Containers/PlanetContainer')
|
||||
|
||||
var routes = (
|
||||
<Route path='/' handler={MainContainer}>
|
||||
<DefaultRoute name='root'/>
|
||||
|
||||
<Route name='login' path='login' handler={LoginContainer}/>
|
||||
<Route name='signup' path='signup' handler={SignupContainer}/>
|
||||
|
||||
<Route name='home' path='home' handler={HomeContainer}>
|
||||
<DefaultRoute name='homeEmpty'/>
|
||||
<Route name='user' path=':userName' handler={UserContainer}>
|
||||
<DefaultRoute name='userHome'/>
|
||||
<Route name='planet' path=':planetName' handler={PlanetContainer}>
|
||||
<DefaultRoute name='planetHome'/>
|
||||
<Route name='codes' path='codes/:localId'/>
|
||||
<Route name='notes' path='notes/:localId'/>
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
)
|
||||
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.
|
Before Width: | Height: | Size: 5.5 KiB |
@@ -1,2 +0,0 @@
|
||||
require('../styles/main/index.styl')
|
||||
require('react-select/dist/default.css')
|
||||
@@ -4,22 +4,31 @@
|
||||
global-reset()
|
||||
@import '../shared/*'
|
||||
|
||||
iptBgColor = #E6E6E6
|
||||
iptFocusBorderColor = #369DCD
|
||||
|
||||
DEFAULT_FONTS = 'Lato', 'MS Gothic', 'Malgun Gothic', 'Sans-serif'
|
||||
|
||||
body
|
||||
font-family "Lato"
|
||||
font-family DEFAULT_FONTS
|
||||
color textColor
|
||||
font-size fontSize
|
||||
width 100%
|
||||
height 100%
|
||||
overflow hidden
|
||||
button, input
|
||||
font-family "Lato"
|
||||
|
||||
.Finder
|
||||
absolute top bottom left right
|
||||
.FinderInput
|
||||
position absolute
|
||||
top 11px
|
||||
left 11px
|
||||
right 11px
|
||||
padding 11px
|
||||
margin 0 auto
|
||||
height 44px
|
||||
height 55px
|
||||
box-sizing border-box
|
||||
border-bottom solid 1px borderColor
|
||||
background-color iptBgColor
|
||||
z-index 200
|
||||
input
|
||||
display block
|
||||
width 100%
|
||||
@@ -29,9 +38,9 @@ body
|
||||
height 33px
|
||||
border-radius 5px
|
||||
box-sizing border-box
|
||||
border-radius 16.5px
|
||||
border-radius 5px
|
||||
&:focus, &.focus
|
||||
border-color brandBorderColor
|
||||
border-color iptFocusBorderColor
|
||||
outline none
|
||||
.FinderList
|
||||
absolute left bottom
|
||||
@@ -40,6 +49,7 @@ body
|
||||
box-sizing border-box
|
||||
width 250px
|
||||
overflow-y auto
|
||||
z-index 0
|
||||
&>ul>li
|
||||
.articleItem
|
||||
padding 10px
|
||||
@@ -60,25 +70,54 @@ body
|
||||
absolute right bottom
|
||||
top 55px
|
||||
left 250px
|
||||
box-shadow 0px 0px 10px 0 #CCC
|
||||
z-index 100
|
||||
.header
|
||||
absolute top left right
|
||||
height 44px
|
||||
height 55px
|
||||
box-sizing border-box
|
||||
padding 0 10px
|
||||
border-bottom solid 1px borderColor
|
||||
line-height 44px
|
||||
font-size 1.3em
|
||||
line-height 55px
|
||||
font-size 18px
|
||||
white-space nowrap
|
||||
text-overflow ellipsis
|
||||
overflow-x hidden
|
||||
clearfix()
|
||||
.left
|
||||
float left
|
||||
.right
|
||||
float right
|
||||
button
|
||||
border-radius 16.5px
|
||||
cursor pointer
|
||||
height 33px
|
||||
width 33px
|
||||
border none
|
||||
margin-right 5px
|
||||
font-size 18px
|
||||
color inactiveTextColor
|
||||
background-color transparent
|
||||
padding 0
|
||||
.tooltip
|
||||
tooltip()
|
||||
&.clipboardBtn .tooltip
|
||||
margin-left -160px
|
||||
margin-top 25px
|
||||
&:hover
|
||||
color textColor
|
||||
.tooltip
|
||||
opacity 1
|
||||
.content
|
||||
.ace_editor, .marked
|
||||
position absolute
|
||||
top 49px
|
||||
left 5px
|
||||
right 5px
|
||||
bottom 5px
|
||||
box-sizing border-box
|
||||
.marked
|
||||
position absolute
|
||||
top 55px
|
||||
padding 10px
|
||||
bottom 0
|
||||
left 0
|
||||
right 0
|
||||
box-sizing border-box
|
||||
overflow-y auto
|
||||
.MarkdownPreview
|
||||
marked()
|
||||
overflow-y auto
|
||||
.CodeEditor
|
||||
absolute top bottom left right
|
||||
|
||||
423
browser/styles/main/HomeContainer/components/ArticleDetail.styl
Normal file
423
browser/styles/main/HomeContainer/components/ArticleDetail.styl
Normal file
@@ -0,0 +1,423 @@
|
||||
noTagsColor = #999
|
||||
iptFocusBorderColor = #369DCD
|
||||
|
||||
.ArticleDetail
|
||||
absolute right bottom
|
||||
top 60px
|
||||
left 450px
|
||||
padding 10px
|
||||
background-color #E6E6E6
|
||||
border-top 1px solid borderColor
|
||||
border-left 1px solid borderColor
|
||||
*
|
||||
-webkit-user-select all
|
||||
.deleteConfirm
|
||||
width 100%
|
||||
height 70px
|
||||
.right
|
||||
float right
|
||||
button
|
||||
cursor pointer
|
||||
height 33px
|
||||
padding 0 10px
|
||||
margin-left 5px
|
||||
font-size 14px
|
||||
color inactiveTextColor
|
||||
background-color darken(white, 5%)
|
||||
border solid 1px borderColor
|
||||
border-radius 5px
|
||||
&:hover
|
||||
background-color white
|
||||
&.primary
|
||||
border none
|
||||
background-color brandColor
|
||||
color white
|
||||
&:hover
|
||||
color white
|
||||
background-color lighten(brandColor, 10%)
|
||||
|
||||
.detailInfo
|
||||
height 70px
|
||||
width 100%
|
||||
font-size 12px
|
||||
position relative
|
||||
.left
|
||||
absolute top left bottom
|
||||
right 120px
|
||||
.folderName
|
||||
display inline-block
|
||||
max-width 100px
|
||||
overflow ellipsis
|
||||
height 10px
|
||||
.right
|
||||
absolute top right
|
||||
.detailBody
|
||||
absolute left right bottom
|
||||
top 70px
|
||||
overflow-x hidden
|
||||
overflow-y auto
|
||||
.detailPanel
|
||||
absolute top
|
||||
left 10px
|
||||
right 10px
|
||||
bottom 10px
|
||||
background-color white
|
||||
border-radius 5px
|
||||
border solid 1px borderColor
|
||||
&>.header
|
||||
absolute top left right
|
||||
height 60px
|
||||
.MarkdownPreview
|
||||
absolute left right bottom
|
||||
top 60px
|
||||
marked()
|
||||
box-sizing border-box
|
||||
padding 5px 15px
|
||||
border-top solid 1px borderColor
|
||||
overflow-y auto
|
||||
.CodeEditor
|
||||
absolute left right bottom
|
||||
top 60px
|
||||
border-top solid 1px borderColor
|
||||
min-height 300px
|
||||
border-bottom-left-radius 5px
|
||||
border-bottom-right-radius 5px
|
||||
&.edit
|
||||
.detailInfo
|
||||
.left
|
||||
&>.tutorial
|
||||
position fixed
|
||||
z-index 35
|
||||
font-style italic
|
||||
.folder
|
||||
border none
|
||||
width 150px
|
||||
height 27px
|
||||
outline none
|
||||
background-color darken(white, 5%)
|
||||
&:hover
|
||||
background-color white
|
||||
.TagSelect
|
||||
.tags
|
||||
white-space nowrap
|
||||
overflow-x auto
|
||||
position relative
|
||||
max-width 350px
|
||||
margin-top 5px
|
||||
noSelect()
|
||||
z-index 30
|
||||
background-color #E6E6E6
|
||||
.tagItem
|
||||
background-color brandColor
|
||||
border-radius 2px
|
||||
color white
|
||||
margin 0 2px
|
||||
padding 0
|
||||
border 1px solid darken(brandColor, 10%)
|
||||
button.tagRemoveBtn
|
||||
color white
|
||||
border-radius 2px
|
||||
border none
|
||||
background-color transparent
|
||||
padding 4px 2px
|
||||
border-right 1px solid #E6E6E6
|
||||
font-size 8px
|
||||
line-height 12px
|
||||
transition 0.1s
|
||||
&:hover
|
||||
background-color lighten(brandColor, 10%)
|
||||
.tagLabel
|
||||
padding 4px 4px
|
||||
font-size 12px
|
||||
line-height 12px
|
||||
input.tagInput
|
||||
background-color transparent
|
||||
outline none
|
||||
margin 0 2px
|
||||
border-radius 2px
|
||||
border none
|
||||
transition 0.1s
|
||||
height 18px
|
||||
.suggestTags
|
||||
position fixed
|
||||
width 150px
|
||||
max-height 150px
|
||||
background-color white
|
||||
z-index 5
|
||||
border 1px solid borderColor
|
||||
border-radius 5px
|
||||
button
|
||||
width 100%
|
||||
display block
|
||||
padding 0 15px
|
||||
height 33px
|
||||
line-height 33px
|
||||
background-color transparent
|
||||
border none
|
||||
text-align left
|
||||
font-size 14px
|
||||
&:hover
|
||||
background-color darken(white, 10%)
|
||||
.right
|
||||
z-index 30
|
||||
button
|
||||
cursor pointer
|
||||
height 33px
|
||||
width 55px
|
||||
margin-left 5px
|
||||
font-size 14px
|
||||
color inactiveTextColor
|
||||
background-color darken(white, 5%)
|
||||
border solid 1px borderColor
|
||||
border-radius 5px
|
||||
&>.tooltip
|
||||
tooltip()
|
||||
top 105px
|
||||
&.preview
|
||||
width inherit
|
||||
.tooltip
|
||||
margin-left -55px
|
||||
&:hover
|
||||
background-color white
|
||||
.tooltip
|
||||
opacity 1
|
||||
&.cancelBtn
|
||||
.tooltip
|
||||
margin-left -25px
|
||||
&.saveBtn
|
||||
border none
|
||||
background-color brandColor
|
||||
color white
|
||||
.tooltip
|
||||
margin-left -45px
|
||||
&:hover
|
||||
color white
|
||||
background-color lighten(brandColor, 10%)
|
||||
.detailBody
|
||||
.detailPanel
|
||||
&>.header
|
||||
&>.tutorial
|
||||
fixed right
|
||||
z-index 35
|
||||
font-style italic
|
||||
.mode
|
||||
position relative
|
||||
z-index 30
|
||||
absolute top bottom right
|
||||
display block
|
||||
height 33px
|
||||
margin-top 12px
|
||||
width 150px
|
||||
margin-right 15px
|
||||
border-radius 5px
|
||||
border solid 1px borderColor
|
||||
transition 0.1s
|
||||
&.idle
|
||||
background-color darken(white, 5%)
|
||||
cursor pointer
|
||||
&:hover
|
||||
background-color white
|
||||
.ModeIcon
|
||||
float left
|
||||
width 25px
|
||||
line-height 33px
|
||||
text-align center
|
||||
.modeLabel
|
||||
line-height 30px
|
||||
&.edit
|
||||
background-color white
|
||||
input
|
||||
width 150px
|
||||
line-height 30px
|
||||
padding 0 10px
|
||||
border none
|
||||
outline none
|
||||
background-color transparent
|
||||
font-size 14px
|
||||
.modeOptions
|
||||
position fixed
|
||||
width 150px
|
||||
z-index 10
|
||||
margin-top 5px
|
||||
border 1px solid borderColor
|
||||
border-radius 5px
|
||||
background-color white
|
||||
max-height 250px
|
||||
overflow-y auto
|
||||
.option
|
||||
height 33px
|
||||
line-height 33px
|
||||
cursor pointer
|
||||
&.active, &:hover.active
|
||||
background-color brandColor
|
||||
color white
|
||||
.ModeIcon
|
||||
width 30px
|
||||
text-align center
|
||||
display inline-block
|
||||
&:hover
|
||||
background-color darken(white, 10%)
|
||||
.title
|
||||
absolute left top bottom
|
||||
right 150px
|
||||
padding 0 15px
|
||||
input
|
||||
width 100%
|
||||
border none
|
||||
background-color transparent
|
||||
line-height 60px
|
||||
font-size 24px
|
||||
outline none
|
||||
&.idle
|
||||
.detailInfo
|
||||
&>.tutorial
|
||||
fixed top right
|
||||
z-index 35
|
||||
font-style italic
|
||||
.left
|
||||
right 99px
|
||||
.info
|
||||
padding 5px
|
||||
overflow ellipsis
|
||||
.tags
|
||||
padding 10px 10px 5px
|
||||
color articleItemColor
|
||||
a
|
||||
background-color brandColor
|
||||
color white
|
||||
border-radius 2px
|
||||
padding 1.5px 5px
|
||||
margin 2px
|
||||
font-size 10px
|
||||
opacity 0.8
|
||||
cursor pointer
|
||||
&:hover
|
||||
opacity 1
|
||||
span.noTags
|
||||
color noTagsColor
|
||||
.right
|
||||
z-index 30
|
||||
div.share-dropdown
|
||||
position absolute
|
||||
right 5px
|
||||
top 30px
|
||||
background-color transparentify(invBackgroundColor, 80%)
|
||||
padding 5px 0
|
||||
width 200px
|
||||
&.hide
|
||||
display none
|
||||
&>button
|
||||
width 200px
|
||||
text-align left
|
||||
display block
|
||||
height 33px
|
||||
background-color transparent
|
||||
color white
|
||||
font-size 14px
|
||||
padding 0 10px
|
||||
border none
|
||||
&:hover
|
||||
background-color transparentify(lighten(invBackgroundColor, 30%), 80%)
|
||||
&>.ShareButton-url
|
||||
clearfix()
|
||||
input.ShareButton-url-input
|
||||
width 155px
|
||||
margin 0 0 0 5px
|
||||
height 25px
|
||||
outline none
|
||||
border none
|
||||
border-top-left-radius 5px
|
||||
border-bottom-left-radius 5px
|
||||
float left
|
||||
padding 5px
|
||||
button.ShareButton-url-button
|
||||
width 35px
|
||||
height 25px
|
||||
border none
|
||||
margin 0 5px 0 0
|
||||
outline none
|
||||
border-top-right-radius 5px
|
||||
border-bottom-right-radius 5px
|
||||
background-color darken(white, 5%)
|
||||
color inactiveTextColor
|
||||
float right
|
||||
div.ShareButton-url-button-tooltip
|
||||
tooltip()
|
||||
right 10px
|
||||
&:hover
|
||||
color textColor
|
||||
div.ShareButton-url-button-tooltip
|
||||
opacity 1
|
||||
div.ShareButton-url-alert
|
||||
float left
|
||||
height 25px
|
||||
line-height 25px
|
||||
padding 0 15px
|
||||
color white
|
||||
|
||||
.ShareButton
|
||||
display inline-block
|
||||
button.ShareButton-open-button
|
||||
border-radius 16.5px
|
||||
cursor pointer
|
||||
height 33px
|
||||
width 33px
|
||||
border none
|
||||
margin-right 5px
|
||||
font-size 18px
|
||||
color inactiveTextColor
|
||||
background-color darken(white, 5%)
|
||||
padding 0
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-top 25px
|
||||
margin-left -40px
|
||||
&:hover
|
||||
color textColor
|
||||
.tooltip
|
||||
opacity 1
|
||||
|
||||
&>button
|
||||
border-radius 16.5px
|
||||
cursor pointer
|
||||
height 33px
|
||||
width 33px
|
||||
border none
|
||||
margin-right 5px
|
||||
font-size 18px
|
||||
color inactiveTextColor
|
||||
background-color darken(white, 5%)
|
||||
padding 0
|
||||
.tooltip
|
||||
tooltip()
|
||||
&.editBtn .tooltip
|
||||
margin-top 25px
|
||||
margin-left -45px
|
||||
&.deleteBtn .tooltip
|
||||
margin-top 25px
|
||||
margin-left -73px
|
||||
&:hover
|
||||
color textColor
|
||||
.tooltip
|
||||
opacity 1
|
||||
.detailBody
|
||||
.detailPanel
|
||||
&>.header
|
||||
.mode
|
||||
display block
|
||||
line-height 60px
|
||||
width 45px
|
||||
height 60px
|
||||
font-size 18px
|
||||
text-align center
|
||||
.title
|
||||
absolute top bottom
|
||||
left 45px
|
||||
right 15px
|
||||
font-size 24px
|
||||
line-height 60px
|
||||
white-space nowrap
|
||||
overflow-x auto
|
||||
overflow-y hidden
|
||||
small
|
||||
color #AAA
|
||||
@@ -0,0 +1,76 @@
|
||||
articleItemHoverBgColor = darken(white, 5%)
|
||||
articleItemColor = #777
|
||||
|
||||
.ArticleList
|
||||
absolute bottom
|
||||
top 60px
|
||||
left 200px
|
||||
width 250px
|
||||
border-top 1px solid borderColor
|
||||
border-right 1px solid borderColor
|
||||
overflow-y auto
|
||||
noSelect()
|
||||
&>div
|
||||
.articleItem
|
||||
border solid 2px transparent
|
||||
position relative
|
||||
height 88px
|
||||
width 100%
|
||||
cursor pointer
|
||||
transition 0.1s
|
||||
background-color white
|
||||
padding 0 10px
|
||||
font-size 12px
|
||||
.top
|
||||
clearfix()
|
||||
line-height 20px
|
||||
padding 5px 0
|
||||
color articleItemColor
|
||||
.folderName
|
||||
overflow ellipsis
|
||||
display inline-block
|
||||
width 120px
|
||||
.updatedAt
|
||||
float right
|
||||
line-height 20px
|
||||
.middle
|
||||
padding 3px 0 7px
|
||||
font-size 16px
|
||||
position relative
|
||||
height 26px
|
||||
.mode
|
||||
position absolute
|
||||
left 0
|
||||
font-size 12px
|
||||
line-height 16px
|
||||
.title
|
||||
position absolute
|
||||
left 19px
|
||||
right 0
|
||||
overflow ellipsis
|
||||
small
|
||||
color #AAA
|
||||
.bottom
|
||||
padding 5px 0
|
||||
overflow-x auto
|
||||
white-space nowrap
|
||||
.tags
|
||||
color articleItemColor
|
||||
a
|
||||
background-color brandColor
|
||||
color white
|
||||
border-radius 2px
|
||||
padding 1.5px 5px
|
||||
margin 2px
|
||||
font-size 10px
|
||||
opacity 0.8
|
||||
&:hover
|
||||
opacity 1
|
||||
&:hover, &.hover
|
||||
background-color articleItemHoverBgColor
|
||||
&:active, &.active
|
||||
background-color white
|
||||
&:active, &.active
|
||||
border-color brandBorderColor
|
||||
.divider
|
||||
border-bottom solid 1px borderColor
|
||||
@@ -0,0 +1,170 @@
|
||||
articleNavBgColor = #353535
|
||||
articleCount = #999
|
||||
|
||||
.ArticleNavigator
|
||||
background-color articleNavBgColor
|
||||
absolute top bottom left
|
||||
width 200px
|
||||
border-right 1px solid borderColor
|
||||
color white
|
||||
.userInfo
|
||||
height 60px
|
||||
display block
|
||||
border-bottom 1px solid borderColor
|
||||
.userProfileName
|
||||
color brandColor
|
||||
font-size 28px
|
||||
padding 6px 37px 0 10px
|
||||
white-space nowrap
|
||||
text-overflow ellipsis
|
||||
overflow hidden
|
||||
.userName
|
||||
color white
|
||||
padding-left 20px
|
||||
margin-top 3px
|
||||
.tutorial
|
||||
position fixed
|
||||
z-index 35
|
||||
top 0
|
||||
left 0
|
||||
pointer-event none
|
||||
font-style italic
|
||||
transition 0.1s
|
||||
&.hide
|
||||
opacity 0
|
||||
.settingBtn
|
||||
width 22px
|
||||
height 22px
|
||||
line-height 22px
|
||||
border-radius 11px
|
||||
position absolute
|
||||
top 19px
|
||||
right 14px
|
||||
color white
|
||||
padding 0
|
||||
background-color transparent
|
||||
border 1px solid white
|
||||
z-index 31
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-top -5px
|
||||
margin-left 10px
|
||||
&:hover
|
||||
.tooltip
|
||||
opacity 1
|
||||
&:active
|
||||
background-color brandColor
|
||||
border-color brandColor
|
||||
.controlSection
|
||||
height 88px
|
||||
padding 22px 15px
|
||||
margin-bottom 44px
|
||||
.tutorial
|
||||
fixed top left
|
||||
z-index 35
|
||||
pointer-event none
|
||||
font-style italic
|
||||
transition 0.1s
|
||||
&.hide
|
||||
opacity 0
|
||||
.newPostBtn
|
||||
position relative
|
||||
border none
|
||||
background-color brandColor
|
||||
color white
|
||||
height 44px
|
||||
width 170px
|
||||
border-radius 5px
|
||||
font-size 20px
|
||||
transition 0.1s
|
||||
z-index 30
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-left 48px
|
||||
margin-top -3px
|
||||
&:hover
|
||||
background-color lighten(brandColor, 7%)
|
||||
.tooltip
|
||||
opacity 1
|
||||
.folders, .members
|
||||
.header
|
||||
border-bottom 1px solid borderColor
|
||||
padding-bottom 5px
|
||||
margin-bottom 10px
|
||||
clearfix()
|
||||
position relative
|
||||
z-index 30
|
||||
.title
|
||||
float left
|
||||
padding-left 10px
|
||||
font-size 18px
|
||||
line-height 22px
|
||||
.addBtn
|
||||
float right
|
||||
margin-right 15px
|
||||
width 22px
|
||||
height 22px
|
||||
font-size 10px
|
||||
padding 0
|
||||
line-height 22px
|
||||
border 1px solid white
|
||||
border-radius 11px
|
||||
background-color transparent
|
||||
color white
|
||||
padding 0
|
||||
font-weight bold
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-top -6px
|
||||
margin-left 11px
|
||||
&:hover
|
||||
.tooltip
|
||||
opacity 1
|
||||
&:active
|
||||
background-color brandColor
|
||||
border-color brandColor
|
||||
.folders
|
||||
absolute bottom
|
||||
top 200px
|
||||
width 100%
|
||||
.header
|
||||
.tutorial
|
||||
position fixed
|
||||
z-index 35px
|
||||
top 200px
|
||||
font-style italic
|
||||
.folderList
|
||||
absolute bottom
|
||||
top 38px
|
||||
overflow-y auto
|
||||
.folderList button
|
||||
height 33px
|
||||
width 199px
|
||||
border none
|
||||
text-align left
|
||||
font-size 14px
|
||||
background-color transparent
|
||||
color white
|
||||
padding-left 15px
|
||||
overflow ellipsis
|
||||
&:hover
|
||||
background-color transparentify(white, 5%)
|
||||
&.active, &:active
|
||||
background-color transparentify(lighten(brandColor, 25%), 70%)
|
||||
.articleCount
|
||||
color articleCount
|
||||
font-size 12px
|
||||
.members
|
||||
.memberList>div
|
||||
height 33px
|
||||
width 200px
|
||||
margin-bottom 5px
|
||||
padding-left 15px
|
||||
.memberImage
|
||||
float left
|
||||
margin-top 5.5px
|
||||
border-radius 11px
|
||||
.memberProfileName
|
||||
float left
|
||||
line-height 33px
|
||||
margin-left 7px
|
||||
179
browser/styles/main/HomeContainer/components/ArticleTopBar.styl
Normal file
179
browser/styles/main/HomeContainer/components/ArticleTopBar.styl
Normal file
@@ -0,0 +1,179 @@
|
||||
bgColor = #E6E6E6
|
||||
inputBgColor = white
|
||||
iptFocusBorderColor = #369DCD
|
||||
|
||||
topBarBtnColor = #B3B3B3
|
||||
topBarBtnBgColor = #B3B3B3
|
||||
topBarBtnBgActiveColor = #3A3A3A
|
||||
|
||||
infoBtnColor = bgColor
|
||||
infoBtnBgColor = #B3B3B3
|
||||
infoBtnActiveBgColor = #3A3A3A
|
||||
|
||||
.ArticleTopBar
|
||||
absolute top right
|
||||
left 200px
|
||||
height 60px
|
||||
background-color bgColor
|
||||
&>.tutorial
|
||||
.clickJammer
|
||||
fixed top left bottom right
|
||||
z-index 40
|
||||
background transparent
|
||||
.global
|
||||
fixed bottom right
|
||||
height 100px
|
||||
z-index 35
|
||||
font-style italic
|
||||
.finder
|
||||
fixed bottom right
|
||||
height 250px
|
||||
left 50%
|
||||
margin-left -250px
|
||||
z-index 35
|
||||
font-style italic
|
||||
.back
|
||||
fixed top left bottom right
|
||||
z-index 20
|
||||
background-color transparentify(black, 80%)
|
||||
&>.left
|
||||
float left
|
||||
&>.tutorial
|
||||
fixed top
|
||||
left 200px
|
||||
z-index 36
|
||||
font-style italic
|
||||
&>.search
|
||||
position relative
|
||||
float left
|
||||
height 33px
|
||||
margin-top 13.5px
|
||||
margin-left 15px
|
||||
width 350px
|
||||
padding 5px 15px
|
||||
transition 0.1s
|
||||
font-size 16px
|
||||
border 1px solid transparent
|
||||
z-index 30
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-left -24px
|
||||
margin-top 35px
|
||||
opacity 1
|
||||
&.hide
|
||||
opacity 0
|
||||
ul
|
||||
li:last-child
|
||||
line-height 10px
|
||||
margin-bottom 3px
|
||||
small
|
||||
font-size 10px
|
||||
margin-left 15px
|
||||
input
|
||||
absolute top left
|
||||
width 350px
|
||||
border-radius 16.5px
|
||||
background-color inputBgColor
|
||||
border 1px solid transparent
|
||||
padding-left 35px
|
||||
outline none
|
||||
font-size 14px
|
||||
height 33px
|
||||
line-height 33px
|
||||
z-index 0
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
i.fa.fa-search
|
||||
position absolute
|
||||
display block
|
||||
top 0
|
||||
left 10px
|
||||
line-height 33px
|
||||
z-index 1
|
||||
pointer-events none
|
||||
.searchClearBtn
|
||||
position absolute
|
||||
top 6px
|
||||
right 10px
|
||||
width 20px
|
||||
height 20px
|
||||
border-radius 10px
|
||||
border none
|
||||
background-color transparent
|
||||
color topBarBtnColor
|
||||
transition 0.1s
|
||||
line-height 20px
|
||||
text-align center
|
||||
padding 0
|
||||
&:hover
|
||||
color white
|
||||
background-color topBarBtnBgColor
|
||||
&>.refreshBtn
|
||||
float left
|
||||
width 33px
|
||||
height 33px
|
||||
margin-top 13.5px
|
||||
margin-left 15px
|
||||
border none
|
||||
color refreshBtColor
|
||||
background transparent
|
||||
font-size 18px
|
||||
line-height 18px
|
||||
transition 0.1s
|
||||
&:hover
|
||||
color refreshBtnActiveColor
|
||||
&>.right
|
||||
float right
|
||||
&>button
|
||||
display block
|
||||
position absolute
|
||||
right 74px
|
||||
top 20px
|
||||
width 20px
|
||||
height 20px
|
||||
font-size 14px
|
||||
line-height 14px
|
||||
background-color infoBtnBgColor
|
||||
color bgColor
|
||||
border-radius 11px
|
||||
border none
|
||||
transition 0.1s
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-left -50px
|
||||
margin-top 29px
|
||||
&:hover
|
||||
background-color infoBtnActiveBgColor
|
||||
.tooltip
|
||||
opacity 1
|
||||
|
||||
&>.linksBtn
|
||||
display block
|
||||
position absolute
|
||||
top 8px
|
||||
right 15px
|
||||
opacity 0.7
|
||||
&:hover
|
||||
opacity 1
|
||||
.tooltip
|
||||
opacity 1
|
||||
&>.links-dropdown
|
||||
position fixed
|
||||
z-index 50
|
||||
right 10px
|
||||
top 40px
|
||||
background-color transparentify(invBackgroundColor, 80%)
|
||||
padding 5px 0
|
||||
.links-item
|
||||
padding 0 10px
|
||||
height 33px
|
||||
width 100%
|
||||
display block
|
||||
line-height 33px
|
||||
text-decoration none
|
||||
color white
|
||||
&:hover
|
||||
background-color transparentify(lighten(invBackgroundColor, 30%), 80%)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
userNavigatorBgColor = #1B1C1C
|
||||
userNavigatorColor = #DDD
|
||||
userAnchorColor = #979797
|
||||
userAnchorBgColor = #BEBEBE
|
||||
userAnchorActiveColor = textColor
|
||||
userAnchorActiveBgColor = white
|
||||
|
||||
.UserNavigator
|
||||
noSelect()
|
||||
background-color userNavigatorBgColor
|
||||
absolute left top bottom
|
||||
width 60px
|
||||
text-align center
|
||||
box-sizing border-box
|
||||
ul.userList
|
||||
position absolute
|
||||
top 25px
|
||||
left 0
|
||||
right 0
|
||||
bottom 70px
|
||||
// overflow-y auto
|
||||
&>li
|
||||
a
|
||||
display block
|
||||
width 38px
|
||||
height 64px
|
||||
margin 0 auto 10px
|
||||
text-align center
|
||||
text-decoration none
|
||||
color userAnchorColor
|
||||
line-height 44px
|
||||
font-size 1.1em
|
||||
cursor pointer
|
||||
transition 0.1s
|
||||
|
||||
img.ProfileImage
|
||||
width 38px
|
||||
height 38px
|
||||
border-radius 22px
|
||||
opacity 0.7
|
||||
&:hover
|
||||
img.ProfileImage
|
||||
opacity 1
|
||||
.userTooltip
|
||||
opacity 1
|
||||
&.active
|
||||
img.ProfileImage
|
||||
opacity 1
|
||||
.userTooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
margin-top -52px
|
||||
margin-left 44px
|
||||
.keyLabel
|
||||
margin-top -25px
|
||||
font-size 0.8em
|
||||
color userNavigatorColor
|
||||
button.createTeamBtn
|
||||
display block
|
||||
margin 0 auto
|
||||
width 30px
|
||||
height 30px
|
||||
border-radius 15px
|
||||
border 2px solid darken(white, 5%)
|
||||
color darken(white, 5%)
|
||||
text-align center
|
||||
background-image none
|
||||
background-color transparent
|
||||
box-sizing border-box
|
||||
absolute left right
|
||||
bottom 15px
|
||||
font-size 22px
|
||||
line-height 22px
|
||||
transition 0.1s
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-top -26px
|
||||
margin-left 30px
|
||||
&:hover, &.hover, &:focus, &.focus
|
||||
color white
|
||||
border-color white
|
||||
.tooltip
|
||||
opacity 1
|
||||
&:active
|
||||
background-color brandColor
|
||||
border-color brandColor
|
||||
12
browser/styles/main/HomeContainer/index.styl
Normal file
12
browser/styles/main/HomeContainer/index.styl
Normal file
@@ -0,0 +1,12 @@
|
||||
@require './components/UserNavigator'
|
||||
@require './components/ArticleNavigator'
|
||||
@require './components/ArticleTopBar'
|
||||
@require './components/ArticleList'
|
||||
@require './components/ArticleDetail'
|
||||
|
||||
@require './lib/modal'
|
||||
@require './lib/CreateNewTeam'
|
||||
@require './lib/CreateNewFolder'
|
||||
@require './lib/Preferences'
|
||||
@require './lib/Tutorial'
|
||||
@require './lib/EditedAlert'
|
||||
91
browser/styles/main/HomeContainer/lib/CreateNewFolder.styl
Normal file
91
browser/styles/main/HomeContainer/lib/CreateNewFolder.styl
Normal file
@@ -0,0 +1,91 @@
|
||||
tabNavColor = #999999
|
||||
iptFocusBorderColor = #369DCD
|
||||
|
||||
.CreateNewFolder.modal
|
||||
width 600px
|
||||
height 450px
|
||||
.closeBtn
|
||||
position absolute
|
||||
top 15px
|
||||
right 15px
|
||||
width 33px
|
||||
height 33px
|
||||
font-size 18px
|
||||
line-height 33px
|
||||
padding 0
|
||||
text-align center
|
||||
background-color transparent
|
||||
border none
|
||||
color stripBtnColor
|
||||
&:hover
|
||||
color stripHoverBtnColor
|
||||
.title
|
||||
font-size 32px
|
||||
text-align center
|
||||
font-weight bold
|
||||
margin-top 25px
|
||||
.ipt
|
||||
display block
|
||||
width 330px
|
||||
font-size 14px
|
||||
height 44px
|
||||
line-height 44px
|
||||
padding 0 15px
|
||||
border-radius 5px
|
||||
border solid 1px borderColor
|
||||
outline none
|
||||
margin 75px auto 20px
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
.colorSelect
|
||||
text-align center
|
||||
.option
|
||||
cursor pointer
|
||||
font-size 22px
|
||||
height 48px
|
||||
width 48px
|
||||
margin 0 2px
|
||||
border 1px solid transparent
|
||||
border-radius 5px
|
||||
overflow hidden
|
||||
line-height 45px
|
||||
text-align center
|
||||
transition 0.1s
|
||||
display inline-block
|
||||
&:hover
|
||||
border-color borderColor
|
||||
font-size 28px
|
||||
&.active
|
||||
font-size 28px
|
||||
border-color iptFocusBorderColor
|
||||
.alert
|
||||
color infoTextColor
|
||||
background-color infoBackgroundColor
|
||||
font-size 14px
|
||||
padding 15px 15px
|
||||
width 330px
|
||||
border-radius 5px
|
||||
margin 15px auto 0
|
||||
&.error
|
||||
color errorTextColor
|
||||
background-color errorBackgroundColor
|
||||
.confirmBtn
|
||||
display block
|
||||
position absolute
|
||||
left 180px
|
||||
bottom 44px
|
||||
width 240px
|
||||
font-size 24px
|
||||
height 44px
|
||||
line-height 24px
|
||||
font-weight bold
|
||||
background-color brandColor
|
||||
color white
|
||||
border none
|
||||
border-radius 5px
|
||||
margin 0 auto
|
||||
transition 0.1s
|
||||
&:hover
|
||||
transform scale(1.1)
|
||||
&:disabled
|
||||
opacity 0.7
|
||||
199
browser/styles/main/HomeContainer/lib/CreateNewTeam.styl
Normal file
199
browser/styles/main/HomeContainer/lib/CreateNewTeam.styl
Normal file
@@ -0,0 +1,199 @@
|
||||
tabNavColor = #999999
|
||||
iptFocusBorderColor = #369DCD
|
||||
stripHoverBtnColor = #333
|
||||
stripBtnColor = lighten(stripHoverBtnColor, 35%)
|
||||
|
||||
.CreateNewTeam.modal
|
||||
width 600px
|
||||
height 450px
|
||||
.closeBtn
|
||||
position absolute
|
||||
top 15px
|
||||
right 15px
|
||||
width 33px
|
||||
height 33px
|
||||
font-size 18px
|
||||
line-height 33px
|
||||
padding 0
|
||||
text-align center
|
||||
background-color transparent
|
||||
border none
|
||||
color stripBtnColor
|
||||
&:hover
|
||||
color stripHoverBtnColor
|
||||
.title
|
||||
font-size 32px
|
||||
text-align center
|
||||
font-weight bold
|
||||
margin-top 25px
|
||||
.ipt
|
||||
display block
|
||||
width 330px
|
||||
font-size 14px
|
||||
height 44px
|
||||
line-height 44px
|
||||
padding 0 15px
|
||||
border-radius 5px
|
||||
border solid 1px borderColor
|
||||
outline none
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
.alert
|
||||
padding 0 15px
|
||||
height 44px
|
||||
line-height 44px
|
||||
width 300px
|
||||
margin 0 auto
|
||||
border-radius 5px
|
||||
color infoTextColor
|
||||
background-color infoBackgroundColor
|
||||
white-space nowrap
|
||||
overflow-x auto
|
||||
&.error
|
||||
color errorTextColor
|
||||
background-color errorBackgroundColor
|
||||
.confirmBtn
|
||||
display block
|
||||
position absolute
|
||||
left 180px
|
||||
bottom 44px
|
||||
width 240px
|
||||
font-size 24px
|
||||
height 44px
|
||||
line-height 24px
|
||||
font-weight bold
|
||||
background-color brandColor
|
||||
color white
|
||||
border none
|
||||
border-radius 5px
|
||||
margin 0 auto
|
||||
transition 0.1s
|
||||
&:hover
|
||||
transform scale(1.1)
|
||||
&:disabled
|
||||
opacity 0.7
|
||||
.tabNav
|
||||
absolute left right
|
||||
bottom 15px
|
||||
height 33px
|
||||
line-height 33px
|
||||
width 150px
|
||||
text-align center
|
||||
font-size 12px
|
||||
color tabNavColor
|
||||
margin 0 auto
|
||||
transition 0.1s
|
||||
i.active
|
||||
color brandColor
|
||||
.createTab
|
||||
.ipt
|
||||
margin 105px auto 15px
|
||||
.selectTab
|
||||
.memberForm
|
||||
display block
|
||||
margin 25px auto 15px
|
||||
width 330px
|
||||
clearfix()
|
||||
padding 0
|
||||
font-size 14px
|
||||
height 44px
|
||||
line-height 44px
|
||||
outline none
|
||||
.Select.memberName
|
||||
display block
|
||||
margin 0
|
||||
float left
|
||||
width 280px
|
||||
height 44px
|
||||
font-size 14px
|
||||
border none
|
||||
line-height 44px
|
||||
background-color transparent
|
||||
outline none
|
||||
&.is-focus
|
||||
.Select-control
|
||||
border-color iptFocusBorderColor
|
||||
.Select-control
|
||||
height 44px
|
||||
line-height 44px
|
||||
padding 0 0 0 15px
|
||||
border-radius 5px 0 0 5px
|
||||
border 1px solid borderColor
|
||||
border-right none
|
||||
.Select-placeholder
|
||||
padding 0 0 0 15px
|
||||
.Seleect-arrow
|
||||
top 21px
|
||||
.Select-clear
|
||||
padding 0 10px
|
||||
.Select-noresults, .Select-option
|
||||
line-height 44px
|
||||
padding 0 0 0 15px
|
||||
|
||||
&:focus, &.focus
|
||||
border-color iptFocusBorderColor
|
||||
button
|
||||
font-weight 400
|
||||
height 44px
|
||||
cursor pointer
|
||||
margin 0
|
||||
padding 0
|
||||
width 50px
|
||||
float right
|
||||
border none
|
||||
background-color brandColor
|
||||
border-top-right-radius 5px
|
||||
border-bottom-right-radius 5px
|
||||
color white
|
||||
font-size 14px
|
||||
.memberList
|
||||
width 480px
|
||||
margin 0 auto
|
||||
height 190px
|
||||
overflow scroll
|
||||
border-bottom 1px solid borderColor
|
||||
&>li
|
||||
border-bottom 1px solid borderColor
|
||||
height 44px
|
||||
padding 0 25px
|
||||
clearfix()
|
||||
&:nth-last-child(1)
|
||||
border-bottom-color transparent
|
||||
.userPhoto
|
||||
width 30px
|
||||
height 30px
|
||||
float left
|
||||
margin-top 7px
|
||||
margin-right 15px
|
||||
border-radius 15px
|
||||
.userInfo
|
||||
float left
|
||||
margin-top 7px
|
||||
.userName
|
||||
font-size 16px
|
||||
margin-bottom 2px
|
||||
.userEmail
|
||||
font-size 12px
|
||||
.userControl
|
||||
float right
|
||||
.userRole
|
||||
float left
|
||||
height 30px
|
||||
background-color transparent
|
||||
border 1px solid transparent
|
||||
margin-top 7px
|
||||
margin-right 35px
|
||||
outline none
|
||||
cursor pointer
|
||||
&:hover
|
||||
border-color borderColor
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
button
|
||||
border none
|
||||
height 30px
|
||||
margin-top 7px
|
||||
background-color transparent
|
||||
color stripBtnColor
|
||||
&:hover
|
||||
color stripHoverBtnColor
|
||||
28
browser/styles/main/HomeContainer/lib/EditedAlert.styl
Normal file
28
browser/styles/main/HomeContainer/lib/EditedAlert.styl
Normal file
@@ -0,0 +1,28 @@
|
||||
.EditedAlert.modal
|
||||
width 350px
|
||||
top 100px
|
||||
.title
|
||||
font-size 24px
|
||||
margin-bottom 15px
|
||||
.message
|
||||
font-size 14px
|
||||
margin-bottom 15px
|
||||
.control
|
||||
text-align right
|
||||
button
|
||||
border-radius 5px
|
||||
height 33px
|
||||
padding 0 15px
|
||||
font-size 14px
|
||||
background-color white
|
||||
border 1px solid borderColor
|
||||
border-radius 5px
|
||||
margin-left 5px
|
||||
&:hover
|
||||
background-color darken(white, 10%)
|
||||
&.primary
|
||||
border-color brandColor
|
||||
background-color brandColor
|
||||
color white
|
||||
&:hover
|
||||
background-color lighten(brandColor, 10%)
|
||||
633
browser/styles/main/HomeContainer/lib/Preferences.styl
Normal file
633
browser/styles/main/HomeContainer/lib/Preferences.styl
Normal file
@@ -0,0 +1,633 @@
|
||||
menuColor = #808080
|
||||
menuBgColor = #E6E6E6
|
||||
closeBtnBgColor = #1790C6
|
||||
iptFocusBorderColor = #369DCD
|
||||
|
||||
.Preferences.modal
|
||||
padding 0
|
||||
border-radius 5px
|
||||
overflow hidden
|
||||
width 720px
|
||||
height 450px
|
||||
&>.header
|
||||
absolute top left right
|
||||
height 50px
|
||||
border-bottom 1px solid borderColor
|
||||
background-color menuBgColor
|
||||
&>.title
|
||||
font-size 22px
|
||||
font-weight bold
|
||||
float left
|
||||
padding-left 30px
|
||||
line-height 50px
|
||||
&>.closeBtn
|
||||
float right
|
||||
font-size 14px
|
||||
background-color closeBtnBgColor
|
||||
color white
|
||||
padding 0 15px
|
||||
height 33px
|
||||
margin-top 9px
|
||||
margin-right 15px
|
||||
border none
|
||||
border-radius 5px
|
||||
&:hover
|
||||
background-color lighten(closeBtnBgColor, 10%)
|
||||
&>.nav
|
||||
absolute left bottom
|
||||
top 50px
|
||||
width 180px
|
||||
background-color menuBgColor
|
||||
border-right 1px solid borderColor
|
||||
&>button
|
||||
width 100%
|
||||
height 44px
|
||||
font-size 18px
|
||||
color menuColor
|
||||
border none
|
||||
background-color transparent
|
||||
transition 0.1s
|
||||
text-align left
|
||||
padding-left 15px
|
||||
&:hover
|
||||
background-color darken(menuBgColor, 10%)
|
||||
&.active, &:active
|
||||
background-color brandColor
|
||||
color white
|
||||
&>.content
|
||||
absolute right bottom
|
||||
top 50px
|
||||
left 180px
|
||||
overflow-y auto
|
||||
&>.section
|
||||
padding 10px
|
||||
border-bottom 1px solid borderColor
|
||||
overflow-y auto
|
||||
&:nth-last-child(1)
|
||||
border-bottom none
|
||||
&>.sectionTitle
|
||||
font-size 18px
|
||||
margin 10px 0 5px
|
||||
color brandColor
|
||||
&>.sectionInput
|
||||
height 33px
|
||||
margin-bottom 5px
|
||||
clearfix()
|
||||
label
|
||||
width 180px
|
||||
padding-left 15px
|
||||
float left
|
||||
line-height 33px
|
||||
input
|
||||
width 300px
|
||||
float left
|
||||
height 33px
|
||||
border-radius 5px
|
||||
border 1px solid borderColor
|
||||
padding 0 10px
|
||||
font-size 14px
|
||||
outline none
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
&>.sectionConfirm
|
||||
clearfix()
|
||||
padding 5px 15px
|
||||
button
|
||||
float right
|
||||
background-color brandColor
|
||||
color white
|
||||
border none
|
||||
border-radius 5px
|
||||
height 33px
|
||||
padding 0 15px
|
||||
font-size 14px
|
||||
&:hover
|
||||
background-color lighten(brandColor, 10%)
|
||||
.alert
|
||||
float right
|
||||
width 250px
|
||||
padding 10px 15px
|
||||
margin 0 10px 0
|
||||
.alert
|
||||
color infoTextColor
|
||||
background-color infoBackgroundColor
|
||||
font-size 14px
|
||||
padding 15px 15px
|
||||
width 330px
|
||||
border-radius 5px
|
||||
margin 10px auto
|
||||
&.error
|
||||
color errorTextColor
|
||||
background-color errorBackgroundColor
|
||||
&.ContactTab
|
||||
&.done
|
||||
.message
|
||||
margin-top 75px
|
||||
margin-bottom 15px
|
||||
text-align center
|
||||
font-size 22px
|
||||
.checkIcon
|
||||
margin-bottom 15px
|
||||
font-size 144px
|
||||
color brandColor
|
||||
text-align center
|
||||
.control
|
||||
text-align center
|
||||
button
|
||||
border solid 1px borderColor
|
||||
border-radius 5px
|
||||
background-color white
|
||||
padding 15px 15px
|
||||
font-size 14px
|
||||
&:hover
|
||||
background-color darken(white, 10%)
|
||||
&.form
|
||||
padding 10px
|
||||
.title
|
||||
font-size 18px
|
||||
color brandColor
|
||||
margin-top 10px
|
||||
margin-bottom 10px
|
||||
.description
|
||||
margin-bottom 15px
|
||||
.iptGroup
|
||||
margin-bottom 10px
|
||||
input, textarea
|
||||
border-radius 5px
|
||||
border 1px solid borderColor
|
||||
font-size 14px
|
||||
outline none
|
||||
padding 10px 15px
|
||||
width 100%
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
textarea
|
||||
resize vertical
|
||||
min-height 150px
|
||||
.formControl
|
||||
clearfix()
|
||||
.alert
|
||||
float right
|
||||
padding 10px 15px
|
||||
margin 0 5px 0
|
||||
font-size 14px
|
||||
line-height normal
|
||||
button
|
||||
padding 10px 15px
|
||||
background-color brandColor
|
||||
color white
|
||||
font-size 14px
|
||||
border-radius 5px
|
||||
border none
|
||||
float right
|
||||
&:hover
|
||||
background-color lighten(brandColor, 10%)
|
||||
|
||||
&.AppSettingTab
|
||||
.description
|
||||
marked()
|
||||
&.TeamSettingTab
|
||||
.header
|
||||
border-bottom 1px solid borderColor
|
||||
padding 10px
|
||||
font-size 18px
|
||||
color brandColor
|
||||
line-height 33px
|
||||
.teamSelect
|
||||
border 1px solid borderColor
|
||||
height 33px
|
||||
width 200px
|
||||
margin 0 10px
|
||||
outline none
|
||||
font-size 14px
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
.teamDeleteConfirm
|
||||
|
||||
label
|
||||
line-height 33px
|
||||
font-size 14px
|
||||
.teamDelete
|
||||
label
|
||||
line-height 33px
|
||||
font-size 18px
|
||||
color brandColor
|
||||
.teamDelete, .teamDeleteConfirm
|
||||
padding 15px 20px 15px 15px
|
||||
button
|
||||
background-color white
|
||||
height 33px
|
||||
font-size 14px
|
||||
padding 0 15px
|
||||
border 1px solid borderColor
|
||||
float right
|
||||
margin 0 5px
|
||||
border-radius 5px
|
||||
&:hover
|
||||
background-color darken(white, 10%)
|
||||
button.deleteBtn
|
||||
background-color brandColor
|
||||
border none
|
||||
color white
|
||||
&:hover
|
||||
background-color lighten(brandColor, 10%)
|
||||
&.MemberSettingTab
|
||||
&>.header
|
||||
border-bottom 1px solid borderColor
|
||||
padding 10px
|
||||
font-size 18px
|
||||
color brandColor
|
||||
line-height 33px
|
||||
.teamSelect
|
||||
border 1px solid borderColor
|
||||
height 33px
|
||||
width 200px
|
||||
margin 0 10px
|
||||
outline none
|
||||
font-size 14px
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
.membersTableSection
|
||||
.addMember
|
||||
clearfix()
|
||||
padding 10px
|
||||
.addMemberLabel
|
||||
font-size 14px
|
||||
line-height 33px
|
||||
float left
|
||||
.addMemberControl
|
||||
width 330px
|
||||
float left
|
||||
margin-left 25px
|
||||
.Select
|
||||
display block
|
||||
margin 0
|
||||
float left
|
||||
width 280px
|
||||
height 33px
|
||||
font-size 14px
|
||||
border none
|
||||
line-height 33px
|
||||
background-color transparent
|
||||
outline none
|
||||
&.is-focus
|
||||
.Select-control
|
||||
border-color iptFocusBorderColor
|
||||
.Select-control
|
||||
height 33px
|
||||
line-height 33px
|
||||
padding 0 0 0 15px
|
||||
border-radius 5px 0 0 5px
|
||||
border 1px solid borderColor
|
||||
border-right none
|
||||
.Select-placeholder
|
||||
padding 0 0 0 15px
|
||||
.Seleect-arrow
|
||||
top 21px
|
||||
.Select-clear
|
||||
padding 0 10px
|
||||
.Select-noresults, .Select-option
|
||||
line-height 33px
|
||||
padding 0 0 0 15px
|
||||
button
|
||||
font-weight 400
|
||||
height 33px
|
||||
cursor pointer
|
||||
margin 0
|
||||
padding 0
|
||||
width 50px
|
||||
float right
|
||||
border none
|
||||
background-color brandColor
|
||||
border-top-right-radius 5px
|
||||
border-bottom-right-radius 5px
|
||||
color white
|
||||
font-size 14px
|
||||
.memberList
|
||||
&>.header
|
||||
clearfix()
|
||||
&>.userName
|
||||
float left
|
||||
&>.role
|
||||
float left
|
||||
&>.control
|
||||
float right
|
||||
&>li
|
||||
&.edit
|
||||
.colDescription
|
||||
font-size 14px
|
||||
line-height 33px
|
||||
padding-left 15px
|
||||
float left
|
||||
strong
|
||||
font-size 16px
|
||||
color brandColor
|
||||
.colDeleteConfirm
|
||||
float right
|
||||
margin-right 15px
|
||||
button
|
||||
border none
|
||||
height 30px
|
||||
width 60px
|
||||
margin-top 1.5px
|
||||
font-size 14px
|
||||
background-color transparent
|
||||
color stripBtnColor
|
||||
&:hover
|
||||
color stripHoverBtnColor
|
||||
&:disabled
|
||||
color lighten(stripBtnColor, 10%)
|
||||
cursor not-allowed
|
||||
&.primary
|
||||
color brandColor
|
||||
&:hover
|
||||
color lighten(brandColor, 10%)
|
||||
|
||||
border-bottom 1px solid borderColor
|
||||
height 44px
|
||||
padding 0 25px
|
||||
width 420px
|
||||
margin 0 auto
|
||||
clearfix()
|
||||
&:nth-last-child(1)
|
||||
border-bottom-color transparent
|
||||
.colUserName
|
||||
float left
|
||||
width 250px
|
||||
clearfix()
|
||||
.userPhoto
|
||||
width 30px
|
||||
height 30px
|
||||
float left
|
||||
margin-top 7px
|
||||
margin-right 15px
|
||||
border-radius 15px
|
||||
.userInfo
|
||||
float left
|
||||
margin-top 7px
|
||||
width 205px
|
||||
.userName
|
||||
font-size 16px
|
||||
margin-bottom 2px
|
||||
overflow ellipsis
|
||||
.userEmail
|
||||
font-size 12px
|
||||
overflow ellipsis
|
||||
.colRole
|
||||
float left
|
||||
width 75px
|
||||
.userRole
|
||||
height 30px
|
||||
background-color transparent
|
||||
border 1px solid transparent
|
||||
margin-top 7px
|
||||
margin-right 35px
|
||||
outline none
|
||||
cursor pointer
|
||||
&:hover
|
||||
border-color borderColor
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
&:disabled
|
||||
border-color transparent
|
||||
cursor not-allowed
|
||||
.colDelete
|
||||
width 45px
|
||||
float right
|
||||
text-align center
|
||||
button.deleteButton
|
||||
border none
|
||||
height 30px
|
||||
width 30px
|
||||
margin-top 7px
|
||||
background-color transparent
|
||||
color stripBtnColor
|
||||
&:hover
|
||||
color stripHoverBtnColor
|
||||
&:disabled
|
||||
color lighten(stripBtnColor, 10%)
|
||||
cursor not-allowed
|
||||
&.header
|
||||
.colRole, .colDelete
|
||||
text-align center
|
||||
.colUserName, .colRole, .colDelete
|
||||
line-height 44px
|
||||
&.FolderSettingTab
|
||||
&>.header
|
||||
border-bottom 1px solid borderColor
|
||||
padding 10px
|
||||
font-size 18px
|
||||
color brandColor
|
||||
line-height 33px
|
||||
.teamSelect
|
||||
border 1px solid borderColor
|
||||
height 33px
|
||||
width 200px
|
||||
margin 0 10px
|
||||
outline none
|
||||
font-size 14px
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
.section
|
||||
.folderTable
|
||||
width 420px
|
||||
margin 15px auto
|
||||
&>div
|
||||
border-bottom 1px solid borderColor
|
||||
clearfix()
|
||||
height 43px
|
||||
line-height 33px
|
||||
padding 5px 0
|
||||
&:last-child
|
||||
border-color transparent
|
||||
.folderColor
|
||||
float left
|
||||
margin-left 10px
|
||||
text-align center
|
||||
width 44px
|
||||
.folderName
|
||||
float left
|
||||
width 175px
|
||||
overflow ellipsis
|
||||
.folderControl
|
||||
float right
|
||||
width 125px
|
||||
text-align center
|
||||
&.folderHeader
|
||||
.folderName
|
||||
padding-left 25px
|
||||
&.newFolder
|
||||
.alert
|
||||
display block
|
||||
color infoTextColor
|
||||
background-color infoBackgroundColor
|
||||
font-size 14px
|
||||
padding 15px 15px
|
||||
width 330px
|
||||
border-radius 5px
|
||||
margin 0 auto
|
||||
&.error
|
||||
color errorTextColor
|
||||
background-color errorBackgroundColor
|
||||
.folderName input
|
||||
height 33px
|
||||
border 1px solid transparent
|
||||
border-radius 5px
|
||||
padding 0 10px
|
||||
font-size 14px
|
||||
outline none
|
||||
width 150px
|
||||
overflow ellipsis
|
||||
&:hover
|
||||
border-color borderColor
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
.folderPublic select
|
||||
height 33px
|
||||
border 1px solid transparent
|
||||
background-color white
|
||||
outline none
|
||||
display block
|
||||
margin 0 auto
|
||||
font-size 14px
|
||||
&:hover
|
||||
border-color borderColor
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
.folderControl
|
||||
button
|
||||
border none
|
||||
height 30px
|
||||
margin-top 1.5px
|
||||
font-size 14px
|
||||
background-color transparent
|
||||
color brandColor
|
||||
&:hover
|
||||
color lighten(brandColor, 10%)
|
||||
&.FolderRow
|
||||
.sortBtns
|
||||
float left
|
||||
display block
|
||||
height 30px
|
||||
width 30px
|
||||
margin-top 1.5px
|
||||
position absolute
|
||||
button
|
||||
absolute left
|
||||
background-color transparent
|
||||
border none
|
||||
height 15px
|
||||
padding 0
|
||||
margin 0
|
||||
color stripBtnColor
|
||||
&:first-child
|
||||
top 0
|
||||
&:last-child
|
||||
top 15px
|
||||
&:hover
|
||||
color stripHoverBtnColor
|
||||
&:disabled
|
||||
color lighten(stripBtnColor, 10%)
|
||||
cursor not-allowed
|
||||
.folderName input
|
||||
height 33px
|
||||
border 1px solid borderColor
|
||||
border-radius 5px
|
||||
padding 0 10px
|
||||
font-size 14px
|
||||
outline none
|
||||
width 150px
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
.folderColor
|
||||
.select
|
||||
height 33px
|
||||
width 33px
|
||||
border 1px solid borderColor
|
||||
background-color white
|
||||
outline none
|
||||
display block
|
||||
margin 0 auto
|
||||
font-size 14px
|
||||
border-radius 5px
|
||||
&:focus
|
||||
border-color iptFocusBorderColor
|
||||
.options
|
||||
position absolute
|
||||
background-color white
|
||||
text-align left
|
||||
border 1px solid borderColor
|
||||
border-radius 5px
|
||||
padding 0 5px 5px
|
||||
margin-left 5px
|
||||
margin-top -34px
|
||||
clearfix()
|
||||
.label
|
||||
margin-left 5px
|
||||
line-height 22px
|
||||
font-size 12px
|
||||
button
|
||||
float left
|
||||
border none
|
||||
width 33px
|
||||
height 33px
|
||||
margin-right 5px
|
||||
border 1px solid transparent
|
||||
line-height 29px
|
||||
overflow hidden
|
||||
border-radius 5px
|
||||
background-color transparent
|
||||
outline none
|
||||
transition 0.1s
|
||||
&:hover
|
||||
border-color borderColor
|
||||
&.active
|
||||
border-color iptFocusBorderColor
|
||||
.FolderMark
|
||||
transform scale(1.4)
|
||||
.folderControl
|
||||
button
|
||||
border none
|
||||
height 30px
|
||||
width 30px
|
||||
margin-top 1.5px
|
||||
font-size 14px
|
||||
background-color transparent
|
||||
color stripBtnColor
|
||||
&:hover
|
||||
color stripHoverBtnColor
|
||||
&:disabled
|
||||
color lighten(stripBtnColor, 10%)
|
||||
cursor not-allowed
|
||||
&.edit
|
||||
.folderControl
|
||||
button
|
||||
width 60px
|
||||
&.primary
|
||||
color brandColor
|
||||
&:hover
|
||||
color lighten(brandColor, 10%)
|
||||
&.delete
|
||||
.folderDeleteLabel
|
||||
float left
|
||||
height 33px
|
||||
width 250px
|
||||
padding-left 15px
|
||||
overflow ellipsis
|
||||
strong
|
||||
font-size 16px
|
||||
color brandColor
|
||||
.folderControl
|
||||
button
|
||||
width 60px
|
||||
&.primary
|
||||
color brandColor
|
||||
&:hover
|
||||
color lighten(brandColor, 10%)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
132
browser/styles/main/HomeContainer/lib/Tutorial.styl
Normal file
132
browser/styles/main/HomeContainer/lib/Tutorial.styl
Normal file
@@ -0,0 +1,132 @@
|
||||
|
||||
slideBgColor0 = #2BAC8F
|
||||
slideBgColor1 = #F68F92
|
||||
slideBgColor2 = #D6AD56
|
||||
slideBgColor3 = #26969B
|
||||
slideBgColor4 = #00B493
|
||||
|
||||
.Tutorial.modal
|
||||
background-color slideBgColor0
|
||||
color white
|
||||
width 720px
|
||||
height 480px
|
||||
margin-top 75px
|
||||
border-radius 5px
|
||||
overflow hidden
|
||||
|
||||
.priorBtn, .nextBtn
|
||||
font-size 72px
|
||||
position absolute
|
||||
background-color transparent
|
||||
color transparentify(white, 50%)
|
||||
transition 0.1s
|
||||
border none
|
||||
line-height 72px
|
||||
padding 0
|
||||
width 93px
|
||||
height 72px
|
||||
z-index 2
|
||||
top 189px
|
||||
&:hover
|
||||
color white
|
||||
&.hide
|
||||
opacity 0
|
||||
.priorBtn
|
||||
left 15px
|
||||
.nextBtn
|
||||
right 15px
|
||||
.title
|
||||
text-align center
|
||||
font-size 54px
|
||||
margin 40px 0
|
||||
.content
|
||||
text-align center
|
||||
font-size 22px
|
||||
line-height 1.8
|
||||
.dots
|
||||
position absolute
|
||||
left 0
|
||||
right 0
|
||||
bottom 25px
|
||||
margin 0 auto
|
||||
color gray
|
||||
text-align center
|
||||
z-index 2
|
||||
&>i
|
||||
transition 0.3s
|
||||
&.active
|
||||
color white
|
||||
.slide
|
||||
absolute top bottom left right
|
||||
z-index 1
|
||||
.slide0
|
||||
background-color slideBgColor0
|
||||
.content
|
||||
margin-top 100px
|
||||
.slide1
|
||||
background-color slideBgColor1
|
||||
.content
|
||||
.markdown
|
||||
background-color white
|
||||
color textColor
|
||||
width 480px
|
||||
height 140px
|
||||
margin 45px auto 0
|
||||
clearfix()
|
||||
text-align left
|
||||
border-radius 5px
|
||||
overflow hidden
|
||||
.left
|
||||
float left
|
||||
width 240px
|
||||
height 140px
|
||||
box-sizing border-box
|
||||
font-size 0.5em
|
||||
padding 30px
|
||||
border-right 1px solid borderColor
|
||||
.right
|
||||
width 240px
|
||||
height 140px
|
||||
float right
|
||||
box-sizing border-box
|
||||
padding: 28px 0 0 10px
|
||||
font-size 0.45em
|
||||
marked()
|
||||
ul
|
||||
padding-left 20px
|
||||
.slide2
|
||||
background-color slideBgColor2
|
||||
.code
|
||||
border-radius 5px
|
||||
overflow hidden
|
||||
text-align left
|
||||
width 480px
|
||||
heght 140px
|
||||
margin 45px auto 0
|
||||
font-size 14px
|
||||
.ace_editor
|
||||
height 140px
|
||||
.slide3
|
||||
background-color slideBgColor3
|
||||
.title
|
||||
margin-bottom 15px
|
||||
.content
|
||||
font-size 18px
|
||||
&>img
|
||||
margin-top 25px
|
||||
.slide4
|
||||
background-color slideBgColor4
|
||||
.content
|
||||
&>button
|
||||
background-color white
|
||||
color brandColor
|
||||
font-size 60px
|
||||
width 250px
|
||||
height 250px
|
||||
border-radius 125px
|
||||
border none
|
||||
transition 0.1s
|
||||
&:hover
|
||||
transform scale(1.2)
|
||||
|
||||
|
||||
21
browser/styles/main/HomeContainer/lib/modal.styl
Normal file
21
browser/styles/main/HomeContainer/lib/modal.styl
Normal file
@@ -0,0 +1,21 @@
|
||||
modalZIndex= 1000
|
||||
modalBackColor = transparentify(black, 65%)
|
||||
|
||||
.ModalBase
|
||||
fixed top left bottom right
|
||||
z-index modalZIndex
|
||||
&.hide
|
||||
display none
|
||||
.modalBack
|
||||
absolute top left bottom right
|
||||
background-color modalBackColor
|
||||
z-index modalZIndex + 1
|
||||
.modal
|
||||
position relative
|
||||
width 650px
|
||||
margin 50px auto 0
|
||||
z-index modalZIndex + 2
|
||||
background-color white
|
||||
padding 15px
|
||||
color #666666
|
||||
border-radius 5px
|
||||
@@ -23,7 +23,7 @@
|
||||
transition: all 200ms ease;
|
||||
}
|
||||
.Select-control:hover {
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
|
||||
// box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.is-searchable.is-open > .Select-control {
|
||||
cursor: text;
|
||||
@@ -42,8 +42,8 @@
|
||||
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);
|
||||
// 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;
|
||||
@@ -141,8 +141,8 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
.Select-menu-outer {
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #cccccc;
|
||||
border-top-color: #e6e6e6;
|
||||
@@ -168,8 +168,8 @@
|
||||
padding: 8px 10px;
|
||||
}
|
||||
.Select-option:last-child {
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
.Select-option.is-focused {
|
||||
background-color: #f2f9fc;
|
||||
@@ -196,10 +196,10 @@
|
||||
padding: 3px 0;
|
||||
}
|
||||
.Select-item {
|
||||
background-color: #f2f9fc;
|
||||
background-color: brandColor;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #c9e6f2;
|
||||
color: #0088cc;
|
||||
// border: 1px solid #c9e6f2;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
font-size: 1em;
|
||||
margin: 2px;
|
||||
@@ -216,20 +216,19 @@
|
||||
padding: 3px 5px;
|
||||
}
|
||||
.Select-item-label .Select-item-label__a {
|
||||
color: #0088cc;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
cursor: white;
|
||||
}
|
||||
.Select-item-icon {
|
||||
cursor: pointer;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-top-left-radius: 2px;
|
||||
border-right: 1px solid #c9e6f2;
|
||||
border-right: 1px solid darken(brandColor, 10%)
|
||||
padding: 2px 5px 4px;
|
||||
}
|
||||
.Select-item-icon:hover,
|
||||
.Select-item-icon:focus {
|
||||
background-color: #ddeff7;
|
||||
color: #0077b3;
|
||||
background-color: lighten(brandColor, 10%)
|
||||
}
|
||||
.Select-item-icon:active {
|
||||
background-color: #c9e6f2;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.LoginContainer, .SignupContainer
|
||||
margin 0 auto
|
||||
padding 25px 15px
|
||||
padding 105px 15px
|
||||
box-sizing border-box
|
||||
color inactiveTextColor
|
||||
.logo
|
||||
@@ -58,17 +58,24 @@
|
||||
.alertInfo, .alertError
|
||||
margin-top 15px
|
||||
margin-bottom 15px
|
||||
height 44px
|
||||
padding 5px
|
||||
border-radius 10px
|
||||
line-height 44px
|
||||
padding 10px
|
||||
border-radius 5px
|
||||
line-height 1.6
|
||||
text-align center
|
||||
.alertInfo
|
||||
alertInfo()
|
||||
.alertError
|
||||
alertError()
|
||||
div.form-group:last-child
|
||||
margin-top 15px
|
||||
div.formField
|
||||
input
|
||||
stripInput()
|
||||
height 33px
|
||||
width 100%
|
||||
margin-bottom 10px
|
||||
text-align center
|
||||
font-size 1.1em
|
||||
&:last-child
|
||||
margin-top 15px
|
||||
button.logInButton
|
||||
btnPrimary()
|
||||
height 44px
|
||||
|
||||
@@ -1,310 +1,123 @@
|
||||
.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
|
||||
userNavigatorWidth = 200px
|
||||
userNavigatorBgColor = #333
|
||||
userNavigatorColor = #DDD
|
||||
userNavigatorProfileNameColor = brandColor
|
||||
userNavigatorBorderColor = #666
|
||||
|
||||
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
|
||||
userContentBgColor = #E6E6E6
|
||||
|
||||
.UserContainer
|
||||
absolute top bottom right
|
||||
left 60px
|
||||
.content
|
||||
absolute top bottom right
|
||||
left userNavigatorWidth
|
||||
background-color userContentBgColor
|
||||
.UserNavigator
|
||||
absolute left top bottom
|
||||
width userNavigatorWidth
|
||||
background-color userNavigatorBgColor
|
||||
color userNavigatorColor
|
||||
noSelect()
|
||||
&>.profile
|
||||
height 60px
|
||||
padding 10px 15px 0
|
||||
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%)
|
||||
position relative
|
||||
border-bottom solid 1px userNavigatorBorderColor
|
||||
cursor pointer
|
||||
&>.profileName
|
||||
color userNavigatorProfileNameColor
|
||||
font-size 22px
|
||||
cursor pointer
|
||||
transition 0.1s
|
||||
&>.name
|
||||
padding 5px 10px
|
||||
font-size 14px
|
||||
color userNavigatorColor
|
||||
cursor pointer
|
||||
transition 0.1s
|
||||
&>.dropdownIcon
|
||||
position absolute
|
||||
top 20px
|
||||
right 25px
|
||||
float right
|
||||
width 20px
|
||||
height 20px
|
||||
line-height 20px
|
||||
font-size 8px
|
||||
border solid 1px userNavigatorColor
|
||||
border-radius 12.5px
|
||||
text-align center
|
||||
transition 0.1s
|
||||
&:hover
|
||||
&>.profileName
|
||||
color lighten(brandColor, 10%)
|
||||
&>.name
|
||||
color white
|
||||
&>.dropdownIcon
|
||||
border-color white
|
||||
&:active
|
||||
&>.dropdownIcon
|
||||
background-color brandColor
|
||||
border-color brandColor
|
||||
&>.control
|
||||
padding 15px 15px
|
||||
&>.newPostButton
|
||||
background-color brandColor
|
||||
color white
|
||||
.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
|
||||
height 44px
|
||||
width 100%
|
||||
border none
|
||||
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
|
||||
font-size 16px
|
||||
font-weight 600
|
||||
transition 0.1s
|
||||
&:hover
|
||||
background-color lighten(brandColor, 10%)
|
||||
&>.menu
|
||||
absolute left right bottom
|
||||
top 134px
|
||||
padding 15px 0
|
||||
overflow auto
|
||||
&>.menuGruop
|
||||
&>.label
|
||||
border-bottom 1px solid userNavigatorBorderColor
|
||||
padding 10px 15px
|
||||
font-size 18px
|
||||
margin-bottom 10px
|
||||
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()
|
||||
&>.plusButton
|
||||
float right
|
||||
width 20px
|
||||
height 20px
|
||||
margin-top -2.5px
|
||||
margin-right -5px
|
||||
line-height 15px
|
||||
font-size 8px
|
||||
border solid 1px userNavigatorColor
|
||||
border-radius 10px
|
||||
background-color transparent
|
||||
text-align center
|
||||
color userNavigatorColor
|
||||
&:hover
|
||||
border-color white
|
||||
color white
|
||||
&:active
|
||||
background-color brandColor
|
||||
border-color brandColor
|
||||
&>.folders
|
||||
.folderButton
|
||||
padding 10px 25px
|
||||
width 100%
|
||||
background-color transparent
|
||||
border none
|
||||
font-size 14px
|
||||
color userNavigatorColor
|
||||
transition 0.1s
|
||||
text-align left
|
||||
&:hover
|
||||
background-color transparentify(white, 20%)
|
||||
color white
|
||||
&.active
|
||||
background-color brandColor
|
||||
color white
|
||||
|
||||
@@ -5,6 +5,13 @@ global-reset()
|
||||
@import '../shared/*'
|
||||
@import './components/*'
|
||||
@import './containers/*'
|
||||
@import './HomeContainer'
|
||||
|
||||
DEFAULT_FONTS = 'Lato', 'MS Gothic', 'Malgun Gothic', 'Sans-serif'
|
||||
|
||||
*
|
||||
-webkit-app-region no-drag
|
||||
-webkit-user-select none
|
||||
|
||||
html, body
|
||||
width 100%
|
||||
@@ -12,28 +19,17 @@ html, body
|
||||
overflow hidden
|
||||
|
||||
body
|
||||
font-family "Lato"
|
||||
font-family DEFAULT_FONTS
|
||||
color textColor
|
||||
font-size fontSize
|
||||
font-weight 400
|
||||
button
|
||||
font-family "Lato"
|
||||
|
||||
button, input, select
|
||||
font-family DEFAULT_FONTS
|
||||
|
||||
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
|
||||
@@ -52,6 +48,9 @@ button
|
||||
&:focus, &.focus
|
||||
outline none
|
||||
|
||||
.noSelect
|
||||
noSelect()
|
||||
|
||||
.text-center
|
||||
text-align center
|
||||
|
||||
@@ -61,13 +60,6 @@ button
|
||||
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
|
||||
@@ -107,22 +99,32 @@ textarea.block-input
|
||||
z-index 2000
|
||||
bottom 5px
|
||||
right 53px
|
||||
btnPrimary()
|
||||
padding 10px 15px
|
||||
border none
|
||||
border-radius 5px
|
||||
background-color backgroundColor
|
||||
background-color brandColor
|
||||
color white
|
||||
opacity 0.7
|
||||
&:hover
|
||||
opacity 1
|
||||
background-color lighten(brandColor, 10%)
|
||||
.contactButton
|
||||
position fixed
|
||||
z-index 2000
|
||||
bottom 5px
|
||||
right 5px
|
||||
btnPrimary()
|
||||
padding 10px 15px
|
||||
border none
|
||||
border-radius 5px
|
||||
background-color backgroundColor
|
||||
background-color brandColor
|
||||
color white
|
||||
opacity 0.7
|
||||
&:hover
|
||||
opacity 1
|
||||
background-color lighten(brandColor, 10%)
|
||||
.tooltip
|
||||
tooltip()
|
||||
margin-top -22px
|
||||
margin-left -97px
|
||||
margin-left -107px
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user