Compare commits
123 Commits
0.4.2-rc.0
...
v0.5.0
| 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 | ||
|
|
ee4ac7371c | ||
|
|
e731b7882d | ||
|
|
ee91daad7e | ||
|
|
1318abd37e | ||
|
|
76a031a8c9 | ||
|
|
09482ebcf3 | ||
|
|
67424f2d3a | ||
|
|
61cc44cc83 | ||
|
|
59d3c6c94f | ||
|
|
388027b731 | ||
|
|
9758f5baa8 | ||
|
|
248262a597 | ||
|
|
3f54eb52b2 | ||
|
|
1e7415b692 | ||
|
|
ff950ef28a | ||
|
|
5fa37dbffb | ||
|
|
06a54d451c | ||
|
|
45541a255b | ||
|
|
345d7b427a | ||
|
|
b2845e2284 | ||
|
|
4bda84d69c | ||
|
|
b510aa11f5 | ||
|
|
85f833c865 | ||
|
|
b93990d10b | ||
|
|
a0bcb8edbe | ||
|
|
19930a2472 | ||
|
|
e75d95b1fc | ||
|
|
3b7215b36a | ||
|
|
8ab96cf2fb | ||
|
|
63af2fd8b1 | ||
|
|
fcf26f7844 | ||
|
|
a05bba15e7 | ||
|
|
3b907627f7 | ||
|
|
2284fd41b9 |
4
.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
.env
|
.env
|
||||||
node_modules/*
|
node_modules/*
|
||||||
!node_modules/boost
|
!node_modules/boost
|
||||||
Boost-darwin-x64/
|
dist/
|
||||||
backup/
|
|
||||||
compiled
|
compiled
|
||||||
|
/secret
|
||||||
|
|||||||
2
LICENSE
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
本製品をインストール、または使用することによって、お客様は利用規約(
|
||||||
|
https://b00st.io/regulations.html)より拘束されることに承諾されたものとします。利用規約に同意されない場合、Boostnoteは、お客様に本製品のインストール、使用のいずれも許諾できません。
|
||||||
@@ -1,30 +1,161 @@
|
|||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const BrowserWindow = electron.BrowserWindow
|
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 path = require('path')
|
||||||
|
const nodeIpc = require('@rokt33r/node-ipc')
|
||||||
|
|
||||||
var finderWindow = new BrowserWindow({
|
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,
|
width: 640,
|
||||||
height: 400,
|
height: 400,
|
||||||
show: false,
|
show: false,
|
||||||
frame: false,
|
frame: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
'zoom-factor': 1.0,
|
'zoom-factor': 1.0,
|
||||||
'always-on-top': true,
|
|
||||||
'web-preferences': {
|
'web-preferences': {
|
||||||
'overlay-scrollbars': true,
|
'overlay-scrollbars': true,
|
||||||
'skip-taskbar': true
|
'skip-taskbar': true
|
||||||
},
|
},
|
||||||
'standard-window': false
|
'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')
|
var url = path.resolve(__dirname, '../browser/finder/index.html')
|
||||||
|
|
||||||
finderWindow.loadURL('file://' + url)
|
finderWindow.loadURL('file://' + url)
|
||||||
|
finderWindow.setSkipTaskbar(true)
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
finderWindow.setVisibleOnAllWorkspaces(true)
|
||||||
|
}
|
||||||
|
|
||||||
finderWindow.on('blur', function () {
|
finderWindow.on('blur', function () {
|
||||||
finderWindow.hide()
|
hideFinder()
|
||||||
})
|
})
|
||||||
|
|
||||||
finderWindow.setVisibleOnAllWorkspaces(true)
|
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
|
module.exports = finderWindow
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
|
const app = electron.app
|
||||||
const BrowserWindow = electron.BrowserWindow
|
const BrowserWindow = electron.BrowserWindow
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
@@ -16,10 +17,13 @@ const url = path.resolve(__dirname, '../browser/main/index.html')
|
|||||||
|
|
||||||
mainWindow.loadURL('file://' + url)
|
mainWindow.loadURL('file://' + url)
|
||||||
|
|
||||||
mainWindow.setVisibleOnAllWorkspaces(true)
|
|
||||||
|
|
||||||
mainWindow.webContents.on('new-window', function (e) {
|
mainWindow.webContents.on('new-window', function (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.on('activate', function () {
|
||||||
|
if (mainWindow == null) return null
|
||||||
|
mainWindow.show()
|
||||||
|
})
|
||||||
|
|
||||||
module.exports = mainWindow
|
module.exports = mainWindow
|
||||||
|
|||||||
@@ -1,122 +1,160 @@
|
|||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const BrowserWindow = electron.BrowserWindow
|
const BrowserWindow = electron.BrowserWindow
|
||||||
|
const shell = electron.shell
|
||||||
|
|
||||||
module.exports = [
|
var boost = {
|
||||||
{
|
label: 'Boostnote',
|
||||||
label: 'Electron',
|
submenu: [
|
||||||
submenu: [
|
{
|
||||||
{
|
label: 'About Boostnote',
|
||||||
label: 'About Boost',
|
selector: 'orderFrontStandardAboutPanel:'
|
||||||
selector: 'orderFrontStandardAboutPanel:'
|
},
|
||||||
},
|
{
|
||||||
{
|
type: 'separator'
|
||||||
type: 'separator'
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Services',
|
||||||
label: 'Services',
|
submenu: []
|
||||||
submenu: []
|
},
|
||||||
},
|
{
|
||||||
{
|
type: 'separator'
|
||||||
type: 'separator'
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Hide Boostnote',
|
||||||
label: 'Hide Boost',
|
accelerator: 'Command+H',
|
||||||
accelerator: 'Command+H',
|
selector: 'hide:'
|
||||||
selector: 'hide:'
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Hide Others',
|
||||||
label: 'Hide Others',
|
accelerator: 'Command+Shift+H',
|
||||||
accelerator: 'Command+Shift+H',
|
selector: 'hideOtherApplications:'
|
||||||
selector: 'hideOtherApplications:'
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Show All',
|
||||||
label: 'Show All',
|
selector: 'unhideAllApplications:'
|
||||||
selector: 'unhideAllApplications:'
|
},
|
||||||
},
|
{
|
||||||
{
|
type: 'separator'
|
||||||
type: 'separator'
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Quit',
|
||||||
label: 'Quit',
|
accelerator: 'Command+Q',
|
||||||
accelerator: 'Command+Q',
|
selector: 'terminate:'
|
||||||
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',
|
||||||
label: 'Edit',
|
// accelerator: (function () {
|
||||||
submenu: [
|
// if (process.platform === 'darwin') return 'Alt+Command+I'
|
||||||
{
|
// else return 'Ctrl+Shift+I'
|
||||||
label: 'Undo',
|
// })(),
|
||||||
accelerator: 'Command+Z',
|
// click: function (item, focusedWindow) {
|
||||||
selector: 'undo:'
|
// if (focusedWindow) BrowserWindow.getFocusedWindow().toggleDevTools()
|
||||||
},
|
// }
|
||||||
{
|
// }
|
||||||
label: 'Redo',
|
]
|
||||||
accelerator: 'Shift+Command+Z',
|
}
|
||||||
selector: 'redo:'
|
|
||||||
},
|
var window = {
|
||||||
{
|
label: 'Window',
|
||||||
type: 'separator'
|
submenu: [
|
||||||
},
|
{
|
||||||
{
|
label: 'Minimize',
|
||||||
label: 'Cut',
|
accelerator: 'Command+M',
|
||||||
accelerator: 'Command+X',
|
selector: 'performMiniaturize:'
|
||||||
selector: 'cut:'
|
},
|
||||||
},
|
{
|
||||||
{
|
label: 'Close',
|
||||||
label: 'Copy',
|
accelerator: 'Command+W',
|
||||||
accelerator: 'Command+C',
|
selector: 'performClose:'
|
||||||
selector: 'copy:'
|
},
|
||||||
},
|
{
|
||||||
{
|
type: 'separator'
|
||||||
label: 'Paste',
|
},
|
||||||
accelerator: 'Command+V',
|
{
|
||||||
selector: 'paste:'
|
label: 'Bring All to Front',
|
||||||
},
|
selector: 'arrangeInFront:'
|
||||||
{
|
}
|
||||||
label: 'Select All',
|
]
|
||||||
accelerator: 'Command+A',
|
}
|
||||||
selector: 'selectAll:'
|
|
||||||
}
|
var help = {
|
||||||
]
|
label: 'Help',
|
||||||
},
|
role: 'help',
|
||||||
{
|
submenu: [
|
||||||
label: 'View',
|
{
|
||||||
submenu: [
|
label: 'Boostnote official site',
|
||||||
{
|
click: function () { shell.openExternal('https://b00st.io/') }
|
||||||
label: 'Reload',
|
},
|
||||||
accelerator: 'Command+R',
|
{
|
||||||
click: function () {
|
label: 'Tutorial page',
|
||||||
BrowserWindow.getFocusedWindow().reload()
|
click: function () { shell.openExternal('https://b00st.io/tutorial.html') }
|
||||||
}
|
},
|
||||||
}
|
{
|
||||||
]
|
label: 'Discussions',
|
||||||
},
|
click: function () { shell.openExternal('https://github.com/BoostIO/boost-app-discussions/issues') }
|
||||||
{
|
},
|
||||||
label: 'Window',
|
{
|
||||||
submenu: [
|
label: 'Changelog',
|
||||||
{
|
click: function () { shell.openExternal('https://github.com/BoostIO/boost-releases/blob/master/changelog.md') }
|
||||||
label: 'Minimize',
|
}
|
||||||
accelerator: 'Command+M',
|
]
|
||||||
selector: 'performMiniaturize:'
|
}
|
||||||
},
|
|
||||||
{
|
module.exports = process.platform === 'darwin'
|
||||||
label: 'Close',
|
? [boost, edit, view, window, help]
|
||||||
accelerator: 'Command+W',
|
: [view, help]
|
||||||
selector: 'performClose:'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Bring All to Front',
|
|
||||||
selector: 'arrangeInFront:'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Help',
|
|
||||||
submenu: []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
||||||
<title>Boost Finder</title>
|
<title>Boostnote Finder</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||||
|
|
||||||
@@ -30,7 +30,8 @@
|
|||||||
<script>
|
<script>
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
electron.webFrame.setZoomLevelLimits(1, 1)
|
||||||
var scriptUrl = process.env.BOOST_ENV === 'development'
|
const _ = require('lodash')
|
||||||
|
var scriptUrl = _.find(electron.remote.process.argv, a => a === '--hot')
|
||||||
? 'http://localhost:8080/assets/finder.js'
|
? 'http://localhost:8080/assets/finder.js'
|
||||||
: '../../compiled/finder.js'
|
: '../../compiled/finder.js'
|
||||||
var scriptEl=document.createElement('script')
|
var scriptEl=document.createElement('script')
|
||||||
|
|||||||
@@ -11,12 +11,19 @@ import _ from 'lodash'
|
|||||||
import dataStore from 'boost/dataStore'
|
import dataStore from 'boost/dataStore'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote, clipboard } = electron
|
const { remote, clipboard, ipcRenderer } = electron
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
var hideFinder = remote.getGlobal('hideFinder')
|
function hideFinder () {
|
||||||
|
ipcRenderer.send('hide-finder')
|
||||||
|
}
|
||||||
|
|
||||||
function notify (...args) {
|
function notify (title, options) {
|
||||||
return new window.Notification(...args)
|
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')
|
require('../styles/finder/index.styl')
|
||||||
@@ -32,11 +39,20 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
this.keyDownHandler = e => this.handleKeyDown(e)
|
||||||
|
document.addEventListener('keydown', this.keyDownHandler)
|
||||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
||||||
|
this.focusHandler = e => {
|
||||||
|
let { dispatch } = this.props
|
||||||
|
|
||||||
|
dispatch(searchArticle(''))
|
||||||
|
}
|
||||||
|
window.addEventListener('focus', this.focusHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick (e) {
|
componentWillUnmount () {
|
||||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
document.removeEventListener('keydown', this.keyDownHandler)
|
||||||
|
window.removeEventListener('focus', this.focusHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown (e) {
|
||||||
@@ -58,12 +74,18 @@ class FinderMain extends React.Component {
|
|||||||
hideFinder()
|
hideFinder()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
if (e.keyCode === 91 || e.metaKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
saveToClipboard () {
|
saveToClipboard () {
|
||||||
let { activeArticle } = this.props
|
let { activeArticle } = this.props
|
||||||
clipboard.writeText(activeArticle.content)
|
clipboard.writeText(activeArticle.content)
|
||||||
|
|
||||||
|
ipcRenderer.send('copy-finder')
|
||||||
notify('Saved to Clipboard!', {
|
notify('Saved to Clipboard!', {
|
||||||
body: 'Paste it wherever you want!'
|
body: 'Paste it wherever you want!'
|
||||||
})
|
})
|
||||||
@@ -98,7 +120,7 @@ class FinderMain extends React.Component {
|
|||||||
let { articles, activeArticle, status, dispatch } = this.props
|
let { articles, activeArticle, status, dispatch } = this.props
|
||||||
let saveToClipboard = () => this.saveToClipboard()
|
let saveToClipboard = () => this.saveToClipboard()
|
||||||
return (
|
return (
|
||||||
<div onClick={e => this.handleClick(e)} onKeyDown={e => this.handleKeyDown(e)} className='Finder'>
|
<div onClick={e => this.handleClick(e)} className='Finder'>
|
||||||
<FinderInput
|
<FinderInput
|
||||||
handleSearchChange={e => this.handleSearchChange(e)}
|
handleSearchChange={e => this.handleSearchChange(e)}
|
||||||
ref='finderInput'
|
ref='finderInput'
|
||||||
@@ -152,6 +174,14 @@ function buildFilter (key) {
|
|||||||
return {type: TEXT_FILTER, value: key}
|
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) {
|
function remap (state) {
|
||||||
let { articles, folders, status } = state
|
let { articles, folders, status } = state
|
||||||
|
|
||||||
@@ -168,10 +198,10 @@ function remap (state) {
|
|||||||
let targetFolders
|
let targetFolders
|
||||||
if (folders != null) {
|
if (folders != null) {
|
||||||
let exactTargetFolders = folders.filter(folder => {
|
let exactTargetFolders = folders.filter(folder => {
|
||||||
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
|
return _.find(folderExactFilters, filter => isContaining(folder.name, filter.value))
|
||||||
})
|
})
|
||||||
let fuzzyTargetFolders = folders.filter(folder => {
|
let fuzzyTargetFolders = folders.filter(folder => {
|
||||||
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
|
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
|
||||||
})
|
})
|
||||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
||||||
|
|
||||||
@@ -184,7 +214,7 @@ function remap (state) {
|
|||||||
if (textFilters.length > 0) {
|
if (textFilters.length > 0) {
|
||||||
articles = textFilters.reduce((articles, textFilter) => {
|
articles = textFilters.reduce((articles, textFilter) => {
|
||||||
return articles.filter(article => {
|
return articles.filter(article => {
|
||||||
return article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
|
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
|
||||||
})
|
})
|
||||||
}, articles)
|
}, articles)
|
||||||
}
|
}
|
||||||
@@ -192,7 +222,7 @@ function remap (state) {
|
|||||||
if (tagFilters.length > 0) {
|
if (tagFilters.length > 0) {
|
||||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
articles = tagFilters.reduce((articles, tagFilter) => {
|
||||||
return articles.filter(article => {
|
return articles.filter(article => {
|
||||||
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
|
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
|
||||||
})
|
})
|
||||||
}, articles)
|
}, articles)
|
||||||
}
|
}
|
||||||
@@ -201,7 +231,6 @@ function remap (state) {
|
|||||||
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
||||||
if (activeArticle == null) activeArticle = articles[0]
|
if (activeArticle == null) activeArticle = articles[0]
|
||||||
|
|
||||||
console.log(status.search)
|
|
||||||
return {
|
return {
|
||||||
articles,
|
articles,
|
||||||
activeArticle,
|
activeArticle,
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React, { PropTypes} from 'react'
|
import React, { PropTypes} from 'react'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { CREATE_MODE, EDIT_MODE, IDLE_MODE, NEW, toggleTutorial } from 'boost/actions'
|
import { EDIT_MODE, IDLE_MODE, toggleTutorial } from 'boost/actions'
|
||||||
// import UserNavigator from './HomePage/UserNavigator'
|
|
||||||
import ArticleNavigator from './HomePage/ArticleNavigator'
|
import ArticleNavigator from './HomePage/ArticleNavigator'
|
||||||
import ArticleTopBar from './HomePage/ArticleTopBar'
|
import ArticleTopBar from './HomePage/ArticleTopBar'
|
||||||
import ArticleList from './HomePage/ArticleList'
|
import ArticleList from './HomePage/ArticleList'
|
||||||
import ArticleDetail from './HomePage/ArticleDetail'
|
import ArticleDetail from './HomePage/ArticleDetail'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { isModalOpen, closeModal } from 'boost/modal'
|
import { isModalOpen, closeModal } from 'boost/modal'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const BrowserWindow = electron.remote.BrowserWindow
|
const remote = electron.remote
|
||||||
|
|
||||||
const TEXT_FILTER = 'TEXT_FILTER'
|
const TEXT_FILTER = 'TEXT_FILTER'
|
||||||
const FOLDER_FILTER = 'FOLDER_FILTER'
|
const FOLDER_FILTER = 'FOLDER_FILTER'
|
||||||
@@ -29,12 +29,7 @@ class HomePage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown (e) {
|
||||||
if (process.env.BOOST_ENV === 'development' && e.keyCode === 73 && e.metaKey && e.altKey) {
|
let cmdOrCtrl = process.platform === 'darwin' ? e.metaKey : e.ctrlKey
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
BrowserWindow.getFocusedWindow().toggleDevTools()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isModalOpen()) {
|
if (isModalOpen()) {
|
||||||
if (e.keyCode === 27) closeModal()
|
if (e.keyCode === 27) closeModal()
|
||||||
@@ -51,20 +46,27 @@ class HomePage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search inputがfocusされていたら大体のキー入力は無視される。
|
// Search inputがfocusされていたら大体のキー入力は無視される。
|
||||||
if (top.isInputFocused() && !e.metaKey) {
|
if (top.isInputFocused() && !(e.metaKey || e.ctrlKey)) {
|
||||||
if (e.keyCode === 13 || e.keyCode === 27) top.escape()
|
if (e.keyCode === 13 || e.keyCode === 27) top.escape()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (status.mode) {
|
switch (status.mode) {
|
||||||
case CREATE_MODE:
|
|
||||||
case EDIT_MODE:
|
case EDIT_MODE:
|
||||||
if (e.keyCode === 27) {
|
if (e.keyCode === 27) {
|
||||||
detail.handleCancelButtonClick()
|
detail.handleCancelButtonClick()
|
||||||
}
|
}
|
||||||
if ((e.keyCode === 13 && e.metaKey) || (e.keyCode === 83 && e.metaKey)) {
|
if ((e.keyCode === 13 && (cmdOrCtrl)) || (e.keyCode === 83 && (cmdOrCtrl))) {
|
||||||
detail.handleSaveButtonClick()
|
detail.handleSaveButtonClick()
|
||||||
}
|
}
|
||||||
|
if (e.keyCode === 80 && cmdOrCtrl) {
|
||||||
|
detail.handleTogglePreviewButtonClick()
|
||||||
|
}
|
||||||
|
if (e.keyCode === 78 && cmdOrCtrl) {
|
||||||
|
nav.handleNewPostButtonClick()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case IDLE_MODE:
|
case IDLE_MODE:
|
||||||
if (e.keyCode === 69) {
|
if (e.keyCode === 69) {
|
||||||
@@ -80,14 +82,14 @@ class HomePage extends React.Component {
|
|||||||
if (e.keyCode === 27) {
|
if (e.keyCode === 27) {
|
||||||
detail.handleDeleteCancelButtonClick()
|
detail.handleDeleteCancelButtonClick()
|
||||||
}
|
}
|
||||||
if (e.keyCode === 13 && e.metaKey) {
|
if (e.keyCode === 13 && cmdOrCtrl) {
|
||||||
detail.handleDeleteConfirmButtonClick()
|
detail.handleDeleteConfirmButtonClick()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// `detail`の`openDeleteConfirmMenu`が`true`なら呼ばれない。
|
// `detail`の`openDeleteConfirmMenu`が`true`なら呼ばれない。
|
||||||
if (e.keyCode === 27 || (e.keyCode === 70 && e.metaKey)) {
|
if (e.keyCode === 27 || (e.keyCode === 70 && cmdOrCtrl)) {
|
||||||
top.focusInput()
|
top.focusInput()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +101,7 @@ class HomePage extends React.Component {
|
|||||||
list.selectNextArticle()
|
list.selectNextArticle()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.keyCode === 65 || e.keyCode === 13 && e.metaKey) {
|
if ((e.keyCode === 65 && !e.metaKey && !e.ctrlKey) || (e.keyCode === 13 && cmdOrCtrl) || (e.keyCode === 78 && cmdOrCtrl)) {
|
||||||
nav.handleNewPostButtonClick()
|
nav.handleNewPostButtonClick()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
@@ -107,13 +109,14 @@ class HomePage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { dispatch, status, articles, allArticles, activeArticle, folders, tags, filters } = this.props
|
let { dispatch, status, user, articles, allArticles, activeArticle, folders, tags, filters } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='HomePage'>
|
<div className='HomePage'>
|
||||||
<ArticleNavigator
|
<ArticleNavigator
|
||||||
ref='nav'
|
ref='nav'
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
|
user={user}
|
||||||
folders={folders}
|
folders={folders}
|
||||||
status={status}
|
status={status}
|
||||||
allArticles={allArticles}
|
allArticles={allArticles}
|
||||||
@@ -134,6 +137,7 @@ class HomePage extends React.Component {
|
|||||||
<ArticleDetail
|
<ArticleDetail
|
||||||
ref='detail'
|
ref='detail'
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
|
user={user}
|
||||||
activeArticle={activeArticle}
|
activeArticle={activeArticle}
|
||||||
folders={folders}
|
folders={folders}
|
||||||
status={status}
|
status={status}
|
||||||
@@ -164,8 +168,16 @@ function buildFilter (key) {
|
|||||||
return {type: TEXT_FILTER, value: key}
|
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) {
|
function remap (state) {
|
||||||
let { folders, articles, status } = state
|
let { user, folders, articles, status } = state
|
||||||
|
|
||||||
if (articles == null) articles = []
|
if (articles == null) articles = []
|
||||||
articles.sort((a, b) => {
|
articles.sort((a, b) => {
|
||||||
@@ -192,10 +204,10 @@ function remap (state) {
|
|||||||
let targetFolders
|
let targetFolders
|
||||||
if (folders != null) {
|
if (folders != null) {
|
||||||
let exactTargetFolders = folders.filter(folder => {
|
let exactTargetFolders = folders.filter(folder => {
|
||||||
return _.find(folderExactFilters, filter => folder.name.match(new RegExp(`^${filter.value}$`)))
|
return _.findWhere(folderExactFilters, {value: folder.name})
|
||||||
})
|
})
|
||||||
let fuzzyTargetFolders = folders.filter(folder => {
|
let fuzzyTargetFolders = folders.filter(folder => {
|
||||||
return _.find(folderFilters, filter => folder.name.match(new RegExp(`^${filter.value}`)))
|
return _.find(folderFilters, filter => startsWith(folder.name, filter.value))
|
||||||
})
|
})
|
||||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
||||||
|
|
||||||
@@ -208,7 +220,7 @@ function remap (state) {
|
|||||||
if (textFilters.length > 0) {
|
if (textFilters.length > 0) {
|
||||||
articles = textFilters.reduce((articles, textFilter) => {
|
articles = textFilters.reduce((articles, textFilter) => {
|
||||||
return articles.filter(article => {
|
return articles.filter(article => {
|
||||||
return article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
|
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
|
||||||
})
|
})
|
||||||
}, articles)
|
}, articles)
|
||||||
}
|
}
|
||||||
@@ -216,7 +228,7 @@ function remap (state) {
|
|||||||
if (tagFilters.length > 0) {
|
if (tagFilters.length > 0) {
|
||||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
articles = tagFilters.reduce((articles, tagFilter) => {
|
||||||
return articles.filter(article => {
|
return articles.filter(article => {
|
||||||
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
|
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
|
||||||
})
|
})
|
||||||
}, articles)
|
}, articles)
|
||||||
}
|
}
|
||||||
@@ -227,6 +239,7 @@ function remap (state) {
|
|||||||
if (activeArticle == null) activeArticle = articles[0]
|
if (activeArticle == null) activeArticle = articles[0]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
user,
|
||||||
folders,
|
folders,
|
||||||
status,
|
status,
|
||||||
allArticles,
|
allArticles,
|
||||||
@@ -242,11 +255,9 @@ function remap (state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HomePage.propTypes = {
|
HomePage.propTypes = {
|
||||||
params: PropTypes.shape({
|
status: PropTypes.shape(),
|
||||||
userId: PropTypes.string
|
user: PropTypes.shape({
|
||||||
}),
|
name: PropTypes.string
|
||||||
status: PropTypes.shape({
|
|
||||||
userId: PropTypes.string
|
|
||||||
}),
|
}),
|
||||||
articles: PropTypes.array,
|
articles: PropTypes.array,
|
||||||
allArticles: PropTypes.array,
|
allArticles: PropTypes.array,
|
||||||
@@ -257,7 +268,8 @@ HomePage.propTypes = {
|
|||||||
folder: PropTypes.array,
|
folder: PropTypes.array,
|
||||||
tag: PropTypes.array,
|
tag: PropTypes.array,
|
||||||
text: PropTypes.array
|
text: PropTypes.array
|
||||||
})
|
}),
|
||||||
|
tags: PropTypes.array
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(remap)(HomePage)
|
export default connect(remap)(HomePage)
|
||||||
|
|||||||
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
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -24,6 +24,10 @@ import TagLink from 'boost/components/TagLink'
|
|||||||
import TagSelect from 'boost/components/TagSelect'
|
import TagSelect from 'boost/components/TagSelect'
|
||||||
import ModeSelect from 'boost/components/ModeSelect'
|
import ModeSelect from 'boost/components/ModeSelect'
|
||||||
import activityRecord from 'boost/activityRecord'
|
import activityRecord from 'boost/activityRecord'
|
||||||
|
import ShareButton from './ShareButton'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const clipboard = electron.clipboard
|
||||||
|
|
||||||
const BRAND_COLOR = '#18AF90'
|
const BRAND_COLOR = '#18AF90'
|
||||||
|
|
||||||
@@ -84,6 +88,10 @@ const modeSelectTutorialElement = (
|
|||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function notify (...args) {
|
||||||
|
return new window.Notification(...args)
|
||||||
|
}
|
||||||
|
|
||||||
function makeInstantArticle (article) {
|
function makeInstantArticle (article) {
|
||||||
return Object.assign({}, article)
|
return Object.assign({}, article)
|
||||||
}
|
}
|
||||||
@@ -99,12 +107,16 @@ export default class ArticleDetail extends React.Component {
|
|||||||
isTagChanged: false,
|
isTagChanged: false,
|
||||||
isTitleChanged: false,
|
isTitleChanged: false,
|
||||||
isContentChanged: false,
|
isContentChanged: false,
|
||||||
isModeChanged: false
|
isModeChanged: false,
|
||||||
|
openShareDropdown: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
|
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
|
||||||
|
this.shareDropdownInterceptor = e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
@@ -154,6 +166,14 @@ export default class ArticleDetail extends React.Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
handleEditButtonClick (e) {
|
||||||
let { dispatch } = this.props
|
let { dispatch } = this.props
|
||||||
dispatch(switchMode(EDIT_MODE))
|
dispatch(switchMode(EDIT_MODE))
|
||||||
@@ -176,7 +196,7 @@ export default class ArticleDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderIdle () {
|
renderIdle () {
|
||||||
let { status, activeArticle, folders } = this.props
|
let { status, activeArticle, folders, user } = this.props
|
||||||
|
|
||||||
let tags = activeArticle.tags != null ? activeArticle.tags.length > 0
|
let tags = activeArticle.tags != null ? activeArticle.tags.length > 0
|
||||||
? activeArticle.tags.map(tag => {
|
? activeArticle.tags.map(tag => {
|
||||||
@@ -185,8 +205,13 @@ export default class ArticleDetail extends React.Component {
|
|||||||
: (
|
: (
|
||||||
<span className='noTags'>Not tagged yet</span>
|
<span className='noTags'>Not tagged yet</span>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
let folder = _.findWhere(folders, {key: activeArticle.FolderKey})
|
let folder = _.findWhere(folders, {key: activeArticle.FolderKey})
|
||||||
|
|
||||||
|
let title = activeArticle.title.trim().length === 0
|
||||||
|
? <small>(Untitled)</small>
|
||||||
|
: activeArticle.title
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='ArticleDetail idle'>
|
<div className='ArticleDetail idle'>
|
||||||
{this.state.openDeleteConfirmMenu
|
{this.state.openDeleteConfirmMenu
|
||||||
@@ -214,6 +239,15 @@ export default class ArticleDetail extends React.Component {
|
|||||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
|
<div className='tags'><i className='fa fa-fw fa-tags'/>{tags}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='right'>
|
<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'>
|
<button onClick={e => this.handleEditButtonClick(e)} className='editBtn'>
|
||||||
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Edit (e)</span>
|
<i className='fa fa-fw fa-edit'/><span className='tooltip'>Edit (e)</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -232,7 +266,7 @@ export default class ArticleDetail extends React.Component {
|
|||||||
<div className='detailPanel'>
|
<div className='detailPanel'>
|
||||||
<div className='header'>
|
<div className='header'>
|
||||||
<ModeIcon className='mode' mode={activeArticle.mode}/>
|
<ModeIcon className='mode' mode={activeArticle.mode}/>
|
||||||
<div className='title'>{activeArticle.title}</div>
|
<div className='title'>{title}</div>
|
||||||
</div>
|
</div>
|
||||||
{activeArticle.mode === 'markdown'
|
{activeArticle.mode === 'markdown'
|
||||||
? <MarkdownPreview content={activeArticle.content}/>
|
? <MarkdownPreview content={activeArticle.content}/>
|
||||||
@@ -263,13 +297,17 @@ export default class ArticleDetail extends React.Component {
|
|||||||
|
|
||||||
dispatch(unlockStatus())
|
dispatch(unlockStatus())
|
||||||
|
|
||||||
delete newArticle.status
|
newArticle.status = null
|
||||||
newArticle.updatedAt = new Date()
|
newArticle.updatedAt = new Date()
|
||||||
|
newArticle.title = newArticle.title.trim()
|
||||||
if (newArticle.createdAt == null) {
|
if (newArticle.createdAt == null) {
|
||||||
newArticle.createdAt = new Date()
|
newArticle.createdAt = new Date()
|
||||||
activityRecord.emit('ARTICLE_CREATE')
|
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 {
|
} else {
|
||||||
activityRecord.emit('ARTICLE_UPDATE')
|
activityRecord.emit('ARTICLE_UPDATE', {mode: newArticle.mode})
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(updateArticle(newArticle))
|
dispatch(updateArticle(newArticle))
|
||||||
@@ -408,7 +446,36 @@ export default class ArticleDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTogglePreviewButtonClick (e) {
|
handleTogglePreviewButtonClick (e) {
|
||||||
this.setState({previewMode: !this.state.previewMode})
|
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) {
|
handleTitleKeyDown (e) {
|
||||||
@@ -453,18 +520,39 @@ export default class ArticleDetail extends React.Component {
|
|||||||
<div className='right'>
|
<div className='right'>
|
||||||
{
|
{
|
||||||
this.state.article.mode === 'markdown'
|
this.state.article.mode === 'markdown'
|
||||||
? (<button className='preview' onClick={e => this.handleTogglePreviewButtonClick(e)}>{!this.state.previewMode ? 'Preview' : 'Edit'}</button>)
|
? (<button className='preview' onClick={e => this.handleTogglePreviewButtonClick(e)}>
|
||||||
|
{
|
||||||
|
!this.state.previewMode
|
||||||
|
? 'Preview'
|
||||||
|
: 'Edit'
|
||||||
|
}
|
||||||
|
<span className='tooltip'>{process.platform === 'darwin' ? '⌘' : '^'} + p</span>
|
||||||
|
</button>)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
<button onClick={e => this.handleCancelButtonClick(e)}>Cancel</button>
|
<button onClick={e => this.handleCancelButtonClick(e)} className='cancelBtn'>
|
||||||
<button onClick={e => this.handleSaveButtonClick(e)} className='primary'>Save</button>
|
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>
|
</div>
|
||||||
<div className='detailBody'>
|
<div className='detailBody'>
|
||||||
<div className='detailPanel'>
|
<div className='detailPanel'>
|
||||||
<div className='header'>
|
<div className='header'>
|
||||||
<div className='title'>
|
<div className='title'>
|
||||||
<input onKeyDown={e => this.handleTitleKeyDown(e)} placeholder='Title' ref='title' value={this.state.article.title} onChange={e => this.handleTitleChange(e)}/>
|
<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>
|
</div>
|
||||||
<ModeSelect
|
<ModeSelect
|
||||||
ref='mode'
|
ref='mode'
|
||||||
@@ -478,7 +566,7 @@ export default class ArticleDetail extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{this.state.previewMode
|
{this.state.previewMode
|
||||||
? <MarkdownPreview content={this.state.article.content}/>
|
? <MarkdownPreview ref='preview' content={this.state.article.content}/>
|
||||||
: (<CodeEditor
|
: (<CodeEditor
|
||||||
ref='code'
|
ref='code'
|
||||||
onChange={(e, value) => this.handleContentChange(e, value)}
|
onChange={(e, value) => this.handleContentChange(e, value)}
|
||||||
@@ -512,7 +600,9 @@ export default class ArticleDetail extends React.Component {
|
|||||||
ArticleDetail.propTypes = {
|
ArticleDetail.propTypes = {
|
||||||
status: PropTypes.shape(),
|
status: PropTypes.shape(),
|
||||||
activeArticle: PropTypes.shape(),
|
activeArticle: PropTypes.shape(),
|
||||||
activeUser: PropTypes.shape(),
|
user: PropTypes.shape(),
|
||||||
|
folders: PropTypes.array,
|
||||||
|
tags: PropTypes.array,
|
||||||
dispatch: PropTypes.func
|
dispatch: PropTypes.func
|
||||||
}
|
}
|
||||||
ArticleDetail.prototype.linkState = linkState
|
ArticleDetail.prototype.linkState = linkState
|
||||||
@@ -80,6 +80,12 @@ export default class ArticleList extends React.Component {
|
|||||||
: (<span>Not tagged yet</span>)
|
: (<span>Not tagged yet</span>)
|
||||||
let folder = _.findWhere(folders, {key: article.FolderKey})
|
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 (
|
return (
|
||||||
<div key={'article-' + article.key}>
|
<div key={'article-' + article.key}>
|
||||||
<div onClick={e => this.handleArticleClick(article)(e)} className={'articleItem' + (activeArticle.key === article.key ? ' active' : '')}>
|
<div onClick={e => this.handleArticleClick(article)(e)} className={'articleItem' + (activeArticle.key === article.key ? ' active' : '')}>
|
||||||
@@ -91,7 +97,7 @@ export default class ArticleList extends React.Component {
|
|||||||
<span className='updatedAt'>{article.status != null ? article.status : moment(article.updatedAt).fromNow()}</span>
|
<span className='updatedAt'>{article.status != null ? article.status : moment(article.updatedAt).fromNow()}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='middle'>
|
<div className='middle'>
|
||||||
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{article.status !== NEW ? article.title : '(New article)'}</div>
|
<ModeIcon className='mode' mode={article.mode}/> <div className='title'>{title}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='bottom'>
|
<div className='bottom'>
|
||||||
<div className='tags'><i className='fa fa-fw fa-tags'/>{tagElements}</div>
|
<div className='tags'><i className='fa fa-fw fa-tags'/>{tagElements}</div>
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
import { findWhere } from 'lodash'
|
import { findWhere } from 'lodash'
|
||||||
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, EDIT_MODE } from 'boost/actions'
|
import { setSearchFilter, switchFolder, switchMode, switchArticle, updateArticle, clearNewArticle, EDIT_MODE } from 'boost/actions'
|
||||||
import { openModal } from 'boost/modal'
|
import { openModal } from 'boost/modal'
|
||||||
import FolderMark from 'boost/components/FolderMark'
|
import FolderMark from 'boost/components/FolderMark'
|
||||||
import Preferences from 'boost/components/modal/Preferences'
|
import Preferences from 'boost/components/modal/Preferences'
|
||||||
import CreateNewFolder from 'boost/components/modal/CreateNewFolder'
|
import CreateNewFolder from 'boost/components/modal/CreateNewFolder'
|
||||||
import keygen from 'boost/keygen'
|
import keygen from 'boost/keygen'
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const remote = electron.remote
|
|
||||||
let userName = remote.getGlobal('process').env.USER
|
|
||||||
|
|
||||||
const BRAND_COLOR = '#18AF90'
|
const BRAND_COLOR = '#18AF90'
|
||||||
|
|
||||||
const preferenceTutorialElement = (
|
const preferenceTutorialElement = (
|
||||||
@@ -31,7 +27,7 @@ c-4,0-7.9,0-11.9-0.1C164,294,164,297,165.9,297L165.9,297z'/>
|
|||||||
const newPostTutorialElement = (
|
const newPostTutorialElement = (
|
||||||
<svg width='900' height='900' className='tutorial'>
|
<svg width='900' height='900' className='tutorial'>
|
||||||
<text x='290' y='155' fill={BRAND_COLOR} fontSize='24'>Create a new post!!</text>
|
<text x='290' y='155' fill={BRAND_COLOR} fontSize='24'>Create a new post!!</text>
|
||||||
<text x='300' y='180' fill={BRAND_COLOR} fontSize='16'>press `⌘ + Enter` or `a`</text>
|
<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'>
|
<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
|
<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
|
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
|
||||||
@@ -85,6 +81,7 @@ export default class ArticleNavigator extends React.Component {
|
|||||||
status: 'NEW'
|
status: 'NEW'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatch(clearNewArticle())
|
||||||
dispatch(updateArticle(newArticle))
|
dispatch(updateArticle(newArticle))
|
||||||
dispatch(switchArticle(newArticle.key, true))
|
dispatch(switchArticle(newArticle.key, true))
|
||||||
dispatch(switchMode(EDIT_MODE))
|
dispatch(switchMode(EDIT_MODE))
|
||||||
@@ -108,7 +105,7 @@ export default class ArticleNavigator extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { status, folders, allArticles } = this.props
|
let { status, user, folders, allArticles } = this.props
|
||||||
let { targetFolders } = status
|
let { targetFolders } = status
|
||||||
if (targetFolders == null) targetFolders = []
|
if (targetFolders == null) targetFolders = []
|
||||||
|
|
||||||
@@ -126,7 +123,7 @@ export default class ArticleNavigator extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className='ArticleNavigator'>
|
<div className='ArticleNavigator'>
|
||||||
<div className='userInfo'>
|
<div className='userInfo'>
|
||||||
<div className='userProfileName'>{userName}</div>
|
<div className='userProfileName'>{user.name}</div>
|
||||||
<div className='userName'>localStorage</div>
|
<div className='userName'>localStorage</div>
|
||||||
<button onClick={e => this.handlePreferencesButtonClick(e)} className='settingBtn'>
|
<button onClick={e => this.handlePreferencesButtonClick(e)} className='settingBtn'>
|
||||||
<i className='fa fa-fw fa-chevron-down'/>
|
<i className='fa fa-fw fa-chevron-down'/>
|
||||||
@@ -140,7 +137,7 @@ export default class ArticleNavigator extends React.Component {
|
|||||||
<div className='controlSection'>
|
<div className='controlSection'>
|
||||||
<button onClick={e => this.handleNewPostButtonClick(e)} className='newPostBtn'>
|
<button onClick={e => this.handleNewPostButtonClick(e)} className='newPostBtn'>
|
||||||
New Post
|
New Post
|
||||||
<span className='tooltip'>Create a new Post (⌘ + Enter or a)</span>
|
<span className='tooltip'>Create a new Post ({process.platform === 'darwin' ? '⌘' : '^'} + Enter or a)</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{status.isTutorialOpen ? newPostTutorialElement : null}
|
{status.isTutorialOpen ? newPostTutorialElement : null}
|
||||||
|
|||||||
@@ -35,18 +35,33 @@ export default class ArticleTopBar extends React.Component {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isTooltipHidden: true
|
isTooltipHidden: true,
|
||||||
|
isLinksDropdownOpen: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.searchInput = ReactDOM.findDOMNode(this.refs.searchInput)
|
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 () {
|
componentWillUnmount () {
|
||||||
this.searchInput.removeEventListener('keydown', this.showTooltip)
|
document.removeEventListener('click', this.hideLinksDropdown)
|
||||||
this.searchInput.removeEventListener('focus', this.showTooltip)
|
this.linksButton.removeEventListener('click', this.showLinksDropdown())
|
||||||
this.searchInput.removeEventListener('blur', this.showTooltip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTooltipRequest (e) {
|
handleTooltipRequest (e) {
|
||||||
@@ -118,8 +133,11 @@ export default class ArticleTopBar extends React.Component {
|
|||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
|
<div className={'tooltip' + (this.state.isTooltipHidden ? ' hide' : '')}>
|
||||||
- Search by tag : #{'{string}'}<br/>
|
<ul>
|
||||||
- Search by folder : /{'{folder_name}'}
|
<li>- Search by tag : #{'{string}'}</li>
|
||||||
|
<li>- Search by folder : /{'{folder_name}'}</li>
|
||||||
|
<li><small>exact match : //{'{folder_name}'}</small></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -129,10 +147,23 @@ export default class ArticleTopBar extends React.Component {
|
|||||||
<div className='right'>
|
<div className='right'>
|
||||||
<button onClick={e => this.handleTutorialButtonClick(e)}>?<span className='tooltip'>How to use</span>
|
<button onClick={e => this.handleTutorialButtonClick(e)}>?<span className='tooltip'>How to use</span>
|
||||||
</button>
|
</button>
|
||||||
<ExternalLink className='logo' href='http://b00st.io'>
|
<a ref='links' className='linksBtn' href>
|
||||||
<img src='../../resources/favicon-230x230.png' width='44' height='44'/>
|
<img src='../../resources/app.png' width='44' height='44'/>
|
||||||
<span className='tooltip'>Boost official page</span>
|
</a>
|
||||||
</ExternalLink>
|
{
|
||||||
|
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>
|
</div>
|
||||||
|
|
||||||
{status.isTutorialOpen ? (
|
{status.isTutorialOpen ? (
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ const electron = require('electron')
|
|||||||
const ipc = electron.ipcRenderer
|
const ipc = electron.ipcRenderer
|
||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
|
|
||||||
var ContactModal = require('boost/components/modal/ContactModal')
|
|
||||||
|
|
||||||
export default class MainContainer extends React.Component {
|
export default class MainContainer extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -20,20 +18,12 @@ export default class MainContainer extends React.Component {
|
|||||||
ipc.send('update-app', 'Deal with it.')
|
ipc.send('update-app', 'Deal with it.')
|
||||||
}
|
}
|
||||||
|
|
||||||
openContactModal () {
|
|
||||||
this.openModal(ContactModal)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
return (
|
return (
|
||||||
<div className='Main'>
|
<div className='Main'>
|
||||||
{this.state.updateAvailable ? (
|
{this.state.updateAvailable ? (
|
||||||
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
|
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
|
||||||
) : null}
|
) : null}
|
||||||
{/* <button onClick={this.openContactModal} className='contactButton'>
|
|
||||||
<i className='fa fa-paper-plane-o'/>
|
|
||||||
<div className='tooltip'>Contact us</div>
|
|
||||||
</button> */}
|
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
|
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
|
||||||
<link rel="stylesheet" href="../../node_modules/highlight.js/styles/xcode.css">
|
<link rel="stylesheet" href="../../node_modules/highlight.js/styles/xcode.css">
|
||||||
<link rel="shortcut icon" href="favicon.ico">
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
|
<title>Boostnote</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="loadingCover">
|
<div id="loadingCover">
|
||||||
<img src="../../resources/favicon-230x230.png">
|
<img src="../../resources/app.png">
|
||||||
<div class='message'>Loading...</div>
|
<div class='message'>Loading...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -57,11 +58,11 @@
|
|||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
electron.webFrame.setZoomLevelLimits(1, 1)
|
||||||
var version = electron.remote.app.getVersion()
|
var version = electron.remote.app.getVersion()
|
||||||
document.title = 'Boost' + ((version == null || version.length === 0) ? ' DEV' : '')
|
const _ = require('lodash')
|
||||||
var scriptUrl = process.env.BOOST_ENV === 'development'
|
var scriptUrl = _.find(electron.remote.process.argv, a => a === '--hot')
|
||||||
? 'http://localhost:8080/assets/main.js'
|
? 'http://localhost:8080/assets/main.js'
|
||||||
: '../../compiled/main.js'
|
: '../../compiled/main.js'
|
||||||
var scriptEl=document.createElement('script')
|
var scriptEl = document.createElement('script')
|
||||||
scriptEl.setAttribute("type","text/javascript")
|
scriptEl.setAttribute("type","text/javascript")
|
||||||
scriptEl.setAttribute("src", scriptUrl)
|
scriptEl.setAttribute("src", scriptUrl)
|
||||||
document.getElementsByTagName("head")[0].appendChild(scriptEl)
|
document.getElementsByTagName("head")[0].appendChild(scriptEl)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from 'react'
|
|
||||||
import { Provider } from 'react-redux'
|
import { Provider } from 'react-redux'
|
||||||
// import { updateUser } from 'boost/actions'
|
// import { updateUser } from 'boost/actions'
|
||||||
import { Router, Route, IndexRoute } from 'react-router'
|
import { Router, Route, IndexRoute } from 'react-router'
|
||||||
@@ -6,6 +5,7 @@ import MainPage from './MainPage'
|
|||||||
import HomePage from './HomePage'
|
import HomePage from './HomePage'
|
||||||
// import auth from 'boost/auth'
|
// import auth from 'boost/auth'
|
||||||
import store from 'boost/store'
|
import store from 'boost/store'
|
||||||
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
require('../styles/main/index.styl')
|
require('../styles/main/index.styl')
|
||||||
import { openModal } from 'boost/modal'
|
import { openModal } from 'boost/modal'
|
||||||
@@ -13,14 +13,20 @@ import Tutorial from 'boost/components/modal/Tutorial'
|
|||||||
import activityRecord from 'boost/activityRecord'
|
import activityRecord from 'boost/activityRecord'
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const ipc = electron.ipcRenderer
|
const ipc = electron.ipcRenderer
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
activityRecord.init()
|
activityRecord.init()
|
||||||
window.addEventListener('online', function () {
|
window.addEventListener('online', function () {
|
||||||
ipc.send('check-update', 'check-update')
|
ipc.send('check-update', 'check-update')
|
||||||
})
|
})
|
||||||
|
|
||||||
function notify (...args) {
|
function notify (title, options) {
|
||||||
return new window.Notification(...args)
|
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) {
|
ipc.on('notify', function (e, payload) {
|
||||||
@@ -29,6 +35,13 @@ ipc.on('notify', function (e, payload) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipc.on('copy-finder', function () {
|
||||||
|
activityRecord.emit('FINDER_COPY')
|
||||||
|
})
|
||||||
|
ipc.on('open-finder', function () {
|
||||||
|
activityRecord.emit('FINDER_OPEN')
|
||||||
|
})
|
||||||
|
|
||||||
let routes = (
|
let routes = (
|
||||||
<Route path='/' component={MainPage}>
|
<Route path='/' component={MainPage}>
|
||||||
<IndexRoute name='home' component={HomePage}/>
|
<IndexRoute name='home' component={HomePage}/>
|
||||||
|
|||||||
@@ -7,13 +7,17 @@ global-reset()
|
|||||||
iptBgColor = #E6E6E6
|
iptBgColor = #E6E6E6
|
||||||
iptFocusBorderColor = #369DCD
|
iptFocusBorderColor = #369DCD
|
||||||
|
|
||||||
|
DEFAULT_FONTS = 'Lato', 'MS Gothic', 'Malgun Gothic', 'Sans-serif'
|
||||||
|
|
||||||
body
|
body
|
||||||
font-family "Lato"
|
font-family DEFAULT_FONTS
|
||||||
color textColor
|
color textColor
|
||||||
font-size fontSize
|
font-size fontSize
|
||||||
width 100%
|
width 100%
|
||||||
height 100%
|
height 100%
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
button, input
|
||||||
|
font-family "Lato"
|
||||||
|
|
||||||
.Finder
|
.Finder
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ iptFocusBorderColor = #369DCD
|
|||||||
&:hover
|
&:hover
|
||||||
background-color darken(white, 10%)
|
background-color darken(white, 10%)
|
||||||
.right
|
.right
|
||||||
|
z-index 30
|
||||||
button
|
button
|
||||||
cursor pointer
|
cursor pointer
|
||||||
height 33px
|
height 33px
|
||||||
@@ -169,14 +170,26 @@ iptFocusBorderColor = #369DCD
|
|||||||
background-color darken(white, 5%)
|
background-color darken(white, 5%)
|
||||||
border solid 1px borderColor
|
border solid 1px borderColor
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
|
&>.tooltip
|
||||||
|
tooltip()
|
||||||
|
top 105px
|
||||||
&.preview
|
&.preview
|
||||||
width inherit
|
width inherit
|
||||||
|
.tooltip
|
||||||
|
margin-left -55px
|
||||||
&:hover
|
&:hover
|
||||||
background-color white
|
background-color white
|
||||||
&.primary
|
.tooltip
|
||||||
|
opacity 1
|
||||||
|
&.cancelBtn
|
||||||
|
.tooltip
|
||||||
|
margin-left -25px
|
||||||
|
&.saveBtn
|
||||||
border none
|
border none
|
||||||
background-color brandColor
|
background-color brandColor
|
||||||
color white
|
color white
|
||||||
|
.tooltip
|
||||||
|
margin-left -45px
|
||||||
&:hover
|
&:hover
|
||||||
color white
|
color white
|
||||||
background-color lighten(brandColor, 10%)
|
background-color lighten(brandColor, 10%)
|
||||||
@@ -284,7 +297,87 @@ iptFocusBorderColor = #369DCD
|
|||||||
color noTagsColor
|
color noTagsColor
|
||||||
.right
|
.right
|
||||||
z-index 30
|
z-index 30
|
||||||
button
|
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
|
border-radius 16.5px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
height 33px
|
height 33px
|
||||||
@@ -323,7 +416,8 @@ iptFocusBorderColor = #369DCD
|
|||||||
right 15px
|
right 15px
|
||||||
font-size 24px
|
font-size 24px
|
||||||
line-height 60px
|
line-height 60px
|
||||||
|
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
overflow-x auto
|
overflow-x auto
|
||||||
overflow-y hidden
|
overflow-y hidden
|
||||||
|
small
|
||||||
|
color #AAA
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ articleItemColor = #777
|
|||||||
left 19px
|
left 19px
|
||||||
right 0
|
right 0
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
|
small
|
||||||
|
color #AAA
|
||||||
.bottom
|
.bottom
|
||||||
padding 5px 0
|
padding 5px 0
|
||||||
overflow-x auto
|
overflow-x auto
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ articleCount = #999
|
|||||||
.userProfileName
|
.userProfileName
|
||||||
color brandColor
|
color brandColor
|
||||||
font-size 28px
|
font-size 28px
|
||||||
padding 6px 0 0 10px
|
padding 6px 37px 0 10px
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
text-overflow ellipsis
|
text-overflow ellipsis
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
|||||||
@@ -62,6 +62,13 @@ infoBtnActiveBgColor = #3A3A3A
|
|||||||
opacity 1
|
opacity 1
|
||||||
&.hide
|
&.hide
|
||||||
opacity 0
|
opacity 0
|
||||||
|
ul
|
||||||
|
li:last-child
|
||||||
|
line-height 10px
|
||||||
|
margin-bottom 3px
|
||||||
|
small
|
||||||
|
font-size 10px
|
||||||
|
margin-left 15px
|
||||||
input
|
input
|
||||||
absolute top left
|
absolute top left
|
||||||
width 350px
|
width 350px
|
||||||
@@ -140,17 +147,33 @@ infoBtnActiveBgColor = #3A3A3A
|
|||||||
.tooltip
|
.tooltip
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
&>.logo
|
&>.linksBtn
|
||||||
display block
|
display block
|
||||||
position absolute
|
position absolute
|
||||||
top 8px
|
top 8px
|
||||||
right 15px
|
right 15px
|
||||||
opacity 0.7
|
opacity 0.7
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
margin-top 44px
|
|
||||||
margin-left -120px
|
|
||||||
&:hover
|
&:hover
|
||||||
opacity 1
|
opacity 1
|
||||||
.tooltip
|
.tooltip
|
||||||
opacity 1
|
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%)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ global-reset()
|
|||||||
@import './containers/*'
|
@import './containers/*'
|
||||||
@import './HomeContainer'
|
@import './HomeContainer'
|
||||||
|
|
||||||
|
DEFAULT_FONTS = 'Lato', 'MS Gothic', 'Malgun Gothic', 'Sans-serif'
|
||||||
|
|
||||||
*
|
*
|
||||||
-webkit-app-region no-drag
|
-webkit-app-region no-drag
|
||||||
-webkit-user-select none
|
-webkit-user-select none
|
||||||
@@ -17,13 +19,13 @@ html, body
|
|||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
body
|
body
|
||||||
font-family "Lato"
|
font-family DEFAULT_FONTS
|
||||||
color textColor
|
color textColor
|
||||||
font-size fontSize
|
font-size fontSize
|
||||||
font-weight 400
|
font-weight 400
|
||||||
|
|
||||||
button, input, select
|
button, input, select
|
||||||
font-family "Lato"
|
font-family DEFAULT_FONTS
|
||||||
|
|
||||||
div, span, a, button, input, textarea
|
div, span, a, button, input, textarea
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
|
|||||||
@@ -1,36 +1,38 @@
|
|||||||
marked()
|
marked()
|
||||||
h1, h2, h3, h4, h5, h6, p
|
|
||||||
&:first-child
|
|
||||||
margin-top 0
|
|
||||||
hr
|
hr
|
||||||
border-top none
|
border-top none
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
margin 15px 0
|
margin 15px 0
|
||||||
|
h1, h2, h3, h4, h5, h6
|
||||||
|
margin 0 0 15px
|
||||||
|
font-weight 600
|
||||||
|
* + h1, * + h2, * + h3, * + h4, * + h5, * + h6
|
||||||
|
margin-top 25px
|
||||||
h1
|
h1
|
||||||
font-size 2em
|
font-size 2em
|
||||||
border-bottom solid 2px borderColor
|
border-bottom solid 2px borderColor
|
||||||
margin 0.33em auto 0.67em
|
line-height 2.333em
|
||||||
h2
|
h2
|
||||||
font-size 1.5em
|
font-size 1.66em
|
||||||
margin 0.42em auto 0.83em
|
line-height 2.07em
|
||||||
h3
|
h3
|
||||||
font-size 1.17em
|
font-size 1.33em
|
||||||
margin 0.5em auto 1em
|
line-height 1.6625em
|
||||||
h4
|
h4
|
||||||
font-size 1em
|
font-size 1.15em
|
||||||
margin 0.67em auto 1.33em
|
line-height 1.4375em
|
||||||
h5
|
h5
|
||||||
font-size 0.83em
|
font-size 1em
|
||||||
margin 0.84em auto 1.67em
|
line-height 1.25em
|
||||||
h6
|
h6
|
||||||
font-size 0.67em
|
font-size 0.8em
|
||||||
margin 1.16em auto 2.33em
|
line-height 1em
|
||||||
h1, h2, h3, h4, h5, h6
|
|
||||||
font-weight 700
|
* + p, * + blockquote, * + ul, * + ol, * + pre
|
||||||
line-height 1.8em
|
margin-top 15px
|
||||||
p
|
p
|
||||||
line-height 1.8em
|
line-height 1.9em
|
||||||
margin 15px 0 25px
|
margin 0 0 15px
|
||||||
img
|
img
|
||||||
max-width 100%
|
max-width 100%
|
||||||
strong
|
strong
|
||||||
@@ -41,15 +43,17 @@ marked()
|
|||||||
text-decoration line-through
|
text-decoration line-through
|
||||||
blockquote
|
blockquote
|
||||||
border-left solid 4px brandBorderColor
|
border-left solid 4px brandBorderColor
|
||||||
margin 15px 0 25px
|
margin 0 0 15px
|
||||||
padding 0 25px
|
padding 0 25px
|
||||||
ul
|
ul
|
||||||
list-style-type disc
|
list-style-type disc
|
||||||
padding-left 35px
|
padding-left 35px
|
||||||
margin-bottom 35px
|
margin-bottom 15px
|
||||||
li
|
li
|
||||||
display list-item
|
display list-item
|
||||||
line-height 1.8em
|
line-height 1.8em
|
||||||
|
&>li>ul, &>li>ol
|
||||||
|
margin 0
|
||||||
&>li>ul
|
&>li>ul
|
||||||
list-style-type circle
|
list-style-type circle
|
||||||
&>li>ul
|
&>li>ul
|
||||||
@@ -57,10 +61,12 @@ marked()
|
|||||||
ol
|
ol
|
||||||
list-style-type decimal
|
list-style-type decimal
|
||||||
padding-left 35px
|
padding-left 35px
|
||||||
margin-bottom 35px
|
margin-bottom 15px
|
||||||
li
|
li
|
||||||
display list-item
|
display list-item
|
||||||
line-height 1.8em
|
line-height 1.8em
|
||||||
|
&>li>ul, &>li>ol
|
||||||
|
margin 0
|
||||||
code
|
code
|
||||||
font-family Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace;
|
font-family Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace;
|
||||||
padding 2px 4px
|
padding 2px 4px
|
||||||
@@ -70,15 +76,19 @@ marked()
|
|||||||
color black
|
color black
|
||||||
text-decoration none
|
text-decoration none
|
||||||
background-color #F6F6F6
|
background-color #F6F6F6
|
||||||
|
margin-right 2px
|
||||||
|
* + code
|
||||||
|
margin-left 2px
|
||||||
pre
|
pre
|
||||||
padding 5px
|
padding 5px
|
||||||
border solid 1px borderColor
|
border solid 1px borderColor
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
overflow-x auto
|
overflow-x auto
|
||||||
margin 15px 0 25px
|
margin 0 0 15px
|
||||||
background-color #F6F6F6
|
background-color #F6F6F6
|
||||||
line-height 1.35em
|
line-height 1.35em
|
||||||
&>code
|
&>code
|
||||||
|
margin 0
|
||||||
padding 0
|
padding 0
|
||||||
border none
|
border none
|
||||||
border-radius 0
|
border-radius 0
|
||||||
|
|||||||
71
finder.js
@@ -1,72 +1,17 @@
|
|||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const app = electron.app
|
const app = electron.app
|
||||||
const Tray = electron.Tray
|
|
||||||
const Menu = electron.Menu
|
const Menu = electron.Menu
|
||||||
const MenuItem = electron.MenuItem
|
|
||||||
|
|
||||||
process.stdin.setEncoding('utf8')
|
var finderWindow = null
|
||||||
|
|
||||||
console.log = function () {
|
|
||||||
process.stdout.write(JSON.stringify({
|
|
||||||
type: 'log',
|
|
||||||
data: JSON.stringify(Array.prototype.slice.call(arguments).join(' '))
|
|
||||||
}), 'utf-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
function emit (type, data) {
|
|
||||||
process.stdout.write(JSON.stringify({
|
|
||||||
type: type,
|
|
||||||
data: JSON.stringify(data)
|
|
||||||
}), 'utf-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
var finderWindow
|
|
||||||
app.on('ready', function () {
|
app.on('ready', function () {
|
||||||
app.dock.hide()
|
if (process.platform === 'darwin') {
|
||||||
var appIcon = new Tray(__dirname + '/resources/tray-icon.png')
|
app.dock.hide()
|
||||||
appIcon.setToolTip('Boost')
|
}
|
||||||
|
|
||||||
|
var template = require('./atom-lib/menu-template')
|
||||||
|
var menu = Menu.buildFromTemplate(template)
|
||||||
|
Menu.setApplicationMenu(menu)
|
||||||
|
|
||||||
finderWindow = require('./atom-lib/finder-window')
|
finderWindow = require('./atom-lib/finder-window')
|
||||||
finderWindow.webContents.on('did-finish-load', function () {
|
|
||||||
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',
|
|
||||||
click: function () {
|
|
||||||
finderWindow.show()
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
trayMenu.append(new MenuItem({
|
|
||||||
label: 'Quit',
|
|
||||||
click: function () {
|
|
||||||
emit('quit-app')
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
appIcon.setContextMenu(trayMenu)
|
|
||||||
|
|
||||||
process.stdin.on('data', function (payload) {
|
|
||||||
try {
|
|
||||||
payload = JSON.parse(payload)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Not parsable payload : ', payload)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.log('from main >> ', payload.type)
|
|
||||||
switch (payload.type) {
|
|
||||||
case 'open-finder':
|
|
||||||
finderWindow.show()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
global.hideFinder = function () {
|
|
||||||
Menu.sendActionToFirstResponder('hide:')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
205
gruntfile.js
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const ChildProcess = require('child_process')
|
||||||
|
const packager = require('electron-packager')
|
||||||
|
const fs = require('fs')
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
const appdmg = require('appdmg')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function (grunt) {
|
||||||
|
if (process.platform === 'win32') auth_code = grunt.file.readJSON('secret/auth_code.json')
|
||||||
|
|
||||||
|
var initConfig = {
|
||||||
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
|
'create-windows-installer': {
|
||||||
|
x64: {
|
||||||
|
appDirectory: path.join(__dirname, 'dist', 'Boostnote-win32-x64'),
|
||||||
|
outputDirectory: path.join(__dirname, 'dist'),
|
||||||
|
authors: 'MAISIN&CO., Inc.',
|
||||||
|
exe: 'Boostnote.exe',
|
||||||
|
loadingGif: path.join(__dirname, 'resources/boostnote-install.gif'),
|
||||||
|
iconUrl: path.join(__dirname, 'resources/app.ico'),
|
||||||
|
setupIcon: path.join(__dirname, 'resources/dmg.ico'),
|
||||||
|
certificateFile: path.join(__dirname, 'secret', 'authenticode_cer.p12'),
|
||||||
|
certificatePassword: auth_code.win_cert_pw,
|
||||||
|
noMsi: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grunt.initConfig(initConfig)
|
||||||
|
|
||||||
|
grunt.loadNpmTasks('grunt-electron-installer')
|
||||||
|
|
||||||
|
grunt.registerTask('compile', function () {
|
||||||
|
var done = this.async()
|
||||||
|
var execPath = path.join('node_modules', '.bin', 'webpack') + ' --config webpack.config.production.js'
|
||||||
|
grunt.log.writeln(execPath)
|
||||||
|
ChildProcess.exec(execPath,
|
||||||
|
{
|
||||||
|
env: Object.assign({}, process.env, {
|
||||||
|
BABEL_ENV: 'production'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
function (err, stdout, stderr) {
|
||||||
|
grunt.log.writeln(stdout)
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
grunt.log.writeln(err)
|
||||||
|
grunt.log.writeln(stderr)
|
||||||
|
done(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
grunt.registerTask('zip', function (platform) {
|
||||||
|
var done = this.async()
|
||||||
|
switch (platform) {
|
||||||
|
case 'osx':
|
||||||
|
var execPath = 'cd dist/Boostnote-darwin-x64 && zip -r -y -q ../Boostnote-mac.zip Boostnote.app'
|
||||||
|
grunt.log.writeln(execPath)
|
||||||
|
ChildProcess.exec(execPath,
|
||||||
|
function (err, stdout, stderr) {
|
||||||
|
grunt.log.writeln(stdout)
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
grunt.log.writeln(err)
|
||||||
|
grunt.log.writeln(stderr)
|
||||||
|
done(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
grunt.registerTask('pack', function (platform) {
|
||||||
|
grunt.log.writeln(path.join(__dirname, 'dist'))
|
||||||
|
var done = this.async()
|
||||||
|
var opts = {
|
||||||
|
name: 'Boostnote',
|
||||||
|
arch: 'x64',
|
||||||
|
dir: __dirname,
|
||||||
|
version: grunt.config.get('pkg.config.electron-version'),
|
||||||
|
'app-version': grunt.config.get('pkg.version'),
|
||||||
|
'app-bundle-id': 'com.maisin.boost',
|
||||||
|
asar: true,
|
||||||
|
prune: true,
|
||||||
|
overwrite: true,
|
||||||
|
out: path.join(__dirname, 'dist'),
|
||||||
|
ignore: /submodules\/ace\/(?!src-min)|submodules\/ace\/(?=src-min-noconflict)|node_modules\/devicon\/icons|dist|.env/
|
||||||
|
}
|
||||||
|
switch (platform) {
|
||||||
|
case 'win':
|
||||||
|
Object.assign(opts, {
|
||||||
|
platform: 'win32',
|
||||||
|
icon: path.join(__dirname, 'resources/app.ico'),
|
||||||
|
'version-string': {
|
||||||
|
CompanyName: 'MAISIN&CO., Inc.',
|
||||||
|
LegalCopyright: '© 2015 MAISIN&CO., Inc. All rights reserved.',
|
||||||
|
FileDescription: 'Boostnote',
|
||||||
|
OriginalFilename: 'Boostnote',
|
||||||
|
FileVersion: grunt.config.get('pkg.version'),
|
||||||
|
ProductVersion: grunt.config.get('pkg.version'),
|
||||||
|
ProductName: 'Boostnote',
|
||||||
|
InternalName: 'Boostnote'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
packager(opts, function (err, appPath) {
|
||||||
|
if (err) {
|
||||||
|
grunt.log.writeln(err)
|
||||||
|
done(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
break
|
||||||
|
case 'osx':
|
||||||
|
Object.assign(opts, {
|
||||||
|
platform: 'darwin',
|
||||||
|
icon: path.join(__dirname, 'resources/app.icns'),
|
||||||
|
'app-category-type': 'public.app-category.developer-tools'
|
||||||
|
})
|
||||||
|
packager(opts, function (err, appPath) {
|
||||||
|
if (err) {
|
||||||
|
grunt.log.writeln(err)
|
||||||
|
done(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
grunt.registerTask('codesign', function (platform) {
|
||||||
|
var done = this.async()
|
||||||
|
if (process.platform !== 'darwin') {
|
||||||
|
done(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ChildProcess.exec('codesign --verbose --deep --force --sign \"\" dist/Boostnote-darwin-x64/Boostnote.app', function (err, stdout, stderr) {
|
||||||
|
grunt.log.writeln(stdout)
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
grunt.log.writeln(err)
|
||||||
|
grunt.log.writeln(stderr)
|
||||||
|
done(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
grunt.registerTask('create-osx-installer', function () {
|
||||||
|
var done = this.async()
|
||||||
|
|
||||||
|
var stream = appdmg({
|
||||||
|
target: 'dist/Boostnote-mac.dmg',
|
||||||
|
basepath: __dirname,
|
||||||
|
specification: {
|
||||||
|
'title': 'Boostnote',
|
||||||
|
'icon': 'resources/dmg.icns',
|
||||||
|
'background': 'resources/boostnote-install.png',
|
||||||
|
'icon-size': 80,
|
||||||
|
'contents': [
|
||||||
|
{ 'x': 448, 'y': 344, 'type': 'link', 'path': '/Applications' },
|
||||||
|
{ 'x': 192, 'y': 344, 'type': 'file', 'path': 'dist/Boostnote-darwin-x64/Boostnote.app' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stream.on('finish', function () {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
stream.on('error', function (err) {
|
||||||
|
grunt.log.writeln(err)
|
||||||
|
done(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
grunt.registerTask('build', function (platform) {
|
||||||
|
if (!platform) {
|
||||||
|
platform = process.platform === 'darwin' ? 'osx' : process.platform === 'win32' ? 'win' : null
|
||||||
|
}
|
||||||
|
switch (platform) {
|
||||||
|
case 'win':
|
||||||
|
grunt.task.run(['compile', 'pack:win', 'create-windows-installer'])
|
||||||
|
break
|
||||||
|
case 'osx':
|
||||||
|
grunt.task.run(['compile', 'pack:osx', 'codesign', 'create-osx-installer', 'zip:osx'])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Default task(s).
|
||||||
|
grunt.registerTask('default', ['build'])
|
||||||
|
}
|
||||||
60
hotkey.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
const electron = require('electron')
|
||||||
|
const app = electron.app
|
||||||
|
const ipc = electron.ipcMain
|
||||||
|
const globalShortcut = electron.globalShortcut
|
||||||
|
const jetpack = require('fs-jetpack')
|
||||||
|
const mainWindow = require('./atom-lib/main-window')
|
||||||
|
const nodeIpc = require('@rokt33r/node-ipc')
|
||||||
|
|
||||||
|
var userDataPath = app.getPath('userData')
|
||||||
|
if (!jetpack.cwd(userDataPath).exists('keymap.json')) {
|
||||||
|
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
global.keymap = JSON.parse(jetpack.cwd(userDataPath).read('keymap.json', 'utf-8'))
|
||||||
|
} catch (err) {
|
||||||
|
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
|
||||||
|
global.keymap = {}
|
||||||
|
}
|
||||||
|
if (global.keymap.toggleFinder == null) global.keymap.toggleFinder = 'ctrl+tab+shift'
|
||||||
|
var toggleFinderKey = global.keymap.toggleFinder
|
||||||
|
|
||||||
|
try {
|
||||||
|
globalShortcut.register(toggleFinderKey, function () {
|
||||||
|
emitToFinder('open-finder')
|
||||||
|
mainWindow.webContents.send('open-finder', {})
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc.on('hotkeyUpdated', function (event, newKeymap) {
|
||||||
|
console.log('got new keymap')
|
||||||
|
console.log(newKeymap)
|
||||||
|
globalShortcut.unregisterAll()
|
||||||
|
global.keymap = newKeymap
|
||||||
|
jetpack.cwd(userDataPath).file('keymap.json', {content: JSON.stringify(global.keymap)})
|
||||||
|
|
||||||
|
var toggleFinderKey = global.keymap.toggleFinder != null ? global.keymap.toggleFinder : 'ctrl+tab+shift'
|
||||||
|
try {
|
||||||
|
globalShortcut.register(toggleFinderKey, function () {
|
||||||
|
emitToFinder('open-finder')
|
||||||
|
mainWindow.webContents.send('open-finder', {})
|
||||||
|
})
|
||||||
|
mainWindow.webContents.send('APP_SETTING_DONE', {})
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
mainWindow.webContents.send('APP_SETTING_ERROR', {
|
||||||
|
message: 'Failed to apply hotkey: Invalid format'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function emitToFinder (type, data) {
|
||||||
|
var payload = {
|
||||||
|
type: type,
|
||||||
|
data: data
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeIpc.server.broadcast('message', payload)
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
// Action types
|
// Action types
|
||||||
|
export const USER_UPDATE = 'USER_UPDATE'
|
||||||
|
|
||||||
export const CLEAR_NEW_ARTICLE = 'CLEAR_NEW_ARTICLE'
|
export const CLEAR_NEW_ARTICLE = 'CLEAR_NEW_ARTICLE'
|
||||||
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
|
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
|
||||||
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
|
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
|
||||||
@@ -24,6 +26,13 @@ export const EDIT_MODE = 'EDIT_MODE'
|
|||||||
// Article status
|
// Article status
|
||||||
export const NEW = 'NEW'
|
export const NEW = 'NEW'
|
||||||
|
|
||||||
|
export function updateUser (input) {
|
||||||
|
return {
|
||||||
|
type: USER_UPDATE,
|
||||||
|
data: input
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// DB
|
// DB
|
||||||
export function clearNewArticle () {
|
export function clearNewArticle () {
|
||||||
return {
|
return {
|
||||||
@@ -137,3 +146,23 @@ export function toggleTutorial () {
|
|||||||
type: TOGGLE_TUTORIAL
|
type: TOGGLE_TUTORIAL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
updateUser,
|
||||||
|
clearNewArticle,
|
||||||
|
updateArticle,
|
||||||
|
destroyArticle,
|
||||||
|
createFolder,
|
||||||
|
updateFolder,
|
||||||
|
destroyFolder,
|
||||||
|
replaceFolder,
|
||||||
|
switchFolder,
|
||||||
|
switchMode,
|
||||||
|
switchArticle,
|
||||||
|
setSearchFilter,
|
||||||
|
setTagFilter,
|
||||||
|
clearSearch,
|
||||||
|
lockStatus,
|
||||||
|
unlockStatus,
|
||||||
|
toggleTutorial
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import keygen from 'boost/keygen'
|
|
||||||
import dataStore from 'boost/dataStore'
|
import dataStore from 'boost/dataStore'
|
||||||
import { request, WEB_URL } from 'boost/api'
|
import { request, SERVER_URL } from 'boost/api'
|
||||||
|
import clientKey from 'boost/clientKey'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const version = electron.remote.app.getVersion()
|
||||||
|
|
||||||
function isSameDate (a, b) {
|
function isSameDate (a, b) {
|
||||||
a = moment(a).utcOffset(+540).format('YYYYMMDD')
|
a = moment(a).utcOffset(+540).format('YYYYMMDD')
|
||||||
@@ -16,6 +19,7 @@ export function init () {
|
|||||||
if (records == null) {
|
if (records == null) {
|
||||||
saveAllRecords([])
|
saveAllRecords([])
|
||||||
}
|
}
|
||||||
|
emit(null)
|
||||||
|
|
||||||
postRecords()
|
postRecords()
|
||||||
if (window != null) {
|
if (window != null) {
|
||||||
@@ -24,16 +28,6 @@ export function init () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getClientKey () {
|
|
||||||
let clientKey = localStorage.getItem('clientKey')
|
|
||||||
if (!_.isString(clientKey) || clientKey.length !== 40) {
|
|
||||||
clientKey = keygen()
|
|
||||||
localStorage.setItem('clientKey', clientKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientKey
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAllRecords () {
|
export function getAllRecords () {
|
||||||
return JSON.parse(localStorage.getItem('activityRecords'))
|
return JSON.parse(localStorage.getItem('activityRecords'))
|
||||||
}
|
}
|
||||||
@@ -63,10 +57,10 @@ export function postRecords (data) {
|
|||||||
|
|
||||||
console.log('posting...', records)
|
console.log('posting...', records)
|
||||||
let input = {
|
let input = {
|
||||||
clientKey: getClientKey(),
|
clientKey: clientKey.get(),
|
||||||
records
|
records
|
||||||
}
|
}
|
||||||
return request.post(WEB_URL + 'apis/activity')
|
return request.post(SERVER_URL + 'apis/activity')
|
||||||
.send(input)
|
.send(input)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let records = getAllRecords()
|
let records = getAllRecords()
|
||||||
@@ -81,7 +75,7 @@ export function postRecords (data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emit (type, data) {
|
export function emit (type, data = {}) {
|
||||||
let records = getAllRecords()
|
let records = getAllRecords()
|
||||||
|
|
||||||
let index = _.findIndex(records, record => {
|
let index = _.findIndex(records, record => {
|
||||||
@@ -94,7 +88,6 @@ export function emit (type, data) {
|
|||||||
records.push(todayRecord)
|
records.push(todayRecord)
|
||||||
}
|
}
|
||||||
else todayRecord = records[index]
|
else todayRecord = records[index]
|
||||||
console.log(type)
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'ARTICLE_CREATE':
|
case 'ARTICLE_CREATE':
|
||||||
case 'ARTICLE_UPDATE':
|
case 'ARTICLE_UPDATE':
|
||||||
@@ -104,6 +97,8 @@ export function emit (type, data) {
|
|||||||
case 'FOLDER_DESTROY':
|
case 'FOLDER_DESTROY':
|
||||||
case 'FINDER_OPEN':
|
case 'FINDER_OPEN':
|
||||||
case 'FINDER_COPY':
|
case 'FINDER_COPY':
|
||||||
|
case 'MAIN_DETAIL_COPY':
|
||||||
|
case 'ARTICLE_SHARE':
|
||||||
todayRecord[type] = todayRecord[type] == null
|
todayRecord[type] = todayRecord[type] == null
|
||||||
? 1
|
? 1
|
||||||
: todayRecord[type] + 1
|
: todayRecord[type] + 1
|
||||||
@@ -111,9 +106,26 @@ export function emit (type, data) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Count ARTICLE_CREATE and ARTICLE_UPDATE again by syntax
|
||||||
|
if ((type === 'ARTICLE_CREATE' || type === 'ARTICLE_UPDATE') && data.mode != null) {
|
||||||
|
let recordKey = type + '_BY_SYNTAX'
|
||||||
|
if (todayRecord[recordKey] == null) todayRecord[recordKey] = {}
|
||||||
|
|
||||||
|
todayRecord[recordKey][data.mode] = todayRecord[recordKey][data.mode] == null
|
||||||
|
? 1
|
||||||
|
: todayRecord[recordKey][data.mode] + 1
|
||||||
|
}
|
||||||
|
|
||||||
let storeData = dataStore.getData()
|
let storeData = dataStore.getData()
|
||||||
todayRecord.FOLDER_COUNT = _.isArray(storeData.folders) ? storeData.folders.length : 0
|
todayRecord.FOLDER_COUNT = _.isArray(storeData.folders) ? storeData.folders.length : 0
|
||||||
todayRecord.ARTICLE_COUNT = _.isArray(storeData.articles) ? storeData.articles.length : 0
|
todayRecord.ARTICLE_COUNT = _.isArray(storeData.articles) ? storeData.articles.length : 0
|
||||||
|
todayRecord.CLIENT_VERSION = version
|
||||||
|
|
||||||
|
todayRecord.SYNTAX_COUNT = storeData.articles.reduce((sum, article) => {
|
||||||
|
if (sum[article.mode] == null) sum[article.mode] = 1
|
||||||
|
else sum[article.mode]++
|
||||||
|
return sum
|
||||||
|
}, {})
|
||||||
|
|
||||||
saveAllRecords(records)
|
saveAllRecords(records)
|
||||||
}
|
}
|
||||||
@@ -121,6 +133,5 @@ export function emit (type, data) {
|
|||||||
export default {
|
export default {
|
||||||
init,
|
init,
|
||||||
emit,
|
emit,
|
||||||
getClientKey,
|
|
||||||
postRecords
|
postRecords
|
||||||
}
|
}
|
||||||
|
|||||||
189
lib/api.js
@@ -1,191 +1,22 @@
|
|||||||
import superagent from 'superagent'
|
import superagent from 'superagent'
|
||||||
import superagentPromise from 'superagent-promise'
|
import superagentPromise from 'superagent-promise'
|
||||||
import auth from 'boost/auth'
|
// import auth from 'boost/auth'
|
||||||
|
|
||||||
export const API_URL = 'http://boost-api4.elasticbeanstalk.com/'
|
export const SERVER_URL = 'https://b00st.io/'
|
||||||
export const WEB_URL = 'https://b00st.io/'
|
// export const SERVER_URL = 'http://localhost:3333/'
|
||||||
// export const WEB_URL = 'http://localhost:3333/'
|
|
||||||
|
|
||||||
export const request = superagentPromise(superagent, Promise)
|
export const request = superagentPromise(superagent, Promise)
|
||||||
|
|
||||||
export function login (input) {
|
export function shareViaPublicURL (input) {
|
||||||
return request
|
return request
|
||||||
.post(API_URL + 'auth/login')
|
.post(SERVER_URL + 'apis/share')
|
||||||
.send(input)
|
// .set({
|
||||||
}
|
// Authorization: 'Bearer ' + auth.token()
|
||||||
|
// })
|
||||||
export function signup (input) {
|
|
||||||
return request
|
|
||||||
.post(API_URL + 'auth/register')
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateUserInfo (input) {
|
|
||||||
return request
|
|
||||||
.put(API_URL + 'auth/user')
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updatePassword (input) {
|
|
||||||
return request
|
|
||||||
.post(API_URL + 'auth/password')
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchCurrentUser () {
|
|
||||||
return request
|
|
||||||
.get(API_URL + 'auth/user')
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchArticles (userId) {
|
|
||||||
return request
|
|
||||||
.get(API_URL + 'teams/' + userId + '/articles')
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createArticle (input) {
|
|
||||||
return request
|
|
||||||
.post(API_URL + 'articles/')
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveArticle (input) {
|
|
||||||
return request
|
|
||||||
.put(API_URL + 'articles/' + input.id)
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function destroyArticle (articleId) {
|
|
||||||
return request
|
|
||||||
.del(API_URL + 'articles/' + articleId)
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createTeam (input) {
|
|
||||||
return request
|
|
||||||
.post(API_URL + 'teams')
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTeamInfo (teamId, input) {
|
|
||||||
return request
|
|
||||||
.put(API_URL + 'teams/' + teamId)
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function destroyTeam (teamId) {
|
|
||||||
return request
|
|
||||||
.del(API_URL + 'teams/' + teamId)
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function searchUser (key) {
|
|
||||||
return request
|
|
||||||
.get(API_URL + 'search/users')
|
|
||||||
.query({key: key})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setMember (teamId, input) {
|
|
||||||
return request
|
|
||||||
.post(API_URL + 'teams/' + teamId + '/members')
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteMember (teamId, input) {
|
|
||||||
return request
|
|
||||||
.del(API_URL + 'teams/' + teamId + '/members')
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createFolder (input) {
|
|
||||||
return request
|
|
||||||
.post(API_URL + 'folders/')
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateFolder (id, input) {
|
|
||||||
return request
|
|
||||||
.put(API_URL + 'folders/' + id)
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function destroyFolder (id) {
|
|
||||||
return request
|
|
||||||
.del(API_URL + 'folders/' + id)
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendEmail (input) {
|
|
||||||
return request
|
|
||||||
.post(API_URL + 'mail')
|
|
||||||
.set({
|
|
||||||
Authorization: 'Bearer ' + auth.token()
|
|
||||||
})
|
|
||||||
.send(input)
|
.send(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
API_URL,
|
SERVER_URL,
|
||||||
WEB_URL,
|
shareViaPublicURL
|
||||||
request,
|
|
||||||
login,
|
|
||||||
signup,
|
|
||||||
updateUserInfo,
|
|
||||||
updatePassword,
|
|
||||||
fetchCurrentUser,
|
|
||||||
fetchArticles,
|
|
||||||
createArticle,
|
|
||||||
saveArticle,
|
|
||||||
destroyArticle,
|
|
||||||
createTeam,
|
|
||||||
updateTeamInfo,
|
|
||||||
destroyTeam,
|
|
||||||
searchUser,
|
|
||||||
setMember,
|
|
||||||
deleteMember,
|
|
||||||
createFolder,
|
|
||||||
updateFolder,
|
|
||||||
destroyFolder,
|
|
||||||
sendEmail
|
|
||||||
}
|
}
|
||||||
|
|||||||
23
lib/clientKey.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import _ from 'lodash'
|
||||||
|
import keygen from 'boost/keygen'
|
||||||
|
|
||||||
|
function getClientKey () {
|
||||||
|
let clientKey = localStorage.getItem('clientKey')
|
||||||
|
if (!_.isString(clientKey) || clientKey.length !== 40) {
|
||||||
|
clientKey = keygen()
|
||||||
|
setClientKey(clientKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientKey
|
||||||
|
}
|
||||||
|
|
||||||
|
function setClientKey (newKey) {
|
||||||
|
localStorage.setItem('clientKey', newKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
getClientKey()
|
||||||
|
|
||||||
|
export default {
|
||||||
|
get: getClientKey,
|
||||||
|
set: setClientKey
|
||||||
|
}
|
||||||
@@ -30,6 +30,16 @@ module.exports = React.createClass({
|
|||||||
editor.renderer.setShowGutter(true)
|
editor.renderer.setShowGutter(true)
|
||||||
editor.setTheme('ace/theme/xcode')
|
editor.setTheme('ace/theme/xcode')
|
||||||
editor.clearSelection()
|
editor.clearSelection()
|
||||||
|
editor.moveCursorTo(0, 0)
|
||||||
|
editor.commands.addCommand({
|
||||||
|
name: 'Emacs cursor up',
|
||||||
|
bindKey: {mac: 'Ctrl-P'},
|
||||||
|
exec: function (editor) {
|
||||||
|
editor.navigateUp(1)
|
||||||
|
if (editor.getCursorPosition().row < editor.getFirstVisibleRow()) editor.scrollToLine(editor.getCursorPosition().row, false, false)
|
||||||
|
},
|
||||||
|
readOnly: true
|
||||||
|
})
|
||||||
|
|
||||||
editor.setReadOnly(!!this.props.readOnly)
|
editor.setReadOnly(!!this.props.readOnly)
|
||||||
|
|
||||||
@@ -50,16 +60,14 @@ module.exports = React.createClass({
|
|||||||
this.props.onChange(e, value)
|
this.props.onChange(e, value)
|
||||||
}
|
}
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
|
|
||||||
this.setState({editor: editor})
|
|
||||||
},
|
},
|
||||||
componentDidUpdate: function (prevProps) {
|
componentDidUpdate: function (prevProps) {
|
||||||
if (this.state.editor.getValue() !== this.props.code) {
|
if (this.editor.getValue() !== this.props.code) {
|
||||||
this.state.editor.setValue(this.props.code)
|
this.editor.setValue(this.props.code)
|
||||||
this.state.editor.clearSelection()
|
this.editor.clearSelection()
|
||||||
}
|
}
|
||||||
if (prevProps.mode !== this.props.mode) {
|
if (prevProps.mode !== this.props.mode) {
|
||||||
var session = this.state.editor.getSession()
|
var session = this.editor.getSession()
|
||||||
let mode = _.findWhere(modes, {name: this.props.mode})
|
let mode = _.findWhere(modes, {name: this.props.mode})
|
||||||
let syntaxMode = mode != null
|
let syntaxMode = mode != null
|
||||||
? mode.mode
|
? mode.mode
|
||||||
@@ -67,6 +75,18 @@ module.exports = React.createClass({
|
|||||||
session.setMode('ace/mode/' + syntaxMode)
|
session.setMode('ace/mode/' + syntaxMode)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
getFirstVisibleRow: function () {
|
||||||
|
return this.editor.getFirstVisibleRow()
|
||||||
|
},
|
||||||
|
getCursorPosition: function () {
|
||||||
|
return this.editor.getCursorPosition()
|
||||||
|
},
|
||||||
|
moveCursorTo: function (row, col) {
|
||||||
|
this.editor.moveCursorTo(row, col)
|
||||||
|
},
|
||||||
|
scrollToLine: function (num) {
|
||||||
|
this.editor.scrollToLine(num, false, false)
|
||||||
|
},
|
||||||
render: function () {
|
render: function () {
|
||||||
return (
|
return (
|
||||||
<div ref='target' className={this.props.className == null ? 'CodeEditor' : 'CodeEditor ' + this.props.className}></div>
|
<div ref='target' className={this.props.className == null ? 'CodeEditor' : 'CodeEditor ' + this.props.className}></div>
|
||||||
|
|||||||
@@ -161,8 +161,8 @@ export default class ModeSelect extends React.Component {
|
|||||||
let filteredOptions = modes
|
let filteredOptions = modes
|
||||||
.filter(mode => {
|
.filter(mode => {
|
||||||
let search = this.state.search
|
let search = this.state.search
|
||||||
let nameMatched = mode.name.match(search)
|
let nameMatched = mode.name.match(_.escapeRegExp(search))
|
||||||
let aliasMatched = _.some(mode.alias, alias => alias.match(search))
|
let aliasMatched = _.some(mode.alias, alias => alias.match(_.escapeRegExp(search)))
|
||||||
return nameMatched || aliasMatched
|
return nameMatched || aliasMatched
|
||||||
})
|
})
|
||||||
.map((mode, index) => {
|
.map((mode, index) => {
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import React, { PropTypes, findDOMNode } from 'react'
|
|
||||||
import linkState from 'boost/linkState'
|
|
||||||
import { sendEmail } from 'boost/api'
|
|
||||||
|
|
||||||
export default class ContactModal extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.linkState = linkState
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isSent: false,
|
|
||||||
mail: {
|
|
||||||
title: '',
|
|
||||||
content: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onKeyCast (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
case 'submitContactModal':
|
|
||||||
if (this.state.isSent) {
|
|
||||||
this.props.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.sendEmail()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
findDOMNode(this.refs.title).focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
sendEmail () {
|
|
||||||
sendEmail(this.state.mail)
|
|
||||||
.then(function (res) {
|
|
||||||
this.setState({isSent: !this.state.isSent})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ContactModal.propTypes = {
|
|
||||||
close: PropTypes.func
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
import linkState from 'boost/linkState'
|
import linkState from 'boost/linkState'
|
||||||
import { createFolder } from 'boost/actions'
|
import { createFolder } from 'boost/actions'
|
||||||
import store from 'boost/store'
|
import store from 'boost/store'
|
||||||
@@ -15,6 +16,10 @@ export default class CreateNewFolder extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
ReactDOM.findDOMNode(this.refs.folderName).focus()
|
||||||
|
}
|
||||||
|
|
||||||
handleCloseButton (e) {
|
handleCloseButton (e) {
|
||||||
this.props.close()
|
this.props.close()
|
||||||
}
|
}
|
||||||
@@ -84,7 +89,7 @@ export default class CreateNewFolder extends React.Component {
|
|||||||
|
|
||||||
<div className='title'>Create new folder</div>
|
<div className='title'>Create new folder</div>
|
||||||
|
|
||||||
<input onKeyDown={e => this.handleKeyDown(e)} className='ipt' type='text' valueLink={this.linkState('name')} placeholder='Enter folder name'/>
|
<input ref='folderName' onKeyDown={e => this.handleKeyDown(e)} className='ipt' type='text' valueLink={this.linkState('name')} placeholder='Enter folder name'/>
|
||||||
<div className='colorSelect'>
|
<div className='colorSelect'>
|
||||||
{colorElements}
|
{colorElements}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
import store from 'boost/store'
|
import store from 'boost/store'
|
||||||
import { unlockStatus, clearNewArticle } from 'boost/actions'
|
import { unlockStatus, clearNewArticle } from 'boost/actions'
|
||||||
|
|
||||||
export default class EditedAlert extends React.Component {
|
export default class EditedAlert extends React.Component {
|
||||||
|
componentDidMount () {
|
||||||
|
ReactDOM.findDOMNode(this.refs.no).focus()
|
||||||
|
}
|
||||||
|
|
||||||
handleNoButtonClick (e) {
|
handleNoButtonClick (e) {
|
||||||
this.props.close()
|
this.props.close()
|
||||||
}
|
}
|
||||||
@@ -22,8 +27,8 @@ export default class EditedAlert extends React.Component {
|
|||||||
<div className='message'>Do you really want to leave without finishing?</div>
|
<div className='message'>Do you really want to leave without finishing?</div>
|
||||||
|
|
||||||
<div className='control'>
|
<div className='control'>
|
||||||
<button onClick={e => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close'/> No</button>
|
<button ref='no' onClick={e => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close'/> No</button>
|
||||||
<button onClick={e => this.handleYesButtonClick(e)} className='primary'><i className='fa fa-fw fa-check'/> Yes</button>
|
<button ref='yes' onClick={e => this.handleYesButtonClick(e)} className='primary'><i className='fa fa-fw fa-check'/> Yes</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
import linkState from 'boost/linkState'
|
import linkState from 'boost/linkState'
|
||||||
|
import { updateUser } from 'boost/actions'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const ipc = electron.ipcRenderer
|
const ipc = electron.ipcRenderer
|
||||||
@@ -9,24 +10,32 @@ export default class AppSettingTab extends React.Component {
|
|||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
let keymap = remote.getGlobal('keymap')
|
let keymap = remote.getGlobal('keymap')
|
||||||
|
let userName = props.user != null ? props.user.name : null
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
toggleFinder: keymap.toggleFinder,
|
user: {
|
||||||
alert: null
|
name: userName,
|
||||||
|
alert: null
|
||||||
|
},
|
||||||
|
userAlert: null,
|
||||||
|
keymap: {
|
||||||
|
toggleFinder: keymap.toggleFinder
|
||||||
|
},
|
||||||
|
keymapAlert: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.handleSettingDone = () => {
|
this.handleSettingDone = () => {
|
||||||
this.setState({alert: {
|
this.setState({keymapAlert: {
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: 'Successfully done!'
|
message: 'Successfully done!'
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
this.handleSettingError = err => {
|
this.handleSettingError = err => {
|
||||||
this.setState({alert: {
|
this.setState({keymapAlert: {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: err.message
|
message: err.message != null ? err.message : 'Error occurs!'
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
@@ -40,7 +49,7 @@ export default class AppSettingTab extends React.Component {
|
|||||||
|
|
||||||
submitHotKey () {
|
submitHotKey () {
|
||||||
ipc.send('hotkeyUpdated', {
|
ipc.send('hotkeyUpdated', {
|
||||||
toggleFinder: this.state.toggleFinder
|
toggleFinder: this.state.keymap.toggleFinder
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,25 +63,56 @@ export default class AppSettingTab extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleNameSaveButtonClick (e) {
|
||||||
|
let { dispatch } = this.props
|
||||||
|
|
||||||
|
dispatch(updateUser({name: this.state.user.name}))
|
||||||
|
this.setState({
|
||||||
|
userAlert: {
|
||||||
|
type: 'success',
|
||||||
|
message: 'Successfully done!'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let alert = this.state.alert
|
let keymapAlert = this.state.keymapAlert
|
||||||
let alertElement = alert != null ? (
|
let keymapAlertElement = keymapAlert != null
|
||||||
<p className={`alert ${alert.type}`}>
|
? (
|
||||||
{alert.message}
|
<p className={`alert ${keymapAlert.type}`}>
|
||||||
|
{keymapAlert.message}
|
||||||
|
</p>
|
||||||
|
) : null
|
||||||
|
let userAlert = this.state.userAlert
|
||||||
|
let userAlertElement = userAlert != null
|
||||||
|
? (
|
||||||
|
<p className={`alert ${userAlert.type}`}>
|
||||||
|
{userAlert.message}
|
||||||
</p>
|
</p>
|
||||||
) : null
|
) : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='AppSettingTab content'>
|
<div className='AppSettingTab content'>
|
||||||
|
<div className='section'>
|
||||||
|
<div className='sectionTitle'>User's info</div>
|
||||||
|
<div className='sectionInput'>
|
||||||
|
<label>User name</label>
|
||||||
|
<input valueLink={this.linkState('user.name')} type='text'/>
|
||||||
|
</div>
|
||||||
|
<div className='sectionConfirm'>
|
||||||
|
<button onClick={e => this.handleNameSaveButtonClick(e)}>Save</button>
|
||||||
|
{userAlertElement}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className='section'>
|
<div className='section'>
|
||||||
<div className='sectionTitle'>Hotkey</div>
|
<div className='sectionTitle'>Hotkey</div>
|
||||||
<div className='sectionInput'>
|
<div className='sectionInput'>
|
||||||
<label>Toggle Finder(popup)</label>
|
<label>Toggle Finder(popup)</label>
|
||||||
<input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('toggleFinder')} type='text'/>
|
<input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('keymap.toggleFinder')} type='text'/>
|
||||||
</div>
|
</div>
|
||||||
<div className='sectionConfirm'>
|
<div className='sectionConfirm'>
|
||||||
<button onClick={e => this.handleSaveButtonClick(e)}>Save</button>
|
<button onClick={e => this.handleSaveButtonClick(e)}>Save</button>
|
||||||
{alertElement}
|
{keymapAlertElement}
|
||||||
</div>
|
</div>
|
||||||
<div className='description'>
|
<div className='description'>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -101,3 +141,6 @@ export default class AppSettingTab extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AppSettingTab.prototype.linkState = linkState
|
AppSettingTab.prototype.linkState = linkState
|
||||||
|
AppSettingTab.propTypes = {
|
||||||
|
dispatch: PropTypes.func
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class Preferences extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderContent () {
|
renderContent () {
|
||||||
let { folders, dispatch } = this.props
|
let { user, folders, dispatch } = this.props
|
||||||
|
|
||||||
switch (this.state.currentTab) {
|
switch (this.state.currentTab) {
|
||||||
case HELP:
|
case HELP:
|
||||||
@@ -80,12 +80,20 @@ class Preferences extends React.Component {
|
|||||||
)
|
)
|
||||||
case APP:
|
case APP:
|
||||||
default:
|
default:
|
||||||
return (<AppSettingTab/>)
|
return (
|
||||||
|
<AppSettingTab
|
||||||
|
user={user}
|
||||||
|
dispatch={dispatch}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Preferences.propTypes = {
|
Preferences.propTypes = {
|
||||||
|
user: PropTypes.shape({
|
||||||
|
name: PropTypes.string
|
||||||
|
}),
|
||||||
folders: PropTypes.array,
|
folders: PropTypes.array,
|
||||||
dispatch: PropTypes.func
|
dispatch: PropTypes.func
|
||||||
}
|
}
|
||||||
@@ -93,9 +101,10 @@ Preferences.propTypes = {
|
|||||||
Preferences.prototype.linkState = linkState
|
Preferences.prototype.linkState = linkState
|
||||||
|
|
||||||
function remap (state) {
|
function remap (state) {
|
||||||
let { folders, status } = state
|
let { user, folders, status } = state
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
user,
|
||||||
folders,
|
folders,
|
||||||
status
|
status
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export default class Tutorial extends React.Component {
|
|||||||
There is a short-cut key [control + shift + tab] to open the Finder.<br/>
|
There is a short-cut key [control + shift + tab] to open the Finder.<br/>
|
||||||
It is available to save your articles on the Clipboard<br/>
|
It is available to save your articles on the Clipboard<br/>
|
||||||
by selecting your file with pressing Enter key,<br/>
|
by selecting your file with pressing Enter key,<br/>
|
||||||
and to paste the contents of the Clipboard with [Command-V]
|
and to paste the contents of the Clipboard with [{process.platform === 'darwin' ? 'Command' : 'Control'}-V]
|
||||||
|
|
||||||
<img width='480' src='../../resources/finder.png'/>
|
<img width='480' src='../../resources/finder.png'/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import keygen from 'boost/keygen'
|
import keygen from 'boost/keygen'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const remote = electron.remote
|
const remote = electron.remote
|
||||||
const jetpack = require('fs-jetpack')
|
const jetpack = require('fs-jetpack')
|
||||||
@@ -10,15 +12,74 @@ function getLocalPath () {
|
|||||||
return path.join(remote.app.getPath('userData'), 'local.json')
|
return path.join(remote.app.getPath('userData'), 'local.json')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function forgeInitialRepositories () {
|
||||||
|
let defaultRepo = {
|
||||||
|
key: keygen(),
|
||||||
|
name: 'local',
|
||||||
|
type: 'userData',
|
||||||
|
user: {
|
||||||
|
name: 'New user'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
defaultRepo.user.name = remote.process.env.USER
|
||||||
|
} else if (process.platform === 'win32') {
|
||||||
|
defaultRepo.user.name = remote.process.env.USERNAME
|
||||||
|
}
|
||||||
|
|
||||||
|
return [defaultRepo]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRepositories () {
|
||||||
|
let raw = localStorage.getItem('repositories')
|
||||||
|
try {
|
||||||
|
let parsed = JSON.parse(raw)
|
||||||
|
if (!_.isArray(parsed)) {
|
||||||
|
throw new Error('repositories data is currupte. re-init data.')
|
||||||
|
}
|
||||||
|
return parsed
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
let newRepos = forgeInitialRepositories()
|
||||||
|
saveRepositories(newRepos)
|
||||||
|
return newRepos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveRepositories (repos) {
|
||||||
|
localStorage.setItem('repositories', JSON.stringify(repos))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUser (repoName) {
|
||||||
|
if (repoName == null) {
|
||||||
|
return getRepositories()[0]
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveUser (repoName, user) {
|
||||||
|
let repos = getRepositories()
|
||||||
|
if (repoName == null) {
|
||||||
|
Object.assign(repos[0].user, user)
|
||||||
|
}
|
||||||
|
saveRepositories(repos)
|
||||||
|
}
|
||||||
|
|
||||||
export function init () {
|
export function init () {
|
||||||
console.log('initialize data store')
|
// set repositories info
|
||||||
|
getRepositories()
|
||||||
|
|
||||||
|
// set local.json
|
||||||
let data = jetpack.read(getLocalPath(), 'json')
|
let data = jetpack.read(getLocalPath(), 'json')
|
||||||
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
// for 0.4.1 -> 0.4.2
|
||||||
if (localStorage.getItem('local') != null) {
|
if (localStorage.getItem('local') != null) {
|
||||||
data = JSON.parse(localStorage.getItem('local'))
|
data = JSON.parse(localStorage.getItem('local'))
|
||||||
jetpack.write(getLocalPath(), data)
|
jetpack.write(getLocalPath(), data)
|
||||||
localStorage.removeItem('local')
|
localStorage.removeItem('local')
|
||||||
|
console.log('update 0.4.1 => 0.4.2')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +131,8 @@ export default (function () {
|
|||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
|
getUser,
|
||||||
|
saveUser,
|
||||||
init,
|
init,
|
||||||
getData,
|
getData,
|
||||||
setArticles,
|
setArticles,
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ var crypto = require('crypto')
|
|||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
var shasum = crypto.createHash('sha1')
|
var shasum = crypto.createHash('sha1')
|
||||||
shasum.update(((new Date()).getTime()).toString())
|
shasum.update(((new Date()).getTime() + Math.round(Math.random()*1000)).toString())
|
||||||
return shasum.digest('hex')
|
return shasum.digest('hex')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,31 @@ var md = markdownit({
|
|||||||
highlight: function (str, lang) {
|
highlight: function (str, lang) {
|
||||||
if (lang && hljs.getLanguage(lang)) {
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
try {
|
try {
|
||||||
return hljs.highlight(lang, str).value;
|
return hljs.highlight(lang, str).value
|
||||||
} catch (__) {}
|
} catch (__) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return hljs.highlightAuto(str).value;
|
return hljs.highlightAuto(str).value
|
||||||
} catch (__) {}
|
} catch (__) {}
|
||||||
|
|
||||||
return ''; // use external default escaping
|
return ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
md.use(emoji)
|
md.use(emoji)
|
||||||
|
|
||||||
|
let originalRenderToken = md.renderer.renderToken
|
||||||
|
md.renderer.renderToken = function renderToken (tokens, idx, options) {
|
||||||
|
let token = tokens[idx]
|
||||||
|
let result = originalRenderToken.call(md.renderer, tokens, idx, options)
|
||||||
|
if (token.map != null) {
|
||||||
|
return result + '<a class=\'lineAnchor\' data-key=\'' + token.map[0] + '\'></a>'
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
export default function markdown (content) {
|
export default function markdown (content) {
|
||||||
if (content == null) content = ''
|
if (content == null) content = ''
|
||||||
|
|
||||||
return md.render(content.toString())
|
return md.render(content.toString())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import {
|
|||||||
UNLOCK_STATUS,
|
UNLOCK_STATUS,
|
||||||
TOGGLE_TUTORIAL,
|
TOGGLE_TUTORIAL,
|
||||||
|
|
||||||
|
// user
|
||||||
|
USER_UPDATE,
|
||||||
|
|
||||||
// Article action type
|
// Article action type
|
||||||
ARTICLE_UPDATE,
|
ARTICLE_UPDATE,
|
||||||
ARTICLE_DESTROY,
|
ARTICLE_DESTROY,
|
||||||
@@ -42,10 +45,22 @@ const initialStatus = {
|
|||||||
let data = dataStore.getData()
|
let data = dataStore.getData()
|
||||||
let initialArticles = data.articles
|
let initialArticles = data.articles
|
||||||
let initialFolders = data.folders
|
let initialFolders = data.folders
|
||||||
|
let initialUser = dataStore.getUser().user
|
||||||
|
|
||||||
let isStatusLocked = false
|
let isStatusLocked = false
|
||||||
let isCreatingNew = false
|
let isCreatingNew = false
|
||||||
|
|
||||||
|
function user (state = initialUser, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case USER_UPDATE:
|
||||||
|
let updated = Object.assign(state, action.data)
|
||||||
|
dataStore.saveUser(null, updated)
|
||||||
|
return updated
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function folders (state = initialFolders, action) {
|
function folders (state = initialFolders, action) {
|
||||||
state = state.slice()
|
state = state.slice()
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@@ -119,6 +134,7 @@ function folders (state = initialFolders, action) {
|
|||||||
state.splice(a, 1, folderB)
|
state.splice(a, 1, folderB)
|
||||||
state.splice(b, 1, folderA)
|
state.splice(b, 1, folderA)
|
||||||
}
|
}
|
||||||
|
dataStore.setFolders(state)
|
||||||
return state
|
return state
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
@@ -165,10 +181,10 @@ function articles (state = initialArticles, action) {
|
|||||||
|
|
||||||
let targetIndex = _.findIndex(state, _article => article.key === _article.key)
|
let targetIndex = _.findIndex(state, _article => article.key === _article.key)
|
||||||
if (targetIndex < 0) state.unshift(article)
|
if (targetIndex < 0) state.unshift(article)
|
||||||
else state.splice(targetIndex, 1, article)
|
else Object.assign(state[targetIndex], article)
|
||||||
|
|
||||||
if (article.status !== 'NEW') dataStore.setArticles(state)
|
if (article.status !== 'NEW') dataStore.setArticles(state)
|
||||||
else isCreatingNew = true
|
else isCreatingNew = true
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
case ARTICLE_DESTROY:
|
case ARTICLE_DESTROY:
|
||||||
@@ -250,6 +266,7 @@ function status (state = initialStatus, action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
|
user,
|
||||||
folders,
|
folders,
|
||||||
articles,
|
articles,
|
||||||
status
|
status
|
||||||
|
|||||||
0
lib/updater.js
Normal file
@@ -68,7 +68,7 @@ const modes = [
|
|||||||
{
|
{
|
||||||
name: 'csharp',
|
name: 'csharp',
|
||||||
label: 'C#',
|
label: 'C#',
|
||||||
alias: ['cs'],
|
alias: ['cs', 'c#'],
|
||||||
mode: 'csharp'
|
mode: 'csharp'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
366
main.js
@@ -2,21 +2,102 @@ const electron = require('electron')
|
|||||||
const app = electron.app
|
const app = electron.app
|
||||||
const Menu = electron.Menu
|
const Menu = electron.Menu
|
||||||
const ipc = electron.ipcMain
|
const ipc = electron.ipcMain
|
||||||
const globalShortcut = electron.globalShortcut
|
|
||||||
const autoUpdater = electron.autoUpdater
|
const autoUpdater = electron.autoUpdater
|
||||||
const jetpack = require('fs-jetpack')
|
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const ChildProcess = require('child_process')
|
const ChildProcess = require('child_process')
|
||||||
electron.crashReporter.start()
|
const _ = require('lodash')
|
||||||
|
// electron.crashReporter.start()
|
||||||
|
|
||||||
var mainWindow = null
|
var mainWindow = null
|
||||||
var finderProcess
|
var finderProcess = null
|
||||||
|
var finderWindow = null
|
||||||
var update = null
|
var update = null
|
||||||
|
|
||||||
// app.on('window-all-closed', function () {
|
// app.on('window-all-closed', function () {
|
||||||
// if (process.platform !== 'darwin') app.quit()
|
// if (process.platform !== 'darwin') app.quit()
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
const appRootPath = path.join(process.execPath, '../..')
|
||||||
|
const updateDotExePath = path.join(appRootPath, 'Update.exe')
|
||||||
|
const exeName = path.basename(process.execPath)
|
||||||
|
|
||||||
|
function spawnUpdate (args, cb) {
|
||||||
|
var stdout = ''
|
||||||
|
var updateProcess = null
|
||||||
|
try {
|
||||||
|
updateProcess = ChildProcess.spawn(updateDotExePath, args)
|
||||||
|
} catch (e) {
|
||||||
|
process.nextTick(function () {
|
||||||
|
cb(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProcess.stdout.on('data', function (data) {
|
||||||
|
stdout += data
|
||||||
|
})
|
||||||
|
|
||||||
|
error = null
|
||||||
|
updateProcess.on('error', function (_error) {
|
||||||
|
error = _error
|
||||||
|
})
|
||||||
|
updateProcess.on('close', function (code, signal) {
|
||||||
|
if (code !== 0) {
|
||||||
|
error = new Error("Command failed: #{signal ? code}")
|
||||||
|
error.code = code
|
||||||
|
error.stdout = stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(error, stdout)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var handleStartupEvent = function () {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var squirrelCommand = process.argv[1]
|
||||||
|
switch (squirrelCommand) {
|
||||||
|
case '--squirrel-install':
|
||||||
|
spawnUpdate(['--createShortcut', exeName], function (err) {
|
||||||
|
quitApp()
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
case '--squirrel-updated':
|
||||||
|
quitApp()
|
||||||
|
return true
|
||||||
|
case '--squirrel-uninstall':
|
||||||
|
spawnUpdate(['--removeShortcut', exeName], function (err) {
|
||||||
|
quitApp()
|
||||||
|
})
|
||||||
|
quitApp()
|
||||||
|
return true
|
||||||
|
case '--squirrel-obsolete':
|
||||||
|
quitApp()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handleStartupEvent()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var shouldQuit = app.makeSingleInstance(function(commandLine, workingDirectory) {
|
||||||
|
if (mainWindow) {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
mainWindow.minimize()
|
||||||
|
mainWindow.restore()
|
||||||
|
}
|
||||||
|
mainWindow.focus()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (shouldQuit) {
|
||||||
|
quitApp()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var appQuit = false
|
var appQuit = false
|
||||||
|
|
||||||
var version = app.getVersion()
|
var version = app.getVersion()
|
||||||
@@ -32,171 +113,186 @@ function notify (title, body) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
autoUpdater
|
var isUpdateReady = false
|
||||||
.on('update-downloaded', function (event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) {
|
var GhReleases = require('electron-gh-releases')
|
||||||
update = quitAndUpdate
|
|
||||||
|
|
||||||
if (mainWindow != null) {
|
var ghReleasesOpts = {
|
||||||
notify('Ready to Update! ' + releaseName, 'Click update button on Main window.')
|
repo: 'BoostIO/boost-releases',
|
||||||
mainWindow.webContents.send('update-available', 'Update available!')
|
currentVersion: app.getVersion()
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.on('error', function (err, message) {
|
const updater = new GhReleases(ghReleasesOpts)
|
||||||
console.error(err)
|
|
||||||
if (!versionNotified) {
|
// Check for updates
|
||||||
notify('Updater error!', message)
|
// `status` returns true if there is a new update available
|
||||||
}
|
function checkUpdate () {
|
||||||
})
|
updater.check((err, status) => {
|
||||||
// .on('checking-for-update', function () {
|
if (err) {
|
||||||
// // Connecting
|
console.error(err)
|
||||||
// console.log('checking...')
|
if (!versionNotified) notify('Updater error!', message)
|
||||||
// })
|
}
|
||||||
.on('update-available', function () {
|
if (!err) {
|
||||||
notify('Update is available!', 'Download started.. wait for the update ready.')
|
if (status) {
|
||||||
})
|
notify('Update is available!', 'Download started.. wait for the update ready.')
|
||||||
.on('update-not-available', function () {
|
updater.download()
|
||||||
if (mainWindow != null && !versionNotified) {
|
} else {
|
||||||
versionNotified = true
|
if (!versionNotified) {
|
||||||
notify('Latest Build!! ' + versionText, 'Hope you to enjoy our app :D')
|
versionNotified = true
|
||||||
|
notify('Latest Build!! ' + versionText, 'Hope you to enjoy our app :D')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updater.on('update-downloaded', (info) => {
|
||||||
|
if (mainWindow != null) {
|
||||||
|
notify('Ready to Update!', 'Click update button on Main window.')
|
||||||
|
mainWindow.webContents.send('update-available', 'Update available!')
|
||||||
|
isUpdateReady = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const nodeIpc = require('@rokt33r/node-ipc')
|
||||||
|
nodeIpc.config.id = 'node'
|
||||||
|
nodeIpc.config.retry = 1500
|
||||||
|
// nodeIpc.config.silent = true
|
||||||
|
|
||||||
|
function spawnFinder() {
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
var finderArgv = [path.join(__dirname, 'finder.js'), '--finder']
|
||||||
|
if (_.find(process.argv, a => a === '--hot')) finderArgv.push('--hot')
|
||||||
|
finderProcess = ChildProcess
|
||||||
|
.execFile(process.execPath, finderArgv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeIpc.serve(
|
||||||
|
path.join(app.getPath('userData'), 'boost.service'),
|
||||||
|
function () {
|
||||||
|
nodeIpc.server.on(
|
||||||
|
'connect',
|
||||||
|
function (socket) {
|
||||||
|
socket.on('close', function () {
|
||||||
|
console.log('socket dead')
|
||||||
|
if (!appQuit) spawnFinder()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
nodeIpc.server.on(
|
||||||
|
'message',
|
||||||
|
function (data, socket) {
|
||||||
|
console.log('>>', data)
|
||||||
|
format(data)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
nodeIpc.server.on(
|
||||||
|
'error',
|
||||||
|
function (err) {
|
||||||
|
console.log('>>', err)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function format (payload) {
|
||||||
|
switch (payload.type) {
|
||||||
|
case 'show-main-window':
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
mainWindow.show()
|
||||||
|
} else {
|
||||||
|
mainWindow.minimize()
|
||||||
|
mainWindow.restore()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'copy-finder':
|
||||||
|
mainWindow.webContents.send('copy-finder')
|
||||||
|
break
|
||||||
|
case 'quit-app':
|
||||||
|
quitApp()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function quitApp () {
|
||||||
|
appQuit = true
|
||||||
|
if (finderProcess) finderProcess.kill()
|
||||||
|
app.quit()
|
||||||
|
}
|
||||||
|
|
||||||
app.on('ready', function () {
|
app.on('ready', function () {
|
||||||
app.on('before-quit', function () {
|
app.on('before-quit', function () {
|
||||||
if (finderProcess) finderProcess.kill()
|
console.log('before quite')
|
||||||
appQuit = true
|
appQuit = true
|
||||||
|
if (finderProcess) finderProcess.kill()
|
||||||
})
|
})
|
||||||
|
|
||||||
autoUpdater.setFeedURL('https://orbital.b00st.io/rokt33r/boost-dev/latest?version=' + version)
|
|
||||||
|
|
||||||
var template = require('./atom-lib/menu-template')
|
var template = require('./atom-lib/menu-template')
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
template.unshift({
|
||||||
|
label: 'Boostnote',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
label: 'Quit',
|
||||||
|
accelerator: 'Control+Q',
|
||||||
|
click: function (e) {
|
||||||
|
quitApp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
var menu = Menu.buildFromTemplate(template)
|
var menu = Menu.buildFromTemplate(template)
|
||||||
Menu.setApplicationMenu(menu)
|
if (process.platform === 'darwin') {
|
||||||
|
Menu.setApplicationMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
if (update == null) autoUpdater.checkForUpdates()
|
checkUpdate()
|
||||||
}, 1000 * 60 * 60 * 24)
|
}, 1000 * 60 * 60 * 24)
|
||||||
|
|
||||||
ipc.on('check-update', function (event, msg) {
|
ipc.on('check-update', function (event, msg) {
|
||||||
if (update == null) autoUpdater.checkForUpdates()
|
if (update == null) checkUpdate()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipc.on('update-app', function (event, msg) {
|
ipc.on('update-app', function (event, msg) {
|
||||||
if (update != null) {
|
if (isUpdateReady) {
|
||||||
appQuit = true
|
appQuit = true
|
||||||
update()
|
updater.install()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
checkUpdate()
|
||||||
|
|
||||||
mainWindow = require('./atom-lib/main-window')
|
mainWindow = require('./atom-lib/main-window')
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
mainWindow.setMenu(menu)
|
||||||
|
}
|
||||||
mainWindow.on('close', function (e) {
|
mainWindow.on('close', function (e) {
|
||||||
if (appQuit) return true
|
if (appQuit) return true
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
mainWindow.hide()
|
mainWindow.hide()
|
||||||
})
|
})
|
||||||
mainWindow.webContents.on('did-finish-load', function () {
|
|
||||||
if (finderProcess == null) {
|
|
||||||
finderProcess = ChildProcess
|
|
||||||
.execFile(process.execPath, [path.resolve(__dirname, 'finder.js'), '--finder'])
|
|
||||||
finderProcess.stdout.setEncoding('utf8')
|
|
||||||
finderProcess.stderr.setEncoding('utf8')
|
|
||||||
finderProcess.stdout.on('data', format)
|
|
||||||
finderProcess.stderr.on('data', errorFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update != null) {
|
if (finderProcess == null && process.platform === 'darwin') {
|
||||||
mainWindow.webContents.send('update-available', 'whoooooooh!')
|
console.log('fired only once ')
|
||||||
} else {
|
spawnFinder()
|
||||||
autoUpdater.checkForUpdates()
|
} else {
|
||||||
}
|
finderWindow = require('./atom-lib/finder-window')
|
||||||
})
|
|
||||||
|
|
||||||
app.on('activate', function () {
|
finderWindow.on('close', function (e) {
|
||||||
if (mainWindow == null) return null
|
if (appQuit) return true
|
||||||
mainWindow.show()
|
e.preventDefault()
|
||||||
})
|
finderWindow.hide()
|
||||||
|
|
||||||
function format (payload) {
|
|
||||||
// console.log('from finder >> ', payload)
|
|
||||||
try {
|
|
||||||
payload = JSON.parse(payload)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Not parsable payload : ', payload)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch (payload.type) {
|
|
||||||
case 'log':
|
|
||||||
console.log('FINDER(stdout): ' + payload.data)
|
|
||||||
break
|
|
||||||
case 'show-main-window':
|
|
||||||
if (mainWindow != null) {
|
|
||||||
mainWindow.show()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'request-data':
|
|
||||||
mainWindow.webContents.send('request-data')
|
|
||||||
break
|
|
||||||
case 'quit-app':
|
|
||||||
appQuit = true
|
|
||||||
app.quit()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function errorFormat (output) {
|
|
||||||
console.error('FINDER(stderr):' + output)
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitToFinder (type, data) {
|
|
||||||
if (!finderProcess) {
|
|
||||||
console.log('finder process is not ready')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var payload = {
|
|
||||||
type: type,
|
|
||||||
data: data
|
|
||||||
}
|
|
||||||
finderProcess.stdin.write(JSON.stringify(payload), 'utf-8')
|
|
||||||
}
|
|
||||||
|
|
||||||
var userDataPath = app.getPath('userData')
|
|
||||||
if (!jetpack.cwd(userDataPath).exists('keymap.json')) {
|
|
||||||
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
global.keymap = JSON.parse(jetpack.cwd(userDataPath).read('keymap.json', 'utf-8'))
|
|
||||||
} catch (err) {
|
|
||||||
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
|
|
||||||
global.keymap = {}
|
|
||||||
}
|
|
||||||
if (global.keymap.toggleFinder == null) global.keymap.toggleFinder = 'ctrl+tab+shift'
|
|
||||||
var toggleFinderKey = global.keymap.toggleFinder
|
|
||||||
|
|
||||||
try {
|
|
||||||
globalShortcut.register(toggleFinderKey, function () {
|
|
||||||
emitToFinder('open-finder')
|
|
||||||
})
|
})
|
||||||
} catch (err) {
|
|
||||||
console.log(err.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ipc.on('hotkeyUpdated', function (event, newKeymap) {
|
nodeIpc.server.start(function (err) {
|
||||||
console.log('got new keymap')
|
if (err.code === 'EADDRINUSE') {
|
||||||
console.log(newKeymap)
|
notify('Error occurs!', 'You have to kill other Boostnote processes.')
|
||||||
globalShortcut.unregisterAll()
|
quitApp()
|
||||||
global.keymap = newKeymap
|
|
||||||
jetpack.cwd(userDataPath).file('keymap.json', {content: JSON.stringify(global.keymap)})
|
|
||||||
|
|
||||||
var toggleFinderKey = global.keymap.toggleFinder != null ? global.keymap.toggleFinder : 'ctrl+tab+shift'
|
|
||||||
try {
|
|
||||||
globalShortcut.register(toggleFinderKey, function () {
|
|
||||||
emitToFinder('open-finder')
|
|
||||||
})
|
|
||||||
mainWindow.webContents.send('APP_SETTING_DONE', {})
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
mainWindow.webContents.send('APP_SETTING_ERROR', {
|
|
||||||
message: 'Failed to apply hotkey: Invalid format'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
require('./hotkey')
|
||||||
})
|
})
|
||||||
|
|||||||
1
node_modules/boost
generated
vendored
@@ -1 +0,0 @@
|
|||||||
../lib
|
|
||||||
25
package.json
@@ -1,19 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"version": "0.4.2-rc.0",
|
"version": "0.5.0",
|
||||||
"description": "Boost App",
|
"description": "Boostnote",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "BOOST_ENV=development electron ./main.js",
|
"start": "electron ./index.js",
|
||||||
"webpack": "webpack-dev-server --hot --inline --config webpack.config.js",
|
"hot": "electron ./index.js --hot",
|
||||||
"compile": "NODE_ENV=production webpack --config webpack.config.production.js",
|
"webpack": "webpack-dev-server --hot --inline --config webpack.config.js"
|
||||||
"build": "electron-packager ./ Boost --app-version=$npm_package_version $npm_package_config_platform $npm_package_config_version $npm_package_config_ignore --overwrite --asar",
|
|
||||||
"codesign": "codesign --verbose --deep --force --sign \"MAISIN solutions Inc.\" Boost-darwin-x64/Boost.app"
|
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"version": "--version=0.35.1 --app-bundle-id=com.maisin.boost",
|
"electron-version": "0.35.4"
|
||||||
"platform": "--platform=darwin --arch=x64 --prune --icon=resources/app.icns",
|
|
||||||
"ignore": "--ignore=Boost-darwin-x64 --ignore=node_modules/devicon/icons --ignore=submodules/ace/(?!src-min)|submodules/ace/(?=src-min-noconflict)"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -31,13 +27,14 @@
|
|||||||
"short code"
|
"short code"
|
||||||
],
|
],
|
||||||
"author": "Dick Choi <fluke8259@gmail.com> (http://kazup.co)",
|
"author": "Dick Choi <fluke8259@gmail.com> (http://kazup.co)",
|
||||||
"license": "No License",
|
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/Rokt33r/codexen-app/issues"
|
"url": "https://github.com/Rokt33r/codexen-app/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Rokt33r/codexen-app#readme",
|
"homepage": "https://github.com/Rokt33r/codexen-app#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@rokt33r/node-ipc": "^5.0.4",
|
||||||
"devicon": "^2.0.0",
|
"devicon": "^2.0.0",
|
||||||
|
"electron-gh-releases": "^2.0.2",
|
||||||
"font-awesome": "^4.3.0",
|
"font-awesome": "^4.3.0",
|
||||||
"fs-jetpack": "^0.7.0",
|
"fs-jetpack": "^0.7.0",
|
||||||
"highlight.js": "^8.9.1",
|
"highlight.js": "^8.9.1",
|
||||||
@@ -56,6 +53,9 @@
|
|||||||
"css-loader": "^0.19.0",
|
"css-loader": "^0.19.0",
|
||||||
"electron-packager": "^5.1.0",
|
"electron-packager": "^5.1.0",
|
||||||
"electron-prebuilt": "^0.35.1",
|
"electron-prebuilt": "^0.35.1",
|
||||||
|
"electron-release": "^2.2.0",
|
||||||
|
"grunt": "^0.4.5",
|
||||||
|
"grunt-electron-installer": "^1.2.0",
|
||||||
"nib": "^1.1.0",
|
"nib": "^1.1.0",
|
||||||
"react": "^0.14.0",
|
"react": "^0.14.0",
|
||||||
"react-dom": "^0.14.0",
|
"react-dom": "^0.14.0",
|
||||||
@@ -72,6 +72,9 @@
|
|||||||
"webpack": "^1.12.2",
|
"webpack": "^1.12.2",
|
||||||
"webpack-dev-server": "^1.12.0"
|
"webpack-dev-server": "^1.12.0"
|
||||||
},
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"appdmg": "^0.3.5"
|
||||||
|
},
|
||||||
"standard": {
|
"standard": {
|
||||||
"ignore": [],
|
"ignore": [],
|
||||||
"globals": [
|
"globals": [
|
||||||
|
|||||||
BIN
resources/app.ico
Normal file
|
After Width: | Height: | Size: 361 KiB |
BIN
resources/app.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
resources/boostnote-install.gif
Executable file
|
After Width: | Height: | Size: 138 KiB |
BIN
resources/boostnote-install.png
Executable file
|
After Width: | Height: | Size: 234 KiB |
BIN
resources/boostnote-install@2x.png
Executable file
|
After Width: | Height: | Size: 837 KiB |
BIN
resources/dmg.icns
Normal file
BIN
resources/dmg.ico
Normal file
|
After Width: | Height: | Size: 361 KiB |
BIN
resources/dmg.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
@@ -3,6 +3,7 @@ var path = require('path')
|
|||||||
var JsonpTemplatePlugin = webpack.JsonpTemplatePlugin
|
var JsonpTemplatePlugin = webpack.JsonpTemplatePlugin
|
||||||
var FunctionModulePlugin = require('webpack/lib/FunctionModulePlugin')
|
var FunctionModulePlugin = require('webpack/lib/FunctionModulePlugin')
|
||||||
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin')
|
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin')
|
||||||
|
|
||||||
var opt = {
|
var opt = {
|
||||||
path: path.join(__dirname, 'compiled'),
|
path: path.join(__dirname, 'compiled'),
|
||||||
filename: '[name].js',
|
filename: '[name].js',
|
||||||
@@ -10,6 +11,7 @@ var opt = {
|
|||||||
libraryTarget: 'commonjs2',
|
libraryTarget: 'commonjs2',
|
||||||
publicPath: 'http://localhost:8080/assets/'
|
publicPath: 'http://localhost:8080/assets/'
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = {
|
var config = {
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
loaders: [
|
||||||
@@ -34,7 +36,10 @@ var config = {
|
|||||||
output: opt,
|
output: opt,
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['', '.js', '.jsx'],
|
extensions: ['', '.js', '.jsx'],
|
||||||
packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main']
|
packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'],
|
||||||
|
alias: {
|
||||||
|
'boost': path.resolve(__dirname, 'lib')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.NoErrorsPlugin(),
|
new webpack.NoErrorsPlugin(),
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
var webpack = require('webpack')
|
var webpack = require('webpack')
|
||||||
module.exports = {
|
var path = require('path')
|
||||||
entry: {
|
var JsonpTemplatePlugin = webpack.JsonpTemplatePlugin
|
||||||
main: './browser/main/index.js',
|
var FunctionModulePlugin = require('webpack/lib/FunctionModulePlugin')
|
||||||
finder: './browser/finder/index.js'
|
var NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin')
|
||||||
},
|
|
||||||
output: {
|
var opt = {
|
||||||
path: 'compiled',
|
path: path.join(__dirname, 'compiled'),
|
||||||
filename: '[name].js',
|
filename: '[name].js',
|
||||||
// sourceMapFilename: '[name].map',
|
sourceMapFilename: '[name].map',
|
||||||
libraryTarget: 'commonjs2'
|
libraryTarget: 'commonjs2',
|
||||||
},
|
publicPath: 'http://localhost:8080/assets/'
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = {
|
||||||
module: {
|
module: {
|
||||||
loaders: [
|
loaders: [
|
||||||
{
|
{
|
||||||
@@ -24,11 +27,26 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
entry: {
|
||||||
|
main: './browser/main/index.js',
|
||||||
|
finder: './browser/finder/index.js'
|
||||||
|
},
|
||||||
|
output: opt,
|
||||||
|
resolve: {
|
||||||
|
extensions: ['', '.js', '.jsx'],
|
||||||
|
packageMains: ['webpack', 'browser', 'web', 'browserify', ['jam', 'main'], 'main'],
|
||||||
|
alias: {
|
||||||
|
'boost': path.resolve(__dirname, 'lib')
|
||||||
|
}
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
new webpack.NoErrorsPlugin(),
|
||||||
|
new NodeTargetPlugin(),
|
||||||
new webpack.optimize.OccurenceOrderPlugin(),
|
new webpack.optimize.OccurenceOrderPlugin(),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
'NODE_ENV': JSON.stringify('production')
|
'NODE_ENV': JSON.stringify('production'),
|
||||||
|
'BABEL_ENV': JSON.stringify('production')
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new webpack.optimize.UglifyJsPlugin({
|
new webpack.optimize.UglifyJsPlugin({
|
||||||
@@ -49,8 +67,14 @@ module.exports = {
|
|||||||
'highlight.js',
|
'highlight.js',
|
||||||
'markdown-it-emoji',
|
'markdown-it-emoji',
|
||||||
'fs-jetpack'
|
'fs-jetpack'
|
||||||
],
|
]
|
||||||
resolve: {
|
|
||||||
extensions: ['', '.js', '.jsx', 'styl']
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config.target = function renderer (compiler) {
|
||||||
|
compiler.apply(
|
||||||
|
new JsonpTemplatePlugin(opt),
|
||||||
|
new FunctionModulePlugin(opt)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = config
|
||||||
|
|||||||