1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

Going LIte

This commit is contained in:
Rokt33r
2015-10-30 14:53:09 +09:00
parent ba0daf4452
commit d9442aa23c
40 changed files with 978 additions and 853 deletions

View File

@@ -1,42 +0,0 @@
var React = require('react')
var CodeViewer = require('../../main/Components/CodeViewer')
var MarkdownPreview = require('../../main/Components/MarkdownPreview')
module.exports = React.createClass({
propTypes: {
currentArticle: React.PropTypes.object
},
render: function () {
var article = this.props.currentArticle
if (article != null) {
if (article.type === 'code') {
return (
<div className='FinderDetail'>
<div className='header'><i className='fa fa-code fa-fw'/> {article.description}</div>
<div className='content'>
<CodeViewer code={article.content} mode={article.mode}/>
</div>
</div>
)
} else if (article.type === 'note') {
return (
<div className='FinderDetail'>
<div className='header'><i className='fa fa-file-text-o fa-fw'/> {article.title}</div>
<div className='content'>
<MarkdownPreview className='marked' content={article.content}/>
</div>
</div>
)
}
}
return (
<div className='FinderDetail'>
<div className='nothing'>Nothing selected</div>
</div>
)
}
})

View File

@@ -1,15 +0,0 @@
var React = require('react')
module.exports = React.createClass({
propTypes: {
onChange: React.PropTypes.func,
search: React.PropTypes.string
},
render: function () {
return (
<div className='FinderInput'>
<input value={this.props.search} onChange={this.props.onChange} type='text'/>
</div>
)
}
})

View File

@@ -1,79 +0,0 @@
var React = require('react')
module.exports = React.createClass({
propTypes: {
articles: React.PropTypes.arrayOf,
currentArticle: React.PropTypes.shape({
id: React.PropTypes.number,
type: React.PropTypes.string
}),
selectArticle: React.PropTypes.func
},
componentDidUpdate: function () {
var index = this.props.articles.indexOf(this.props.currentArticle)
var el = React.findDOMNode(this)
var li = el.querySelectorAll('li')[index]
if (li == null) {
return
}
var overflowBelow = el.clientHeight + el.scrollTop < li.offsetTop + li.clientHeight
if (overflowBelow) {
el.scrollTop = li.offsetTop + li.clientHeight - el.clientHeight
}
var overflowAbove = el.scrollTop > li.offsetTop
if (overflowAbove) {
el.scrollTop = li.offsetTop
}
},
handleArticleClick: function (article) {
return function () {
this.props.selectArticle(article)
}.bind(this)
},
render: function () {
var list = this.props.articles.map(function (article) {
if (article == null) {
return (
<li className={isActive ? 'active' : ''}>
<div className='articleItem'>Undefined</div>
<div className='divider'/>
</li>
)
}
var isActive = this.props.currentArticle != null && (article.type === this.props.currentArticle.type && article.id === this.props.currentArticle.id)
if (article.type === 'code') {
return (
<li onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
<div className='articleItem'><i className='fa fa-code fa-fw'/> {article.description}</div>
<div className='divider'/>
</li>
)
}
if (article.type === 'note') {
return (
<li onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
<div className='articleItem'><i className='fa fa-file-text-o fa-fw'/> {article.title}</div>
<div className='divider'/>
</li>
)
}
return (
<li className={isActive ? 'active' : ''}>
<div className='articleItem'>Undefined</div>
<div className='divider'/>
</li>
)
}.bind(this))
return (
<div className='FinderList'>
<ul>
{list}
</ul>
</div>
)
}
})

View File

@@ -0,0 +1,34 @@
import React, { PropTypes } from 'react'
import CodeEditor from 'boost/components/CodeEditor'
import MarkdownPreview from 'boost/components/MarkdownPreview'
import ModeIcon from 'boost/components/ModeIcon'
export default class FinderDetail extends React.Component {
render () {
let { activeArticle } = this.props
if (activeArticle != null) {
return (
<div className='FinderDetail'>
<div className='header'>
<ModeIcon mode={activeArticle.mode}/> {activeArticle.title}</div>
<div className='content'>
{activeArticle.mode === 'markdown'
? <MarkdownPreview content={activeArticle.content}/>
: <CodeEditor readOnly mode={activeArticle.mode} code={activeArticle.content}/>
}
</div>
</div>
)
}
return (
<div className='FinderDetail'>
<div className='nothing'>Nothing selected</div>
</div>
)
}
}
FinderDetail.propTypes = {
activeArticle: PropTypes.shape()
}

View File

@@ -0,0 +1,16 @@
import React, { PropTypes } from 'react'
export default class FinderInput extends React.Component {
render () {
return (
<div className='FinderInput'>
<input ref='input' value={this.props.value} onChange={this.props.handleSearchChange} type='text'/>
</div>
)
}
}
FinderInput.propTypes = {
handleSearchChange: PropTypes.func,
value: PropTypes.string
}

View File

@@ -0,0 +1,71 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import ModeIcon from 'boost/components/ModeIcon'
import { selectArticle } from './actions'
export default class FinderList extends React.Component {
componentDidUpdate () {
var index = this.props.articles.indexOf(this.props.activeArticle)
var el = ReactDOM.findDOMNode(this)
var li = el.querySelectorAll('li')[index]
if (li == null) {
return
}
var overflowBelow = el.clientHeight + el.scrollTop < li.offsetTop + li.clientHeight
if (overflowBelow) {
el.scrollTop = li.offsetTop + li.clientHeight - el.clientHeight
}
var overflowAbove = el.scrollTop > li.offsetTop
if (overflowAbove) {
el.scrollTop = li.offsetTop
}
}
handleArticleClick (article) {
return (e) => {
let { dispatch } = this.props
dispatch(selectArticle(article.key))
}
}
render () {
let articleElements = this.props.articles.map(function (article) {
if (article == null) {
return (
<li className={isActive ? 'active' : ''}>
<div className='articleItem'>Undefined</div>
<div className='divider'/>
</li>
)
}
var isActive = this.props.activeArticle != null && (article.key === this.props.activeArticle.key)
return (
<li key={'article-' + article.key} onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
<div className='articleItem'>
<ModeIcon mode={article.mode}/> {article.title}</div>
<div className='divider'/>
</li>
)
}.bind(this))
return (
<div className='FinderList'>
<ul>
{articleElements}
</ul>
</div>
)
}
}
FinderList.propTypes = {
articles: PropTypes.array,
activeArticle: PropTypes.shape({
type: PropTypes.string,
key: PropTypes.string
}),
dispatch: PropTypes.func
}

33
browser/finder/actions.js Normal file
View File

@@ -0,0 +1,33 @@
export const SELECT_ARTICLE = 'SELECT_ARTICLE'
export const SEARCH_ARTICLE = 'SEARCH_ARTICLE'
export const REFRESH_DATA = 'REFRESH_DATA'
export function selectArticle (key) {
return {
type: SELECT_ARTICLE,
data: { key }
}
}
export function searchArticle (input) {
return {
type: SEARCH_ARTICLE,
data: { input }
}
}
export function refreshData () {
console.log('refreshing data')
let data = JSON.parse(localStorage.getItem('local'))
if (data == null) return null
let { folders, articles } = data
return {
type: REFRESH_DATA,
data: {
articles,
folders
}
}
}

View File

@@ -6,8 +6,11 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
<link rel="stylesheet" href="../../node_modules/devicon/devicon.min.css">
<link rel="shortcut icon" href="favicon.ico">
<style>
@font-face {
font-family: 'Lato';
@@ -19,21 +22,23 @@
text-rendering: optimizeLegibility;
}
</style>
<script>
document.addEventListener('mousewheel', function(e) {
if(e.deltaY % 1 !== 0) {
e.preventDefault()
}
})
</script>
</head>
<body>
<div id="content"></div>
<script src="../ace/src-min/ace.js"></script>
<script src="../../submodules/ace/src-min/ace.js"></script>
<script>
require("babel-core/register")
require('./index.jsx')
document.addEventListener('mousewheel', function(e) {
if(e.deltaY % 1 !== 0) {
e.preventDefault()
}
})
var scriptUrl = process.env.BOOST_ENV === 'development'
? 'http://localhost:8080/assets/finder.js'
: '../../compiled/finder.js'
var scriptEl=document.createElement('script')
scriptEl.setAttribute("type","text/javascript")
scriptEl.setAttribute("src", scriptUrl)
document.getElementsByTagName("head")[0].appendChild(scriptEl)
</script>
</body>
</html>

183
browser/finder/index.js Normal file
View File

@@ -0,0 +1,183 @@
import React, { PropTypes } from 'react'
import ReactDOM from 'react-dom'
import { connect, Provider } from 'react-redux'
import reducer from './reducer'
import { createStore } from 'redux'
import FinderInput from './FinderInput'
import FinderList from './FinderList'
import FinderDetail from './FinderDetail'
import { selectArticle, searchArticle, refreshData } from './actions'
import _ from 'lodash'
import remote from 'remote'
var hideFinder = remote.getGlobal('hideFinder')
import clipboard from 'clipboard'
require('../styles/finder/index.styl')
const FOLDER_FILTER = 'FOLDER_FILTER'
const TEXT_FILTER = 'TEXT_FILTER'
const TAG_FILTER = 'TAG_FILTER'
class FinderMain extends React.Component {
constructor (props) {
super(props)
}
componentDidMount () {
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
}
handleClick (e) {
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
}
handleKeyDown (e) {
if (e.keyCode === 38) {
this.selectPrevious()
e.preventDefault()
}
if (e.keyCode === 40) {
this.selectNext()
e.preventDefault()
}
if (e.keyCode === 13) {
let { activeArticle } = this.props
clipboard.writeText(activeArticle.content)
hideFinder()
e.preventDefault()
}
if (e.keyCode === 27) {
hideFinder()
e.preventDefault()
}
}
handleSearchChange (e) {
let { dispatch } = this.props
dispatch(searchArticle(e.target.value))
}
selectArticle (article) {
this.setState({currentArticle: article})
}
selectPrevious () {
let { activeArticle, dispatch } = this.props
let index = this.refs.finderList.props.articles.indexOf(activeArticle)
let previousArticle = this.refs.finderList.props.articles[index - 1]
if (previousArticle != null) dispatch(selectArticle(previousArticle.key))
}
selectNext () {
let { activeArticle, dispatch } = this.props
let index = this.refs.finderList.props.articles.indexOf(activeArticle)
let previousArticle = this.refs.finderList.props.articles[index + 1]
if (previousArticle != null) dispatch(selectArticle(previousArticle.key))
}
render () {
let { articles, activeArticle, status, dispatch } = this.props
return (
<div onClick={e => this.handleClick(e)} onKeyDown={e => this.handleKeyDown(e)} className='Finder'>
<FinderInput
handleSearchChange={e => this.handleSearchChange(e)}
ref='finderInput'
onChange={this.handleChange}
value={status.search}
/>
<FinderList
ref='finderList'
activeArticle={activeArticle}
articles={articles}
dispatch={dispatch}
selectArticle={article => this.selectArticle(article)}
/>
<FinderDetail activeArticle={activeArticle}/>
</div>
)
}
}
FinderMain.propTypes = {
articles: PropTypes.array,
activeArticle: PropTypes.shape({
key: PropTypes.string,
tags: PropTypes.array,
title: PropTypes.string,
content: PropTypes.string
}),
status: PropTypes.shape(),
dispatch: PropTypes.func
}
function remap (state) {
let { articles, folders, status } = state
let filters = status.search.split(' ').map(key => key.trim()).filter(key => key.length > 0 && !key.match(/^#$/)).map(key => {
if (key.match(/^in:.+$/)) {
return {type: FOLDER_FILTER, value: key.match(/^in:(.+)$/)[1]}
}
if (key.match(/^#(.+)/)) {
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
}
return {type: TEXT_FILTER, value: key}
})
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)
if (folders != null) {
let targetFolders = folders.filter(folder => {
return _.findWhere(folderFilters, {value: folder.name})
})
status.targetFolders = targetFolders
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 article.title.match(new RegExp(textFilter.value, 'i')) || article.content.match(new RegExp(textFilter.value, 'i'))
})
}, articles)
}
if (tagFilters.length > 0) {
articles = tagFilters.reduce((articles, tagFilter) => {
return articles.filter(article => {
return _.find(article.tags, tag => tag.match(new RegExp(tagFilter.value, 'i')))
})
}, articles)
}
}
let activeArticle = _.findWhere(articles, {key: status.articleKey})
if (activeArticle == null) activeArticle = articles[0]
return {
articles,
activeArticle,
status
}
}
var Finder = connect(remap)(FinderMain)
var store = createStore(reducer)
window.onfocus = e => {
store.dispatch(refreshData())
}
ReactDOM.render((
<Provider store={store}>
<Finder/>
</Provider>
), document.getElementById('content'))

View File

@@ -1,135 +0,0 @@
/* global localStorage */
var remote = require('remote')
var hideFinder = remote.getGlobal('hideFinder')
var clipboard = require('clipboard')
var React = require('react')
var ArticleFilter = require('../main/Mixins/ArticleFilter')
var FinderInput = require('./Components/FinderInput')
var FinderList = require('./Components/FinderList')
var FinderDetail = require('./Components/FinderDetail')
// Filter end
function fetchArticles () {
var user = JSON.parse(localStorage.getItem('currentUser'))
if (user == null) {
console.log('need to login')
return []
}
var articles = []
user.Planets.forEach(function (planet) {
var _planet = JSON.parse(localStorage.getItem('planet-' + planet.id))
articles = articles.concat(_planet.Codes, _planet.Notes)
})
user.Teams.forEach(function (team) {
team.Planets.forEach(function (planet) {
var _planet = JSON.parse(localStorage.getItem('planet-' + planet.id))
articles = articles.concat(_planet.Codes, _planet.Notes)
})
})
return articles
}
var Finder = React.createClass({
mixins: [ArticleFilter],
getInitialState: function () {
var articles = fetchArticles()
return {
articles: articles,
currentArticle: articles[0],
search: ''
}
},
componentDidMount: function () {
document.addEventListener('keydown', this.handleKeyDown)
document.addEventListener('click', this.handleClick)
window.addEventListener('focus', this.handleFinderFocus)
this.handleFinderFocus()
},
componentWillUnmount: function () {
document.removeEventListener('keydown', this.handleKeyDown)
document.removeEventListener('click', this.handleClick)
window.removeEventListener('focus', this.handleFinderFocus)
},
handleFinderFocus: function () {
console.log('focusseeddddd')
this.focusInput()
var articles = fetchArticles()
this.setState({
articles: articles,
search: ''
}, function () {
var firstArticle = this.refs.finderList.props.articles[0]
if (firstArticle) {
this.setState({
currentArticle: firstArticle
})
}
})
},
handleKeyDown: function (e) {
if (e.keyCode === 38) {
this.selectPrevious()
e.preventDefault()
}
if (e.keyCode === 40) {
this.selectNext()
e.preventDefault()
}
if (e.keyCode === 13) {
var article = this.state.currentArticle
clipboard.writeText(article.content)
hideFinder()
e.preventDefault()
}
if (e.keyCode === 27) {
hideFinder()
e.preventDefault()
}
},
focusInput: function () {
React.findDOMNode(this.refs.finderInput).querySelector('input').focus()
},
handleClick: function () {
this.focusInput()
},
selectPrevious: function () {
var index = this.refs.finderList.props.articles.indexOf(this.state.currentArticle)
if (index > 0) {
this.setState({currentArticle: this.refs.finderList.props.articles[index - 1]})
}
},
selectNext: function () {
var index = this.refs.finderList.props.articles.indexOf(this.state.currentArticle)
if (index > -1 && index < this.refs.finderList.props.articles.length - 1) {
this.setState({currentArticle: this.refs.finderList.props.articles[index + 1]})
}
},
selectArticle: function (article) {
this.setState({currentArticle: article})
},
handleChange: function (e) {
this.setState({search: e.target.value}, function () {
this.setState({currentArticle: this.refs.finderList.props.articles[0]})
})
},
render: function () {
var articles = this.searchArticle(this.state.search, this.state.articles)
return (
<div className='Finder'>
<FinderInput ref='finderInput' onChange={this.handleChange} search={this.state.search}/>
<FinderList ref='finderList' currentArticle={this.state.currentArticle} articles={articles} selectArticle={this.selectArticle}/>
<FinderDetail currentArticle={this.state.currentArticle}/>
</div>
)
}
})
React.render(<Finder/>, document.getElementById('content'))

49
browser/finder/reducer.js Normal file
View File

@@ -0,0 +1,49 @@
import { combineReducers } from 'redux'
import { SELECT_ARTICLE, SEARCH_ARTICLE, REFRESH_DATA } from './actions'
let data = JSON.parse(localStorage.getItem('local'))
let initialArticles = data != null ? data.articles : []
let initialFolders = data != null ? data.folders : []
let initialStatus = {
articleKey: null,
search: ''
}
function status (state = initialStatus, action) {
switch (action.type) {
case SELECT_ARTICLE:
state.articleKey = action.data.key
return state
case SEARCH_ARTICLE:
state.search = action.data.input
return state
default:
return state
}
}
function articles (state = initialArticles, action) {
switch (action.type) {
case REFRESH_DATA:
return action.data.articles
default:
return state
}
}
function folders (state = initialFolders, action) {
switch (action.type) {
case REFRESH_DATA:
console.log(action)
return action.data.folders
default:
return state
}
}
export default combineReducers({
status,
folders,
articles
})