diff --git a/.gitignore b/.gitignore
index 9f75dd1b..ace5316c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,5 +8,5 @@ node_modules/*
/compiled
/secret
*.log
-.vscode
-.idea
\ No newline at end of file
+.idea
+.vscode
\ No newline at end of file
diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js
index b5e06a66..28fc9b1f 100644
--- a/browser/components/CodeEditor.js
+++ b/browser/components/CodeEditor.js
@@ -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
diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js
index c2693312..c1be9ef1 100755
--- a/browser/components/MarkdownPreview.js
+++ b/browser/components/MarkdownPreview.js
@@ -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)
}
diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js
new file mode 100644
index 00000000..0ea41a44
--- /dev/null
+++ b/browser/components/MarkdownSplitEditor.js
@@ -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 (
+
@@ -57,17 +57,22 @@ const InfoPanel = ({
+
+
@@ -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,
diff --git a/browser/main/Detail/InfoPanel.styl b/browser/main/Detail/InfoPanel.styl
index a115d108..72b07c87 100644
--- a/browser/main/Detail/InfoPanel.styl
+++ b/browser/main/Detail/InfoPanel.styl
@@ -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
\ No newline at end of file
+ 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
diff --git a/browser/main/Detail/InfoPanelTrashed.js b/browser/main/Detail/InfoPanelTrashed.js
index 77ecea22..6e86b884 100644
--- a/browser/main/Detail/InfoPanelTrashed.js
+++ b/browser/main/Detail/InfoPanelTrashed.js
@@ -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
}) => (
@@ -31,17 +31,22 @@ const InfoPanelTrashed = ({
+
+
@@ -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)
diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js
index 956efb63..15c584d4 100755
--- a/browser/main/Detail/MarkdownNoteDetail.js
+++ b/browser/main/Detail/MarkdownNoteDetail.js
@@ -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
+ } else {
+ return
+ }
+ }
+
+ 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 {
this.handleChange(e)}
+ onChange={this.handleUpdateTag.bind(this)}
/>
-
-
-

-
-
-

-
-
+ this.handleSwitchMode(e)} editorType={editorType} />
-
+
this.handleFullScreenButton(e)}
- >
-
-
+ this.handleFullScreenButton(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}
- this.handleChange(e)}
- ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
- />
+ {this.renderEditor()}
onClick(e)}
>
+ Permanent Delete
)
diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js
index bec6573b..218b4f87 100644
--- a/browser/main/Detail/SnippetNoteDetail.js
+++ b/browser/main/Detail/SnippetNoteDetail.js
@@ -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}
/>
@@ -634,7 +636,7 @@ class SnippetNoteDetail extends React.Component {
isActive={note.isStarred}
/>
-
diff --git a/browser/main/Detail/StarButton.js b/browser/main/Detail/StarButton.js
index 0616a1e0..57ba79c8 100644
--- a/browser/main/Detail/StarButton.js
+++ b/browser/main/Detail/StarButton.js
@@ -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}>

+
Star
)
}
diff --git a/browser/main/Detail/StarButton.styl b/browser/main/Detail/StarButton.styl
index 1e5bf239..647f3f23 100644
--- a/browser/main/Detail/StarButton.styl
+++ b/browser/main/Detail/StarButton.styl
@@ -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
diff --git a/browser/main/Detail/ToggleModeButton.js b/browser/main/Detail/ToggleModeButton.js
new file mode 100644
index 00000000..5a78cc51
--- /dev/null
+++ b/browser/main/Detail/ToggleModeButton.js
@@ -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
+}) => (
+
+
onClick('SPLIT')}>
+

+
+
onClick('EDITOR_PREVIEW')}>
+

+
+
Toggle Mode
+
+)
+
+ToggleModeButton.propTypes = {
+ onClick: PropTypes.func.isRequired,
+ editorType: PropTypes.string.Required
+}
+
+export default CSSModules(ToggleModeButton, styles)
diff --git a/browser/main/Detail/ToggleModeButton.styl b/browser/main/Detail/ToggleModeButton.styl
new file mode 100644
index 00000000..b9d3cc6c
--- /dev/null
+++ b/browser/main/Detail/ToggleModeButton.styl
@@ -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
\ No newline at end of file
diff --git a/browser/main/Detail/TrashButton.js b/browser/main/Detail/TrashButton.js
index bfaafe1e..474eb9e5 100644
--- a/browser/main/Detail/TrashButton.js
+++ b/browser/main/Detail/TrashButton.js
@@ -10,6 +10,7 @@ const TrashButton = ({
onClick={(e) => onClick(e)}
>

+
Trash
)
diff --git a/browser/main/Detail/TrashButton.styl b/browser/main/Detail/TrashButton.styl
index 0acd60a5..455d36a6 100644
--- a/browser/main/Detail/TrashButton.styl
+++ b/browser/main/Detail/TrashButton.styl
@@ -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
diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js
index b54b0661..083eb75f 100644
--- a/browser/main/NoteList/index.js
+++ b/browser/main/NoteList/index.js
@@ -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')
}
diff --git a/browser/main/SideNav/ListButton.js b/browser/main/SideNav/ListButton.js
new file mode 100644
index 00000000..1365c4cb
--- /dev/null
+++ b/browser/main/SideNav/ListButton.js
@@ -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
+}) => (
+
+)
+
+ListButton.propTypes = {
+ onClick: PropTypes.func.isRequired,
+ isTagActive: PropTypes.bool.isRequired
+}
+
+export default CSSModules(ListButton, styles)
diff --git a/browser/main/SideNav/PreferenceButton.js b/browser/main/SideNav/PreferenceButton.js
new file mode 100644
index 00000000..9f483a28
--- /dev/null
+++ b/browser/main/SideNav/PreferenceButton.js
@@ -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
+}) => (
+
+)
+
+PreferenceButton.propTypes = {
+ onClick: PropTypes.func.isRequired
+}
+
+export default CSSModules(PreferenceButton, styles)
diff --git a/browser/main/SideNav/PreferenceButton.styl b/browser/main/SideNav/PreferenceButton.styl
new file mode 100644
index 00000000..97a48982
--- /dev/null
+++ b/browser/main/SideNav/PreferenceButton.styl
@@ -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
\ No newline at end of file
diff --git a/browser/main/SideNav/SideNav.styl b/browser/main/SideNav/SideNav.styl
index 474b1af3..a0ffb2e7 100644
--- a/browser/main/SideNav/SideNav.styl
+++ b/browser/main/SideNav/SideNav.styl
@@ -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
diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js
index 4379a76c..bbf87306 100644
--- a/browser/main/SideNav/StorageItem.js
+++ b/browser/main/SideNav/StorageItem.js
@@ -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',
diff --git a/browser/main/SideNav/SwitchButton.styl b/browser/main/SideNav/SwitchButton.styl
new file mode 100644
index 00000000..54e21b03
--- /dev/null
+++ b/browser/main/SideNav/SwitchButton.styl
@@ -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%)
\ No newline at end of file
diff --git a/browser/main/SideNav/TagButton.js b/browser/main/SideNav/TagButton.js
new file mode 100644
index 00000000..87d92c49
--- /dev/null
+++ b/browser/main/SideNav/TagButton.js
@@ -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
+}) => (
+
+)
+
+TagButton.propTypes = {
+ onClick: PropTypes.func.isRequired,
+ isTagActive: PropTypes.bool.isRequired
+}
+
+export default CSSModules(TagButton, styles)
diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js
index 05361ea4..4c162f1e 100644
--- a/browser/main/SideNav/index.js
+++ b/browser/main/SideNav/index.js
@@ -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 {
>
-
-
+
+
-
+
{this.SideNavComponent(isFolded, storageList)}
diff --git a/browser/main/StatusBar/index.js b/browser/main/StatusBar/index.js
index 2106a230..49c1b40c 100644
--- a/browser/main/StatusBar/index.js
+++ b/browser/main/StatusBar/index.js
@@ -63,7 +63,7 @@ class StatusBar extends React.Component {
{status.updateReady
?
+
: null
}
diff --git a/browser/main/lib/AwsMobileAnalyticsConfig.js b/browser/main/lib/AwsMobileAnalyticsConfig.js
index f10d0b66..1ef4f8da 100644
--- a/browser/main/lib/AwsMobileAnalyticsConfig.js
+++ b/browser/main/lib/AwsMobileAnalyticsConfig.js
@@ -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
diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js
index fc9b4ee9..3d70a7a3 100644
--- a/browser/main/lib/ConfigManager.js
+++ b/browser/main/lib/ConfigManager.js
@@ -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',
diff --git a/browser/main/lib/dataApi/exportImage.js b/browser/main/lib/dataApi/copyFile.js
similarity index 66%
rename from browser/main/lib/dataApi/exportImage.js
rename to browser/main/lib/dataApi/copyFile.js
index a1c84390..b46ffd6a 100755
--- a/browser/main/lib/dataApi/exportImage.js
+++ b/browser/main/lib/dataApi/copyFile.js
@@ -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
diff --git a/browser/main/lib/dataApi/exportFolder.js b/browser/main/lib/dataApi/exportFolder.js
new file mode 100644
index 00000000..75dba959
--- /dev/null
+++ b/browser/main/lib/dataApi/exportFolder.js
@@ -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
diff --git a/browser/main/lib/dataApi/index.js b/browser/main/lib/dataApi/index.js
index 768dfe32..311ca2f3 100644
--- a/browser/main/lib/dataApi/index.js
+++ b/browser/main/lib/dataApi/index.js
@@ -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'),
diff --git a/browser/main/modals/PreferencesModal/ConfigTab.styl b/browser/main/modals/PreferencesModal/ConfigTab.styl
index ae54218c..ea26af08 100644
--- a/browser/main/modals/PreferencesModal/ConfigTab.styl
+++ b/browser/main/modals/PreferencesModal/ConfigTab.styl
@@ -76,8 +76,8 @@
color #1EC38B
.error
color red
-
-
+ .warning
+ color #FFA500
.group-control-leftButton
colorDefaultButton()
diff --git a/browser/main/modals/PreferencesModal/HotkeyTab.js b/browser/main/modals/PreferencesModal/HotkeyTab.js
index 9a15e79f..4b4a3060 100644
--- a/browser/main/modals/PreferencesModal/HotkeyTab.js
+++ b/browser/main/modals/PreferencesModal/HotkeyTab.js
@@ -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)
diff --git a/browser/main/modals/PreferencesModal/PreferencesModal.styl b/browser/main/modals/PreferencesModal/PreferencesModal.styl
index 4a280a38..57b5dbad 100644
--- a/browser/main/modals/PreferencesModal/PreferencesModal.styl
+++ b/browser/main/modals/PreferencesModal/PreferencesModal.styl
@@ -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
diff --git a/browser/main/modals/PreferencesModal/Tab.styl b/browser/main/modals/PreferencesModal/Tab.styl
index e5fc48da..a316f3eb 100644
--- a/browser/main/modals/PreferencesModal/Tab.styl
+++ b/browser/main/modals/PreferencesModal/Tab.styl
@@ -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
\ No newline at end of file
diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js
index 42afd8f4..c79329d0 100644
--- a/browser/main/modals/PreferencesModal/UiTab.js
+++ b/browser/main/modals/PreferencesModal/UiTab.js
@@ -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)
diff --git a/browser/main/modals/PreferencesModal/index.js b/browser/main/modals/PreferencesModal/index.js
index 2fff0364..09885e1c 100644
--- a/browser/main/modals/PreferencesModal/index.js
+++ b/browser/main/modals/PreferencesModal/index.js
@@ -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 {
this.setState({HotkeyAlert: alert})}
/>
)
case 'UI':
@@ -65,6 +68,7 @@ class Preferences extends React.Component {
this.setState({UIAlert: alert})}
/>
)
case 'CROWDFUNDING':
@@ -94,19 +98,26 @@ class Preferences extends React.Component {
return node.getBoundingClientRect()
}
+ haveToSaveNotif (type, message) {
+ return (
+ {message}
+ )
+ }
+
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 (
)
})
diff --git a/browser/main/store.js b/browser/main/store.js
index 647d0ac9..36e7850d 100644
--- a/browser/main/store.js
+++ b/browser/main/store.js
@@ -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)
diff --git a/lib/finder.html b/lib/finder.html
index f0ac3df5..c6ad502e 100644
--- a/lib/finder.html
+++ b/lib/finder.html
@@ -28,6 +28,7 @@
+
diff --git a/lib/main-app.js b/lib/main-app.js
index 2c55ba97..02b38a04 100644
--- a/lib/main-app.js
+++ b/lib/main-app.js
@@ -102,12 +102,11 @@ app.on('ready', function () {
Menu.setApplicationMenu(menu)
break
case 'win32':
- /* eslint-disable */
- finderWindow = require('./finder-window')
- /* eslint-disable */
+ require('./finder-window')
mainWindow.setMenu(menu)
break
case 'linux':
+ require('./finder-window')
Menu.setApplicationMenu(menu)
mainWindow.setMenu(menu)
}
diff --git a/lib/main-menu.js b/lib/main-menu.js
index 3555c381..0d49ab86 100644
--- a/lib/main-menu.js
+++ b/lib/main-menu.js
@@ -108,6 +108,13 @@ const file = {
mainWindow.webContents.send('list:isMarkdownNote')
mainWindow.webContents.send('export:save-md')
}
+ },
+ {
+ label: 'HTML (.html)',
+ click () {
+ mainWindow.webContents.send('list:isMarkdownNote')
+ mainWindow.webContents.send('export:save-html')
+ }
}
]
},
diff --git a/lib/main.html b/lib/main.html
index 69a47900..d7936628 100644
--- a/lib/main.html
+++ b/lib/main.html
@@ -69,6 +69,7 @@
+
diff --git a/package.json b/package.json
index f1f8aa0c..f38e8958 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "boost",
"productName": "Boostnote",
- "version": "0.8.18",
+ "version": "0.8.19",
"main": "index.js",
"description": "Boostnote",
"license": "GPL-3.0",
@@ -53,6 +53,7 @@
"aws-sdk": "^2.48.0",
"aws-sdk-mobile-analytics": "^0.9.2",
"codemirror": "^5.19.0",
+ "codemirror-mode-elixir": "^1.1.1",
"electron-config": "^0.2.1",
"electron-gh-releases": "^2.0.2",
"flowchart.js": "^1.6.5",
diff --git a/readme.md b/readme.md
index 9d64bf5d..4b571fa0 100644
--- a/readme.md
+++ b/readme.md
@@ -1,6 +1,4 @@
-New:zap:
-
-Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mobile)!
+:mega: Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mobile)!

@@ -12,7 +10,6 @@ Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mo
## Authors & Maintainers
- [Rokt33r](https://github.com/rokt33r)
-- [sota1235](https://github.com/sota1235)
- [Kohei TAKATA](https://github.com/kohei-takata)
- [Sosuke](https://github.com/sosukesuzuki)
- [Kazz](https://github.com/kazup01)
@@ -29,7 +26,7 @@ Boostnote is an open source project. It's an independent project with its ongoin
## Community
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
- [Twitter](https://twitter.com/boostnoteapp)
-- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMjc2MDM0MDEyODk2LThlZDlhYmYwMjdkMmJjMGM5MGFiMGJmNzk5ZTdhNzFhMmNmMDFlY2M2YTE1MTZkOThiOGZmNTI3YzJiOTBhMTQ)
+- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMjkxMzMwODYxMDI1LTgwZmRiODg0NzA5MWRmOTJjNzBjZjAwMmMyZGQ4Y2RkOGE0MDg0YjcyMjA5OGUzMmZhNmFiNTMzOTlkYWNlMTM)
- [Blog](https://medium.com/boostnote)
- [Reddit](https://www.reddit.com/r/Boostnote/)
diff --git a/resources/icon/icon-WYSIWYG-off.svg b/resources/icon/icon-WYSIWYG-off.svg
deleted file mode 100644
index ffd6088a..00000000
--- a/resources/icon/icon-WYSIWYG-off.svg
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
\ No newline at end of file
diff --git a/resources/icon/icon-WYSIWYG-on.svg b/resources/icon/icon-WYSIWYG-on.svg
deleted file mode 100644
index b8ee9489..00000000
--- a/resources/icon/icon-WYSIWYG-on.svg
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
\ No newline at end of file
diff --git a/resources/icon/icon-edit.svg b/resources/icon/icon-edit.svg
index 3707c6fe..cb7d92cc 100644
--- a/resources/icon/icon-edit.svg
+++ b/resources/icon/icon-edit.svg
@@ -3,11 +3,22 @@
icon-edit
Created with Sketch.
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/resources/icon/icon-full.svg b/resources/icon/icon-full.svg
new file mode 100644
index 00000000..621ebacc
--- /dev/null
+++ b/resources/icon/icon-full.svg
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/resources/icon/icon-mode-markdown-off-active.svg b/resources/icon/icon-mode-markdown-off-active.svg
new file mode 100644
index 00000000..0159836b
--- /dev/null
+++ b/resources/icon/icon-mode-markdown-off-active.svg
@@ -0,0 +1,23 @@
+
+
\ No newline at end of file
diff --git a/resources/icon/icon-mode-markdown-off.svg b/resources/icon/icon-mode-markdown-off.svg
new file mode 100644
index 00000000..7f6a0235
--- /dev/null
+++ b/resources/icon/icon-mode-markdown-off.svg
@@ -0,0 +1,23 @@
+
+
\ No newline at end of file
diff --git a/resources/icon/icon-mode-split-on-active.svg b/resources/icon/icon-mode-split-on-active.svg
new file mode 100644
index 00000000..338d2bd7
--- /dev/null
+++ b/resources/icon/icon-mode-split-on-active.svg
@@ -0,0 +1,27 @@
+
+
\ No newline at end of file
diff --git a/resources/icon/icon-mode-split-on.svg b/resources/icon/icon-mode-split-on.svg
new file mode 100644
index 00000000..c212d7f2
--- /dev/null
+++ b/resources/icon/icon-mode-split-on.svg
@@ -0,0 +1,27 @@
+
+
\ No newline at end of file
diff --git a/resources/icon/icon-previewoff-off.svg b/resources/icon/icon-previewoff-off.svg
new file mode 100644
index 00000000..b0e720e7
--- /dev/null
+++ b/resources/icon/icon-previewoff-off.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/resources/icon/icon-previewoff-on.svg b/resources/icon/icon-previewoff-on.svg
new file mode 100644
index 00000000..8a6c5d7e
--- /dev/null
+++ b/resources/icon/icon-previewoff-on.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/tests/dataApi/exportFolder-test.js b/tests/dataApi/exportFolder-test.js
new file mode 100644
index 00000000..ee6fb898
--- /dev/null
+++ b/tests/dataApi/exportFolder-test.js
@@ -0,0 +1,62 @@
+const test = require('ava')
+const exportFolder = require('browser/main/lib/dataApi/exportFolder')
+const createNote = require('browser/main/lib/dataApi/createNote')
+
+global.document = require('jsdom').jsdom('')
+global.window = document.defaultView
+global.navigator = window.navigator
+
+const Storage = require('dom-storage')
+const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true })
+const path = require('path')
+const TestDummy = require('../fixtures/TestDummy')
+const os = require('os')
+const faker = require('faker')
+const fs = require('fs')
+
+const storagePath = path.join(os.tmpdir(), 'test/export-note')
+
+test.beforeEach((t) => {
+ t.context.storage = TestDummy.dummyStorage(storagePath)
+ localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
+})
+
+test.serial('Export a folder', (t) => {
+ const storageKey = t.context.storage.cache.key
+ const folderKey = t.context.storage.json.folders[0].key
+
+ const input1 = {
+ type: 'MARKDOWN_NOTE',
+ description: '*Some* markdown text',
+ tags: faker.lorem.words().split(' '),
+ folder: folderKey
+ }
+ input1.title = 'input1'
+
+ const input2 = {
+ type: 'SNIPPET_NOTE',
+ description: 'Some normal text',
+ snippets: [{
+ name: faker.system.fileName(),
+ mode: 'text',
+ content: faker.lorem.lines()
+ }],
+ tags: faker.lorem.words().split(' '),
+ folder: folderKey
+ }
+ input2.title = 'input2'
+
+ return createNote(storageKey, input1)
+ .then(function () {
+ return createNote(storageKey, input2)
+ })
+ .then(function () {
+ return exportFolder(storageKey, folderKey, 'md', storagePath)
+ })
+ .then(function assert () {
+ let filePath = path.join(storagePath, 'input1.md')
+ t.true(fs.existsSync(filePath))
+ filePath = path.join(storagePath, 'input2.md')
+ t.false(fs.existsSync(filePath))
+ })
+})