this.handleArticleListKeyDown(e)} className='ArticleList'>
- {articleElements}
-
- )
- }
-}
-
-ArticleList.propTypes = {
- dispatch: PropTypes.func,
- folders: PropTypes.array,
- articles: PropTypes.array,
- modified: PropTypes.array,
- activeArticle: PropTypes.shape()
-}
diff --git a/browser/main/HomePage/ArticleNavigator.js b/browser/main/HomePage/ArticleNavigator.js
deleted file mode 100644
index b8b69790..00000000
--- a/browser/main/HomePage/ArticleNavigator.js
+++ /dev/null
@@ -1,202 +0,0 @@
-import React, { PropTypes } from 'react'
-import { findWhere } from 'lodash'
-import { setSearchFilter, switchFolder, uncacheArticle, saveAllArticles, switchArticle, clearSearch } from '../actions'
-import { openModal, isModalOpen } from 'browser/lib/modal'
-import FolderMark from 'browser/components/FolderMark'
-import Preferences from '../modal/Preferences'
-import CreateNewFolder from '../modal/CreateNewFolder'
-import _ from 'lodash'
-import ModeIcon from 'browser/components/ModeIcon'
-
-const ipc = require('electron').ipcRenderer
-
-const BRAND_COLOR = '#18AF90'
-const OSX = global.process.platform === 'darwin'
-
-const preferenceTutorialElement = (
-
-)
-
-export default class ArticleNavigator extends React.Component {
- constructor (props) {
- super(props)
- this.newFolderHandler = e => {
- if (isModalOpen()) return true
- this.handleNewFolderButton(e)
- }
- }
-
- componentDidMount () {
- ipc.on('nav-new-folder', this.newFolderHandler)
- }
-
- componentWillUnmount () {
- ipc.removeListener('nav-new-folder', this.newFolderHandler)
- }
-
- handlePreferencesButtonClick (e) {
- openModal(Preferences)
- }
-
- handleNewFolderButton (e) {
- let { user } = this.props
- openModal(CreateNewFolder, {user: user})
- }
-
- handleFolderButtonClick (name) {
- return e => {
- let { dispatch } = this.props
- dispatch(switchFolder(name))
- }
- }
-
- handleAllFoldersButtonClick (e) {
- let { dispatch } = this.props
- dispatch(setSearchFilter(''))
- }
-
- handleUnsavedItemClick (article) {
- let { dispatch } = this.props
- return e => {
- let { articles } = this.props
- let isInArticleList = articles.some(_article => _article.key === article.key)
- if (!isInArticleList) dispatch(clearSearch())
- dispatch(switchArticle(article.key))
- }
- }
-
- handleUncacheButtonClick (article) {
- let { dispatch } = this.props
- return e => {
- dispatch(uncacheArticle(article.key))
- }
- }
-
- handleSaveAllClick (e) {
- let { dispatch } = this.props
- dispatch(saveAllArticles())
- }
-
- render () {
- let { status, user, folders, allArticles, modified, activeArticle } = this.props
- let { targetFolders } = status
- if (targetFolders == null) targetFolders = []
-
- let modifiedElements = modified.map(modifiedArticle => {
- let originalArticle = _.findWhere(allArticles, {key: modifiedArticle.key})
- if (originalArticle == null) return false
- let combinedArticle = Object.assign({}, originalArticle, modifiedArticle)
-
- let className = 'ArticleNavigator-unsaved-list-item'
- if (activeArticle && activeArticle.key === combinedArticle.key) className += ' active'
-
- return (
-
- )
- }).filter(modifiedArticle => modifiedArticle).sort((a, b) => a.updatedAt - b.updatedAt)
- let hasModified = modifiedElements.length > 0
-
- let folderElememts = folders.map((folder, index) => {
- let isActive = findWhere(targetFolders, {key: folder.key})
- let articleCount = allArticles.filter(article => article.FolderKey === folder.key && article.status !== 'NEW').length
-
- return (
-
- )
- }
-}
-
-ArticleNavigator.propTypes = {
- 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
- })
-}
-
diff --git a/browser/main/HomePage/ArticleTopBar.js b/browser/main/HomePage/ArticleTopBar.js
deleted file mode 100644
index 6fd94095..00000000
--- a/browser/main/HomePage/ArticleTopBar.js
+++ /dev/null
@@ -1,269 +0,0 @@
-import React, { PropTypes } from 'react'
-import ReactDOM from 'react-dom'
-import ExternalLink from 'browser/components/ExternalLink'
-import { setSearchFilter, clearSearch, toggleTutorial, saveArticle, switchFolder } from '../actions'
-import { isModalOpen } from 'browser/lib/modal'
-import keygen from 'browser/lib/keygen'
-import activityRecord from 'browser/lib/activityRecord'
-
-const electron = require('electron')
-const remote = electron.remote
-const ipc = electron.ipcRenderer
-
-const OSX = global.process.platform === 'darwin'
-
-const BRAND_COLOR = '#18AF90'
-
-const searchTutorialElement = (
-
-)
-
-export default class ArticleTopBar extends React.Component {
- constructor (props) {
- super(props)
-
- this.saveAllHandler = e => {
- if (isModalOpen()) return true
- this.handleSaveAllButtonClick(e)
- }
- this.focusSearchHandler = e => {
- if (isModalOpen()) return true
- this.focusInput(e)
- }
- this.newPostHandler = e => {
- if (isModalOpen()) return true
- this.handleNewPostButtonClick(e)
- }
-
- this.state = {
- isTooltipHidden: true,
- isLinksDropdownOpen: false
- }
- }
-
- componentDidMount () {
- this.searchInput = ReactDOM.findDOMNode(this.refs.searchInput)
- this.linksButton = ReactDOM.findDOMNode(this.refs.links)
- this.showLinksDropdown = e => {
- e.preventDefault()
- e.stopPropagation()
- if (!this.state.isLinksDropdownOpen) {
- this.setState({isLinksDropdownOpen: true})
- }
- }
- this.linksButton.addEventListener('click', this.showLinksDropdown)
- this.hideLinksDropdown = e => {
- if (this.state.isLinksDropdownOpen) {
- this.setState({isLinksDropdownOpen: false})
- }
- }
- document.addEventListener('click', this.hideLinksDropdown)
-
- // ipc.on('top-save-all', this.saveAllHandler)
- ipc.on('top-focus-search', this.focusSearchHandler)
- ipc.on('top-new-post', this.newPostHandler)
- }
-
- componentWillUnmount () {
- document.removeEventListener('click', this.hideLinksDropdown)
- this.linksButton.removeEventListener('click', this.showLinksDropdown())
-
- // ipc.removeListener('top-save-all', this.saveAllHandler)
- ipc.removeListener('top-focus-search', this.focusSearchHandler)
- ipc.removeListener('top-new-post', this.newPostHandler)
- }
-
- handleTooltipRequest (e) {
- if (this.searchInput.value.length === 0 && (document.activeElement === this.searchInput)) {
- this.setState({isTooltipHidden: false})
- } else {
- this.setState({isTooltipHidden: true})
- }
- }
-
- isInputFocused () {
- return document.activeElement === ReactDOM.findDOMNode(this.refs.searchInput)
- }
-
- escape () {
- let { status, dispatch } = this.props
- if (status.search.length > 0) {
- dispatch(clearSearch())
- return
- }
- }
-
- focusInput () {
- this.searchInput.focus()
- }
-
- blurInput () {
- this.searchInput.blur()
- }
-
- handleSearchChange (e) {
- let { dispatch } = this.props
-
- dispatch(setSearchFilter(e.target.value))
- this.handleTooltipRequest()
- }
-
- handleSearchClearButton (e) {
- this.searchInput.value = ''
- this.focusInput()
- }
-
- handleNewPostButtonClick (e) {
- let { dispatch, folders, status } = this.props
- let { targetFolders } = status
-
- let isFolderFilterApplied = targetFolders.length > 0
- let FolderKey = isFolderFilterApplied
- ? targetFolders[0].key
- : folders[0].key
-
- let newArticle = {
- key: keygen(),
- title: '',
- content: '',
- mode: 'markdown',
- tags: [],
- FolderKey: FolderKey,
- craetedAt: new Date(),
- updatedAt: new Date()
- }
-
- dispatch(saveArticle(newArticle.key, newArticle, true))
- if (isFolderFilterApplied) dispatch(switchFolder(targetFolders[0].name))
- remote.getCurrentWebContents().send('detail-title')
- activityRecord.emit('ARTICLE_CREATE')
- }
-
- handleTutorialButtonClick (e) {
- let { dispatch } = this.props
-
- dispatch(toggleTutorial())
- }
-
- render () {
- let { status } = this.props
- return (
-
-
-
-
-
this.handleSearchChange(e)}
- onBlur={e => this.handleSearchChange(e)}
- value={this.props.status.search}
- onChange={e => this.handleSearchChange(e)}
- placeholder='Search'
- type='text'
- />
- {
- this.props.status.search != null && this.props.status.search.length > 0
- ?
this.handleSearchClearButton(e)} className='ArticleTopBar-left-search-clear-button'>
- : null
- }
-
-
- - Search by tag : #{'{string}'}
- - Search by folder : /{'{folder_name}'}exact match : //{'{folder_name}'}
- - Only unsaved : --unsaved
-
-
-
-
- {status.isTutorialOpen ? searchTutorialElement : null}
-
-
- this.handleNewPostButtonClick(e)}>
-
- New Post ({OSX ? '⌘' : '^'} + n)
-
- {status.isTutorialOpen ? newPostTutorialElement : null}
-
-
-
-
-
this.handleTutorialButtonClick(e)}>?How to use
-
-
-
-
- {
- this.state.isLinksDropdownOpen
- ? (
-
-
- Boost official page
-
-
- Issues
-
-
- )
- : null
- }
-
-
- {status.isTutorialOpen ? (
-
-
this.handleTutorialButtonClick(e)} className='clickJammer'/>
-
- Also, you can open Finder!!
-
-
-
- Hope you to enjoy our app :D
- Press any key or click to escape tutorial mode
-
-
-
- ) : null}
-
-
- )
- }
-}
-
-ArticleTopBar.propTypes = {
- dispatch: PropTypes.func,
- status: PropTypes.shape({
- search: PropTypes.string
- }),
- folders: PropTypes.array
-}
diff --git a/browser/main/HomePage/TagLink.js b/browser/main/HomePage/TagLink.js
deleted file mode 100644
index 2193c42c..00000000
--- a/browser/main/HomePage/TagLink.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import React, { PropTypes } from 'react'
-import store from '../store'
-import { setTagFilter } from '../actions'
-
-export default class TagLink extends React.Component {
- handleClick (e) {
- store.dispatch(setTagFilter(this.props.tag))
- }
- render () {
- return (
-
this.handleClick(e)}>{this.props.tag}
- )
- }
-}
-
-TagLink.propTypes = {
- tag: PropTypes.string
-}
diff --git a/browser/main/HomePage/UserNavigator.js b/browser/main/HomePage/UserNavigator.js
deleted file mode 100644
index e260f749..00000000
--- a/browser/main/HomePage/UserNavigator.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import React, { Component, PropTypes } from 'react'
-import { Link } from 'react-router'
-import ProfileImage from 'browser/components/ProfileImage'
-
-export default class UserNavigator extends Component {
- renderUserList () {
- if (this.props.users == null) return null
-
- var users = this.props.users.map((user, index) => (
-
-
-
- {user.name}
- {index < 9 ? {'⌘' + (index + 1)}
: null}
-
-
- ))
-
- return (
-
- )
- }
-
- render () {
- return (
-
- {this.renderUserList()}
-
- +
- Create a new team
-
-
- )
- }
-}
-
-UserNavigator.propTypes = {
- users: PropTypes.array
-}
diff --git a/browser/main/HomePage/index.js b/browser/main/HomePage/index.js
deleted file mode 100644
index e8eb88dc..00000000
--- a/browser/main/HomePage/index.js
+++ /dev/null
@@ -1,238 +0,0 @@
-import React, { PropTypes} from 'react'
-import { connect } from 'react-redux'
-import ReactDOM from 'react-dom'
-import { toggleTutorial } from '../actions'
-import ArticleNavigator from './ArticleNavigator'
-import ArticleTopBar from './ArticleTopBar'
-import ArticleList from './ArticleList'
-import ArticleDetail from './ArticleDetail'
-import _ from 'lodash'
-import { isModalOpen, closeModal } from 'browser/lib/modal'
-
-const electron = require('electron')
-const remote = electron.remote
-
-const TEXT_FILTER = 'TEXT_FILTER'
-const FOLDER_FILTER = 'FOLDER_FILTER'
-const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
-const TAG_FILTER = 'TAG_FILTER'
-
-const OSX = global.process.platform === 'darwin'
-
-class HomePage extends React.Component {
- componentDidMount () {
- // React自体のKey入力はfocusされていないElementからは動かないため、
- // `window`に直接かける
- this.keyHandler = e => this.handleKeyDown(e)
- window.addEventListener('keydown', this.keyHandler)
- }
-
- componentWillUnmount () {
- window.removeEventListener('keydown', this.keyHandler)
- }
-
- handleKeyDown (e) {
- if (isModalOpen()) {
- if (e.keyCode === 27) closeModal()
- return
- }
-
- let { status, dispatch } = this.props
- let { top, list } = this.refs
- let listElement = ReactDOM.findDOMNode(list)
-
- if (status.isTutorialOpen) {
- dispatch(toggleTutorial())
- e.preventDefault()
- return
- }
-
- if (e.keyCode === 13 && top.isInputFocused()) {
- listElement.focus()
- return
- }
- if (e.keyCode === 27 && top.isInputFocused()) {
- if (status.search.length > 0) top.escape()
- else listElement.focus()
- return
- }
-
- // Search inputがfocusされていたら大体のキー入力は無視される。
- if (e.keyCode === 27) {
- if (document.activeElement !== listElement) {
- listElement.focus()
- } else {
- top.focusInput()
- }
- return
- }
- }
-
- render () {
- let { dispatch, status, user, articles, allArticles, modified, activeArticle, folders, tags } = this.props
-
- return (
-
- )
- }
-}
-
-// 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]
-
- return {
- user,
- folders,
- status,
- articles,
- allArticles,
- modified,
- activeArticle,
- tags
- }
-}
-
-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,
- folders: PropTypes.array,
- tags: PropTypes.array
-}
-
-export default connect(remap)(HomePage)
diff --git a/browser/main/Main.js b/browser/main/Main.js
new file mode 100644
index 00000000..8c688833
--- /dev/null
+++ b/browser/main/Main.js
@@ -0,0 +1,159 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './Main.styl'
+import { connect } from 'react-redux'
+import SideNav from './SideNav'
+import TopBar from './TopBar'
+import NoteList from './NoteList'
+import Detail from './Detail'
+import dataApi from 'browser/main/lib/dataApi'
+import StatusBar from './StatusBar'
+import _ from 'lodash'
+import ConfigManager from 'browser/main/lib/ConfigManager'
+import modal from 'browser/main/lib/modal'
+import InitModal from 'browser/main/modals/InitModal'
+
+class Main extends React.Component {
+ constructor (props) {
+ super(props)
+
+ let { config } = props
+
+ this.state = {
+ isSliderFocused: false,
+ listWidth: config.listWidth
+ }
+ }
+
+ componentDidMount () {
+ let { dispatch } = this.props
+
+ // Reload all data
+ dataApi.init()
+ .then((data) => {
+ dispatch({
+ type: 'INIT_ALL',
+ storages: data.storages,
+ notes: data.notes
+ })
+
+ if (data.storages.length < 1) {
+ modal.open(InitModal)
+ }
+ })
+ }
+
+ handleSlideMouseDown (e) {
+ e.preventDefault()
+ this.setState({
+ isSliderFocused: true
+ })
+ }
+
+ handleMouseUp (e) {
+ if (this.state.isSliderFocused) {
+ this.setState({
+ isSliderFocused: false
+ }, () => {
+ let { dispatch } = this.props
+ let newListWidth = this.state.listWidth
+ // TODO: ConfigManager should dispatch itself.
+ ConfigManager.set({listWidth: newListWidth})
+ dispatch({
+ type: 'SET_LIST_WIDTH',
+ listWidth: newListWidth
+ })
+ })
+ }
+ }
+
+ handleMouseMove (e) {
+ if (this.state.isSliderFocused) {
+ let offset = this.refs.body.getBoundingClientRect().left
+ let newListWidth = e.pageX - offset
+ if (newListWidth < 10) {
+ newListWidth = 10
+ } else if (newListWidth > 600) {
+ newListWidth = 600
+ }
+ this.setState({
+ listWidth: newListWidth
+ })
+ }
+ }
+
+ render () {
+ let { config } = this.props
+
+ return (
+
this.handleMouseMove(e)}
+ onMouseUp={(e) => this.handleMouseUp(e)}
+ >
+
+
+
+
+
this.handleSlideMouseDown(e)}
+ draggable='false'
+ >
+
+
+
+
+
+
+ )
+ }
+}
+
+Main.propTypes = {
+ dispatch: PropTypes.func,
+ repositories: PropTypes.array
+}
+
+export default connect((x) => x)(CSSModules(Main, styles))
diff --git a/browser/main/Main.styl b/browser/main/Main.styl
new file mode 100644
index 00000000..f16a583c
--- /dev/null
+++ b/browser/main/Main.styl
@@ -0,0 +1,30 @@
+.root
+ absolute top left bottom right
+
+.body
+ absolute right top
+ bottom $statusBar-height - 1
+ left $sideNav-width
+
+.body--expanded
+ @extend .body
+ left $sideNav--folded-width
+
+.slider
+ absolute top bottom
+ width 1px
+ background-color $ui-borderColor
+ border-width 0
+ border-style solid
+ border-color $ui-borderColor
+
+.slider--active
+ @extend .slider
+ background-color $ui-button--active-backgroundColor
+
+.slider-hitbox
+ absolute top bottom left right
+ width 7px
+ left -3px
+ z-index 10
+ cursor col-resize
diff --git a/browser/main/MainPage.js b/browser/main/MainPage.js
deleted file mode 100644
index fb729bd5..00000000
--- a/browser/main/MainPage.js
+++ /dev/null
@@ -1,54 +0,0 @@
-const electron = require('electron')
-const ipc = electron.ipcRenderer
-import React, { PropTypes } from 'react'
-import HomePage from './HomePage'
-
-export default class MainContainer extends React.Component {
- constructor (props) {
- super(props)
- this.state = {updateAvailable: false}
- }
-
- componentDidMount () {
- ipc.on('update-available', function (message) {
- this.setState({updateAvailable: true})
- }.bind(this))
- }
-
- updateApp () {
- ipc.send('update-app', 'Deal with it.')
- }
-
- handleWheel (e) {
- if (e.ctrlKey && global.process.platform !== 'darwin') {
- if (window.document.body.style.zoom == null) {
- window.document.body.style.zoom = 1
- }
-
- let zoom = Number(window.document.body.style.zoom)
- if (e.deltaY > 0 && zoom < 4) {
- document.body.style.zoom = zoom + 0.05
- } else if (e.deltaY < 0 && zoom > 0.5) {
- document.body.style.zoom = zoom - 0.05
- }
- }
- }
-
- render () {
- return (
-
this.handleWheel(e)}
- >
- {this.state.updateAvailable ? (
- Update available!
- ) : null}
-
-
- )
- }
-}
-
-MainContainer.propTypes = {
- children: PropTypes.element
-}
diff --git a/browser/main/HomePage/ArticleDetail/ShareButton.js b/browser/main/NoteDetail/ShareButton.js
similarity index 100%
rename from browser/main/HomePage/ArticleDetail/ShareButton.js
rename to browser/main/NoteDetail/ShareButton.js
diff --git a/browser/main/NoteList/NoteList.styl b/browser/main/NoteList/NoteList.styl
new file mode 100644
index 00000000..d7166f9e
--- /dev/null
+++ b/browser/main/NoteList/NoteList.styl
@@ -0,0 +1,93 @@
+.root
+ absolute left bottom
+ border-top $ui-border
+ border-bottom $ui-border
+ overflow auto
+ top $topBar-height - 1
+
+.item
+ position relative
+ height 80px
+ border-bottom $ui-border
+ padding 0 5px
+ user-select none
+ cursor pointer
+ transition background-color 0.15s
+ &:hover
+ background-color alpha($ui-active-color, 10%)
+
+.item--active
+ @extend .item
+ .item-border
+ border-color $ui-active-color
+
+.item-border
+ absolute top bottom left right
+ border-style solid
+ border-width 2px
+ border-color transparent
+ transition 0.15s
+
+.item-info
+ height 30px
+ clearfix()
+ font-size 12px
+ color $ui-inactive-text-color
+ line-height 30px
+ overflow-y hidden
+
+.item-info-left
+ float left
+ overflow ellipsis
+
+.item-info-left-folder
+ border-left 4px solid transparent
+ padding 2px 5px
+ color $ui-text-color
+.item-info-left-folder-surfix
+ font-size 10px
+ margin-left 5px
+ color $ui-inactive-text-color
+.item-info-right
+ float right
+
+.item-title
+ height 20px
+ line-height 20px
+ padding 0 5px 0 0
+ font-weight bold
+ overflow ellipsis
+ color $ui-text-color
+.item-title-icon
+ font-size 12px
+ color $ui-inactive-text-color
+ padding-right 3px
+.item-title-empty
+ font-weight normal
+ color $ui-inactive-text-color
+
+.item-tagList
+ height 30px
+ font-size 12px
+ line-height 30px
+ overflow ellipsis
+
+.item-tagList-icon
+ vertical-align middle
+ color $ui-button-color
+
+.item-tagList-item
+ margin 0 4px
+ padding 0 4px
+ height 20px
+ border-radius 3px
+ vertical-align middle
+ border-style solid
+ border-color $ui-borderColor
+ border-width 0 0 0 3px
+ background-color $ui-backgroundColor
+
+.item-tagList-empty
+ color $ui-inactive-text-color
+ vertical-align middle
+
diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js
new file mode 100644
index 00000000..f60a45da
--- /dev/null
+++ b/browser/main/NoteList/index.js
@@ -0,0 +1,299 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './NoteList.styl'
+import moment from 'moment'
+import _ from 'lodash'
+import ee from 'browser/main/lib/eventEmitter'
+
+class NoteList extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.selectNextNoteHandler = () => {
+ console.log('fired next')
+ this.selectNextNote()
+ }
+ this.selectPriorNoteHandler = () => {
+ this.selectPriorNote()
+ }
+ }
+
+ componentDidMount () {
+ this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
+ ee.on('list:next', this.selectNextNoteHandler)
+ ee.on('list:prior', this.selectPriorNoteHandler)
+ }
+
+ componentWillUnmount () {
+ clearInterval(this.refreshTimer)
+
+ ee.off('list:next', this.selectNextNoteHandler)
+ ee.off('list:prior', this.selectPriorNoteHandler)
+ }
+
+ componentDidUpdate () {
+ let { location } = this.props
+ if (this.notes.length > 0 && location.query.key == null) {
+ let { router } = this.context
+ router.replace({
+ pathname: location.pathname,
+ query: {
+ key: this.notes[0].uniqueKey
+ }
+ })
+ return
+ }
+
+ // Auto scroll
+ if (_.isString(location.query.key)) {
+ let targetIndex = _.findIndex(this.notes, (note) => {
+ return note.uniqueKey === location.query.key
+ })
+ if (targetIndex > -1) {
+ let list = this.refs.root
+ let item = list.childNodes[targetIndex]
+
+ let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
+ if (overflowBelow) {
+ list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
+ }
+ let overflowAbove = list.scrollTop > item.offsetTop
+ if (overflowAbove) {
+ list.scrollTop = item.offsetTop
+ }
+ }
+ }
+ }
+
+ selectPriorNote () {
+ if (this.notes == null || this.notes.length === 0) {
+ return
+ }
+ let { router } = this.context
+ let { location } = this.props
+
+ let targetIndex = _.findIndex(this.notes, (note) => {
+ return note.uniqueKey === location.query.key
+ })
+
+ if (targetIndex === 0) {
+ return
+ }
+ targetIndex--
+ if (targetIndex < 0) targetIndex = 0
+
+ router.push({
+ pathname: location.pathname,
+ query: {
+ key: this.notes[targetIndex].uniqueKey
+ }
+ })
+ }
+
+ selectNextNote () {
+ if (this.notes == null || this.notes.length === 0) {
+ return
+ }
+ let { router } = this.context
+ let { location } = this.props
+
+ let targetIndex = _.findIndex(this.notes, (note) => {
+ return note.uniqueKey === location.query.key
+ })
+
+ if (targetIndex === this.notes.length - 1) {
+ return
+ }
+ targetIndex++
+ if (targetIndex < 0) targetIndex = 0
+ else if (targetIndex > this.notes.length - 1) targetIndex === this.notes.length - 1
+
+ router.push({
+ pathname: location.pathname,
+ query: {
+ key: this.notes[targetIndex].uniqueKey
+ }
+ })
+ ee.emit('list:moved')
+ }
+
+ handleNoteListKeyDown (e) {
+ if (e.metaKey || e.ctrlKey) return true
+
+ // if (e.keyCode === 65 && !e.shiftKey) {
+ // e.preventDefault()
+ // remote.getCurrentWebContents().send('top-new-post')
+ // }
+
+ // if (e.keyCode === 65 && e.shiftKey) {
+ // e.preventDefault()
+ // remote.getCurrentWebContents().send('nav-new-folder')
+ // }
+
+ // if (e.keyCode === 68) {
+ // e.preventDefault()
+ // remote.getCurrentWebContents().send('detail-delete')
+ // }
+
+ // if (e.keyCode === 84) {
+ // e.preventDefault()
+ // remote.getCurrentWebContents().send('detail-title')
+ // }
+
+ // if (e.keyCode === 69) {
+ // e.preventDefault()
+ // }
+
+ // if (e.keyCode === 83) {
+ // e.preventDefault()
+ // remote.getCurrentWebContents().send('detail-save')
+ // }
+
+ if (e.keyCode === 38) {
+ e.preventDefault()
+ this.selectPriorNote()
+ }
+
+ if (e.keyCode === 40) {
+ e.preventDefault()
+ this.selectNextNote()
+ }
+ }
+
+ getNotes () {
+ let { storages, notes, params, location } = this.props
+
+ if (location.pathname.match(/\/home/)) {
+ return notes
+ }
+
+ if (location.pathname.match(/\/starred/)) {
+ return notes
+ .filter((note) => note.isStarred)
+ }
+
+ let storageKey = params.storageKey
+ let folderKey = params.folderKey
+ let storage = _.find(storages, {key: storageKey})
+ if (storage == null) return []
+
+ let folder = _.find(storage.folders, {key: folderKey})
+ if (folder == null) {
+ return notes
+ .filter((note) => note.storage === storageKey)
+ }
+
+ return notes
+ .filter((note) => note.folder === folderKey)
+ }
+
+ handleNoteClick (uniqueKey) {
+ return (e) => {
+ let { router } = this.context
+ let { location } = this.props
+
+ router.push({
+ pathname: location.pathname,
+ query: {
+ key: uniqueKey
+ }
+ })
+ }
+ }
+
+ render () {
+ let { location, storages, notes } = this.props
+ this.notes = notes = this.getNotes()
+ .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
+
+ let noteList = notes
+ .map((note) => {
+ let storage = _.find(storages, {key: note.storage})
+ let folder = _.find(storage.folders, {key: note.folder})
+ let tagElements = _.isArray(note.tags)
+ ? note.tags.map((tag) => {
+ return (
+
+ {tag}
+
+ )
+ })
+ : []
+ let isActive = location.query.key === note.uniqueKey
+ return (
+
this.handleNoteClick(note.uniqueKey)(e)}
+ >
+
+
+
+
+
+ {folder.name}
+ in {storage.name}
+
+
+
+
+ {moment(note.updatedAt).fromNow()}
+
+
+
+
+
+ {note.type === 'SNIPPET_NOTE'
+ ?
+ :
+ }
+ {note.title.trim().length > 0
+ ? note.title
+ : Empty
+ }
+
+
+
+
+ {tagElements.length > 0
+ ? tagElements
+ : Not tagged yet
+ }
+
+
+ )
+ })
+
+ return (
+
this.handleNoteListKeyDown(e)}
+ style={this.props.style}
+ >
+ {noteList}
+
+ )
+ }
+}
+NoteList.contextTypes = {
+ router: PropTypes.shape([])
+}
+
+NoteList.propTypes = {
+ dispatch: PropTypes.func,
+ repositories: PropTypes.array,
+ style: PropTypes.shape({
+ width: PropTypes.number
+ })
+}
+
+export default CSSModules(NoteList, styles)
diff --git a/browser/main/SideNav/SideNav.styl b/browser/main/SideNav/SideNav.styl
new file mode 100644
index 00000000..7d9dc948
--- /dev/null
+++ b/browser/main/SideNav/SideNav.styl
@@ -0,0 +1,122 @@
+.root
+ absolute top left
+ bottom $statusBar-height - 1
+ width $sideNav-width
+ border-right $ui-border
+ border-bottom $ui-border
+ background-color $ui-backgroundColor
+ user-select none
+ color $ui-text-color
+
+.top
+ height $topBar-height
+ border-bottom $ui-border
+
+.top-menu
+ navButtonColor()
+ height $topBar-height - 1
+ padding 0 10px
+ font-size 14px
+ width 100%
+ text-align left
+
+.top-menu-label
+ margin-left 5px
+
+.menu
+ margin-top 15px
+
+.menu-button
+ navButtonColor()
+ height 44px
+ padding 0 10px
+ font-size 14px
+ width 100%
+ text-align left
+
+.menu-button--active
+ @extend .menu-button
+ background-color $ui-button--active-backgroundColor
+ color $ui-button--active-color
+ &:hover
+ background-color $ui-button--active-backgroundColor
+
+.menu-button-label
+ margin-left 5px
+
+.storageList
+ absolute left right
+ bottom 44px
+ top 178px
+ overflow-y auto
+
+.storageList-empty
+ padding 0 10px
+ margin-top 15px
+ line-height 24px
+ color $ui-inactive-text-color
+
+.navToggle
+ navButtonColor()
+ display block
+ position absolute
+ right 5px
+ bottom 5px
+ border-radius 16.5px
+ height 34px
+ width 34px
+ line-height 32px
+ padding 0
+
+.root--folded
+ @extend .root
+ width 44px
+ .storageList-empty
+ white-space nowrap
+ transform rotate(90deg)
+ .top-menu
+ width 44px - 1
+ text-align center
+ &:hover .top-menu-label
+ width 100px
+ .top-menu-label
+ position fixed
+ display inline-block
+ height 34px
+ left 44px
+ width 0
+ margin-top -5px
+ margin-left 0
+ overflow hidden
+ background-color $ui-tooltip-backgroundColor
+ z-index 10
+ color white
+ line-height 34px
+ border-top-right-radius 5px
+ border-bottom-right-radius 5px
+ transition width 0.15s
+ pointer-events none
+ .menu-button, .menu-button--active
+ width 44px - 1
+ text-align center
+ &:hover .menu-button-label
+ width 100px
+ // TODO: extract tooltip style to a mixin
+ .menu-button-label
+ position fixed
+ display inline-block
+ height 34px
+ left 44px
+ width 0
+ padding-left 0
+ margin-top -9px
+ margin-left 0
+ overflow ellipsis
+ background-color $ui-tooltip-backgroundColor
+ z-index 10
+ color white
+ line-height 34px
+ border-top-right-radius 5px
+ border-bottom-right-radius 5px
+ transition width 0.15s
+ pointer-events none
diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js
new file mode 100644
index 00000000..3fb5caf0
--- /dev/null
+++ b/browser/main/SideNav/StorageItem.js
@@ -0,0 +1,95 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './StorageItem.styl'
+import { hashHistory } from 'react-router'
+
+class StorageItem extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ isOpen: true
+ }
+ }
+
+ handleToggleButtonClick (e) {
+ this.setState({
+ isOpen: !this.state.isOpen
+ })
+ }
+
+ handleHeaderInfoClick (e) {
+ let { storage } = this.props
+ hashHistory.push('/storages/' + storage.key)
+ }
+
+ handleFolderButtonClick (folderKey) {
+ return (e) => {
+ let { storage } = this.props
+ hashHistory.push('/storages/' + storage.key + '/folders/' + folderKey)
+ }
+ }
+
+ render () {
+ let { storage, location } = this.props
+ let folderList = storage.folders.map((folder) => {
+ let isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key))
+ return
this.handleFolderButtonClick(folder.key)(e)}
+ >
+
+ {folder.name}
+
+
+ })
+
+ let isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
+
+ return (
+
+
+ this.handleToggleButtonClick(e)}
+ >
+
+
+ this.handleHeaderInfoClick(e)}
+ >
+
+ {storage.name}
+
+
+ ({storage.path})
+
+
+
+ {this.state.isOpen &&
+
+ {folderList}
+
+ }
+
+ )
+ }
+}
+
+StorageItem.propTypes = {
+}
+
+export default CSSModules(StorageItem, styles)
diff --git a/browser/main/SideNav/StorageItem.styl b/browser/main/SideNav/StorageItem.styl
new file mode 100644
index 00000000..171aacc3
--- /dev/null
+++ b/browser/main/SideNav/StorageItem.styl
@@ -0,0 +1,89 @@
+.root
+ width 100%
+ user-select none
+.header
+ position relative
+ height 30px
+ width 100%
+ &:hover
+ background-color $ui-button--hover-backgroundColor
+ &:active
+ .header-toggleButton
+ color white
+.header--active
+ @extend .header
+ .header-info
+ color $ui-button--active-color
+ background-color $ui-button--active-backgroundColor
+ .header-toggleButton
+ color white
+ &:active
+ color white
+
+.header-toggleButton
+ position absolute
+ left 0
+ width 25px
+ height 30px
+ padding 0
+ border none
+ color $ui-inactive-text-color
+ background-color transparent
+ &:hover
+ color $ui-text-color
+ &:active
+ color $ui-active-color
+
+.header-info
+ display block
+ width 100%
+ height 30px
+ padding-left 25px
+ padding-right 10px
+ line-height 30px
+ cursor pointer
+ font-size 14px
+ border none
+ overflow ellipsis
+ text-align left
+ background-color transparent
+ color $ui-inactive-text-color
+ &:active
+ color $ui-button--active-color
+ background-color $ui-button--active-backgroundColor
+
+.header-info-path
+ font-size 10px
+ margin 0 5px
+
+.folderList-item
+ display block
+ width 100%
+ height 3 0px
+ background-color transparent
+ color $ui-inactive-text-color
+ padding 0
+ margin 2px 0
+ text-align left
+ border none
+ font-size 14px
+ &:hover
+ background-color $ui-button--hover-backgroundColor
+ &:active
+ color $ui-button--active-color
+ background-color $ui-button--active-backgroundColor
+.folderList-item--active
+ @extend .folderList-item
+ color $ui-button--active-color
+ background-color $ui-button--active-backgroundColor
+ &:hover
+ color $ui-button--active-color
+ background-color $ui-button--active-backgroundColor
+.folderList-item-name
+ display block
+ padding 0 10px
+ height 30px
+ line-height 30px
+ border-width 0 0 0 4px
+ border-style solid
+ border-color transparent
diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js
new file mode 100644
index 00000000..d688b9d3
--- /dev/null
+++ b/browser/main/SideNav/index.js
@@ -0,0 +1,116 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './SideNav.styl'
+import { openModal } from 'browser/main/lib/modal'
+import PreferencesModal from '../modals/PreferencesModal'
+import ConfigManager from 'browser/main/lib/ConfigManager'
+import StorageItem from './StorageItem'
+
+const electron = require('electron')
+const { remote } = electron
+
+class SideNav extends React.Component {
+ // TODO: should not use electron stuff v0.7
+ handleMenuButtonClick (e) {
+ openModal(PreferencesModal)
+ }
+
+ handleHomeButtonClick (e) {
+ let { router } = this.context
+ router.push('/home')
+ }
+
+ handleStarredButtonClick (e) {
+ let { router } = this.context
+ router.push('/starred')
+ }
+
+ handleToggleButtonClick (e) {
+ let { dispatch, config } = this.props
+
+ ConfigManager.set({isSideNavFolded: !config.isSideNavFolded})
+ dispatch({
+ type: 'SET_IS_SIDENAV_FOLDED',
+ isFolded: !config.isSideNavFolded
+ })
+ }
+
+ render () {
+ let { storages, location, config } = this.props
+
+ let isFolded = config.isSideNavFolded
+ let isHomeActive = location.pathname.match(/^\/home$/)
+ let isStarredActive = location.pathname.match(/^\/starred$/)
+ let storageList = storages.map((storage) => {
+ return
+ })
+
+ return (
+
+
+ this.handleMenuButtonClick(e)}
+ >
+
+ Menu
+
+
+
+
+ this.handleHomeButtonClick(e)}
+ >
+
+ Home
+
+ this.handleStarredButtonClick(e)}
+ >
+
+ Starred
+
+
+
+
+ {storageList.length > 0 ? storageList : (
+
No storage mount.
+ )}
+
+ {false &&
+
this.handleToggleButtonClick(e)}
+ >
+ {isFolded
+ ?
+ :
+ }
+
+ }
+
+ )
+ }
+}
+
+SideNav.contextTypes = {
+ router: PropTypes.shape({})
+}
+
+SideNav.propTypes = {
+ dispatch: PropTypes.func,
+ storages: PropTypes.array,
+ config: PropTypes.shape({
+ isSideNavFolded: PropTypes.bool
+ }),
+ location: PropTypes.shape({
+ pathname: PropTypes.string
+ })
+}
+
+export default CSSModules(SideNav, styles)
diff --git a/browser/main/StatusBar/StatusBar.styl b/browser/main/StatusBar/StatusBar.styl
new file mode 100644
index 00000000..f91d5930
--- /dev/null
+++ b/browser/main/StatusBar/StatusBar.styl
@@ -0,0 +1,38 @@
+.root
+ absolute bottom left right
+ height $statusBar-height - 1
+ background-color $ui-backgroundColor
+
+.pathname
+ absolute left
+ height 24px
+ overflow ellipsis
+ right 185px
+ line-height 24px
+ font-size 12px
+ padding 0 15px
+ color $ui-inactive-text-color
+
+.zoom
+ navButtonColor()
+ absolute right
+ height 24px
+ width 60px
+ border-width 0 1px
+ border-style solid
+ border-color $ui-borderColor
+
+.update
+ navButtonColor()
+ position absolute
+ right 60px
+ height 24px
+ width 125px
+ border-width 0 0 0 1px
+ border-style solid
+ border-color $ui-borderColor
+ &:active .update-icon
+ color white
+
+.update-icon
+ color $brand-color
diff --git a/browser/main/StatusBar/index.js b/browser/main/StatusBar/index.js
new file mode 100644
index 00000000..4a1ade40
--- /dev/null
+++ b/browser/main/StatusBar/index.js
@@ -0,0 +1,94 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './StatusBar.styl'
+import ZoomManager from 'browser/main/lib/ZoomManager'
+
+const electron = require('electron')
+const ipc = electron.ipcRenderer
+const { remote } = electron
+const { Menu, MenuItem, dialog } = remote
+
+const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3]
+
+class StatusBar extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ updateAvailable: false
+ }
+ }
+
+ componentDidMount () {
+ ipc.on('update-available', function (message) {
+ this.setState({updateAvailable: true})
+ }.bind(this))
+ }
+
+ updateApp () {
+ let index = dialog.showMessageBox(remote.getCurrentWindow(), {
+ type: 'warning',
+ message: 'Update Boostnote',
+ detail: 'New Boostnote is ready to be installed.',
+ buttons: ['Restart & Install', 'Not Now']
+ })
+
+ if (index === 0) {
+ ipc.send('update-app', 'Deal with it.')
+ }
+ }
+
+ handleZoomButtonClick (e) {
+ let menu = new Menu()
+
+ zoomOptions.forEach((zoom) => {
+ menu.append(new MenuItem({
+ label: Math.floor(zoom * 100) + '%',
+ click: () => this.handleZoomMenuItemClick(zoom)
+ }))
+ })
+
+ menu.popup(remote.getCurrentWindow())
+ }
+
+ handleZoomMenuItemClick (zoomFactor) {
+ let { dispatch } = this.props
+ ZoomManager.setZoom(zoomFactor)
+ dispatch({
+ type: 'SET_ZOOM',
+ zoom: zoomFactor
+ })
+ }
+
+ render () {
+ let { config, location } = this.props
+
+ return (
+
+
{location.pathname + location.search}
+ {this.state.updateAvailable
+ ?
+ Update is available!
+
+ : null
+ }
+
this.handleZoomButtonClick(e)}
+ >
+
+ {Math.floor(config.zoom * 100)}%
+
+
+ )
+ }
+}
+
+StatusBar.propTypes = {
+ config: PropTypes.shape({
+ zoom: PropTypes.number
+ })
+}
+
+export default CSSModules(StatusBar, styles)
diff --git a/browser/main/TopBar/TopBar.styl b/browser/main/TopBar/TopBar.styl
new file mode 100644
index 00000000..ae6a18e9
--- /dev/null
+++ b/browser/main/TopBar/TopBar.styl
@@ -0,0 +1,106 @@
+.root
+ position relative
+ width 100%
+ background-color $ui-backgroundColor
+ height $topBar-height - 1
+
+$control-height = 34px
+
+.control
+ position absolute
+ top 8px
+ left 8px
+ right 8px
+ height $control-height
+ border $ui-border
+ border-radius 20px
+ overflow hidden
+.control-search
+ absolute top left bottom
+ right 40px
+ background-color white
+
+.control-search-icon
+ absolute top bottom left
+ line-height 32px
+ width 35px
+ color $ui-inactive-text-color
+
+.control-search-input
+ display block
+ absolute top bottom right
+ left 30px
+ input
+ width 100%
+ height 100%
+ outline none
+ border none
+
+.control-search-optionList
+ position fixed
+ z-index 200
+ width 275px
+ height 175px
+ overflow-y auto
+ background-color $modal-background
+ border-radius 2px
+ box-shadow 2px 2px 10px gray
+
+.control-search-optionList-item
+ height 50px
+ border-bottom $ui-border
+ transition background-color 0.15s
+ padding 5px
+ cursor pointer
+ overflow ellipsis
+ &:hover
+ background-color alpha($ui-active-color, 10%)
+.control-search-optionList-item-folder
+ border-left 4px solid transparent
+ padding 2px 5px
+ color $ui-text-color
+ overflow ellipsis
+ font-size 12px
+ height 16px
+ margin-bottom 4px
+.control-search-optionList-item-folder-surfix
+ font-size 10px
+ margin-left 5px
+ color $ui-inactive-text-color
+.control-search-optionList-item-type
+ font-size 12px
+ color $ui-inactive-text-color
+ padding-right 3px
+.control-search-optionList-empty
+ height 150px
+ color $ui-inactive-text-color
+ line-height 150px
+ text-align center
+.control-newPostButton
+ display block
+ absolute top right bottom
+ width 40px
+ height $control-height - 2
+ navButtonColor()
+ border-left $ui-border
+ font-size 14px
+ line-height 28px
+ padding 0
+ &:active
+ border-color $ui-button--active-backgroundColor
+ &:hover .left-control-newPostButton-tooltip
+ display block
+
+.control-newPostButton-tooltip
+ position fixed
+ line-height 1.4
+ background-color $ui-tooltip-backgroundColor
+ color $ui-tooltip-text-color
+ font-size 10px
+ margin-left -25px
+ margin-top 5px
+ padding 5px
+ z-index 1
+ border-radius 5px
+ display none
+ pointer-events none
diff --git a/browser/main/TopBar/index.js b/browser/main/TopBar/index.js
new file mode 100644
index 00000000..e575056d
--- /dev/null
+++ b/browser/main/TopBar/index.js
@@ -0,0 +1,216 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './TopBar.styl'
+import _ from 'lodash'
+import modal from 'browser/main/lib/modal'
+import NewNoteModal from 'browser/main/modals/NewNoteModal'
+import { hashHistory } from 'react-router'
+import ee from 'browser/main/lib/eventEmitter'
+
+const OSX = window.process.platform === 'darwin'
+
+class TopBar extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ search: '',
+ searchOptions: [],
+ searchPopupOpen: false
+ }
+
+ this.newNoteHandler = () => {
+ this.handleNewPostButtonClick()
+ }
+ }
+
+ componentDidMount () {
+ ee.on('top:new-note', this.newNoteHandler)
+ }
+
+ componentWillUnmount () {
+ ee.off('top:new-note', this.newNoteHandler)
+ }
+
+ handleNewPostButtonClick (e) {
+ let { storages, params, dispatch, location } = this.props
+ let storage = _.find(storages, {key: params.storageKey})
+ if (storage == null) storage = storages[0]
+ if (storage == null) throw new Error('No storage to create a note')
+ let folder = _.find(storage.folders, {key: params.folderKey})
+ if (folder == null) folder = storage.folders[0]
+ if (folder == null) throw new Error('No folder to craete a note')
+
+ modal.open(NewNoteModal, {
+ storage: storage.key,
+ folder: folder.key,
+ dispatch,
+ location
+ })
+ }
+
+ handleSearchChange (e) {
+ this.setState({
+ search: this.refs.searchInput.value
+ })
+ }
+
+ getOptions () {
+ let { notes } = this.props
+ let { search } = this.state
+ if (search.trim().length === 0) return []
+ let searchBlocks = search.split(' ')
+ searchBlocks.forEach((block) => {
+ if (block.match(/^#.+/)) {
+ let tag = block.match(/#(.+)/)[1]
+ notes = notes
+ .filter((note) => {
+ if (!_.isArray(note.tags)) return false
+ return note.tags.some((_tag) => {
+ return _tag === tag
+ })
+ })
+ } else {
+ notes = notes.filter((note) => {
+ if (note.type === 'SNIPPET_NOTE') {
+ return note.description.match(block)
+ } else if (note.type === 'MARKDOWN_NOTE') {
+ return note.content.match(block)
+ }
+ return false
+ })
+ }
+ })
+
+ return notes
+ }
+
+ handleOptionClick (uniqueKey) {
+ return (e) => {
+ this.setState({
+ searchPopupOpen: false
+ }, () => {
+ let { location } = this.props
+ hashHistory.push({
+ pathname: location.pathname,
+ query: {
+ key: uniqueKey
+ }
+ })
+ })
+ }
+ }
+
+ handleSearchFocus (e) {
+ this.setState({
+ searchPopupOpen: true
+ })
+ }
+ handleSearchBlur (e) {
+ e.stopPropagation()
+
+ let el = e.relatedTarget
+ let isStillFocused = false
+ while (el != null) {
+ if (el === this.refs.search) {
+ isStillFocused = true
+ break
+ }
+ el = el.parentNode
+ }
+ if (!isStillFocused) {
+ this.setState({
+ searchPopupOpen: false
+ })
+ }
+ }
+
+ render () {
+ let { config, style, storages } = this.props
+ let searchOptionList = this.getOptions()
+ .map((note) => {
+ let storage = _.find(storages, {key: note.storage})
+ let folder = _.find(storage.folders, {key: note.folder})
+ return
this.handleOptionClick(note.uniqueKey)(e)}
+ >
+
+ {folder.name}
+ in {storage.name}
+
+ {note.type === 'SNIPPET_NOTE'
+ ?
+ :
+ }
+ {note.title}
+
+ })
+
+ return (
+
+
+
+
+
this.handleSearchFocus(e)}
+ onBlur={(e) => this.handleSearchBlur(e)}
+ tabIndex='-1'
+ ref='search'
+ >
+
this.handleSearchChange(e)}
+ placeholder='Search'
+ type='text'
+ />
+ {this.state.searchPopupOpen &&
+
+ {searchOptionList.length > 0
+ ? searchOptionList
+ :
Empty List
+ }
+
+ }
+
+ {this.state.search > 0 &&
+
this.handleSearchClearButton(e)}
+ >
+
+
+ }
+
+
+
this.handleNewPostButtonClick(e)}>
+
+
+ New Note {OSX ? '⌘' : '^'} + n
+
+
+
+
+ )
+ }
+}
+
+TopBar.contextTypes = {
+ router: PropTypes.shape({
+ push: PropTypes.func
+ })
+}
+
+TopBar.propTypes = {
+ dispatch: PropTypes.func,
+ config: PropTypes.shape({
+ isSideNavFolded: PropTypes.bool
+ })
+}
+
+export default CSSModules(TopBar, styles)
diff --git a/browser/main/actions.js b/browser/main/actions.js
deleted file mode 100644
index 37e02f58..00000000
--- a/browser/main/actions.js
+++ /dev/null
@@ -1,178 +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
- }
-}
-
-export default {
- updateUser,
-
- updateArticle,
- destroyArticle,
- cacheArticle,
- uncacheArticle,
- uncacheAllArticles,
- saveArticle,
- saveAllArticles,
-
- createFolder,
- updateFolder,
- destroyFolder,
- replaceFolder,
-
- switchFolder,
- switchArticle,
- setSearchFilter,
- setTagFilter,
- clearSearch,
- toggleTutorial
-}
diff --git a/browser/main/global.styl b/browser/main/global.styl
new file mode 100644
index 00000000..d4073aff
--- /dev/null
+++ b/browser/main/global.styl
@@ -0,0 +1,85 @@
+global-reset()
+
+DEFAULT_FONTS = 'Lato', helvetica, arial, sans-serif
+
+html, body
+ width 100%
+ height 100%
+ overflow hidden
+
+body
+ font-family DEFAULT_FONTS
+ color textColor
+ font-size fontSize
+ font-weight 400
+
+button, input, select, textarea
+ font-family DEFAULT_FONTS
+
+div, span, a, button, input, textarea
+ box-sizing border-box
+
+a
+ color $brand-color
+ &:hover
+ color lighten($brand-color, 5%)
+ &:visited
+ color $brand-color
+
+hr
+ border-top none
+ border-bottom solid 1px $border-color
+ margin 15px 0
+
+button
+ font-weight 400
+ cursor pointer
+ font-size 12px
+ &:focus, &.focus
+ outline none
+ &:disabled
+ cursor not-allowed
+input
+ &:disabled
+ cursor not-allowed
+.noSelect
+ noSelect()
+
+.text-center
+ text-align center
+
+.form-group
+ margin-bottom 15px
+ &>label
+ display block
+ margin-bottom 5px
+
+textarea.block-input
+ resize vertical
+ height 125px
+ border-radius 5px
+ padding 5px 10px
+
+#content
+ fullsize()
+
+modalZIndex= 1000
+modalBackColor = transparentify(white, 65%)
+.ace_focus
+ outline-color rgb(59, 153, 252)
+ outline-offset 0px
+ outline-style auto
+ outline-width 5px
+.ModalBase
+ fixed top left bottom right
+ z-index modalZIndex
+ display flex
+ align-items center
+ justify-content center
+
+ &.hide
+ display none
+ .modalBack
+ absolute top left bottom right
+ background-color modalBackColor
+ z-index modalZIndex + 1
diff --git a/browser/main/index.js b/browser/main/index.js
index 1bc6f297..015dffed 100644
--- a/browser/main/index.js
+++ b/browser/main/index.js
@@ -1,34 +1,18 @@
import { Provider } from 'react-redux'
-import MainPage from './MainPage'
+import Main from './Main'
import store from './store'
import React from 'react'
import ReactDOM from 'react-dom'
-require('../styles/main/index.styl')
-import { openModal } from 'browser/lib/modal'
-import OSSAnnounceModal from './modal/OSSAnnounceModal'
+require('!!style!css!stylus?sourceMap!./global.styl')
import activityRecord from 'browser/lib/activityRecord'
-import fetchConfig from '../lib/fetchConfig'
+import { Router, Route, IndexRoute, IndexRedirect, hashHistory } from 'react-router'
+import { syncHistoryWithStore } from 'react-router-redux'
+
const electron = require('electron')
const ipc = electron.ipcRenderer
const path = require('path')
const remote = electron.remote
-let config = fetchConfig()
-applyConfig(config)
-
-ipc.on('config-apply', function (e, newConfig) {
- config = newConfig
- applyConfig(config)
-})
-
-function applyConfig (config) {
- let body = document.body
- body.setAttribute('data-theme', config['theme-ui'])
-
- let hljsCss = document.getElementById('hljs-css')
- hljsCss.setAttribute('href', '../node_modules/highlight.js/styles/' + config['theme-code'] + '.css')
-}
-
if (process.env.NODE_ENV !== 'production') {
window.addEventListener('keydown', function (e) {
if (e.keyCode === 73 && e.metaKey && e.altKey) {
@@ -74,21 +58,26 @@ ipc.on('open-finder', function () {
})
let el = document.getElementById('content')
+const history = syncHistoryWithStore(hashHistory, store)
+
ReactDOM.render((
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
), el, function () {
let loadingCover = document.getElementById('loadingCover')
loadingCover.parentNode.removeChild(loadingCover)
- let status = JSON.parse(localStorage.getItem('status'))
- if (status == null) status = {}
-
- if (!status.ossAnnounceWatched) {
- openModal(OSSAnnounceModal)
- status.ossAnnounceWatched = true
- localStorage.setItem('status', JSON.stringify(status))
- }
})
diff --git a/browser/main/lib/Commander.js b/browser/main/lib/Commander.js
new file mode 100644
index 00000000..959de5f0
--- /dev/null
+++ b/browser/main/lib/Commander.js
@@ -0,0 +1,31 @@
+let callees = []
+
+function bind (name, el) {
+ callees.push({
+ name: name,
+ element: el
+ })
+}
+
+function release (el) {
+ callees = callees.filter((callee) => callee.element !== el)
+}
+
+function fire (command) {
+ console.info('COMMAND >>', command)
+ let splitted = command.split(':')
+ let target = splitted[0]
+ let targetCommand = splitted[1]
+ let targetCallees = callees
+ .filter((callee) => callee.name === target)
+
+ targetCallees.forEach((callee) => {
+ callee.element.fire(targetCommand)
+ })
+}
+
+export default {
+ bind,
+ release,
+ fire
+}
diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js
new file mode 100644
index 00000000..075ac8e1
--- /dev/null
+++ b/browser/main/lib/ConfigManager.js
@@ -0,0 +1,84 @@
+import _ from 'lodash'
+
+const OSX = global.process.platform === 'darwin'
+const electron = require('electron')
+const { ipcRenderer } = electron
+
+const defaultConfig = {
+ zoom: 1,
+ isSideNavFolded: false,
+ listWidth: 250,
+ hotkey: {
+ toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
+ toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
+ },
+ ui: {
+ theme: 'default',
+ disableDirectWrite: false
+ },
+ editor: {
+ theme: 'xcode',
+ fontSize: '14',
+ fontFamily: 'Monaco, Consolas',
+ indentType: 'space',
+ indentSize: '4',
+ switchPreview: 'BLUR' // Available value: RIGHTCLICK, BLUR
+ },
+ preview: {
+ fontSize: '14',
+ fontFamily: 'Lato',
+ codeBlockTheme: 'xcode',
+ lineNumber: true
+ }
+}
+
+function validate (config) {
+ if (!_.isObject(config)) return false
+ if (!_.isNumber(config.zoom) || config.zoom < 0) return false
+ if (!_.isBoolean(config.isSideNavFolded)) return false
+ if (!_.isNumber(config.listWidth) || config.listWidth <= 0) return false
+
+ return true
+}
+
+function _save (config) {
+ console.log(config)
+ window.localStorage.setItem('config', JSON.stringify(config))
+}
+
+function get () {
+ let config = window.localStorage.getItem('config')
+
+ try {
+ config = Object.assign({}, defaultConfig, JSON.parse(config))
+ if (!validate(config)) throw new Error('INVALID CONFIG')
+ } catch (err) {
+ console.warn('Boostnote resets the malformed configuration.')
+ config = defaultConfig
+ _save(config)
+ }
+
+ return config
+}
+
+function set (updates) {
+ let currentConfig = get()
+ let newConfig = Object.assign({}, defaultConfig, currentConfig, updates)
+ if (!validate(newConfig)) throw new Error('INVALID CONFIG')
+ _save(newConfig)
+ ipcRenderer.send('CONFIG_RENEW', {
+ config: get(),
+ silent: false
+ })
+}
+
+ipcRenderer.send('CONFIG_RENEW', {
+ config: get(),
+ silent: true
+})
+
+export default {
+ get,
+ set,
+ validate
+}
diff --git a/browser/main/lib/ZoomManager.js b/browser/main/lib/ZoomManager.js
new file mode 100644
index 00000000..463df222
--- /dev/null
+++ b/browser/main/lib/ZoomManager.js
@@ -0,0 +1,30 @@
+import ConfigManager from './ConfigManager'
+
+const electron = require('electron')
+const { remote } = electron
+
+_init()
+
+function _init () {
+ setZoom(getZoom(), true)
+}
+
+function _saveZoom (zoomFactor) {
+ ConfigManager.set({zoom: zoomFactor})
+}
+
+function setZoom (zoomFactor, noSave = false) {
+ if (!noSave) _saveZoom(zoomFactor)
+ remote.getCurrentWebContents().setZoomFactor(zoomFactor)
+}
+
+function getZoom () {
+ let config = ConfigManager.get()
+
+ return config.zoom
+}
+
+export default {
+ setZoom,
+ getZoom
+}
diff --git a/browser/main/lib/dataApi.js b/browser/main/lib/dataApi.js
new file mode 100644
index 00000000..77a3d04e
--- /dev/null
+++ b/browser/main/lib/dataApi.js
@@ -0,0 +1,565 @@
+const keygen = require('browser/lib/keygen')
+const CSON = require('season')
+const path = require('path')
+const _ = require('lodash')
+const sander = require('sander')
+const consts = require('browser/lib/consts')
+
+let storages = []
+let notes = []
+
+let queuedTasks = []
+
+function queueSaveFolder (storageKey, folderKey) {
+ let storage = _.find(storages, {key: storageKey})
+ if (storage == null) throw new Error('Failed to queue: Storage doesn\'t exist.')
+
+ let targetTasks = queuedTasks.filter((task) => task.storage === storageKey && task.folder === folderKey)
+ targetTasks.forEach((task) => {
+ clearTimeout(task.timer)
+ })
+ queuedTasks = queuedTasks.filter((task) => task.storage !== storageKey || task.folder !== folderKey)
+ let newTimer = setTimeout(() => {
+ let folderNotes = notes.filter((note) => note.storage === storageKey && note.folder === folderKey)
+ sander
+ .writeFile(path.join(storage.cache.path, folderKey, 'data.json'), JSON.stringify({
+ notes: folderNotes.map((note) => {
+ let json = note.toJSON()
+ delete json.storage
+ return json
+ })
+ }))
+ }, 1500)
+
+ queuedTasks.push({
+ storage: storageKey,
+ folder: folderKey,
+ timer: newTimer
+ })
+}
+
+class Storage {
+ constructor (cache) {
+ this.key = cache.key
+ this.cache = cache
+ }
+
+ loadJSONData () {
+ return new Promise((resolve, reject) => {
+ try {
+ let data = CSON.readFileSync(path.join(this.cache.path, 'boostnote.json'))
+ this.data = data
+ resolve(this)
+ } catch (err) {
+ reject(err)
+ }
+ })
+ }
+
+ toJSON () {
+ return Object.assign({}, this.cache, this.data)
+ }
+
+ initStorage () {
+ return this.loadJSONData()
+ .catch((err) => {
+ console.error(err.code)
+ if (err.code === 'ENOENT') {
+ let initialStorage = {
+ folders: []
+ }
+
+ return sander.writeFile(path.join(this.cache.path, 'boostnote.json'), JSON.stringify(initialStorage))
+ } else throw err
+ })
+ .then(() => this.loadJSONData())
+ }
+
+ saveData () {
+ return sander
+ .writeFile(path.join(this.cache.path, 'boostnote.json'), JSON.stringify(this.data))
+ .then(() => this)
+ }
+
+ saveCache () {
+ _saveCaches()
+ }
+
+ static forge (cache) {
+ let instance = new this(cache)
+ return instance
+ }
+}
+
+class Note {
+ constructor (note) {
+ this.storage = note.storage
+ this.folder = note.folder
+ this.key = note.key
+ this.uniqueKey = `${note.storage}-${note.folder}-${note.key}`
+ this.data = note
+ }
+
+ toJSON () {
+ return Object.assign({}, this.data, {
+ uniqueKey: this.uniqueKey
+ })
+ }
+
+ save () {
+ let storage = _.find(storages, {key: this.storage})
+ if (storage == null) return Promise.reject(new Error('Storage doesn\'t exist.'))
+ let folder = _.find(storage.data.folders, {key: this.folder})
+ if (folder == null) return Promise.reject(new Error('Storage doesn\'t exist.'))
+
+ // FS MUST BE MANIPULATED BY ASYNC METHOD
+ queueSaveFolder(storage.key, folder.key)
+ return Promise.resolve(this)
+ }
+
+ static forge (note) {
+ let instance = new this(note)
+
+ return Promise.resolve(instance)
+ }
+}
+
+function init () {
+ let fetchStorages = function () {
+ let caches
+ try {
+ caches = JSON.parse(localStorage.getItem('storages'))
+ if (!_.isArray(caches)) throw new Error('Cached data is not valid.')
+ } catch (e) {
+ console.error(e)
+ caches = []
+ localStorage.setItem('storages', JSON.stringify(caches))
+ }
+
+ return caches.map((cache) => {
+ return Storage
+ .forge(cache)
+ .loadJSONData()
+ .catch((err) => {
+ console.error(err)
+ console.error('Failed to load a storage JSON File: %s', cache)
+ return null
+ })
+ })
+ }
+
+ let fetchNotes = function (storages) {
+ let notes = []
+ let modifiedStorages = []
+ storages
+ .forEach((storage) => {
+ storage.data.folders.forEach((folder) => {
+ let dataPath = path.join(storage.cache.path, folder.key, 'data.json')
+ let data
+ try {
+ data = CSON.readFileSync(dataPath)
+ } catch (e) {
+ // Remove folder if fetching failed.
+ console.error('Failed to load data: %s', dataPath)
+ storage.data.folders = storage.data.folders.filter((_folder) => _folder.key !== folder.key)
+ if (modifiedStorages.some((modified) => modified.key === storage.key)) modifiedStorages.push(storage)
+ return
+ }
+ data.notes.forEach((note) => {
+ note.storage = storage.key
+ note.folder = folder.key
+ notes.push(Note.forge(note))
+ })
+ })
+ }, [])
+ return Promise
+ .all(modifiedStorages.map((storage) => storage.saveData()))
+ .then(() => Promise.all(notes))
+ }
+
+ return Promise.all(fetchStorages())
+ .then((_storages) => {
+ storages = _storages.filter((storage) => {
+ if (!_.isObject(storage)) return false
+ return true
+ })
+ _saveCaches()
+
+ return storages
+ })
+ .then(fetchNotes)
+ .then((_notes) => {
+ notes = _notes
+ return {
+ storages: storages.map((storage) => storage.toJSON()),
+ notes: notes.map((note) => note.toJSON())
+ }
+ })
+}
+
+function _saveCaches () {
+ localStorage.setItem('storages', JSON.stringify(storages.map((storage) => storage.cache)))
+}
+
+function addStorage (input) {
+ if (!_.isString(input.path) || !input.path.match(/^\//)) {
+ return Promise.reject(new Error('Path must be absolute.'))
+ }
+
+ let key = keygen()
+ while (storages.some((storage) => storage.key === key)) {
+ key = keygen()
+ }
+
+ return Storage
+ .forge({
+ name: input.name,
+ key: key,
+ type: input.type,
+ path: input.path
+ })
+ .initStorage()
+ .then((storage) => {
+ let _notes = []
+ let isFolderRemoved = false
+ storage.data.folders.forEach((folder) => {
+ let dataPath = path.join(storage.cache.path, folder.key, 'data.json')
+ let data
+ try {
+ data = CSON.readFileSync(dataPath)
+ } catch (e) {
+ // Remove folder if fetching failed.
+ console.error('Failed to load data: %s', dataPath)
+ storage.data.folders = storage.data.folders.filter((_folder) => _folder.key !== folder.key)
+ isFolderRemoved = true
+ return true
+ }
+ data.notes.forEach((note) => {
+ note.storage = storage.key
+ note.folder = folder.key
+ _notes.push(Note.forge(note))
+ })
+ })
+
+ return Promise.all(_notes)
+ .then((_notes) => {
+ notes = notes.concat(_notes)
+ let data = {
+ storage: storage,
+ notes: _notes
+ }
+ return isFolderRemoved
+ ? storage.saveData().then(() => data)
+ : data
+ })
+ })
+ .then((data) => {
+ storages = storages.filter((storage) => storage.key !== data.storage.key)
+ storages.push(data.storage)
+ _saveCaches()
+
+ if (data.storage.data.folders.length < 1) {
+ return createFolder(data.storage.key, {
+ name: 'Default',
+ color: consts.FOLDER_COLORS[0]
+ }).then(() => data)
+ }
+
+ return data
+ })
+ .then((data) => {
+ return {
+ storage: data.storage.toJSON(),
+ notes: data.notes.map((note) => note.toJSON())
+ }
+ })
+}
+
+function removeStorage (key) {
+ storages = storages.filter((storage) => storage.key !== key)
+ _saveCaches()
+ notes = notes.filter((note) => note.storage !== key)
+ return Promise.resolve(true)
+}
+
+function renameStorage (key, name) {
+ let storage = _.find(storages, {key: key})
+ if (storage == null) throw new Error('Storage doesn\'t exist.')
+ storage.cache.name = name
+ storage.saveCache()
+
+ return Promise.resolve(storage.toJSON())
+}
+
+function migrateFromV5 (key, data) {
+ let oldFolders = data.folders
+ let oldArticles = data.articles
+ let storage = _.find(storages, {key: key})
+ if (storage == null) throw new Error('Storage doesn\'t exist.')
+
+ let migrateFolders = oldFolders.map((oldFolder) => {
+ let folderKey = keygen()
+ while (storage.data.folders.some((folder) => folder.key === folderKey)) {
+ folderKey = keygen()
+ }
+ let newFolder = {
+ key: folderKey,
+ name: oldFolder.name,
+ color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
+ }
+ storage.data.folders.push(newFolder)
+ let articles = oldArticles.filter((article) => article.FolderKey === oldFolder.key)
+ let folderNotes = []
+ articles.forEach((article) => {
+ let noteKey = keygen()
+ while (notes.some((note) => note.storage === key && note.folder === folderKey && note.key === noteKey)) {
+ key = keygen()
+ }
+ if (article.mode === 'markdown') {
+ let newNote = new Note({
+ tags: article.tags,
+ createdAt: article.createdAt,
+ updatedAt: article.updatedAt,
+ folder: folderKey,
+ storage: key,
+ type: 'MARKDOWN_NOTE',
+ isStarred: false,
+ title: article.title,
+ content: '# ' + article.title + '\n\n' + article.content,
+ key: noteKey
+ })
+ notes.push(newNote)
+ folderNotes.push(newNote)
+ } else {
+ let newNote = new Note({
+ tags: article.tags,
+ createdAt: article.createdAt,
+ updatedAt: article.updatedAt,
+ folder: folderKey,
+ storage: key,
+ type: 'SNIPPET_NOTE',
+ isStarred: false,
+ title: article.title,
+ description: article.title,
+ key: noteKey,
+ snippets: [{
+ name: article.mode,
+ mode: article.mode,
+ content: article.content
+ }]
+ })
+ notes.push(newNote)
+ folderNotes.push(newNote)
+ }
+ })
+
+ return sander
+ .writeFile(path.join(storage.cache.path, folderKey, 'data.json'), JSON.stringify({
+ notes: folderNotes.map((note) => {
+ let json = note.toJSON()
+ delete json.storage
+ return json
+ })
+ }))
+ })
+ return Promise.all(migrateFolders)
+ .then(() => storage.saveData())
+ .then(() => {
+ return {
+ storage: storage.toJSON(),
+ notes: notes.filter((note) => note.storage === key)
+ .map((note) => note.toJSON())
+ }
+ })
+}
+
+function createFolder (key, input) {
+ let storage = _.find(storages, {key: key})
+ if (storage == null) throw new Error('Storage doesn\'t exist.')
+
+ let folderKey = keygen()
+ while (storage.data.folders.some((folder) => folder.key === folderKey)) {
+ folderKey = keygen()
+ }
+
+ let newFolder = {
+ key: folderKey,
+ name: input.name,
+ color: input.color
+ }
+
+ const defaultData = {notes: []}
+ // FS MUST BE MANIPULATED BY ASYNC METHOD
+ return sander
+ .writeFile(path.join(storage.cache.path, folderKey, 'data.json'), JSON.stringify(defaultData))
+ .then(() => {
+ storage.data.folders.push(newFolder)
+ return storage
+ .saveData()
+ .then((storage) => storage.toJSON())
+ })
+}
+
+function updateFolder (storageKey, folderKey, input) {
+ let storage = _.find(storages, {key: storageKey})
+ if (storage == null) throw new Error('Storage doesn\'t exist.')
+ let folder = _.find(storage.data.folders, {key: folderKey})
+ folder.color = input.color
+ folder.name = input.name
+
+ return storage
+ .saveData()
+ .then((storage) => storage.toJSON())
+}
+
+function removeFolder (storageKey, folderKey) {
+ let storage = _.find(storages, {key: storageKey})
+ if (storage == null) throw new Error('Storage doesn\'t exist.')
+ storage.data.folders = storage.data.folders.filter((folder) => folder.key !== folderKey)
+ notes = notes.filter((note) => note.storage !== storageKey || note.folder !== folderKey)
+
+ // FS MUST BE MANIPULATED BY ASYNC METHOD
+ return sander
+ .rimraf(path.join(storage.cache.path, folderKey))
+ .catch((err) => {
+ if (err.code === 'ENOENT') return true
+ else throw err
+ })
+ .then(() => storage.saveData())
+ .then((storage) => storage.toJSON())
+}
+
+function createMarkdownNote (storageKey, folderKey, input) {
+ let key = keygen()
+ while (notes.some((note) => note.storage === storageKey && note.folder === folderKey && note.key === key)) {
+ key = keygen()
+ }
+
+ let newNote = new Note(Object.assign({
+ tags: [],
+ title: '',
+ content: ''
+ }, input, {
+ type: 'MARKDOWN_NOTE',
+ storage: storageKey,
+ folder: folderKey,
+ key: key,
+ isStarred: false,
+ createdAt: new Date(),
+ updatedAt: new Date()
+ }))
+ notes.push(newNote)
+
+ return newNote
+ .save()
+ .then(() => newNote.toJSON())
+}
+
+function createSnippetNote (storageKey, folderKey, input) {
+ let key = keygen()
+ while (notes.some((note) => note.storage === storageKey && note.folder === folderKey && note.key === key)) {
+ key = keygen()
+ }
+
+ let newNote = new Note(Object.assign({
+ tags: [],
+ title: '',
+ description: '',
+ snippets: [{
+ name: '',
+ mode: 'text',
+ content: ''
+ }]
+ }, input, {
+ type: 'SNIPPET_NOTE',
+ storage: storageKey,
+ folder: folderKey,
+ key: key,
+ isStarred: false,
+ createdAt: new Date(),
+ updatedAt: new Date()
+ }))
+ notes.push(newNote)
+
+ return newNote
+ .save()
+ .then(() => newNote.toJSON())
+}
+
+function updateNote (storageKey, folderKey, noteKey, input) {
+ let note = _.find(notes, {
+ key: noteKey,
+ storage: storageKey,
+ folder: folderKey
+ })
+
+ switch (note.data.type) {
+ case 'MARKDOWN_NOTE':
+ note.data.title = input.title
+ note.data.tags = input.tags
+ note.data.content = input.content
+ note.data.updatedAt = input.updatedAt
+ break
+ case 'SNIPPET_NOTE':
+ note.data.title = input.title
+ note.data.tags = input.tags
+ note.data.description = input.description
+ note.data.snippets = input.snippets
+ note.data.updatedAt = input.updatedAt
+ }
+
+ return note.save()
+ .then(() => note.toJSON())
+}
+
+function removeNote (storageKey, folderKey, noteKey) {
+ notes = notes.filter((note) => note.storage !== storageKey || note.folder !== folderKey || note.key !== noteKey)
+ queueSaveFolder(storageKey, folderKey)
+
+ return Promise.resolve(null)
+}
+
+function moveNote (storageKey, folderKey, noteKey, newStorageKey, newFolderKey) {
+ let note = _.find(notes, {
+ key: noteKey,
+ storage: storageKey,
+ folder: folderKey
+ })
+ if (note == null) throw new Error('Note doesn\'t exist.')
+
+ let storage = _.find(storages, {key: newStorageKey})
+ if (storage == null) throw new Error('Storage doesn\'t exist.')
+ let folder = _.find(storage.data.folders, {key: newFolderKey})
+ if (folder == null) throw new Error('Folder doesn\'t exist.')
+ note.storage = storage.key
+ note.data.storage = storage.key
+ note.folder = folder.key
+ note.data.folder = folder.key
+ let key = note.key
+ while (notes.some((note) => note.storage === storage.key && note.folder === folder.key && note.key === key)) {
+ key = keygen()
+ }
+ note.key = key
+ note.data.key = key
+ note.uniqueKey = `${note.storage}-${note.folder}-${note.key}`
+ console.log(note.uniqueKey)
+ queueSaveFolder(storageKey, folderKey)
+ return note.save()
+ .then(() => note.toJSON())
+}
+
+export default {
+ init,
+ addStorage,
+ removeStorage,
+ renameStorage,
+ createFolder,
+ updateFolder,
+ removeFolder,
+ createMarkdownNote,
+ createSnippetNote,
+ updateNote,
+ removeNote,
+ moveNote,
+ migrateFromV5
+}
diff --git a/browser/main/lib/eventEmitter.js b/browser/main/lib/eventEmitter.js
new file mode 100644
index 00000000..de08f078
--- /dev/null
+++ b/browser/main/lib/eventEmitter.js
@@ -0,0 +1,26 @@
+const electron = require('electron')
+const { ipcRenderer, remote } = electron
+
+function on (name, listener) {
+ ipcRenderer.on(name, listener)
+}
+
+function off (name, listener) {
+ ipcRenderer.removeListener(name, listener)
+}
+
+function once (name, listener) {
+ ipcRenderer.once(name, listener)
+}
+
+function emit (name, ...args) {
+ console.log(name)
+ remote.getCurrentWindow().webContents.send(name, ...args)
+}
+
+export default {
+ emit,
+ on,
+ off,
+ once
+}
diff --git a/browser/lib/modal.js b/browser/main/lib/modal.js
similarity index 79%
rename from browser/lib/modal.js
rename to browser/main/lib/modal.js
index 6dcf0638..92b1bc8c 100644
--- a/browser/lib/modal.js
+++ b/browser/main/lib/modal.js
@@ -1,7 +1,7 @@
import React from 'react'
+import { Provider } from 'react-redux'
import ReactDOM from 'react-dom'
-
-const remote = require('electron').remote
+import store from '../store'
class ModalBase extends React.Component {
constructor (props) {
@@ -15,17 +15,16 @@ class ModalBase extends React.Component {
close () {
if (modalBase != null) modalBase.setState({component: null, componentProps: null, isHidden: true})
- document.body.setAttribute('data-modal', 'close')
-
- remote.getCurrentWebContents().send('list-focus')
}
render () {
return (
-
this.close(e)} className='modalBack'/>
+
this.close(e)} className='modalBack'/>
{this.state.component == null ? null : (
-
+
+
+
)}
)
diff --git a/browser/main/modal/CreateNewFolder.js b/browser/main/modal/CreateNewFolder.js
deleted file mode 100644
index eb797a0c..00000000
--- a/browser/main/modal/CreateNewFolder.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import React, { PropTypes } from 'react'
-import ReactDOM from 'react-dom'
-import linkState from 'browser/lib/linkState'
-import { createFolder } from '../actions'
-import store from '../store'
-import FolderMark from 'browser/components/FolderMark'
-
-export default class CreateNewFolder extends React.Component {
- constructor (props) {
- super(props)
-
- this.state = {
- name: '',
- color: Math.round(Math.random() * 7),
- alert: null
- }
- }
-
- componentDidMount () {
- ReactDOM.findDOMNode(this.refs.folderName).focus()
- }
-
- handleCloseButton (e) {
- this.props.close()
- }
-
- handleConfirmButton (e) {
- this.setState({alert: null}, () => {
- let { close } = this.props
- let { name, color } = this.state
-
- let input = {
- name,
- color
- }
- try {
- store.dispatch(createFolder(input))
- } catch (e) {
- this.setState({alert: {
- type: 'error',
- message: e.message
- }})
- return
- }
- close()
- })
- }
-
- handleColorClick (colorIndex) {
- return e => {
- this.setState({
- color: colorIndex
- })
- }
- }
-
- handleKeyDown (e) {
- if (e.keyCode === 13) {
- this.handleConfirmButton()
- }
- }
-
- render () {
- let alert = this.state.alert
- let alertElement = alert != null ? (
-
- {alert.message}
-
- ) : null
- let colorIndexes = []
- for (let i = 0; i < 8; i++) {
- colorIndexes.push(i)
- }
- let colorElements = colorIndexes.map(index => {
- let className = 'option'
- if (index === this.state.color) className += ' active'
-
- return (
-
this.handleColorClick(index)(e)}>
-
-
- )
- })
-
- return (
-
-
this.handleCloseButton(e)} className='closeBtn'>
-
-
Create new folder
-
-
this.handleKeyDown(e)} className='ipt' type='text' valueLink={this.linkState('name')} placeholder='Enter folder name'/>
-
- {colorElements}
-
- {alertElement}
-
-
this.handleConfirmButton(e)} className='confirmBtn'>Create
-
- )
- }
-}
-
-CreateNewFolder.propTypes = {
- close: PropTypes.func
-}
-
-CreateNewFolder.prototype.linkState = linkState
diff --git a/browser/main/modal/OSSAnnounceModal.js b/browser/main/modal/OSSAnnounceModal.js
deleted file mode 100644
index 0902682e..00000000
--- a/browser/main/modal/OSSAnnounceModal.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React, { PropTypes } from 'react'
-import ExternalLink from 'browser/components/ExternalLink'
-
-export default class OSSAnnounceModal extends React.Component {
- handleCloseBtnClick (e) {
- this.props.close()
- }
- render () {
- return (
-
-
-
Boostnote has been Open-sourced
-
-
- https://github.com/BoostIO/Boostnote
-
-
-
this.handleCloseBtnClick(e)}
- >Close
-
- )
- }
-}
-
-OSSAnnounceModal.propTypes = {
- close: PropTypes.func
-}
diff --git a/browser/main/modal/Preference/AppSettingTab.js b/browser/main/modal/Preference/AppSettingTab.js
deleted file mode 100644
index 36410ef8..00000000
--- a/browser/main/modal/Preference/AppSettingTab.js
+++ /dev/null
@@ -1,276 +0,0 @@
-import React, { PropTypes } from 'react'
-import linkState from 'browser/lib/linkState'
-import { updateUser } from '../../actions'
-import fetchConfig from 'browser/lib/fetchConfig'
-import hljsTheme from 'browser/lib/hljsThemes'
-
-const electron = require('electron')
-const ipc = electron.ipcRenderer
-const remote = electron.remote
-const ace = window.ace
-
-const OSX = global.process.platform === 'darwin'
-
-export default class AppSettingTab extends React.Component {
- constructor (props) {
- super(props)
- let keymap = Object.assign({}, remote.getGlobal('keymap'))
- let config = Object.assign({}, fetchConfig())
- let userName = props.user != null ? props.user.name : null
-
- this.state = {
- user: {
- name: userName,
- alert: null
- },
- userAlert: null,
- keymap: keymap,
- keymapAlert: null,
- config: config,
- configAlert: null
- }
- }
-
- componentDidMount () {
- this.handleSettingDone = () => {
- this.setState({keymapAlert: {
- type: 'success',
- message: 'Successfully done!'
- }})
- }
- this.handleSettingError = err => {
- this.setState({keymapAlert: {
- type: 'error',
- message: err.message != null ? err.message : 'Error occurs!'
- }})
- }
- ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
- ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
- }
-
- componentWillUnmount () {
- ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
- ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
- }
-
- submitHotKey () {
- ipc.send('hotkeyUpdated', this.state.keymap)
- }
-
- submitConfig () {
- ipc.send('configUpdated', this.state.config)
- }
-
- handleSaveButtonClick (e) {
- this.submitHotKey()
- }
-
- handleConfigSaveButtonClick (e) {
- this.submitConfig()
- }
-
- handleKeyDown (e) {
- if (e.keyCode === 13) {
- this.submitHotKey()
- }
- }
-
- handleConfigKeyDown (e) {
- if (e.keyCode === 13) {
- this.submitConfig()
- }
- }
-
- handleLineNumberingClick (e) {
- let config = this.state.config
-
- config['preview-line-number'] = e.target.checked
- this.setState({
- config
- })
- }
-
- handleDisableDirectWriteClick (e) {
- let config = this.state.config
- config['disable-direct-write'] = e.target.checked
- this.setState({
- config
- })
- }
-
- handleNameSaveButtonClick (e) {
- let { dispatch } = this.props
-
- dispatch(updateUser({name: this.state.user.name}))
- this.setState({
- userAlert: {
- type: 'success',
- message: 'Successfully done!'
- }
- })
- }
-
- render () {
- let keymapAlert = this.state.keymapAlert
- let keymapAlertElement = keymapAlert != null
- ? (
-
- {keymapAlert.message}
-
- ) : null
- let userAlert = this.state.userAlert
- let userAlertElement = userAlert != null
- ? (
-
- {userAlert.message}
-
- ) : null
- let aceThemeList = ace.require("ace/ext/themelist")
- let hljsThemeList = hljsTheme()
-
- return (
-
-
-
User's info
-
- User name
-
-
-
- this.handleNameSaveButtonClick(e)}>Save
- {userAlertElement}
-
-
-
-
Editor
-
- Editor Font Size
- this.handleConfigKeyDown(e)} type='text'/>
-
-
- Editor Font Family
- this.handleConfigKeyDown(e)} type='text'/>
-
-
-
Editor Indent Style
-
- type
-
- Space
- Tab
-
- size
-
- 2
- 4
- 8
-
-
-
-
Preview
-
- Preview Font Size
- this.handleConfigKeyDown(e)} type='text'/>
-
-
- Preview Font Family
- this.handleConfigKeyDown(e)} type='text'/>
-
-
- Switching Preview
-
- When Editor Blurred
- When Right Clicking
-
-
-
- this.handleLineNumberingClick(e)} checked={this.state.config['preview-line-number']} type='checkbox'/>Code block line numbering
-
- {
- global.process.platform === 'win32'
- ? (
-
- this.handleDisableDirectWriteClick(e)} checked={this.state.config['disable-direct-write']} disabled={OSX} type='checkbox'/>Disable Direct WriteIt will be applied after restarting
-
- )
- : null
- }
-
Theme
-
- UI Theme
-
- Light
- Dark
-
-
-
- Code block Theme
-
- {
- hljsThemeList.map(function(v, i){
- return ({v.caption} )
- })
- }
-
-
-
- Editor Theme
-
- {
- aceThemeList.themes.map(function(v, i){
- return ({v.caption} )
- })
- }
-
-
-
- this.handleConfigSaveButtonClick(e)}>Save
-
-
-
-
Hotkey
-
- Toggle Main
- this.handleKeyDown(e)} valueLink={this.linkState('keymap.toggleMain')} type='text'/>
-
-
- Toggle Finder(popup)
- this.handleKeyDown(e)} valueLink={this.linkState('keymap.toggleFinder')} type='text'/>
-
-
- this.handleSaveButtonClick(e)}>Save
- {keymapAlertElement}
-
-
-
- 0 to 9
- A to Z
- F1 to F24
- Punctuations like ~, !, @, #, $, etc.
- Plus
- Space
- Backspace
- Delete
- Insert
- Return (or Enter as alias)
- Up, Down, Left and Right
- Home and End
- PageUp and PageDown
- Escape (or Esc for short)
- VolumeUp, VolumeDown and VolumeMute
- MediaNextTrack, MediaPreviousTrack, MediaStop and MediaPlayPause
-
-
-
-
- )
- }
-}
-
-AppSettingTab.prototype.linkState = linkState
-AppSettingTab.propTypes = {
- user: PropTypes.shape({
- name: PropTypes.string
- }),
- dispatch: PropTypes.func
-}
diff --git a/browser/main/modal/Preference/ContactTab.js b/browser/main/modal/Preference/ContactTab.js
deleted file mode 100644
index aad8c331..00000000
--- a/browser/main/modal/Preference/ContactTab.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import linkState from 'browser/lib/linkState'
-import ExternalLink from 'browser/components/ExternalLink'
-
-export default class ContactTab extends React.Component {
- componentDidMount () {
- let titleInput = ReactDOM.findDOMNode(this.refs.title)
- if (titleInput != null) titleInput.focus()
- }
-
- render () {
- return (
-
-
Contact
-
- - Issues: https://github.com/BoostIO/Boostnote/issues
-
-
- )
- }
-}
-
-ContactTab.prototype.linkState = linkState
diff --git a/browser/main/modal/Preference/FolderRow.js b/browser/main/modal/Preference/FolderRow.js
deleted file mode 100644
index f836e9d1..00000000
--- a/browser/main/modal/Preference/FolderRow.js
+++ /dev/null
@@ -1,187 +0,0 @@
-import React, { PropTypes } from 'react'
-import linkState from 'browser/lib/linkState'
-import FolderMark from 'browser/components/FolderMark'
-import store from '../../store'
-import { updateFolder, destroyFolder, replaceFolder } from '../../actions'
-
-const IDLE = 'IDLE'
-const EDIT = 'EDIT'
-const DELETE = 'DELETE'
-
-export default class FolderRow extends React.Component {
- constructor (props) {
- super(props)
-
- this.state = {
- mode: IDLE
- }
- }
-
- handleUpClick (e) {
- let { index } = this.props
- if (index > 0) {
- store.dispatch(replaceFolder(index, index - 1))
- }
- }
-
- handleDownClick (e) {
- let { index, count } = this.props
- if (index < count - 1) {
- store.dispatch(replaceFolder(index, index + 1))
- }
- }
-
- handleCancelButtonClick (e) {
- this.setState({
- mode: IDLE
- })
- }
-
- handleEditButtonClick (e) {
- this.setState({
- mode: EDIT,
- name: this.props.folder.name,
- color: this.props.folder.color,
- isColorEditing: false
- })
- }
-
- handleDeleteButtonClick (e) {
- this.setState({mode: DELETE})
- }
-
- handleNameInputKeyDown (e) {
- if (e.keyCode === 13) {
- this.handleSaveButtonClick()
- }
- }
-
- handleColorSelectClick (e) {
- this.setState({
- isColorEditing: true
- })
- }
-
- handleColorButtonClick (index) {
- return (e) => {
- this.setState({
- color: index,
- isColorEditing: false
- })
- }
- }
-
- handleSaveButtonClick (e) {
- let { folder, setAlert } = this.props
-
- setAlert(null, () => {
- let input = {
- name: this.state.name,
- color: this.state.color
- }
- folder = Object.assign({}, folder, input)
-
- try {
- store.dispatch(updateFolder(folder))
- this.setState({
- mode: IDLE
- })
- } catch (e) {
- console.error(e)
- setAlert({
- type: 'error',
- message: e.message
- })
- }
- })
- }
-
- handleDeleteConfirmButtonClick (e) {
- let { folder } = this.props
- store.dispatch(destroyFolder(folder.key))
- }
-
- render () {
- let folder = this.props.folder
-
- switch (this.state.mode) {
- case EDIT:
- let colorIndexes = []
- for (let i = 0; i < 8; i++) {
- colorIndexes.push(i)
- }
-
- let colorOptions = colorIndexes.map(index => {
- let className = this.state.color === index
- ? 'active'
- : null
- return (
-
this.handleColorButtonClick(index)(e)} className={className} key={index}>
-
-
- )
- })
-
- return (
-
-
-
this.handleColorSelectClick(e)} className='select'>
-
-
- {this.state.isColorEditing
- ? (
-
-
Color select
- {colorOptions}
-
- )
- : null
- }
-
-
- this.handleNameInputKeyDown(e)} valueLink={this.linkState('name')} type='text'/>
-
-
- this.handleSaveButtonClick(e)} className='primary'>Save
- this.handleCancelButtonClick(e)}>Cancel
-
-
- )
- case DELETE:
- return (
-
-
Are you sure to delete {folder.name} folder?
-
- this.handleDeleteConfirmButtonClick(e)} className='primary'>Sure
- this.handleCancelButtonClick(e)}>Cancel
-
-
- )
- case IDLE:
- default:
- return (
-
-
- this.handleUpClick(e)}>
- this.handleDownClick(e)}>
-
-
-
{folder.name}
-
- this.handleEditButtonClick(e)}>
- this.handleDeleteButtonClick(e)}>
-
-
- )
- }
- }
-}
-
-FolderRow.propTypes = {
- folder: PropTypes.shape(),
- index: PropTypes.number,
- count: PropTypes.number,
- setAlert: PropTypes.func
-}
-
-FolderRow.prototype.linkState = linkState
diff --git a/browser/main/modal/Preference/FolderSettingTab.js b/browser/main/modal/Preference/FolderSettingTab.js
deleted file mode 100644
index 2337aa32..00000000
--- a/browser/main/modal/Preference/FolderSettingTab.js
+++ /dev/null
@@ -1,97 +0,0 @@
-import React, { PropTypes } from 'react'
-import FolderRow from './FolderRow'
-import linkState from 'browser/lib/linkState'
-import { createFolder } from '../../actions'
-
-export default class FolderSettingTab extends React.Component {
- constructor (props) {
- super(props)
-
- this.state = {
- name: ''
- }
- }
-
- handleNewFolderNameKeyDown (e) {
- if (e.keyCode === 13) {
- this.handleSaveButtonClick()
- }
- }
-
- handleSaveButtonClick (e) {
- this.setState({alert: null}, () => {
- let { dispatch } = this.props
-
- try {
- dispatch(createFolder({
- name: this.state.name
- }))
- } catch (e) {
- this.setState({alert: {
- type: 'error',
- message: e.message
- }})
- return
- }
-
- this.setState({name: ''})
- })
- }
-
- setAlert (alert, cb) {
- this.setState({alert: alert}, cb)
- }
-
- render () {
- let { folders } = this.props
- let folderElements = folders.map((folder, index) => {
- return (
-
this.setAlert(alert, cb)}
- />
- )
- })
-
- let alert = this.state.alert
- let alertElement = alert != null ? (
-
- {alert.message}
-
- ) : null
-
- return (
-
-
-
Manage folder
-
-
- {folderElements}
-
-
- this.handleNewFolderNameKeyDown(e)} valueLink={this.linkState('name')} type='text' placeholder='New Folder'/>
-
-
- this.handleSaveButtonClick(e)} className='primary'>Add
-
-
- {alertElement}
-
-
-
- )
- }
-}
-
-FolderSettingTab.propTypes = {
- folders: PropTypes.array,
- dispatch: PropTypes.func
-}
-
-FolderSettingTab.prototype.linkState = linkState
diff --git a/browser/main/modal/Preferences.js b/browser/main/modal/Preferences.js
deleted file mode 100644
index 01b7ac22..00000000
--- a/browser/main/modal/Preferences.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import React, { PropTypes } from 'react'
-import { connect, Provider } from 'react-redux'
-import linkState from 'browser/lib/linkState'
-import store from '../store'
-import AppSettingTab from './Preference/AppSettingTab'
-import FolderSettingTab from './Preference/FolderSettingTab'
-import ContactTab from './Preference/ContactTab'
-import { closeModal } from 'browser/lib/modal'
-
-const APP = 'APP'
-const FOLDER = 'FOLDER'
-const CONTACT = 'CONTACT'
-
-class Preferences extends React.Component {
- constructor (props) {
- super(props)
-
- this.state = {
- currentTab: APP
- }
- }
-
- switchTeam (teamId) {
- this.setState({currentTeamId: teamId})
- }
-
- handleNavButtonClick (tab) {
- return e => {
- this.setState({currentTab: tab})
- }
- }
-
- render () {
- let content = this.renderContent()
-
- let tabs = [
- {target: APP, label: 'Preferences'},
- {target: FOLDER, label: 'Manage folder'},
- {target: CONTACT, label: 'Contact form'}
- ]
-
- let navButtons = tabs.map(tab => (
- this.handleNavButtonClick(tab.target)(e)} className={this.state.currentTab === tab.target ? 'active' : ''}>{tab.label}
- ))
-
- return (
-
-
-
Setting
-
closeModal()} className='closeBtn'>Done
-
-
-
- {navButtons}
-
-
- {content}
-
- )
- }
-
- renderContent () {
- let { user, folders, dispatch } = this.props
-
- switch (this.state.currentTab) {
- case FOLDER:
- return (
-
- )
- case CONTACT:
- return (
-
- )
- case APP:
- default:
- return (
-
- )
- }
- }
-}
-
-Preferences.propTypes = {
- user: PropTypes.shape({
- name: PropTypes.string
- }),
- folders: PropTypes.array,
- dispatch: PropTypes.func
-}
-
-Preferences.prototype.linkState = linkState
-
-function remap (state) {
- let { user, folders, status } = state
-
- return {
- user,
- folders,
- status
- }
-}
-
-let RootComponent = connect(remap)(Preferences)
-export default class PreferencesModal extends React.Component {
- render () {
- return (
-
-
-
- )
- }
-}
diff --git a/browser/main/modal/Tutorial.js b/browser/main/modal/Tutorial.js
deleted file mode 100644
index 6fbc1f88..00000000
--- a/browser/main/modal/Tutorial.js
+++ /dev/null
@@ -1,115 +0,0 @@
-import React, { PropTypes } from 'react'
-import MarkdownPreview from 'browser/components/MarkdownPreview'
-import CodeEditor from 'browser/components/CodeEditor'
-
-export default class Tutorial extends React.Component {
- constructor (props) {
- super(props)
-
- this.state = {
- slideIndex: 0
- }
- }
-
- handlePriorSlideClick () {
- if (this.state.slideIndex > 0) this.setState({slideIndex: this.state.slideIndex - 1})
- }
-
- handleNextSlideClick () {
- if (this.state.slideIndex < 4) this.setState({slideIndex: this.state.slideIndex + 1})
- }
-
- startButtonClick (e) {
- this.props.close()
- }
-
- render () {
- let content = this.renderContent(this.state.slideIndex)
-
- let dotElements = []
- for (let i = 0; i < 5; i++) {
- dotElements.push( )
- }
-
- return (
-
-
this.handlePriorSlideClick()} className={'priorBtn' + (this.state.slideIndex === 0 ? ' hide' : '')}>
-
-
-
this.handleNextSlideClick()} className={'nextBtn' + (this.state.slideIndex === 4 ? ' hide' : '')}>
-
-
- {content}
-
- {dotElements}
-
-
- )
- }
-
- renderContent (index) {
- switch (index) {
- case 0:
- return (
-
Welcome to Boost
-
- Boost is a brand new note app for software
- Don't waste time cleaning up your data.
- devote that time to more creative work.
- Hack your memory.
-
-
)
- case 1:
- let content = '## Boost is a note app for engineer.\n\n - Write with markdown\n - Stylize beautiful'
- return (
-
Write with Markdown
-
- Markdown is available.
- Your notes will be stylized beautifully and quickly.
-
-
-
)
- case 2:
- let code = 'import shell from \'shell\'\r\nvar React = require(\'react\')\r\nvar { PropTypes } = React\r\nimport markdown from \'boost\/markdown\'\r\nvar ReactDOM = require(\'react-dom\')\r\n\r\nfunction handleAnchorClick (e) {\r\n shell.openExternal(e.target.href)\r\n e.preventDefault()\r\n}\r\n\r\nexport default class MarkdownPreview extends React.Component {\r\n componentDidMount () {\r\n this.addListener()\r\n }\r\n\r\n componentDidUpdate () {\r\n this.addListener()\r\n }\r\n\r\n componentWillUnmount () {\r\n this.removeListener()\r\n }'
- return (
-
Beautiful code highlighting
-
- Boost supports code syntax highlighting.
- There are more than 100 different type of language.
-
-
-
-
-
)
- case 3:
- return (
-
Easy to access with Finder
-
- The Finder helps you organize all of the files and documents.
- There is a short-cut key [⌘ + alt + s] to open the Finder.
- It is available to save your articles on the Clipboard
- by selecting your file with pressing Enter key,
- and to paste the contents of the Clipboard with [{process.platform === 'darwin' ? 'Command' : 'Control'}-V]
-
-
-
-
)
- case 4:
- return (
-
Are you ready?
-
- this.startButtonClick(e)}>Start Boost
-
-
)
- default:
- return null
- }
- }
-}
-
-Tutorial.propTypes = {
- close: PropTypes.func
-}
diff --git a/browser/main/modal/DeleteArticleModal.js b/browser/main/modals/DeleteArticleModal.js
similarity index 71%
rename from browser/main/modal/DeleteArticleModal.js
rename to browser/main/modals/DeleteArticleModal.js
index 095ca949..78db1bfa 100644
--- a/browser/main/modal/DeleteArticleModal.js
+++ b/browser/main/modals/DeleteArticleModal.js
@@ -1,7 +1,6 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import store from '../store'
-import { destroyArticle } from '../actions'
const electron = require('electron')
const ipc = electron.ipcRenderer
@@ -10,7 +9,7 @@ export default class DeleteArticleModal extends React.Component {
constructor (props) {
super(props)
- this.confirmHandler = e => this.handleYesButtonClick()
+ this.confirmHandler = (e) => this.handleYesButtonClick()
}
componentDidMount () {
@@ -27,7 +26,7 @@ export default class DeleteArticleModal extends React.Component {
}
handleYesButtonClick (e) {
- store.dispatch(destroyArticle(this.props.articleKey))
+ // store.dispatch(destroyArticle(this.props.articleKey))
this.props.close()
}
@@ -39,8 +38,8 @@ export default class DeleteArticleModal extends React.Component {
Do you really want to delete?
- this.handleNoButtonClick(e)}> No
- this.handleYesButtonClick(e)} className='danger'> Yes
+ this.handleNoButtonClick(e)}> No
+ this.handleYesButtonClick(e)} className='danger'> Yes
)
diff --git a/browser/main/modals/InitModal.js b/browser/main/modals/InitModal.js
new file mode 100644
index 00000000..906373d6
--- /dev/null
+++ b/browser/main/modals/InitModal.js
@@ -0,0 +1,243 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './InitModal.styl'
+import dataApi from 'browser/main/lib/dataApi'
+import store from 'browser/main/store'
+import { hashHistory } from 'react-router'
+import _ from 'lodash'
+
+const CSON = require('season')
+const path = require('path')
+const electron = require('electron')
+const { remote } = electron
+
+function browseFolder () {
+ let dialog = remote.dialog
+
+ let defaultPath = remote.app.getPath('home')
+ return new Promise((resolve, reject) => {
+ dialog.showOpenDialog({
+ title: 'Select Directory',
+ defaultPath,
+ properties: ['openDirectory', 'createDirectory']
+ }, function (targetPaths) {
+ if (targetPaths == null) return resolve('')
+ resolve(targetPaths[0])
+ })
+ })
+}
+
+class InitModal extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ path: path.join(remote.app.getPath('home'), 'Boostnote'),
+ migrationRequested: true,
+ isLoading: true,
+ data: null,
+ legacyStorageExists: false,
+ isSending: false
+ }
+ }
+
+ handleCloseButtonClick (e) {
+ this.props.close()
+ }
+
+ handlePathChange (e) {
+ this.setState({
+ path: e.target.value
+ })
+ }
+
+ componentDidMount () {
+ let data = null
+ try {
+ data = CSON.readFileSync(path.join(remote.app.getPath('userData'), 'local.json'))
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ return
+ }
+ console.error(err)
+ }
+ let newState = {
+ isLoading: false
+ }
+ if (data != null) {
+ newState.legacyStorageExists = true
+ newState.data = data
+ }
+ this.setState(newState, () => {
+ this.refs.createButton.focus()
+ })
+ }
+
+ handlePathBrowseButtonClick (e) {
+ browseFolder()
+ .then((targetPath) => {
+ if (targetPath.length > 0) {
+ this.setState({
+ path: targetPath
+ })
+ }
+ })
+ .catch((err) => {
+ console.error('BrowseFAILED')
+ console.error(err)
+ })
+ }
+
+ handleSubmitButtonClick (e) {
+ this.setState({
+ isSending: true
+ }, () => {
+ dataApi
+ .addStorage({
+ name: 'My Storage',
+ path: this.state.path
+ })
+ .then((data) => {
+ if (this.state.migrationRequested && _.isObject(this.state.data) && _.isArray(this.state.data.folders) && _.isArray(this.state.data.articles)) {
+ return dataApi.migrateFromV5(data.storage.key, this.state.data)
+ }
+ return data
+ })
+ .then((data) => {
+ store.dispatch({
+ type: 'ADD_STORAGE',
+ storage: data.storage,
+ notes: data.notes
+ })
+
+ let defaultMarkdownNote = dataApi
+ .createMarkdownNote(data.storage.key, data.storage.folders[0].key, {
+ title: 'Welcome to Boostnote :)',
+ content: '# Welcome to Boostnote :)\nThis is a markdown note.\n\nClick to edit this note.'
+ })
+ .then((note) => {
+ store.dispatch({
+ type: 'CREATE_NOTE',
+ note: note
+ })
+ })
+ let defaultSnippetNote = dataApi
+ .createSnippetNote(data.storage.key, data.storage.folders[0].key, {
+ title: 'Snippet note example',
+ description: 'Snippet note example\nYou can store a series of snippet as a single note like Gist.',
+ snippets: [
+ {
+ name: 'example.html',
+ mode: 'html',
+ content: '\n\n
Hello World \n\n'
+ },
+ {
+ name: 'example.js',
+ mode: 'javascript',
+ content: 'var html = document.getElementById(\'hello\').innerHTML\n\nconsole.log(html)'
+ }
+ ]
+ })
+ .then((note) => {
+ store.dispatch({
+ type: 'CREATE_NOTE',
+ note: note
+ })
+ })
+
+ return Promise.resolve(defaultSnippetNote)
+ .then(defaultMarkdownNote)
+ .then(() => data.storage)
+ })
+ .then((storage) => {
+ hashHistory.push('/storages/' + storage.key)
+ this.props.close()
+ })
+ .catch((err) => {
+ this.setState({
+ isSending: false
+ })
+ throw err
+ })
+ })
+ }
+
+ handleMigrationRequestedChange (e) {
+ this.setState({
+ migrationRequested: e.target.checked
+ })
+ }
+
+ handleKeyDown (e) {
+ if (e.keyCode === 27) {
+ this.props.close()
+ }
+ }
+
+ render () {
+ if (this.state.isLoading) {
+ return
+
+
Preparing initialization...
+
+ }
+ return (
+
this.handleKeyDown(e)}
+ >
+
+
+
this.handleCloseButtonClick(e)}
+ >Close
+
+
+ Welcome you!
+
+
+ Boostnote will use this directory as a default storage.
+
+
+ this.handlePathChange(e)}
+ />
+ this.handlePathBrowseButtonClick(e)}
+ >
+ ...
+
+
+
+ this.handleMigrationRequestedChange(e)}/> Migrate old data from the legacy app v0.5
+
+
+
+ this.handleSubmitButtonClick(e)}
+ disabled={this.state.isSending}
+ >
+ {this.state.isSending
+ ?
+ Loading...
+
+ : 'Let\'s Go!'
+ }
+
+
+
+
+
+ )
+ }
+}
+
+InitModal.propTypes = {
+}
+
+export default CSSModules(InitModal, styles)
diff --git a/browser/main/modals/InitModal.styl b/browser/main/modals/InitModal.styl
new file mode 100644
index 00000000..551fd1d0
--- /dev/null
+++ b/browser/main/modals/InitModal.styl
@@ -0,0 +1,86 @@
+.root
+ modal()
+ max-width 540px
+ overflow hidden
+ position relative
+.root--loading
+ @extend .root
+ text-align center
+.spinner
+ font-size 100px
+ margin 35px auto
+ color $ui-text-color
+.loadingMessage
+ color $ui-text-color
+ margin 15px auto 35px
+.header
+ height 50px
+ font-size 18px
+ line-height 50px
+ padding 0 15px
+ background-color $ui-backgroundColor
+ border-bottom solid 1px $ui-borderColor
+ color $ui-text-color
+
+.closeButton
+ position absolute
+ top 10px
+ right 10px
+ height 30px
+ padding 0 25px
+ border $ui-border
+ border-radius 2px
+ color $ui-text-color
+ colorDefaultButton()
+
+.body
+ padding 30px
+
+.body-welcome
+ text-align center
+ margin-bottom 25px
+ font-size 32px
+ color $ui-text-color
+
+.body-description
+ font-size 14px
+ color $ui-text-color
+ text-align center
+ margin-bottom 25px
+
+.body-path
+ margin 0 auto 25px
+ width 280px
+
+.body-path-input
+ height 30px
+ vertical-align middle
+ width 250px
+ font-size 12px
+ border-style solid
+ border-width 1px 0 1px 1px
+ border-color $border-color
+ border-top-left-radius 2px
+ border-bottom-left-radius 2px
+ padding 0 5px
+
+.body-path-button
+ height 30px
+ border none
+ border-top-right-radius 2px
+ border-bottom-right-radius 2px
+ colorPrimaryButton()
+ vertical-align middle
+.body-migration
+ margin 0 auto 25px
+ text-align center
+
+.body-control
+ text-align center
+
+.body-control-createButton
+ colorPrimaryButton()
+ border none
+ border-radius 2px
+ height 40px
+ padding 0 25px
diff --git a/browser/main/modals/NewNoteModal.js b/browser/main/modals/NewNoteModal.js
new file mode 100644
index 00000000..f8793fdc
--- /dev/null
+++ b/browser/main/modals/NewNoteModal.js
@@ -0,0 +1,140 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './NewNoteModal.styl'
+import dataApi from 'browser/main/lib/dataApi'
+import { hashHistory } from 'react-router'
+import ee from 'browser/main/lib/eventEmitter'
+
+class NewNoteModal extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ }
+ }
+
+ componentDidMount () {
+ this.refs.markdownButton.focus()
+ }
+
+ handleCloseButtonClick (e) {
+ this.props.close()
+ }
+
+ handleMarkdownNoteButtonClick (e) {
+ let { storage, folder, dispatch, location } = this.props
+ dataApi
+ .createMarkdownNote(storage, folder, {
+ title: '',
+ content: ''
+ })
+ .then((note) => {
+ dispatch({
+ type: 'CREATE_NOTE',
+ note: note
+ })
+ hashHistory.push({
+ pathname: location.pathname,
+ query: {key: note.uniqueKey}
+ })
+ ee.emit('detail:focus')
+ this.props.close()
+ })
+ }
+ handleMarkdownNoteButtonKeyDown (e) {
+ if (e.keyCode === 9) {
+ e.preventDefault()
+ this.refs.snippetButton.focus()
+ }
+ }
+
+ handleSnippetNoteButtonClick (e) {
+ let { storage, folder, dispatch, location } = this.props
+ dataApi
+ .createSnippetNote(storage, folder, {
+ title: '',
+ description: '',
+ snippets: [{
+ name: '',
+ mode: 'text',
+ content: ''
+ }]
+ })
+ .then((note) => {
+ dispatch({
+ type: 'CREATE_NOTE',
+ note: note
+ })
+ hashHistory.push({
+ pathname: location.pathname,
+ query: {key: note.uniqueKey}
+ })
+ ee.emit('detail:focus')
+ this.props.close()
+ })
+ }
+
+ handleSnippetNoteButtonKeyDown (e) {
+ if (e.keyCode === 9) {
+ e.preventDefault()
+ this.refs.markdownButton.focus()
+ }
+ }
+
+ handleKeyDown (e) {
+ if (e.keyCode === 27) {
+ this.props.close()
+ }
+ }
+
+ render () {
+ return (
+
this.handleKeyDown(e)}
+ >
+
+
this.handleCloseButtonClick(e)}
+ >Close
+
+
+ this.handleMarkdownNoteButtonClick(e)}
+ onKeyDown={(e) => this.handleMarkdownNoteButtonKeyDown(e)}
+ ref='markdownButton'
+ >
+
+ Markdown Note
+ It is good for any type of documents. Check List, Code block and Latex block are available.
+
+
+ this.handleSnippetNoteButtonClick(e)}
+ onKeyDown={(e) => this.handleSnippetNoteButtonKeyDown(e)}
+ ref='snippetButton'
+ >
+
+ Snippet Note
+ This format is specialized on managing snippets like Gist. Multiple snippets can be grouped as a note.
+
+
+
+
+
Tab to switch format
+
+
+ )
+ }
+}
+
+NewNoteModal.propTypes = {
+}
+
+export default CSSModules(NewNoteModal, styles)
diff --git a/browser/main/modals/NewNoteModal.styl b/browser/main/modals/NewNoteModal.styl
new file mode 100644
index 00000000..7973a463
--- /dev/null
+++ b/browser/main/modals/NewNoteModal.styl
@@ -0,0 +1,56 @@
+.root
+ modal()
+ max-width 540px
+ overflow hidden
+ position relative
+
+.header
+ height 50px
+ font-size 18px
+ line-height 50px
+ padding 0 15px
+ background-color $ui-backgroundColor
+ border-bottom solid 1px $ui-borderColor
+ color $ui-text-color
+
+.closeButton
+ position absolute
+ top 10px
+ right 10px
+ height 30px
+ width 0 25px
+ border $ui-border
+ border-radius 2px
+ color $ui-text-color
+ colorDefaultButton()
+
+.control
+ padding 25px 15px 15px
+ text-align center
+
+.control-button
+ width 220px
+ height 220px
+ margin 0 15px
+ border $ui-border
+ border-radius 5px
+ color $ui-text-color
+ colorDefaultButton()
+ padding 10px
+ &:focus
+ colorPrimaryButton()
+
+.control-button-icon
+ font-size 50px
+ margin-bottom 15px
+
+.control-button-label
+ font-size 18px
+ line-height 32px
+.control-button-description
+ font-size 12px
+
+.description
+ color $ui-inactive-text-color
+ text-align center
+ margin-bottom 25px
diff --git a/browser/main/modals/PreferencesModal/ConfigTab.js b/browser/main/modals/PreferencesModal/ConfigTab.js
new file mode 100644
index 00000000..ade253c4
--- /dev/null
+++ b/browser/main/modals/PreferencesModal/ConfigTab.js
@@ -0,0 +1,412 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './ConfigTab.styl'
+import hljsTheme from 'browser/lib/hljsThemes'
+import ConfigManager from 'browser/main/lib/ConfigManager'
+import store from 'browser/main/store'
+
+const electron = require('electron')
+const ipc = electron.ipcRenderer
+const ace = window.ace
+
+const OSX = global.process.platform === 'darwin'
+
+class ConfigTab extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ isHotkeyHintOpen: false,
+ config: props.config
+ }
+ }
+
+ componentDidMount () {
+ this.handleSettingDone = () => {
+ this.setState({keymapAlert: {
+ type: 'success',
+ message: 'Successfully applied!'
+ }})
+ }
+ this.handleSettingError = (err) => {
+ this.setState({keymapAlert: {
+ type: 'error',
+ message: err.message != null ? err.message : 'Error occurs!'
+ }})
+ }
+ ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
+ ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
+ }
+
+ componentWillUnmount () {
+ ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
+ ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
+ }
+
+ handleSaveButtonClick (e) {
+ let newConfig = {
+ hotkey: this.state.config.hotkey
+ }
+
+ ConfigManager.set(newConfig)
+
+ store.dispatch({
+ type: 'SET_UI',
+ config: newConfig
+ })
+ }
+
+ handleKeyDown (e) {
+ if (e.keyCode === 13) {
+ this.submitHotKey()
+ }
+ }
+
+ handleConfigKeyDown (e) {
+ if (e.keyCode === 13) {
+ this.submitConfig()
+ }
+ }
+
+ handleLineNumberingClick (e) {
+ let config = this.state.config
+
+ config['preview-line-number'] = e.target.checked
+ this.setState({
+ config
+ })
+ }
+
+ handleDisableDirectWriteClick (e) {
+ let config = this.state.config
+ config['disable-direct-write'] = e.target.checked
+ this.setState({
+ config
+ })
+ }
+
+ handleHintToggleButtonClick (e) {
+ this.setState({
+ isHotkeyHintOpen: !this.state.isHotkeyHintOpen
+ })
+ }
+
+ handleHotkeyChange (e) {
+ let { config } = this.state
+ config.hotkey = {
+ toggleFinder: this.refs.toggleFinder.value,
+ toggleMain: this.refs.toggleMain.value
+ }
+ this.setState({
+ config
+ })
+ }
+
+ handleUIChange (e) {
+ let { config } = this.state
+
+ config.ui = {
+ theme: this.refs.uiTheme.value,
+ disableDirectWrite: this.refs.uiD2w != null
+ ? this.refs.uiD2w.checked
+ : false
+ }
+ config.editor = {
+ theme: this.refs.editorTheme.value,
+ fontSize: this.refs.editorFontSize.value,
+ fontFamily: this.refs.editorFontFamily.value,
+ indentType: this.refs.editorIndentType.value,
+ indentSize: this.refs.editorIndentSize.value,
+ switchPreview: this.refs.editorSwitchPreview.value
+ }
+ config.preview = {
+ fontSize: this.refs.previewFontSize.value,
+ fontFamily: this.refs.previewFontFamily.value,
+ codeBlockTheme: this.refs.previewCodeBlockTheme.value,
+ lineNumber: this.refs.previewLineNumber.checked
+ }
+
+ this.setState({
+ config
+ })
+ }
+
+ handleSaveUIClick (e) {
+ let newConfig = {
+ ui: this.state.config.ui,
+ editor: this.state.config.editor,
+ preview: this.state.config.preview
+ }
+
+ ConfigManager.set(newConfig)
+
+ store.dispatch({
+ type: 'SET_UI',
+ config: newConfig
+ })
+ }
+
+ render () {
+ let keymapAlert = this.state.keymapAlert
+ let keymapAlertElement = keymapAlert != null
+ ?
+ {keymapAlert.message}
+
+ : null
+ let aceThemeList = ace.require('ace/ext/themelist')
+ let hljsThemeList = hljsTheme()
+ let { config } = this.state
+
+ return (
+
+
+
Hotkey
+
+
Toggle Main
+
+ this.handleHotkeyChange(e)}
+ ref='toggleMain'
+ value={config.hotkey.toggleMain}
+ type='text'
+ />
+
+
+
+
Toggle Finder(popup)
+
+ this.handleHotkeyChange(e)}
+ ref='toggleFinder'
+ value={config.hotkey.toggleFinder}
+ type='text'
+ disabled
+ />
+
+
+
+ this.handleHintToggleButtonClick(e)}
+ >
+ {this.state.isHotkeyHintOpen
+ ? 'Hide Hint'
+ : 'Show Hint'
+ }
+
+ this.handleSaveButtonClick(e)}>Save Hotkey
+
+ {keymapAlertElement}
+
+ {this.state.isHotkeyHintOpen &&
+
+
Available Keys
+
+ 0 to 9
+ A to Z
+ F1 to F24
+ Punctuations like ~, !, @, #, $, etc.
+ Plus
+ Space
+ Backspace
+ Delete
+ Insert
+ Return (or Enter as alias)
+ Up, Down, Left and Right
+ Home and End
+ PageUp and PageDown
+ Escape (or Esc for short)
+ VolumeUp, VolumeDown and VolumeMute
+ MediaNextTrack, MediaPreviousTrack, MediaStop and MediaPlayPause
+
+
+ }
+
+
+
+
UI
+
+
Theme
+
+ this.handleUIChange(e)}
+ ref='uiTheme'
+ disabled
+ >
+ Light
+ Dark
+
+
+
+ {
+ global.process.platform === 'win32'
+ ?
+
+ this.handleUIChange(e)}
+ checked={this.state.config.ui.disableDirectWrite}
+ refs='uiD2w'
+ disabled={OSX}
+ type='checkbox'
+ />
+ Disable Direct Write(It will be applied after restarting)
+
+
+ : null
+ }
+
Editor
+
+
+
+ Editor Theme
+
+
+ this.handleUIChange(e)}
+ >
+ {
+ aceThemeList.themes.map((theme) => {
+ return ({theme.caption} )
+ })
+ }
+
+
+
+
+
+ Editor Font Size
+
+
+ this.handleUIChange(e)}
+ type='text'
+ />
+
+
+
+
+ Editor Font Family
+
+
+ this.handleUIChange(e)}
+ type='text'
+ />
+
+
+
+
+ Editor Indent Style
+
+
+ this.handleUIChange(e)}
+ >
+ 1
+ 2
+ 4
+ 8
+
+ this.handleUIChange(e)}
+ >
+ Spaces
+ Tabs
+
+
+
+
+
+
+ Switching Preview
+
+
+ this.handleUIChange(e)}
+ >
+ When Editor Blurred
+ When Right Clicking
+
+
+
+
Preview
+
+
+ Preview Font Size
+
+
+ this.handleUIChange(e)}
+ type='text'
+ />
+
+
+
+
+ Preview Font Family
+
+
+ this.handleUIChange(e)}
+ type='text'
+ />
+
+
+
+
Code block Theme
+
+ this.handleUIChange(e)}
+ >
+ {
+ hljsThemeList.map((theme) => {
+ return ({theme.caption} )
+ })
+ }
+
+
+
+
+
+ this.handleUIChange(e)}
+ checked={this.state.config.preview.lineNumber}
+ ref='previewLineNumber'
+ type='checkbox'
+ />
+ Code block line numbering
+
+
+
+
+ this.handleSaveUIClick(e)}
+ >
+ Save UI Config
+
+
+
+
+ )
+ }
+}
+
+ConfigTab.propTypes = {
+ user: PropTypes.shape({
+ name: PropTypes.string
+ }),
+ dispatch: PropTypes.func
+}
+
+export default CSSModules(ConfigTab, styles)
diff --git a/browser/main/modals/PreferencesModal/ConfigTab.styl b/browser/main/modals/PreferencesModal/ConfigTab.styl
new file mode 100644
index 00000000..51ed9776
--- /dev/null
+++ b/browser/main/modals/PreferencesModal/ConfigTab.styl
@@ -0,0 +1,88 @@
+.root
+ padding 15px
+ color $ui-text-color
+.group
+ margin-bottom 45px
+.group-header
+ font-size 24px
+ color $ui-text-color
+ padding 5px
+ border-bottom $default-border
+ margin-bottom 15px
+
+.group-header2
+ font-size 18px
+ color $ui-text-color
+ padding 5px
+ margin-bottom 15px
+
+.group-section
+ margin-bottom 15px
+ display flex
+ line-height 30px
+.group-section-label
+ width 150px
+ text-align right
+ margin-right 10px
+.group-section-control
+ flex 1
+.group-section-control-input
+ height 30px
+ vertical-align middle
+ width 150px
+ font-size 12px
+ border solid 1px $border-color
+ border-radius 2px
+ padding 0 5px
+ &:disabled
+ background-color $ui-input--disabled-backgroundColor
+
+.group-checkBoxSection
+ margin-bottom 15px
+ display flex
+ line-height 30px
+ padding-left 15px
+
+.group-control
+ border-top $default-border
+ padding-top 10px
+ box-sizing border-box
+ height 40px
+ text-align right
+ :global
+ .alert
+ font-size 12px
+ line-height 30px
+ padding 0 5px
+ float right
+.group-control-leftButton
+ float left
+ colorDefaultButton()
+ border $default-border
+ border-radius 2px
+ height 30px
+ padding 0 15px
+ margin-right 5px
+.group-control-rightButton
+ float right
+ colorPrimaryButton()
+ border none
+ border-radius 2px
+ height 30px
+ padding 0 15px
+ margin-right 5px
+.group-hint
+ border $ui-border
+ padding 10px 15px
+ margin 15px 0
+ border-radius 5px
+ background-color $ui-backgroundColor
+ color $ui-inactive-text-color
+ ul
+ list-style inherit
+ padding-left 1em
+ line-height 1.2
+ p
+ line-height 1.2
+
+
diff --git a/browser/main/modals/PreferencesModal/InfoTab.js b/browser/main/modals/PreferencesModal/InfoTab.js
new file mode 100644
index 00000000..ae3d52d0
--- /dev/null
+++ b/browser/main/modals/PreferencesModal/InfoTab.js
@@ -0,0 +1,58 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './InfoTab.styl'
+
+const electron = require('electron')
+const { shell, remote } = electron
+const appVersion = remote.app.getVersion()
+
+class InfoTab extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ }
+ }
+
+ handleLinkClick (e) {
+ shell.openExternal(e.currentTarget.href)
+ e.preventDefault()
+ }
+
+ render () {
+ return (
+
+
+
+
Boostnote {appVersion}
+
+ A simple markdown/snippet note app for developer.
+
+
+
Copyright 2016 MAISIN&CO. All rights reserved.
+
+
+
+ )
+ }
+}
+
+InfoTab.propTypes = {
+}
+
+export default CSSModules(InfoTab, styles)
diff --git a/browser/main/modals/PreferencesModal/InfoTab.styl b/browser/main/modals/PreferencesModal/InfoTab.styl
new file mode 100644
index 00000000..7dba4eef
--- /dev/null
+++ b/browser/main/modals/PreferencesModal/InfoTab.styl
@@ -0,0 +1,35 @@
+.root
+ padding 15px
+ white-space pre
+ line-height 1.4
+ color $ui-text-color
+ width 100%
+
+.top
+ text-align center
+ margin-bottom 25px
+
+.appId
+ font-size 24px
+
+.description
+ overflow hidden
+ white-space normal
+ line-height 1.5
+ margin 5px auto 10px
+ font-size 14px
+ text-align center
+
+.madeBy
+ font-size 12px
+ $ui-inactive-text-color
+
+.copyright
+ font-size 12px
+ $ui-inactive-text-color
+
+.list
+ list-style square
+ padding-left 2em
+ li
+ white-space normal
diff --git a/browser/main/modals/PreferencesModal/PreferencesModal.styl b/browser/main/modals/PreferencesModal/PreferencesModal.styl
new file mode 100644
index 00000000..f48c93e9
--- /dev/null
+++ b/browser/main/modals/PreferencesModal/PreferencesModal.styl
@@ -0,0 +1,37 @@
+.root
+ modal()
+ max-width 540px
+ min-height 400px
+ height 80%
+ overflow hidden
+ position relative
+
+.nav
+ absolute top left right
+ height 50px
+ background-color $ui-backgroundColor
+ border-bottom solid 1px $ui-borderColor
+
+.nav-button
+ width 80px
+ height 50px
+ border none
+ background-color transparent
+ color #939395
+ font-size 14px
+ &:hover
+ color #515151
+
+.nav-button--active
+ @extend .nav-button
+ color #6AA5E9
+ &:hover
+ color #6AA5E9
+
+.nav-button-icon
+ display block
+
+.content
+ absolute left right bottom
+ top 50px
+ overflow-y auto
diff --git a/browser/main/modals/PreferencesModal/StorageItem.js b/browser/main/modals/PreferencesModal/StorageItem.js
new file mode 100644
index 00000000..9744e6c9
--- /dev/null
+++ b/browser/main/modals/PreferencesModal/StorageItem.js
@@ -0,0 +1,351 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './StorageItem.styl'
+import consts from 'browser/lib/consts'
+import dataApi from 'browser/main/lib/dataApi'
+import store from 'browser/main/store'
+
+const electron = require('electron')
+const { shell, remote } = electron
+const { Menu, MenuItem } = remote
+
+class UnstyledFolderItem extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ status: 'IDLE',
+ folder: {
+ color: props.color,
+ name: props.name
+ }
+ }
+ }
+
+ handleEditChange (e) {
+ let { folder } = this.state
+
+ folder.name = this.refs.nameInput.value
+ this.setState({
+ folder
+ })
+ }
+
+ handleConfirmButtonClick (e) {
+ let { storage, folder } = this.props
+ dataApi
+ .updateFolder(storage.key, folder.key, {
+ color: this.state.folder.color,
+ name: this.state.folder.name
+ })
+ .then((storage) => {
+ store.dispatch({
+ type: 'UPDATE_STORAGE',
+ storage: storage
+ })
+ this.setState({
+ status: 'IDLE'
+ })
+ })
+ }
+
+ handleColorButtonClick (e) {
+ var menu = new Menu()
+
+ consts.FOLDER_COLORS.forEach((color, index) => {
+ menu.append(new MenuItem({
+ label: consts.FOLDER_COLOR_NAMES[index],
+ click: (e) => {
+ let { folder } = this.state
+ folder.color = color
+ this.setState({
+ folder
+ })
+ }
+ }))
+ })
+
+ menu.popup(remote.getCurrentWindow())
+ }
+
+ handleCancelButtonClick (e) {
+ this.setState({
+ status: 'IDLE'
+ })
+ }
+
+ renderEdit (e) {
+ return (
+
+
+ this.handleColorButtonClick(e)}
+ >
+
+
+ this.handleEditChange(e)}
+ />
+
+
+ this.handleConfirmButtonClick(e)}
+ >
+ Confirm
+
+ this.handleCancelButtonClick(e)}
+ >
+ Cancel
+
+
+
+ )
+ }
+
+ handleDeleteConfirmButtonClick (e) {
+ let { storage, folder } = this.props
+ dataApi
+ .removeFolder(storage.key, folder.key)
+ .then((storage) => {
+ store.dispatch({
+ type: 'REMOVE_FOLDER',
+ key: folder.key,
+ storage: storage
+ })
+ })
+ }
+
+ renderDelete () {
+ return (
+
+
+ Are you sure to delete this folder?
+
+
+ this.handleDeleteConfirmButtonClick(e)}
+ >
+ Confirm
+
+ this.handleCancelButtonClick(e)}
+ >
+ Cancel
+
+
+
+ )
+ }
+
+ handleEditButtonClick (e) {
+ let { folder } = this.props
+ this.setState({
+ status: 'EDIT',
+ folder: {
+ color: folder.color,
+ name: folder.name
+ }
+ }, () => {
+ this.refs.nameInput.select()
+ })
+ }
+
+ handleDeleteButtonClick (e) {
+ this.setState({
+ status: 'DELETE'
+ })
+ }
+
+ renderIdle () {
+ let { folder } = this.props
+ return (
+
this.handleEditButtonClick(e)}
+ >
+
+ {folder.name}
+ ({folder.key})
+
+
+ this.handleEditButtonClick(e)}
+ >
+ Edit
+
+ this.handleDeleteButtonClick(e)}
+ >
+ Delete
+
+
+
+
+ )
+ }
+
+ render () {
+ switch (this.state.status) {
+ case 'DELETE':
+ return this.renderDelete()
+ case 'EDIT':
+ return this.renderEdit()
+ case 'IDLE':
+ default:
+ return this.renderIdle()
+ }
+ }
+}
+
+const FolderItem = CSSModules(UnstyledFolderItem, styles)
+
+class StorageItem extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ isLabelEditing: false
+ }
+ }
+
+ handleNewFolderButtonClick (e) {
+ let { storage } = this.props
+ let input = {
+ name: 'Untitled',
+ color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
+ }
+
+ dataApi.createFolder(storage.key, input)
+ .then((storage) => {
+ store.dispatch({
+ type: 'ADD_FOLDER',
+ storage: storage
+ })
+ })
+ .catch((err) => {
+ console.error(err)
+ })
+ }
+
+ handleExternalButtonClick () {
+ let { storage } = this.props
+ shell.showItemInFolder(storage.path)
+ }
+
+ handleUnlinkButtonClick (e) {
+ let { storage } = this.props
+ dataApi.removeStorage(storage.key)
+ .then(() => {
+ store.dispatch({
+ type: 'REMOVE_STORAGE',
+ key: storage.key
+ })
+ })
+ .catch((err) => {
+ console.error(err)
+ })
+ }
+
+ handleLabelClick (e) {
+ let { storage } = this.props
+ this.setState({
+ isLabelEditing: true,
+ name: storage.name
+ }, () => {
+ this.refs.label.focus()
+ })
+ }
+ handleLabelChange (e) {
+ this.setState({
+ name: this.refs.label.value
+ })
+ }
+ handleLabelBlur (e) {
+ let { storage } = this.props
+ dataApi
+ .renameStorage(storage.key, this.state.name)
+ .then((storage) => {
+ store.dispatch({
+ type: 'RENAME_STORAGE',
+ storage: storage
+ })
+ this.setState({
+ isLabelEditing: false
+ })
+ })
+ }
+
+ render () {
+ let { storage } = this.props
+ let folderList = storage.folders.map((folder) => {
+ return
+ })
+ return (
+
+
+ {this.state.isLabelEditing
+ ?
+ this.handleLabelChange(e)}
+ onBlur={(e) => this.handleLabelBlur(e)}
+ />
+
+ :
this.handleLabelClick(e)}
+ >
+
+ {storage.name}
+ ({storage.path})
+
+
+ }
+
+ this.handleNewFolderButtonClick(e)}
+ >
+
+
+ this.handleExternalButtonClick(e)}
+ >
+
+
+ this.handleUnlinkButtonClick(e)}
+ >
+
+
+
+
+
+ {folderList.length > 0
+ ? folderList
+ :
No Folders
+ }
+
+
+ )
+ }
+}
+
+StorageItem.propTypes = {
+ storage: PropTypes.shape({
+ key: PropTypes.string
+ }),
+ folder: PropTypes.shape({
+ key: PropTypes.string,
+ color: PropTypes.string,
+ name: PropTypes.string
+ })
+}
+
+export default CSSModules(StorageItem, styles)
diff --git a/browser/main/modals/PreferencesModal/StorageItem.styl b/browser/main/modals/PreferencesModal/StorageItem.styl
new file mode 100644
index 00000000..48184101
--- /dev/null
+++ b/browser/main/modals/PreferencesModal/StorageItem.styl
@@ -0,0 +1,119 @@
+.root
+ position relative
+ margin-bottom 15px
+
+.header
+ height 35px
+ line-height 30px
+ padding 0 10px 5px
+ box-sizing border-box
+ border-bottom $default-border
+ margin-bottom 5px
+
+.header-label
+ float left
+ cursor pointer
+ &:hover
+ .header-label-editButton
+ opacity 1
+
+.header-label-path
+ color $ui-inactive-text-color
+ font-size 10px
+ margin 0 5px
+.header-label-editButton
+ color $ui-text-color
+ opacity 0
+ transition 0.2s
+.header-label--edit
+ @extend .header-label
+
+.header-label-input
+ height 25px
+ box-sizing border-box
+ vertical-align middle
+ border $ui-border
+ border-radius 2px
+ padding 0 5px
+
+
+.header-control
+ float right
+
+.header-control-button
+ width 30px
+ height 25px
+ colorDefaultButton()
+ border-radius 2px
+ border $ui-border
+ margin-right 5px
+ &:last-child
+ margin-right 0
+
+.folderList-item
+ height 35px
+ box-sizing border-box
+ padding 2.5px 15px
+ &:hover
+ background-color darken(white, 3%)
+.folderList-item-left
+ height 30px
+ border-left solid 4px transparent
+ padding 0 10px
+ line-height 30px
+ float left
+.folderList-item-left-danger
+ color $danger-color
+ font-weight bold
+
+.folderList-item-left-key
+ color $ui-inactive-text-color
+ font-size 10px
+ margin 0 5px
+ border none
+
+.folderList-item-left-colorButton
+ colorDefaultButton()
+ height 25px
+ width 25px
+ line-height 23px
+ padding 0
+ box-sizing border-box
+ vertical-align middle
+ border $ui-border
+ border-radius 2px
+ margin-right 5px
+ margin-left -15px
+
+.folderList-item-left-nameInput
+ height 25px
+ box-sizing border-box
+ vertical-align middle
+ border $ui-border
+ border-radius 2px
+ padding 0 5px
+
+.folderList-item-right
+ float right
+
+.folderList-item-right-button
+ vertical-align middle
+ height 25px
+ margin-top 2.5px
+ colorDefaultButton()
+ border-radius 2px
+ border $ui-border
+ margin-right 5px
+ padding 0 5px
+ &:last-child
+ margin-right 0
+
+.folderList-item-right-confirmButton
+ @extend .folderList-item-right-button
+ border none
+ colorPrimaryButton()
+
+.folderList-item-right-dangerButton
+ @extend .folderList-item-right-button
+ border none
+ colorDangerButton()
diff --git a/browser/main/modals/PreferencesModal/StoragesTab.js b/browser/main/modals/PreferencesModal/StoragesTab.js
new file mode 100644
index 00000000..f9a7183c
--- /dev/null
+++ b/browser/main/modals/PreferencesModal/StoragesTab.js
@@ -0,0 +1,223 @@
+import React, { PropTypes } from 'react'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './StoragesTab.styl'
+import dataApi from 'browser/main/lib/dataApi'
+import StorageItem from './StorageItem'
+
+const electron = require('electron')
+const remote = electron.remote
+
+function browseFolder () {
+ let dialog = remote.dialog
+
+ let defaultPath = remote.app.getPath('home')
+ return new Promise((resolve, reject) => {
+ dialog.showOpenDialog({
+ title: 'Select Directory',
+ defaultPath,
+ properties: ['openDirectory', 'createDirectory']
+ }, function (targetPaths) {
+ if (targetPaths == null) return resolve('')
+ resolve(targetPaths[0])
+ })
+ })
+}
+
+class StoragesTab extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ page: 'LIST',
+ newStorage: {
+ name: 'Unnamed',
+ type: 'FILESYSTEM',
+ path: ''
+ }
+ }
+ }
+
+ handleAddStorageButton (e) {
+ this.setState({
+ page: 'ADD_STORAGE',
+ newStorage: {
+ name: 'Unnamed',
+ type: 'FILESYSTEM',
+ path: ''
+ }
+ }, () => {
+ this.refs.addStorageName.select()
+ })
+ }
+
+ renderList () {
+ let { storages } = this.props
+
+ let storageList = storages.map((storage) => {
+ return
+ })
+ return (
+
+ {storageList.length > 0
+ ? storageList
+ :
No storage found.
+ }
+
+ this.handleAddStorageButton(e)}
+ >
+ Add Storage
+
+
+
+ )
+ }
+
+ handleAddStorageBrowseButtonClick (e) {
+ browseFolder()
+ .then((targetPath) => {
+ if (targetPath.length > 0) {
+ let { newStorage } = this.state
+ newStorage.path = targetPath
+ this.setState({
+ newStorage
+ })
+ }
+ })
+ .catch((err) => {
+ console.error('BrowseFAILED')
+ console.error(err)
+ })
+ }
+
+ handleAddStorageChange (e) {
+ let { newStorage } = this.state
+ newStorage.name = this.refs.addStorageName.value
+ newStorage.path = this.refs.addStoragePath.value
+ this.setState({
+ newStorage
+ })
+ }
+
+ handleAddStorageCreateButton (e) {
+ dataApi
+ .addStorage({
+ name: this.state.newStorage.name,
+ path: this.state.newStorage.path
+ })
+ .then((data) => {
+ let { dispatch } = this.props
+ dispatch({
+ type: 'ADD_STORAGE',
+ storage: data.storage,
+ notes: data.notes
+ })
+ this.setState({
+ page: 'LIST'
+ })
+ })
+ }
+
+ handleAddStorageCancelButton (e) {
+ this.setState({
+ page: 'LIST'
+ })
+ }
+
+ renderAddStorage () {
+ return (
+
+
+
Add Storage
+
+
+
+
+
+ Name
+
+
+ this.handleAddStorageChange(e)}
+ />
+
+
+
+
+
Type
+
+
+ File System
+
+
+ 3rd party cloud integration(such as Google Drive and Dropbox) will be available soon.
+
+
+
+
+
+
Location
+
+
+ this.handleAddStorageChange(e)}
+ />
+ this.handleAddStorageBrowseButtonClick(e)}
+ >
+ ...
+
+
+
+
+
+ this.handleAddStorageCreateButton(e)}
+ >Create
+ this.handleAddStorageCancelButton(e)}
+ >Cancel
+
+
+
+
+
+ )
+ }
+
+ renderContent () {
+ switch (this.state.page) {
+ case 'ADD_STORAGE':
+ case 'ADD_FOLDER':
+ return this.renderAddStorage()
+ case 'LIST':
+ default:
+ return this.renderList()
+ }
+ }
+
+ render () {
+ return (
+
+ {this.renderContent()}
+
+ )
+ }
+}
+
+StoragesTab.propTypes = {
+ dispatch: PropTypes.func
+}
+
+export default CSSModules(StoragesTab, styles)
diff --git a/browser/main/modals/PreferencesModal/StoragesTab.styl b/browser/main/modals/PreferencesModal/StoragesTab.styl
new file mode 100644
index 00000000..513048c5
--- /dev/null
+++ b/browser/main/modals/PreferencesModal/StoragesTab.styl
@@ -0,0 +1,115 @@
+.root
+ padding 15px
+ color $ui-text-color
+
+.list
+ margin-bottom 15px
+ font-size 14px
+
+.folderList
+ padding 0 15px
+
+.folderList-item
+ height 30px
+ line-height 30px
+ border-bottom $ui-border
+
+.folderList-empty
+ height 30px
+ line-height 30px
+ font-size 12px
+ color $ui-inactive-text-color
+
+.list-empty
+ height 30px
+ color $ui-inactive-text-color
+.list-control
+ height 30px
+.list-control-addStorageButton
+ height 30px
+ padding 0 15px
+ border $ui-border
+ colorDefaultButton()
+ border-radius 2px
+
+.addStorage
+ margin-bottom 15px
+
+.addStorage-header
+ font-size 24px
+ color $ui-text-color
+ padding 5px
+ border-bottom $default-border
+ margin-bottom 15px
+
+.addStorage-body-section
+ margin-bottom 15px
+ display flex
+ line-height 30px
+
+.addStorage-body-section-label
+ width 150px
+ text-align right
+ margin-right 10px
+
+.addStorage-body-section-name
+ flex 1
+.addStorage-body-section-name-input
+ height 30px
+ vertical-align middle
+ width 150px
+ font-size 12px
+ border solid 1px $border-color
+ border-radius 2px
+ padding 0 5px
+
+.addStorage-body-section-type
+ flex 1
+.addStorage-body-section-type-select
+ height 30px
+.addStorage-body-section-type-description
+ margin 5px
+ font-size 12px
+ color $ui-inactive-text-color
+ line-height 16px
+
+.addStorage-body-section-path
+ flex 1
+.addStorage-body-section-path-input
+ height 30px
+ vertical-align middle
+ width 150px
+ font-size 12px
+ border-style solid
+ border-width 1px 0 1px 1px
+ border-color $border-color
+ border-top-left-radius 2px
+ border-bottom-left-radius 2px
+ padding 0 5px
+.addStorage-body-section-path-button
+ height 30px
+ border none
+ border-top-right-radius 2px
+ border-bottom-right-radius 2px
+ colorPrimaryButton()
+ vertical-align middle
+.addStorage-body-control
+ border-top $default-border
+ padding-top 10px
+ box-sizing border-box
+ height 40px
+ text-align right
+
+.addStorage-body-control-createButton
+ colorPrimaryButton()
+ border none
+ border-radius 2px
+ height 30px
+ padding 0 15px
+ margin-right 5px
+.addStorage-body-control-cancelButton
+ colorDefaultButton()
+ border $default-border
+ border-radius 2px
+ height 30px
+ padding 0 15px
diff --git a/browser/main/modals/PreferencesModal/index.js b/browser/main/modals/PreferencesModal/index.js
new file mode 100644
index 00000000..90c2718e
--- /dev/null
+++ b/browser/main/modals/PreferencesModal/index.js
@@ -0,0 +1,112 @@
+import React, { PropTypes } from 'react'
+import { connect } from 'react-redux'
+import ConfigTab from './ConfigTab'
+import InfoTab from './InfoTab'
+import StoragesTab from './StoragesTab'
+import CSSModules from 'browser/lib/CSSModules'
+import styles from './PreferencesModal.styl'
+
+class Preferences extends React.Component {
+ constructor (props) {
+ super(props)
+
+ this.state = {
+ currentTab: 'STORAGES'
+ }
+ }
+
+ componentDidMount () {
+ this.refs.root.focus()
+ }
+
+ switchTeam (teamId) {
+ this.setState({currentTeamId: teamId})
+ }
+
+ handleNavButtonClick (tab) {
+ return (e) => {
+ this.setState({currentTab: tab})
+ }
+ }
+
+ renderContent () {
+ let { dispatch, config, storages } = this.props
+
+ switch (this.state.currentTab) {
+ case 'INFO':
+ return
+ case 'CONFIG':
+ return (
+
+ )
+ case 'STORAGES':
+ default:
+ return (
+
+ )
+ }
+ }
+
+ handleKeyDown (e) {
+ if (e.keyCode === 27) {
+ this.props.close()
+ }
+ }
+
+ render () {
+ let content = this.renderContent()
+
+ let tabs = [
+ {target: 'STORAGES', label: 'Storages', icon: 'database'},
+ {target: 'CONFIG', label: 'Config', icon: 'cogs'},
+ {target: 'INFO', label: 'Info', icon: 'info-circle'}
+ ]
+
+ let navButtons = tabs.map((tab) => {
+ let isActive = this.state.currentTab === tab.target
+ return (
+
this.handleNavButtonClick(tab.target)(e)}
+ >
+
+
+ {tab.label}
+
+
+ )
+ })
+
+ return (
+
this.handleKeyDown(e)}
+ >
+
+ {navButtons}
+
+
+ {content}
+
+
+ )
+ }
+}
+
+Preferences.propTypes = {
+ dispatch: PropTypes.func
+}
+
+export default connect((x) => x)(CSSModules(Preferences, styles))
diff --git a/browser/main/reducer.js b/browser/main/reducer.js
deleted file mode 100644
index 281b3d47..00000000
--- a/browser/main/reducer.js
+++ /dev/null
@@ -1,306 +0,0 @@
-import { combineReducers } from 'redux'
-import _ from 'lodash'
-import {
- // Status action type
- SWITCH_FOLDER,
- SWITCH_ARTICLE,
- SET_SEARCH_FILTER,
- SET_TAG_FILTER,
- CLEAR_SEARCH,
- TOGGLE_TUTORIAL,
-
- // user
- USER_UPDATE,
-
- // Article action type
- ARTICLE_UPDATE,
- ARTICLE_DESTROY,
- ARTICLE_CACHE,
- ARTICLE_UNCACHE,
- ARTICLE_UNCACHE_ALL,
- ARTICLE_SAVE,
- ARTICLE_SAVE_ALL,
-
- // Folder action type
- FOLDER_CREATE,
- FOLDER_UPDATE,
- FOLDER_DESTROY,
- FOLDER_REPLACE
-} from './actions'
-import dataStore from 'browser/lib/dataStore'
-import keygen from 'browser/lib/keygen'
-import activityRecord from 'browser/lib/activityRecord'
-
-const initialStatus = {
- search: '',
- isTutorialOpen: false
-}
-
-dataStore.init()
-let data = dataStore.getData()
-let initialArticles = {
- data: data && data.articles ? data.articles : [],
- modified: []
-}
-let initialFolders = data && data.folders ? data.folders : []
-let initialUser = dataStore.getUser().user
-
-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) {
- state = state.slice()
- switch (action.type) {
- case FOLDER_CREATE:
- {
- let newFolder = action.data.folder
- if (!_.isString(newFolder.name)) throw new Error('Folder name must be a string')
- newFolder.name = newFolder.name.trim().replace(/\s/g, '_')
-
- Object.assign(newFolder, {
- key: keygen(),
- createdAt: new Date(),
- updatedAt: new Date()
- })
-
- if (newFolder.name == null || newFolder.name.length === 0) throw new Error('Folder name is required')
- if (newFolder.name.match(/\//)) throw new Error('`/` is not available for folder name')
-
- let conflictFolder = _.find(state, (folder) => folder.name.toLowerCase() === newFolder.name.toLowerCase())
- if (conflictFolder != null) throw new Error(`${conflictFolder.name} already exists!`)
- state.push(newFolder)
-
- dataStore.setFolders(state)
- activityRecord.emit('FOLDER_CREATE')
- return state
- }
- case FOLDER_UPDATE:
- {
- let folder = action.data.folder
- let targetFolder = _.findWhere(state, {key: folder.key})
-
- if (!_.isString(folder.name)) throw new Error('Folder name must be a string')
- folder.name = folder.name.trim().replace(/\s/g, '_')
- if (folder.name.length === 0) throw new Error('Folder name is required')
- if (folder.name.match(/\//)) throw new Error('`/` is not available for folder name')
-
- // Folder existence check
- if (targetFolder == null) throw new Error('Folder doesnt exist')
- // Name conflict check
- if (targetFolder.name !== folder.name) {
- let conflictFolder = _.find(state, (_folder) => {
- return folder.name.toLowerCase() === _folder.name.toLowerCase() && folder.key !== _folder.key
- })
- if (conflictFolder != null) throw new Error('Name conflicted')
- }
- Object.assign(targetFolder, folder, {
- updatedAt: new Date()
- })
-
- dataStore.setFolders(state)
- activityRecord.emit('FOLDER_UPDATE')
- return state
- }
- case FOLDER_DESTROY:
- {
- if (state.length < 2) throw new Error('Folder must exist more than one')
-
- let targetKey = action.data.key
- let targetIndex = _.findIndex(state, (folder) => folder.key === targetKey)
- if (targetIndex >= 0) {
- state.splice(targetIndex, 1)
- }
- dataStore.setFolders(state)
- activityRecord.emit('FOLDER_DESTROY')
- return state
- }
- case FOLDER_REPLACE:
- {
- let { a, b } = action.data
- let folderA = state[a]
- let folderB = state[b]
- state.splice(a, 1, folderB)
- state.splice(b, 1, folderA)
- }
- dataStore.setFolders(state)
- return state
- default:
- return state
- }
-}
-
-function compareArticle (original, modified) {
- var keys = _.keys(_.pick(modified, ['mode', 'title', 'tags', 'content', 'FolderKey']))
-
- return keys.reduce((sum, key) => {
- if ((key === 'tags' && !_.isEqual(original[key], modified[key])) || (key !== 'tags' && original[key] !== modified[key])) {
- if (sum == null) {
- sum = {
- key: original.key
- }
- }
- sum[key] = modified[key]
- }
- return sum
- }, null)
-}
-
-function articles (state = initialArticles, action) {
- switch (action.type) {
- case ARTICLE_CACHE:
- {
- let modified = action.data.article
- let targetKey = action.data.key
- let originalIndex = _.findIndex(state.data, (_article) => targetKey === _article.key)
- if (originalIndex === -1) return state
- let modifiedIndex = _.findIndex(state.modified, (_article) => targetKey === _article.key)
-
- modified = compareArticle(state.data[originalIndex], modified)
- if (modified == null) {
- if (modifiedIndex !== -1) state.modified.splice(modifiedIndex, 1)
- return state
- }
-
- if (modifiedIndex === -1) state.modified.push(modified)
- else Object.assign(state.modified[modifiedIndex], modified)
- return state
- }
- case ARTICLE_UNCACHE:
- {
- let targetKey = action.data.key
- let modifiedIndex = _.findIndex(state.modified, _article => targetKey === _article.key)
- if (modifiedIndex >= 0) state.modified.splice(modifiedIndex, 1)
- return state
- }
- case ARTICLE_UNCACHE_ALL:
- state.modified = []
- return state
- case ARTICLE_SAVE:
- {
- let targetKey = action.data.key
- let override = action.data.article
- let modifiedIndex = _.findIndex(state.modified, _article => targetKey === _article.key)
- let modified = modifiedIndex !== -1 ? state.modified.splice(modifiedIndex, 1)[0] : null
-
- let targetIndex = _.findIndex(state.data, _article => targetKey === _article.key)
- // Make a new if target article is not found.
- if (targetIndex === -1) {
- state.data.push(Object.assign({
- title: '',
- content: '',
- mode: 'markdown',
- tags: [],
- craetedAt: new Date()
- }, modified, override, {key: targetKey, updatedAt: new Date()}))
- return state
- }
-
- Object.assign(state.data[targetIndex], modified, override, {key: targetKey, updatedAt: new Date()})
-
- dataStore.setArticles(state.data)
- return state
- }
- case ARTICLE_SAVE_ALL:
- if (state.modified.length > 0) {
- state.modified.forEach(modifiedArticle => {
- let targetIndex = _.findIndex(state.data, _article => modifiedArticle.key === _article.key)
- Object.assign(state.data[targetIndex], modifiedArticle, {key: modifiedArticle.key, updatedAt: new Date()})
- })
- }
-
- state.modified = []
- dataStore.setArticles(state.data)
-
- return state
- case ARTICLE_UPDATE:
- {
- let article = action.data.article
-
- let targetIndex = _.findIndex(state.data, _article => article.key === _article.key)
- if (targetIndex < 0) state.data.unshift(article)
- else Object.assign(state.data[targetIndex], article)
-
- dataStore.setArticles(state.data)
- return state
- }
- case ARTICLE_DESTROY:
- {
- let articleKey = action.data.key
-
- let targetIndex = _.findIndex(state.data, _article => articleKey === _article.key)
- if (targetIndex >= 0) state.data.splice(targetIndex, 1)
- let modifiedIndex = _.findIndex(state.modified, _article => articleKey === _article.key)
- if (modifiedIndex >= 0) state.modified.splice(modifiedIndex, 1)
-
- dataStore.setArticles(state.data)
- return state
- }
- case FOLDER_DESTROY:
- {
- let folderKey = action.data.key
-
- state.data = state.data.filter(article => article.FolderKey !== folderKey)
-
- dataStore.setArticles(state.data)
- return state
- }
- default:
- return state
- }
-}
-
-function status (state = initialStatus, action) {
- state = Object.assign({}, state)
- switch (action.type) {
- case TOGGLE_TUTORIAL:
- state.isTutorialOpen = !state.isTutorialOpen
- return state
- }
-
- switch (action.type) {
- case ARTICLE_SAVE:
- if (action.data.forceSwitch) {
- let article = action.data.article
- state.articleKey = article.key
- state.search = ''
- }
- return state
- case SWITCH_FOLDER:
- state.search = `\/\/${action.data} `
-
- return state
- case SWITCH_ARTICLE:
- state.articleKey = action.data.key
-
- return state
- case SET_SEARCH_FILTER:
- state.search = action.data
-
- return state
- case SET_TAG_FILTER:
- state.search = `#${action.data}`
-
- return state
- case CLEAR_SEARCH:
- state.search = ''
-
- return state
- default:
- return state
- }
-}
-
-export default combineReducers({
- user,
- folders,
- articles,
- status
-})
diff --git a/browser/main/store.js b/browser/main/store.js
index e910bfaa..b1c2e68b 100644
--- a/browser/main/store.js
+++ b/browser/main/store.js
@@ -1,5 +1,126 @@
-import reducer from './reducer'
-import { createStore } from 'redux'
+import { combineReducers, createStore } from 'redux'
+import { routerReducer } from 'react-router-redux'
+import ConfigManager from 'browser/main/lib/ConfigManager'
+
+function storages (state = [], action) {
+ console.info('REDUX >> ', action)
+ switch (action.type) {
+ case 'INIT_ALL':
+ return action.storages
+ case 'ADD_STORAGE':
+ {
+ let storages = state.slice()
+
+ storages.push(action.storage)
+
+ return storages
+ }
+ case 'ADD_FOLDER':
+ case 'REMOVE_FOLDER':
+ case 'UPDATE_STORAGE':
+ case 'RENAME_STORAGE':
+ {
+ let storages = state.slice()
+ storages = storages
+ .filter((storage) => storage.key !== action.storage.key)
+ storages.push(action.storage)
+
+ return storages
+ }
+ case 'REMOVE_STORAGE':
+ {
+ let storages = state.slice()
+ storages = storages
+ .filter((storage) => storage.key !== action.key)
+
+ return storages
+ }
+ }
+ return state
+}
+
+function notes (state = [], action) {
+ switch (action.type) {
+ case 'INIT_ALL':
+ return action.notes
+ case 'ADD_STORAGE':
+ {
+ let notes = state.concat(action.notes)
+ return notes
+ }
+ case 'REMOVE_STORAGE':
+ {
+ let notes = state.slice()
+ notes = notes
+ .filter((note) => note.storage !== action.key)
+
+ return notes
+ }
+ case 'REMOVE_FOLDER':
+ {
+ let notes = state.slice()
+ notes = notes
+ .filter((note) => note.storage !== action.storage.key || note.folder !== action.key)
+
+ return notes
+ }
+ case 'CREATE_NOTE':
+ {
+ let notes = state.slice()
+ notes.push(action.note)
+ return notes
+ }
+ case 'UPDATE_NOTE':
+ {
+ let notes = state.slice()
+ notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage)
+ notes.push(action.note)
+ return notes
+ }
+ case 'MOVE_NOTE':
+ {
+ let notes = state.slice()
+ notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage)
+ notes.push(action.newNote)
+ return notes
+ }
+ case 'REMOVE_NOTE':
+ {
+ let notes = state.slice()
+ notes = notes.filter((note) => note.key !== action.note.key || note.folder !== action.note.folder || note.storage !== action.note.storage)
+ return notes
+ }
+ }
+ return state
+}
+
+const defaultConfig = ConfigManager.get()
+
+function config (state = defaultConfig, action) {
+ switch (action.type) {
+ case 'SET_IS_SIDENAV_FOLDED':
+ state.isSideNavFolded = action.isFolded
+ return Object.assign({}, state)
+ case 'SET_ZOOM':
+ state.zoom = action.zoom
+ return Object.assign({}, state)
+ case 'SET_LIST_WIDTH':
+ state.listWidth = action.listWidth
+ return Object.assign({}, state)
+ case 'SET_CONFIG':
+ return Object.assign({}, state, action.config)
+ case 'SET_UI':
+ return Object.assign({}, state, action.config)
+ }
+ return state
+}
+
+let reducer = combineReducers({
+ storages,
+ notes,
+ config,
+ routing: routerReducer
+})
let store = createStore(reducer)
diff --git a/browser/styles/index.styl b/browser/styles/index.styl
new file mode 100644
index 00000000..14d50946
--- /dev/null
+++ b/browser/styles/index.styl
@@ -0,0 +1,130 @@
+$brand-color = #6AA5E9
+
+$danger-color = #c9302c
+$danger-lighten-color = lighten(#c9302c, 5%)
+
+// Layouts
+$statusBar-height = 24px
+$sideNav-width = 200px
+$sideNav--folded-width = 44px
+$topBar-height = 50px
+
+// UI default
+$ui-text-color = #515151
+$ui-inactive-text-color = #939395
+$ui-borderColor = #D1D1D1
+$ui-backgroundColor = #FAFAFA
+$ui-border = solid 1px $ui-borderColor
+$ui-active-color = #6AA5E9
+
+// UI Button
+$ui-button-color = #939395
+$ui-button--hover-backgroundColor = rgba(126, 127, 129, 0.08)
+$ui-button--active-color = white
+$ui-button--active-backgroundColor = #6AA5E9
+$ui-button--focus-borderColor = lighten(#369DCD, 25%)
+
+// UI Tooltip
+$ui-tooltip-text-color = white
+$ui-tooltip-backgroundColor = alpha(#444, 70%)
+$ui-tooltip-button-backgroundColor = #D1D1D1
+$ui-tooltip-button--hover-backgroundColor = lighten(#D1D1D1, 30%)
+
+// UI Input
+$ui-input--focus-borderColor = #369DCD
+$ui-input--disabled-backgroundColor = #DDD
+
+/*
+* # Border
+*/
+$border-color = #D0D0D0
+$active-border-color = #369DCD
+$focus-border-color = #369DCD
+
+$default-border = solid 1px $border-color
+$active-border = solid 1px $active-border-color
+
+/**
+* # Button styles
+*/
+
+// Default button
+$default-button-background = white
+$default-button-background--hover = #e6e6e6
+$default-button-background--active = #d4d4d4
+
+colorDefaultButton()
+ background-color $default-button-background
+ &:hover
+ background-color $default-button-background--hover
+ &:active
+ background-color $default-button-background--active
+ &:active:hover
+ background-color $default-button-background--active
+
+// Primary button(Brand color)
+$primary-button-background = $brand-color
+$primary-button-background--hover = darken($brand-color, 5%)
+$primary-button-background--active = darken($brand-color, 10%)
+
+colorPrimaryButton()
+ color white
+ background-color $primary-button-background
+ &:hover
+ background-color $primary-button-background--hover
+ &:active
+ background-color $primary-button-background--active
+ &:active:hover
+ background-color $primary-button-background--activ
+
+// Danger button(Brand color)
+$danger-button-background = #c9302c
+$danger-button-background--hover = darken(#c9302c, 5%)
+$danger-button-background--active = darken(#c9302c, 10%)
+
+colorDangerButton()
+ color white
+ background-color $danger-button-background
+ &:hover
+ background-color $danger-button-background--hover
+ &:active
+ background-color $danger-button-background--active
+ &:active:hover
+ background-color $danger-button-background--active
+
+/**
+* Nav
+*/
+
+navButtonColor()
+ border none
+ color $ui-button-color
+ background-color transparent
+ transition color background-color 0.15s
+ &:hover
+ background-color $ui-button--hover-backgroundColor
+ &:active
+ background-color $ui-button--active-backgroundColor
+ color $ui-button--active-color
+ &:active, &:active:hover
+ background-color $ui-button--active-backgroundColor
+ color $ui-button--active-color
+
+/**
+* # Modal Stuff
+* These will be moved lib/modal
+*/
+
+$modal-z-index = 1002
+$modal-background = white
+$modal-margin = 64px auto 64px
+$modal-border-radius = 5px
+
+modal()
+ position relative
+ z-index $modal-z-index
+ width 100%
+ background-color $modal-background
+ overflow hidden
+ border-radius $modal-border-radius
+ box-shadow 2px 2px 10px gray
diff --git a/browser/styles/main/ArticleDetail.styl b/browser/styles/main/ArticleDetail.styl
deleted file mode 100644
index bcf9edeb..00000000
--- a/browser/styles/main/ArticleDetail.styl
+++ /dev/null
@@ -1,393 +0,0 @@
-noTagsColor = #999
-
-infoButton()
- display inline-block
- border-radius 16.5px
- cursor pointer
- height 33px
- width 33px
- line-height 33px
- margin-right 5px
- font-size 18px
- color inactiveTextColor
- background-color white
- padding 0
- border 1px solid white
- &:focus
- border-color focusBorderColor
- &:hover
- color inherit
-
-.ArticleDetail
- absolute right bottom
- top 60px
- left 450px
- padding 10px
- background-color #E6E6E6
- border-top 1px solid borderColor
- &.empty
- .ArticleDetail-empty-box
- line-height 72px
- font-size 42px
- height 320px
- display flex
- align-items center
- .ArticleDetail-empty-box-message
- text-align center
- width 100%
- color inactiveTextColor
- .ArticleDetail-info
- height 70px
- width 100%
- font-size 12px
- user-select none
- &>.tutorial
- position fixed
- z-index 35
- .ArticleDetail-info-folder
- display inline-block
- max-width 100px
- overflow ellipsis
- height 10px
- width 150px
- height 27px
- outline none
- background-color darken(white, 5%)
- border 1px solid transparent
- &:hover
- background-color white
- &:focus
- border-color focusBorderColor
- &>.tutorial
- position fixed
- z-index 35
- .ArticleDetail-info-status
- padding 0 5px
- .unsaved-mark
- color brandColor
- .ArticleDetail-info-control
- float right
- clearfix
- .ShareButton
- display block
- float left
- &>button, .ShareButton-open-button
- infoButton()
- .tooltip
- tooltip()
- margin-top 30px
- &:hover
- .tooltip
- opacity 1
- &>button
- float left
- &:nth-child(1) .tooltip
- margin-left -65px
- .ArticleDetail-info-control-delete-button
- .tooltip
- right 5px
- .ArticleDetail-info-control-save
- float left
- width 80px
- margin-right 5px
- overflow hidden
- transition width 0.15s ease-in-out
- border-radius 16.5px
- &.hide
- width 0px
- opacity 0.2
- .ArticleDetail-info-control-save-button
- infoButton()
- background-color brandColor
- color white
- font-size 12px
- width 100%
- border 1px solid brandBorderColor
- white-space nowrap
- .fa
- font-size 18px
- &:hover
- color white
- background-color lighten(brandColor, 15%)
- &:focus
- color white
- background-color lighten(brandColor, 15%)
- .tooltip
- tooltip()
- margin-top 30px
- margin-left -90px
- &:hover .tooltip
- opacity 1
-
- .ShareButton-open-button .tooltip
- margin-left -40px
- .ShareButton-dropdown
- position fixed
- width 185px
- z-index 35
- background-color #F0F0F0
- padding 4px 0
- border-radius 5px
- right 5px
- top 95px
- box-shadow 0px 0px 10px 1px alpha(#bbb, 0.8)
- border 1px solid #bcbcbc
- &.hide
- display none
- &>button
- background-color transparent
- height 21px
- width 100%
- border none
- padding-left 20px
- text-align left
- font-size 13px
- font-family '.HelveticaNeueDeskInterface-Regular', sans-serif
- &:hover
- background-color #4297FE
- color white
- .ShareButton-url
- height 40px
- width 100%
- position relative
- padding 0 5px
- .ShareButton-url-input
- height 21px
- border none
- width 143px
- float left
- border-top-left-radius 3px
- border-bottom-left-radius 3px
- border 1px solid borderColor
- border-right none
- .ShareButton-url-button
- height 21px
- border none
- width 30px
- float left
- background-color #F0F0F0
- border-top-right-radius 3px
- border-bottom-right-radius 3px
- border 1px solid borderColor
- .ShareButton-url-button-tooltip
- tooltip()
- right 10px
- margin-top 5px
- &:hover
- .ShareButton-url-button-tooltip
- opacity 1
- &:active
- background-color #4297FE
- color white
- .ShareButton-url-alert
- padding 10px
- line-height 16px
-
-
- .ArticleDetail-info-row2
- .tutorial
- position fixed
- z-index 35
- font-style italic
- .TagSelect
- margin-top 5px
- .TagSelect-tags
- white-space nowrap
- overflow-x auto
- position relative
- noSelect()
- z-index 30
- background-color #E6E6E6
- clearfix()
- .TagSelect-tags-item
- background-color transparent
- color white
- margin 0 2px
- padding 0
- height 17px
- float left
- button.TagSelect-tags-item-remove
- display block
- float left
- background-color transparent
- border none
- font-size 8px
- color white
- width 15px
- height 17px
- text-align center
- line-height 12px
- padding 0
- margin 0
- border-top solid 1px darken(brandColor, 5%)
- border-bottom solid 1px darken(brandColor, 5%)
- border-left solid 1px darken(brandColor, 5%)
- border-right solid 1px transparent
- border-radius left 2px
- background-color brandColor
- &:hover
- background-color lighten(brandColor, 10%)
- border-color lighten(brandColor, 10%)
- &:focus
- background-color lighten(brandColor, 10%)
- border-color focusBorderColor
- .TagSelect-tags-item-label
- background-color brandColor
- float left
- font-size 12px
- border-top solid 1px darken(brandColor, 5%)
- border-bottom solid 1px darken(brandColor, 5%)
- border-right solid 1px darken(brandColor, 5%)
- line-height 15px
- padding 0 5px
- border-radius right 2px
- input.TagSelect-input
- background-color transparent
- border none
- border-bottom 1px solid transparent
- outline none
- margin 0 2px
- transition 0.15s
- height 18px
- &:focus
- border-color focusBorderColor
- .TagSelect-suggest
- position fixed
- width 150px
- max-height 150px
- background-color white
- z-index 50
- border 1px solid borderColor
- border-radius 5px
- overflow-y auto
- &>button
- width 100%
- display block
- padding 0 15px
- height 33px
- line-height 33px
- background-color transparent
- border none
- text-align left
- font-size 14px
- &:hover
- background-color darken(white, 10%)
- .ArticleDetail-panel
- position absolute
- top 70px
- left 10px
- right 10px
- bottom 10px
- overflow-x hidden
- overflow-y auto
- background-color white
- border-radius 5px
- border solid 1px lighten(borderColor, 15%)
- &>.ArticleDetail-panel-header
- display block
- height 60px
- &>.tutorial
- fixed right
- z-index 35
- font-style italic
- .ArticleDetail-panel-header-mode
- z-index 30
- background-color white
- absolute top bottom
- right 10px
- display block
- height 33px
- margin-top 14px
- width 120px
- margin-right 15px
- border solid 1px borderColor
- border-radius 5px
- transition width 0.15s
- user-select none
- &.idle
- cursor pointer
- &:hover
- background-color darken(white, 5%)
- .ModeIcon
- padding 0 5px
- line-height 33px
- &.edit
- border-color focusBorderColor
- input
- width 120px
- line-height 31px
- padding 0 10px
- border none
- outline none
- background-color transparent
- font-size 14px
- .ModeSelect-options
- position fixed
- width 120px
- z-index 10
- border 1px solid borderColor
- border-radius 5px
- background-color white
- max-height 250px
- overflow-y auto
- margin-top 5px
- .ModeSelect-options-item
- height 33px
- line-height 33px
- cursor pointer
- &.active, &:hover.active
- background-color brandColor
- color white
- .ModeIcon
- width 30px
- text-align center
- display inline-block
- &:hover
- background-color darken(white, 10%)
- .ArticleDetail-panel-header-title
- absolute left top
- right 145px
- padding 0 15px
- background-color transparent
- input
- border none
- line-height 60px
- width 100%
- font-size 24px
- outline none
- .ArticleEditor
- absolute left right bottom
- top 60px
- .ArticleDetail-panel-content-tooltip
- absolute bottom right
- height 24px
- background-color alpha(black, 0.5)
- line-height 24px
- color white
- padding 0 15px
- opacity 0
- transition 0.1s
- z-index 35
- &:hover .ArticleDetail-panel-content-tooltip
- opacity 1
- .MarkdownPreview
- absolute top left right bottom
- marked()
- box-sizing border-box
- padding 5px 15px
- border-top solid 1px borderColor
- overflow-y auto
- user-select all
- &.empty
- color lighten(inactiveTextColor, 10%)
- user-select none
- font-size 14px
- &.lineNumbered
- .lineNumber
- display block
- .CodeEditor
- absolute top left right bottom
- border-top solid 1px borderColor
- min-height 300px
- border-bottom-left-radius 5px
- border-bottom-right-radius 5px
diff --git a/browser/styles/main/ArticleList.styl b/browser/styles/main/ArticleList.styl
deleted file mode 100644
index 4e3a63f5..00000000
--- a/browser/styles/main/ArticleList.styl
+++ /dev/null
@@ -1,105 +0,0 @@
-articleItemHoverBgColor = darken(white, 5%)
-articleItemColor = #777
-
-.ArticleList
- absolute bottom
- top 60px
- left 200px
- width 250px
- border-top 1px solid borderColor
- border-right 1px solid borderColor
- &:focus
- border-color focusBorderColor
- overflow-y auto
- noSelect()
- &>div
- .ArticleList-item
- border solid 2px transparent
- position relative
- min-height 110px
- width 100%
- cursor pointer
- transition 0.1s
- background-color white
- padding 0 10px
- font-size 12px
- .ArticleList-item-top
- clearfix()
- padding-top 2px
- line-height 18px
- height 20px
- color articleItemColor
- font-size 11px
- i
- margin-right 4px
- .folderName
- overflow ellipsis
- display inline-block
- width 120px
- .updatedAt
- float right
- line-height 18px
- .unsaved-mark
- color brandColor
- .ArticleList-item-middle
- font-size 16px
- position relative
- padding-top 6px
- height 22px
- .mode
- position absolute
- left 0
- font-size 12px
- line-height 16px
- .title
- position absolute
- left 19px
- right 0
- overflow ellipsis
- small
- color #AAA
- .ArticleList-item-middle2
- padding-top 8px
- pre
- color lighten(inactiveTextColor, 10%)
- white-space pre-wrap
- overflow hidden
- height 33px
- line-height 14px
- font-size 10px
- code
- font-family Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace
- .ArticleList-item-bottom
- padding-bottom 5px
- .tags
- color articleItemColor
- line-height 18px
- word-wrap break-word
- clearfix()
- i.fa-tags
- display inline
- float left
- padding 2px 2px 0 0
- height 14px
- line-height 13px
- a
- background-color brandColor
- float left
- color white
- border-radius 2px
- padding 1px 5px
- margin 2px
- height 14px
- line-height 13px
- font-size 10px
- opacity 0.8
- &:hover
- opacity 1
- &:hover, &.hover
- background-color articleItemHoverBgColor
- &:active, &.active
- background-color white
- &:active, &.active
- border-color brandBorderColor
- .divider
- border-bottom solid 1px borderColor
diff --git a/browser/styles/main/ArticleNavigator.styl b/browser/styles/main/ArticleNavigator.styl
deleted file mode 100644
index 90835f51..00000000
--- a/browser/styles/main/ArticleNavigator.styl
+++ /dev/null
@@ -1,213 +0,0 @@
-articleNavBgColor = #353535
-articleCount = #999
-
-.ArticleNavigator
- background-color articleNavBgColor
- absolute top bottom left
- width 200px
- border-right 1px solid borderColor
- color white
- user-select none
- .userInfo
- height 60px
- display block
- box-sizing content-box
- border-bottom 1px solid borderColor
- .userProfileName
- color brandColor
- font-size 28px
- padding 6px 37px 0 10px
- white-space nowrap
- text-overflow ellipsis
- overflow hidden
- .userName
- color white
- padding-left 20px
- margin-top 3px
- .tutorial
- position fixed
- z-index 35
- top 0
- left 0
- pointer-event none
- font-style italic
- transition 0.1s
- &.hide
- opacity 0
- .settingBtn
- width 22px
- height 22px
- line-height 22px
- border-radius 11px
- position absolute
- top 19px
- right 14px
- color white
- padding 0
- background-color transparent
- border 1px solid white
- z-index 31
- .tooltip
- tooltip()
- margin-top -5px
- margin-left 10px
- &:hover
- .tooltip
- opacity 1
- &:active
- background-color brandColor
- border-color brandColor
- .ArticleNavigator-unsaved
- position absolute
- top 100px
- width 100%
- height 225px
- transition opacity 0.2s ease-in-out
- &.hide
- opacity 0.2
- .ArticleNavigator-unsaved-header
- border-bottom 1px solid alpha(borderColor, 0.5)
- padding-bottom 5px
- clearfix()
- position relative
- padding-left 10px
- font-size 18px
- line-height 22px
- .ArticleNavigator-unsaved-list
- height 165px
- padding 5px 0
- overflow-y scroll
- .ArticleNavigator-unsaved-list-item
- height 33px
- padding-left 15px
- clearfix()
- transition 0.1s
- cursor pointer
- overflow hidden
- &:hover
- background-color alpha(white, 0.05)
- &.active, &:active
- background-color alpha(lighten(brandColor, 25%), 70%)
- .ArticleNavigator-unsaved-list-item-label
- float left
- width 151px
- line-height 33px
- overflow ellipsis
- .ArticleNavigator-unsaved-list-item-label-untitled
- color inactiveTextColor
- .ArticleNavigator-unsaved-list-item-discard-button
- float right
- width 33px
- line-height 30px
- height 33px
- border none
- background-color transparent
- color white
- font-size 18px
- opacity 0.5
- &:hover
- opacity 1
- .ArticleNavigator-unsaved-list-empty
- height 33px
- padding-left 15px
- color alpha(white, 0.4)
- transition 0.1s
- line-height 33px
- &:hover
- color alpha(white, 0.6)
- .ArticleNavigator-unsaved-control
- absolute bottom
- height 33px
- border-top 1px solid alpha(borderColor, 0.5)
- width 100%
- .ArticleNavigator-unsaved-control-save-all-button
- border none
- background-color transparent
- font-size 14px
- color brandColor
- padding-left 15px
- width 100%
- height 33px
- text-align left
- &:hover
- color lighten(brandColor, 15%)
- background-color alpha(white, 0.05)
- &:active
- color white
- &:disabled
- color alpha(brandColor, 0.5)
- &:hover
- color alpha(lighten(brandColor, 25%), 0.5)
- background-color transparent
-
-
- .ArticleNavigator-folders
- absolute bottom
- top 365px
- width 100%
- transition top 0.15s ease-in-out
- background-color articleNavBgColor
- .tutorial
- position fixed
- z-index 35
- font-style italic
- &.expand
- top 100px
- .ArticleNavigator-folders-header
- border-bottom 1px solid alpha(borderColor, 0.5)
- padding-bottom 5px
- clearfix()
- position relative
- z-index 30
- .title
- float left
- padding-left 10px
- font-size 18px
- line-height 22px
- .addBtn
- float right
- margin-right 15px
- width 22px
- height 22px
- font-size 10px
- padding 0
- line-height 22px
- border 1px solid white
- border-radius 11px
- background-color transparent
- color white
- padding 0
- font-weight bold
- .tooltip
- tooltip()
- margin-top -6px
- margin-left 11px
- &:hover
- .tooltip
- opacity 1
- &:active
- background-color brandColor
- border-color brandColor
- .folderList
- absolute bottom
- top 33px
- overflow-y auto
- .folderList button
- height 33px
- width 199px
- border none
- text-align left
- font-size 14px
- background-color transparent
- color white
- padding-left 15px
- overflow ellipsis
- &:hover
- background-color alpha(white, 0.05)
- &.active, &:active
- background-color alpha(lighten(brandColor, 25%), 70%)
- .articleCount
- color white
- .articleCount
- color articleCount
- font-size 12px
diff --git a/browser/styles/main/ArticleTopBar.styl b/browser/styles/main/ArticleTopBar.styl
deleted file mode 100644
index 37857c84..00000000
--- a/browser/styles/main/ArticleTopBar.styl
+++ /dev/null
@@ -1,224 +0,0 @@
-bgColor = #E6E6E6
-inputBgColor = white
-
-topBarBtnColor = #B3B3B3
-topBarBtnBgColor = #B3B3B3
-topBarBtnBgActiveColor = #3A3A3A
-
-infoBtnColor = bgColor
-infoBtnBgColor = #B3B3B3
-infoBtnActiveBgColor = #3A3A3A
-
-.ArticleTopBar
- absolute top right
- left 200px
- height 60px
- background-color bgColor
- user-select none
- &>.tutorial
- .clickJammer
- fixed top left bottom right
- z-index 40
- background transparent
- .global
- fixed bottom right
- height 100px
- z-index 35
- font-style italic
- .finder
- fixed bottom right
- height 250px
- left 50%
- margin-left -250px
- z-index 35
- font-style italic
- .back
- fixed top left bottom right
- z-index 20
- background-color transparentify(black, 80%)
- &>.ArticleTopBar-left
- float left
- &>.tutorial
- fixed top
- left 100px
- top 30px
- z-index 36
- font-style italic
- &>.ArticleTopBar-left-search
- position relative
- float left
- height 33px
- margin-top 13.5px
- margin-left 15px
- width 350px
- padding 5px 15px
- transition 0.1s
- font-size 16px
- border 1px solid transparent
- z-index 30
- .tooltip
- tooltip()
- margin-left -24px
- margin-top 35px
- opacity 1
- &.hide
- opacity 0
- ul
- li
- line-height 18px
- li:last-child
- line-height 10px
- margin-bottom 3px
- small
- font-size 10px
- position relative
- top -2px
- margin-left 15px
- input
- absolute top left
- width 350px
- border-radius 16.5px
- background-color inputBgColor
- border 1px solid transparent
- padding-left 35px
- outline none
- font-size 14px
- height 33px
- line-height 33px
- z-index 0
- &:focus
- border-color focusBorderColor
- i.fa.fa-search
- position absolute
- display block
- top 0
- left 10px
- line-height 33px
- z-index 1
- pointer-events none
- .ArticleTopBar-left-search-clear-button
- position absolute
- top 6px
- right 10px
- width 20px
- height 20px
- border-radius 10px
- border none
- background-color transparent
- color topBarBtnColor
- transition 0.1s
- line-height 20px
- text-align center
- padding 0
- &:focus
- color textColor
- &:hover
- color white
- background-color topBarBtnBgColor
- &:active
- color white
- background-color darken(topBarBtnBgColor, 35%)
- .ArticleTopBar-left-control
- line-height 33px
- float left
- height 33px
- margin-top 13.5px
- margin-left 20px
- .tutorial
- fixed top
- left 200px
- z-index 36
- font-style italic
- button.ArticleTopBar-left-control-new-post-button
- position fixed
- background bgColor
- font-size 20px
- border none
- outline none
- color inactiveTextColor
- width 33px
- height 33px
- border-radius 16.5px
- transition 0.1s
- border 1px solid transparent
- z-index 30
- &:hover
- color textColor
- &:active
- color textColor
- background-color lighten(topBarBtnBgColor, 15%)
- &:disabled
- color inactiveTextColor
- background transparent
- &:focus
- color textColor
- .tooltip
- tooltip()
- margin-left -80px
- margin-top 40px
- &:hover
- .tooltip
- opacity 1
- &>.ArticleTopBar-right
- float right
- &>button
- display block
- position absolute
- right 74px
- top 20px
- width 20px
- height 20px
- font-size 14px
- line-height 14px
- background-color infoBtnBgColor
- color bgColor
- border-radius 11px
- border 1px solid bgColor
- transition 0.1s
- &:focus
- background-color lighten(infoBtnActiveBgColor, 15%)
- .tooltip
- tooltip()
- margin-left -50px
- margin-top 20px
- &:hover
- background-color infoBtnActiveBgColor
- .tooltip
- opacity 1
-
- &>.ArticleTopBar-right-links-button
- display block
- position absolute
- top 8px
- right 15px
- opacity 0.7
- border-radius 23px
- height 46px
- width 46px
- border 1px solid transparent
- &:focus
- border-color focusBorderColor
- &:hover
- opacity 1
- .tooltip
- opacity 1
- &>.ArticleTopBar-right-links-button-dropdown
- position fixed
- z-index 50
- right 10px
- top 40px
- background-color transparentify(invBackgroundColor, 80%)
- padding 5px 0
- .ArticleTopBar-right-links-button-dropdown-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%)
-
-
-
diff --git a/browser/styles/main/UserNavigator.styl b/browser/styles/main/UserNavigator.styl
deleted file mode 100644
index 9c3d9442..00000000
--- a/browser/styles/main/UserNavigator.styl
+++ /dev/null
@@ -1,86 +0,0 @@
-userNavigatorBgColor = #1B1C1C
-userNavigatorColor = #DDD
-userAnchorColor = #979797
-userAnchorBgColor = #BEBEBE
-userAnchorActiveColor = textColor
-userAnchorActiveBgColor = white
-
-.UserNavigator
- noSelect()
- background-color userNavigatorBgColor
- absolute left top bottom
- width 60px
- text-align center
- box-sizing border-box
- ul.userList
- position absolute
- top 25px
- left 0
- right 0
- bottom 70px
- // overflow-y auto
- &>li
- a
- display block
- width 38px
- height 64px
- margin 0 auto 10px
- text-align center
- text-decoration none
- color userAnchorColor
- line-height 44px
- font-size 1.1em
- cursor pointer
- transition 0.1s
-
- img.ProfileImage
- width 38px
- height 38px
- border-radius 22px
- opacity 0.7
- &:hover
- img.ProfileImage
- opacity 1
- .userTooltip
- opacity 1
- &.active
- img.ProfileImage
- opacity 1
- .userTooltip
- tooltip()
- position absolute
- margin-top -52px
- margin-left 44px
- .keyLabel
- margin-top -25px
- font-size 0.8em
- color userNavigatorColor
- button.createTeamBtn
- display block
- margin 0 auto
- width 30px
- height 30px
- border-radius 15px
- border 2px solid darken(white, 5%)
- color darken(white, 5%)
- text-align center
- background-image none
- background-color transparent
- box-sizing border-box
- absolute left right
- bottom 15px
- font-size 22px
- line-height 22px
- transition 0.1s
- .tooltip
- tooltip()
- margin-top -26px
- margin-left 30px
- &:hover, &.hover, &:focus, &.focus
- color white
- border-color white
- .tooltip
- opacity 1
- &:active
- background-color brandColor
- border-color brandColor
diff --git a/browser/styles/main/index.styl b/browser/styles/main/index.styl
index 1f737a84..a27b98ac 100644
--- a/browser/styles/main/index.styl
+++ b/browser/styles/main/index.styl
@@ -1,13 +1,6 @@
-@import '../../../node_modules/nib/lib/nib'
@import '../vars'
@import '../mixins/*'
global-reset()
-@import '../shared/*'
-@import './ArticleNavigator'
-@import './ArticleTopBar'
-@import './ArticleList'
-@import './ArticleDetail'
-@import './modal/*'
@import '../theme/*'
DEFAULT_FONTS = 'Lato', helvetica, arial, sans-serif
@@ -96,63 +89,25 @@ textarea.block-input
#content
fullsize()
-.Main
- .appUpdateButton
- position fixed
- z-index 2000
- bottom 5px
- right 53px
- padding 10px 15px
- border none
- border-radius 5px
- background-color brandColor
- color white
- opacity 0.7
- &:hover
- opacity 1
- background-color lighten(brandColor, 10%)
- .contactButton
- position fixed
- z-index 2000
- bottom 5px
- right 5px
- padding 10px 15px
- border none
- border-radius 5px
- background-color brandColor
- color white
- opacity 0.7
- &:hover
- opacity 1
- background-color lighten(brandColor, 10%)
- .tooltip
- tooltip()
- margin-top -22px
- margin-left -107px
- &:hover .tooltip
- opacity 1
+modalZIndex= 1000
+modalBackColor = transparentify(black, 65%)
-.OSSAnnounceModal
- height 250
- text-align center
- .OSSAnnounceModal-title
- font-size 32px
- padding 45px 0
-
- .OSSAnnounceModal-link
- display block
- font-size 20px
- margin 25px 0 65px
- .OSSAnnounceModal-closeBtn
- display block
- margin 0 auto
- border none
+.ModalBase
+ fixed top left bottom right
+ z-index modalZIndex
+ &.hide
+ display none
+ .modalBack
+ absolute top left bottom right
+ background-color modalBackColor
+ z-index modalZIndex + 1
+ .modal
+ position relative
+ width 650px
+ margin 50px auto 0
+ z-index modalZIndex + 2
+ background-color white
+ padding 15px
+ color #666666
border-radius 5px
- width 150px
- height 33px
- background-color brandColor
- color white
- opacity 0.7
- &:hover
- opacity 1
- background-color lighten(brandColor, 10%)
+
diff --git a/browser/styles/main/modal/CreateNewFolder.styl b/browser/styles/main/modal/CreateNewFolder.styl
deleted file mode 100644
index 1d7870d2..00000000
--- a/browser/styles/main/modal/CreateNewFolder.styl
+++ /dev/null
@@ -1,91 +0,0 @@
-tabNavColor = #999999
-iptFocusBorderColor = #369DCD
-
-.CreateNewFolder.modal
- width 600px
- height 450px
- .closeBtn
- position absolute
- top 15px
- right 15px
- width 33px
- height 33px
- font-size 18px
- line-height 33px
- padding 0
- text-align center
- background-color transparent
- border none
- color stripBtnColor
- &:hover
- color stripHoverBtnColor
- .title
- font-size 32px
- text-align center
- font-weight bold
- margin-top 25px
- .ipt
- display block
- width 330px
- font-size 14px
- height 44px
- line-height 44px
- padding 0 15px
- border-radius 5px
- border solid 1px borderColor
- outline none
- margin 75px auto 20px
- &:focus
- border-color iptFocusBorderColor
- .colorSelect
- text-align center
- .option
- cursor pointer
- font-size 22px
- height 48px
- width 48px
- margin 0 2px
- border 1px solid transparent
- border-radius 5px
- overflow hidden
- line-height 45px
- text-align center
- transition 0.1s
- display inline-block
- &:hover
- border-color borderColor
- font-size 28px
- &.active
- font-size 28px
- border-color iptFocusBorderColor
- .alert
- color infoTextColor
- background-color infoBackgroundColor
- font-size 14px
- padding 15px 15px
- width 330px
- border-radius 5px
- margin 15px auto 0
- &.error
- color errorTextColor
- background-color errorBackgroundColor
- .confirmBtn
- display block
- position absolute
- left 205px
- bottom 44px
- width 240px
- font-size 24px
- height 44px
- line-height 24px
- font-weight bold
- background-color brandColor
- color white
- border none
- border-radius 5px
- margin 0 auto
- transition 0.1s
- &:hover
- transform scale(1.1)
- &:disabled
- opacity 0.7
diff --git a/browser/styles/main/modal/CreateNewTeam.styl b/browser/styles/main/modal/CreateNewTeam.styl
deleted file mode 100644
index 20dc353e..00000000
--- a/browser/styles/main/modal/CreateNewTeam.styl
+++ /dev/null
@@ -1,199 +0,0 @@
-tabNavColor = #999999
-iptFocusBorderColor = #369DCD
-stripHoverBtnColor = #333
-stripBtnColor = lighten(stripHoverBtnColor, 35%)
-
-.CreateNewTeam.modal
- width 600px
- height 450px
- .closeBtn
- position absolute
- top 15px
- right 15px
- width 33px
- height 33px
- font-size 18px
- line-height 33px
- padding 0
- text-align center
- background-color transparent
- border none
- color stripBtnColor
- &:hover
- color stripHoverBtnColor
- .title
- font-size 32px
- text-align center
- font-weight bold
- margin-top 25px
- .ipt
- display block
- width 330px
- font-size 14px
- height 44px
- line-height 44px
- padding 0 15px
- border-radius 5px
- border solid 1px borderColor
- outline none
- &:focus
- border-color iptFocusBorderColor
- .alert
- padding 0 15px
- height 44px
- line-height 44px
- width 300px
- margin 0 auto
- border-radius 5px
- color infoTextColor
- background-color infoBackgroundColor
- white-space nowrap
- overflow-x auto
- &.error
- color errorTextColor
- background-color errorBackgroundColor
- .confirmBtn
- display block
- position absolute
- left 180px
- bottom 44px
- width 240px
- font-size 24px
- height 44px
- line-height 24px
- font-weight bold
- background-color brandColor
- color white
- border none
- border-radius 5px
- margin 0 auto
- transition 0.1s
- &:hover
- transform scale(1.1)
- &:disabled
- opacity 0.7
- .tabNav
- absolute left right
- bottom 15px
- height 33px
- line-height 33px
- width 150px
- text-align center
- font-size 12px
- color tabNavColor
- margin 0 auto
- transition 0.1s
- i.active
- color brandColor
- .createTab
- .ipt
- margin 105px auto 15px
- .selectTab
- .memberForm
- display block
- margin 25px auto 15px
- width 330px
- clearfix()
- padding 0
- font-size 14px
- height 44px
- line-height 44px
- outline none
- .Select.memberName
- display block
- margin 0
- float left
- width 280px
- height 44px
- font-size 14px
- border none
- line-height 44px
- background-color transparent
- outline none
- &.is-focus
- .Select-control
- border-color iptFocusBorderColor
- .Select-control
- height 44px
- line-height 44px
- padding 0 0 0 15px
- border-radius 5px 0 0 5px
- border 1px solid borderColor
- border-right none
- .Select-placeholder
- padding 0 0 0 15px
- .Seleect-arrow
- top 21px
- .Select-clear
- padding 0 10px
- .Select-noresults, .Select-option
- line-height 44px
- padding 0 0 0 15px
-
- &:focus, &.focus
- border-color iptFocusBorderColor
- button
- font-weight 400
- height 44px
- cursor pointer
- margin 0
- padding 0
- width 50px
- float right
- border none
- background-color brandColor
- border-top-right-radius 5px
- border-bottom-right-radius 5px
- color white
- font-size 14px
- .memberList
- width 480px
- margin 0 auto
- height 190px
- overflow scroll
- border-bottom 1px solid borderColor
- &>li
- border-bottom 1px solid borderColor
- height 44px
- padding 0 25px
- clearfix()
- &:nth-last-child(1)
- border-bottom-color transparent
- .userPhoto
- width 30px
- height 30px
- float left
- margin-top 7px
- margin-right 15px
- border-radius 15px
- .userInfo
- float left
- margin-top 7px
- .userName
- font-size 16px
- margin-bottom 2px
- .userEmail
- font-size 12px
- .userControl
- float right
- .userRole
- float left
- height 30px
- background-color transparent
- border 1px solid transparent
- margin-top 7px
- margin-right 35px
- outline none
- cursor pointer
- &:hover
- border-color borderColor
- &:focus
- border-color iptFocusBorderColor
- button
- border none
- height 30px
- margin-top 7px
- background-color transparent
- color stripBtnColor
- &:hover
- color stripHoverBtnColor
diff --git a/browser/styles/main/modal/DeleteArticleModal.styl b/browser/styles/main/modal/DeleteArticleModal.styl
deleted file mode 100644
index 741224a8..00000000
--- a/browser/styles/main/modal/DeleteArticleModal.styl
+++ /dev/null
@@ -1,33 +0,0 @@
-.DeleteArticleModal.modal
- width 350px !important
- top 100px
- user-select none
- .title
- font-size 24px
- margin-bottom 15px
- .message
- font-size 14px
- margin-bottom 15px
- .control
- text-align right
- button
- border-radius 5px
- height 33px
- padding 0 15px
- font-size 14px
- background-color white
- border 1px solid borderColor
- border-radius 5px
- margin-left 5px
- &:hover
- background-color darken(white, 10%)
- &:focus
- border-color focusBorderColor
- &.danger
- border-color #E9432A
- background-color #E9432A
- color white
- &:hover
- background-color lighten(#E9432A, 15%)
- &:focus
- background-color lighten(#E9432A, 15%)
diff --git a/browser/styles/main/modal/Preferences.styl b/browser/styles/main/modal/Preferences.styl
deleted file mode 100644
index 0bb32d97..00000000
--- a/browser/styles/main/modal/Preferences.styl
+++ /dev/null
@@ -1,628 +0,0 @@
-menuColor = #808080
-menuBgColor = #E6E6E6
-closeBtnBgColor = #1790C6
-iptFocusBorderColor = #369DCD
-
-.Preferences.modal
- padding 0
- border-radius 5px
- overflow hidden
- width 720px
- height 600px
- &>.header
- absolute top left right
- height 50px
- border-bottom 1px solid borderColor
- background-color menuBgColor
- &>.title
- font-size 22px
- font-weight bold
- float left
- padding-left 30px
- line-height 50px
- &>.closeBtn
- float right
- font-size 14px
- background-color closeBtnBgColor
- color white
- padding 0 15px
- height 33px
- margin-top 9px
- margin-right 15px
- border none
- border-radius 5px
- &:hover
- background-color lighten(closeBtnBgColor, 10%)
- &>.nav
- absolute left bottom
- top 50px
- width 180px
- background-color menuBgColor
- border-right 1px solid borderColor
- &>button
- width 100%
- height 44px
- font-size 18px
- color menuColor
- border none
- background-color transparent
- transition 0.1s
- text-align left
- padding-left 15px
- &:hover
- background-color darken(menuBgColor, 10%)
- &.active, &:active
- background-color brandColor
- color white
- &>.content
- absolute right bottom
- top 50px
- left 180px
- overflow-y auto
- &>.section
- padding 10px 20px
- border-bottom 1px solid borderColor
- overflow-y auto
- &:nth-last-child(1)
- border-bottom none
- &>.sectionTitle
- font-size 18px
- margin 10px 0 5px
- color brandColor
- &>.sectionCheck
- margin-bottom 5px
- height 33px
- label
- width 150px
- padding-left 15px
- line-height 33px
- .sectionCheck-warn
- font-size 12px
- margin-left 10px
- border-left 2px solid brandColor
- padding-left 5px
- &>.sectionInput
- margin-bottom 5px
- clearfix()
- height 33px
- label
- width 150px
- padding-left 15px
- float left
- line-height 33px
- input
- width 250px
- float left
- height 33px
- border-radius 5px
- border 1px solid borderColor
- padding 0 10px
- font-size 14px
- outline none
- &:focus
- border-color iptFocusBorderColor
- &>.sectionSelect
- margin-bottom 5px
- clearfix()
- height 33px
- label
- width 150px
- padding-left 15px
- float left
- line-height 33px
- select
- float left
- width 200px
- height 25px
- margin-top 4px
- border-radius 5px
- border 1px solid borderColor
- padding 0 10px
- font-size 14px
- outline none
- &:focus
- border-color iptFocusBorderColor
- &>.sectionMultiSelect
- margin-bottom 5px
- clearfix()
- height 33px
- label
- width 150px
- padding-left 15px
- float left
- line-height 33px
- .sectionMultiSelect-input
- float left
- select
- width 80px
- height 25px
- margin-top 4px
- border-radius 5px
- border 1px solid borderColor
- padding 0 10px
- font-size 14px
- outline none
- margin-left 5px
- margin-right 15px
- &:focus
- border-color iptFocusBorderColor
- &>.sectionConfirm
- clearfix()
- padding 5px 15px
- button
- float right
- background-color brandColor
- color white
- border none
- border-radius 5px
- height 33px
- padding 0 15px
- font-size 14px
- &:hover
- background-color lighten(brandColor, 10%)
- .alert
- float right
- width 250px
- padding 10px 15px
- margin 0 10px 0
- .alert
- color infoTextColor
- background-color infoBackgroundColor
- font-size 14px
- padding 15px 15px
- width 330px
- border-radius 5px
- margin 10px auto
- &.error
- color errorTextColor
- background-color errorBackgroundColor
- &.ContactTab
- padding 10px
- .title
- font-size 18px
- color brandColor
- margin-top 10px
- margin-bottom 10px
- p
- line-height 2
- &.AppSettingTab
- .description
- marked()
- &.TeamSettingTab
- .header
- border-bottom 1px solid borderColor
- padding 10px
- font-size 18px
- color brandColor
- line-height 33px
- .teamSelect
- border 1px solid borderColor
- height 33px
- width 200px
- margin 0 10px
- outline none
- font-size 14px
- &:focus
- border-color iptFocusBorderColor
- .teamDeleteConfirm
-
- label
- line-height 33px
- font-size 14px
- .teamDelete
- label
- line-height 33px
- font-size 18px
- color brandColor
- .teamDelete, .teamDeleteConfirm
- padding 15px 20px 15px 15px
- button
- background-color white
- height 33px
- font-size 14px
- padding 0 15px
- border 1px solid borderColor
- float right
- margin 0 5px
- border-radius 5px
- &:hover
- background-color darken(white, 10%)
- button.deleteBtn
- background-color brandColor
- border none
- color white
- &:hover
- background-color lighten(brandColor, 10%)
- &.MemberSettingTab
- &>.header
- border-bottom 1px solid borderColor
- padding 10px
- font-size 18px
- color brandColor
- line-height 33px
- .teamSelect
- border 1px solid borderColor
- height 33px
- width 200px
- margin 0 10px
- outline none
- font-size 14px
- &:focus
- border-color iptFocusBorderColor
- .membersTableSection
- .addMember
- clearfix()
- padding 10px
- .addMemberLabel
- font-size 14px
- line-height 33px
- float left
- .addMemberControl
- width 330px
- float left
- margin-left 25px
- .Select
- display block
- margin 0
- float left
- width 280px
- height 33px
- font-size 14px
- border none
- line-height 33px
- background-color transparent
- outline none
- &.is-focus
- .Select-control
- border-color iptFocusBorderColor
- .Select-control
- height 33px
- line-height 33px
- padding 0 0 0 15px
- border-radius 5px 0 0 5px
- border 1px solid borderColor
- border-right none
- .Select-placeholder
- padding 0 0 0 15px
- .Seleect-arrow
- top 21px
- .Select-clear
- padding 0 10px
- .Select-noresults, .Select-option
- line-height 33px
- padding 0 0 0 15px
- button
- font-weight 400
- height 33px
- cursor pointer
- margin 0
- padding 0
- width 50px
- float right
- border none
- background-color brandColor
- border-top-right-radius 5px
- border-bottom-right-radius 5px
- color white
- font-size 14px
- .memberList
- &>.header
- clearfix()
- &>.userName
- float left
- &>.role
- float left
- &>.control
- float right
- &>li
- &.edit
- .colDescription
- font-size 14px
- line-height 33px
- padding-left 15px
- float left
- strong
- font-size 16px
- color brandColor
- .colDeleteConfirm
- float right
- margin-right 15px
- button
- border none
- height 30px
- width 60px
- margin-top 1.5px
- font-size 14px
- background-color transparent
- color stripBtnColor
- &:hover
- color stripHoverBtnColor
- &:disabled
- color lighten(stripBtnColor, 10%)
- cursor not-allowed
- &.primary
- color brandColor
- &:hover
- color lighten(brandColor, 10%)
-
- border-bottom 1px solid borderColor
- height 44px
- padding 0 25px
- width 420px
- margin 0 auto
- clearfix()
- &:nth-last-child(1)
- border-bottom-color transparent
- .colUserName
- float left
- width 250px
- clearfix()
- .userPhoto
- width 30px
- height 30px
- float left
- margin-top 7px
- margin-right 15px
- border-radius 15px
- .userInfo
- float left
- margin-top 7px
- width 205px
- .userName
- font-size 16px
- margin-bottom 2px
- overflow ellipsis
- .userEmail
- font-size 12px
- overflow ellipsis
- .colRole
- float left
- width 75px
- .userRole
- height 30px
- background-color transparent
- border 1px solid transparent
- margin-top 7px
- margin-right 35px
- outline none
- cursor pointer
- &:hover
- border-color borderColor
- &:focus
- border-color iptFocusBorderColor
- &:disabled
- border-color transparent
- cursor not-allowed
- .colDelete
- width 45px
- float right
- text-align center
- button.deleteButton
- border none
- height 30px
- width 30px
- margin-top 7px
- background-color transparent
- color stripBtnColor
- &:hover
- color stripHoverBtnColor
- &:disabled
- color lighten(stripBtnColor, 10%)
- cursor not-allowed
- &.header
- .colRole, .colDelete
- text-align center
- .colUserName, .colRole, .colDelete
- line-height 44px
- &.FolderSettingTab
- &>.header
- border-bottom 1px solid borderColor
- padding 10px
- font-size 18px
- color brandColor
- line-height 33px
- .teamSelect
- border 1px solid borderColor
- height 33px
- width 200px
- margin 0 10px
- outline none
- font-size 14px
- &:focus
- border-color iptFocusBorderColor
- .section
- .folderTable
- width 420px
- margin 15px auto
- &>div
- border-bottom 1px solid borderColor
- clearfix()
- height 43px
- line-height 33px
- padding 5px 0
- &:last-child
- border-color transparent
- .folderColor
- float left
- margin-left 10px
- text-align center
- width 44px
- .folderName
- float left
- width 175px
- overflow ellipsis
- .folderControl
- float right
- width 125px
- text-align center
- &.folderHeader
- .folderName
- padding-left 25px
- &.newFolder
- .alert
- display block
- color infoTextColor
- background-color infoBackgroundColor
- font-size 14px
- padding 15px 15px
- width 330px
- border-radius 5px
- margin 0 auto
- &.error
- color errorTextColor
- background-color errorBackgroundColor
- .folderName input
- height 33px
- border 1px solid transparent
- border-radius 5px
- padding 0 10px
- font-size 14px
- outline none
- width 150px
- overflow ellipsis
- &:hover
- border-color borderColor
- &:focus
- border-color iptFocusBorderColor
- .folderPublic select
- height 33px
- border 1px solid transparent
- background-color white
- outline none
- display block
- margin 0 auto
- font-size 14px
- &:hover
- border-color borderColor
- &:focus
- border-color iptFocusBorderColor
- .folderControl
- button
- border none
- height 30px
- margin-top 1.5px
- font-size 14px
- background-color transparent
- color brandColor
- &:hover
- color lighten(brandColor, 10%)
- &.FolderRow
- .sortBtns
- float left
- display block
- height 30px
- width 30px
- margin-top 1.5px
- position absolute
- button
- absolute left
- background-color transparent
- border none
- height 15px
- padding 0
- margin 0
- color stripBtnColor
- &:first-child
- top 0
- &:last-child
- top 15px
- &:hover
- color stripHoverBtnColor
- &:disabled
- color lighten(stripBtnColor, 10%)
- cursor not-allowed
- .folderName input
- height 33px
- border 1px solid borderColor
- border-radius 5px
- padding 0 10px
- font-size 14px
- outline none
- width 150px
- &:focus
- border-color iptFocusBorderColor
- .folderColor
- .select
- height 33px
- width 33px
- border 1px solid borderColor
- background-color white
- outline none
- display block
- margin 0 auto
- font-size 14px
- border-radius 5px
- &:focus
- border-color iptFocusBorderColor
- .options
- position absolute
- background-color white
- text-align left
- border 1px solid borderColor
- border-radius 5px
- padding 0 5px 5px
- margin-left 5px
- margin-top -34px
- clearfix()
- .label
- margin-left 5px
- line-height 22px
- font-size 12px
- button
- float left
- border none
- width 33px
- height 33px
- margin-right 5px
- border 1px solid transparent
- line-height 29px
- overflow hidden
- border-radius 5px
- background-color transparent
- outline none
- transition 0.1s
- &:hover
- border-color borderColor
- &.active
- border-color iptFocusBorderColor
- .FolderMark
- transform scale(1.4)
- .folderControl
- button
- border none
- height 30px
- width 30px
- margin-top 1.5px
- font-size 14px
- background-color transparent
- color stripBtnColor
- &:hover
- color stripHoverBtnColor
- &:disabled
- color lighten(stripBtnColor, 10%)
- cursor not-allowed
- &.edit
- .folderControl
- button
- width 60px
- &.primary
- color brandColor
- &:hover
- color lighten(brandColor, 10%)
- &.delete
- .folderDeleteLabel
- float left
- height 33px
- width 250px
- padding-left 15px
- overflow ellipsis
- strong
- font-size 16px
- color brandColor
- .folderControl
- button
- width 60px
- &.primary
- color brandColor
- &:hover
- color lighten(brandColor, 10%)
diff --git a/browser/styles/main/modal/Tutorial.styl b/browser/styles/main/modal/Tutorial.styl
deleted file mode 100644
index f1c48671..00000000
--- a/browser/styles/main/modal/Tutorial.styl
+++ /dev/null
@@ -1,132 +0,0 @@
-
-slideBgColor0 = #2BAC8F
-slideBgColor1 = #F68F92
-slideBgColor2 = #D6AD56
-slideBgColor3 = #26969B
-slideBgColor4 = #00B493
-
-.Tutorial.modal
- background-color slideBgColor0
- color white !important
- width 720px
- height 480px
- margin-top 75px
- border-radius 5px
- overflow hidden
-
- .priorBtn, .nextBtn
- font-size 72px
- position absolute
- background-color transparent
- color transparentify(white, 50%)
- transition 0.1s
- border none
- line-height 72px
- padding 0
- width 93px
- height 72px
- z-index 2
- top 189px
- &:hover
- color white
- &.hide
- opacity 0
- .priorBtn
- left 15px
- .nextBtn
- right 15px
- .title
- text-align center
- font-size 54px
- margin 40px 0
- .content
- text-align center
- font-size 22px
- line-height 1.8
- .dots
- position absolute
- left 0
- right 0
- bottom 25px
- margin 0 auto
- color gray
- text-align center
- z-index 2
- &>i
- transition 0.3s
- &.active
- color white
- .slide
- absolute top bottom left right
- z-index 1
- .slide0
- background-color slideBgColor0
- .content
- margin-top 100px
- .slide1
- background-color slideBgColor1
- .content
- .markdown
- background-color white
- color textColor
- width 480px
- height 140px
- margin 45px auto 0
- clearfix()
- text-align left
- border-radius 5px
- overflow hidden
- .left
- float left
- width 240px
- height 140px
- box-sizing border-box
- font-size 0.5em
- padding 30px
- border-right 1px solid borderColor
- .right
- width 240px
- height 140px
- float right
- box-sizing border-box
- padding: 28px 0 0 10px
- font-size 0.45em
- marked()
- ul
- padding-left 20px
- .slide2
- background-color slideBgColor2
- .code
- border-radius 5px
- overflow hidden
- text-align left
- width 480px
- heght 140px
- margin 45px auto 0
- font-size 14px
- .ace_editor
- height 140px
- .slide3
- background-color slideBgColor3
- .title
- margin-bottom 15px
- .content
- font-size 18px
- &>img
- margin-top 25px
- .slide4
- background-color slideBgColor4
- .content
- &>button
- background-color white
- color brandColor
- font-size 60px
- width 250px
- height 250px
- border-radius 125px
- border none
- transition 0.1s
- &:hover
- transform scale(1.2)
-
-
diff --git a/browser/styles/main/modal/modal.styl b/browser/styles/main/modal/modal.styl
deleted file mode 100644
index 005ac7b3..00000000
--- a/browser/styles/main/modal/modal.styl
+++ /dev/null
@@ -1,21 +0,0 @@
-modalZIndex= 1000
-modalBackColor = transparentify(black, 65%)
-
-.ModalBase
- fixed top left bottom right
- z-index modalZIndex
- &.hide
- display none
- .modalBack
- absolute top left bottom right
- background-color modalBackColor
- z-index modalZIndex + 1
- .modal
- position relative
- width 650px
- margin 50px auto 0
- z-index modalZIndex + 2
- background-color white
- padding 15px
- color #666666
- border-radius 5px
diff --git a/browser/styles/shared/modal.styl b/browser/styles/shared/modal.styl
deleted file mode 100644
index 38377145..00000000
--- a/browser/styles/shared/modal.styl
+++ /dev/null
@@ -1,390 +0,0 @@
-// .ModalBase
-// fixed top left bottom right
-// z-index modalZIndex
-// &.hide
-// display none
-// .modalBack
-// absolute top left bottom right
-// background-color modalBackColor
-// z-index modalZIndex + 1
-// .modal
-// position relative
-// width 650px
-// margin 50px auto 0
-// z-index modalZIndex + 2
-// box-shadow popupShadow
-// background-color white
-// border-radius 10px
-// padding 15px
-// .modal-header
-// border-bottom solid 1px borderColor
-// margin-bottom 10px
-// h1
-// padding 10px 0 15px
-// font-size 1.5em
-// .modal-body
-// p
-// margin-bottom 10px
-// .modal-footer
-// clearfix()
-// border-top solid 1px borderColor
-// padding-top 10px
-// .modal-control
-// float right
-//
-// .sideNavModal
-// height 500px
-// .leftPane
-// absolute top bottom left
-// width 175px
-// padding 20px
-// border-right solid 1px borderColor
-// .modalLabel
-// font-size 1.5em
-// margin-top 25px
-// margin-bottom 35px
-// color brandColor
-// .tabList button
-// btnStripDefault()
-// display block
-// width 100%
-// font-size 1.1em
-// padding 10px 5px
-// margin-bottom 15px
-// text-align left
-// .rightPane
-// absolute top bottom right
-// left 175px
-// padding 15px
-// overflow-y auto
-// .tab
-// padding-top 45px
-// .formField
-// position relative
-// clearfix()
-// margin-bottom 15px
-// label
-// width 30%
-// display block
-// line-height 33px
-// float left
-// input
-// width 70%
-// display block
-// borderInput()
-// height 33px
-// font-size 1em
-// border-radius 5px
-// float left
-// .formRadioField
-// margin-bottom 15px
-// input
-// margin-left 25px
-// .formConfirm
-// position relative
-// clearfix()
-// margin-bottom 15px
-// button
-// float right
-// btnDefault()
-// padding 10px 15px
-// border-radius 5px
-// font-size 1em
-// margin-left 5px
-// .alertInfo, .alertSuccess, .alertError
-// float right
-// padding 12px 10px
-// border-radius 5px
-// width 320px
-// font-size 1em
-// overflow-x hidden
-// white-space nowrap
-// transition 0.1s
-// &.hide
-// width 0
-// padding 12px 0
-// .alertInfo
-// alertInfo()
-// .alertSuccess
-// alertSuccess()
-// .alertError
-// alertError()
-// .PreferencesModal
-// .settingsTab
-// .categoryLabel
-// font-size 1.5em
-// margin-bottom 25px
-// .example
-// marked()
-// .aboutTab
-// padding-top 30px
-// .about1
-// margin-bottom 25px
-// .logo
-// display block
-// margin 0 auto
-// .appInfo
-// font-size 1.5em
-// text-align center
-// .about2
-// width 200px
-// margin 0 auto
-// .externalLabel
-// font-size 1.2em
-// margin-bottom 15px
-// .externalList
-// li
-// margin-bottom 15px
-// .PlanetSettingModal
-// .planetDeleteTab
-// padding-top 65px
-// p
-// margin-bottom 25px
-// strong
-// color brandColor
-// font-size 1.1em
-// input
-// borderInput()
-// margin-right 5px
-// height 33px
-// font-size 1em
-// border-radius 10px
-// .formConfirm
-// position relative
-// clearfix()
-// margin-bottom 15px
-// button
-// float right
-// btnDefault()
-// padding 10px 15px
-// border-radius 5px
-// font-size 1em
-// margin-left 5px
-// .alertInfo, .alertSuccess, .alertError
-// float right
-// padding 12px 10px
-// border-radius 5px
-// width 320px
-// font-size 1em
-// overflow-x hidden
-// white-space nowrap
-// transition 0.1s
-// &.hide
-// width 0
-// padding 12px 0
-// .alertInfo
-// alertInfo()
-// .alertSuccess
-// alertSuccess()
-// .alertError
-// alertError()
-// .TeamSettingsModal
-// .membersTab
-// .memberTable
-// width 100%
-// margin-bottom 25px
-// th
-// border-bottom solid 2px borderColor
-// td
-// border-bottom solid 1px borderColor
-// height 38px
-// button
-// btnDefault()
-// padding 5px
-// border-radius 5px
-// .roleSelect
-// height 33px
-// border solid 1px borderColor
-// background-color backgroundColor
-// th, td
-// padding 5px 0
-// .addMemberForm
-// .formLabel
-// margin-bottom 5px
-// .formGroup
-// clearfix()
-// .userNameSelect
-// display block
-// width 200px
-// margin-right 5px
-// float left
-// .roleSelect
-// display block
-// height 33px
-// border solid 1px borderColor
-// background-color backgroundColor
-// float left
-// margin-right 5px
-// .confirmButton
-// display block
-// height 33px
-// btnDefault()
-// border-radius 5px
-// float left
-//
-// .LaunchModal
-// .modal-tab
-// text-align center
-// margin-bottom 10px
-// .btn-primary, .btn-default
-// margin 0
-// border-radius 0
-// border-width 1px
-// width 150px
-// border-radius 0
-// &:nth-child(1)
-// border-right solid 1px borderColor
-// border-top-left-radius 5px
-// border-bottom-left-radius 5px
-// &:nth-child(2)
-// border-left none
-// border-top-right-radius 5px
-// border-bottom-right-radius 5px
-// .Select
-// .Select-control
-// border-color borderColor
-// &.is-focused
-// .Select-control
-// border-color brandBorderColor
-// .Select-menu-outer
-// border-color borderColor
-// .ace_editor
-// border-radius 5px
-// border solid 1px borderColor
-// .CodeForm, .NoteForm
-// .form-group
-// margin-bottom 10px
-// .CodeForm
-// textarea.codeDescription
-// height 75px
-// font-size 0.9em
-// margin-bottom 10px
-// .modeSelect.Select
-// display inline-block
-// width 200px
-// height 37px
-// .Select-control
-// height 37px
-// .ace_editor
-// height 258px
-// .NoteForm
-// .ace_editor
-// height 358px
-// .previewMode
-// absolute top right
-// font-size 0.8em
-// line-height 24px
-// padding 5 15px
-// background-color transparentify(invBackgroundColor, 0.2)
-// color invTextColor
-// border-top-right-radius 5px
-// .marked
-// height 360px
-// overflow-x hidden
-// overflow-y auto
-// box-sizing border-box
-// padding 5px
-// border solid 1px borderColor
-// border-radius 5px
-// marked()
-//
-//
-// .PlanetCreateModal.modal, .TeamCreateModal.modal, .AddMemberModal.modal
-// padding 60px 0
-// .nameInput
-// width 80%
-// font-size 1.3em
-// margin 25px auto 15px
-// text-align center
-// .userNameSelect
-// width 80%
-// font-size 1.3em
-// margin 35px auto
-// text-align center
-// .formField
-// text-align center
-// margin 0 auto 25px
-// select
-// display inline-block
-// width 150px
-// height 33px
-// border solid 1px borderColor
-// background-color white
-// padding 0 10px
-// margin 0 15px
-// .submitButton
-// display block
-// margin 0 auto
-// box-sizing border-box
-// width 55px
-// height 55px
-// circle()
-// btnPrimary()
-// .errorAlert
-// alertError()
-// padding 12px 10px
-// border-radius 5px
-// text-align center
-// display block
-// width 360px
-// margin 0 auto 15px
-//
-// .ContactModal
-// padding 15px
-// .contactForm
-// .formField
-// width 100%
-// margin-bottom 10px
-// input, textarea
-// display block
-// width 100%
-// borderInput()
-// border-radius 5px
-// input
-// height 33px
-// font-size 1em
-// textarea
-// height 175px
-// font-size 1em
-// .formControl
-// clearfix()
-// button
-// float right
-// btnDefault()
-// height 44px
-// padding 0 15px
-// border-radius 5px
-// margin-left 5px
-// font-size 1em
-// button.sendButton
-// btnPrimary()
-// .confirmation
-// .confirmationMessage
-// padding 35px 0
-// text-align center
-// font-size 1.1em
-// .doneButton
-// btnDefault()
-// height 44px
-// padding 0 35px
-// border-radius 5px
-// display block
-// margin 0 auto 25px
-//
-// .LogoutModal
-// padding 65px 0 45px
-// width 350px
-// .messageLabel
-// text-align center
-// font-size 1.1em
-// margin-bottom 35px
-// .formControl
-// text-align center
-// button
-// btnDefault()
-// border-radius 5px
-// height 44px
-// margin 15px 5px
-// padding 0 15px
-// button.logoutButton
-// btnPrimary()
diff --git a/gruntfile.js b/gruntfile.js
index d2f1a2f7..6b1c6b8b 100644
--- a/gruntfile.js
+++ b/gruntfile.js
@@ -1,6 +1,8 @@
const path = require('path')
const ChildProcess = require('child_process')
const packager = require('electron-packager')
+const fs = require('fs')
+const merge = require('merge-stream')
const WIN = process.platform === 'win32'
diff --git a/index.js b/index.js
index fe56a62e..7a1fdf7b 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,6 @@
function isFinderCalled () {
var argv = process.argv.slice(1)
- return argv.some(arg => arg.match(/--finder/))
+ return argv.some((arg) => arg.match(/--finder/))
}
if (isFinderCalled()) {
diff --git a/lib/config.js b/lib/config.js
deleted file mode 100644
index d4a42d80..00000000
--- a/lib/config.js
+++ /dev/null
@@ -1,82 +0,0 @@
-const electron = require('electron')
-const app = electron.app
-const ipc = electron.ipcMain
-const jetpack = require('fs-jetpack')
-const nodeIpc = require('@rokt33r/node-ipc')
-
-const defaultConfig = {
- 'editor-font-size': '14',
- 'editor-font-family': 'Monaco, Consolas',
- 'editor-indent-type': 'space',
- 'editor-indent-size': '4',
- 'preview-font-size': '14',
- 'preview-font-family': 'Lato',
- 'switch-preview': 'blur',
- 'disable-direct-write': false,
- 'theme-ui': 'light',
- 'theme-code': 'xcode',
- 'theme-syntax': 'xcode',
- 'preview-line-number': false
-}
-const configFile = 'config.json'
-
-var userDataPath = app.getPath('userData')
-
-function getConfig () {
- var userDataPath = app.getPath('userData')
- if (jetpack.cwd(userDataPath).exists(configFile)) {
- try {
- return JSON.parse(jetpack.cwd(userDataPath).read(configFile, 'utf-8'))
- } catch (err) {}
- }
- return null
-}
-
-var config = null
-
-function saveConfig () {
- var content
- try {
- content = JSON.stringify(config)
- } catch (e) {
- config = {}
- content = JSON.stringify(config)
- }
- jetpack.cwd(userDataPath).file(configFile, { content })
-}
-
-// Init
-config = getConfig()
-if (config == null) {
- config = Object.assign({}, defaultConfig)
- saveConfig()
-}
-
-config = Object.assign({}, defaultConfig, config)
-
-if (config['disable-direct-write']) {
- app.commandLine.appendSwitch('disable-direct-write')
-}
-
-function emitToFinder (type, data) {
- var payload = {
- type: type,
- data: data
- }
-
- nodeIpc.server.broadcast('message', payload)
-}
-
-app.on('ready', function () {
- const mainWindow = require('./main-window')
- function applyConfig () {
- mainWindow.webContents.send('config-apply', config)
- emitToFinder('config-apply', config)
- }
-
- ipc.on('configUpdated', function (event, newConfig) {
- config = Object.assign({}, defaultConfig, config, newConfig)
- saveConfig()
- applyConfig()
- })
-})
diff --git a/lib/finder.html b/lib/finder.html
index 6e035b07..8f4d74db 100644
--- a/lib/finder.html
+++ b/lib/finder.html
@@ -29,6 +29,10 @@
+
+
+
+