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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
93
browser/components/MarkdownSplitEditor.js
Normal file
93
browser/components/MarkdownSplitEditor.js
Normal 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)
|
||||
9
browser/components/MarkdownSplitEditor.styl
Normal file
9
browser/components/MarkdownSplitEditor.styl
Normal file
@@ -0,0 +1,9 @@
|
||||
.root
|
||||
width 100%
|
||||
height 100%
|
||||
font-size 30px
|
||||
display flex
|
||||
.codeEditor
|
||||
width 50%
|
||||
.preview
|
||||
width 50%
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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']})
|
||||
|
||||
19
browser/main/Detail/FullscreenButton.js
Normal file
19
browser/main/Detail/FullscreenButton.js
Normal 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)
|
||||
22
browser/main/Detail/FullscreenButton.styl
Normal file
22
browser/main/Detail/FullscreenButton.styl
Normal 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()
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
25
browser/main/Detail/ToggleModeButton.js
Normal file
25
browser/main/Detail/ToggleModeButton.js
Normal 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)
|
||||
61
browser/main/Detail/ToggleModeButton.styl
Normal file
61
browser/main/Detail/ToggleModeButton.styl
Normal 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
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
24
browser/main/SideNav/ListButton.js
Normal file
24
browser/main/SideNav/ListButton.js
Normal 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)
|
||||
19
browser/main/SideNav/PreferenceButton.js
Normal file
19
browser/main/SideNav/PreferenceButton.js
Normal 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)
|
||||
51
browser/main/SideNav/PreferenceButton.styl
Normal file
51
browser/main/SideNav/PreferenceButton.styl
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
59
browser/main/SideNav/SwitchButton.styl
Normal file
59
browser/main/SideNav/SwitchButton.styl
Normal 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%)
|
||||
24
browser/main/SideNav/TagButton.js
Normal file
24
browser/main/SideNav/TagButton.js
Normal 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)
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
63
browser/main/lib/dataApi/exportFolder.js
Normal file
63
browser/main/lib/dataApi/exportFolder.js
Normal 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
|
||||
@@ -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'),
|
||||
|
||||
@@ -76,8 +76,8 @@
|
||||
color #1EC38B
|
||||
.error
|
||||
color red
|
||||
|
||||
|
||||
.warning
|
||||
color #FFA500
|
||||
|
||||
.group-control-leftButton
|
||||
colorDefaultButton()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user