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

Export images together with document

This commit is contained in:
Nikolay Lopin
2018-02-05 02:08:09 +03:00
64 changed files with 1139 additions and 336 deletions

View File

@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import _ from 'lodash'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import path from 'path'
import copyImage from 'browser/main/lib/dataApi/copyImage'
import { findStorage } from 'browser/lib/findStorage'
@@ -249,7 +250,7 @@ export default class CodeEditor extends React.Component {
render () {
const { className, fontSize } = this.props
let fontFamily = this.props.className
let fontFamily = this.props.fontFamily
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
? [fontFamily].concat(defaultEditorFontFamily)
: defaultEditorFontFamily

View File

@@ -3,11 +3,13 @@ import React from 'react'
import markdown from 'browser/lib/markdown'
import _ from 'lodash'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import consts from 'browser/lib/consts'
import Raphael from 'raphael'
import flowchart from 'flowchart'
import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter'
import fs from 'fs'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
@@ -115,6 +117,9 @@ export default class MarkdownPreview extends React.Component {
this.mouseUpHandler = (e) => this.handleMouseUp(e)
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
this.saveAsTextHandler = () => this.handleSaveAsText()
this.saveAsMdHandler = () => this.handleSaveAsMd()
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
this.printHandler = () => this.handlePrint()
this.linkClickHandler = this.handlelinkClick.bind(this)
@@ -141,10 +146,12 @@ export default class MarkdownPreview extends React.Component {
}
handleContextMenu (e) {
if (!this.props.onContextMenu) return
this.props.onContextMenu(e)
}
handleMouseDown (e) {
if (!this.props.onMouseDown) return
if (e.target != null) {
switch (e.target.tagName) {
case 'A':
@@ -156,16 +163,50 @@ export default class MarkdownPreview extends React.Component {
}
handleMouseUp (e) {
if (!this.props.onMouseUp) return
if (e.target != null && e.target.tagName === 'A') {
return null
}
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
}
handleSaveAsText () {
this.exportAsDocument('txt')
}
handleSaveAsMd () {
this.exportAsDocument('md')
}
handleSaveAsHtml () {
this.exportAsDocument('html', (value) => {
return this.refs.root.contentWindow.document.documentElement.outerHTML
})
}
handlePrint () {
this.refs.root.contentWindow.print()
}
exportAsDocument (fileType, formatter) {
const options = {
filters: [
{ name: 'Documents', extensions: [fileType] }
],
properties: ['openFile', 'createDirectory']
}
const value = formatter ? formatter.call(this, this.props.value) : this.props.value
dialog.showSaveDialog(remote.getCurrentWindow(), options,
(filename) => {
if (filename) {
fs.writeFile(filename, value, (err) => {
if (err) throw err
})
}
})
}
fixDecodedURI (node) {
if (node && node.children.length === 1 && typeof node.children[0] === 'string') {
const { innerText, href } = node
@@ -193,6 +234,9 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
eventEmitter.on('print', this.printHandler)
}
@@ -202,6 +246,9 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
eventEmitter.off('print', this.printHandler)
}

View File

@@ -0,0 +1,93 @@
import React from 'react'
import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import { findStorage } from 'browser/lib/findStorage'
import styles from './MarkdownSplitEditor.styl'
import CSSModules from 'browser/lib/CSSModules'
class MarkdownSplitEditor extends React.Component {
constructor (props) {
super(props)
this.value = props.value
this.focus = () => this.refs.code.focus()
this.reload = () => this.refs.code.reload()
}
handleOnChange () {
this.value = this.refs.code.value
this.props.onChange()
}
handleCheckboxClick (e) {
e.preventDefault()
e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/
const checkedMatch = /\[x\]/i
const uncheckedMatch = /\[ \]/
if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value
.split('\n')
const targetLine = lines[lineIndex]
if (targetLine.match(checkedMatch)) {
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
}
if (targetLine.match(uncheckedMatch)) {
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
}
this.refs.code.setValue(lines.join('\n'))
}
}
render () {
const { config, value, storageKey } = this.props
const storage = findStorage(storageKey)
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const previewStyle = {}
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
return (
<div styleName='root'>
<CodeEditor
styleName='codeEditor'
ref='code'
mode='GitHub Flavored Markdown'
value={value}
theme={config.editor.theme}
keyMap={config.editor.keyMap}
fontFamily={config.editor.fontFamily}
fontSize={editorFontSize}
indentType={config.editor.indentType}
indentSize={editorIndentSize}
scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey}
onChange={this.handleOnChange.bind(this)}
/>
<MarkdownPreview
style={previewStyle}
styleName='preview'
theme={config.ui.theme}
keyMap={config.editor.keyMap}
fontSize={config.preview.fontSize}
fontFamily={config.preview.fontFamily}
codeBlockTheme={config.preview.codeBlockTheme}
codeBlockFontFamily={config.editor.fontFamily}
lineNumber={config.preview.lineNumber}
ref='preview'
tabInde='0'
value={value}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
/>
</div>
)
}
}
export default CSSModules(MarkdownSplitEditor, styles)

View File

@@ -0,0 +1,9 @@
.root
width 100%
height 100%
font-size 30px
display flex
.codeEditor
width 50%
.preview
width 50%

View File

@@ -120,6 +120,7 @@ hr
margin 15px 0
h1, h2, h3, h4, h5, h6
font-weight bold
word-wrap break-word
h1
font-size 2.55em
padding-bottom 0.3em
@@ -157,6 +158,7 @@ p
line-height 1.6em
margin 0 0 1em
white-space pre-line
word-wrap break-word
img
max-width 100%
strong, b
@@ -334,8 +336,29 @@ body[data-theme="dark"]
background-color themeDarkBorder
color themeDarkText
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
themeSolarizedDarkTableBorder = themeDarkBorder
body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color
border-color themeDarkBorder
background-color $ui-solarized-dark-noteDetail-backgroundColor
background-color $ui-solarized-dark-noteDetail-backgroundColor
table
thead
tr
background-color themeSolarizedDarkTableHead
th
border-color themeSolarizedDarkTableBorder
&:last-child
border-right solid 1px themeSolarizedDarkTableBorder
tbody
tr:nth-child(2n + 1)
background-color themeSolarizedDarkTableOdd
tr:nth-child(2n)
background-color themeSolarizedDarkTableEven
td
border-color themeSolarizedDarkTableBorder
&:last-child
border-right solid 1px themeSolarizedDarkTableBorder

View File

@@ -5,6 +5,7 @@ import MarkdownPreview from 'browser/components/MarkdownPreview'
import MarkdownEditor from 'browser/components/MarkdownEditor'
import CodeEditor from 'browser/components/CodeEditor'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import { findStorage } from 'browser/lib/findStorage'
const electron = require('electron')

View File

@@ -1,3 +1,5 @@
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})

View File

@@ -0,0 +1,19 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FullscreenButton.styl'
const FullscreenButton = ({
onClick
}) => (
<button styleName='control-fullScreenButton' title='Fullscreen' onMouseDown={(e) => onClick(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>Fullscreen</span>
</button>
)
FullscreenButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(FullscreenButton, styles)

View File

@@ -0,0 +1,22 @@
.control-fullScreenButton
top 80px
topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 26px
right 0
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="dark"]
.control-fullScreenButton
topBarButtonDark()

View File

@@ -10,6 +10,7 @@ const InfoButton = ({
onClick={(e) => onClick(e)}
>
<img className='infoButton' src='../resources/icon/icon-info.svg' />
<span styleName='tooltip'>Info</span>
</button>
)

View File

@@ -1,6 +1,21 @@
.control-infoButton
top 10px
topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 26px
right 0
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.infoButton
padding 0px

View File

@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
const InfoPanel = ({
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type, print
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
}) => (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<div>
@@ -57,17 +57,22 @@ const InfoPanel = ({
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o fa-fw' />
<i className='fa fa-file-code-o' />
<p>.md</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o fa-fw' />
<i className='fa fa-file-text-o' />
<p>.txt</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
<i className='fa fa-html5' />
<p>.html</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e)}>
<i className='fa fa-print fa-fw' />
<i className='fa fa-print' />
<p>Print</p>
</button>
</div>
@@ -82,6 +87,7 @@ InfoPanel.propTypes = {
createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired,
wordCount: PropTypes.number,
letterCount: PropTypes.number,
type: PropTypes.string.isRequired,

View File

@@ -41,12 +41,12 @@
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
border-radius 2px
.count-wrap
.count-wrap
display flex
position relative
width 100%
.count-number
.count-number
position relative
display block
width 50%
@@ -81,15 +81,15 @@
margin-bottom 6px
.infoPanel-trash
color #EA4447
font-weight 600
color #EA4447
font-weight 600
font-size 14px
width 70px
background-color rgba(226,33,113,0.1)
border none
border none
outline none
border-radius 2px
margin-right 5px
border-radius 2px
margin-right 5px
padding 2px 5px
[id=export-wrap]
@@ -160,4 +160,44 @@ body[data-theme="dark"]
p
color $ui-dark-inactive-text-color
&:hover
color $ui-dark-text-color
color $ui-dark-text-color
body[data-theme="solarized-dark"]
.control-infoButton-panel
background-color $ui-solarized-dark-noteList-backgroundColor
.control-infoButton-panel-trash
background-color $ui-solarized-ark-noteList-backgroundColor
.modification-date
color $ui-solarized-ark-text-color
.modification-date-desc
color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-solarized-dark-text-color
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-solarized-ark-text-color
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-solarized-dark-borderColor, 20%)
color $ui-solarized-dark-text-color
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-solarized-dark-borderColor, 20%)
color $ui-solarized-ark-text-color
p
color $ui-dark-inactive-text-color
&:hover
color $ui-solarized-ark-text-color

View File

@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
}) => (
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div>
@@ -31,17 +31,22 @@ const InfoPanelTrashed = ({
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o fa-fw' />
<i className='fa fa-file-code-o' />
<p>.md</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o fa-fw' />
<i className='fa fa-file-text-o' />
<p>.txt</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
<i className='fa fa-html5' />
<p>.html</p>
</button>
<button styleName='export--unable'>
<i className='fa fa-file-pdf-o fa-fw' />
<i className='fa fa-file-pdf-o' />
<p>.pdf</p>
</button>
</div>
@@ -54,7 +59,8 @@ InfoPanelTrashed.propTypes = {
updatedAt: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired
exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired
}
export default CSSModules(InfoPanelTrashed, styles)

View File

@@ -3,6 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './MarkdownNoteDetail.styl'
import MarkdownEditor from 'browser/components/MarkdownEditor'
import MarkdownSplitEditor from 'browser/components/MarkdownSplitEditor'
import TodoListPercentage from 'browser/components/TodoListPercentage'
import StarButton from './StarButton'
import TagSelect from './TagSelect'
@@ -15,15 +16,17 @@ import StatusBar from '../StatusBar'
import _ from 'lodash'
import { findNoteTitle } from 'browser/lib/findNoteTitle'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import ConfigManager from 'browser/main/lib/ConfigManager'
import TrashButton from './TrashButton'
import FullscreenButton from './FullscreenButton'
import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton'
import ToggleModeButton from './ToggleModeButton'
import InfoPanel from './InfoPanel'
import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter'
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags'
import exportNote from 'browser/main/lib/dataApi/exportNote'
const electron = require('electron')
const { remote } = electron
@@ -40,13 +43,12 @@ class MarkdownNoteDetail extends React.Component {
content: ''
}, props.note),
isLockButtonShown: false,
isLocked: false
isLocked: false,
editorType: props.config.editor.type
}
this.dispatchTimer = null
this.toggleLockButton = this.handleToggleLockButton.bind(this)
this.saveAsText = this.handleSaveAsText.bind(this)
this.saveAsMd = this.handleSaveAsMd.bind(this)
}
focus () {
@@ -55,8 +57,6 @@ class MarkdownNoteDetail extends React.Component {
componentDidMount () {
ee.on('topbar:togglelockbutton', this.toggleLockButton)
ee.on('export:save-text', this.saveAsText)
ee.on('export:save-md', this.saveAsMd)
}
componentWillReceiveProps (nextProps) {
@@ -77,21 +77,24 @@ class MarkdownNoteDetail extends React.Component {
componentDidUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
ee.off('export:save-text', this.saveAsTextHandler)
ee.off('export:save-md', this.saveAsMdHandler)
}
handleChange (e) {
handleUpdateTag () {
const { note } = this.state
note.content = this.refs.content.value
if (this.refs.tags) note.tags = this.refs.tags.value
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
note.updatedAt = new Date()
this.updateNote(note)
}
this.setState({
note
}, () => {
handleUpdateContent () {
const { note } = this.state
note.content = this.refs.content.value
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
this.updateNote(note)
}
updateNote (note) {
note.updatedAt = new Date()
this.setState({note}, () => {
this.save()
})
}
@@ -177,28 +180,8 @@ class MarkdownNoteDetail extends React.Component {
ee.emit('export:save-text')
}
exportAsDocument (fileType) {
const options = {
filters: [
{ name: 'Documents', extensions: [fileType] }
],
properties: ['openFile', 'createDirectory']
}
dialog.showSaveDialog(remote.getCurrentWindow(), options,
(filename) => {
if (filename) {
const note = this.props.note
exportNote(note.storage, note.content, filename)
.then((res) => {
dialog.showMessageBox(remote.getCurrentWindow(), {type: 'info', message: `Exported to ${filename}`})
}).catch((err) => {
dialog.showErrorBox('Export error', err ? err.message || err : 'Unexpected error during export')
throw err
})
}
})
exportAsHtml () {
ee.emit('export:save-html')
}
handleTrashButtonClick (e) {
@@ -238,14 +221,6 @@ class MarkdownNoteDetail extends React.Component {
ee.emit('list:next')
}
handleSaveAsText () {
this.exportAsDocument('txt')
}
handleSaveAsMd () {
this.exportAsDocument('md')
}
handleUndoButtonClick (e) {
const { note } = this.state
@@ -272,7 +247,7 @@ class MarkdownNoteDetail extends React.Component {
}
getToggleLockButton () {
return this.state.isLocked ? '../resources/icon/icon-edit-lock.svg' : '../resources/icon/icon-edit.svg'
return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
}
handleDeleteKeyDown (e) {
@@ -301,9 +276,42 @@ class MarkdownNoteDetail extends React.Component {
ee.emit('print')
}
render () {
const { data, config, location } = this.props
handleSwitchMode (type) {
this.setState({ editorType: type }, () => {
const newConfig = Object.assign({}, this.props.config)
newConfig.editor.type = type
ConfigManager.set(newConfig)
})
}
renderEditor () {
const { config, ignorePreviewPointerEvents } = this.props
const { note } = this.state
if (this.state.editorType === 'EDITOR_PREVIEW') {
return <MarkdownEditor
ref='content'
styleName='body-noteEditor'
config={config}
value={note.content}
storageKey={note.storage}
onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/>
} else {
return <MarkdownSplitEditor
ref='content'
config={config}
value={note.content}
storageKey={note.storage}
onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/>
}
}
render () {
const { data, location } = this.props
const { note, editorType } = this.state
const storageKey = note.storage
const folderKey = note.folder
@@ -335,6 +343,7 @@ class MarkdownNoteDetail extends React.Component {
folderName={currentOption.folder.name}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsHtml={this.exportAsHtml}
exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt}
/>
@@ -355,21 +364,12 @@ class MarkdownNoteDetail extends React.Component {
<TagSelect
ref='tags'
value={this.state.note.tags}
onChange={(e) => this.handleChange(e)}
onChange={this.handleUpdateTag.bind(this)}
/>
<div styleName='mode-tab'>
<div styleName='active'>
<img styleName='item-star' src='../resources/icon/icon-WYSIWYG-on.svg' />
</div>
<div>
<img styleName='item-star' src='../resources/icon/icon-code-off.svg' />
</div>
</div>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<TodoListPercentage
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
/>
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div>
<div styleName='info-right'>
<InfoButton
@@ -396,11 +396,7 @@ class MarkdownNoteDetail extends React.Component {
)
})()}
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
</button>
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -412,6 +408,7 @@ class MarkdownNoteDetail extends React.Component {
createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt}
exportAsHtml={this.exportAsHtml}
wordCount={note.content.split(' ').length}
letterCount={note.content.replace(/\r?\n/g, '').length}
type={note.type}
@@ -429,15 +426,7 @@ class MarkdownNoteDetail extends React.Component {
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
<div styleName='body'>
<MarkdownEditor
ref='content'
styleName='body-noteEditor'
config={config}
value={this.state.note.content}
storageKey={this.state.note.storage}
onChange={(e) => this.handleChange(e)}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
/>
{this.renderEditor()}
</div>
<StatusBar

View File

@@ -19,10 +19,6 @@
top 40px
position relative
.control-fullScreenButton
top 80px
topBarButtonRight()
.body
absolute left right
left 0
@@ -32,28 +28,6 @@
margin 0 45px
.body-noteEditor
absolute top bottom left right
.mode-tab
border 1px solid #eee
height 34px
display flex
align-items center
div
width 100px
height 100%
background-color #f9f9f9
display flex
align-items center
justify-content center
cursor pointer
&:first-child
border-right 1px solid #eee
img
transform scale(0.7)
.active
background-color #fff
box-shadow 2px 0px 7px #eee
z-index 1
body[data-theme="white"]
.root
@@ -75,7 +49,8 @@ body[data-theme="dark"]
.control-fullScreenButton
topBarButtonDark()
body[data-theme="solarized-dark"]
.root
border-left 1px solid $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor
background-color $ui-solarized-dark-noteDetail-backgroundColor

View File

@@ -10,6 +10,7 @@ const PermanentDeleteButton = ({
onClick={(e) => onClick(e)}
>
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>Permanent Delete</span>
</button>
)

View File

@@ -11,6 +11,7 @@ import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import SnippetTab from 'browser/components/SnippetTab'
import StatusBar from '../StatusBar'
import context from 'browser/lib/context'
@@ -380,7 +381,7 @@ class SnippetNoteDetail extends React.Component {
handleModeButtonClick (e, index) {
const menu = new Menu()
CodeMirror.modeInfo.forEach((mode) => {
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
menu.append(new MenuItem({
label: mode.name,
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
@@ -603,6 +604,7 @@ class SnippetNoteDetail extends React.Component {
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning}
/>
</div>
</div>
@@ -634,7 +636,7 @@ class SnippetNoteDetail extends React.Component {
isActive={note.isStarred}
/>
<button styleName='control-fullScreenButton'
<button styleName='control-fullScreenButton' title='Fullscreen'
onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
</button>

View File

@@ -46,14 +46,14 @@ class StarButton extends React.Component {
onMouseDown={(e) => this.handleMouseDown(e)}
onMouseUp={(e) => this.handleMouseUp(e)}
onMouseLeave={(e) => this.handleMouseLeave(e)}
onClick={this.props.onClick}
>
onClick={this.props.onClick}>
<img styleName='icon'
src={this.state.isActive || this.props.isActive
? '../resources/icon/icon-starred.svg'
: '../resources/icon/icon-star.svg'
}
/>
<span styleName='tooltip'>Star</span>
</button>
)
}

View File

@@ -4,6 +4,22 @@
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 26px
right 0
width 100%
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.root--active
@extend .root

View File

@@ -0,0 +1,25 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ToggleModeButton.styl'
const ToggleModeButton = ({
onClick, editorType
}) => (
<div styleName='control-toggleModeButton'>
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-split-on.svg' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div>
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : '../resources/icon/icon-mode-markdown-off.svg'} />
</div>
<span styleName='tooltip'>Toggle Mode</span>
</div>
)
ToggleModeButton.propTypes = {
onClick: PropTypes.func.isRequired,
editorType: PropTypes.string.Required
}
export default CSSModules(ToggleModeButton, styles)

View File

@@ -0,0 +1,61 @@
.control-toggleModeButton
border 1px solid #eee
height 34px
display flex
align-items center
div
width 40px
height 100%
background-color #f9f9f9
display flex
align-items center
justify-content center
cursor pointer
&:first-child
border-right 1px solid #eee
.active
background-color #fff
box-shadow 2px 0px 7px #eee
z-index 1
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 47px
right 11px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="dark"]
.control-fullScreenButton
topBarButtonDark()
.control-toggleModeButton
border 1px solid #444444
div
background-color $ui-dark-noteDetail-backgroundColor
&:first-child
border-right 1px solid #444444
.active
background-color #3A404C
box-shadow 2px 0px 7px #444444
body[data-theme="solarized-dark"]
.control-toggleModeButton
border 1px solid #586E75
div
background-color $ui-solarized-dark-noteDetail-backgroundColor
&:first-child
border-right 1px solid #586E75
.active
background-color #002B36
box-shadow 2px 0px 7px #222222

View File

@@ -10,6 +10,7 @@ const TrashButton = ({
onClick={(e) => onClick(e)}
>
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>Trash</span>
</button>
)

View File

@@ -1,6 +1,21 @@
.control-trashButton
top 115px
topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 26px
right 0
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.control-trashButton--in-trash
top 60px

View File

@@ -235,18 +235,8 @@ class NoteList extends React.Component {
return
}
const { router } = this.context
const { location } = this.props
let targetIndex = this.getTargetIndex()
if (targetIndex < 0) targetIndex = 0
const selectedNoteKeys = []
const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
selectedNoteKeys.push(nextNoteKey)
this.focusNote(selectedNoteKeys, nextNoteKey)
const selectedNoteKeys = [noteHash]
this.focusNote(selectedNoteKeys, noteHash)
ee.emit('list:moved')
}

View File

@@ -0,0 +1,24 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
const ListButton = ({
onClick, isTagActive
}) => (
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={onClick}>
<img src={isTagActive
? '../resources/icon/icon-list.svg'
: '../resources/icon/icon-list-active.svg'
}
/>
<span styleName='tooltip'>Notes</span>
</button>
)
ListButton.propTypes = {
onClick: PropTypes.func.isRequired,
isTagActive: PropTypes.bool.isRequired
}
export default CSSModules(ListButton, styles)

View File

@@ -0,0 +1,19 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './PreferenceButton.styl'
const PreferenceButton = ({
onClick
}) => (
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
<span styleName='tooltip'>Preferences</span>
</button>
)
PreferenceButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(PreferenceButton, styles)

View File

@@ -0,0 +1,51 @@
.top-menu-preference
navButtonColor()
position absolute
top 22px
right 10px
width 2em
background-color transparent
&:hover
color $ui-button-default--active-backgroundColor
background-color transparent
.tooltip
opacity 1
&:active, &:active:hover
color $ui-button-default--active-backgroundColor
body[data-theme="white"]
.top-menu-preference
navWhiteButtonColor()
background-color transparent
&:hover
color #0B99F1
background-color transparent
&:active, &:active:hover
color #0B99F1
background-color transparent
body[data-theme="dark"]
.top-menu-preference
navDarkButtonColor()
background-color transparent
&:active
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
.tooltip
tooltip()
position absolute
pointer-events none
top 26px
left -20px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s

View File

@@ -11,19 +11,6 @@
.top
padding-bottom 15px
.top-menu-preference
navButtonColor()
position absolute
top 22px
right 10px
width 2em
background-color transparent
&:hover
color $ui-button-default--active-backgroundColor
background-color transparent
&:active, &:active:hover
color $ui-button-default--active-backgroundColor
.switch-buttons
background-color transparent
border 0
@@ -31,21 +18,7 @@
display flex
text-align center
.non-active-button
color $ui-inactive-text-color
font-size 16px
border 0
background-color transparent
transition 0.2s
display flex
text-align center
margin-right 4px;
&:hover
color alpha(#239F86, 60%)
.active-button
@extend .non-active-button
color $ui-button-default--active-backgroundColor
.top-menu-label
margin-left 5px
@@ -109,33 +82,6 @@ body[data-theme="white"]
background-color #f9f9f9
color $ui-text-color
.top-menu-preference
navWhiteButtonColor()
background-color transparent
&:hover
color #0B99F1
background-color transparent
&:active, &:active:hover
color #0B99F1
background-color transparent
.non-active-button
color $ui-inactive-text-color
&:hover
color alpha(#0B99F1, 60%)
.tag-title
p
color $ui-text-color
.non-active-button
&:hover
color alpha(#0B99F1, 60%)
.active-button
@extend .non-active-button
color #0B99F1
body[data-theme="dark"]
.root, .root--folded
border-right 1px solid $ui-dark-borderColor
@@ -145,25 +91,6 @@ body[data-theme="dark"]
.top
border-color $ui-dark-borderColor
.top-menu-preference
navDarkButtonColor()
background-color transparent
&:active
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
.non-active-button
color alpha($ui-dark-text-color, 60%)
&:hover
color alpha(#0B99F1, 60%)
.tag-title
p
color alpha($ui-dark-text-color, 60%)
body[data-theme="solarized-dark"]
.root, .root--folded
background-color $ui-solarized-dark-backgroundColor

View File

@@ -10,6 +10,7 @@ import dataApi from 'browser/main/lib/dataApi'
import StorageItemChild from 'browser/components/StorageItem'
import eventEmitter from 'browser/main/lib/eventEmitter'
import _ from 'lodash'
import * as path from 'path'
const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote
@@ -24,18 +25,20 @@ class StorageItem extends React.Component {
}
handleHeaderContextMenu (e) {
const menu = new Menu()
menu.append(new MenuItem({
label: 'Add Folder',
click: (e) => this.handleAddFolderButtonClick(e)
}))
menu.append(new MenuItem({
type: 'separator'
}))
menu.append(new MenuItem({
label: 'Unlink Storage',
click: (e) => this.handleUnlinkStorageClick(e)
}))
const menu = Menu.buildFromTemplate([
{
label: 'Add Folder',
click: (e) => this.handleAddFolderButtonClick(e)
},
{
type: 'separator'
},
{
label: 'Unlink Storage',
click: (e) => this.handleUnlinkStorageClick(e)
}
])
menu.popup()
}
@@ -89,18 +92,36 @@ class StorageItem extends React.Component {
}
handleFolderButtonContextMenu (e, folder) {
const menu = new Menu()
menu.append(new MenuItem({
label: 'Rename Folder',
click: (e) => this.handleRenameFolderClick(e, folder)
}))
menu.append(new MenuItem({
type: 'separator'
}))
menu.append(new MenuItem({
label: 'Delete Folder',
click: (e) => this.handleFolderDeleteClick(e, folder)
}))
const menu = Menu.buildFromTemplate([
{
label: 'Rename Folder',
click: (e) => this.handleRenameFolderClick(e, folder)
},
{
type: 'separator'
},
{
label: 'Export Folder',
submenu: [
{
label: 'Export as txt',
click: (e) => this.handleExportFolderClick(e, folder, 'txt')
},
{
label: 'Export as md',
click: (e) => this.handleExportFolderClick(e, folder, 'md')
}
]
},
{
type: 'separator'
},
{
label: 'Delete Folder',
click: (e) => this.handleFolderDeleteClick(e, folder)
}
])
menu.popup()
}
@@ -112,6 +133,31 @@ class StorageItem extends React.Component {
})
}
handleExportFolderClick (e, folder, fileType) {
const options = {
properties: ['openDirectory', 'createDirectory'],
buttonLabel: 'Select directory',
title: 'Select a folder to export the files to',
multiSelections: false
}
dialog.showOpenDialog(remote.getCurrentWindow(), options,
(paths) => {
if (paths && paths.length === 1) {
const { storage, dispatch } = this.props
dataApi
.exportFolder(storage.key, folder.key, fileType, paths[0])
.then((data) => {
dispatch({
type: 'EXPORT_FOLDER',
storage: data.storage,
folderKey: data.folderKey,
fileType: data.fileType
})
})
}
})
}
handleFolderDeleteClick (e, folder) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',

View File

@@ -0,0 +1,59 @@
.non-active-button
color $ui-inactive-text-color
font-size 16px
border 0
background-color transparent
transition 0.2s
display flex
text-align center
margin-right 4px
position relative
&:hover
color alpha(#239F86, 60%)
.tooltip
opacity 1
.active-button
@extend .non-active-button
color $ui-button-default--active-backgroundColor
.tooltip
tooltip()
position absolute
pointer-events none
top 22px
left -2px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="white"]
.non-active-button
color $ui-inactive-text-color
&:hover
color alpha(#0B99F1, 60%)
.tag-title
p
color $ui-text-color
.non-active-button
&:hover
color alpha(#0B99F1, 60%)
.active-button
@extend .non-active-button
color #0B99F1
body[data-theme="dark"]
.non-active-button
color alpha($ui-dark-text-color, 60%)
&:hover
color alpha(#0B99F1, 60%)
.tag-title
p
color alpha($ui-dark-text-color, 60%)

View File

@@ -0,0 +1,24 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
const TagButton = ({
onClick, isTagActive
}) => (
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={onClick}>
<img src={isTagActive
? '../resources/icon/icon-tag-active.svg'
: '../resources/icon/icon-tag.svg'
}
/>
<span styleName='tooltip'>Tags</span>
</button>
)
TagButton.propTypes = {
onClick: PropTypes.func.isRequired,
isTagActive: PropTypes.bool.isRequired
}
export default CSSModules(TagButton, styles)

View File

@@ -11,6 +11,9 @@ import SideNavFilter from 'browser/components/SideNavFilter'
import StorageList from 'browser/components/StorageList'
import NavToggleButton from 'browser/components/NavToggleButton'
import EventEmitter from 'browser/main/lib/eventEmitter'
import PreferenceButton from './PreferenceButton'
import ListButton from './ListButton'
import TagButton from './TagButton'
class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7
@@ -162,27 +165,11 @@ class SideNav extends React.Component {
>
<div styleName='top'>
<div styleName='switch-buttons'>
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={this.handleSwitchFoldersButtonClick.bind(this)}>
<img src={isTagActive
? '../resources/icon/icon-list.svg'
: '../resources/icon/icon-list-active.svg'
}
/>
</button>
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={this.handleSwitchTagsButtonClick.bind(this)}>
<img src={isTagActive
? '../resources/icon/icon-tag-active.svg'
: '../resources/icon/icon-tag.svg'
}
/>
</button>
<ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
<TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
</div>
<div>
<button styleName='top-menu-preference'
onClick={(e) => this.handleMenuButtonClick(e)}
>
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
</button>
<PreferenceButton onClick={this.handleMenuButtonClick} />
</div>
</div>
{this.SideNavComponent(isFolded, storageList)}

View File

@@ -63,7 +63,7 @@ class StatusBar extends React.Component {
{status.updateReady
? <button onClick={this.updateApp} styleName='update'>
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
</button>
</button>
: null
}
</div>

View File

@@ -7,7 +7,7 @@ const os = require('os')
let mobileAnalyticsClient
AWS.config.region = 'us-east-1'
if (process.env.NODE_ENV === 'production' && ConfigManager.default.get().amaEnabled) {
if (!getSendEventCond()) {
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx'
})
@@ -34,8 +34,15 @@ function convertPlatformName (platformName) {
}
}
function getSendEventCond () {
const isDev = process.env.NODE_ENV !== 'production'
const isDisable = !ConfigManager.default.get().amaEnabled
const isOffline = !window.navigator.onLine
return isDev || isDisable || isOffline
}
function initAwsMobileAnalytics () {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
if (getSendEventCond()) return
AWS.config.credentials.get((err) => {
if (!err) {
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
@@ -46,7 +53,7 @@ function initAwsMobileAnalytics () {
}
function recordDynamicCustomEvent (type, options = {}) {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
if (getSendEventCond()) return
try {
mobileAnalyticsClient.recordEvent(type, options)
} catch (analyticsError) {
@@ -57,7 +64,7 @@ function recordDynamicCustomEvent (type, options = {}) {
}
function recordStaticCustomEvent () {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
if (getSendEventCond()) return
try {
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
uiColorTheme: ConfigManager.default.get().ui.theme

View File

@@ -35,7 +35,8 @@ export const DEFAULT_CONFIG = {
indentType: 'space',
indentSize: '2',
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false
scrollPastEnd: false,
type: 'SPLIT'
},
preview: {
fontSize: '14',

View File

@@ -2,14 +2,14 @@ const fs = require('fs')
const path = require('path')
/**
* @description Export an image
* @description Export a file
* @param {String} storagePath
* @param {String} srcFilename
* @param {String} dstPath
* @param {String} dstFilename if not present, destination filename will be equal to srcFilename
* @return {Promise} an image path
*/
function exportImage (storagePath, srcFilename, dstPath, dstFilename = '') {
function exportFile (storagePath, srcFilename, dstPath, dstFilename = '') {
dstFilename = dstFilename || srcFilename
const src = path.join(storagePath, 'images', srcFilename)
@@ -18,20 +18,19 @@ function exportImage (storagePath, srcFilename, dstPath, dstFilename = '') {
dstFilename += path.extname(srcFilename)
}
const dstImagesFolder = path.join(dstPath, 'images')
const dst = path.join(dstImagesFolder, dstFilename)
const dst = path.join(dstPath, dstFilename)
return new Promise((resolve, reject) => {
if (!fs.existsSync(dstImagesFolder)) fs.mkdirSync(dstImagesFolder)
if (!fs.existsSync(dstPath)) fs.mkdirSync(dstPath)
const input = fs.createReadStream(src)
const output = fs.createWriteStream(dst)
output.on('error', reject)
input.on('error', reject)
input.on('end', resolve)
input.on('end', resolve, dst)
input.pipe(output)
})
}
module.exports = exportImage
module.exports = exportFile

View File

@@ -0,0 +1,63 @@
import { findStorage } from 'browser/lib/findStorage'
import resolveStorageData from './resolveStorageData'
import resolveStorageNotes from './resolveStorageNotes'
import * as path from 'path'
import * as fs from 'fs'
/**
* @param {String} storageKey
* @param {String} folderKey
* @param {String} fileType
* @param {String} exportDir
*
* @return {Object}
* ```
* {
* storage: Object,
* folderKey: String,
* fileType: String,
* exportDir: String
* }
* ```
*/
function exportFolder (storageKey, folderKey, fileType, exportDir) {
let targetStorage
try {
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}
return resolveStorageData(targetStorage)
.then(function assignNotes (storage) {
return resolveStorageNotes(storage)
.then((notes) => {
return {
storage,
notes
}
})
})
.then(function exportNotes (data) {
const { storage, notes } = data
notes
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
.forEach(snippet => {
const notePath = path.join(exportDir, `${snippet.title}.${fileType}`)
fs.writeFileSync(notePath, snippet.content, (err) => {
if (err) throw err
})
})
return {
storage,
folderKey,
fileType,
exportDir
}
})
}
module.exports = exportFolder

View File

@@ -7,6 +7,7 @@ const dataApi = {
updateFolder: require('./updateFolder'),
deleteFolder: require('./deleteFolder'),
reorderFolder: require('./reorderFolder'),
exportFolder: require('./exportFolder'),
createNote: require('./createNote'),
updateNote: require('./updateNote'),
deleteNote: require('./deleteNote'),

View File

@@ -76,8 +76,8 @@
color #1EC38B
.error
color red
.warning
color #FFA500
.group-control-leftButton
colorDefaultButton()

View File

@@ -32,6 +32,7 @@ class HotkeyTab extends React.Component {
message: err.message != null ? err.message : 'Error occurs!'
}})
}
this.oldHotkey = this.state.config.hotkey
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
}
@@ -53,6 +54,7 @@ class HotkeyTab extends React.Component {
config: newConfig
})
this.clearMessage()
this.props.haveToSave()
}
handleHintToggleButtonClick (e) {
@@ -70,6 +72,15 @@ class HotkeyTab extends React.Component {
this.setState({
config
})
if (_.isEqual(this.oldHotkey, config.hotkey)) {
this.props.haveToSave()
} else {
this.props.haveToSave({
tab: 'Hotkey',
type: 'warning',
message: 'You have to save!'
})
}
}
clearMessage () {
@@ -161,7 +172,8 @@ class HotkeyTab extends React.Component {
}
HotkeyTab.propTypes = {
dispatch: PropTypes.func
dispatch: PropTypes.func,
haveToSave: PropTypes.func
}
export default CSSModules(HotkeyTab, styles)

View File

@@ -42,6 +42,8 @@ top-bar--height = 50px
background-color transparent
color $ui-text-color
font-size 16px
.saving--warning
haveToSave()
.nav-button--active
@extend .nav-button
@@ -49,6 +51,8 @@ top-bar--height = 50px
background-color $ui-button--active-backgroundColor
&:hover
color $ui-text-color
.saving--warning
haveToSave()
.nav-button-icon
display block

View File

@@ -20,3 +20,8 @@ $tab--dark-text-color = #E5E5E5
body[data-theme="dark"]
.header
color $tab--dark-text-color
haveToSave()
color #FFA500
font-size 10px
margin-top 3px

View File

@@ -7,11 +7,11 @@ import store from 'browser/main/store'
import consts from 'browser/lib/consts'
import ReactCodeMirror from 'react-codemirror'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import _ from 'lodash'
const OSX = global.process.platform === 'darwin'
import _ from 'lodash'
const electron = require('electron')
const ipc = electron.ipcRenderer
@@ -93,8 +93,19 @@ class UiTab extends React.Component {
if (newCodemirrorTheme !== codemirrorTheme) {
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`)
}
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme })
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }, () => {
const {ui, editor, preview} = this.props.config
this.currentConfig = {ui, editor, preview}
if (_.isEqual(this.currentConfig, this.state.config)) {
this.props.haveToSave()
} else {
this.props.haveToSave({
tab: 'UI',
type: 'warning',
message: 'You have to save!'
})
}
})
}
handleSaveUIClick (e) {
@@ -111,6 +122,7 @@ class UiTab extends React.Component {
config: newConfig
})
this.clearMessage()
this.props.haveToSave()
}
clearMessage () {
@@ -412,7 +424,8 @@ UiTab.propTypes = {
user: PropTypes.shape({
name: PropTypes.string
}),
dispatch: PropTypes.func
dispatch: PropTypes.func,
haveToSave: PropTypes.func
}
export default CSSModules(UiTab, styles)

View File

@@ -17,7 +17,9 @@ class Preferences extends React.Component {
super(props)
this.state = {
currentTab: 'STORAGES'
currentTab: 'STORAGES',
UIAlert: '',
HotkeyAlert: ''
}
}
@@ -58,6 +60,7 @@ class Preferences extends React.Component {
<HotkeyTab
dispatch={dispatch}
config={config}
haveToSave={alert => this.setState({HotkeyAlert: alert})}
/>
)
case 'UI':
@@ -65,6 +68,7 @@ class Preferences extends React.Component {
<UiTab
dispatch={dispatch}
config={config}
haveToSave={alert => this.setState({UIAlert: alert})}
/>
)
case 'CROWDFUNDING':
@@ -94,19 +98,26 @@ class Preferences extends React.Component {
return node.getBoundingClientRect()
}
haveToSaveNotif (type, message) {
return (
<p styleName={`saving--${type}`}>{message}</p>
)
}
render () {
const content = this.renderContent()
const tabs = [
{target: 'STORAGES', label: 'Storages'},
{target: 'HOTKEY', label: 'Hotkey'},
{target: 'UI', label: 'UI'},
{target: 'HOTKEY', label: 'Hotkey', Hotkey: this.state.HotkeyAlert},
{target: 'UI', label: 'UI', UI: this.state.UIAlert},
{target: 'INFO', label: 'Community / Info'},
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
]
const navButtons = tabs.map((tab) => {
const isActive = this.state.currentTab === tab.target
const isUiHotkeyTab = _.isObject(tab[tab.label]) && tab.label === tab[tab.label].tab
return (
<button styleName={isActive
? 'nav-button--active'
@@ -118,6 +129,7 @@ class Preferences extends React.Component {
<span styleName='nav-button-label'>
{tab.label}
</span>
{isUiHotkeyTab ? this.haveToSaveNotif(tab[tab.label].type, tab[tab.label].message) : null}
</button>
)
})

View File

@@ -87,8 +87,13 @@ function data (state = defaultDataMap(), action) {
state.trashedSet = new Set(state.trashedSet)
if (note.isTrashed) {
state.trashedSet.add(uniqueKey)
state.starredSet.delete(uniqueKey)
} else {
state.trashedSet.delete(uniqueKey)
if (note.isStarred) {
state.starredSet.add(uniqueKey)
}
}
}
@@ -349,6 +354,13 @@ function data (state = defaultDataMap(), action) {
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
return state
case 'EXPORT_FOLDER':
{
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
}
return state
case 'DELETE_FOLDER':
{
state = Object.assign({}, state)