Export images together with document
4
.gitignore
vendored
@@ -8,5 +8,5 @@ node_modules/*
|
|||||||
/compiled
|
/compiled
|
||||||
/secret
|
/secret
|
||||||
*.log
|
*.log
|
||||||
.vscode
|
.idea
|
||||||
.idea
|
.vscode
|
||||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
import 'codemirror-mode-elixir'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
||||||
import { findStorage } from 'browser/lib/findStorage'
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
@@ -249,7 +250,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { className, fontSize } = this.props
|
const { className, fontSize } = this.props
|
||||||
let fontFamily = this.props.className
|
let fontFamily = this.props.fontFamily
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||||
? [fontFamily].concat(defaultEditorFontFamily)
|
? [fontFamily].concat(defaultEditorFontFamily)
|
||||||
: defaultEditorFontFamily
|
: defaultEditorFontFamily
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import React from 'react'
|
|||||||
import markdown from 'browser/lib/markdown'
|
import markdown from 'browser/lib/markdown'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
import 'codemirror-mode-elixir'
|
||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
import Raphael from 'raphael'
|
import Raphael from 'raphael'
|
||||||
import flowchart from 'flowchart'
|
import flowchart from 'flowchart'
|
||||||
import SequenceDiagram from 'js-sequence-diagrams'
|
import SequenceDiagram from 'js-sequence-diagrams'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
import fs from 'fs'
|
||||||
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import mdurl from 'mdurl'
|
import mdurl from 'mdurl'
|
||||||
@@ -115,6 +117,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
||||||
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
|
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
|
||||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||||
|
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||||
|
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||||
|
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
||||||
this.printHandler = () => this.handlePrint()
|
this.printHandler = () => this.handlePrint()
|
||||||
|
|
||||||
this.linkClickHandler = this.handlelinkClick.bind(this)
|
this.linkClickHandler = this.handlelinkClick.bind(this)
|
||||||
@@ -141,10 +146,12 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu (e) {
|
||||||
|
if (!this.props.onContextMenu) return
|
||||||
this.props.onContextMenu(e)
|
this.props.onContextMenu(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
handleMouseDown (e) {
|
||||||
|
if (!this.props.onMouseDown) return
|
||||||
if (e.target != null) {
|
if (e.target != null) {
|
||||||
switch (e.target.tagName) {
|
switch (e.target.tagName) {
|
||||||
case 'A':
|
case 'A':
|
||||||
@@ -156,16 +163,50 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp (e) {
|
||||||
|
if (!this.props.onMouseUp) return
|
||||||
if (e.target != null && e.target.tagName === 'A') {
|
if (e.target != null && e.target.tagName === 'A') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
|
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 () {
|
handlePrint () {
|
||||||
this.refs.root.contentWindow.print()
|
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) {
|
fixDecodedURI (node) {
|
||||||
if (node && node.children.length === 1 && typeof node.children[0] === 'string') {
|
if (node && node.children.length === 1 && typeof node.children[0] === 'string') {
|
||||||
const { innerText, href } = node
|
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('mouseup', this.mouseUpHandler)
|
||||||
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
|
||||||
this.refs.root.contentWindow.document.addEventListener('dragover', 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)
|
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('mouseup', this.mouseUpHandler)
|
||||||
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
|
||||||
this.refs.root.contentWindow.document.removeEventListener('dragover', 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)
|
eventEmitter.off('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
@@ -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
|
margin 15px 0
|
||||||
h1, h2, h3, h4, h5, h6
|
h1, h2, h3, h4, h5, h6
|
||||||
font-weight bold
|
font-weight bold
|
||||||
|
word-wrap break-word
|
||||||
h1
|
h1
|
||||||
font-size 2.55em
|
font-size 2.55em
|
||||||
padding-bottom 0.3em
|
padding-bottom 0.3em
|
||||||
@@ -157,6 +158,7 @@ p
|
|||||||
line-height 1.6em
|
line-height 1.6em
|
||||||
margin 0 0 1em
|
margin 0 0 1em
|
||||||
white-space pre-line
|
white-space pre-line
|
||||||
|
word-wrap break-word
|
||||||
img
|
img
|
||||||
max-width 100%
|
max-width 100%
|
||||||
strong, b
|
strong, b
|
||||||
@@ -334,8 +336,29 @@ body[data-theme="dark"]
|
|||||||
background-color themeDarkBorder
|
background-color themeDarkBorder
|
||||||
color themeDarkText
|
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"]
|
body[data-theme="solarized-dark"]
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
border-color themeDarkBorder
|
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 MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||||
import CodeEditor from 'browser/components/CodeEditor'
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
import 'codemirror-mode-elixir'
|
||||||
import { findStorage } from 'browser/lib/findStorage'
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
import CodeMirror from 'codemirror'
|
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: '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
@@ -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
@@ -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)}
|
onClick={(e) => onClick(e)}
|
||||||
>
|
>
|
||||||
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||||
|
<span styleName='tooltip'>Info</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
.control-infoButton
|
.control-infoButton
|
||||||
top 10px
|
top 10px
|
||||||
topBarButtonRight()
|
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
|
.infoButton
|
||||||
padding 0px
|
padding 0px
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './InfoPanel.styl'
|
import styles from './InfoPanel.styl'
|
||||||
|
|
||||||
const InfoPanel = ({
|
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 className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||||
<div>
|
<div>
|
||||||
@@ -57,17 +57,22 @@ const InfoPanel = ({
|
|||||||
|
|
||||||
<div id='export-wrap'>
|
<div id='export-wrap'>
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
<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>
|
<p>.md</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
<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>
|
<p>.txt</p>
|
||||||
</button>
|
</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)}>
|
<button styleName='export--enable' onClick={(e) => print(e)}>
|
||||||
<i className='fa fa-print fa-fw' />
|
<i className='fa fa-print' />
|
||||||
<p>Print</p>
|
<p>Print</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -82,6 +87,7 @@ InfoPanel.propTypes = {
|
|||||||
createdAt: PropTypes.string.isRequired,
|
createdAt: PropTypes.string.isRequired,
|
||||||
exportAsMd: PropTypes.func.isRequired,
|
exportAsMd: PropTypes.func.isRequired,
|
||||||
exportAsTxt: PropTypes.func.isRequired,
|
exportAsTxt: PropTypes.func.isRequired,
|
||||||
|
exportAsHtml: PropTypes.func.isRequired,
|
||||||
wordCount: PropTypes.number,
|
wordCount: PropTypes.number,
|
||||||
letterCount: PropTypes.number,
|
letterCount: PropTypes.number,
|
||||||
type: PropTypes.string.isRequired,
|
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)
|
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
|
border-radius 2px
|
||||||
|
|
||||||
.count-wrap
|
.count-wrap
|
||||||
display flex
|
display flex
|
||||||
position relative
|
position relative
|
||||||
width 100%
|
width 100%
|
||||||
|
|
||||||
.count-number
|
.count-number
|
||||||
position relative
|
position relative
|
||||||
display block
|
display block
|
||||||
width 50%
|
width 50%
|
||||||
@@ -81,15 +81,15 @@
|
|||||||
margin-bottom 6px
|
margin-bottom 6px
|
||||||
|
|
||||||
.infoPanel-trash
|
.infoPanel-trash
|
||||||
color #EA4447
|
color #EA4447
|
||||||
font-weight 600
|
font-weight 600
|
||||||
font-size 14px
|
font-size 14px
|
||||||
width 70px
|
width 70px
|
||||||
background-color rgba(226,33,113,0.1)
|
background-color rgba(226,33,113,0.1)
|
||||||
border none
|
border none
|
||||||
outline none
|
outline none
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
margin-right 5px
|
margin-right 5px
|
||||||
padding 2px 5px
|
padding 2px 5px
|
||||||
|
|
||||||
[id=export-wrap]
|
[id=export-wrap]
|
||||||
@@ -160,4 +160,44 @@ body[data-theme="dark"]
|
|||||||
p
|
p
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&: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'
|
import styles from './InfoPanel.styl'
|
||||||
|
|
||||||
const InfoPanelTrashed = ({
|
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 className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||||
<div>
|
<div>
|
||||||
@@ -31,17 +31,22 @@ const InfoPanelTrashed = ({
|
|||||||
|
|
||||||
<div id='export-wrap'>
|
<div id='export-wrap'>
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
<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>
|
<p>.md</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
<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>
|
<p>.txt</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
|
||||||
|
<i className='fa fa-html5' />
|
||||||
|
<p>.html</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
<button styleName='export--unable'>
|
<button styleName='export--unable'>
|
||||||
<i className='fa fa-file-pdf-o fa-fw' />
|
<i className='fa fa-file-pdf-o' />
|
||||||
<p>.pdf</p>
|
<p>.pdf</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +59,8 @@ InfoPanelTrashed.propTypes = {
|
|||||||
updatedAt: PropTypes.string.isRequired,
|
updatedAt: PropTypes.string.isRequired,
|
||||||
createdAt: PropTypes.string.isRequired,
|
createdAt: PropTypes.string.isRequired,
|
||||||
exportAsMd: PropTypes.func.isRequired,
|
exportAsMd: PropTypes.func.isRequired,
|
||||||
exportAsTxt: PropTypes.func.isRequired
|
exportAsTxt: PropTypes.func.isRequired,
|
||||||
|
exportAsHtml: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(InfoPanelTrashed, styles)
|
export default CSSModules(InfoPanelTrashed, styles)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import React from 'react'
|
|||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './MarkdownNoteDetail.styl'
|
import styles from './MarkdownNoteDetail.styl'
|
||||||
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||||
|
import MarkdownSplitEditor from 'browser/components/MarkdownSplitEditor'
|
||||||
import TodoListPercentage from 'browser/components/TodoListPercentage'
|
import TodoListPercentage from 'browser/components/TodoListPercentage'
|
||||||
import StarButton from './StarButton'
|
import StarButton from './StarButton'
|
||||||
import TagSelect from './TagSelect'
|
import TagSelect from './TagSelect'
|
||||||
@@ -15,15 +16,17 @@ import StatusBar from '../StatusBar'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import TrashButton from './TrashButton'
|
import TrashButton from './TrashButton'
|
||||||
|
import FullscreenButton from './FullscreenButton'
|
||||||
import PermanentDeleteButton from './PermanentDeleteButton'
|
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||||
import InfoButton from './InfoButton'
|
import InfoButton from './InfoButton'
|
||||||
|
import ToggleModeButton from './ToggleModeButton'
|
||||||
import InfoPanel from './InfoPanel'
|
import InfoPanel from './InfoPanel'
|
||||||
import InfoPanelTrashed from './InfoPanelTrashed'
|
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||||
import { formatDate } from 'browser/lib/date-formatter'
|
import { formatDate } from 'browser/lib/date-formatter'
|
||||||
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||||
import striptags from 'striptags'
|
import striptags from 'striptags'
|
||||||
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
@@ -40,13 +43,12 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
content: ''
|
content: ''
|
||||||
}, props.note),
|
}, props.note),
|
||||||
isLockButtonShown: false,
|
isLockButtonShown: false,
|
||||||
isLocked: false
|
isLocked: false,
|
||||||
|
editorType: props.config.editor.type
|
||||||
}
|
}
|
||||||
this.dispatchTimer = null
|
this.dispatchTimer = null
|
||||||
|
|
||||||
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||||
this.saveAsText = this.handleSaveAsText.bind(this)
|
|
||||||
this.saveAsMd = this.handleSaveAsMd.bind(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
@@ -55,8 +57,6 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
ee.on('export:save-text', this.saveAsText)
|
|
||||||
ee.on('export:save-md', this.saveAsMd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
@@ -77,21 +77,24 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
componentDidUnmount () {
|
componentDidUnmount () {
|
||||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
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
|
const { note } = this.state
|
||||||
|
|
||||||
note.content = this.refs.content.value
|
|
||||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||||
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
|
this.updateNote(note)
|
||||||
note.updatedAt = new Date()
|
}
|
||||||
|
|
||||||
this.setState({
|
handleUpdateContent () {
|
||||||
note
|
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()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -177,28 +180,8 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
ee.emit('export:save-text')
|
ee.emit('export:save-text')
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsDocument (fileType) {
|
exportAsHtml () {
|
||||||
const options = {
|
ee.emit('export:save-html')
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTrashButtonClick (e) {
|
handleTrashButtonClick (e) {
|
||||||
@@ -238,14 +221,6 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
ee.emit('list:next')
|
ee.emit('list:next')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsText () {
|
|
||||||
this.exportAsDocument('txt')
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSaveAsMd () {
|
|
||||||
this.exportAsDocument('md')
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUndoButtonClick (e) {
|
handleUndoButtonClick (e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
@@ -272,7 +247,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getToggleLockButton () {
|
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) {
|
handleDeleteKeyDown (e) {
|
||||||
@@ -301,9 +276,42 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
ee.emit('print')
|
ee.emit('print')
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
handleSwitchMode (type) {
|
||||||
const { data, config, location } = this.props
|
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
|
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 storageKey = note.storage
|
||||||
const folderKey = note.folder
|
const folderKey = note.folder
|
||||||
|
|
||||||
@@ -335,6 +343,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
folderName={currentOption.folder.name}
|
folderName={currentOption.folder.name}
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
|
exportAsHtml={this.exportAsHtml}
|
||||||
exportAsMd={this.exportAsMd}
|
exportAsMd={this.exportAsMd}
|
||||||
exportAsTxt={this.exportAsTxt}
|
exportAsTxt={this.exportAsTxt}
|
||||||
/>
|
/>
|
||||||
@@ -355,21 +364,12 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
<TagSelect
|
<TagSelect
|
||||||
ref='tags'
|
ref='tags'
|
||||||
value={this.state.note.tags}
|
value={this.state.note.tags}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={this.handleUpdateTag.bind(this)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div styleName='mode-tab'>
|
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||||
<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>
|
|
||||||
|
|
||||||
<TodoListPercentage
|
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||||
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<InfoButton
|
<InfoButton
|
||||||
@@ -396,11 +396,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
)
|
)
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
<button styleName='control-fullScreenButton'
|
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
|
||||||
onMouseDown={(e) => this.handleFullScreenButton(e)}
|
|
||||||
>
|
|
||||||
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||||
|
|
||||||
@@ -412,6 +408,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.exportAsMd}
|
exportAsMd={this.exportAsMd}
|
||||||
exportAsTxt={this.exportAsTxt}
|
exportAsTxt={this.exportAsTxt}
|
||||||
|
exportAsHtml={this.exportAsHtml}
|
||||||
wordCount={note.content.split(' ').length}
|
wordCount={note.content.split(' ').length}
|
||||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||||
type={note.type}
|
type={note.type}
|
||||||
@@ -429,15 +426,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||||
|
|
||||||
<div styleName='body'>
|
<div styleName='body'>
|
||||||
<MarkdownEditor
|
{this.renderEditor()}
|
||||||
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}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusBar
|
<StatusBar
|
||||||
|
|||||||
@@ -19,10 +19,6 @@
|
|||||||
top 40px
|
top 40px
|
||||||
position relative
|
position relative
|
||||||
|
|
||||||
.control-fullScreenButton
|
|
||||||
top 80px
|
|
||||||
topBarButtonRight()
|
|
||||||
|
|
||||||
.body
|
.body
|
||||||
absolute left right
|
absolute left right
|
||||||
left 0
|
left 0
|
||||||
@@ -32,28 +28,6 @@
|
|||||||
margin 0 45px
|
margin 0 45px
|
||||||
.body-noteEditor
|
.body-noteEditor
|
||||||
absolute top bottom left right
|
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"]
|
body[data-theme="white"]
|
||||||
.root
|
.root
|
||||||
@@ -75,7 +49,8 @@ body[data-theme="dark"]
|
|||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.root
|
.root
|
||||||
border-left 1px solid $ui-solarized-dark-borderColor
|
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)}
|
onClick={(e) => onClick(e)}
|
||||||
>
|
>
|
||||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||||
|
<span styleName='tooltip'>Permanent Delete</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import dataApi from 'browser/main/lib/dataApi'
|
|||||||
import { hashHistory } from 'react-router'
|
import { hashHistory } from 'react-router'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
import 'codemirror-mode-elixir'
|
||||||
import SnippetTab from 'browser/components/SnippetTab'
|
import SnippetTab from 'browser/components/SnippetTab'
|
||||||
import StatusBar from '../StatusBar'
|
import StatusBar from '../StatusBar'
|
||||||
import context from 'browser/lib/context'
|
import context from 'browser/lib/context'
|
||||||
@@ -380,7 +381,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
handleModeButtonClick (e, index) {
|
handleModeButtonClick (e, index) {
|
||||||
const menu = new Menu()
|
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({
|
menu.append(new MenuItem({
|
||||||
label: mode.name,
|
label: mode.name,
|
||||||
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
||||||
@@ -603,6 +604,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.showWarning}
|
exportAsMd={this.showWarning}
|
||||||
exportAsTxt={this.showWarning}
|
exportAsTxt={this.showWarning}
|
||||||
|
exportAsHtml={this.showWarning}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -634,7 +636,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
isActive={note.isStarred}
|
isActive={note.isStarred}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button styleName='control-fullScreenButton'
|
<button styleName='control-fullScreenButton' title='Fullscreen'
|
||||||
onMouseDown={(e) => this.handleFullScreenButton(e)}>
|
onMouseDown={(e) => this.handleFullScreenButton(e)}>
|
||||||
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
|
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -46,14 +46,14 @@ class StarButton extends React.Component {
|
|||||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
onMouseDown={(e) => this.handleMouseDown(e)}
|
||||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
onMouseUp={(e) => this.handleMouseUp(e)}
|
||||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}>
|
||||||
>
|
|
||||||
<img styleName='icon'
|
<img styleName='icon'
|
||||||
src={this.state.isActive || this.props.isActive
|
src={this.state.isActive || this.props.isActive
|
||||||
? '../resources/icon/icon-starred.svg'
|
? '../resources/icon/icon-starred.svg'
|
||||||
: '../resources/icon/icon-star.svg'
|
: '../resources/icon/icon-star.svg'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<span styleName='tooltip'>Star</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,22 @@
|
|||||||
&:hover
|
&:hover
|
||||||
transition 0.2s
|
transition 0.2s
|
||||||
color alpha($ui-favorite-star-button-color, 0.6)
|
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
|
.root--active
|
||||||
@extend .root
|
@extend .root
|
||||||
|
|||||||
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
@@ -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)}
|
onClick={(e) => onClick(e)}
|
||||||
>
|
>
|
||||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||||
|
<span styleName='tooltip'>Trash</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
.control-trashButton
|
.control-trashButton
|
||||||
top 115px
|
top 115px
|
||||||
topBarButtonRight()
|
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
|
.control-trashButton--in-trash
|
||||||
top 60px
|
top 60px
|
||||||
|
|||||||
@@ -235,18 +235,8 @@ class NoteList extends React.Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { router } = this.context
|
const selectedNoteKeys = [noteHash]
|
||||||
const { location } = this.props
|
this.focusNote(selectedNoteKeys, noteHash)
|
||||||
|
|
||||||
let targetIndex = this.getTargetIndex()
|
|
||||||
|
|
||||||
if (targetIndex < 0) targetIndex = 0
|
|
||||||
|
|
||||||
const selectedNoteKeys = []
|
|
||||||
const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
|
|
||||||
selectedNoteKeys.push(nextNoteKey)
|
|
||||||
|
|
||||||
this.focusNote(selectedNoteKeys, nextNoteKey)
|
|
||||||
|
|
||||||
ee.emit('list:moved')
|
ee.emit('list:moved')
|
||||||
}
|
}
|
||||||
|
|||||||
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
@@ -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
@@ -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
|
.top
|
||||||
padding-bottom 15px
|
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
|
.switch-buttons
|
||||||
background-color transparent
|
background-color transparent
|
||||||
border 0
|
border 0
|
||||||
@@ -31,21 +18,7 @@
|
|||||||
display flex
|
display flex
|
||||||
text-align center
|
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
|
.top-menu-label
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
@@ -109,33 +82,6 @@ body[data-theme="white"]
|
|||||||
background-color #f9f9f9
|
background-color #f9f9f9
|
||||||
color $ui-text-color
|
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"]
|
body[data-theme="dark"]
|
||||||
.root, .root--folded
|
.root, .root--folded
|
||||||
border-right 1px solid $ui-dark-borderColor
|
border-right 1px solid $ui-dark-borderColor
|
||||||
@@ -145,25 +91,6 @@ body[data-theme="dark"]
|
|||||||
.top
|
.top
|
||||||
border-color $ui-dark-borderColor
|
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"]
|
body[data-theme="solarized-dark"]
|
||||||
.root, .root--folded
|
.root, .root--folded
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import dataApi from 'browser/main/lib/dataApi'
|
|||||||
import StorageItemChild from 'browser/components/StorageItem'
|
import StorageItemChild from 'browser/components/StorageItem'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import * as path from 'path'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { Menu, MenuItem, dialog } = remote
|
||||||
@@ -24,18 +25,20 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleHeaderContextMenu (e) {
|
handleHeaderContextMenu (e) {
|
||||||
const menu = new Menu()
|
const menu = Menu.buildFromTemplate([
|
||||||
menu.append(new MenuItem({
|
{
|
||||||
label: 'Add Folder',
|
label: 'Add Folder',
|
||||||
click: (e) => this.handleAddFolderButtonClick(e)
|
click: (e) => this.handleAddFolderButtonClick(e)
|
||||||
}))
|
},
|
||||||
menu.append(new MenuItem({
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
}))
|
},
|
||||||
menu.append(new MenuItem({
|
{
|
||||||
label: 'Unlink Storage',
|
label: 'Unlink Storage',
|
||||||
click: (e) => this.handleUnlinkStorageClick(e)
|
click: (e) => this.handleUnlinkStorageClick(e)
|
||||||
}))
|
}
|
||||||
|
])
|
||||||
|
|
||||||
menu.popup()
|
menu.popup()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,18 +92,36 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFolderButtonContextMenu (e, folder) {
|
handleFolderButtonContextMenu (e, folder) {
|
||||||
const menu = new Menu()
|
const menu = Menu.buildFromTemplate([
|
||||||
menu.append(new MenuItem({
|
{
|
||||||
label: 'Rename Folder',
|
label: 'Rename Folder',
|
||||||
click: (e) => this.handleRenameFolderClick(e, folder)
|
click: (e) => this.handleRenameFolderClick(e, folder)
|
||||||
}))
|
},
|
||||||
menu.append(new MenuItem({
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
}))
|
},
|
||||||
menu.append(new MenuItem({
|
{
|
||||||
label: 'Delete Folder',
|
label: 'Export Folder',
|
||||||
click: (e) => this.handleFolderDeleteClick(e, 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()
|
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) {
|
handleFolderDeleteClick (e, folder) {
|
||||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
|||||||
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
@@ -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 StorageList from 'browser/components/StorageList'
|
||||||
import NavToggleButton from 'browser/components/NavToggleButton'
|
import NavToggleButton from 'browser/components/NavToggleButton'
|
||||||
import EventEmitter from 'browser/main/lib/eventEmitter'
|
import EventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
import PreferenceButton from './PreferenceButton'
|
||||||
|
import ListButton from './ListButton'
|
||||||
|
import TagButton from './TagButton'
|
||||||
|
|
||||||
class SideNav extends React.Component {
|
class SideNav extends React.Component {
|
||||||
// TODO: should not use electron stuff v0.7
|
// TODO: should not use electron stuff v0.7
|
||||||
@@ -162,27 +165,11 @@ class SideNav extends React.Component {
|
|||||||
>
|
>
|
||||||
<div styleName='top'>
|
<div styleName='top'>
|
||||||
<div styleName='switch-buttons'>
|
<div styleName='switch-buttons'>
|
||||||
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={this.handleSwitchFoldersButtonClick.bind(this)}>
|
<ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||||
<img src={isTagActive
|
<TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={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>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button styleName='top-menu-preference'
|
<PreferenceButton onClick={this.handleMenuButtonClick} />
|
||||||
onClick={(e) => this.handleMenuButtonClick(e)}
|
|
||||||
>
|
|
||||||
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{this.SideNavComponent(isFolded, storageList)}
|
{this.SideNavComponent(isFolded, storageList)}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class StatusBar extends React.Component {
|
|||||||
{status.updateReady
|
{status.updateReady
|
||||||
? <button onClick={this.updateApp} styleName='update'>
|
? <button onClick={this.updateApp} styleName='update'>
|
||||||
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
|
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
|
||||||
</button>
|
</button>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const os = require('os')
|
|||||||
let mobileAnalyticsClient
|
let mobileAnalyticsClient
|
||||||
|
|
||||||
AWS.config.region = 'us-east-1'
|
AWS.config.region = 'us-east-1'
|
||||||
if (process.env.NODE_ENV === 'production' && ConfigManager.default.get().amaEnabled) {
|
if (!getSendEventCond()) {
|
||||||
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
|
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
|
||||||
IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx'
|
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 () {
|
function initAwsMobileAnalytics () {
|
||||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
if (getSendEventCond()) return
|
||||||
AWS.config.credentials.get((err) => {
|
AWS.config.credentials.get((err) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
|
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
|
||||||
@@ -46,7 +53,7 @@ function initAwsMobileAnalytics () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function recordDynamicCustomEvent (type, options = {}) {
|
function recordDynamicCustomEvent (type, options = {}) {
|
||||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
if (getSendEventCond()) return
|
||||||
try {
|
try {
|
||||||
mobileAnalyticsClient.recordEvent(type, options)
|
mobileAnalyticsClient.recordEvent(type, options)
|
||||||
} catch (analyticsError) {
|
} catch (analyticsError) {
|
||||||
@@ -57,7 +64,7 @@ function recordDynamicCustomEvent (type, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function recordStaticCustomEvent () {
|
function recordStaticCustomEvent () {
|
||||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
if (getSendEventCond()) return
|
||||||
try {
|
try {
|
||||||
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
|
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
|
||||||
uiColorTheme: ConfigManager.default.get().ui.theme
|
uiColorTheme: ConfigManager.default.get().ui.theme
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ export const DEFAULT_CONFIG = {
|
|||||||
indentType: 'space',
|
indentType: 'space',
|
||||||
indentSize: '2',
|
indentSize: '2',
|
||||||
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
||||||
scrollPastEnd: false
|
scrollPastEnd: false,
|
||||||
|
type: 'SPLIT'
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
fontSize: '14',
|
fontSize: '14',
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ const fs = require('fs')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Export an image
|
* @description Export a file
|
||||||
* @param {String} storagePath
|
* @param {String} storagePath
|
||||||
* @param {String} srcFilename
|
* @param {String} srcFilename
|
||||||
* @param {String} dstPath
|
* @param {String} dstPath
|
||||||
* @param {String} dstFilename if not present, destination filename will be equal to srcFilename
|
* @param {String} dstFilename if not present, destination filename will be equal to srcFilename
|
||||||
* @return {Promise} an image path
|
* @return {Promise} an image path
|
||||||
*/
|
*/
|
||||||
function exportImage (storagePath, srcFilename, dstPath, dstFilename = '') {
|
function exportFile (storagePath, srcFilename, dstPath, dstFilename = '') {
|
||||||
dstFilename = dstFilename || srcFilename
|
dstFilename = dstFilename || srcFilename
|
||||||
|
|
||||||
const src = path.join(storagePath, 'images', srcFilename)
|
const src = path.join(storagePath, 'images', srcFilename)
|
||||||
@@ -18,20 +18,19 @@ function exportImage (storagePath, srcFilename, dstPath, dstFilename = '') {
|
|||||||
dstFilename += path.extname(srcFilename)
|
dstFilename += path.extname(srcFilename)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dstImagesFolder = path.join(dstPath, 'images')
|
const dst = path.join(dstPath, dstFilename)
|
||||||
const dst = path.join(dstImagesFolder, dstFilename)
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
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 input = fs.createReadStream(src)
|
||||||
const output = fs.createWriteStream(dst)
|
const output = fs.createWriteStream(dst)
|
||||||
|
|
||||||
output.on('error', reject)
|
output.on('error', reject)
|
||||||
input.on('error', reject)
|
input.on('error', reject)
|
||||||
input.on('end', resolve)
|
input.on('end', resolve, dst)
|
||||||
input.pipe(output)
|
input.pipe(output)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = exportImage
|
module.exports = exportFile
|
||||||
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'),
|
updateFolder: require('./updateFolder'),
|
||||||
deleteFolder: require('./deleteFolder'),
|
deleteFolder: require('./deleteFolder'),
|
||||||
reorderFolder: require('./reorderFolder'),
|
reorderFolder: require('./reorderFolder'),
|
||||||
|
exportFolder: require('./exportFolder'),
|
||||||
createNote: require('./createNote'),
|
createNote: require('./createNote'),
|
||||||
updateNote: require('./updateNote'),
|
updateNote: require('./updateNote'),
|
||||||
deleteNote: require('./deleteNote'),
|
deleteNote: require('./deleteNote'),
|
||||||
|
|||||||
@@ -76,8 +76,8 @@
|
|||||||
color #1EC38B
|
color #1EC38B
|
||||||
.error
|
.error
|
||||||
color red
|
color red
|
||||||
|
.warning
|
||||||
|
color #FFA500
|
||||||
|
|
||||||
.group-control-leftButton
|
.group-control-leftButton
|
||||||
colorDefaultButton()
|
colorDefaultButton()
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class HotkeyTab extends React.Component {
|
|||||||
message: err.message != null ? err.message : 'Error occurs!'
|
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_DONE', this.handleSettingDone)
|
||||||
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||||
}
|
}
|
||||||
@@ -53,6 +54,7 @@ class HotkeyTab extends React.Component {
|
|||||||
config: newConfig
|
config: newConfig
|
||||||
})
|
})
|
||||||
this.clearMessage()
|
this.clearMessage()
|
||||||
|
this.props.haveToSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHintToggleButtonClick (e) {
|
handleHintToggleButtonClick (e) {
|
||||||
@@ -70,6 +72,15 @@ class HotkeyTab extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
config
|
config
|
||||||
})
|
})
|
||||||
|
if (_.isEqual(this.oldHotkey, config.hotkey)) {
|
||||||
|
this.props.haveToSave()
|
||||||
|
} else {
|
||||||
|
this.props.haveToSave({
|
||||||
|
tab: 'Hotkey',
|
||||||
|
type: 'warning',
|
||||||
|
message: 'You have to save!'
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage () {
|
clearMessage () {
|
||||||
@@ -161,7 +172,8 @@ class HotkeyTab extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HotkeyTab.propTypes = {
|
HotkeyTab.propTypes = {
|
||||||
dispatch: PropTypes.func
|
dispatch: PropTypes.func,
|
||||||
|
haveToSave: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(HotkeyTab, styles)
|
export default CSSModules(HotkeyTab, styles)
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ top-bar--height = 50px
|
|||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
font-size 16px
|
font-size 16px
|
||||||
|
.saving--warning
|
||||||
|
haveToSave()
|
||||||
|
|
||||||
.nav-button--active
|
.nav-button--active
|
||||||
@extend .nav-button
|
@extend .nav-button
|
||||||
@@ -49,6 +51,8 @@ top-bar--height = 50px
|
|||||||
background-color $ui-button--active-backgroundColor
|
background-color $ui-button--active-backgroundColor
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
|
.saving--warning
|
||||||
|
haveToSave()
|
||||||
|
|
||||||
.nav-button-icon
|
.nav-button-icon
|
||||||
display block
|
display block
|
||||||
|
|||||||
@@ -20,3 +20,8 @@ $tab--dark-text-color = #E5E5E5
|
|||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.header
|
.header
|
||||||
color $tab--dark-text-color
|
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 consts from 'browser/lib/consts'
|
||||||
import ReactCodeMirror from 'react-codemirror'
|
import ReactCodeMirror from 'react-codemirror'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
import 'codemirror-mode-elixir'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const ipc = electron.ipcRenderer
|
const ipc = electron.ipcRenderer
|
||||||
|
|
||||||
@@ -93,8 +93,19 @@ class UiTab extends React.Component {
|
|||||||
if (newCodemirrorTheme !== codemirrorTheme) {
|
if (newCodemirrorTheme !== codemirrorTheme) {
|
||||||
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`)
|
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) {
|
handleSaveUIClick (e) {
|
||||||
@@ -111,6 +122,7 @@ class UiTab extends React.Component {
|
|||||||
config: newConfig
|
config: newConfig
|
||||||
})
|
})
|
||||||
this.clearMessage()
|
this.clearMessage()
|
||||||
|
this.props.haveToSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage () {
|
clearMessage () {
|
||||||
@@ -412,7 +424,8 @@ UiTab.propTypes = {
|
|||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
name: PropTypes.string
|
name: PropTypes.string
|
||||||
}),
|
}),
|
||||||
dispatch: PropTypes.func
|
dispatch: PropTypes.func,
|
||||||
|
haveToSave: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(UiTab, styles)
|
export default CSSModules(UiTab, styles)
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ class Preferences extends React.Component {
|
|||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
currentTab: 'STORAGES'
|
currentTab: 'STORAGES',
|
||||||
|
UIAlert: '',
|
||||||
|
HotkeyAlert: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +60,7 @@ class Preferences extends React.Component {
|
|||||||
<HotkeyTab
|
<HotkeyTab
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
config={config}
|
config={config}
|
||||||
|
haveToSave={alert => this.setState({HotkeyAlert: alert})}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'UI':
|
case 'UI':
|
||||||
@@ -65,6 +68,7 @@ class Preferences extends React.Component {
|
|||||||
<UiTab
|
<UiTab
|
||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
config={config}
|
config={config}
|
||||||
|
haveToSave={alert => this.setState({UIAlert: alert})}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
case 'CROWDFUNDING':
|
case 'CROWDFUNDING':
|
||||||
@@ -94,19 +98,26 @@ class Preferences extends React.Component {
|
|||||||
return node.getBoundingClientRect()
|
return node.getBoundingClientRect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
haveToSaveNotif (type, message) {
|
||||||
|
return (
|
||||||
|
<p styleName={`saving--${type}`}>{message}</p>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const content = this.renderContent()
|
const content = this.renderContent()
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{target: 'STORAGES', label: 'Storages'},
|
{target: 'STORAGES', label: 'Storages'},
|
||||||
{target: 'HOTKEY', label: 'Hotkey'},
|
{target: 'HOTKEY', label: 'Hotkey', Hotkey: this.state.HotkeyAlert},
|
||||||
{target: 'UI', label: 'UI'},
|
{target: 'UI', label: 'UI', UI: this.state.UIAlert},
|
||||||
{target: 'INFO', label: 'Community / Info'},
|
{target: 'INFO', label: 'Community / Info'},
|
||||||
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
|
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
|
||||||
]
|
]
|
||||||
|
|
||||||
const navButtons = tabs.map((tab) => {
|
const navButtons = tabs.map((tab) => {
|
||||||
const isActive = this.state.currentTab === tab.target
|
const isActive = this.state.currentTab === tab.target
|
||||||
|
const isUiHotkeyTab = _.isObject(tab[tab.label]) && tab.label === tab[tab.label].tab
|
||||||
return (
|
return (
|
||||||
<button styleName={isActive
|
<button styleName={isActive
|
||||||
? 'nav-button--active'
|
? 'nav-button--active'
|
||||||
@@ -118,6 +129,7 @@ class Preferences extends React.Component {
|
|||||||
<span styleName='nav-button-label'>
|
<span styleName='nav-button-label'>
|
||||||
{tab.label}
|
{tab.label}
|
||||||
</span>
|
</span>
|
||||||
|
{isUiHotkeyTab ? this.haveToSaveNotif(tab[tab.label].type, tab[tab.label].message) : null}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -87,8 +87,13 @@ function data (state = defaultDataMap(), action) {
|
|||||||
state.trashedSet = new Set(state.trashedSet)
|
state.trashedSet = new Set(state.trashedSet)
|
||||||
if (note.isTrashed) {
|
if (note.isTrashed) {
|
||||||
state.trashedSet.add(uniqueKey)
|
state.trashedSet.add(uniqueKey)
|
||||||
|
state.starredSet.delete(uniqueKey)
|
||||||
} else {
|
} else {
|
||||||
state.trashedSet.delete(uniqueKey)
|
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 = new Map(state.storageMap)
|
||||||
state.storageMap.set(action.storage.key, action.storage)
|
state.storageMap.set(action.storage.key, action.storage)
|
||||||
return state
|
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':
|
case 'DELETE_FOLDER':
|
||||||
{
|
{
|
||||||
state = Object.assign({}, state)
|
state = Object.assign({}, state)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
<script src="../node_modules/codemirror/lib/codemirror.js"></script>
|
<script src="../node_modules/codemirror/lib/codemirror.js"></script>
|
||||||
<script src="../node_modules/codemirror/mode/meta.js"></script>
|
<script src="../node_modules/codemirror/mode/meta.js"></script>
|
||||||
|
<script src="../node_modules/codemirror-mode-elixir/dist/elixir.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
|
||||||
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
|
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
|
||||||
|
|||||||
@@ -102,12 +102,11 @@ app.on('ready', function () {
|
|||||||
Menu.setApplicationMenu(menu)
|
Menu.setApplicationMenu(menu)
|
||||||
break
|
break
|
||||||
case 'win32':
|
case 'win32':
|
||||||
/* eslint-disable */
|
require('./finder-window')
|
||||||
finderWindow = require('./finder-window')
|
|
||||||
/* eslint-disable */
|
|
||||||
mainWindow.setMenu(menu)
|
mainWindow.setMenu(menu)
|
||||||
break
|
break
|
||||||
case 'linux':
|
case 'linux':
|
||||||
|
require('./finder-window')
|
||||||
Menu.setApplicationMenu(menu)
|
Menu.setApplicationMenu(menu)
|
||||||
mainWindow.setMenu(menu)
|
mainWindow.setMenu(menu)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,13 @@ const file = {
|
|||||||
mainWindow.webContents.send('list:isMarkdownNote')
|
mainWindow.webContents.send('list:isMarkdownNote')
|
||||||
mainWindow.webContents.send('export:save-md')
|
mainWindow.webContents.send('export:save-md')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'HTML (.html)',
|
||||||
|
click () {
|
||||||
|
mainWindow.webContents.send('list:isMarkdownNote')
|
||||||
|
mainWindow.webContents.send('export:save-html')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
|
|
||||||
<script src="../node_modules/codemirror/lib/codemirror.js"></script>
|
<script src="../node_modules/codemirror/lib/codemirror.js"></script>
|
||||||
<script src="../node_modules/codemirror/mode/meta.js"></script>
|
<script src="../node_modules/codemirror/mode/meta.js"></script>
|
||||||
|
<script src="../node_modules/codemirror-mode-elixir/dist/elixir.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/mode/simple.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/simple.js"></script>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"productName": "Boostnote",
|
"productName": "Boostnote",
|
||||||
"version": "0.8.18",
|
"version": "0.8.19",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"description": "Boostnote",
|
"description": "Boostnote",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
"aws-sdk": "^2.48.0",
|
"aws-sdk": "^2.48.0",
|
||||||
"aws-sdk-mobile-analytics": "^0.9.2",
|
"aws-sdk-mobile-analytics": "^0.9.2",
|
||||||
"codemirror": "^5.19.0",
|
"codemirror": "^5.19.0",
|
||||||
|
"codemirror-mode-elixir": "^1.1.1",
|
||||||
"electron-config": "^0.2.1",
|
"electron-config": "^0.2.1",
|
||||||
"electron-gh-releases": "^2.0.2",
|
"electron-gh-releases": "^2.0.2",
|
||||||
"flowchart.js": "^1.6.5",
|
"flowchart.js": "^1.6.5",
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
New:zap:
|
:mega: Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mobile)!
|
||||||
|
|
||||||
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
|
## Authors & Maintainers
|
||||||
- [Rokt33r](https://github.com/rokt33r)
|
- [Rokt33r](https://github.com/rokt33r)
|
||||||
- [sota1235](https://github.com/sota1235)
|
|
||||||
- [Kohei TAKATA](https://github.com/kohei-takata)
|
- [Kohei TAKATA](https://github.com/kohei-takata)
|
||||||
- [Sosuke](https://github.com/sosukesuzuki)
|
- [Sosuke](https://github.com/sosukesuzuki)
|
||||||
- [Kazz](https://github.com/kazup01)
|
- [Kazz](https://github.com/kazup01)
|
||||||
@@ -29,7 +26,7 @@ Boostnote is an open source project. It's an independent project with its ongoin
|
|||||||
## Community
|
## Community
|
||||||
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
||||||
- [Twitter](https://twitter.com/boostnoteapp)
|
- [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)
|
- [Blog](https://medium.com/boostnote)
|
||||||
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
|
|
||||||
<title>icon-WYSIWYG-off</title>
|
|
||||||
<desc>Created with Sketch.</desc>
|
|
||||||
<defs>
|
|
||||||
<filter x="0.0%" y="0.0%" width="100.0%" height="100.0%" filterUnits="objectBoundingBox" id="filter-1">
|
|
||||||
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
|
||||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowOffsetOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
|
||||||
<feMerge>
|
|
||||||
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
|
||||||
<feMergeNode in="SourceGraphic"></feMergeNode>
|
|
||||||
</feMerge>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<g id="Artboard-4" transform="translate(-1378.000000, -372.000000)" stroke="#1EC38B" stroke-width="1.43999988">
|
|
||||||
<g id="Group" filter="url(#filter-1)" transform="translate(1336.000000, 363.000000)">
|
|
||||||
<g id="icon-WYSIWYG-off" transform="translate(43.000000, 9.714286)">
|
|
||||||
<path d="M12.768,7.79918367 C14.4091141,6.17242331 14.4091141,3.53492363 12.768,1.90816327 C11.1268859,0.281402899 8.46611413,0.281402899 6.825,1.90816327 L2.1,6.59183673 L2.1,12.4897959 L8.05,12.4897959 L12.768,7.79918367 Z" id="Shape"></path>
|
|
||||||
<path d="M9.8,4.85714286 L0,14.5714286" id="Shape"></path>
|
|
||||||
<path d="M10.5,9.71428571 L4.9,9.71428571" id="Shape"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
|
|
||||||
<title>icon-WYSIWYG-on</title>
|
|
||||||
<desc>Created with Sketch.</desc>
|
|
||||||
<defs></defs>
|
|
||||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
|
|
||||||
<g id="Artboard-4" transform="translate(-1254.000000, -293.000000)" stroke="#1EC38B" stroke-width="2">
|
|
||||||
<g id="icon-WYSIWYG-on" transform="translate(1255.000000, 293.000000)">
|
|
||||||
<path d="M18.24,11.24 C20.5844488,8.89555124 20.5844488,5.09444876 18.24,2.75 C15.8955512,0.405551237 12.0944488,0.405551237 9.75,2.75 L3,9.5 L3,18 L11.5,18 L18.24,11.24 Z" id="Shape"></path>
|
|
||||||
<path d="M14,7 L0,21" id="Shape"></path>
|
|
||||||
<path d="M15,14 L7,14" id="Shape"></path>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -3,11 +3,22 @@
|
|||||||
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>icon-edit</title>
|
<title>icon-edit</title>
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
<defs></defs>
|
<defs>
|
||||||
<g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-1464.000000, -336.000000)" stroke-linecap="round" stroke-linejoin="round">
|
<filter x="0.0%" y="0.0%" width="100.0%" height="100.0%" filterUnits="objectBoundingBox" id="filter-1">
|
||||||
<g id="icon-edit" transform="translate(1465.000000, 337.000000)" stroke="#C7C7C7" stroke-width="1.66666667">
|
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||||
<polygon id="Shape" points="9.16666667 0 12.5 3.42857143 3.33333333 12.8571429 0 12.8571429 0 9.42857143"></polygon>
|
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowOffsetOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||||
<path d="M0,17.1428571 L15,17.1428571" id="Shape"></path>
|
<feMerge>
|
||||||
|
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||||
|
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g id="Artboard-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-1478.000000, -370.000000)" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<g id="Group" filter="url(#filter-1)" transform="translate(1336.000000, 363.000000)" stroke="#C7C7C7" stroke-width="1.66666667">
|
||||||
|
<g id="icon-edit" transform="translate(143.000000, 8.000000)">
|
||||||
|
<polygon id="Shape" points="9.16666667 0 12.5 3.42857143 3.33333333 12.8571429 0 12.8571429 0 9.42857143"></polygon>
|
||||||
|
<path d="M0,17.1428571 L15,17.1428571" id="Shape"></path>
|
||||||
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 877 B After Width: | Height: | Size: 1.5 KiB |
15
resources/icon/icon-full.svg
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="14px" height="14px" viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>icon-full</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-1421.000000, -219.000000)" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<g id="icon-full" transform="translate(1422.000000, 220.000000)" stroke="#C7C7C7" stroke-width="1.33333333">
|
||||||
|
<polyline id="Shape" points="8 0 12 0 12 4"></polyline>
|
||||||
|
<polyline id="Shape" points="4 12 0 12 0 8"></polyline>
|
||||||
|
<path d="M12,0 L7.33333333,4.66666667" id="Shape"></path>
|
||||||
|
<path d="M0,12 L4.66666667,7.33333333" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 954 B |
23
resources/icon/icon-mode-markdown-off-active.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>icon-mode-markdown-off</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
<filter x="0.0%" y="0.0%" width="100.0%" height="100.0%" filterUnits="objectBoundingBox" id="filter-1">
|
||||||
|
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||||
|
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowOffsetOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||||
|
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g id="Artboard-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-1478.000000, -372.000000)" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<g id="Group" filter="url(#filter-1)" transform="translate(1336.000000, 363.000000)" stroke="#1EC38B" stroke-width="1.55555556">
|
||||||
|
<g id="icon-mode-markdown-off" transform="translate(143.000000, 10.000000)">
|
||||||
|
<polygon id="Shape" points="10.1111111 0 14 3.88888889 3.88888889 14 0 14 0 10.1111111"></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
23
resources/icon/icon-mode-markdown-off.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>icon-mode-markdown-off</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
<filter x="0.0%" y="0.0%" width="100.0%" height="100.0%" filterUnits="objectBoundingBox" id="filter-1">
|
||||||
|
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||||
|
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowOffsetOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||||
|
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g id="Artboard-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-1478.000000, -372.000000)" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<g id="Group" filter="url(#filter-1)" transform="translate(1336.000000, 363.000000)" stroke="#C7C7C7" stroke-width="1.55555556">
|
||||||
|
<g id="icon-mode-markdown-off" transform="translate(143.000000, 10.000000)">
|
||||||
|
<polygon id="Shape" points="10.1111111 0 14 3.88888889 3.88888889 14 0 14 0 10.1111111"></polygon>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
27
resources/icon/icon-mode-split-on-active.svg
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>icon-mode-split-on</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
<filter x="0.0%" y="0.0%" width="100.0%" height="100.0%" filterUnits="objectBoundingBox" id="filter-1">
|
||||||
|
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||||
|
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowOffsetOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||||
|
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g id="Artboard-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-1378.000000, -372.000000)">
|
||||||
|
<g id="Group" filter="url(#filter-1)" transform="translate(1336.000000, 363.000000)">
|
||||||
|
<g id="icon-mode-split-on" transform="translate(40.000000, 7.000000)">
|
||||||
|
<rect id="Rectangle-7" fill="#D8D8D8" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||||
|
<g id="sidebar" transform="translate(3.000000, 3.000000)" stroke="#1EC38B" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.55555556">
|
||||||
|
<rect id="Rectangle-path" x="0" y="0" width="14" height="14" rx="1.55555556"></rect>
|
||||||
|
<path d="M5.66666667,0 L5.66666667,14" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
27
resources/icon/icon-mode-split-on.svg
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>icon-mode-split-on</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs>
|
||||||
|
<filter x="0.0%" y="0.0%" width="100.0%" height="100.0%" filterUnits="objectBoundingBox" id="filter-1">
|
||||||
|
<feOffset dx="0" dy="0" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
|
||||||
|
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0" type="matrix" in="shadowOffsetOuter1" result="shadowMatrixOuter1"></feColorMatrix>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
|
||||||
|
<feMergeNode in="SourceGraphic"></feMergeNode>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<g id="Artboard-4" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-1378.000000, -372.000000)">
|
||||||
|
<g id="Group" filter="url(#filter-1)" transform="translate(1336.000000, 363.000000)">
|
||||||
|
<g id="icon-mode-split-on" transform="translate(40.000000, 7.000000)">
|
||||||
|
<rect id="Rectangle-7" fill="#D8D8D8" opacity="0" x="0" y="0" width="20" height="20"></rect>
|
||||||
|
<g id="sidebar" transform="translate(3.000000, 3.000000)" stroke="#C7C7C7" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.55555556">
|
||||||
|
<rect id="Rectangle-path" x="0" y="0" width="14" height="14" rx="1.55555556"></rect>
|
||||||
|
<path d="M5.66666667,0 L5.66666667,14" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
13
resources/icon/icon-previewoff-off.svg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>icon-previewoff-off</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-1463.000000, -366.000000)" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<g id="icon-previewoff-off" transform="translate(1464.000000, 367.000000)" stroke="#C7C7C7" stroke-width="1.45454545">
|
||||||
|
<path d="M12.32,12.32 C11.0767927,13.267638 9.56298725,13.7926278 8,13.8181818 C2.90909091,13.8181818 0,8 0,8 C0.904647197,6.31410594 2.15937124,4.84116902 3.68,3.68 M6.47272727,2.35636364 C6.97333258,2.2391851 7.48586478,2.18060999 8,2.18181818 C13.0909091,2.18181818 16,8 16,8 C15.5585306,8.82589626 15.0320403,9.60344443 14.4290909,10.32 M9.54181818,9.54181818 C8.99790377,10.1255365 8.17874751,10.3658158 7.40570499,10.1683943 C6.63266247,9.97097281 6.02902719,9.36733753 5.8316057,8.59429501 C5.6341842,7.82125249 5.87446348,7.00209623 6.45818182,6.45818182" id="Shape"></path>
|
||||||
|
<path d="M0,0 L16,16" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
13
resources/icon/icon-previewoff-on.svg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="18px" height="18px" viewBox="0 0 18 18" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
|
<title>icon-previewoff-on</title>
|
||||||
|
<desc>Created with Sketch.</desc>
|
||||||
|
<defs></defs>
|
||||||
|
<g id="Artboard" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-1438.000000, -366.000000)" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<g id="icon-previewoff-on" transform="translate(1439.000000, 367.000000)" stroke="#1EC38B" stroke-width="1.45454545">
|
||||||
|
<path d="M12.32,12.32 C11.0767927,13.267638 9.56298725,13.7926278 8,13.8181818 C2.90909091,13.8181818 0,8 0,8 C0.904647197,6.31410594 2.15937124,4.84116902 3.68,3.68 M6.47272727,2.35636364 C6.97333258,2.2391851 7.48586478,2.18060999 8,2.18181818 C13.0909091,2.18181818 16,8 16,8 C15.5585306,8.82589626 15.0320403,9.60344443 14.4290909,10.32 M9.54181818,9.54181818 C8.99790377,10.1255365 8.17874751,10.3658158 7.40570499,10.1683943 C6.63266247,9.97097281 6.02902719,9.36733753 5.8316057,8.59429501 C5.6341842,7.82125249 5.87446348,7.00209623 6.45818182,6.45818182" id="Shape"></path>
|
||||||
|
<path d="M0,0 L16,16" id="Shape"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
62
tests/dataApi/exportFolder-test.js
Normal file
@@ -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('<body></body>')
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
})
|
||||||