Initial commit

This commit is contained in:
Sarah Seo 2016-11-19 23:36:28 +09:00
commit a4cc4901a8
21 changed files with 1313 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules/*
.DS_Store
.env
Desktop.ini
Thumbs.db
*.log

34
app/index.js Normal file
View File

@ -0,0 +1,34 @@
'use strict'
const electron = require('electron')
const { app, BrowserWindow } = electron
const path = require('path')
let mainWindow = null
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('ready', () => {
mainWindow = new BrowserWindow({
frame: false
})
mainWindow.loadURL('file://' + path.join(__dirname, '/main.html'))
mainWindow.webContents.on('new-window', (e) => {
e.preventDefault()
})
mainWindow.on('close', (e) => {
e.preventDefault()
mainWindow.hide()
})
app.on('activate', (e) => {
mainWindow.show()
mainWindow.focus()
})
app.on('before-quit', (e) => {
mainWindow.removeAllListeners()
})
})

39
app/main.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html>
<head>
<title>Drafter</title>
<style>
#content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
body {
font-family: Helvetica, Arial, sans-serif;
}
</style>
<link rel="stylesheet" type="text/css" href="../node_modules/octicons/build/octicons.css">
</head>
<body>
<div id="content"></div>
<script type="text/javascript" src="../node_modules/codemirror/lib/codemirror.js"></script>
<script type="text/javascript" src="../node_modules/codemirror/mode/meta.js"></script>
<script type="text/javascript" src="../node_modules/codemirror/addon/mode/overlay.js"></script>
<script type="text/javascript" src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
<script type="text/javascript" src="../node_modules/codemirror/keymap/sublime.js"></script>
<script type="text/javascript" src="../node_modules/codemirror/addon/runmode/runmode.js"></script>
<script type="text/javascript" src="../node_modules/codemirror/addon/edit/continuelist.js"></script>
<script type="text/javascript" src="../node_modules/react/dist/react.js"></script>
<script type="text/javascript" src="../node_modules/react-dom/dist/react-dom.js"></script>
<script type="text/javascript" src="../node_modules/redux/dist/redux.js"></script>
<script type="text/javascript" src="../node_modules/react-redux/dist/react-redux.js"></script>
<script type="text/javascript" src="../node_modules/immutable/dist/immutable.js"></script>
<script type="text/javascript" src="http://localhost:8080/assets/main.js"></script>
</body>
</html>

32
docs/database.md Normal file
View File

@ -0,0 +1,32 @@
# Database
## Sequences
### Initialization
When app started
DB 관리
파일과의 연결처리.
항상 기동시 레벨 DB와 덤프파일간에 레플리케이션을 시도한다.
파일 워치를 넣어두어서 해당파일이 바뀌면 레플리케이션을 시도한다.
스토리지 변경후 레플리케이트까지 10초 유예기간
*.padstorage 확장자
스토리지 = 데이터베이스
작성자는 이메일로 구분한다.
공유전에는 이메일을 입력하게한다.
입력시 해당이메일을 글로벌 프로필로 사용할지 질문한다.
디폴트DB는 항상 계속 가져옴
Keys|
---|---
folder_${path}/path|
note_${createdAt}
_local/resourcesPath|
_local/remotes|

71
package.json Normal file
View File

@ -0,0 +1,71 @@
{
"name": "Inpad",
"version": "0.1.0",
"description": "A simple note app for developer",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "NODE_ENV=development electron app/index.js",
"pack": "build --dir",
"dist": "build",
"webpack": "NODE_ENV=development webpack-dev-server --config webpack.config.js",
"rebuild": "electron-rebuild"
},
"keywords": [
"markdown",
"snippet",
"note"
],
"author": "Sarah Seo <sarah.seo.0311@gmail.com>",
"license": "MIT",
"devDependencies": {
"babel-core": "^6.18.0",
"babel-eslint": "^7.1.0",
"babel-loader": "^6.2.7",
"babel-plugin-transform-class-properties": "^6.18.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"css-loader": "^0.25.0",
"electron": "^1.4.5",
"electron-builder": "^7.15.2",
"electron-devtools-installer": "^2.0.1",
"electron-rebuild": "^1.3.0",
"file-loader": "^0.9.0",
"pouchdb": "^6.0.7",
"react-desktop": "^0.2.14",
"react-hot-loader": "^3.0.0-beta.6",
"react-router": "^3.0.0",
"react-router-redux": "^4.0.6",
"standard": "^8.5.0",
"style-loader": "^0.13.1",
"url-loader": "^0.5.7",
"webpack": "^2.1.0-beta",
"webpack-dev-server": "^2.1.0-beta"
},
"dependencies": {
"codemirror": "^5.20.2",
"github-markdown-css": "^2.4.1",
"immutable": "^3.8.1",
"leveldown": "^1.5.0",
"lodash": "^4.16.6",
"octicons": "^5.0.1",
"react": "^15.3.2",
"react-dom": "^15.3.2",
"react-redux": "^4.4.5",
"redux": "^3.6.0",
"sander": "^0.5.1",
"styled-components": "^1.0.10"
},
"standard": {
"parser": "babel-eslint"
},
"babel": {
"presets": [
"react",
"es2015"
],
"plugins": [
"transform-class-properties"
]
}
}

6
readme.md Normal file
View File

@ -0,0 +1,6 @@
# Inpad
A simple note app for developer.
## Resources

57
src/components/Octicon.js Normal file
View File

@ -0,0 +1,57 @@
import React, { PropTypes } from 'react'
import styled, { keyframes } from 'styled-components'
import _ from 'lodash'
import octicons from 'octicons'
const pulse = keyframes`
from {
transform: scale(0.9);
}
to {
transform: scale(1.2);
}
`
const pulseStyle = `
animation: ${pulse} 0.5s linear infinite;
`
const Icon = styled.svg`
display: inline-block;
line-height: 1em;
width: 1em;
height: 1em;
vertical-align: middle;
fill: ${(p) => _.isString(p.color) ? p.color : 'inherit'};
${(p) => p.pulse ? pulseStyle : ''}
`
class Octicon extends React.Component {
constructor (props) {
super(props)
this.state = {
}
}
render () {
const { size, icon } = this.props
const octicon = octicons[icon]
return (
<Icon
{...this.props}
width={size}
height={size}
viewBox={octicon.options.viewBox}
dangerouslySetInnerHTML={{__html: octicon.path}}
/>
)
}
}
Octicon.propTypes = {
icon: PropTypes.string.isRequired,
pulse: PropTypes.bool
}
export default Octicon

44
src/main/App.js Normal file
View File

@ -0,0 +1,44 @@
import React from 'react'
import { Provider } from 'react-redux'
import { Router } from 'react-router'
import routes from './routes'
Router.prototype.componentWillReceiveProps = function (nextProps) {
let components = []
function grabComponents (element) {
if (element.props && element.props.component) {
components.push(element.props.component)
}
if (element.props && element.props.children) {
React.Children.forEach(element.props.children, grabComponents)
}
}
grabComponents(nextProps.routes || nextProps.children)
components.forEach(React.createElement)
}
class App extends React.Component {
constructor (props) {
super(props)
this.state = {
}
}
render () {
let { store, history } = this.props
return (
<Provider store={store}>
<Router history={history}>
{routes}
</Router>
</Provider>
)
}
}
App.propTypes = {
}
export default App

149
src/main/Main.js Normal file
View File

@ -0,0 +1,149 @@
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import styled, { ThemeProvider } from 'styled-components'
import TitleBar from './TitleBar'
import themes from './lib/themes'
import Nav from './Nav'
import { Map } from 'immutable'
import StorageManager from './lib/StorageManager'
const Root = styled.div`
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
user-select: none;
`
const Body = styled.div`
position: absolute;
top: 36px;
left: 0;
bottom: 0;
right: 0;
display: flex;
`
const Slider = styled.div`
position: relative;
width: 5px;
cursor: col-resize;
display: flex;
margin-left: -2px;
margin-right: -2px;
z-index: 2;
`
const SliderLine = styled.div`
margin-left: 2px;
width: 1px;
background-color: ${(p) => p.active ? p.theme.activeBorderColor : p.theme.borderColor};
`
const Content = styled.div`
flex: 1;
position: relative;
z-index: 1;
`
class Main extends React.Component {
constructor (props) {
super(props)
this.state = {
navWidth: props.status.get('navWidth'),
isSliderActive: false
}
this.handleSliderMouseDown = () => {
window.addEventListener('mouseup', this.handleSliderMouseUp)
window.addEventListener('mousemove', this.handleSliderMouseMove)
this.setState({
isSliderActive: true
})
}
this.handleSliderMouseMove = (e) => {
this.setState({
navWidth: e.clientX
})
}
this.handleSliderMouseUp = (e) => {
window.removeEventListener('mouseup', this.handleSliderMouseUp)
window.removeEventListener('mousemove', this.handleSliderMouseMove)
this.setState({
isSliderActive: false,
navWidth: e.clientX
})
}
}
componentWillUnmount () {
window.removeEventListener('mouseup', this.handleSliderMouseUp)
window.removeEventListener('mousemove', this.handleSliderMouseMove)
}
getChildContext () {
return {
status: this.props.status
}
}
componentDidMount () {
const { dispatch } = this.props
StorageManager.init()
.then(() => {
return StorageManager.loadAll()
})
.then((data) => {
dispatch({
type: 'LOAD_ALL_STORAGES',
payload: {
storageMap: data
}
})
})
}
render () {
let { storageMap } = this.props
return (
<ThemeProvider theme={themes.default}>
<Root>
<TitleBar />
<Body>
<Nav storageMap={storageMap} width={this.state.navWidth} />
<Slider
onMouseDown={this.handleSliderMouseDown}
onMouseUp={this.handleSliderMouseUp}
>
<SliderLine
active={this.state.isSliderActive}
/>
</Slider>
<Content>
{this.props.children}
</Content>
</Body>
</Root>
</ThemeProvider>
)
}
}
Main.propTypes = {
}
Main.childContextTypes = {
status: PropTypes.instanceOf(Map)
}
export default connect((x) => x)(Main)

110
src/main/Nav.js Normal file
View File

@ -0,0 +1,110 @@
import React, { PropTypes } from 'react'
import styled from 'styled-components'
import Octicon from 'components/Octicon'
import { Map } from 'immutable'
import { Link } from 'react-router'
const Root = styled.div`
position: relative;
min-width: 150px;
width: ${(p) => p.width}px;
overflow: hidden;
`
const StorageSection = styled.div`
margin: 10px 0;
`
const NavButton = styled.a`
${(p) => p.active ? p.theme.navButtonActive : p.theme.navButton}
display: block;
height: 24px;
line-height: 24px;
margin: 0;
padding: 0 10px;
cursor: pointer;
`
const FolderButton = styled(NavButton)`
padding: 0 20px;
`
const BottomButton = styled.button`
${(p) => p.theme.navButton}
display: block;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 30px;
width: 100%;
line-height: 30px;
padding: 0 10px;
cursor: pointer;
`
class Nav extends React.Component {
constructor (props) {
super(props)
this.state = {
}
}
render () {
const { storageMap } = this.props
const { router } = this.context
const storageList = storageMap
.map((data, storageName) => {
const folderList = data.folders
.map((meta, folderName) => {
const folderPath = `/storages/${storageName}/folders/${folderName}`
return <FolderButton
key={folderName}
href={'#' + folderPath}
active={router.isActive(folderPath)}
>
{folderName}
</FolderButton>
})
.toArray()
const storagePath = `/storages/${storageName}/all-notes`
const isStorageActive = router.isActive(storagePath)
return <StorageSection
key={storageName}
>
<NavButton
href={'#' + storagePath}
active={isStorageActive}
>
<Octicon icon='repo' size={12} color={isStorageActive && 'white'} /> {storageName}
</NavButton>
{folderList}
</StorageSection>
})
.toArray()
return (
<Root width={this.props.width}>
{storageList}
<BottomButton>
<Octicon icon='plus' /> Add Folder
</BottomButton>
</Root>
)
}
}
Nav.propTypes = {
}
Nav.contextTypes = {
router: PropTypes.shape({
push: PropTypes.func,
isActive: PropTypes.func
})
}
export default Nav

120
src/main/TitleBar.js Normal file
View File

@ -0,0 +1,120 @@
import React from 'react'
import styled from 'styled-components'
import { TitleBar as MacTitleBar, Toolbar } from 'react-desktop/macOs'
import Octicon from 'components/Octicon'
const { remote } = require('electron')
const SearchInput = styled.input`
${(p) => p.theme.input}
-webkit-app-region: no-drag;
-webkit-user-select: none;
margin: 0 2.5px;
height: 26px;
padding: 0 10px;
box-sizing: border-box;
&:focus {
border: ${(p) => p.theme.activeBorder}
}
`
const Button = styled.button`
${(p) => p.theme.button}
padding: 0;
width: 30px;
height: 26px;
-webkit-app-region: no-drag;
-webkit-user-select: none;
margin: 0 2.5px;
`
const Seperator = styled.div`
display: inline-block;
margin: 0 10px;
height: 26px;
`
const BordedTitleBar = styled(MacTitleBar)`
border-bottom: ${(p) => p.theme.border};
`
const Root = styled.div`
position: relative;
`
class TitleBar extends React.Component {
constructor (props) {
super(props)
this.state = {
isFullscreen: false,
search: ''
}
this.handleChange = (e) => {
this.setState({
search: e.target.value
})
}
}
handleCloseClick = () => {
remote.getCurrentWindow().close()
}
handleResizeClick () {
let currentWindow = remote.getCurrentWindow()
let isFullscreen = currentWindow.isFullScreen()
currentWindow.setFullScreen(!isFullscreen)
this.setState({
isFullscreen: !isFullscreen
})
}
handleMinimizeClick = () => {
remote.getCurrentWindow().minimize()
}
handleMaximizeClick = () => {
remote.getCurrentWindow().maximize()
}
render () {
return (
<Root>
<BordedTitleBar
inset
controls
transparent
isFullscreen={this.state.isFullscreen}
onCloseClick={this.handleCloseClick}
onMinimizeClick={this.handleMinimizeClick}
onMaximizeClick={this.handleMaximizeClick}
onResizeClick={this.handleResizeClick.bind(this)}
>
<Toolbar height='36' horizontalAlignment='center'>
<SearchInput
placeholder='Search...'
value={this.state.search}
onChange={this.handleChange}
/>
<Button>
<Octicon icon='plus' />
</Button>
<Seperator />
<Button>
<Octicon icon='settings' />
</Button>
</Toolbar>
</BordedTitleBar>
</Root>
)
}
}
TitleBar.propTypes = {
}
export default TitleBar

View File

@ -0,0 +1,117 @@
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import styled from 'styled-components'
import { Map } from 'immutable'
import Octicon from 'components/Octicon'
const Root = styled.div`
display: flex;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
`
const Left = styled.div`
width: ${(p) => p.width}px;
min-width: 150px;
`
const LeftMenu = styled.div`
border-bottom: ${(p) => p.theme.border}
`
const LeftList = styled.div`
`
const LeftListItem = styled.div`
`
const Slider = styled.div`
position: relative;
width: 5px;
cursor: col-resize;
display: flex;
margin-left: -2px;
margin-right: -2px;
`
const SliderLine = styled.div`
margin-left: 2px;
width: 1px;
background-color: ${(p) => p.active ? p.theme.activeBorderColor : p.theme.borderColor};
`
const Detail = styled.div`
`
class NoteList extends React.Component {
constructor (props) {
super(props)
this.state = {
listWidth: props.status.get('noteListWidth')
}
this.handleSliderMouseDown = () => {
window.addEventListener('mouseup', this.handleSliderMouseUp)
window.addEventListener('mousemove', this.handleSliderMouseMove)
this.setState({
isSliderActive: true
})
}
this.handleSliderMouseMove = (e) => {
this.setState({
listWidth: e.clientX - this.props.status.get('navWidth')
})
}
this.handleSliderMouseUp = (e) => {
window.removeEventListener('mouseup', this.handleSliderMouseUp)
window.removeEventListener('mousemove', this.handleSliderMouseMove)
this.setState({
isSliderActive: false,
listWidth: e.clientX - this.props.status.get('navWidth')
})
}
}
render () {
return (
<Root>
<Left width={this.state.listWidth}>
<LeftMenu>
Sort By <select/>
<button><Octicon icon='grabber' size='12' /></button>
<button><Octicon icon='three-bars' size='12' /></button>
</LeftMenu>
<LeftList>
</LeftList>
</Left>
<Slider
onMouseDown={this.handleSliderMouseDown}
onMouseUp={this.handleSliderMouseUp}
>
<SliderLine
active={this.state.isSliderActive}
/>
</Slider>
<Detail />
</Root>
)
}
}
NoteList.propTypes = {
}
NoteList.contextTypes = {
status: PropTypes.instanceOf(Map)
}
export default connect((x) => x)(NoteList)

View File

@ -0,0 +1,66 @@
import React, { PropTypes } from 'react'
import styled from 'styled-components'
import Octicon from 'components/Octicon'
const Root = styled.div`
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
`
class StorageCreate extends React.Component {
constructor (props, context) {
super(props, context)
this.state = {
name: '',
isSaving: false
}
}
handleNameChange = (e) => {
this.setState({
name: e.target.value
})
}
handleConfirmClick = (e) => {
this.setState({
isSaving: true
}, () => {
})
}
render () {
return (
<Root>
<div>Add Storage</div>
<input
value={this.state.name}
onChange={this.handleNameChange}
/>
<div>
<button onClick={this.handleConfirmClick}>
{this.state.isSaving
? <span>
<Octicon icon='pulse' pulse /> Saving...
</span>
: <span>
<Octicon icon='check' /> Confirm
</span>
}
</button>
</div>
</Root>
)
}
}
StorageCreate.propTypes = {
}
export default StorageCreate

View File

@ -0,0 +1,22 @@
import React, { PropTypes } from 'react'
import styled from 'styled-components'
class StorageIndex extends React.Component {
constructor (props) {
super(props)
this.state = {
}
}
render () {
return (
<div></div>
)
}
}
StorageIndex.propTypes = {
}
export default StorageIndex

48
src/main/index.js Normal file
View File

@ -0,0 +1,48 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import App from './App'
import store from './lib/redux/store'
import { hashHistory } from 'react-router'
import { syncHistoryWithStore } from 'react-router-redux'
import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer'
if (process.env.NODE_ENV !== 'production') {
installExtension(REACT_DEVELOPER_TOOLS)
.then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log('An error occurred: ', err))
}
document.addEventListener('drop', function (e) {
e.preventDefault()
e.stopPropagation()
})
document.addEventListener('dragover', function (e) {
e.preventDefault()
e.stopPropagation()
})
const history = syncHistoryWithStore(hashHistory, store)
let el = document.getElementById('content')
ReactDOM.render((
<AppContainer>
<App store={store} history={history} />
</AppContainer>
), el, function () {
})
// 강제적으로 App을 다시 불러와서 새롭게 렌더링합니다.
// 고로 App을 바깥으로 빼둘 필요가 있습니다.
if (module.hot) {
module.hot.accept('./App', () => {
let NextApp = require('./App').default
ReactDOM.render((
<AppContainer>
<NextApp store={store} history={history} />
</AppContainer>
), el)
})
}

View File

@ -0,0 +1,168 @@
const sander = require('sander')
const path = require('path')
const PouchDB = require('pouchdb')
const { OrderedMap, Map } = require('immutable')
const util = require('../util')
const electron = require('electron')
const { remote } = electron
const storagesPath = path.join(remote.app.getPath('userData'), 'storages')
let dbs
/**
* Initialize db connection
* If nothing is found, add a new connection
*
* @return {OrderedMap} All DB connections
*/
export function init () {
return sander.readdir(storagesPath)
// If `storages` doesn't exist, create it.
.catch((err) => {
if (err.code === 'ENOENT') {
return sander.mkdir(storagesPath)
.then(() => [])
} else throw err
})
// If `storages/default` doesn't exist, create it.
.then(function (dirNames) {
if (!dirNames.some((dirName) => dirName === 'notebook')) {
return sander.mkdir(storagesPath, 'notebook')
.then(() => dirNames.push('notebook'))
}
return dirNames
})
.then(function initPouchDBs (dirNames) {
dbs = dirNames.reduce(function (map, name) {
return map.set(name, new PouchDB(path.join(storagesPath, name)))
}, new OrderedMap())
return dbs
})
}
export function list () {
if (dbs == null) return init()
return Promise.resolve(new OrderedMap(dbs))
}
/**
* load dataMap from a storage
*
* @param {String} name [description]
* @return {Map} return data map of a Storage
* including `notes` and `folders` field
*/
export function load (name) {
const db = dbs.get(name)
if (db == null) return Promise.reject(new Error('DB doesn\'t exist.'))
return db
.allDocs({include_docs: true})
.then((docs) => {
return {
notes: new Map([]),
folders: new Map([['Notes', {}]])
}
})
}
/**
* load dataMaps from all storages and map them
*
* @return {OrderedMap} Data Map of all storages
*/
export function loadAll () {
const promises = dbs
.keySeq()
.map((name) => {
return load(name)
// struct tuple
.then((dataMap) => [name, dataMap])
})
// Promise.all only understands array
.toArray()
return Promise.all(promises)
// destruct tuple
.then((storageMap) => new OrderedMap(storageMap))
}
export function upsertFolder (name, folderPath) {
const db = dbs.get(name)
if (db == null) return Promise.reject(new Error('DB doesn\'t exist.'))
return db
.put({
_id: 'folder:' + folderPath
})
}
export function deleteFolder (name, folderPath) {
const db = dbs.get(name)
if (db == null) return Promise.reject(new Error('DB doesn\'t exist.'))
return db
.put({
_id: 'folder:' + folderPath,
_deleted: true
})
}
export function createNote (name, payload) {
const db = dbs.get(name)
if (db == null) return Promise.reject(new Error('DB doesn\'t exist.'))
function genNoteId () {
let id = 'note:' + util.randomBytes()
return db.get(id)
.then((doc) => {
if (doc == null) return id
return genNoteId()
})
}
return genNoteId()
.then((noteId) => {
return db
.put({}, payload, {
_id: noteId
})
})
}
export function updateNote (name, noteId, payload) {
const db = dbs.get(name)
if (db == null) return Promise.reject(new Error('DB doesn\'t exist.'))
return db.get(noteId)
.then((doc) => {
return db
.put({}, doc, payload, {
_id: doc._id,
_rev: doc._rev
})
})
}
export function deleteNote (name, noteId) {
const db = dbs.get(name)
if (db == null) return Promise.reject(new Error('DB doesn\'t exist.'))
return db.get(noteId)
.then((doc) => {
return db
.remove(doc)
})
}
export default {
init,
list,
load,
loadAll,
upsertFolder,
deleteFolder,
createNote,
updateNote,
deleteNote
}

View File

@ -0,0 +1,40 @@
import { combineReducers, createStore } from 'redux'
import { routerReducer } from 'react-router-redux'
import { Map, OrderedMap } from 'immutable'
function config (state = {}, action) {
return state
}
const defaultStatus = Map({
navWidth: 150,
noteListWidth: 200
})
function status (state = defaultStatus, action) {
// switch (action.type) {
// case 'UPDATE_STATUS':
// return Object.assign({}, state, action.payload)
// }
return state
}
const defaultStorageMap = OrderedMap()
function storageMap (state = defaultStorageMap, action) {
switch (action.type) {
case 'LOAD_ALL_STORAGES':
return action.payload.storageMap
}
return state
}
let reducer = combineReducers({
config,
status,
storageMap,
routing: routerReducer
})
let store = createStore(reducer)
export default store

68
src/main/lib/themes.js Normal file
View File

@ -0,0 +1,68 @@
const defaultUIColor = '#333'
const defaultUIActiveColor = '#4F9DFB'
const defaultBorderColor = '#DEDCDE'
const defaultUIFontSize = '12px'
const defaultUIFontFamily = 'Helvetica, Arial, sans-serif'
const defaultTheme = {
borderColor: defaultBorderColor,
border: 'solid 1px ' + defaultBorderColor,
activeBorderColor: defaultUIActiveColor,
activeBorder: 'solid 1px ' + defaultUIActiveColor,
input: `
border: solid 1px
${defaultBorderColor};
outline: none;
border-radius: 4px;
background-color: #FCFCFC;
color: ${defaultUIColor};
font-size: ${defaultUIFontSize};
font-family: ${defaultUIFontFamily};
`,
button: `
border: solid 1px
${defaultBorderColor};
outline: none;
border-radius: 4px;
background-color: #FCFCFC;
color: ${defaultUIColor};
font-size: ${defaultUIFontSize};
font-family: ${defaultUIFontFamily};
&:active {
background-color: #DCDCDC;
}
`,
navButton: `
display: block;
outline: none;
border: none;
background-color: transparent;
text-align: left;
color: ${defaultUIColor};
text-decoration: none;
font-size: ${defaultUIFontSize};
font-family: ${defaultUIFontFamily};
&:hover {
background-color: #EEE;
}
&:active {
background-color: #DCDCDC;
}
`,
navButtonActive: `
display: block;
outline: none;
border: none;
background-color: transparent;
text-align: left;
text-decoration: none;
font-size: ${defaultUIFontSize};
font-family: ${defaultUIFontFamily};
background-color: ${defaultUIActiveColor};
color: white;
`
}
export default {
default: defaultTheme
}

17
src/main/lib/util.js Normal file
View File

@ -0,0 +1,17 @@
const crypto = require('crypto')
const _ = require('lodash')
const defaultLength = 10
export function randomBytes (length = defaultLength) {
if (!_.isFinite(length)) length = defaultLength
return crypto.randomBytes(length).toString('hex')
}
const util = {
randomBytes
}
export default util
module.exports = util

27
src/main/routes.js Normal file
View File

@ -0,0 +1,27 @@
import React from 'react'
import { Route, IndexRedirect } from 'react-router'
import Main from './Main'
import StorageIndex from './contents/StorageIndex'
import NoteList from './contents/NoteList'
import StorageCreate from './contents/StorageCreate'
const routes = (
<Route path='/' component={Main}>
<IndexRedirect to='home' />
<Route path='home' component={NoteList} />
<Route path='storages/:storageId'>
<IndexRedirect to='all-notes' />
<Route path='all-notes' component={NoteList} />
<Route path='settings' component={StorageIndex} />
<Route path='folders/:folderId' component={NoteList} />
</Route>
<Route path='new-storage' component={StorageCreate} />
</Route>
)
export default routes

72
webpack.config.js Normal file
View File

@ -0,0 +1,72 @@
'use strict'
const path = require('path')
const webpack = require('webpack')
const NodeTargetPlugin = require('webpack/lib/node/NodeTargetPlugin')
const config = {
entry: {
main: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'./src/main/index.js'
]
},
resolve: {
extensions: ['.js', '.jsx'],
alias: {
'components': path.join(__dirname, 'src/components'),
'main': path.join(__dirname, 'src/main')
}
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new NodeTargetPlugin()
],
externals: [
'electron',
'styled-components',
'pouchdb',
'sander',
'electron-devtools-installer',
'octicons',
{
react: 'var React',
'react-dom': 'var ReactDOM',
'react-redux': 'var ReactRedux',
'redux': 'var Redux',
'immutable': 'var Immutable'
}
],
module: {
loaders: [
{
test: /\.js?$/,
use: [
{
loader: 'react-hot-loader/webpack'
},
{
loader: 'babel-loader'
}
],
include: path.join(__dirname, 'src')
}
]
},
output: {
path: path.join(__dirname, 'compiled'),
filename: '[name].js',
sourceMapFilename: '[name].map',
libraryTarget: 'commonjs2',
publicPath: 'http://localhost:8080/assets/'
},
devtool: 'eval',
devServer: {
hot: true
}
}
module.exports = config