1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

using Repository class

This commit is contained in:
Rokt33r
2016-05-03 15:37:14 +09:00
parent 1d2ca469fc
commit 45c627b0a5
8 changed files with 151 additions and 747 deletions

View File

@@ -1,312 +0,0 @@
const keygen = require('browser/lib/keygen')
const fs = require('fs')
const path = require('path')
const CSON = require('season')
const _ = require('lodash')
/**
* # Repo structure
*
* ```
* root
* |- data
* |-note1.cson
* |-note2.cson
* |-note3.cson
* |- boostrepo.json
* ```
*
* ## `boostrepo.json`
*
* ```js
* {
* name: String,
* author: String, // Same convention of package.json, `John Doe <email@example.com> (http://example.com)`
* remotes: [{
* name: String,
* url: String, // url of git remote
* branch: String // if branch isn't set, it will try to use `master` branch.
* }],
* folders: [{
* key: String // Unique sha1 hash key to identify folder,
* name: String,
* color: String // All CSS color formats available.
* }]
* }
* ```
*
* ## `data` directory
*
* Every note will be saved here as a single CSON file to `git diff` efficiently.
* > This is because CSON supports Multiline string.
* File name of each cson file will be used to identify note.
* Commonly, Boostnote will automatically generate sha1 key and use it as a file name when creating a new note.
*
* ### `note.cson`
*
* ```cson
* name: String
* tags: [String] // tags
* folder: String // hash key of folder
* mode: String // syntax mode
* title: String
* content: String
* createdAt: Date
* updatedAt: Date
* ```
*/
/**
* # Resolve directory.
*
* If directory doesn't exist, it will try to make a new one.
* If failed return rejected promise
*
* @param {String} targetPath Target path of directory
* @return {Promise} [description]
*/
function _resolveDirectory (targetPath) {
return new Promise(function (resolve, reject) {
// check the directory exists
fs.stat(targetPath, function (err, stat) {
// Reject errors except no suchfile
if (err != null && err.code !== 'ENOENT') {
return reject(err)
}
// Handle no suchfile error only
// Make new Folder by given path
if (err != null) {
return fs.mkdir(targetPath, function (err, stat) {
// If failed to make a new directory, reject it.
if (err != null) {
return reject(err)
}
resolve(targetPath)
})
}
// Check the target is not a directory
if (!stat.isDirectory()) {
return reject(new Error(targetPath + ' path is not a directory'))
}
resolve(targetPath)
})
})
}
function _generateDefaultRepoJSON (override) {
return Object.assign({
name: 'default',
remotes: [],
folders: [{
key: keygen(),
name: 'general',
color: 'green'
}]
}, override)
}
/**
* # Resolve RepoJSON
*
* Every repository must have `boostrepo.json`
*
* If boostrepo.json doesn't exist, create new one.
*
* @param {[type]} targetPath [description]
* @return {[type]} [description]
*/
function _resolveRepoJSON (targetPath) {
return new Promise(function checkIfExists (resolve, reject) {
// If JSON doesn't exist, make a new one.
if (CSON.resolve(targetPath) == null) {
let newRepoJSON = _generateDefaultRepoJSON()
return CSON.writeFile(targetPath, newRepoJSON, function (err) {
if (err != null) return reject(err)
resolve(newRepoJSON)
})
}
CSON.readFile(targetPath, function (err, obj) {
if (err != null) return reject(err)
resolve(obj)
})
})
}
/**
* Get all repository stats from localStorage
* it is stored to `repositories` key.
* if the data is corrupted, re-intialize it.
*
* @return {Array} registered repositories
* ```
* [{
* key: String,
* name: String,
* path: String // path of repository
* }]
* ```
*/
function getAllRepoStats () {
let data
try {
data = JSON.parse(localStorage.getItem('repoStats'))
if (!_.isArray(data)) {
throw new Error('Data is corrupted. it must be an array.')
}
} catch (err) {
console.log(err)
data = []
_saveAllRepoStats(data)
}
return data
}
/**
* Save All Repos
*/
function _saveAllRepoStats (repoStats) {
localStorage.setItem('repoStats', JSON.stringify(repoStats))
}
/**
* Add repository and return new Repo
* @param {Object} newRepo [description]
* ```
* {
* key: String,
* name: String,
* path: String,
* status: String,
* folders: [{
* key: String,
* color: String,
* name: String
* }],
* notes: [{
* key: String,
* title: String,
* content: String,
* folder: String,
* tags: [String],
* createdAt: Date,
* updatedAt: Date
* }]
* }
* ```
*/
function addRepo (newRepo) {
let { targetPath, name } = newRepo
targetPath = path.resolve(targetPath)
let repoStat, repoJSON
return _resolveDirectory(targetPath)
.then(function initializeRepo () {
let resolveDataDirectory = _resolveDirectory(path.resolve(targetPath, 'data'))
let resolveBoostrepoJSON = _resolveRepoJSON(path.resolve(targetPath, 'boostrepo.json'))
return Promise.all([resolveDataDirectory, resolveBoostrepoJSON])
})
.then(function saveToLocalStorage (data) {
let dataPath = data[0]
repoJSON = data[1]
let repoStats = getAllRepoStats()
// generate unique key
let key = keygen()
while (repoStats.some((repoStat) => repoStat.key === key)) {
key = keygen()
}
repoStat = {
key,
name: name,
path: targetPath
}
repoStats.push(repoStat)
_saveAllRepoStats(repoStats)
return dataPath
})
.then(function fetchNotes (dataPath) {
let noteNames = fs.readdirSync(dataPath)
let notes = noteNames
.map((noteName) => {
let notePath = path.resolve(dataPath, noteNames)
return new Promise(function (resolve, reject) {
CSON.readFile(notePath, function (err, obj) {
if (err != null) {
console.log(err)
return resolve(null)
}
obj.key = path.basename(noteName, '.cson')
return resolve(obj)
})
})
})
.filter((note) => note != null)
return Promise.all(notes)
})
.then(function resolveRepo (notes) {
return Object.assign({}, repoJSON, repoStat, {
status: 'IDLE',
notes
})
})
}
function removeRepo (repository) {
return new Promise(function (resolve, reject) {
try {
let repoStats = getAllRepoStats()
let targetIndex = _.findIndex(repoStats, {key: repository.key})
if (targetIndex > -1) {
repoStats.splice(targetIndex, 1)
}
_saveAllRepoStats(repoStats)
resolve(true)
} catch (err) {
reject(err)
}
})
}
function getRepos () {
let repoStats
try {
repoStats = JSON.parse(localStorage.getItem('repoStats'))
if (repoStats == null) repoStats = []
} catch (err) {
repoStats = []
}
return repoStats
.map((repoStat) => {
let repoJSON, notes
try {
repoJSON = CSON.readFileSync(path.resolve(repoStat.path, 'boostrepo.json'))
let notePaths = fs.readdirSync(path.resolve(repoStat.path, 'data'))
notes = notePaths.map((notePath) => CSON.readFileSync(notePath))
} catch (err) {
return Object.assign({}, repoStat, {
status: 'ERROR',
error: err
})
}
return Object.assign({}, repoJSON, repoStat, {
status: 'IDLE',
notes
})
})
}
export default {
getAllRepoStats,
addRepo,
removeRepo,
getRepos
}

View File

@@ -1,16 +1,21 @@
import React, { PropTypes } from 'react' import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './Repository.styl' import styles from './RepositorySection.styl'
import actions from 'browser/main/actions' import Repository from 'browser/lib/Repository'
import RepositoryManager from 'browser/lib/RepositoryManager'
class Repository extends React.Component { class RepositorySection extends React.Component {
handleUnlinkButtonClick (e) { handleUnlinkButtonClick (e) {
let { dispatch, repository } = this.props let { dispatch, repository } = this.props
RepositoryManager.removeRepo(repository) Repository.find(repository.key)
.then((repositoryInstance) => {
return repositoryInstance.unmount()
})
.then(() => { .then(() => {
dispatch(actions.removeRepo(repository)) dispatch({
type: 'REMOVE_REPOSITORY',
key: repository.key
})
}) })
} }
@@ -40,7 +45,7 @@ class Repository extends React.Component {
return ( return (
<div <div
className='Repository' className='RepositorySection'
styleName='root' styleName='root'
> >
<div styleName='header'> <div styleName='header'>
@@ -76,7 +81,7 @@ class Repository extends React.Component {
} }
} }
Repository.propTypes = { RepositorySection.propTypes = {
repository: PropTypes.shape({ repository: PropTypes.shape({
name: PropTypes.string, name: PropTypes.string,
folders: PropTypes.arrayOf(PropTypes.shape({ folders: PropTypes.arrayOf(PropTypes.shape({
@@ -86,4 +91,4 @@ Repository.propTypes = {
dispatch: PropTypes.func dispatch: PropTypes.func
} }
export default CSSModules(Repository, styles) export default CSSModules(RepositorySection, styles)

View File

@@ -1,11 +1,10 @@
import React, { PropTypes } from 'react' import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './SideNav.styl' import styles from './SideNav.styl'
import actions from 'browser/main/actions'
import { openModal, isModalOpen } from 'browser/lib/modal' import { openModal, isModalOpen } from 'browser/lib/modal'
import Preferences from '../../modal/Preferences' import Preferences from '../../modal/Preferences'
import CreateNewFolder from '../../modal/CreateNewFolder' import CreateNewFolder from '../../modal/CreateNewFolder'
import Repository from './Repository' import RepositorySection from './RepositorySection'
import NewRepositoryModal from '../../modal/NewRepositoryModal' import NewRepositoryModal from '../../modal/NewRepositoryModal'
const ipc = require('electron').ipcRenderer const ipc = require('electron').ipcRenderer
@@ -39,13 +38,11 @@ class SideNav extends React.Component {
handleFolderButtonClick (name) { handleFolderButtonClick (name) {
return (e) => { return (e) => {
let { dispatch } = this.props let { dispatch } = this.props
dispatch(actions.switchFolder(name))
} }
} }
handleAllFoldersButtonClick (e) { handleAllFoldersButtonClick (e) {
let { dispatch } = this.props let { dispatch } = this.props
dispatch(actions.setSearchFilter(''))
} }
handleNewRepositoryButtonClick (e) { handleNewRepositoryButtonClick (e) {
@@ -55,8 +52,8 @@ class SideNav extends React.Component {
render () { render () {
let { repositories, dispatch } = this.props let { repositories, dispatch } = this.props
let repositorieElements = repositories.map((repo) => { let repositorieElements = repositories.map((repo) => {
return <Repository return <RepositorySection
key={repo.name} key={repo.key}
repository={repo} repository={repo}
dispatch={dispatch} dispatch={dispatch}
/> />
@@ -108,17 +105,6 @@ class SideNav extends React.Component {
SideNav.propTypes = { SideNav.propTypes = {
dispatch: PropTypes.func, dispatch: PropTypes.func,
status: PropTypes.shape({
folderId: PropTypes.number
}),
user: PropTypes.object,
folders: PropTypes.array,
allArticles: PropTypes.array,
articles: PropTypes.array,
modified: PropTypes.array,
activeArticle: PropTypes.shape({
key: PropTypes.string
}),
repositories: PropTypes.array repositories: PropTypes.array
} }

View File

@@ -1,30 +1,31 @@
import React, { PropTypes} from 'react' import React, { PropTypes} from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { toggleTutorial } from '../actions'
import SideNav from './SideNav' import SideNav from './SideNav'
import ArticleTopBar from './ArticleTopBar' import ArticleTopBar from './ArticleTopBar'
import ArticleList from './ArticleList' import ArticleList from './ArticleList'
import ArticleDetail from './ArticleDetail' import ArticleDetail from './ArticleDetail'
import _ from 'lodash'
import { isModalOpen, closeModal } from 'browser/lib/modal' import { isModalOpen, closeModal } from 'browser/lib/modal'
import Repository from 'browser/lib/Repository'
const electron = require('electron') const electron = require('electron')
const remote = electron.remote const remote = electron.remote
const TEXT_FILTER = 'TEXT_FILTER'
const FOLDER_FILTER = 'FOLDER_FILTER'
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
const TAG_FILTER = 'TAG_FILTER'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
class HomePage extends React.Component { class HomePage extends React.Component {
componentDidMount () { componentDidMount () {
// React自体のKey入力はfocusされていないElementからは動かないため、 let { dispatch } = this.props
// `window`に直接かける
this.keyHandler = e => this.handleKeyDown(e) // Bind directly to window
window.addEventListener('keydown', this.keyHandler) // this.keyHandler = (e) => this.handleKeyDown(e)
// window.addEventListener('keydown', this.keyHandler)
// Reload all data
Repository.loadAll()
.then((allData) => {
dispatch({type: 'INIT_ALL', data: allData})
})
} }
componentWillUnmount () { componentWillUnmount () {
@@ -32,216 +33,72 @@ class HomePage extends React.Component {
} }
handleKeyDown (e) { handleKeyDown (e) {
if (isModalOpen()) { // if (isModalOpen()) {
if (e.keyCode === 13 && (OSX ? e.metaKey : e.ctrlKey)) { // if (e.keyCode === 13 && (OSX ? e.metaKey : e.ctrlKey)) {
remote.getCurrentWebContents().send('modal-confirm') // remote.getCurrentWebContents().send('modal-confirm')
} // }
if (e.keyCode === 27) closeModal() // if (e.keyCode === 27) closeModal()
return // return
} // }
let { status, dispatch } = this.props // let { dispatch } = this.props
let { top, list } = this.refs // let { top, list } = this.refs
let listElement = ReactDOM.findDOMNode(list) // let listElement = ReactDOM.findDOMNode(list)
if (status.isTutorialOpen) { // if (status.isTutorialOpen) {
dispatch(toggleTutorial()) // // dispatch(toggleTutorial())
e.preventDefault() // e.preventDefault()
return // return
} // }
if (e.keyCode === 13 && top.isInputFocused()) { // if (e.keyCode === 13 && top.isInputFocused()) {
listElement.focus() // listElement.focus()
return // return
} // }
if (e.keyCode === 27 && top.isInputFocused()) { // if (e.keyCode === 27 && top.isInputFocused()) {
if (status.search.length > 0) top.escape() // // if (status.search.length > 0) top.escape()
else listElement.focus() // // else listElement.focus()
return // return
} // }
// Search inputがfocusされていたら大体のキー入力は無視される。 // // Search inputがfocusされていたら大体のキー入力は無視される。
if (e.keyCode === 27) { // if (e.keyCode === 27) {
if (document.activeElement !== listElement) { // if (document.activeElement !== listElement) {
listElement.focus() // listElement.focus()
} else { // } else {
top.focusInput() // top.focusInput()
} // }
return // return
} // }
} }
render () { render () {
let { dispatch, status, user, articles, allArticles, modified, activeArticle, folders, tags } = this.props
let { repositories } = this.props
return ( return (
<div className='HomePage'> <div className='HomePage'>
<SideNav <SideNav
ref='nav' ref='nav'
dispatch={dispatch} {...this.props}
repositories={repositories}
status={status}
user={user}
folders={folders}
allArticles={allArticles}
articles={articles}
modified={modified}
activeArticle={activeArticle}
/> />
<ArticleTopBar <ArticleTopBar
ref='top' ref='top'
dispatch={dispatch} {...this.props}
status={status}
folders={folders}
/> />
<ArticleList <ArticleList
ref='list' ref='list'
dispatch={dispatch} {...this.props}
folders={folders}
articles={articles}
modified={modified}
activeArticle={activeArticle}
/> />
<ArticleDetail <ArticleDetail
ref='detail' ref='detail'
dispatch={dispatch} {...this.props}
status={status}
tags={tags}
user={user}
folders={folders}
modified={modified}
activeArticle={activeArticle}
/> />
</div> </div>
) )
} }
} }
// Ignore invalid key
function ignoreInvalidKey (key) {
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/) && !key.match(/^--/)
}
// Build filter object by key
function buildFilter (key) {
if (key.match(/^\/\/.+/)) {
return {type: FOLDER_EXACT_FILTER, value: key.match(/^\/\/(.+)$/)[1]}
}
if (key.match(/^\/.+/)) {
return {type: FOLDER_FILTER, value: key.match(/^\/(.+)$/)[1]}
}
if (key.match(/^#(.+)/)) {
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
}
return {type: TEXT_FILTER, value: key}
}
function isContaining (target, needle) {
return target.match(new RegExp(_.escapeRegExp(needle), 'i'))
}
function startsWith (target, needle) {
return target.match(new RegExp('^' + _.escapeRegExp(needle), 'i'))
}
function remap (state) {
let { user, folders, status } = state
let _articles = state.articles
let articles = _articles != null ? _articles.data : []
let modified = _articles != null ? _articles.modified : []
articles.sort((a, b) => {
let match = new Date(b.updatedAt) - new Date(a.updatedAt)
if (match === 0) match = b.title.localeCompare(a.title)
if (match === 0) match = b.key.localeCompare(a.key)
return match
})
let allArticles = articles.slice()
let tags = _.uniq(allArticles.reduce((sum, article) => {
if (!_.isArray(article.tags)) return sum
return sum.concat(article.tags)
}, []))
if (status.search.split(' ').some(key => key === '--unsaved')) articles = articles.filter(article => _.findWhere(modified, {key: article.key}))
// Filter articles
let filters = status.search.split(' ')
.map(key => key.trim())
.filter(ignoreInvalidKey)
.map(buildFilter)
let folderExactFilters = filters.filter(filter => filter.type === FOLDER_EXACT_FILTER)
let folderFilters = filters.filter(filter => filter.type === FOLDER_FILTER)
let textFilters = filters.filter(filter => filter.type === TEXT_FILTER)
let tagFilters = filters.filter(filter => filter.type === TAG_FILTER)
let targetFolders
if (folders != null) {
let exactTargetFolders = folders.filter(folder => {
return _.find(folderExactFilters, filter => filter.value.toLowerCase() === folder.name.toLowerCase())
})
let fuzzyTargetFolders = folders.filter(folder => {
return _.find(folderFilters, filter => startsWith(folder.name.replace(/_/g, ''), filter.value.replace(/_/g, '')))
})
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
if (targetFolders.length > 0) {
articles = articles.filter(article => {
return _.findWhere(targetFolders, {key: article.FolderKey})
})
}
if (textFilters.length > 0) {
articles = textFilters.reduce((articles, textFilter) => {
return articles.filter(article => {
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
})
}, articles)
}
if (tagFilters.length > 0) {
articles = tagFilters.reduce((articles, tagFilter) => {
return articles.filter(article => {
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
})
}, articles)
}
}
// Grab active article
let activeArticle = _.findWhere(articles, {key: status.articleKey})
if (activeArticle == null) activeArticle = articles[0]
let { repositories } = state
return {
user,
folders,
status,
articles,
allArticles,
modified,
activeArticle,
tags,
repositories
}
}
HomePage.propTypes = { HomePage.propTypes = {
status: PropTypes.shape(),
user: PropTypes.shape({
name: PropTypes.string
}),
articles: PropTypes.array,
allArticles: PropTypes.array,
modified: PropTypes.array,
activeArticle: PropTypes.shape(),
dispatch: PropTypes.func, dispatch: PropTypes.func,
folders: PropTypes.array,
tags: PropTypes.array,
repositories: PropTypes.array repositories: PropTypes.array
} }
export default connect(remap)(HomePage) export default connect((x) => x)(HomePage)

View File

@@ -1,200 +0,0 @@
// Action types
export const USER_UPDATE = 'USER_UPDATE'
export const ARTICLE_UPDATE = 'ARTICLE_UPDATE'
export const ARTICLE_DESTROY = 'ARTICLE_DESTROY'
export const ARTICLE_SAVE = 'ARTICLE_SAVE'
export const ARTICLE_SAVE_ALL = 'ARTICLE_SAVE_ALL'
export const ARTICLE_CACHE = 'ARTICLE_CACHE'
export const ARTICLE_UNCACHE = 'ARTICLE_UNCACHE'
export const ARTICLE_UNCACHE_ALL = 'ARTICLE_UNCACHE_ALL'
export const FOLDER_CREATE = 'FOLDER_CREATE'
export const FOLDER_UPDATE = 'FOLDER_UPDATE'
export const FOLDER_DESTROY = 'FOLDER_DESTROY'
export const FOLDER_REPLACE = 'FOLDER_REPLACE'
export const SWITCH_FOLDER = 'SWITCH_FOLDER'
export const SWITCH_ARTICLE = 'SWITCH_ARTICLE'
export const SET_SEARCH_FILTER = 'SET_SEARCH_FILTER'
export const SET_TAG_FILTER = 'SET_TAG_FILTER'
export const CLEAR_SEARCH = 'CLEAR_SEARCH'
export const TOGGLE_TUTORIAL = 'TOGGLE_TUTORIAL'
// Article status
export const NEW = 'NEW'
export function updateUser (input) {
return {
type: USER_UPDATE,
data: input
}
}
// DB
export function cacheArticle (key, article) {
return {
type: ARTICLE_CACHE,
data: { key, article }
}
}
export function uncacheArticle (key) {
return {
type: ARTICLE_UNCACHE,
data: { key }
}
}
export function uncacheAllArticles () {
return {
type: ARTICLE_UNCACHE_ALL
}
}
export function saveArticle (key, article, forceSwitch) {
return {
type: ARTICLE_SAVE,
data: { key, article, forceSwitch }
}
}
export function saveAllArticles () {
return {
type: ARTICLE_SAVE_ALL
}
}
export function updateArticle (article) {
return {
type: ARTICLE_UPDATE,
data: { article }
}
}
export function destroyArticle (key) {
return {
type: ARTICLE_DESTROY,
data: { key }
}
}
export function createFolder (folder) {
return {
type: FOLDER_CREATE,
data: { folder }
}
}
export function updateFolder (folder) {
return {
type: FOLDER_UPDATE,
data: { folder }
}
}
export function destroyFolder (key) {
return {
type: FOLDER_DESTROY,
data: { key }
}
}
export function replaceFolder (a, b) {
return {
type: FOLDER_REPLACE,
data: {
a,
b
}
}
}
export function switchFolder (folderName) {
return {
type: SWITCH_FOLDER,
data: folderName
}
}
export function switchArticle (articleKey) {
return {
type: SWITCH_ARTICLE,
data: {
key: articleKey
}
}
}
export function setSearchFilter (search) {
return {
type: SET_SEARCH_FILTER,
data: search
}
}
export function setTagFilter (tag) {
return {
type: SET_TAG_FILTER,
data: tag
}
}
export function clearSearch () {
return {
type: CLEAR_SEARCH
}
}
export function toggleTutorial () {
return {
type: TOGGLE_TUTORIAL
}
}
/**
* v0.6.* Actions
*/
export function addRepo (data) {
return {
type: 'ADD_REPOSITORY',
data
}
}
export function removeRepo (data) {
return {
type: 'REMOVE_REPOSITORY',
data
}
}
export default {
updateUser,
updateArticle,
destroyArticle,
cacheArticle,
uncacheArticle,
uncacheAllArticles,
saveArticle,
saveAllArticles,
createFolder,
updateFolder,
destroyFolder,
replaceFolder,
switchFolder,
switchArticle,
setSearchFilter,
setTagFilter,
clearSearch,
toggleTutorial,
// v0.6.*
addRepo,
removeRepo
}

View File

@@ -1,10 +1,8 @@
import React, { PropTypes } from 'react' import React, { PropTypes } from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './NewRepositoryModal.styl' import styles from './NewRepositoryModal.styl'
import linkState from 'browser/lib/linkState' import Repository from 'browser/lib/Repository'
import RepositoryManager from 'browser/lib/RepositoryManager'
import store from 'browser/main/store' import store from 'browser/main/store'
import actions from 'browser/main/actions'
const electron = require('electron') const electron = require('electron')
const remote = electron.remote const remote = electron.remote
@@ -81,13 +79,19 @@ class NewRepositoryModal extends React.Component {
let targetPath = this.state.path let targetPath = this.state.path
let name = this.state.name let name = this.state.name
RepositoryManager let repository = new Repository({
.addRepo({ name: name,
targetPath, path: targetPath
name })
})
.then((newRepo) => { repository
store.dispatch(actions.addRepo(newRepo)) .mount()
.then(() => repository.load())
.then((data) => {
store.dispatch({
type: 'ADD_REPOSITORY',
repository: data
})
this.props.close() this.props.close()
}) })
.catch((err) => { .catch((err) => {
@@ -98,6 +102,15 @@ class NewRepositoryModal extends React.Component {
}) })
} }
handleChange (e) {
let name = this.refs.nameInput.value
let path = this.refs.pathInput.value
this.setState({
name,
path
})
}
render () { render () {
return ( return (
<div className='NewRepositoryModal' <div className='NewRepositoryModal'
@@ -116,7 +129,8 @@ class NewRepositoryModal extends React.Component {
<div styleName='body-section-label'>Repository Name</div> <div styleName='body-section-label'>Repository Name</div>
<input styleName='body-section-input' <input styleName='body-section-input'
ref='nameInput' ref='nameInput'
valueLink={this.linkState('name')} value={this.state.name}
onChange={(e) => this.handleChange(e)}
/> />
</div> </div>
@@ -124,11 +138,13 @@ class NewRepositoryModal extends React.Component {
<div styleName='body-section-label'>Repository Path</div> <div styleName='body-section-label'>Repository Path</div>
<div styleName={!this.state.isPathSectionFocused ? 'body-section-path' : 'body-section-path--focus'}> <div styleName={!this.state.isPathSectionFocused ? 'body-section-path' : 'body-section-path--focus'}>
<input styleName='body-section-path-input' <input styleName='body-section-path-input'
valueLink={this.linkState('path')} ref='pathInput'
value={this.state.path}
style={styles.body_section_path_input} style={styles.body_section_path_input}
onFocus={(e) => this.handlePathFocus(e)} onFocus={(e) => this.handlePathFocus(e)}
onBlur={(e) => this.handlePathBlur(e)} onBlur={(e) => this.handlePathBlur(e)}
disabled={this.state.isBrowsingPath} disabled={this.state.isBrowsingPath}
onChange={(e) => this.handleChange(e)}
/> />
<button styleName='body-section-path-button' <button styleName='body-section-path-button'
onClick={(e) => this.handleBrowseButtonClick(e)} onClick={(e) => this.handleBrowseButtonClick(e)}
@@ -170,6 +186,4 @@ NewRepositoryModal.propTypes = {
close: PropTypes.func close: PropTypes.func
} }
NewRepositoryModal.prototype.linkState = linkState
export default CSSModules(NewRepositoryModal, styles) export default CSSModules(NewRepositoryModal, styles)

View File

@@ -1,5 +1,59 @@
import reducer from './reducer' import { combineReducers, createStore } from 'redux'
import { createStore } from 'redux' import _ from 'lodash'
/**
* Repositories
* ```
* repositories = [{
* key: String,
* name: String,
* path: String, // path of repository
* status: String, // status of repository [IDLE, LOADING, READY, ERROR]
* folders: {
* name: String,
* color: String
* },
* notes: [{
* key: String,
* title: String,
* content: String,
* folder: String,
* tags: [String],
* createdAt: Date,
* updatedAt: Date
* }]
* }]
* ```
*/
const initialRepositories = []
function repositories (state = initialRepositories, action) {
console.log(action)
switch (action.type) {
case 'INIT_ALL':
return action.data.slice()
case 'ADD_REPOSITORY':
{
let repos = state.slice()
repos.push(action.repository)
return repos
}
case 'REMOVE_REPOSITORY':
{
let repos = state.slice()
let targetIndex = _.findIndex(repos, {key: action.key})
if (targetIndex > -1) {
repos.splice(targetIndex, 1)
}
return repos
}
}
return state
}
let reducer = combineReducers({
repositories
})
let store = createStore(reducer) let store = createStore(reducer)