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

embed hotkey, config to preferences modal & fix datasaving sequence(Async, Queue)

This commit is contained in:
Rokt33r
2016-01-08 14:38:29 +09:00
parent ee280d5c7b
commit 09735b7f47
14 changed files with 461 additions and 194 deletions

View File

@@ -3,10 +3,35 @@ import ReactDOM from 'react-dom'
import modes from '../lib/modes' import modes from '../lib/modes'
import _ from 'lodash' import _ from 'lodash'
const remote = require('electron').remote const electron = require('electron')
const remote = electron.remote
const ipc = electron.ipcRenderer
const ace = window.ace const ace = window.ace
function getConfig () {
return Object.assign({}, remote.getGlobal('config'))
}
let config = getConfig()
export default class CodeEditor extends React.Component { export default class CodeEditor extends React.Component {
constructor (props) {
super(props)
this.configApplyHandler = e => this.handleConfigApply(e)
this.changeHandler = e => this.handleChange(e)
this.state = {
fontSize: config['editor-font-size'],
fontFamily: config['editor-font-family'],
indentType: config['editor-indent-type'],
indentSize: config['editor-indent-size']
}
this.silentChange = false
}
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (nextProps.readOnly !== this.props.readOnly) { if (nextProps.readOnly !== this.props.readOnly) {
this.editor.setReadOnly(!!nextProps.readOnly) this.editor.setReadOnly(!!nextProps.readOnly)
@@ -14,6 +39,7 @@ export default class CodeEditor extends React.Component {
} }
componentDidMount () { componentDidMount () {
let { article } = this.props
var el = ReactDOM.findDOMNode(this) var el = ReactDOM.findDOMNode(this)
var editor = this.editor = ace.edit(el) var editor = this.editor = ace.edit(el)
editor.$blockScrolling = Infinity editor.$blockScrolling = Infinity
@@ -21,6 +47,7 @@ export default class CodeEditor extends React.Component {
editor.setTheme('ace/theme/xcode') editor.setTheme('ace/theme/xcode')
editor.moveCursorTo(0, 0) editor.moveCursorTo(0, 0)
editor.setReadOnly(!!this.props.readOnly) editor.setReadOnly(!!this.props.readOnly)
editor.setFontSize(this.state.fontSize)
editor.commands.addCommand({ editor.commands.addCommand({
name: 'Emacs cursor up', name: 'Emacs cursor up',
@@ -45,36 +72,62 @@ export default class CodeEditor extends React.Component {
}) })
var session = editor.getSession() var session = editor.getSession()
let mode = _.findWhere(modes, {name: this.props.mode}) let mode = _.findWhere(modes, {name: article.mode})
let syntaxMode = mode != null let syntaxMode = mode != null
? mode.mode ? mode.mode
: 'text' : 'text'
session.setMode('ace/mode/' + syntaxMode) session.setMode('ace/mode/' + syntaxMode)
session.setUseSoftTabs(true) session.setUseSoftTabs(this.state.indentType === 'space')
session.setOption('useWorker', false) session.setTabSize(!isNaN(this.state.indentSize) ? parseInt(this.state.indentSize, 10) : 4)
session.setOption('useWorker', true)
session.setUseWrapMode(true) session.setUseWrapMode(true)
session.setValue(this.props.code) session.setValue(this.props.article.content)
session.on('change', e => { session.on('change', this.changeHandler)
if (this.props.onChange != null) {
var value = editor.getValue() ipc.on('config-apply', this.configApplyHandler)
this.props.onChange(e, value)
} }
componentWillUnmount () {
ipc.removeListener('config-apply', this.configApplyHandler)
this.editor.getSession().removeListener('change', this.changeHandler)
}
componentDidUpdate (prevProps, prevState) {
var session = this.editor.getSession()
if (this.props.article.key !== prevProps.article.key) {
session.removeListener('change', this.changeHandler)
session.setValue(this.props.article.content)
session.getUndoManager().reset()
session.on('change', this.changeHandler)
}
if (prevProps.article.mode !== this.props.article.mode) {
let mode = _.findWhere(modes, {name: this.props.article.mode})
let syntaxMode = mode != null
? mode.mode
: 'text'
session.setMode('ace/mode/' + syntaxMode)
}
}
handleConfigApply () {
config = getConfig()
this.setState({
fontSize: config['editor-font-size'],
fontFamily: config['editor-font-family'],
indentType: config['editor-indent-type'],
indentSize: config['editor-indent-size']
}, function () {
var session = this.editor.getSession()
session.setUseSoftTabs(this.state.indentType === 'space')
session.setTabSize(!isNaN(this.state.indentSize) ? parseInt(this.state.indentSize, 10) : 4)
}) })
} }
handleChange (e) {
componentDidUpdate (prevProps) { if (this.props.onChange) {
var session = this.editor.getSession() var value = this.editor.getValue()
if (this.editor.getValue() !== this.props.code) { this.props.onChange(value)
session.setValue(this.props.code)
}
if (prevProps.mode !== this.props.mode) {
let mode = _.findWhere(modes, {name: this.props.mode})
let syntaxMode = mode != null
? mode.mode
: 'text'
session.setMode('ace/mode/' + syntaxMode)
} }
} }
@@ -96,14 +149,23 @@ export default class CodeEditor extends React.Component {
render () { render () {
return ( return (
<div className={this.props.className == null ? 'CodeEditor' : 'CodeEditor ' + this.props.className}></div> <div
className={this.props.className == null ? 'CodeEditor' : 'CodeEditor ' + this.props.className}
style={{
fontSize: this.state.fontSize,
fontFamily: this.state.fontFamily
}}
/>
) )
} }
} }
CodeEditor.propTypes = { CodeEditor.propTypes = {
code: PropTypes.string, article: PropTypes.shape({
content: PropTypes.string,
mode: PropTypes.string, mode: PropTypes.string,
key: PropTypes.string
}),
className: PropTypes.string, className: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
onBlur: PropTypes.func, onBlur: PropTypes.func,

View File

@@ -2,11 +2,12 @@ import React, { PropTypes } from 'react'
import markdown from '../lib/markdown' import markdown from '../lib/markdown'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import sanitizeHtml from '@rokt33r/sanitize-html' import sanitizeHtml from '@rokt33r/sanitize-html'
import hljs from 'highlight.js'
import _ from 'lodash' import _ from 'lodash'
const electron = require('electron') const electron = require('electron')
const shell = electron.shell const shell = electron.shell
const remote = electron.remote
const ipc = electron.ipcRenderer
const katex = window.katex const katex = window.katex
@@ -65,41 +66,43 @@ function math2Katex (display) {
} }
} }
function getConfig () {
return Object.assign({}, remote.getGlobal('config'))
}
let config = getConfig()
export default class MarkdownPreview extends React.Component { export default class MarkdownPreview extends React.Component {
constructor (props) {
super(props)
this.configApplyHandler = e => this.handleConfigApply(e)
this.state = {
fontSize: config['preview-font-size'],
fontFamily: config['preview-font-family']
}
}
componentDidMount () { componentDidMount () {
this.addListener() this.addListener()
// this.renderCode()
this.renderMath() this.renderMath()
ipc.on('config-apply', this.configApplyHandler)
} }
componentDidUpdate () { componentDidUpdate () {
this.addListener() this.addListener()
// this.renderCode()
this.renderMath() this.renderMath()
} }
componentWillUnmount () { componentWillUnmount () {
this.removeListener() this.removeListener()
ipc.removeListener('config-apply', this.configApplyHandler)
} }
componentWillUpdate () { componentWillUpdate () {
this.removeListener() this.removeListener()
} }
renderCode () {
let codes = ReactDOM.findDOMNode(this).querySelectorAll('code:not(.rendered)')
Array.prototype.forEach.call(codes, el => {
let matched = el.className.match(/language-(.+)/)
let lang = matched ? matched[1] : null
try {
let result = hljs.highlight(lang, el.innerHTML)
el.innerHTML = result.value
el.className += ' rendered'
} catch (e) {
}
})
}
renderMath () { renderMath () {
let inline = ReactDOM.findDOMNode(this).querySelectorAll('span.math') let inline = ReactDOM.findDOMNode(this).querySelectorAll('span.math')
Array.prototype.forEach.call(inline, math2Katex(false)) Array.prototype.forEach.call(inline, math2Katex(false))
@@ -157,6 +160,14 @@ export default class MarkdownPreview extends React.Component {
} }
} }
handleConfigApply () {
config = getConfig()
this.setState({
fontSize: config['preview-font-size'],
fontFamily: config['preview-font-family']
})
}
render () { render () {
let isEmpty = this.props.content.trim().length === 0 let isEmpty = this.props.content.trim().length === 0
let content = isEmpty let content = isEmpty
@@ -174,6 +185,7 @@ export default class MarkdownPreview extends React.Component {
onMouseMove={e => this.handleMouseMove(e)} onMouseMove={e => this.handleMouseMove(e)}
onMouseUp={e => this.handleMouseUp(e)} onMouseUp={e => this.handleMouseUp(e)}
dangerouslySetInnerHTML={{__html: ' ' + content}} dangerouslySetInnerHTML={{__html: ' ' + content}}
style={{fontSize: this.state.fontSize, fontFamily: this.state.fontFamily}}
/> />
) )
} }

View File

@@ -24,7 +24,7 @@ export default class FinderDetail extends React.Component {
<div className='content'> <div className='content'>
{activeArticle.mode === 'markdown' {activeArticle.mode === 'markdown'
? <MarkdownPreview content={activeArticle.content}/> ? <MarkdownPreview content={activeArticle.content}/>
: <CodeEditor readOnly mode={activeArticle.mode} code={activeArticle.content}/> : <CodeEditor readOnly article={activeArticle}/>
} }
</div> </div>
</div> </div>

View File

@@ -105,7 +105,7 @@ export function init () {
folders: [defaultFolder], folders: [defaultFolder],
version: '0.4' version: '0.4'
} }
jetpack.write(getLocalPath(), data) queueSave()
} }
} }
@@ -114,18 +114,44 @@ export function getData () {
return data return data
} }
let timer = null
let isSaving = false
let saveAgain = false
function saveData () {
timer = null
isSaving = true
jetpack.writeAsync(getLocalPath(), data)
.then(function () {
isSaving = false
if (saveAgain) {
saveAgain = false
queueSave()
}
})
}
function queueSave () {
if (!isSaving) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(saveData, 3000)
} else {
saveAgain = true
}
}
export function setArticles (articles) { export function setArticles (articles) {
if (!_.isArray(articles)) throw new Error('Articles must be an array') if (!_.isArray(articles)) throw new Error('Articles must be an array')
let data = getData() let data = getData()
data.articles = articles data.articles = articles
jetpack.write(getLocalPath(), data) queueSave()
} }
export function setFolders (folders) { export function setFolders (folders) {
if (!_.isArray(folders)) throw new Error('Folders must be an array') if (!_.isArray(folders)) throw new Error('Folders must be an array')
let data = getData() let data = getData()
data.folders = folders data.folders = folders
jetpack.write(getLocalPath(), data) queueSave()
} }
function isFinderCalled () { function isFinderCalled () {

View File

@@ -17,6 +17,14 @@ export default class ArticleEditor extends React.Component {
} }
} }
componentWillReceiveProps (nextProps) {
if (nextProps.article.key !== this.props.article.key) {
this.setState({
content: this.props.article.content
})
}
}
resetCursorPosition () { resetCursorPosition () {
this.setState({ this.setState({
cursorPosition: null, cursorPosition: null,
@@ -77,13 +85,19 @@ export default class ArticleEditor extends React.Component {
} }
handleBlurCodeEditor () { handleBlurCodeEditor () {
if (this.props.mode === 'markdown') { let { article } = this.props
if (article.mode === 'markdown') {
this.switchPreviewMode() this.switchPreviewMode()
} }
} }
handleCodeEditorChange (value) {
this.props.onChange(value)
}
render () { render () {
let showPreview = this.props.mode === 'markdown' && this.state.status === PREVIEW_MODE let { article } = this.props
let showPreview = article.mode === 'markdown' && this.state.status === PREVIEW_MODE
if (showPreview) { if (showPreview) {
return ( return (
<div className='ArticleEditor'> <div className='ArticleEditor'>
@@ -92,7 +106,7 @@ export default class ArticleEditor extends React.Component {
onMouseUp={e => this.handlePreviewMouseUp(e)} onMouseUp={e => this.handlePreviewMouseUp(e)}
onMouseDown={e => this.handlePreviewMouseDown(e)} onMouseDown={e => this.handlePreviewMouseDown(e)}
onMouseMove={e => this.handlePreviewMouseMove(e)} onMouseMove={e => this.handlePreviewMouseMove(e)}
content={this.props.content} content={article.content}
/> />
<div className='ArticleDetail-panel-content-tooltip'>Click to Edit</div> <div className='ArticleDetail-panel-content-tooltip'>Click to Edit</div>
</div> </div>
@@ -104,11 +118,10 @@ export default class ArticleEditor extends React.Component {
<CodeEditor <CodeEditor
ref='editor' ref='editor'
onBlur={e => this.handleBlurCodeEditor(e)} onBlur={e => this.handleBlurCodeEditor(e)}
onChange={this.props.onChange} onChange={value => this.handleCodeEditorChange(value)}
mode={this.props.mode} article={article}
code={this.props.content}
/> />
{this.props.mode === 'markdown' {article.mode === 'markdown'
? ( ? (
<div className='ArticleDetail-panel-content-tooltip'>Press ESC to watch Preview</div> <div className='ArticleDetail-panel-content-tooltip'>Press ESC to watch Preview</div>
) )
@@ -120,7 +133,10 @@ export default class ArticleEditor extends React.Component {
} }
ArticleEditor.propTypes = { ArticleEditor.propTypes = {
article: PropTypes.shape({
content: PropTypes.string, content: PropTypes.string,
mode: PropTypes.string, key: PropTypes.string,
mode: PropTypes.string
}),
onChange: PropTypes.func onChange: PropTypes.func
} }

View File

@@ -18,9 +18,9 @@ import DeleteArticleModal from '../../modal/DeleteArticleModal'
import ArticleEditor from './ArticleEditor' import ArticleEditor from './ArticleEditor'
const electron = require('electron') const electron = require('electron')
const ipc = electron.ipcRenderer const ipc = electron.ipcRenderer
const remote = electron.remote let count = 0
const { Menu, MenuItem } = remote // const remote = electron.remote
// const { Menu, MenuItem } = remote
// const othersMenu = new Menu() // const othersMenu = new Menu()
// othersMenu.append(new MenuItem({ // othersMenu.append(new MenuItem({
// label: 'Delete Post', // label: 'Delete Post',
@@ -152,18 +152,6 @@ export default class ArticleDetail extends React.Component {
ipc.removeListener('detail-edit', this.editHandler) ipc.removeListener('detail-edit', this.editHandler)
} }
componentWillReceiveProps (nextProps) {
let nextArticle = nextProps.activeArticle
let nextModified = nextArticle != null ? _.findWhere(nextProps.modified, {key: nextArticle.key}) : null
let article = Object.assign({content: ''}, nextProps.activeArticle, nextModified)
let nextState = {
article
}
this.setState(nextState)
}
componentDidUpdate (prevProps, prevState) { componentDidUpdate (prevProps, prevState) {
if (this.props.activeArticle == null || prevProps.activeArticle == null || this.props.activeArticle.key !== prevProps.activeArticle.key) { if (this.props.activeArticle == null || prevProps.activeArticle == null || this.props.activeArticle.key !== prevProps.activeArticle.key) {
this.refs.editor.resetCursorPosition() this.refs.editor.resetCursorPosition()
@@ -175,20 +163,6 @@ export default class ArticleDetail extends React.Component {
ReactDOM.findDOMNode(this.refs.title).select() ReactDOM.findDOMNode(this.refs.title).select()
} }
cacheArticle () {
let { dispatch, status, folders } = this.props
let input = Object.assign({}, this.props.activeArticle.key, this.state.article, {updatedAt: new Date()})
dispatch(updateArticle(input))
let targetFolderKey = this.state.article.FolderKey
if (status.targetFolders.length > 0) {
let targetFolder = _.findWhere(folders, {key: targetFolderKey})
dispatch(switchFolder(targetFolder.name))
}
}
renderEmpty () { renderEmpty () {
return ( return (
<div className='ArticleDetail empty'> <div className='ArticleDetail empty'>
@@ -199,68 +173,64 @@ export default class ArticleDetail extends React.Component {
) )
} }
handleSaveButtonClick (e) {
// let { dispatch, folders, status } = this.props
// let targetFolderKey = this.state.article.FolderKey
// dispatch(saveArticle(this.props.activeArticle.key, this.state.article), true)
// if (status.targetFolders.length > 0) {
// let targetFolder = _.findWhere(folders, {key: targetFolderKey})
// dispatch(switchFolder(targetFolder.name))
// }
}
handleOthersButtonClick (e) { handleOthersButtonClick (e) {
this.deleteHandler() this.deleteHandler()
} }
handleFolderKeyChange (e) { handleFolderKeyChange (e) {
let article = this.state.article let { dispatch, activeArticle, status, folders } = this.props
article.FolderKey = e.target.value let article = Object.assign({}, activeArticle, {
FolderKey: e.target.value,
updatedAt: new Date()
})
this.setState({article: article}, () => this.cacheArticle()) dispatch(updateArticle(article))
let targetFolderKey = this.state.article.FolderKey
if (status.targetFolders.length > 0) {
let targetFolder = _.findWhere(folders, {key: targetFolderKey})
dispatch(switchFolder(targetFolder.name))
}
} }
handleTitleChange (e) { handleTitleChange (e) {
let { article } = this.state let { dispatch, activeArticle } = this.props
article.title = e.target.value let article = Object.assign({}, activeArticle, {
title: e.target.value,
this.setState({ updatedAt: new Date()
article })
}, () => this.cacheArticle()) dispatch(updateArticle(article))
} }
handleTagsChange (newTag, tags) { handleTagsChange (newTag, tags) {
let article = this.state.article let { dispatch, activeArticle } = this.props
article.tags = tags let article = Object.assign({}, activeArticle, {
tags: tags,
updatedAt: new Date()
})
this.setState({ dispatch(updateArticle(article))
article
}, () => this.cacheArticle())
} }
handleModeChange (value) { handleModeChange (value) {
let { article } = this.state let { dispatch, activeArticle } = this.props
article.mode = value let article = Object.assign({}, activeArticle, {
this.setState({ mode: value,
article updatedAt: new Date()
}, () => this.cacheArticle())
}
handleContentChange (e, value) {
let { article } = this.state
article.content = value
this.setState({
article
}, () => this.cacheArticle())
}
handleCodeEditorBlur (e) {
if (this.state.article.mode === 'markdown' && !this.state.previewMode) {
this.setState({
previewMode: true
}) })
dispatch(updateArticle(article))
}
handleContentChange (value) {
let { dispatch, activeArticle } = this.props
if (activeArticle.content !== value) {
let article = Object.assign({}, activeArticle, {
content: value,
updatedAt: new Date()
})
dispatch(updateArticle(article))
} }
} }
@@ -270,13 +240,6 @@ export default class ArticleDetail extends React.Component {
} }
} }
handleUncache (e) {
if (this.props.activeArticle) {
let { dispatch, activeArticle } = this.props
dispatch(uncacheArticle(activeArticle.key))
}
}
handleTitleKeyDown (e) { handleTitleKeyDown (e) {
if (e.keyCode === 9 && !e.shiftKey) { if (e.keyCode === 9 && !e.shiftKey) {
e.preventDefault() e.preventDefault()
@@ -312,7 +275,7 @@ export default class ArticleDetail extends React.Component {
<div className='ArticleDetail-info-row'> <div className='ArticleDetail-info-row'>
<select <select
className='ArticleDetail-info-folder' className='ArticleDetail-info-folder'
value={this.state.article.FolderKey} value={activeArticle.FolderKey}
onChange={e => this.handleFolderKeyChange(e)} onChange={e => this.handleFolderKeyChange(e)}
> >
{folderOptions} {folderOptions}
@@ -321,7 +284,7 @@ export default class ArticleDetail extends React.Component {
children={ children={
isUnsaved isUnsaved
? <span> <span className='unsaved-mark'></span> Unsaved</span> ? <span> <span className='unsaved-mark'></span> Unsaved</span>
: `Created : ${moment(this.state.article.createdAt).format('YYYY/MM/DD')} Updated : ${moment(this.state.article.updatedAt).format('YYYY/MM/DD')}` : `Created : ${moment(activeArticle.createdAt).format('YYYY/MM/DD')} Updated : ${moment(activeArticle.updatedAt).format('YYYY/MM/DD')}`
} }
/> />
@@ -351,7 +314,7 @@ export default class ArticleDetail extends React.Component {
{status.isTutorialOpen ? editDeleteTutorialElement : null} {status.isTutorialOpen ? editDeleteTutorialElement : null}
<div> <div>
<TagSelect <TagSelect
tags={this.state.article.tags} tags={activeArticle.tags}
onChange={(tags, tag) => this.handleTagsChange(tags, tag)} onChange={(tags, tag) => this.handleTagsChange(tags, tag)}
suggestTags={tags} suggestTags={tags}
/> />
@@ -367,7 +330,7 @@ export default class ArticleDetail extends React.Component {
onKeyDown={e => this.handleTitleKeyDown(e)} onKeyDown={e => this.handleTitleKeyDown(e)}
placeholder='(Untitled)' placeholder='(Untitled)'
ref='title' ref='title'
value={this.state.article.title} value={activeArticle.title}
onChange={e => this.handleTitleChange(e)} onChange={e => this.handleTitleChange(e)}
/> />
</div> </div>
@@ -375,16 +338,15 @@ export default class ArticleDetail extends React.Component {
ref='mode' ref='mode'
onChange={e => this.handleModeChange(e)} onChange={e => this.handleModeChange(e)}
onKeyDown={e => this.handleModeSelectKeyDown(e)} onKeyDown={e => this.handleModeSelectKeyDown(e)}
value={this.state.article.mode} value={activeArticle.mode}
className='ArticleDetail-panel-header-mode' className='ArticleDetail-panel-header-mode'
/> />
</div> </div>
{status.isTutorialOpen ? modeSelectTutorialElement : null} {status.isTutorialOpen ? modeSelectTutorialElement : null}
<ArticleEditor <ArticleEditor
ref='editor' ref='editor'
content={this.state.article.content} article={activeArticle}
mode={this.state.article.mode} onChange={content => this.handleContentChange(content)}
onChange={(e, content) => this.handleContentChange(e, content)}
/> />
</div> </div>
</div> </div>

View File

@@ -130,7 +130,7 @@ export default class ArticleList extends React.Component {
let modifiedArticle = _.findWhere(modified, {key: article.key}) let modifiedArticle = _.findWhere(modified, {key: article.key})
let originalArticle = article let originalArticle = article
if (modifiedArticle) { if (modifiedArticle) {
article = Object.assign({}, article, modifiedArticle) article = Object.assign({}, article)
} }
let tagElements = Array.isArray(article.tags) && article.tags.length > 0 let tagElements = Array.isArray(article.tags) && article.tags.length > 0
? article.tags.slice().map(tag => { ? article.tags.slice().map(tag => {

View File

@@ -33,7 +33,7 @@ export default class CreateNewFolder extends React.Component {
name, name,
color color
} }
console.log(input)
try { try {
store.dispatch(createFolder(input)) store.dispatch(createFolder(input))
} catch (e) { } catch (e) {

View File

@@ -6,10 +6,13 @@ const electron = require('electron')
const ipc = electron.ipcRenderer const ipc = electron.ipcRenderer
const remote = electron.remote const remote = electron.remote
const OSX = global.process.platform === 'darwin'
export default class AppSettingTab extends React.Component { export default class AppSettingTab extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
let keymap = remote.getGlobal('keymap') let keymap = Object.assign({}, remote.getGlobal('keymap'))
let config = Object.assign({}, remote.getGlobal('config'))
let userName = props.user != null ? props.user.name : null let userName = props.user != null ? props.user.name : null
this.state = { this.state = {
@@ -18,10 +21,10 @@ export default class AppSettingTab extends React.Component {
alert: null alert: null
}, },
userAlert: null, userAlert: null,
keymap: { keymap: keymap,
toggleFinder: keymap.toggleFinder keymapAlert: null,
}, config: config,
keymapAlert: null configAlert: null
} }
} }
@@ -48,21 +51,33 @@ export default class AppSettingTab extends React.Component {
} }
submitHotKey () { submitHotKey () {
ipc.send('hotkeyUpdated', { ipc.send('hotkeyUpdated', this.state.keymap)
toggleFinder: this.state.keymap.toggleFinder }
})
submitConfig () {
ipc.send('configUpdated', this.state.config)
} }
handleSaveButtonClick (e) { handleSaveButtonClick (e) {
this.submitHotKey() this.submitHotKey()
} }
handleConfigSaveButtonClick (e) {
this.submitConfig()
}
handleKeyDown (e) { handleKeyDown (e) {
if (e.keyCode === 13) { if (e.keyCode === 13) {
this.submitHotKey() this.submitHotKey()
} }
} }
handleConfigKeyDown (e) {
if (e.keyCode === 13) {
this.submitConfig()
}
}
handleNameSaveButtonClick (e) { handleNameSaveButtonClick (e) {
let { dispatch } = this.props let { dispatch } = this.props
@@ -104,8 +119,61 @@ export default class AppSettingTab extends React.Component {
{userAlertElement} {userAlertElement}
</div> </div>
</div> </div>
<div className='section'>
<div className='sectionTitle'>Text</div>
<div className='sectionInput'>
<label>Editor Font Size</label>
<input valueLink={this.linkState('config.editor-font-size')} onKeyDown={e => this.handleConfigKeyDown(e)} type='text'/>
</div>
<div className='sectionInput'>
<label>Editor Font Family</label>
<input valueLink={this.linkState('config.editor-font-family')} onKeyDown={e => this.handleConfigKeyDown(e)} type='text'/>
</div>
<div className='sectionSelect'>
<label>Editor Indent Style</label>
<div className='sectionSelect-input'>
type
<select valueLink={this.linkState('config.editor-indent-type')}>
<option value='space'>Space</option>
<option value='tab'>Tab</option>
</select>
size
<select valueLink={this.linkState('config.editor-indent-size')}>
<option value='2'>2</option>
<option value='4'>4</option>
<option value='8'>8</option>
</select>
</div>
</div>
<div className='sectionInput'>
<label>Preview Font Size</label>
<input valueLink={this.linkState('config.preview-font-size')} onKeyDown={e => this.handleConfigKeyDown(e)} type='text'/>
</div>
<div className='sectionInput'>
<label>Preview Font Family</label>
<input valueLink={this.linkState('config.preview-font-family')} onKeyDown={e => this.handleConfigKeyDown(e)} type='text'/>
</div>
{
true// !OSX
? (
<div className='sectionInput'>
<label>Direct write(Windows only)</label>
<input disabled={OSX} onKeyDown={e => this.handleConfigKeyDown(e)} type='checkbox'/>
</div>
)
: null
}
<div className='sectionConfirm'>
<button onClick={e => this.handleConfigSaveButtonClick(e)}>Save</button>
</div>
</div>
<div className='section'> <div className='section'>
<div className='sectionTitle'>Hotkey</div> <div className='sectionTitle'>Hotkey</div>
<div className='sectionInput'>
<label>Toggle Main</label>
<input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('keymap.toggleMain')} type='text'/>
</div>
<div className='sectionInput'> <div className='sectionInput'>
<label>Toggle Finder(popup)</label> <label>Toggle Finder(popup)</label>
<input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('keymap.toggleFinder')} type='text'/> <input onKeyDown={e => this.handleKeyDown(e)} valueLink={this.linkState('keymap.toggleFinder')} type='text'/>
@@ -142,5 +210,8 @@ export default class AppSettingTab extends React.Component {
AppSettingTab.prototype.linkState = linkState AppSettingTab.prototype.linkState = linkState
AppSettingTab.propTypes = { AppSettingTab.propTypes = {
user: PropTypes.shape({
name: PropTypes.string
}),
dispatch: PropTypes.func dispatch: PropTypes.func
} }

View File

@@ -89,7 +89,7 @@ export default class Tutorial extends React.Component {
<div className='title'>Easy to access with Finder</div> <div className='title'>Easy to access with Finder</div>
<div className='content'> <div className='content'>
The Finder helps you organize all of the files and documents.<br/> The Finder helps you organize all of the files and documents.<br/>
There is a short-cut key [control + shift + tab] to open the Finder.<br/> There is a short-cut key [ + alt + s] to open the Finder.<br/>
It is available to save your articles on the Clipboard<br/> It is available to save your articles on the Clipboard<br/>
by selecting your file with pressing Enter key,<br/> by selecting your file with pressing Enter key,<br/>
and to paste the contents of the Clipboard with [{process.platform === 'darwin' ? 'Command' : 'Control'}-V] and to paste the contents of the Clipboard with [{process.platform === 'darwin' ? 'Command' : 'Control'}-V]

View File

@@ -89,6 +89,31 @@ iptFocusBorderColor = #369DCD
outline none outline none
&:focus &:focus
border-color iptFocusBorderColor border-color iptFocusBorderColor
&>.sectionSelect
margin-bottom 5px
clearfix()
height 33px
label
width 150px
padding-left 15px
float left
line-height 33px
.sectionSelect-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 &>.sectionConfirm
clearfix() clearfix()
padding 5px 15px padding 5px 15px
@@ -624,10 +649,3 @@ iptFocusBorderColor = #369DCD
color brandColor color brandColor
&:hover &:hover
color lighten(brandColor, 10%) color lighten(brandColor, 10%)

53
lib/config.js Normal file
View File

@@ -0,0 +1,53 @@
const electron = require('electron')
const app = electron.app
const ipc = electron.ipcMain
const jetpack = require('fs-jetpack')
const mainWindow = require('./main-window')
const defaultConfig = {
'editor-font-size': '14',
'editor-font-family': 'Monaco, Ubuntu Mono, Consolas, source-code-pro, monospace',
'editor-indent-type': 'space',
'editor-indent-size': '4',
'preview-font-size': '14',
'preview-font-family': 'Lato, helvetica, arial, sans-serif',
'disable-direct-write': 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 {}
}
function saveConfig () {
var content
try {
content = JSON.stringify(global.config)
} catch (e) {
global.config = {}
content = JSON.stringify(global.config)
}
jetpack.cwd(userDataPath).file(configFile, { content })
}
// Init
global.config = Object.assign({}, defaultConfig, getConfig())
function applyConfig () {
mainWindow.webContents.send('config-apply')
}
ipc.on('configUpdated', function (event, newConfig) {
global.config = Object.assign({}, defaultConfig, global.config, newConfig)
saveConfig()
applyConfig()
})

View File

@@ -1,54 +1,40 @@
const electron = require('electron') const electron = require('electron')
const app = electron.app const app = electron.app
const ipc = electron.ipcMain const ipc = electron.ipcMain
const Menu = electron.Menu
const globalShortcut = electron.globalShortcut const globalShortcut = electron.globalShortcut
const jetpack = require('fs-jetpack') const jetpack = require('fs-jetpack')
const mainWindow = require('./main-window') const mainWindow = require('./main-window')
const nodeIpc = require('@rokt33r/node-ipc') const nodeIpc = require('@rokt33r/node-ipc')
const defaultKeymap = {
toggleFinder: 'Cmd + alt + s',
toggleMain: 'Cmd + alt + v'
}
const keymapFilename = 'keymap.json'
var userDataPath = app.getPath('userData') var userDataPath = app.getPath('userData')
if (!jetpack.cwd(userDataPath).exists('keymap.json')) {
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
}
try {
global.keymap = JSON.parse(jetpack.cwd(userDataPath).read('keymap.json', 'utf-8'))
} catch (err) {
jetpack.cwd(userDataPath).file('keymap.json', {content: '{}'})
global.keymap = {}
}
if (global.keymap.toggleFinder == null) global.keymap.toggleFinder = 'ctrl+tab+shift'
var toggleFinderKey = global.keymap.toggleFinder
try { function getKeymap () {
globalShortcut.register(toggleFinderKey, function () { var userDataPath = app.getPath('userData')
emitToFinder('open-finder') if (jetpack.cwd(userDataPath).exists(keymapFilename)) {
mainWindow.webContents.send('open-finder', {})
})
} catch (err) {
console.log(err.name)
}
ipc.on('hotkeyUpdated', function (event, newKeymap) {
console.log('got new keymap')
console.log(newKeymap)
globalShortcut.unregisterAll()
global.keymap = newKeymap
jetpack.cwd(userDataPath).file('keymap.json', {content: JSON.stringify(global.keymap)})
var toggleFinderKey = global.keymap.toggleFinder != null ? global.keymap.toggleFinder : 'ctrl+tab+shift'
try { try {
globalShortcut.register(toggleFinderKey, function () { return JSON.parse(jetpack.cwd(userDataPath).read(keymapFilename, 'utf-8'))
emitToFinder('open-finder') } catch (err) {}
mainWindow.webContents.send('open-finder', {})
})
mainWindow.webContents.send('APP_SETTING_DONE', {})
} catch (err) {
console.error(err)
mainWindow.webContents.send('APP_SETTING_ERROR', {
message: 'Failed to apply hotkey: Invalid format'
})
} }
}) return {}
}
function saveKeymap () {
var content
try {
content = JSON.stringify(global.keymap)
} catch (e) {
global.keymap = {}
content = JSON.stringify(global.keymap)
}
jetpack.cwd(userDataPath).file(keymapFilename, { content })
}
function emitToFinder (type, data) { function emitToFinder (type, data) {
var payload = { var payload = {
@@ -58,3 +44,63 @@ function emitToFinder (type, data) {
nodeIpc.server.broadcast('message', payload) nodeIpc.server.broadcast('message', payload)
} }
function toggleFinder () {
emitToFinder('open-finder')
mainWindow.webContents.send('open-finder', {})
}
function toggleMain () {
if (mainWindow.isFocused()) {
if (process.platform === 'darwin') {
Menu.sendActionToFirstResponder('hide:')
} else {
mainWindow.minimize()
}
} else {
if (process.platform === 'darwin') {
mainWindow.show()
} else {
mainWindow.minimize()
mainWindow.restore()
}
mainWindow.webContents.send('list-focus')
}
}
// Init
global.keymap = Object.assign({}, defaultKeymap, getKeymap())
function registerKey (name, callback, broadcast) {
if (broadcast == null) broadcast = true
try {
globalShortcut.register(global.keymap[name], callback)
if (broadcast) {
mainWindow.webContents.send('APP_SETTING_DONE', {})
}
} catch (err) {
console.log(err)
if (broadcast) {
mainWindow.webContents.send('APP_SETTING_ERROR', {
message: 'Failed to apply hotkey: Invalid format'
})
}
}
}
function registerAllKeys (broadcast) {
if (broadcast == null) broadcast = true
registerKey('toggleFinder', toggleFinder, broadcast)
registerKey('toggleMain', toggleMain, broadcast)
}
registerAllKeys(false)
ipc.on('hotkeyUpdated', function (event, newKeymap) {
global.keymap = Object.assign({}, defaultKeymap, global.keymap, newKeymap)
saveKeymap()
globalShortcut.unregisterAll()
registerAllKeys()
})

View File

@@ -295,6 +295,7 @@ app.on('ready', function () {
}) })
require('./hotkey') require('./hotkey')
require('./config')
}) })
module.exports = app module.exports = app