mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 01:36:22 +00:00
Merge branch 'master' into new-snippet-tabbar
This commit is contained in:
@@ -7,6 +7,7 @@ import path from 'path'
|
||||
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
import fs from 'fs'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
|
||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||
|
||||
@@ -47,6 +48,40 @@ export default class CodeEditor extends React.Component {
|
||||
this.loadStyleHandler = (e) => {
|
||||
this.editor.refresh()
|
||||
}
|
||||
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
||||
this.searchState = null
|
||||
}
|
||||
|
||||
handleSearch (msg) {
|
||||
const cm = this.editor
|
||||
const component = this
|
||||
|
||||
if (component.searchState) cm.removeOverlay(component.searchState)
|
||||
if (msg.length < 3) return
|
||||
|
||||
cm.operation(function () {
|
||||
component.searchState = makeOverlay(msg, 'searching')
|
||||
cm.addOverlay(component.searchState)
|
||||
|
||||
function makeOverlay (query, style) {
|
||||
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi')
|
||||
return {
|
||||
token: function (stream) {
|
||||
query.lastIndex = stream.pos
|
||||
var match = query.exec(stream.string)
|
||||
if (match && match.index === stream.pos) {
|
||||
stream.pos += match[0].length || 1
|
||||
return style
|
||||
} else if (match) {
|
||||
stream.pos = match.index
|
||||
} else {
|
||||
stream.skipToEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -107,6 +142,10 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.on('blur', this.blurHandler)
|
||||
this.editor.on('change', this.changeHandler)
|
||||
this.editor.on('paste', this.pasteHandler)
|
||||
eventEmitter.on('top:search', this.searchHandler)
|
||||
|
||||
eventEmitter.emit('code:init')
|
||||
this.editor.on('scroll', this.scrollHandler)
|
||||
|
||||
const editorTheme = document.getElementById('editorTheme')
|
||||
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||
@@ -126,6 +165,8 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.off('blur', this.blurHandler)
|
||||
this.editor.off('change', this.changeHandler)
|
||||
this.editor.off('paste', this.pasteHandler)
|
||||
eventEmitter.off('top:search', this.searchHandler)
|
||||
this.editor.off('scroll', this.scrollHandler)
|
||||
const editorTheme = document.getElementById('editorTheme')
|
||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||
}
|
||||
@@ -231,27 +272,67 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
handlePaste (editor, e) {
|
||||
const dataTransferItem = e.clipboardData.items[0]
|
||||
if (!dataTransferItem.type.match('image')) return
|
||||
|
||||
const blob = dataTransferItem.getAsFile()
|
||||
const reader = new window.FileReader()
|
||||
let base64data
|
||||
|
||||
reader.readAsDataURL(blob)
|
||||
reader.onloadend = () => {
|
||||
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
||||
base64data += base64data.replace('+', ' ')
|
||||
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
||||
const imageName = Math.random().toString(36).slice(-16)
|
||||
const storagePath = findStorage(this.props.storageKey).path
|
||||
const imageDir = path.join(storagePath, 'images')
|
||||
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
|
||||
const imagePath = path.join(imageDir, `${imageName}.png`)
|
||||
fs.writeFile(imagePath, binaryData, 'binary')
|
||||
const imageMd = `})`
|
||||
this.insertImageMd(imageMd)
|
||||
const clipboardData = e.clipboardData
|
||||
const dataTransferItem = clipboardData.items[0]
|
||||
const pastedTxt = clipboardData.getData('text')
|
||||
const isURL = (str) => {
|
||||
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
|
||||
return matcher.test(str)
|
||||
}
|
||||
if (dataTransferItem.type.match('image')) {
|
||||
const blob = dataTransferItem.getAsFile()
|
||||
const reader = new FileReader()
|
||||
let base64data
|
||||
|
||||
reader.readAsDataURL(blob)
|
||||
reader.onloadend = () => {
|
||||
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
||||
base64data += base64data.replace('+', ' ')
|
||||
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
||||
const imageName = Math.random().toString(36).slice(-16)
|
||||
const storagePath = findStorage(this.props.storageKey).path
|
||||
const imageDir = path.join(storagePath, 'images')
|
||||
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
|
||||
const imagePath = path.join(imageDir, `${imageName}.png`)
|
||||
fs.writeFile(imagePath, binaryData, 'binary')
|
||||
const imageMd = `})`
|
||||
this.insertImageMd(imageMd)
|
||||
}
|
||||
} else if (this.props.fetchUrlTitle && isURL(pastedTxt)) {
|
||||
this.handlePasteUrl(e, editor, pastedTxt)
|
||||
}
|
||||
}
|
||||
|
||||
handleScroll (e) {
|
||||
if (this.props.onScroll) {
|
||||
this.props.onScroll(e)
|
||||
}
|
||||
}
|
||||
|
||||
handlePasteUrl (e, editor, pastedTxt) {
|
||||
e.preventDefault()
|
||||
const taggedUrl = `<${pastedTxt}>`
|
||||
editor.replaceSelection(taggedUrl)
|
||||
|
||||
fetch(pastedTxt, {
|
||||
method: 'get'
|
||||
}).then((response) => {
|
||||
return (response.text())
|
||||
}).then((response) => {
|
||||
const parsedResponse = (new window.DOMParser()).parseFromString(response, 'text/html')
|
||||
const value = editor.getValue()
|
||||
const cursor = editor.getCursor()
|
||||
const LinkWithTitle = `[${parsedResponse.title}](${pastedTxt})`
|
||||
const newValue = value.replace(taggedUrl, LinkWithTitle)
|
||||
editor.setValue(newValue)
|
||||
editor.setCursor(cursor)
|
||||
}).catch((e) => {
|
||||
const value = editor.getValue()
|
||||
const newValue = value.replace(taggedUrl, pastedTxt)
|
||||
const cursor = editor.getCursor()
|
||||
editor.setValue(newValue)
|
||||
editor.setCursor(cursor)
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
@@ -5,7 +5,7 @@ import styles from './MarkdownEditor.styl'
|
||||
import CodeEditor from 'browser/components/CodeEditor'
|
||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
import {findStorage} from 'browser/lib/findStorage'
|
||||
|
||||
class MarkdownEditor extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -92,7 +92,9 @@ class MarkdownEditor extends React.Component {
|
||||
if (this.state.isLocked) return
|
||||
this.setState({ keyPressed: new Set() })
|
||||
const { config } = this.props
|
||||
if (config.editor.switchPreview === 'BLUR') {
|
||||
if (config.editor.switchPreview === 'BLUR' ||
|
||||
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
|
||||
) {
|
||||
const cursorPosition = this.refs.code.editor.getCursor()
|
||||
this.setState({
|
||||
status: 'PREVIEW'
|
||||
@@ -104,6 +106,20 @@ class MarkdownEditor extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleDoubleClick (e) {
|
||||
if (this.state.isLocked) return
|
||||
this.setState({keyPressed: new Set()})
|
||||
const { config } = this.props
|
||||
if (config.editor.switchPreview === 'DBL_CLICK') {
|
||||
this.setState({
|
||||
status: 'CODE'
|
||||
}, () => {
|
||||
this.refs.code.focus()
|
||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handlePreviewMouseDown (e) {
|
||||
this.previewMouseDownedAt = new Date()
|
||||
}
|
||||
@@ -245,6 +261,7 @@ class MarkdownEditor extends React.Component {
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
storageKey={storageKey}
|
||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onBlur={(e) => this.handleBlur(e)}
|
||||
/>
|
||||
@@ -264,6 +281,7 @@ class MarkdownEditor extends React.Component {
|
||||
scrollPastEnd={config.preview.scrollPastEnd}
|
||||
ref='preview'
|
||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||
onDoubleClick={(e) => this.handleDoubleClick(e)}
|
||||
tabIndex='0'
|
||||
value={this.state.renderValue}
|
||||
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
||||
|
||||
@@ -120,6 +120,8 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
||||
this.mouseDownHandler = (e) => this.handleMouseDown(e)
|
||||
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
||||
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
|
||||
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
||||
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
|
||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||
@@ -150,10 +152,20 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.props.onCheckboxClick(e)
|
||||
}
|
||||
|
||||
handleScroll (e) {
|
||||
if (this.props.onScroll) {
|
||||
this.props.onScroll(e)
|
||||
}
|
||||
}
|
||||
|
||||
handleContextMenu (e) {
|
||||
this.props.onContextMenu(e)
|
||||
}
|
||||
|
||||
handleDoubleClick (e) {
|
||||
if (this.props.onDoubleClick != null) this.props.onDoubleClick(e)
|
||||
}
|
||||
|
||||
handleMouseDown (e) {
|
||||
if (e.target != null) {
|
||||
switch (e.target.tagName) {
|
||||
@@ -271,8 +283,10 @@ export default class MarkdownPreview extends React.Component {
|
||||
|
||||
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
|
||||
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
|
||||
this.refs.root.contentWindow.document.addEventListener('dblclick', this.DoubleClickHandler)
|
||||
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
|
||||
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
|
||||
this.refs.root.contentWindow.document.addEventListener('scroll', this.scrollHandler)
|
||||
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
||||
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
||||
@@ -283,8 +297,10 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
|
||||
this.refs.root.contentWindow.document.removeEventListener('mousedown', this.mouseDownHandler)
|
||||
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
|
||||
this.refs.root.contentWindow.document.removeEventListener('dblclick', this.DoubleClickHandler)
|
||||
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
|
||||
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
|
||||
this.refs.root.contentWindow.document.removeEventListener('scroll', this.scrollHandler)
|
||||
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
||||
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
||||
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react'
|
||||
import CodeEditor from 'browser/components/CodeEditor'
|
||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
import _ from 'lodash'
|
||||
|
||||
import styles from './MarkdownSplitEditor.styl'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
@@ -12,6 +13,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
this.value = props.value
|
||||
this.focus = () => this.refs.code.focus()
|
||||
this.reload = () => this.refs.code.reload()
|
||||
this.userScroll = true
|
||||
}
|
||||
|
||||
handleOnChange () {
|
||||
@@ -19,6 +21,49 @@ class MarkdownSplitEditor extends React.Component {
|
||||
this.props.onChange()
|
||||
}
|
||||
|
||||
handleScroll (e) {
|
||||
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
|
||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||
let srcTop, srcHeight, targetTop, targetHeight
|
||||
|
||||
if (this.userScroll) {
|
||||
if (e.doc) {
|
||||
srcTop = _.get(e, 'doc.scrollTop')
|
||||
srcHeight = _.get(e, 'doc.height')
|
||||
targetTop = _.get(previewDoc, 'body.scrollTop')
|
||||
targetHeight = _.get(previewDoc, 'body.scrollHeight')
|
||||
} else {
|
||||
srcTop = _.get(previewDoc, 'body.scrollTop')
|
||||
srcHeight = _.get(previewDoc, 'body.scrollHeight')
|
||||
targetTop = _.get(codeDoc, 'scrollTop')
|
||||
targetHeight = _.get(codeDoc, 'height')
|
||||
}
|
||||
|
||||
const distance = (targetHeight * srcTop / srcHeight) - targetTop
|
||||
const framerate = 1000 / 60
|
||||
const frames = 20
|
||||
const refractory = frames * framerate
|
||||
|
||||
this.userScroll = false
|
||||
|
||||
let frame = 0
|
||||
let scrollPos, time
|
||||
const timer = setInterval(() => {
|
||||
time = frame / frames
|
||||
scrollPos = time < 0.5
|
||||
? 2 * time * time // ease in
|
||||
: -1 + (4 - 2 * time) * time // ease out
|
||||
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
||||
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
|
||||
if (frame >= frames) {
|
||||
clearInterval(timer)
|
||||
setTimeout(() => { this.userScroll = true }, refractory)
|
||||
}
|
||||
frame++
|
||||
}, framerate)
|
||||
}
|
||||
}
|
||||
|
||||
handleCheckboxClick (e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@@ -66,8 +111,10 @@ class MarkdownSplitEditor extends React.Component {
|
||||
indentType={config.editor.indentType}
|
||||
indentSize={editorIndentSize}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||
storageKey={storageKey}
|
||||
onChange={this.handleOnChange.bind(this)}
|
||||
onScroll={this.handleScroll.bind(this)}
|
||||
/>
|
||||
<MarkdownPreview
|
||||
style={previewStyle}
|
||||
@@ -84,6 +131,7 @@ class MarkdownSplitEditor extends React.Component {
|
||||
tabInde='0'
|
||||
value={value}
|
||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||
onScroll={this.handleScroll.bind(this)}
|
||||
showCopyNotification={config.ui.showCopyNotification}
|
||||
storagePath={storage.path}
|
||||
/>
|
||||
|
||||
@@ -46,11 +46,22 @@ const TagElementList = (tags) => {
|
||||
* @param {Function} handleDragStart
|
||||
* @param {string} dateDisplay
|
||||
*/
|
||||
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
|
||||
const NoteItem = ({
|
||||
isActive,
|
||||
note,
|
||||
dateDisplay,
|
||||
handleNoteClick,
|
||||
handleNoteContextMenu,
|
||||
handleDragStart,
|
||||
pathname,
|
||||
storageName,
|
||||
folderName,
|
||||
viewType
|
||||
}) => (
|
||||
<div styleName={isActive
|
||||
? 'item--active'
|
||||
: 'item'
|
||||
}
|
||||
? 'item--active'
|
||||
: 'item'
|
||||
}
|
||||
key={`${note.storage}-${note.key}`}
|
||||
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
||||
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
||||
@@ -68,23 +79,33 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteCont
|
||||
: <span styleName='item-title-empty'>Empty</span>
|
||||
}
|
||||
</div>
|
||||
{['ALL', 'STORAGE'].includes(viewType) && <div styleName='item-middle'>
|
||||
<div styleName='item-middle-time'>{dateDisplay}</div>
|
||||
<div styleName='item-middle-app-meta'>
|
||||
<div title={viewType === 'ALL' ? storageName : viewType === 'STORAGE' ? folderName : null} styleName='item-middle-app-meta-label'>
|
||||
{viewType === 'ALL' && storageName}
|
||||
{viewType === 'STORAGE' && folderName}
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
|
||||
<div styleName='item-bottom-time'>{dateDisplay}</div>
|
||||
{note.isStarred
|
||||
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
|
||||
}
|
||||
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
||||
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
|
||||
}
|
||||
{note.type === 'MARKDOWN_NOTE'
|
||||
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
||||
: ''
|
||||
}
|
||||
<div styleName='item-bottom'>
|
||||
<div styleName='item-bottom-tagList'>
|
||||
{note.tags.length > 0
|
||||
? TagElementList(note.tags)
|
||||
: <span styleName='item-bottom-tagList-empty' />
|
||||
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>No tags</span>
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
{note.isStarred
|
||||
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
|
||||
}
|
||||
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
|
||||
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
|
||||
}
|
||||
{note.type === 'MARKDOWN_NOTE'
|
||||
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -90,6 +90,26 @@ $control-height = 30px
|
||||
font-weight normal
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.item-middle
|
||||
font-size 13px
|
||||
padding-left 2px
|
||||
padding-bottom 2px
|
||||
|
||||
.item-middle-time
|
||||
color $ui-inactive-text-color
|
||||
display inline-block
|
||||
|
||||
.item-middle-app-meta
|
||||
float right
|
||||
.item-middle-app-meta-label
|
||||
opacity 0.4
|
||||
color $ui-inactive-text-color
|
||||
padding 0 3px
|
||||
white-space nowrap
|
||||
text-overflow ellipsis
|
||||
overflow hidden
|
||||
max-width 200px
|
||||
|
||||
.item-bottom
|
||||
position relative
|
||||
bottom 0px
|
||||
@@ -124,9 +144,7 @@ $control-height = 30px
|
||||
padding-bottom 2px
|
||||
|
||||
.item-star
|
||||
position absolute
|
||||
right -6px
|
||||
bottom 23px
|
||||
position relative
|
||||
width 16px
|
||||
height 16px
|
||||
color alpha($ui-favorite-star-button-color, 60%)
|
||||
@@ -135,9 +153,7 @@ $control-height = 30px
|
||||
border-radius 17px
|
||||
|
||||
.item-pin
|
||||
position absolute
|
||||
right 0px
|
||||
bottom 2px
|
||||
position relative
|
||||
width 34px
|
||||
height 34px
|
||||
color #E54D42
|
||||
@@ -192,7 +208,7 @@ body[data-theme="dark"]
|
||||
.item-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(white, 10%)
|
||||
color $ui-dark-text-color
|
||||
color $ui-dark-text-color
|
||||
|
||||
.item-wrapper
|
||||
border-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||
@@ -266,7 +282,7 @@ body[data-theme="solarized-dark"]
|
||||
.item-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha($ui-solarized-dark-noteList-backgroundColor, 10%)
|
||||
color $ui-solarized-dark-text-color
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.item-wrapper
|
||||
border-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)
|
||||
@@ -304,4 +320,4 @@ body[data-theme="solarized-dark"]
|
||||
|
||||
.item-bottom-tagList-empty
|
||||
color $ui-inactive-text-color
|
||||
vertical-align middle
|
||||
vertical-align middle
|
||||
|
||||
@@ -14,11 +14,20 @@ import styles from './NoteItemSimple.styl'
|
||||
* @param {Function} handleNoteContextMenu
|
||||
* @param {Function} handleDragStart
|
||||
*/
|
||||
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
|
||||
const NoteItemSimple = ({
|
||||
isActive,
|
||||
isAllNotesView,
|
||||
note,
|
||||
handleNoteClick,
|
||||
handleNoteContextMenu,
|
||||
handleDragStart,
|
||||
pathname,
|
||||
storage
|
||||
}) => (
|
||||
<div styleName={isActive
|
||||
? 'item-simple--active'
|
||||
: 'item-simple'
|
||||
}
|
||||
? 'item-simple--active'
|
||||
: 'item-simple'
|
||||
}
|
||||
key={`${note.storage}-${note.key}`}
|
||||
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
||||
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
||||
@@ -38,6 +47,11 @@ const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu
|
||||
? note.title
|
||||
: <span styleName='item-simple-title-empty'>Empty</span>
|
||||
}
|
||||
{isAllNotesView && <div styleName='item-simple-right'>
|
||||
<span styleName='item-simple-right-storageName'>
|
||||
{storage.name}
|
||||
</span>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -124,7 +124,7 @@ body[data-theme="dark"]
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(white, 10%)
|
||||
color $ui-dark-text-color
|
||||
color $ui-dark-text-color
|
||||
|
||||
.item-simple--active
|
||||
border-color $ui-dark-borderColor
|
||||
@@ -188,7 +188,7 @@ body[data-theme="solarized-dark"]
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(white, 10%)
|
||||
color $ui-solarized-dark-text-color
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.item-simple--active
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
@@ -206,4 +206,9 @@ body[data-theme="solarized-dark"]
|
||||
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||
color #c0392b
|
||||
.item-simple-bottom-tagList-item
|
||||
background-color alpha(#fff, 20%)
|
||||
background-color alpha(#fff, 20%)
|
||||
.item-simple-right
|
||||
float right
|
||||
.item-simple-right-storageName
|
||||
padding-left 4px
|
||||
opacity 0.4
|
||||
|
||||
@@ -17,7 +17,7 @@ import styles from './SideNavFilter.styl'
|
||||
const SideNavFilter = ({
|
||||
isFolded, isHomeActive, handleAllNotesButtonClick,
|
||||
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
|
||||
counterTotalNote, counterStarredNote
|
||||
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu
|
||||
}) => (
|
||||
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
||||
|
||||
@@ -26,9 +26,9 @@ const SideNavFilter = ({
|
||||
>
|
||||
<div styleName='iconWrap'>
|
||||
<img src={isHomeActive
|
||||
? '../resources/icon/icon-all-active.svg'
|
||||
: '../resources/icon/icon-all.svg'
|
||||
}
|
||||
? '../resources/icon/icon-all-active.svg'
|
||||
: '../resources/icon/icon-all.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span styleName='menu-button-label'>All Notes</span>
|
||||
@@ -40,9 +40,9 @@ const SideNavFilter = ({
|
||||
>
|
||||
<div styleName='iconWrap'>
|
||||
<img src={isStarredActive
|
||||
? '../resources/icon/icon-star-active.svg'
|
||||
: '../resources/icon/icon-star-sidenav.svg'
|
||||
}
|
||||
? '../resources/icon/icon-star-active.svg'
|
||||
: '../resources/icon/icon-star-sidenav.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span styleName='menu-button-label'>Starred</span>
|
||||
@@ -54,12 +54,12 @@ const SideNavFilter = ({
|
||||
>
|
||||
<div styleName='iconWrap'>
|
||||
<img src={isTrashedActive
|
||||
? '../resources/icon/icon-trash-active.svg'
|
||||
: '../resources/icon/icon-trash-sidenav.svg'
|
||||
}
|
||||
? '../resources/icon/icon-trash-active.svg'
|
||||
: '../resources/icon/icon-trash-sidenav.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span styleName='menu-button-label'>Trash</span>
|
||||
<span onContextMenu={handleFilterButtonContextMenu} styleName='menu-button-label'>Trash</span>
|
||||
<span styleName='counters'>{counterDelNote}</span>
|
||||
</button>
|
||||
|
||||
|
||||
@@ -12,10 +12,11 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
* @param {bool} isActive
|
||||
*/
|
||||
|
||||
const TagListItem = ({name, handleClickTagListItem, isActive}) => (
|
||||
const TagListItem = ({name, handleClickTagListItem, isActive, count}) => (
|
||||
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
||||
<span styleName='tagList-item-name'>
|
||||
{`# ${name}`}
|
||||
<span styleName='tagList-item-count'> {count}</span>
|
||||
</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
overflow hidden
|
||||
text-overflow ellipsis
|
||||
|
||||
.tagList-item-count
|
||||
padding 0 3px
|
||||
|
||||
body[data-theme="white"]
|
||||
.tagList-item
|
||||
color $ui-inactive-text-color
|
||||
@@ -63,6 +66,8 @@ body[data-theme="white"]
|
||||
color $ui-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
.tagList-item-count
|
||||
color $ui-text-color
|
||||
|
||||
body[data-theme="dark"]
|
||||
.tagList-item
|
||||
@@ -81,4 +86,6 @@ body[data-theme="dark"]
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||
&:hover
|
||||
color $ui-dark-text-color
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||
.tagList-item-count
|
||||
color $ui-dark-button--active-color
|
||||
|
||||
@@ -220,6 +220,7 @@ pre
|
||||
background-color white
|
||||
&.CodeMirror
|
||||
height initial
|
||||
flex-wrap wrap
|
||||
&>code
|
||||
flex 1
|
||||
overflow-x auto
|
||||
@@ -229,6 +230,13 @@ pre
|
||||
padding 0
|
||||
border none
|
||||
border-radius 0
|
||||
&>span.filename
|
||||
width 100%
|
||||
border-radius: 5px 0px 0px 0px
|
||||
margin -8px 100% 8px -8px
|
||||
padding 0px 6px
|
||||
background-color #777;
|
||||
color white
|
||||
&>span.lineNumber
|
||||
display none
|
||||
font-size 1em
|
||||
|
||||
0
browser/finder/NoteDetail.js
Normal file
0
browser/finder/NoteDetail.js
Normal file
@@ -9,10 +9,11 @@ import {lastFindInArray} from './utils'
|
||||
const katex = window.katex
|
||||
const config = ConfigManager.get()
|
||||
|
||||
function createGutter (str) {
|
||||
const lc = (str.match(/\n/g) || []).length
|
||||
function createGutter (str, firstLineNumber) {
|
||||
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
|
||||
const lines = []
|
||||
for (let i = 1; i <= lc; i++) {
|
||||
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
|
||||
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
|
||||
}
|
||||
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
||||
@@ -25,15 +26,22 @@ var md = markdownit({
|
||||
xhtmlOut: true,
|
||||
breaks: true,
|
||||
highlight: function (str, lang) {
|
||||
if (lang === 'flowchart') {
|
||||
const delimiter = ':'
|
||||
const langInfo = lang.split(delimiter)
|
||||
const langType = langInfo[0]
|
||||
const fileName = langInfo[1] || ''
|
||||
const firstLineNumber = parseInt(langInfo[2], 10)
|
||||
|
||||
if (langType === 'flowchart') {
|
||||
return `<pre class="flowchart">${str}</pre>`
|
||||
}
|
||||
if (lang === 'sequence') {
|
||||
if (langType === 'sequence') {
|
||||
return `<pre class="sequence">${str}</pre>`
|
||||
}
|
||||
return '<pre class="code">' +
|
||||
createGutter(str) +
|
||||
'<code class="' + lang + '">' +
|
||||
'<span class="filename">' + fileName + '</span>' +
|
||||
createGutter(str, firstLineNumber) +
|
||||
'<code class="' + langType + '">' +
|
||||
str +
|
||||
'</code></pre>'
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import TrashButton from './TrashButton'
|
||||
import FullscreenButton from './FullscreenButton'
|
||||
import RestoreButton from './RestoreButton'
|
||||
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||
import InfoButton from './InfoButton'
|
||||
import ToggleModeButton from './ToggleModeButton'
|
||||
@@ -68,11 +69,8 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
}
|
||||
|
||||
componentDidUnmount () {
|
||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||
if (this.saveQueue != null) this.saveNow()
|
||||
}
|
||||
|
||||
handleUpdateTag () {
|
||||
@@ -324,10 +322,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
const trashTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<i styleName='undo-button'
|
||||
className='fa fa-undo fa-fw'
|
||||
onClick={(e) => this.handleUndoButtonClick(e)}
|
||||
/>
|
||||
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
@@ -362,12 +357,10 @@ class MarkdownNoteDetail extends React.Component {
|
||||
value={this.state.note.tags}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
/>
|
||||
|
||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||
|
||||
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||
<StarButton
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
box-shadow none
|
||||
padding 20px 40px
|
||||
overflow hidden
|
||||
|
||||
.lock-button
|
||||
padding-bottom 3px
|
||||
@@ -44,7 +45,7 @@
|
||||
margin 0 30px
|
||||
.body-noteEditor
|
||||
absolute top bottom left right
|
||||
|
||||
|
||||
body[data-theme="white"]
|
||||
.root
|
||||
box-shadow $note-detail-box-shadow
|
||||
|
||||
21
browser/main/Detail/RestoreButton.js
Normal file
21
browser/main/Detail/RestoreButton.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './RestoreButton.styl'
|
||||
|
||||
const RestoreButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-restoreButton'
|
||||
onClick={onClick}
|
||||
>
|
||||
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
|
||||
<span styleName='tooltip'>Restore</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
RestoreButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(RestoreButton, styles)
|
||||
22
browser/main/Detail/RestoreButton.styl
Normal file
22
browser/main/Detail/RestoreButton.styl
Normal file
@@ -0,0 +1,22 @@
|
||||
.control-restoreButton
|
||||
top 115px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 50px
|
||||
left 25px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-restoreButton
|
||||
topBarButtonDark()
|
||||
@@ -20,6 +20,7 @@ import _ from 'lodash'
|
||||
import {findNoteTitle} from 'browser/lib/findNoteTitle'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import TrashButton from './TrashButton'
|
||||
import RestoreButton from './RestoreButton'
|
||||
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||
import InfoButton from './InfoButton'
|
||||
import InfoPanel from './InfoPanel'
|
||||
@@ -648,6 +649,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
displayLineNumbers={config.editor.displayLineNumbers}
|
||||
keyMap={config.editor.keyMap}
|
||||
scrollPastEnd={config.editor.scrollPastEnd}
|
||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||
ref={'code-' + index}
|
||||
/>
|
||||
@@ -668,10 +670,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
|
||||
const trashTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<i styleName='undo-button'
|
||||
className='fa fa-undo fa-fw'
|
||||
onClick={(e) => this.handleUndoButtonClick(e)}
|
||||
/>
|
||||
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
width 52px
|
||||
display flex
|
||||
align-items center
|
||||
position absolute
|
||||
right 165px
|
||||
position: relative
|
||||
top 2px
|
||||
.active
|
||||
background-color #1EC38B
|
||||
width 33px
|
||||
@@ -55,4 +55,4 @@ body[data-theme="solarized-dark"]
|
||||
background-color #002B36
|
||||
.active
|
||||
background-color #1EC38B
|
||||
box-shadow 2px 0px 7px #222222
|
||||
box-shadow 2px 0px 7px #222222
|
||||
|
||||
@@ -66,6 +66,10 @@ class NoteList extends React.Component {
|
||||
this.deleteNote = this.deleteNote.bind(this)
|
||||
this.focusNote = this.focusNote.bind(this)
|
||||
this.pinToTop = this.pinToTop.bind(this)
|
||||
this.getNoteStorage = this.getNoteStorage.bind(this)
|
||||
this.getNoteFolder = this.getNoteFolder.bind(this)
|
||||
this.getViewType = this.getViewType.bind(this)
|
||||
this.restoreNote = this.restoreNote.bind(this)
|
||||
|
||||
// TODO: not Selected noteKeys but SelectedNote(for reusing)
|
||||
this.state = {
|
||||
@@ -109,14 +113,27 @@ class NoteList extends React.Component {
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { location } = this.props
|
||||
const { selectedNoteKeys } = this.state
|
||||
const visibleNoteKeys = this.notes.map(note => `${note.storage}-${note.key}`)
|
||||
const note = this.notes[0]
|
||||
const prevKey = prevProps.location.query.key
|
||||
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && `${note.storage}-${note.key}`
|
||||
|
||||
if (this.notes.length > 0 && location.query.key == null) {
|
||||
if (note && location.query.key == null) {
|
||||
const { router } = this.context
|
||||
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
|
||||
|
||||
// A visible note is an active note
|
||||
if (!selectedNoteKeys.includes(noteKey)) {
|
||||
if (selectedNoteKeys.length === 1) selectedNoteKeys.pop()
|
||||
selectedNoteKeys.push(noteKey)
|
||||
ee.emit('list:moved')
|
||||
}
|
||||
|
||||
router.replace({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
key: this.notes[0].storage + '-' + this.notes[0].key
|
||||
key: noteKey
|
||||
}
|
||||
})
|
||||
return
|
||||
@@ -440,6 +457,7 @@ class NoteList extends React.Component {
|
||||
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
|
||||
const deleteLabel = 'Delete Note'
|
||||
const cloneNote = 'Clone Note'
|
||||
const restoreNote = 'Restore Note'
|
||||
|
||||
const menu = new Menu()
|
||||
if (!location.pathname.match(/\/starred|\/trash/)) {
|
||||
@@ -448,6 +466,14 @@ class NoteList extends React.Component {
|
||||
click: this.pinToTop
|
||||
}))
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/trash/)) {
|
||||
menu.append(new MenuItem({
|
||||
label: restoreNote,
|
||||
click: this.restoreNote
|
||||
}))
|
||||
}
|
||||
|
||||
menu.append(new MenuItem({
|
||||
label: deleteLabel,
|
||||
click: this.deleteNote
|
||||
@@ -459,28 +485,50 @@ class NoteList extends React.Component {
|
||||
menu.popup()
|
||||
}
|
||||
|
||||
pinToTop () {
|
||||
updateSelectedNotes (updateFunc, cleanSelection = true) {
|
||||
const { selectedNoteKeys } = this.state
|
||||
const { dispatch } = this.props
|
||||
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||
|
||||
if (!_.isFunction(updateFunc)) {
|
||||
console.warn('Update function is not defined. No update will happen')
|
||||
updateFunc = (note) => { return note }
|
||||
}
|
||||
|
||||
Promise.all(
|
||||
selectedNotes.map((note) => {
|
||||
note.isPinned = !note.isPinned
|
||||
return dataApi
|
||||
.updateNote(note.storage, note.key, note)
|
||||
})
|
||||
)
|
||||
.then((updatedNotes) => {
|
||||
updatedNotes.forEach((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note
|
||||
selectedNotes.map((note) => {
|
||||
note = updateFunc(note)
|
||||
return dataApi
|
||||
.updateNote(note.storage, note.key, note)
|
||||
})
|
||||
})
|
||||
)
|
||||
.then((updatedNotes) => {
|
||||
updatedNotes.forEach((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
if (cleanSelection) {
|
||||
this.selectNextNote()
|
||||
}
|
||||
}
|
||||
|
||||
pinToTop () {
|
||||
this.updateSelectedNotes((note) => {
|
||||
note.isPinned = !note.isPinned
|
||||
return note
|
||||
})
|
||||
}
|
||||
|
||||
restoreNote () {
|
||||
this.updateSelectedNotes((note) => {
|
||||
note.isTrashed = false
|
||||
return note
|
||||
})
|
||||
this.setState({ selectedNoteKeys: [] })
|
||||
}
|
||||
|
||||
deleteNote () {
|
||||
@@ -678,6 +726,24 @@ class NoteList extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
getNoteStorage (note) { // note.storage = storage key
|
||||
return this.props.data.storageMap.toJS()[note.storage]
|
||||
}
|
||||
|
||||
getNoteFolder (note) { // note.folder = folder key
|
||||
return _.find(this.getNoteStorage(note).folders, ({ key }) => key === note.folder)
|
||||
}
|
||||
|
||||
getViewType () {
|
||||
const { pathname } = this.props.location
|
||||
const folder = /\/folders\/[a-zA-Z0-9]+/.test(pathname)
|
||||
const storage = /\/storages\/[a-zA-Z0-9]+/.test(pathname) && !folder
|
||||
const allNotes = pathname === '/home'
|
||||
if (allNotes) return 'ALL'
|
||||
if (folder) return 'FOLDER'
|
||||
if (storage) return 'STORAGE'
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, config } = this.props
|
||||
let { notes } = this.props
|
||||
@@ -714,6 +780,8 @@ class NoteList extends React.Component {
|
||||
}
|
||||
})
|
||||
|
||||
const viewType = this.getViewType()
|
||||
|
||||
const noteList = notes
|
||||
.map(note => {
|
||||
if (note == null) {
|
||||
@@ -739,6 +807,9 @@ class NoteList extends React.Component {
|
||||
handleNoteClick={this.handleNoteClick.bind(this)}
|
||||
handleDragStart={this.handleDragStart.bind(this)}
|
||||
pathname={location.pathname}
|
||||
folderName={this.getNoteFolder(note).name}
|
||||
storageName={this.getNoteStorage(note).name}
|
||||
viewType={viewType}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -752,6 +823,9 @@ class NoteList extends React.Component {
|
||||
handleNoteClick={this.handleNoteClick.bind(this)}
|
||||
handleDragStart={this.handleDragStart.bind(this)}
|
||||
pathname={location.pathname}
|
||||
folderName={this.getNoteFolder(note).name}
|
||||
storageName={this.getNoteStorage(note).name}
|
||||
viewType={viewType}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
const { remote } = require('electron')
|
||||
const { Menu } = remote
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import styles from './SideNav.styl'
|
||||
import { openModal } from 'browser/main/lib/modal'
|
||||
import PreferencesModal from '../modals/PreferencesModal'
|
||||
@@ -89,6 +92,7 @@ class SideNav extends React.Component {
|
||||
counterTotalNote={data.noteMap._map.size - data.trashedSet._set.size}
|
||||
counterStarredNote={data.starredSet._set.size}
|
||||
counterDelNote={data.trashedSet._set.size}
|
||||
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)}
|
||||
/>
|
||||
|
||||
<StorageList storageList={storageList} />
|
||||
@@ -113,18 +117,21 @@ class SideNav extends React.Component {
|
||||
|
||||
tagListComponent () {
|
||||
const { data, location } = this.props
|
||||
const tagList = data.tagNoteMap.map((tag, key) => {
|
||||
return key
|
||||
const tagList = data.tagNoteMap.map((tag, name) => {
|
||||
return { name, size: tag.size }
|
||||
})
|
||||
return (
|
||||
tagList.map(tag => (
|
||||
<TagListItem
|
||||
name={tag}
|
||||
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
||||
isActive={this.getTagActive(location.pathname, tag)}
|
||||
key={tag}
|
||||
/>
|
||||
))
|
||||
tagList.map(tag => {
|
||||
return (
|
||||
<TagListItem
|
||||
name={tag.name}
|
||||
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
||||
isActive={this.getTagActive(location.pathname, tag)}
|
||||
key={tag.name}
|
||||
count={tag.size}
|
||||
/>
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -139,6 +146,34 @@ class SideNav extends React.Component {
|
||||
router.push(`/tags/${name}`)
|
||||
}
|
||||
|
||||
emptyTrash (entries) {
|
||||
const { dispatch } = this.props
|
||||
const deletionPromises = entries.map((storageAndNoteKey) => {
|
||||
const storageKey = storageAndNoteKey.split('-')[0]
|
||||
const noteKey = storageAndNoteKey.split('-')[1]
|
||||
return dataApi.deleteNote(storageKey, noteKey)
|
||||
})
|
||||
Promise.all(deletionPromises)
|
||||
.then((arrayOfStorageAndNoteKeys) => {
|
||||
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
|
||||
dispatch({ type: 'DELETE_NOTE', storageKey, noteKey })
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Cannot Delete note: ' + err)
|
||||
})
|
||||
console.log('Trash emptied')
|
||||
}
|
||||
|
||||
handleFilterButtonContextMenu (event) {
|
||||
const { data } = this.props
|
||||
const entries = data.trashedSet.toJS()
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{ label: 'Empty Trash', click: () => this.emptyTrash(entries) }
|
||||
])
|
||||
menu.popup()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { data, location, config, dispatch } = this.props
|
||||
|
||||
|
||||
@@ -22,14 +22,18 @@ class TopBar extends React.Component {
|
||||
this.focusSearchHandler = () => {
|
||||
this.handleOnSearchFocus()
|
||||
}
|
||||
|
||||
this.codeInitHandler = this.handleCodeInit.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
ee.on('top:focus-search', this.focusSearchHandler)
|
||||
ee.on('code:init', this.codeInitHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ee.off('top:focus-search', this.focusSearchHandler)
|
||||
ee.off('code:init', this.codeInitHandler)
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
@@ -73,14 +77,16 @@ class TopBar extends React.Component {
|
||||
|
||||
handleSearchChange (e) {
|
||||
const { router } = this.context
|
||||
const keyword = this.refs.searchInput.value
|
||||
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
|
||||
router.push('/searched')
|
||||
} else {
|
||||
e.preventDefault()
|
||||
}
|
||||
this.setState({
|
||||
search: this.refs.searchInput.value
|
||||
search: keyword
|
||||
})
|
||||
ee.emit('top:search', keyword)
|
||||
}
|
||||
|
||||
handleSearchFocus (e) {
|
||||
@@ -115,6 +121,10 @@ class TopBar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleCodeInit () {
|
||||
ee.emit('top:search', this.refs.searchInput.value)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { config, style, location } = this.props
|
||||
return (
|
||||
|
||||
@@ -36,7 +36,8 @@ export const DEFAULT_CONFIG = {
|
||||
displayLineNumbers: true,
|
||||
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
||||
scrollPastEnd: false,
|
||||
type: 'SPLIT'
|
||||
type: 'SPLIT',
|
||||
fetchUrlTitle: true
|
||||
},
|
||||
preview: {
|
||||
fontSize: '14',
|
||||
|
||||
@@ -35,14 +35,16 @@ class NewNoteModal extends React.Component {
|
||||
content: ''
|
||||
})
|
||||
.then((note) => {
|
||||
const noteHash = `${note.storage}-${note.key}`
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: note.storage + '-' + note.key}
|
||||
query: {key: noteHash}
|
||||
})
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
this.props.close()
|
||||
})
|
||||
@@ -73,14 +75,16 @@ class NewNoteModal extends React.Component {
|
||||
}]
|
||||
})
|
||||
.then((note) => {
|
||||
const noteHash = `${note.storage}-${note.key}`
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: note.storage + '-' + note.key}
|
||||
query: {key: noteHash}
|
||||
})
|
||||
ee.emit('list:jump', noteHash)
|
||||
ee.emit('detail:focus')
|
||||
this.props.close()
|
||||
})
|
||||
|
||||
@@ -83,7 +83,7 @@ class InfoTab extends React.Component {
|
||||
>GitHub</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://medium.com/boostnote'
|
||||
<a href='https://boostlog.io/@junp1234'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Blog</a>
|
||||
</li>
|
||||
|
||||
@@ -76,7 +76,8 @@ class UiTab extends React.Component {
|
||||
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
|
||||
switchPreview: this.refs.editorSwitchPreview.value,
|
||||
keyMap: this.refs.editorKeyMap.value,
|
||||
scrollPastEnd: this.refs.scrollPastEnd.checked
|
||||
scrollPastEnd: this.refs.scrollPastEnd.checked,
|
||||
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked
|
||||
},
|
||||
preview: {
|
||||
fontSize: this.refs.previewFontSize.value,
|
||||
@@ -283,6 +284,7 @@ class UiTab extends React.Component {
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
>
|
||||
<option value='BLUR'>When Editor Blurred</option>
|
||||
<option value='DBL_CLICK'>When Editor Blurred, Edit On Double Click</option>
|
||||
<option value='RIGHTCLICK'>On Right Click</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -327,6 +329,17 @@ class UiTab extends React.Component {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
checked={this.state.config.editor.fetchUrlTitle}
|
||||
ref='editorFetchUrlTitle'
|
||||
type='checkbox'
|
||||
/>
|
||||
Bring in web page title when pasting URL on editor
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div styleName='group-header2'>Preview</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build
|
||||
Diese Seite ist auch verfügbar in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
|
||||
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
|
||||
|
||||
## Umgebungen
|
||||
* npm: 4.x
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
# How to debug Boostnote (Electron app)
|
||||
Diese Seite ist auch verfügbar in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/add-german-documents/docs/de/debug.md).
|
||||
|
||||
Boostnote is eine Electron app, somit basiert sie auf Chromium; Entwickler können die `Developer Tools` verwenden, wie Google Chrome.
|
||||
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md).
|
||||
|
||||
|
||||
Boostnote ist eine Electron App und basiert auf Chromium.
|
||||
|
||||
Zum Entwicklen verwendest du am Besten die `Developer Tools` von Google Chrome verwenden. Diese kannst du ganz einfach im unter dem Menüpunkt `View` mit `Toggle Developer Tools` aktivieren:
|
||||
|
||||
Du kannst die `Developer Tools` so einschalten:
|
||||

|
||||
|
||||
Die `Developer Tools` schauen dann ungefähr so aus:
|
||||
Die Anzeige der `Developer Tools` sieht in etwa so aus:
|
||||
|
||||

|
||||
|
||||
Wenn Fehler vorkommen, werden die Fehlermeldungen in der `console` ausgegeben.
|
||||
|
||||
## Debugging
|
||||
Zum Beispiel kannst du mit dem `debugger` Haltepunkte im Code setzen wie hier veranschaulicht:
|
||||
|
||||
Fehlermeldungen werden in der Regel in der `console` ausgegeben, die du über den gleichnamigen Reiter der `Developer Tools` anzeigen lassen kannst. Zum Debuggen kannst du beispielsweise über den `debugger` Haltepunkte im Code setzen.
|
||||
|
||||

|
||||
|
||||
Das ist ledigtlich ein Beispiel, du kannst die Art von Debugging verwenden die für dich am besten ist.
|
||||
Du kannst aber natürlich auch die Art von Debugging verwenden mit der du am besten zurecht kommst.
|
||||
|
||||
## Referenz
|
||||
|
||||
* [Official document of Google Chrome about debugging](https://developer.chrome.com/devtools)
|
||||
|
||||
---
|
||||
|
||||
Special thanks: Translated by [gino909](https://github.com/gino909)
|
||||
Special thanks: Translated by [gino909](https://github.com/gino909), [mdeuerlein](https://github.com/mdeuerlein)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# How to debug Boostnote (Electron app)
|
||||
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/add-german-documents/docs/de/debug.md).
|
||||
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md).
|
||||
|
||||
Boostnote is an Electron app so it's based on Chromium; developers can use `Developer Tools` just like Google Chrome.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Build
|
||||
Cette page est également disponible en [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), et en [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md).
|
||||
Cette page est également disponible en [Angalis](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md), [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md) et en [Allemand](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md)
|
||||
|
||||
## Environnements
|
||||
* npm: 4.x
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Comment débugger Boostnote (Application Electron)
|
||||
Cette page est également disponible en [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), et en [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md)
|
||||
Cette page est également disponible en [Angalis](https://github.com/BoostIO/Boostnote/blob/master/docs/debug.md), [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md) et en [Allemand](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md)
|
||||
|
||||
Boostnote est une application Electron donc basée sur Chromium. Il est possible d'utiliser les `Developer Tools` comme dans Google Chrome.
|
||||
|
||||
@@ -19,4 +19,4 @@ Par exemple, vous pouvez utiliser le `debugger` pour placer un point d'arrêt da
|
||||
C'est une façon comme une autre de faire, vous pouvez trouver une façon de débugger que vous trouverez plus adaptée.
|
||||
|
||||
## Références
|
||||
* [Documentation officiel de Google Chrome sur le debugging](https://developer.chrome.com/devtools)
|
||||
* [Documentation officiel de Google Chrome sur le debugging](https://developer.chrome.com/devtools)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "boost",
|
||||
"productName": "Boostnote",
|
||||
"version": "0.9.0",
|
||||
"version": "0.10.0",
|
||||
"main": "index.js",
|
||||
"description": "Boostnote",
|
||||
"license": "GPL-3.0",
|
||||
|
||||
@@ -26,7 +26,7 @@ Boostnote is an open source project. It's an independent project with its ongoin
|
||||
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
||||
- [Twitter](https://twitter.com/boostnoteapp)
|
||||
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMzAzMjI1MTIyNTQ3LTc2MjNiYWU3NTc1YjZlMTk3NzFmOWE1ZWU1MGRhMzBkMGIwMWFjOWMxMDRiM2I2NzkzYzc4OGZhNmVhZjYzZTM)
|
||||
- [Blog](https://medium.com/boostnote)
|
||||
- [Blog](https://boostlog.io/@junp1234)
|
||||
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user