mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 17:56:25 +00:00
Merge pull request #110 from BoostIO/ui-improvement
Ui improvement for v0.6.5
This commit is contained in:
@@ -140,7 +140,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
`
|
`
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
this.refs.root.contentWindow.document.body.innerHTML = markdown(value)
|
this.refs.root.contentWindow.document.body.innerHTML = markdown.render(value)
|
||||||
|
|
||||||
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
el.addEventListener('click', this.anchorClickHandler)
|
||||||
|
|||||||
@@ -129,8 +129,40 @@ md.renderer.render = function render (tokens, options, env) {
|
|||||||
}
|
}
|
||||||
window.md = md
|
window.md = md
|
||||||
|
|
||||||
export default function markdown (content) {
|
function strip (input) {
|
||||||
if (!_.isString(content)) content = ''
|
var output = input
|
||||||
|
try {
|
||||||
return md.render(content)
|
output = output
|
||||||
|
.replace(/^([\s\t]*)([\*\-\+]|\d\.)\s+/gm, '$1')
|
||||||
|
.replace(/\n={2,}/g, '\n')
|
||||||
|
.replace(/~~/g, '')
|
||||||
|
.replace(/`{3}.*\n/g, '')
|
||||||
|
.replace(/<(.*?)>/g, '$1')
|
||||||
|
.replace(/^[=\-]{2,}\s*$/g, '')
|
||||||
|
.replace(/\[\^.+?\](\: .*?$)?/g, '')
|
||||||
|
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
|
||||||
|
.replace(/\!\[.*?\][\[\(].*?[\]\)]/g, '')
|
||||||
|
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
||||||
|
.replace(/>/g, '')
|
||||||
|
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
||||||
|
.replace(/^\#{1,6}\s*([^#]*)\s*(\#{1,6})?/gm, '$1')
|
||||||
|
.replace(/([\*_]{1,3})(\S.*?\S)\1/g, '$2')
|
||||||
|
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
||||||
|
.replace(/^-{3,}\s*$/g, '')
|
||||||
|
.replace(/`(.+?)`/g, '$1')
|
||||||
|
.replace(/\n{2,}/g, '\n\n')
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const markdown = {
|
||||||
|
render: function markdown (content) {
|
||||||
|
if (!_.isString(content)) content = ''
|
||||||
|
return md.render(content)
|
||||||
|
},
|
||||||
|
strip
|
||||||
|
}
|
||||||
|
export default markdown
|
||||||
|
|||||||
@@ -200,6 +200,11 @@ class FolderSelect extends React.Component {
|
|||||||
|
|
||||||
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||||
|
|
||||||
|
if (this.state.search.trim().length > 0) {
|
||||||
|
let filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
||||||
|
options = options.filter((option) => filter.test(option.folder.name))
|
||||||
|
}
|
||||||
|
|
||||||
let optionList = options
|
let optionList = options
|
||||||
.map((option, index) => {
|
.map((option, index) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -57,12 +57,13 @@
|
|||||||
|
|
||||||
.search-optionList
|
.search-optionList
|
||||||
position fixed
|
position fixed
|
||||||
|
max-height 450px
|
||||||
|
overflow auto
|
||||||
z-index 200
|
z-index 200
|
||||||
background-color white
|
background-color white
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
box-shadow 2px 2px 10px gray
|
box-shadow 2px 2px 10px gray
|
||||||
|
|
||||||
|
|
||||||
.search-optionList-item
|
.search-optionList-item
|
||||||
height 34px
|
height 34px
|
||||||
width 250px
|
width 250px
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import FolderSelect from './FolderSelect'
|
|||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import { hashHistory } from 'react-router'
|
import { hashHistory } from 'react-router'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
import markdown from 'browser/lib/markdown'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
@@ -72,6 +73,8 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
title = markdown.strip(title)
|
||||||
|
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -247,25 +247,37 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTabButtonClick (index) {
|
handleTabButtonClick (e, index) {
|
||||||
return (e) => {
|
this.setState({
|
||||||
this.setState({
|
snippetIndex: index
|
||||||
snippetIndex: index
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
|
handleTabDeleteButtonClick (e, index) {
|
||||||
|
if (this.state.note.snippets.length > 1) {
|
||||||
|
if (this.state.note.snippets[index].content.trim().length > 0) {
|
||||||
|
let dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Delete a snippet',
|
||||||
|
detail: 'This work cannot be undone.',
|
||||||
|
buttons: ['Confirm', 'Cancel']
|
||||||
|
})
|
||||||
|
if (dialogIndex === 0) {
|
||||||
|
this.deleteSnippetByIndex(index)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.deleteSnippetByIndex(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTabDeleteButtonClick (index) {
|
deleteSnippetByIndex (index) {
|
||||||
return (e) => {
|
let snippets = this.state.note.snippets.slice()
|
||||||
if (this.state.note.snippets.length > 1) {
|
snippets.splice(index, 1)
|
||||||
let snippets = this.state.note.snippets.slice()
|
this.state.note.snippets = snippets
|
||||||
snippets.splice(index, 1)
|
this.setState({
|
||||||
this.state.note.snippets = snippets
|
note: this.state.note
|
||||||
this.setState({
|
})
|
||||||
note: this.state.note
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNameInputChange (e, index) {
|
handleNameInputChange (e, index) {
|
||||||
@@ -344,7 +356,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
key={index}
|
key={index}
|
||||||
>
|
>
|
||||||
<button styleName='tabList-item-button'
|
<button styleName='tabList-item-button'
|
||||||
onClick={(e) => this.handleTabButtonClick(index)(e)}
|
onClick={(e) => this.handleTabButtonClick(e, index)}
|
||||||
>
|
>
|
||||||
{snippet.name.trim().length > 0
|
{snippet.name.trim().length > 0
|
||||||
? snippet.name
|
? snippet.name
|
||||||
@@ -355,7 +367,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
</button>
|
</button>
|
||||||
{note.snippets.length > 1 &&
|
{note.snippets.length > 1 &&
|
||||||
<button styleName='tabList-item-deleteButton'
|
<button styleName='tabList-item-deleteButton'
|
||||||
onClick={(e) => this.handleTabDeleteButtonClick(index)(e)}
|
onClick={(e) => this.handleTabDeleteButtonClick(e, index)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-times'/>
|
<i className='fa fa-times'/>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ class Main extends React.Component {
|
|||||||
let { config } = props
|
let { config } = props
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isSliderFocused: false,
|
isRightSliderFocused: false,
|
||||||
listWidth: config.listWidth
|
listWidth: config.listWidth,
|
||||||
|
navWidth: config.listWidth,
|
||||||
|
isLeftSliderFocused: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,17 +51,24 @@ class Main extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSlideMouseDown (e) {
|
handleLeftSlideMouseDown (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.setState({
|
this.setState({
|
||||||
isSliderFocused: true
|
isLeftSliderFocused: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRightSlideMouseDown (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
isRightSliderFocused: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp (e) {
|
||||||
if (this.state.isSliderFocused) {
|
if (this.state.isRightSliderFocused) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isSliderFocused: false
|
isRightSliderFocused: false
|
||||||
}, () => {
|
}, () => {
|
||||||
let { dispatch } = this.props
|
let { dispatch } = this.props
|
||||||
let newListWidth = this.state.listWidth
|
let newListWidth = this.state.listWidth
|
||||||
@@ -71,10 +80,24 @@ class Main extends React.Component {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (this.state.isLeftSliderFocused) {
|
||||||
|
this.setState({
|
||||||
|
isLeftSliderFocused: false
|
||||||
|
}, () => {
|
||||||
|
let { dispatch } = this.props
|
||||||
|
let navWidth = this.state.navWidth
|
||||||
|
// TODO: ConfigManager should dispatch itself.
|
||||||
|
ConfigManager.set({listWidth: navWidth})
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_NAV_WIDTH',
|
||||||
|
listWidth: navWidth
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove (e) {
|
handleMouseMove (e) {
|
||||||
if (this.state.isSliderFocused) {
|
if (this.state.isRightSliderFocused) {
|
||||||
let offset = this.refs.body.getBoundingClientRect().left
|
let offset = this.refs.body.getBoundingClientRect().left
|
||||||
let newListWidth = e.pageX - offset
|
let newListWidth = e.pageX - offset
|
||||||
if (newListWidth < 10) {
|
if (newListWidth < 10) {
|
||||||
@@ -86,6 +109,17 @@ class Main extends React.Component {
|
|||||||
listWidth: newListWidth
|
listWidth: newListWidth
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (this.state.isLeftSliderFocused) {
|
||||||
|
let navWidth = e.pageX
|
||||||
|
if (navWidth < 80) {
|
||||||
|
navWidth = 80
|
||||||
|
} else if (navWidth > 600) {
|
||||||
|
navWidth = 600
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
navWidth: navWidth
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@@ -105,9 +139,20 @@ class Main extends React.Component {
|
|||||||
'config',
|
'config',
|
||||||
'location'
|
'location'
|
||||||
])}
|
])}
|
||||||
|
width={this.state.navWidth}
|
||||||
/>
|
/>
|
||||||
|
{!config.isSideNavFolded &&
|
||||||
|
<div styleName={this.state.isLeftSliderFocused ? 'slider--active' : 'slider'}
|
||||||
|
style={{left: this.state.navWidth - 1}}
|
||||||
|
onMouseDown={(e) => this.handleLeftSlideMouseDown(e)}
|
||||||
|
draggable='false'
|
||||||
|
>
|
||||||
|
<div styleName='slider-hitbox'/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
||||||
ref='body'
|
ref='body'
|
||||||
|
style={{left: config.isSideNavFolded ? 44 : this.state.navWidth}}
|
||||||
>
|
>
|
||||||
<TopBar style={{width: this.state.listWidth}}
|
<TopBar style={{width: this.state.listWidth}}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
@@ -127,9 +172,9 @@ class Main extends React.Component {
|
|||||||
'location'
|
'location'
|
||||||
])}
|
])}
|
||||||
/>
|
/>
|
||||||
<div styleName={this.state.isSliderFocused ? 'slider--active' : 'slider'}
|
<div styleName={this.state.isRightSliderFocused ? 'slider--active' : 'slider'}
|
||||||
style={{left: this.state.listWidth}}
|
style={{left: this.state.listWidth}}
|
||||||
onMouseDown={(e) => this.handleSlideMouseDown(e)}
|
onMouseDown={(e) => this.handleRightSlideMouseDown(e)}
|
||||||
draggable='false'
|
draggable='false'
|
||||||
>
|
>
|
||||||
<div styleName='slider-hitbox'/>
|
<div styleName='slider-hitbox'/>
|
||||||
@@ -143,7 +188,7 @@ class Main extends React.Component {
|
|||||||
'params',
|
'params',
|
||||||
'location'
|
'location'
|
||||||
])}
|
])}
|
||||||
ignorePreviewPointerEvents={this.state.isSliderFocused}
|
ignorePreviewPointerEvents={this.state.isRightSliderFocused}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import styles from './NoteList.styl'
|
|||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
|
||||||
|
const { remote } = require('electron')
|
||||||
|
const { Menu, MenuItem, dialog } = remote
|
||||||
|
|
||||||
class NoteList extends React.Component {
|
class NoteList extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -19,6 +23,10 @@ class NoteList extends React.Component {
|
|||||||
this.focusHandler = () => {
|
this.focusHandler = () => {
|
||||||
this.refs.root.focus()
|
this.refs.root.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
range: 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@@ -28,6 +36,29 @@ class NoteList extends React.Component {
|
|||||||
ee.on('lost:focus', this.focusHandler)
|
ee.on('lost:focus', this.focusHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (nextProps.location.pathname !== this.props.location.pathname) {
|
||||||
|
this.resetScroll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetScroll () {
|
||||||
|
this.refs.root.scrollTop = 0
|
||||||
|
this.setState({
|
||||||
|
range: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll (e) {
|
||||||
|
let notes = this.notes
|
||||||
|
|
||||||
|
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 250 && notes.length > this.state.range * 10 + 10) {
|
||||||
|
this.setState({
|
||||||
|
range: this.state.range + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
clearInterval(this.refreshTimer)
|
clearInterval(this.refreshTimer)
|
||||||
|
|
||||||
@@ -36,7 +67,7 @@ class NoteList extends React.Component {
|
|||||||
ee.off('lost:focus', this.focusHandler)
|
ee.off('lost:focus', this.focusHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate (prevProps) {
|
||||||
let { location } = this.props
|
let { location } = this.props
|
||||||
if (this.notes.length > 0 && location.query.key == null) {
|
if (this.notes.length > 0 && location.query.key == null) {
|
||||||
let { router } = this.context
|
let { router } = this.context
|
||||||
@@ -50,13 +81,14 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Auto scroll
|
// Auto scroll
|
||||||
if (_.isString(location.query.key)) {
|
if (_.isString(location.query.key) && prevProps.location.query.key !== location.query.key) {
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||||
return note != null && note.storage + '-' + note.key === location.query.key
|
return note != null && note.storage + '-' + note.key === location.query.key
|
||||||
})
|
})
|
||||||
if (targetIndex > -1) {
|
if (targetIndex > -1) {
|
||||||
let list = this.refs.root
|
let list = this.refs.root
|
||||||
let item = list.childNodes[targetIndex]
|
let item = list.childNodes[targetIndex]
|
||||||
|
if (item == null) return false
|
||||||
|
|
||||||
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
||||||
if (overflowBelow) {
|
if (overflowBelow) {
|
||||||
@@ -185,17 +217,58 @@ class NoteList extends React.Component {
|
|||||||
: []
|
: []
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNoteClick (uniqueKey) {
|
handleNoteClick (e, uniqueKey) {
|
||||||
return (e) => {
|
let { router } = this.context
|
||||||
let { router } = this.context
|
let { location } = this.props
|
||||||
let { location } = this.props
|
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: uniqueKey
|
key: uniqueKey
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNoteContextMenu (e, uniqueKey) {
|
||||||
|
let menu = new Menu()
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: 'Delete Note',
|
||||||
|
click: (e) => this.handleDeleteNote(e, uniqueKey)
|
||||||
|
}))
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteNote (e, uniqueKey) {
|
||||||
|
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Delete a note',
|
||||||
|
detail: 'This work cannot be undone.',
|
||||||
|
buttons: ['Confirm', 'Cancel']
|
||||||
|
})
|
||||||
|
if (index === 0) {
|
||||||
|
let { dispatch, location } = this.props
|
||||||
|
let splitted = uniqueKey.split('-')
|
||||||
|
let storageKey = splitted.shift()
|
||||||
|
let noteKey = splitted.shift()
|
||||||
|
|
||||||
|
dataApi
|
||||||
|
.deleteNote(storageKey, noteKey)
|
||||||
|
.then((data) => {
|
||||||
|
let dispatchHandler = () => {
|
||||||
|
dispatch({
|
||||||
|
type: 'DELETE_NOTE',
|
||||||
|
storageKey: data.storageKey,
|
||||||
|
noteKey: data.noteKey
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location.query.key === uniqueKey) {
|
||||||
|
ee.once('list:moved', dispatchHandler)
|
||||||
|
ee.emit('list:next')
|
||||||
|
} else {
|
||||||
|
dispatchHandler()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +277,7 @@ class NoteList extends React.Component {
|
|||||||
this.notes = notes = this.getNotes()
|
this.notes = notes = this.getNotes()
|
||||||
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
||||||
|
|
||||||
let noteList = notes
|
let noteList = notes.slice(0, 10 + 10 * this.state.range)
|
||||||
.map((note) => {
|
.map((note) => {
|
||||||
if (note == null) return null
|
if (note == null) return null
|
||||||
let storage = data.storageMap.get(note.storage)
|
let storage = data.storageMap.get(note.storage)
|
||||||
@@ -226,7 +299,8 @@ class NoteList extends React.Component {
|
|||||||
: 'item'
|
: 'item'
|
||||||
}
|
}
|
||||||
key={note.storage + '-' + note.key}
|
key={note.storage + '-' + note.key}
|
||||||
onClick={(e) => this.handleNoteClick(note.storage + '-' + note.key)(e)}
|
onClick={(e) => this.handleNoteClick(e, note.storage + '-' + note.key)}
|
||||||
|
onContextMenu={(e) => this.handleNoteContextMenu(e, note.storage + '-' + note.key)}
|
||||||
>
|
>
|
||||||
<div styleName='item-border'/>
|
<div styleName='item-border'/>
|
||||||
<div styleName='item-info'>
|
<div styleName='item-info'>
|
||||||
@@ -277,6 +351,7 @@ class NoteList extends React.Component {
|
|||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
onKeyDown={(e) => this.handleNoteListKeyDown(e)}
|
onKeyDown={(e) => this.handleNoteListKeyDown(e)}
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
|
onScroll={(e) => this.handleScroll(e)}
|
||||||
>
|
>
|
||||||
{noteList}
|
{noteList}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
.top-menu-label
|
.top-menu-label
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
|
overflow ellipsis
|
||||||
|
|
||||||
.menu
|
.menu
|
||||||
margin-top 15px
|
margin-top 15px
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
font-size 14px
|
font-size 14px
|
||||||
width 100%
|
width 100%
|
||||||
text-align left
|
text-align left
|
||||||
|
overflow ellipsis
|
||||||
|
|
||||||
.menu-button--active
|
.menu-button--active
|
||||||
@extend .menu-button
|
@extend .menu-button
|
||||||
|
|||||||
@@ -65,6 +65,7 @@
|
|||||||
margin 2px 0
|
margin 2px 0
|
||||||
text-align left
|
text-align left
|
||||||
border none
|
border none
|
||||||
|
overflow ellipsis
|
||||||
font-size 14px
|
font-size 14px
|
||||||
&:first-child
|
&:first-child
|
||||||
margin-top 0
|
margin-top 0
|
||||||
|
|||||||
@@ -50,11 +50,13 @@ class SideNav extends React.Component {
|
|||||||
isFolded={isFolded}
|
isFolded={isFolded}
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
|
let style = {}
|
||||||
|
if (!isFolded) style.width = this.props.width
|
||||||
return (
|
return (
|
||||||
<div className='SideNav'
|
<div className='SideNav'
|
||||||
styleName={isFolded ? 'root--folded' : 'root'}
|
styleName={isFolded ? 'root--folded' : 'root'}
|
||||||
tabIndex='1'
|
tabIndex='1'
|
||||||
|
style={style}
|
||||||
>
|
>
|
||||||
<div styleName='top'>
|
<div styleName='top'>
|
||||||
<button styleName='top-menu'
|
<button styleName='top-menu'
|
||||||
|
|||||||
@@ -15,10 +15,13 @@ $control-height = 34px
|
|||||||
border $ui-border
|
border $ui-border
|
||||||
border-radius 20px
|
border-radius 20px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
display flex
|
||||||
|
|
||||||
.control-search
|
.control-search
|
||||||
absolute top left bottom
|
height 32px
|
||||||
right 40px
|
flex 1
|
||||||
background-color white
|
background-color white
|
||||||
|
position relative
|
||||||
|
|
||||||
.control-search-icon
|
.control-search-icon
|
||||||
absolute top bottom left
|
absolute top bottom left
|
||||||
@@ -76,10 +79,24 @@ $control-height = 34px
|
|||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
line-height 150px
|
line-height 150px
|
||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
|
.control-contextButton
|
||||||
|
display block
|
||||||
|
width 20px
|
||||||
|
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 .control-newPostButton-tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
.control-newPostButton
|
.control-newPostButton
|
||||||
display block
|
display block
|
||||||
absolute top right bottom
|
width 36px
|
||||||
width 40px
|
|
||||||
height $control-height - 2
|
height $control-height - 2
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
border-left $ui-border
|
border-left $ui-border
|
||||||
@@ -143,10 +160,9 @@ body[data-theme="dark"]
|
|||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
padding-right 3px
|
padding-right 3px
|
||||||
.control-search-optionList-empty
|
.control-search-optionList-empty
|
||||||
height 150px
|
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
line-height 150px
|
|
||||||
text-align center
|
.control-contextButton,
|
||||||
.control-newPostButton
|
.control-newPostButton
|
||||||
colorDarkDefaultButton()
|
colorDarkDefaultButton()
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
@@ -155,12 +171,3 @@ body[data-theme="dark"]
|
|||||||
|
|
||||||
.control-newPostButton-tooltip
|
.control-newPostButton-tooltip
|
||||||
darkTooltip()
|
darkTooltip()
|
||||||
position fixed
|
|
||||||
pointer-events none
|
|
||||||
top 45px
|
|
||||||
left 385px
|
|
||||||
z-index 10
|
|
||||||
padding 5px
|
|
||||||
line-height normal
|
|
||||||
opacity 0
|
|
||||||
transition 0.1s
|
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ import modal from 'browser/main/lib/modal'
|
|||||||
import NewNoteModal from 'browser/main/modals/NewNoteModal'
|
import NewNoteModal from 'browser/main/modals/NewNoteModal'
|
||||||
import { hashHistory } from 'react-router'
|
import { hashHistory } from 'react-router'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
|
||||||
const OSX = window.process.platform === 'darwin'
|
const OSX = window.process.platform === 'darwin'
|
||||||
|
const { remote } = require('electron')
|
||||||
|
const { Menu, MenuItem } = remote
|
||||||
|
|
||||||
class TopBar extends React.Component {
|
class TopBar extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -33,7 +37,30 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleNewPostButtonClick (e) {
|
handleNewPostButtonClick (e) {
|
||||||
let { data, params, dispatch, location } = this.props
|
let { config } = this.props
|
||||||
|
|
||||||
|
switch (config.ui.defaultNote) {
|
||||||
|
case 'MARKDOWN_NOTE':
|
||||||
|
this.createNote('MARKDOWN_NOTE')
|
||||||
|
break
|
||||||
|
case 'SNIPPET_NOTE':
|
||||||
|
this.createNote('SNIPPET_NOTE')
|
||||||
|
break
|
||||||
|
case 'ALWAYS_ASK':
|
||||||
|
let { dispatch, location } = this.props
|
||||||
|
let { storage, folder } = this.resolveTargetFolder()
|
||||||
|
|
||||||
|
modal.open(NewNoteModal, {
|
||||||
|
storage: storage.key,
|
||||||
|
folder: folder.key,
|
||||||
|
dispatch,
|
||||||
|
location
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveTargetFolder () {
|
||||||
|
let { data, params } = this.props
|
||||||
let storage = data.storageMap.get(params.storageKey)
|
let storage = data.storageMap.get(params.storageKey)
|
||||||
|
|
||||||
// Find first storage
|
// Find first storage
|
||||||
@@ -48,12 +75,10 @@ class TopBar extends React.Component {
|
|||||||
if (folder == null) folder = storage.folders[0]
|
if (folder == null) folder = storage.folders[0]
|
||||||
if (folder == null) throw new Error('No folder to craete a note')
|
if (folder == null) throw new Error('No folder to craete a note')
|
||||||
|
|
||||||
modal.open(NewNoteModal, {
|
return {
|
||||||
storage: storage.key,
|
storage,
|
||||||
folder: folder.key,
|
folder
|
||||||
dispatch,
|
}
|
||||||
location
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchChange (e) {
|
handleSearchChange (e) {
|
||||||
@@ -63,8 +88,9 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOptions () {
|
getOptions () {
|
||||||
let { notes } = this.props
|
let { data } = this.props
|
||||||
let { search } = this.state
|
let { search } = this.state
|
||||||
|
let notes = data.noteMap.map((note) => note)
|
||||||
if (search.trim().length === 0) return []
|
if (search.trim().length === 0) return []
|
||||||
let searchBlocks = search.split(' ')
|
let searchBlocks = search.split(' ')
|
||||||
searchBlocks.forEach((block) => {
|
searchBlocks.forEach((block) => {
|
||||||
@@ -134,15 +160,110 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleContextButtonClick (e) {
|
||||||
|
let { config } = this.props
|
||||||
|
|
||||||
|
let menu = new Menu()
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: 'Create Markdown Note',
|
||||||
|
click: (e) => this.createNote('MARKDOWN_NOTE')
|
||||||
|
}))
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: 'Create Snippet Note',
|
||||||
|
click: (e) => this.createNote('SNIPPET_NOTE')
|
||||||
|
}))
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
type: 'separator'
|
||||||
|
}))
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: 'Change Default Note',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
type: 'radio',
|
||||||
|
label: 'Markdown Note',
|
||||||
|
checked: config .ui.defaultNote === 'MARKDOWN_NOTE',
|
||||||
|
click: (e) => this.setDefaultNote('MARKDOWN_NOTE')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'radio',
|
||||||
|
label: 'Snippet Note',
|
||||||
|
checked: config.ui.defaultNote === 'SNIPPET_NOTE',
|
||||||
|
click: (e) => this.setDefaultNote('SNIPPET_NOTE')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'radio',
|
||||||
|
label: 'Always Ask',
|
||||||
|
checked: config.ui.defaultNote === 'ALWAYS_ASK',
|
||||||
|
click: (e) => this.setDefaultNote('ALWAYS_ASK')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
menu.popup(remote.getCurrentWindow())
|
||||||
|
}
|
||||||
|
|
||||||
|
createNote (noteType) {
|
||||||
|
let { dispatch, location } = this.props
|
||||||
|
if (noteType !== 'MARKDOWN_NOTE' && noteType !== 'SNIPPET_NOTE') throw new Error('Invalid note type.')
|
||||||
|
|
||||||
|
let { storage, folder } = this.resolveTargetFolder()
|
||||||
|
|
||||||
|
let newNote = noteType === 'MARKDOWN_NOTE'
|
||||||
|
? {
|
||||||
|
type: 'MARKDOWN_NOTE',
|
||||||
|
folder: folder.key,
|
||||||
|
title: '',
|
||||||
|
content: ''
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'SNIPPET_NOTE',
|
||||||
|
folder: folder.key,
|
||||||
|
title: '',
|
||||||
|
description: '',
|
||||||
|
snippets: [{
|
||||||
|
name: '',
|
||||||
|
mode: 'text',
|
||||||
|
content: ''
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
dataApi
|
||||||
|
.createNote(storage.key, newNote)
|
||||||
|
.then((note) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
hashHistory.push({
|
||||||
|
pathname: location.pathname,
|
||||||
|
query: {key: note.storage + '-' + note.key}
|
||||||
|
})
|
||||||
|
ee.emit('detail:focus')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultNote (defaultNote) {
|
||||||
|
let { config, dispatch } = this.props
|
||||||
|
let ui = Object.assign(config.ui)
|
||||||
|
ui.defaultNote = defaultNote
|
||||||
|
ConfigManager.set({
|
||||||
|
ui
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_UI',
|
||||||
|
config: ConfigManager.get()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { config, style, storages } = this.props
|
let { config, style, data } = this.props
|
||||||
let searchOptionList = this.getOptions()
|
let searchOptionList = this.getOptions()
|
||||||
.map((note) => {
|
.map((note) => {
|
||||||
let storage = _.find(storages, {key: note.storage})
|
let storage = data.storageMap.get(note.storage)
|
||||||
let folder = _.find(storage.folders, {key: note.folder})
|
let folder = _.find(storage.folders, {key: note.folder})
|
||||||
return <div styleName='control-search-optionList-item'
|
return <div styleName='control-search-optionList-item'
|
||||||
key={note.uniqueKey}
|
key={note.storage + '-' + note.key}
|
||||||
onClick={(e) => this.handleOptionClick(note.uniqueKey)(e)}
|
onClick={(e) => this.handleOptionClick(note.storage + '-' + note.key)(e)}
|
||||||
>
|
>
|
||||||
<div styleName='control-search-optionList-item-folder'
|
<div styleName='control-search-optionList-item-folder'
|
||||||
style={{borderColor: folder.color}}>
|
style={{borderColor: folder.color}}>
|
||||||
@@ -203,6 +324,11 @@ class TopBar extends React.Component {
|
|||||||
New Note {OSX ? '⌘' : '^'} + n
|
New Note {OSX ? '⌘' : '^'} + n
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button styleName='control-contextButton'
|
||||||
|
onClick={(e) => this.handleContextButtonClick(e)}
|
||||||
|
>
|
||||||
|
<i className='fa fa-caret-down'/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ const defaultConfig = {
|
|||||||
zoom: 1,
|
zoom: 1,
|
||||||
isSideNavFolded: false,
|
isSideNavFolded: false,
|
||||||
listWidth: 250,
|
listWidth: 250,
|
||||||
|
navWidth: 200,
|
||||||
hotkey: {
|
hotkey: {
|
||||||
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
|
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
|
||||||
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
|
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
disableDirectWrite: false
|
disableDirectWrite: false,
|
||||||
|
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
|
||||||
},
|
},
|
||||||
editor: {
|
editor: {
|
||||||
theme: 'xcode',
|
theme: 'xcode',
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ class UnstyledFolderItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleConfirmButtonClick (e) {
|
handleConfirmButtonClick (e) {
|
||||||
|
this.confirm()
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm () {
|
||||||
let { storage, folder } = this.props
|
let { storage, folder } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.updateFolder(storage.key, folder.key, {
|
.updateFolder(storage.key, folder.key, {
|
||||||
@@ -87,6 +91,17 @@ class UnstyledFolderItem extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFolderItemBlur (e) {
|
||||||
|
let el = e.relatedTarget
|
||||||
|
while (el != null) {
|
||||||
|
if (el === this.refs.root) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
el = el.parentNode
|
||||||
|
}
|
||||||
|
this.confirm()
|
||||||
|
}
|
||||||
|
|
||||||
renderEdit (e) {
|
renderEdit (e) {
|
||||||
const popover = { position: 'absolute', zIndex: 2 }
|
const popover = { position: 'absolute', zIndex: 2 }
|
||||||
const cover = {
|
const cover = {
|
||||||
@@ -97,7 +112,11 @@ class UnstyledFolderItem extends React.Component {
|
|||||||
position: 'absolute'
|
position: 'absolute'
|
||||||
}, this.state.folder.colorPickerPos)
|
}, this.state.folder.colorPickerPos)
|
||||||
return (
|
return (
|
||||||
<div styleName='folderList-item'>
|
<div styleName='folderList-item'
|
||||||
|
onBlur={(e) => this.handleFolderItemBlur(e)}
|
||||||
|
tabIndex='-1'
|
||||||
|
ref='root'
|
||||||
|
>
|
||||||
<div styleName='folderList-item-left'>
|
<div styleName='folderList-item-left'>
|
||||||
<button styleName='folderList-item-left-colorButton' style={{color: this.state.folder.color}}
|
<button styleName='folderList-item-left-colorButton' style={{color: this.state.folder.color}}
|
||||||
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}
|
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}
|
||||||
|
|||||||
@@ -461,6 +461,9 @@ function config (state = defaultConfig, action) {
|
|||||||
case 'SET_LIST_WIDTH':
|
case 'SET_LIST_WIDTH':
|
||||||
state.listWidth = action.listWidth
|
state.listWidth = action.listWidth
|
||||||
return Object.assign({}, state)
|
return Object.assign({}, state)
|
||||||
|
case 'SET_NAV_WIDTH':
|
||||||
|
state.navWidth = action.navWidth
|
||||||
|
return Object.assign({}, state)
|
||||||
case 'SET_CONFIG':
|
case 'SET_CONFIG':
|
||||||
return Object.assign({}, state, action.config)
|
return Object.assign({}, state, action.config)
|
||||||
case 'SET_UI':
|
case 'SET_UI':
|
||||||
|
|||||||
Reference in New Issue
Block a user