mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab3ad0eb97 | ||
|
|
2393889028 | ||
|
|
c36ecb1ed1 | ||
|
|
3e9b28ff0c | ||
|
|
d4eec461a9 | ||
|
|
b584f37087 | ||
|
|
5c75644db2 | ||
|
|
72d9e3e00b | ||
|
|
b3d3362f34 | ||
|
|
1cbaf55cee | ||
|
|
7771875d57 | ||
|
|
85937d8e23 | ||
|
|
1480986a3a | ||
|
|
bc968736df | ||
|
|
ad80b8e8b6 | ||
|
|
d44d220c55 | ||
|
|
f6bcef0789 | ||
|
|
a28ec752e8 | ||
|
|
48ea5746d9 | ||
|
|
f473d31970 | ||
|
|
9cd1d4b466 | ||
|
|
4f02065eaf | ||
|
|
4b85e3e8fb | ||
|
|
e4e8c2205e | ||
|
|
18649dd074 | ||
|
|
9f9463f0e8 | ||
|
|
6cf9bc5de2 | ||
|
|
297b4346c5 | ||
|
|
767a203439 | ||
|
|
c564c253b1 | ||
|
|
b4e138e21b | ||
|
|
8ca01921c4 | ||
|
|
c8b97ffde3 | ||
|
|
abc84e5710 | ||
|
|
d732d195f3 | ||
|
|
789975abb0 | ||
|
|
ed1eab7fcc | ||
|
|
29b31c114a | ||
|
|
c8cf353c21 | ||
|
|
e82e2c71f1 | ||
|
|
dd729c406f | ||
|
|
3127e85900 | ||
|
|
304eeb3158 | ||
|
|
bf781c6b50 | ||
|
|
4f7026969f | ||
|
|
16d264ebfa | ||
|
|
04ffbe945b | ||
|
|
974a1a1e7d | ||
|
|
ca2c07244f | ||
|
|
e262d2f19b | ||
|
|
aabfe820ac | ||
|
|
3bba5442bd | ||
|
|
6ce1922fb3 | ||
|
|
9367a404ef | ||
|
|
775200bdd6 | ||
|
|
795fe8ae1d | ||
|
|
6fba62d062 | ||
|
|
5d46adf8fd | ||
|
|
8c8a0ab46d | ||
|
|
959b75bddd | ||
|
|
6a9d4ae0fd | ||
|
|
cb59458c79 | ||
|
|
125a493400 | ||
|
|
83910b55d2 | ||
|
|
f4fd131100 | ||
|
|
d1e5781c24 | ||
|
|
c86e451198 | ||
|
|
b4a7b547f0 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,5 +8,5 @@ node_modules/*
|
||||
/compiled
|
||||
/secret
|
||||
*.log
|
||||
.vscode
|
||||
.idea
|
||||
.vscode
|
||||
@@ -2,6 +2,7 @@ import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
import path from 'path'
|
||||
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
@@ -62,6 +63,7 @@ export default class CodeEditor extends React.Component {
|
||||
scrollPastEnd: this.props.scrollPastEnd,
|
||||
inputStyle: 'textarea',
|
||||
dragDrop: false,
|
||||
autoCloseBrackets: true,
|
||||
extraKeys: {
|
||||
Tab: function (cm) {
|
||||
const cursor = cm.getCursor()
|
||||
@@ -249,7 +251,7 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
render () {
|
||||
const { className, fontSize } = this.props
|
||||
let fontFamily = this.props.className
|
||||
let fontFamily = this.props.fontFamily
|
||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||
? [fontFamily].concat(defaultEditorFontFamily)
|
||||
: defaultEditorFontFamily
|
||||
|
||||
@@ -3,6 +3,7 @@ import React from 'react'
|
||||
import markdown from 'browser/lib/markdown'
|
||||
import _ from 'lodash'
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
import consts from 'browser/lib/consts'
|
||||
import Raphael from 'raphael'
|
||||
import flowchart from 'flowchart'
|
||||
@@ -118,6 +119,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
||||
this.printHandler = () => this.handlePrint()
|
||||
|
||||
this.linkClickHandler = this.handlelinkClick.bind(this)
|
||||
@@ -176,21 +178,29 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.exportAsDocument('md')
|
||||
}
|
||||
|
||||
handleSaveAsHtml () {
|
||||
this.exportAsDocument('html', (value) => {
|
||||
return this.refs.root.contentWindow.document.documentElement.outerHTML
|
||||
})
|
||||
}
|
||||
|
||||
handlePrint () {
|
||||
this.refs.root.contentWindow.print()
|
||||
}
|
||||
|
||||
exportAsDocument (fileType) {
|
||||
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, this.props.value, (err) => {
|
||||
fs.writeFile(filename, value, (err) => {
|
||||
if (err) throw err
|
||||
})
|
||||
}
|
||||
@@ -226,6 +236,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -237,6 +248,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ class MarkdownSplitEditor extends React.Component {
|
||||
render () {
|
||||
const { config, value, storageKey } = this.props
|
||||
const storage = findStorage(storageKey)
|
||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||
const previewStyle = {}
|
||||
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
||||
return (
|
||||
@@ -57,7 +61,9 @@ class MarkdownSplitEditor extends React.Component {
|
||||
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)}
|
||||
|
||||
@@ -336,8 +336,29 @@ body[data-theme="dark"]
|
||||
background-color themeDarkBorder
|
||||
color themeDarkText
|
||||
|
||||
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
|
||||
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
|
||||
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
|
||||
themeSolarizedDarkTableBorder = themeDarkBorder
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
color $ui-solarized-dark-text-color
|
||||
border-color themeDarkBorder
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
table
|
||||
thead
|
||||
tr
|
||||
background-color themeSolarizedDarkTableHead
|
||||
th
|
||||
border-color themeSolarizedDarkTableBorder
|
||||
&:last-child
|
||||
border-right solid 1px themeSolarizedDarkTableBorder
|
||||
tbody
|
||||
tr:nth-child(2n + 1)
|
||||
background-color themeSolarizedDarkTableOdd
|
||||
tr:nth-child(2n)
|
||||
background-color themeSolarizedDarkTableEven
|
||||
td
|
||||
border-color themeSolarizedDarkTableBorder
|
||||
&:last-child
|
||||
border-right solid 1px themeSolarizedDarkTableBorder
|
||||
|
||||
@@ -5,6 +5,7 @@ import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||
import CodeEditor from 'browser/components/CodeEditor'
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
|
||||
const electron = require('electron')
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
|
||||
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
|
||||
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})
|
||||
|
||||
19
browser/main/Detail/FullscreenButton.js
Normal file
19
browser/main/Detail/FullscreenButton.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './FullscreenButton.styl'
|
||||
|
||||
const FullscreenButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-fullScreenButton' title='Fullscreen' onMouseDown={(e) => onClick(e)}>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
||||
<span styleName='tooltip'>Fullscreen</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
FullscreenButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(FullscreenButton, styles)
|
||||
22
browser/main/Detail/FullscreenButton.styl
Normal file
22
browser/main/Detail/FullscreenButton.styl
Normal file
@@ -0,0 +1,22 @@
|
||||
.control-fullScreenButton
|
||||
top 80px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
right 0
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
@@ -10,6 +10,7 @@ const InfoButton = ({
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||
<span styleName='tooltip'>Info</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
.control-infoButton
|
||||
top 10px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
right 0
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.infoButton
|
||||
padding 0px
|
||||
|
||||
@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoPanel.styl'
|
||||
|
||||
const InfoPanel = ({
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type, print
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||
<div>
|
||||
@@ -57,17 +57,22 @@ const InfoPanel = ({
|
||||
|
||||
<div id='export-wrap'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||
<i className='fa fa-file-code-o fa-fw' />
|
||||
<i className='fa fa-file-code-o' />
|
||||
<p>.md</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
||||
<i className='fa fa-file-text-o fa-fw' />
|
||||
<i className='fa fa-file-text-o' />
|
||||
<p>.txt</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
|
||||
<i className='fa fa-html5' />
|
||||
<p>.html</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => print(e)}>
|
||||
<i className='fa fa-print fa-fw' />
|
||||
<i className='fa fa-print' />
|
||||
<p>Print</p>
|
||||
</button>
|
||||
</div>
|
||||
@@ -82,6 +87,7 @@ InfoPanel.propTypes = {
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
exportAsHtml: PropTypes.func.isRequired,
|
||||
wordCount: PropTypes.number,
|
||||
letterCount: PropTypes.number,
|
||||
type: PropTypes.string.isRequired,
|
||||
|
||||
@@ -161,3 +161,43 @@ body[data-theme="dark"]
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-dark-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.control-infoButton-panel
|
||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-solarized-ark-noteList-backgroundColor
|
||||
|
||||
.modification-date
|
||||
color $ui-solarized-ark-text-color
|
||||
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-defaul-count
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-default
|
||||
color $ui-solarized-ark-text-color
|
||||
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-noteLink
|
||||
background-color alpha($ui-solarized-dark-borderColor, 20%)
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
[id=export-wrap]
|
||||
button
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-solarized-dark-borderColor, 20%)
|
||||
color $ui-solarized-ark-text-color
|
||||
p
|
||||
color $ui-dark-inactive-text-color
|
||||
&:hover
|
||||
color $ui-solarized-ark-text-color
|
||||
|
||||
@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoPanel.styl'
|
||||
|
||||
const InfoPanelTrashed = ({
|
||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt
|
||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||
<div>
|
||||
@@ -31,17 +31,22 @@ const InfoPanelTrashed = ({
|
||||
|
||||
<div id='export-wrap'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||
<i className='fa fa-file-code-o fa-fw' />
|
||||
<i className='fa fa-file-code-o' />
|
||||
<p>.md</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
||||
<i className='fa fa-file-text-o fa-fw' />
|
||||
<i className='fa fa-file-text-o' />
|
||||
<p>.txt</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
|
||||
<i className='fa fa-html5' />
|
||||
<p>.html</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--unable'>
|
||||
<i className='fa fa-file-pdf-o fa-fw' />
|
||||
<i className='fa fa-file-pdf-o' />
|
||||
<p>.pdf</p>
|
||||
</button>
|
||||
</div>
|
||||
@@ -54,7 +59,8 @@ InfoPanelTrashed.propTypes = {
|
||||
updatedAt: PropTypes.string.isRequired,
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
exportAsHtml: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(InfoPanelTrashed, styles)
|
||||
|
||||
@@ -18,18 +18,16 @@ import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import TrashButton from './TrashButton'
|
||||
import FullscreenButton from './FullscreenButton'
|
||||
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||
import InfoButton from './InfoButton'
|
||||
import ToggleModeButton from './ToggleModeButton'
|
||||
import InfoPanel from './InfoPanel'
|
||||
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||
import { formatDate } from 'browser/lib/date-formatter'
|
||||
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||
import striptags from 'striptags'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
const { dialog } = remote
|
||||
|
||||
class MarkdownNoteDetail extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
@@ -77,17 +75,22 @@ class MarkdownNoteDetail extends React.Component {
|
||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
handleUpdateTag () {
|
||||
const { note } = this.state
|
||||
|
||||
note.content = this.refs.content.value
|
||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
|
||||
note.updatedAt = new Date()
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
note
|
||||
}, () => {
|
||||
handleUpdateContent () {
|
||||
const { note } = this.state
|
||||
note.content = this.refs.content.value
|
||||
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
|
||||
this.updateNote(note)
|
||||
}
|
||||
|
||||
updateNote (note) {
|
||||
note.updatedAt = new Date()
|
||||
this.setState({note}, () => {
|
||||
this.save()
|
||||
})
|
||||
}
|
||||
@@ -173,19 +176,18 @@ class MarkdownNoteDetail extends React.Component {
|
||||
ee.emit('export:save-text')
|
||||
}
|
||||
|
||||
exportAsHtml () {
|
||||
ee.emit('export:save-html')
|
||||
}
|
||||
|
||||
handleTrashButtonClick (e) {
|
||||
const { note } = this.state
|
||||
const { isTrashed } = note
|
||||
const { confirmDeletion } = this.props
|
||||
|
||||
if (isTrashed) {
|
||||
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Confirm note deletion',
|
||||
detail: 'This will permanently remove this note.',
|
||||
buttons: ['Confirm', 'Cancel']
|
||||
})
|
||||
if (dialogueButtonIndex === 1) return
|
||||
const { note, dispatch } = this.props
|
||||
if (confirmDeletion(true)) {
|
||||
const {note, dispatch} = this.props
|
||||
dataApi
|
||||
.deleteNote(note.storage, note.key)
|
||||
.then((data) => {
|
||||
@@ -198,7 +200,9 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
ee.once('list:moved', dispatchHandler)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (confirmDeletion()) {
|
||||
note.isTrashed = true
|
||||
|
||||
this.setState({
|
||||
@@ -206,9 +210,11 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}, () => {
|
||||
this.save()
|
||||
})
|
||||
}
|
||||
|
||||
ee.emit('list:next')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleUndoButtonClick (e) {
|
||||
const { note } = this.state
|
||||
@@ -283,7 +289,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
/>
|
||||
} else {
|
||||
@@ -292,7 +298,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
config={config}
|
||||
value={note.content}
|
||||
storageKey={note.storage}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onChange={this.handleUpdateContent.bind(this)}
|
||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||
/>
|
||||
}
|
||||
@@ -332,6 +338,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
folderName={currentOption.folder.name}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
/>
|
||||
@@ -352,21 +359,12 @@ class MarkdownNoteDetail extends React.Component {
|
||||
<TagSelect
|
||||
ref='tags'
|
||||
value={this.state.note.tags}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
onChange={this.handleUpdateTag.bind(this)}
|
||||
/>
|
||||
|
||||
<div styleName='mode-tab'>
|
||||
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => this.handleSwitchMode('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={() => this.handleSwitchMode('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>
|
||||
</div>
|
||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||
|
||||
<TodoListPercentage
|
||||
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
||||
/>
|
||||
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<InfoButton
|
||||
@@ -393,11 +391,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
)
|
||||
})()}
|
||||
|
||||
<button styleName='control-fullScreenButton'
|
||||
onMouseDown={(e) => this.handleFullScreenButton(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
||||
</button>
|
||||
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
|
||||
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
|
||||
@@ -409,6 +403,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
wordCount={note.content.split(' ').length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
@@ -447,7 +442,8 @@ MarkdownNoteDetail.propTypes = {
|
||||
style: PropTypes.shape({
|
||||
left: PropTypes.number
|
||||
}),
|
||||
ignorePreviewPointerEvents: PropTypes.bool
|
||||
ignorePreviewPointerEvents: PropTypes.bool,
|
||||
confirmDeletion: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(MarkdownNoteDetail, styles)
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
top 40px
|
||||
position relative
|
||||
|
||||
.control-fullScreenButton
|
||||
top 80px
|
||||
topBarButtonRight()
|
||||
|
||||
.body
|
||||
absolute left right
|
||||
left 0
|
||||
@@ -33,26 +29,6 @@
|
||||
.body-noteEditor
|
||||
absolute top bottom left right
|
||||
|
||||
.mode-tab
|
||||
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
|
||||
|
||||
body[data-theme="white"]
|
||||
.root
|
||||
box-shadow $note-detail-box-shadow
|
||||
@@ -73,27 +49,8 @@ body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
.mode-tab
|
||||
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"]
|
||||
.root
|
||||
border-left 1px solid $ui-solarized-dark-borderColor
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
|
||||
.mode-tab
|
||||
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 PermanentDeleteButton = ({
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||
<span styleName='tooltip'>Permanent Delete</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import dataApi from 'browser/main/lib/dataApi'
|
||||
import { hashHistory } from 'react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
import SnippetTab from 'browser/components/SnippetTab'
|
||||
import StatusBar from '../StatusBar'
|
||||
import context from 'browser/lib/context'
|
||||
@@ -175,16 +176,11 @@ class SnippetNoteDetail extends React.Component {
|
||||
handleTrashButtonClick (e) {
|
||||
const { note } = this.state
|
||||
const { isTrashed } = note
|
||||
const { confirmDeletion } = this.props
|
||||
|
||||
if (isTrashed) {
|
||||
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Confirm note deletion',
|
||||
detail: 'This will permanently remove this note.',
|
||||
buttons: ['Confirm', 'Cancel']
|
||||
})
|
||||
if (dialogueButtonIndex === 1) return
|
||||
const { note, dispatch } = this.props
|
||||
if (confirmDeletion(true)) {
|
||||
const {note, dispatch} = this.props
|
||||
dataApi
|
||||
.deleteNote(note.storage, note.key)
|
||||
.then((data) => {
|
||||
@@ -197,7 +193,9 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
ee.once('list:moved', dispatchHandler)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (confirmDeletion()) {
|
||||
note.isTrashed = true
|
||||
|
||||
this.setState({
|
||||
@@ -205,9 +203,11 @@ class SnippetNoteDetail extends React.Component {
|
||||
}, () => {
|
||||
this.save()
|
||||
})
|
||||
}
|
||||
|
||||
ee.emit('list:next')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleUndoButtonClick (e) {
|
||||
const { note } = this.state
|
||||
@@ -380,7 +380,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
|
||||
handleModeButtonClick (e, index) {
|
||||
const menu = new Menu()
|
||||
CodeMirror.modeInfo.forEach((mode) => {
|
||||
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
|
||||
menu.append(new MenuItem({
|
||||
label: mode.name,
|
||||
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
||||
@@ -603,6 +603,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.showWarning}
|
||||
exportAsTxt={this.showWarning}
|
||||
exportAsHtml={this.showWarning}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -634,7 +635,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
|
||||
<button styleName='control-fullScreenButton'
|
||||
<button styleName='control-fullScreenButton' title='Fullscreen'
|
||||
onMouseDown={(e) => this.handleFullScreenButton(e)}>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
|
||||
</button>
|
||||
@@ -729,7 +730,8 @@ SnippetNoteDetail.propTypes = {
|
||||
style: PropTypes.shape({
|
||||
left: PropTypes.number
|
||||
}),
|
||||
ignorePreviewPointerEvents: PropTypes.bool
|
||||
ignorePreviewPointerEvents: PropTypes.bool,
|
||||
confirmDeletion: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(SnippetNoteDetail, styles)
|
||||
|
||||
@@ -46,14 +46,14 @@ class StarButton extends React.Component {
|
||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
onClick={this.props.onClick}>
|
||||
<img styleName='icon'
|
||||
src={this.state.isActive || this.props.isActive
|
||||
? '../resources/icon/icon-starred.svg'
|
||||
: '../resources/icon/icon-star.svg'
|
||||
}
|
||||
/>
|
||||
<span styleName='tooltip'>Star</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,22 @@
|
||||
&:hover
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
right 0
|
||||
width 100%
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.root--active
|
||||
@extend .root
|
||||
|
||||
25
browser/main/Detail/ToggleModeButton.js
Normal file
25
browser/main/Detail/ToggleModeButton.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ToggleModeButton.styl'
|
||||
|
||||
const ToggleModeButton = ({
|
||||
onClick, editorType
|
||||
}) => (
|
||||
<div styleName='control-toggleModeButton'>
|
||||
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
|
||||
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-split-on.svg' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
||||
</div>
|
||||
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
|
||||
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : '../resources/icon/icon-mode-markdown-off.svg'} />
|
||||
</div>
|
||||
<span styleName='tooltip'>Toggle Mode</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
ToggleModeButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
editorType: PropTypes.string.Required
|
||||
}
|
||||
|
||||
export default CSSModules(ToggleModeButton, styles)
|
||||
61
browser/main/Detail/ToggleModeButton.styl
Normal file
61
browser/main/Detail/ToggleModeButton.styl
Normal file
@@ -0,0 +1,61 @@
|
||||
.control-toggleModeButton
|
||||
border 1px solid #eee
|
||||
height 34px
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
div
|
||||
width 40px
|
||||
height 100%
|
||||
background-color #f9f9f9
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
cursor pointer
|
||||
|
||||
&:first-child
|
||||
border-right 1px solid #eee
|
||||
.active
|
||||
background-color #fff
|
||||
box-shadow 2px 0px 7px #eee
|
||||
z-index 1
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 47px
|
||||
right 11px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-fullScreenButton
|
||||
topBarButtonDark()
|
||||
|
||||
.control-toggleModeButton
|
||||
border 1px solid #444444
|
||||
div
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
&:first-child
|
||||
border-right 1px solid #444444
|
||||
.active
|
||||
background-color #3A404C
|
||||
box-shadow 2px 0px 7px #444444
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.control-toggleModeButton
|
||||
border 1px solid #586E75
|
||||
div
|
||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||
&:first-child
|
||||
border-right 1px solid #586E75
|
||||
.active
|
||||
background-color #002B36
|
||||
box-shadow 2px 0px 7px #222222
|
||||
@@ -10,6 +10,7 @@ const TrashButton = ({
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||
<span styleName='tooltip'>Trash</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
.control-trashButton
|
||||
top 115px
|
||||
topBarButtonRight()
|
||||
&:hover .tooltip
|
||||
opacity 1
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
right 0
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.control-trashButton--in-trash
|
||||
top 60px
|
||||
|
||||
@@ -32,6 +32,26 @@ class Detail extends React.Component {
|
||||
ee.off('detail:delete', this.deleteHandler)
|
||||
}
|
||||
|
||||
confirmDeletion (permanent) {
|
||||
if (this.props.config.ui.confirmDeletion || permanent) {
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
const { dialog } = remote
|
||||
|
||||
const alertConfig = {
|
||||
type: 'warning',
|
||||
message: 'Confirm note deletion',
|
||||
detail: 'This will permanently remove this note.',
|
||||
buttons: ['Confirm', 'Cancel']
|
||||
}
|
||||
|
||||
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), alertConfig)
|
||||
return dialogueButtonIndex === 0
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, data, config } = this.props
|
||||
let note = null
|
||||
@@ -64,6 +84,7 @@ class Detail extends React.Component {
|
||||
<SnippetNoteDetail
|
||||
note={note}
|
||||
config={config}
|
||||
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
|
||||
ref='root'
|
||||
{..._.pick(this.props, [
|
||||
'dispatch',
|
||||
@@ -80,6 +101,7 @@ class Detail extends React.Component {
|
||||
<MarkdownNoteDetail
|
||||
note={note}
|
||||
config={config}
|
||||
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
|
||||
ref='root'
|
||||
{..._.pick(this.props, [
|
||||
'dispatch',
|
||||
|
||||
@@ -10,10 +10,13 @@ import Detail from './Detail'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import _ from 'lodash'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import modal from 'browser/main/lib/modal'
|
||||
import InitModal from 'browser/main/modals/InitModal'
|
||||
import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import { hashHistory } from 'react-router'
|
||||
import store from 'browser/main/store'
|
||||
const path = require('path')
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
|
||||
class Main extends React.Component {
|
||||
|
||||
@@ -48,6 +51,91 @@ class Main extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
init () {
|
||||
dataApi
|
||||
.addStorage({
|
||||
name: 'My Storage',
|
||||
path: path.join(remote.app.getPath('home'), 'Boostnote')
|
||||
})
|
||||
.then((data) => {
|
||||
return data
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.storage.folders[0] != null) {
|
||||
return data
|
||||
} else {
|
||||
return dataApi
|
||||
.createFolder(data.storage.key, {
|
||||
color: '#1278BD',
|
||||
name: 'Default'
|
||||
})
|
||||
.then((_data) => {
|
||||
return {
|
||||
storage: _data.storage,
|
||||
notes: data.notes
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
console.log(data)
|
||||
store.dispatch({
|
||||
type: 'ADD_STORAGE',
|
||||
storage: data.storage,
|
||||
notes: data.notes
|
||||
})
|
||||
|
||||
const defaultSnippetNote = dataApi
|
||||
.createNote(data.storage.key, {
|
||||
type: 'SNIPPET_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
title: 'Snippet note example',
|
||||
description: 'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
|
||||
snippets: [
|
||||
{
|
||||
name: 'example.html',
|
||||
mode: 'html',
|
||||
content: '<html>\n<body>\n<h1 id=\'hello\'>Enjoy Boostnote!</h1>\n</body>\n</html>'
|
||||
},
|
||||
{
|
||||
name: 'example.js',
|
||||
mode: 'javascript',
|
||||
content: 'var boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)'
|
||||
}
|
||||
]
|
||||
})
|
||||
.then((note) => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
})
|
||||
const defaultMarkdownNote = dataApi
|
||||
.createNote(data.storage.key, {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
title: 'Welcome to Boostnote!',
|
||||
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
||||
})
|
||||
.then((note) => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
})
|
||||
|
||||
return Promise.resolve(defaultSnippetNote)
|
||||
.then(defaultMarkdownNote)
|
||||
.then(() => data.storage)
|
||||
})
|
||||
.then((storage) => {
|
||||
hashHistory.push('/storages/' + storage.key)
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch, config } = this.props
|
||||
|
||||
@@ -71,7 +159,7 @@ class Main extends React.Component {
|
||||
})
|
||||
|
||||
if (data.storages.length < 1) {
|
||||
modal.open(InitModal)
|
||||
this.init()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -235,18 +235,8 @@ class NoteList extends React.Component {
|
||||
return
|
||||
}
|
||||
|
||||
const { router } = this.context
|
||||
const { location } = this.props
|
||||
|
||||
let targetIndex = this.getTargetIndex()
|
||||
|
||||
if (targetIndex < 0) targetIndex = 0
|
||||
|
||||
const selectedNoteKeys = []
|
||||
const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
|
||||
selectedNoteKeys.push(nextNoteKey)
|
||||
|
||||
this.focusNote(selectedNoteKeys, nextNoteKey)
|
||||
const selectedNoteKeys = [noteHash]
|
||||
this.focusNote(selectedNoteKeys, noteHash)
|
||||
|
||||
ee.emit('list:moved')
|
||||
}
|
||||
|
||||
24
browser/main/SideNav/ListButton.js
Normal file
24
browser/main/SideNav/ListButton.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './SwitchButton.styl'
|
||||
|
||||
const ListButton = ({
|
||||
onClick, isTagActive
|
||||
}) => (
|
||||
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={onClick}>
|
||||
<img src={isTagActive
|
||||
? '../resources/icon/icon-list.svg'
|
||||
: '../resources/icon/icon-list-active.svg'
|
||||
}
|
||||
/>
|
||||
<span styleName='tooltip'>Notes</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
ListButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
isTagActive: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(ListButton, styles)
|
||||
19
browser/main/SideNav/PreferenceButton.js
Normal file
19
browser/main/SideNav/PreferenceButton.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './PreferenceButton.styl'
|
||||
|
||||
const PreferenceButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
|
||||
<span styleName='tooltip'>Preferences</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
PreferenceButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(PreferenceButton, styles)
|
||||
51
browser/main/SideNav/PreferenceButton.styl
Normal file
51
browser/main/SideNav/PreferenceButton.styl
Normal file
@@ -0,0 +1,51 @@
|
||||
.top-menu-preference
|
||||
navButtonColor()
|
||||
position absolute
|
||||
top 22px
|
||||
right 10px
|
||||
width 2em
|
||||
background-color transparent
|
||||
&:hover
|
||||
color $ui-button-default--active-backgroundColor
|
||||
background-color transparent
|
||||
.tooltip
|
||||
opacity 1
|
||||
&:active, &:active:hover
|
||||
color $ui-button-default--active-backgroundColor
|
||||
|
||||
body[data-theme="white"]
|
||||
.top-menu-preference
|
||||
navWhiteButtonColor()
|
||||
background-color transparent
|
||||
&:hover
|
||||
color #0B99F1
|
||||
background-color transparent
|
||||
&:active, &:active:hover
|
||||
color #0B99F1
|
||||
background-color transparent
|
||||
|
||||
body[data-theme="dark"]
|
||||
.top-menu-preference
|
||||
navDarkButtonColor()
|
||||
background-color transparent
|
||||
&:active
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
background-color transparent
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
background-color transparent
|
||||
|
||||
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
left -20px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
@@ -11,19 +11,6 @@
|
||||
.top
|
||||
padding-bottom 15px
|
||||
|
||||
.top-menu-preference
|
||||
navButtonColor()
|
||||
position absolute
|
||||
top 22px
|
||||
right 10px
|
||||
width 2em
|
||||
background-color transparent
|
||||
&:hover
|
||||
color $ui-button-default--active-backgroundColor
|
||||
background-color transparent
|
||||
&:active, &:active:hover
|
||||
color $ui-button-default--active-backgroundColor
|
||||
|
||||
.switch-buttons
|
||||
background-color transparent
|
||||
border 0
|
||||
@@ -31,21 +18,7 @@
|
||||
display flex
|
||||
text-align center
|
||||
|
||||
.non-active-button
|
||||
color $ui-inactive-text-color
|
||||
font-size 16px
|
||||
border 0
|
||||
background-color transparent
|
||||
transition 0.2s
|
||||
display flex
|
||||
text-align center
|
||||
margin-right 4px;
|
||||
&:hover
|
||||
color alpha(#239F86, 60%)
|
||||
|
||||
.active-button
|
||||
@extend .non-active-button
|
||||
color $ui-button-default--active-backgroundColor
|
||||
|
||||
.top-menu-label
|
||||
margin-left 5px
|
||||
@@ -109,33 +82,6 @@ body[data-theme="white"]
|
||||
background-color #f9f9f9
|
||||
color $ui-text-color
|
||||
|
||||
.top-menu-preference
|
||||
navWhiteButtonColor()
|
||||
background-color transparent
|
||||
&:hover
|
||||
color #0B99F1
|
||||
background-color transparent
|
||||
&:active, &:active:hover
|
||||
color #0B99F1
|
||||
background-color transparent
|
||||
|
||||
.non-active-button
|
||||
color $ui-inactive-text-color
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.tag-title
|
||||
p
|
||||
color $ui-text-color
|
||||
|
||||
.non-active-button
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.active-button
|
||||
@extend .non-active-button
|
||||
color #0B99F1
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root, .root--folded
|
||||
border-right 1px solid $ui-dark-borderColor
|
||||
@@ -145,25 +91,6 @@ body[data-theme="dark"]
|
||||
.top
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.top-menu-preference
|
||||
navDarkButtonColor()
|
||||
background-color transparent
|
||||
&:active
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
background-color transparent
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
background-color transparent
|
||||
|
||||
.non-active-button
|
||||
color alpha($ui-dark-text-color, 60%)
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.tag-title
|
||||
p
|
||||
color alpha($ui-dark-text-color, 60%)
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root, .root--folded
|
||||
background-color $ui-solarized-dark-backgroundColor
|
||||
|
||||
@@ -10,6 +10,7 @@ import dataApi from 'browser/main/lib/dataApi'
|
||||
import StorageItemChild from 'browser/components/StorageItem'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import _ from 'lodash'
|
||||
import * as path from 'path'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { Menu, MenuItem, dialog } = remote
|
||||
@@ -24,18 +25,20 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleHeaderContextMenu (e) {
|
||||
const menu = new Menu()
|
||||
menu.append(new MenuItem({
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Add Folder',
|
||||
click: (e) => this.handleAddFolderButtonClick(e)
|
||||
}))
|
||||
menu.append(new MenuItem({
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
}))
|
||||
menu.append(new MenuItem({
|
||||
},
|
||||
{
|
||||
label: 'Unlink Storage',
|
||||
click: (e) => this.handleUnlinkStorageClick(e)
|
||||
}))
|
||||
}
|
||||
])
|
||||
|
||||
menu.popup()
|
||||
}
|
||||
|
||||
@@ -89,18 +92,36 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleFolderButtonContextMenu (e, folder) {
|
||||
const menu = new Menu()
|
||||
menu.append(new MenuItem({
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Rename Folder',
|
||||
click: (e) => this.handleRenameFolderClick(e, folder)
|
||||
}))
|
||||
menu.append(new MenuItem({
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
}))
|
||||
menu.append(new MenuItem({
|
||||
},
|
||||
{
|
||||
label: 'Export Folder',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Export as txt',
|
||||
click: (e) => this.handleExportFolderClick(e, folder, 'txt')
|
||||
},
|
||||
{
|
||||
label: 'Export as md',
|
||||
click: (e) => this.handleExportFolderClick(e, folder, 'md')
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Delete Folder',
|
||||
click: (e) => this.handleFolderDeleteClick(e, folder)
|
||||
}))
|
||||
}
|
||||
])
|
||||
|
||||
menu.popup()
|
||||
}
|
||||
|
||||
@@ -112,6 +133,31 @@ class StorageItem extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleExportFolderClick (e, folder, fileType) {
|
||||
const options = {
|
||||
properties: ['openDirectory', 'createDirectory'],
|
||||
buttonLabel: 'Select directory',
|
||||
title: 'Select a folder to export the files to',
|
||||
multiSelections: false
|
||||
}
|
||||
dialog.showOpenDialog(remote.getCurrentWindow(), options,
|
||||
(paths) => {
|
||||
if (paths && paths.length === 1) {
|
||||
const { storage, dispatch } = this.props
|
||||
dataApi
|
||||
.exportFolder(storage.key, folder.key, fileType, paths[0])
|
||||
.then((data) => {
|
||||
dispatch({
|
||||
type: 'EXPORT_FOLDER',
|
||||
storage: data.storage,
|
||||
folderKey: data.folderKey,
|
||||
fileType: data.fileType
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleFolderDeleteClick (e, folder) {
|
||||
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
|
||||
59
browser/main/SideNav/SwitchButton.styl
Normal file
59
browser/main/SideNav/SwitchButton.styl
Normal file
@@ -0,0 +1,59 @@
|
||||
.non-active-button
|
||||
color $ui-inactive-text-color
|
||||
font-size 16px
|
||||
border 0
|
||||
background-color transparent
|
||||
transition 0.2s
|
||||
display flex
|
||||
text-align center
|
||||
margin-right 4px
|
||||
position relative
|
||||
&:hover
|
||||
color alpha(#239F86, 60%)
|
||||
.tooltip
|
||||
opacity 1
|
||||
|
||||
.active-button
|
||||
@extend .non-active-button
|
||||
color $ui-button-default--active-backgroundColor
|
||||
|
||||
.tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 22px
|
||||
left -2px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="white"]
|
||||
.non-active-button
|
||||
color $ui-inactive-text-color
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.tag-title
|
||||
p
|
||||
color $ui-text-color
|
||||
|
||||
.non-active-button
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.active-button
|
||||
@extend .non-active-button
|
||||
color #0B99F1
|
||||
|
||||
body[data-theme="dark"]
|
||||
.non-active-button
|
||||
color alpha($ui-dark-text-color, 60%)
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.tag-title
|
||||
p
|
||||
color alpha($ui-dark-text-color, 60%)
|
||||
24
browser/main/SideNav/TagButton.js
Normal file
24
browser/main/SideNav/TagButton.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './SwitchButton.styl'
|
||||
|
||||
const TagButton = ({
|
||||
onClick, isTagActive
|
||||
}) => (
|
||||
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={onClick}>
|
||||
<img src={isTagActive
|
||||
? '../resources/icon/icon-tag-active.svg'
|
||||
: '../resources/icon/icon-tag.svg'
|
||||
}
|
||||
/>
|
||||
<span styleName='tooltip'>Tags</span>
|
||||
</button>
|
||||
)
|
||||
|
||||
TagButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
isTagActive: PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(TagButton, styles)
|
||||
@@ -11,6 +11,9 @@ import SideNavFilter from 'browser/components/SideNavFilter'
|
||||
import StorageList from 'browser/components/StorageList'
|
||||
import NavToggleButton from 'browser/components/NavToggleButton'
|
||||
import EventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import PreferenceButton from './PreferenceButton'
|
||||
import ListButton from './ListButton'
|
||||
import TagButton from './TagButton'
|
||||
|
||||
class SideNav extends React.Component {
|
||||
// TODO: should not use electron stuff v0.7
|
||||
@@ -162,27 +165,11 @@ class SideNav extends React.Component {
|
||||
>
|
||||
<div styleName='top'>
|
||||
<div styleName='switch-buttons'>
|
||||
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={this.handleSwitchFoldersButtonClick.bind(this)}>
|
||||
<img src={isTagActive
|
||||
? '../resources/icon/icon-list.svg'
|
||||
: '../resources/icon/icon-list-active.svg'
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={this.handleSwitchTagsButtonClick.bind(this)}>
|
||||
<img src={isTagActive
|
||||
? '../resources/icon/icon-tag-active.svg'
|
||||
: '../resources/icon/icon-tag.svg'
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
<ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||
<TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
|
||||
</div>
|
||||
<div>
|
||||
<button styleName='top-menu-preference'
|
||||
onClick={(e) => this.handleMenuButtonClick(e)}
|
||||
>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
|
||||
</button>
|
||||
<PreferenceButton onClick={this.handleMenuButtonClick} />
|
||||
</div>
|
||||
</div>
|
||||
{this.SideNavComponent(isFolded, storageList)}
|
||||
|
||||
61
browser/main/lib/dataApi/exportFolder.js
Normal file
61
browser/main/lib/dataApi/exportFolder.js
Normal file
@@ -0,0 +1,61 @@
|
||||
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)
|
||||
})
|
||||
|
||||
return {
|
||||
storage,
|
||||
folderKey,
|
||||
fileType,
|
||||
exportDir
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = exportFolder
|
||||
@@ -7,6 +7,7 @@ const dataApi = {
|
||||
updateFolder: require('./updateFolder'),
|
||||
deleteFolder: require('./deleteFolder'),
|
||||
reorderFolder: require('./reorderFolder'),
|
||||
exportFolder: require('./exportFolder'),
|
||||
createNote: require('./createNote'),
|
||||
updateNote: require('./updateNote'),
|
||||
deleteNote: require('./deleteNote'),
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
width 490px
|
||||
padding 0 5px
|
||||
margin 10px 0
|
||||
border 1px solid #C9C9C9 // TODO: use variable.
|
||||
border 1px solid $ui-input--create-folder-modal
|
||||
border-radius 2px
|
||||
background-color transparent
|
||||
outline none
|
||||
@@ -68,7 +68,7 @@ body[data-theme="dark"]
|
||||
color $ui-dark-text-color
|
||||
|
||||
.control-folder-input
|
||||
border 1px solid #C9C9C9 // TODO: use variable.
|
||||
border 1px solid $ui-input--create-folder-modal
|
||||
color white
|
||||
|
||||
.description
|
||||
@@ -76,3 +76,29 @@ body[data-theme="dark"]
|
||||
|
||||
.control-confirmButton
|
||||
colorDarkPrimaryButton()
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
modalSolarizedDark()
|
||||
width 500px
|
||||
height 270px
|
||||
overflow hidden
|
||||
position relative
|
||||
|
||||
.header
|
||||
background-color transparent
|
||||
border-color $ui-dark-borderColor
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.control-folder-label
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.control-folder-input
|
||||
border 1px solid $ui-input--create-folder-modal
|
||||
color white
|
||||
|
||||
.description
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.control-confirmButton
|
||||
colorSolarizedDarkPrimaryButton()
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InitModal.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { hashHistory } from 'react-router'
|
||||
import _ from 'lodash'
|
||||
|
||||
const CSON = require('@rokt33r/season')
|
||||
const path = require('path')
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
|
||||
function browseFolder () {
|
||||
const dialog = remote.dialog
|
||||
|
||||
const defaultPath = remote.app.getPath('home')
|
||||
return new Promise((resolve, reject) => {
|
||||
dialog.showOpenDialog({
|
||||
title: 'Select Directory',
|
||||
defaultPath,
|
||||
properties: ['openDirectory', 'createDirectory']
|
||||
}, function (targetPaths) {
|
||||
if (targetPaths == null) return resolve('')
|
||||
resolve(targetPaths[0])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
class InitModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
path: path.join(remote.app.getPath('home'), 'Boostnote'),
|
||||
migrationRequested: true,
|
||||
isLoading: true,
|
||||
data: null,
|
||||
legacyStorageExists: false,
|
||||
isSending: false
|
||||
}
|
||||
}
|
||||
|
||||
handlePathChange (e) {
|
||||
this.setState({
|
||||
path: e.target.value
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
let data = null
|
||||
try {
|
||||
data = CSON.readFileSync(path.join(remote.app.getPath('userData'), 'local.json'))
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
const newState = {
|
||||
isLoading: false
|
||||
}
|
||||
if (data != null) {
|
||||
newState.legacyStorageExists = true
|
||||
newState.data = data
|
||||
}
|
||||
this.setState(newState, () => {
|
||||
this.refs.createButton.focus()
|
||||
})
|
||||
}
|
||||
|
||||
handlePathBrowseButtonClick (e) {
|
||||
browseFolder()
|
||||
.then((targetPath) => {
|
||||
if (targetPath.length > 0) {
|
||||
this.setState({
|
||||
path: targetPath
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('BrowseFAILED')
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
handleSubmitButtonClick (e) {
|
||||
this.setState({
|
||||
isSending: true
|
||||
}, () => {
|
||||
dataApi
|
||||
.addStorage({
|
||||
name: 'My Storage',
|
||||
path: this.state.path
|
||||
})
|
||||
.then((data) => {
|
||||
if (this.state.migrationRequested && _.isObject(this.state.data) && _.isArray(this.state.data.folders) && _.isArray(this.state.data.articles)) {
|
||||
return dataApi.migrateFromV5Storage(data.storage.key, this.state.data)
|
||||
}
|
||||
return data
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.storage.folders[0] != null) {
|
||||
return data
|
||||
} else {
|
||||
return dataApi
|
||||
.createFolder(data.storage.key, {
|
||||
color: '#1278BD',
|
||||
name: 'Default'
|
||||
})
|
||||
.then((_data) => {
|
||||
return {
|
||||
storage: _data.storage,
|
||||
notes: data.notes
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
console.log(data)
|
||||
store.dispatch({
|
||||
type: 'ADD_STORAGE',
|
||||
storage: data.storage,
|
||||
notes: data.notes
|
||||
})
|
||||
|
||||
const defaultSnippetNote = dataApi
|
||||
.createNote(data.storage.key, {
|
||||
type: 'SNIPPET_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
title: 'Snippet note example',
|
||||
description: 'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
|
||||
snippets: [
|
||||
{
|
||||
name: 'example.html',
|
||||
mode: 'html',
|
||||
content: '<html>\n<body>\n<h1 id=\'hello\'>Enjoy Boostnote!</h1>\n</body>\n</html>'
|
||||
},
|
||||
{
|
||||
name: 'example.js',
|
||||
mode: 'javascript',
|
||||
content: 'var boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)'
|
||||
}
|
||||
]
|
||||
})
|
||||
.then((note) => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
})
|
||||
const defaultMarkdownNote = dataApi
|
||||
.createNote(data.storage.key, {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
title: 'Welcome to Boostnote!',
|
||||
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
||||
})
|
||||
.then((note) => {
|
||||
store.dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
})
|
||||
|
||||
return Promise.resolve(defaultSnippetNote)
|
||||
.then(defaultMarkdownNote)
|
||||
.then(() => data.storage)
|
||||
})
|
||||
.then((storage) => {
|
||||
hashHistory.push('/storages/' + storage.key)
|
||||
this.props.close()
|
||||
})
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
isSending: false
|
||||
})
|
||||
throw err
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
handleMigrationRequestedChange (e) {
|
||||
this.setState({
|
||||
migrationRequested: e.target.checked
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
if (e.keyCode === 27) {
|
||||
this.props.close()
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.state.isLoading) {
|
||||
return <div styleName='root--loading'>
|
||||
<i styleName='spinner' className='fa fa-spin fa-spinner' />
|
||||
<div styleName='loadingMessage'>Preparing initialization...</div>
|
||||
</div>
|
||||
}
|
||||
return (
|
||||
<div styleName='root'
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
>
|
||||
<div styleName='body'>
|
||||
<div styleName='body-welcome'>
|
||||
Welcome to Boostnote!
|
||||
</div>
|
||||
<div styleName='body-description'>
|
||||
Please select a directory for data storage.
|
||||
</div>
|
||||
<div styleName='body-path'>
|
||||
<input styleName='body-path-input'
|
||||
placeholder='Select Folder'
|
||||
value={this.state.path}
|
||||
onChange={(e) => this.handlePathChange(e)}
|
||||
/>
|
||||
<button styleName='body-path-button'
|
||||
onClick={(e) => this.handlePathBrowseButtonClick(e)}
|
||||
>
|
||||
...
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{this.state.legacyStorageExists &&
|
||||
<div styleName='body-migration'>
|
||||
<label><input type='checkbox' checked={this.state.migrationRequested} onChange={(e) => this.handleMigrationRequestedChange(e)} /> Migrate old data from the legacy app v0.5</label>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div styleName='body-control'>
|
||||
<button styleName='body-control-createButton'
|
||||
ref='createButton'
|
||||
onClick={(e) => this.handleSubmitButtonClick(e)}
|
||||
disabled={this.state.isSending}
|
||||
>
|
||||
{this.state.isSending
|
||||
? <span>
|
||||
<i className='fa fa-spin fa-spinner' /> Loading...
|
||||
</span>
|
||||
: 'CREATE'
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
InitModal.propTypes = {
|
||||
}
|
||||
|
||||
export default CSSModules(InitModal, styles)
|
||||
@@ -1,76 +0,0 @@
|
||||
.root
|
||||
modal()
|
||||
background-color #fff
|
||||
max-width 100vw
|
||||
max-height 100vh
|
||||
overflow hidden
|
||||
margin 0
|
||||
padding 150px 0
|
||||
position relative
|
||||
.root--loading
|
||||
@extend .root
|
||||
text-align center
|
||||
.spinner
|
||||
font-size 100px
|
||||
margin 35px auto
|
||||
color $ui-text-color
|
||||
.loadingMessage
|
||||
color $ui-text-color
|
||||
margin 15px auto 35px
|
||||
|
||||
.body
|
||||
padding 30px
|
||||
|
||||
.body-welcome
|
||||
text-align center
|
||||
margin-bottom 25px
|
||||
font-size 32px
|
||||
color $ui-text-color
|
||||
|
||||
.body-description
|
||||
font-size 16px
|
||||
color $ui-text-color
|
||||
text-align center
|
||||
margin-bottom 25px
|
||||
|
||||
.body-path
|
||||
margin 0 auto 25px
|
||||
width 330px
|
||||
|
||||
.body-path-input
|
||||
height 40px
|
||||
vertical-align middle
|
||||
width 300px
|
||||
font-size 14px
|
||||
border-style solid
|
||||
border-width 1px 0 1px 1px
|
||||
border-color $border-color
|
||||
border-top-left-radius 2px
|
||||
border-bottom-left-radius 2px
|
||||
padding 0 5px
|
||||
|
||||
.body-path-button
|
||||
height 42px
|
||||
width 30px
|
||||
font-size 16px
|
||||
font-weight 600
|
||||
border none
|
||||
border-top-right-radius 2px
|
||||
border-bottom-right-radius 2px
|
||||
colorPrimaryButton()
|
||||
vertical-align middle
|
||||
.body-migration
|
||||
margin 0 auto 25px
|
||||
text-align center
|
||||
|
||||
.body-control
|
||||
text-align center
|
||||
|
||||
.body-control-createButton
|
||||
colorPrimaryButton()
|
||||
font-size 14px
|
||||
font-weight 600
|
||||
border none
|
||||
border-radius 2px
|
||||
height 40px
|
||||
padding 0 25px
|
||||
@@ -76,8 +76,8 @@
|
||||
color #1EC38B
|
||||
.error
|
||||
color red
|
||||
|
||||
|
||||
.warning
|
||||
color #FFA500
|
||||
|
||||
.group-control-leftButton
|
||||
colorDefaultButton()
|
||||
|
||||
@@ -32,6 +32,7 @@ class HotkeyTab extends React.Component {
|
||||
message: err.message != null ? err.message : 'Error occurs!'
|
||||
}})
|
||||
}
|
||||
this.oldHotkey = this.state.config.hotkey
|
||||
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||
}
|
||||
@@ -53,6 +54,7 @@ class HotkeyTab extends React.Component {
|
||||
config: newConfig
|
||||
})
|
||||
this.clearMessage()
|
||||
this.props.haveToSave()
|
||||
}
|
||||
|
||||
handleHintToggleButtonClick (e) {
|
||||
@@ -70,6 +72,15 @@ class HotkeyTab extends React.Component {
|
||||
this.setState({
|
||||
config
|
||||
})
|
||||
if (_.isEqual(this.oldHotkey, config.hotkey)) {
|
||||
this.props.haveToSave()
|
||||
} else {
|
||||
this.props.haveToSave({
|
||||
tab: 'Hotkey',
|
||||
type: 'warning',
|
||||
message: 'You have to save!'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
clearMessage () {
|
||||
@@ -161,7 +172,8 @@ class HotkeyTab extends React.Component {
|
||||
}
|
||||
|
||||
HotkeyTab.propTypes = {
|
||||
dispatch: PropTypes.func
|
||||
dispatch: PropTypes.func,
|
||||
haveToSave: PropTypes.func
|
||||
}
|
||||
|
||||
export default CSSModules(HotkeyTab, styles)
|
||||
|
||||
@@ -42,6 +42,8 @@ top-bar--height = 50px
|
||||
background-color transparent
|
||||
color $ui-text-color
|
||||
font-size 16px
|
||||
.saving--warning
|
||||
haveToSave()
|
||||
|
||||
.nav-button--active
|
||||
@extend .nav-button
|
||||
@@ -49,6 +51,8 @@ top-bar--height = 50px
|
||||
background-color $ui-button--active-backgroundColor
|
||||
&:hover
|
||||
color $ui-text-color
|
||||
.saving--warning
|
||||
haveToSave()
|
||||
|
||||
.nav-button-icon
|
||||
display block
|
||||
|
||||
@@ -20,3 +20,8 @@ $tab--dark-text-color = #E5E5E5
|
||||
body[data-theme="dark"]
|
||||
.header
|
||||
color $tab--dark-text-color
|
||||
|
||||
haveToSave()
|
||||
color #FFA500
|
||||
font-size 10px
|
||||
margin-top 3px
|
||||
@@ -7,11 +7,11 @@ import store from 'browser/main/store'
|
||||
import consts from 'browser/lib/consts'
|
||||
import ReactCodeMirror from 'react-codemirror'
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
import _ from 'lodash'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
import _ from 'lodash'
|
||||
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
|
||||
@@ -62,6 +62,7 @@ class UiTab extends React.Component {
|
||||
ui: {
|
||||
theme: this.refs.uiTheme.value,
|
||||
showCopyNotification: this.refs.showCopyNotification.checked,
|
||||
confirmDeletion: this.refs.confirmDeletion.checked,
|
||||
disableDirectWrite: this.refs.uiD2w != null
|
||||
? this.refs.uiD2w.checked
|
||||
: false
|
||||
@@ -93,8 +94,19 @@ class UiTab extends React.Component {
|
||||
if (newCodemirrorTheme !== codemirrorTheme) {
|
||||
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`)
|
||||
}
|
||||
|
||||
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme })
|
||||
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }, () => {
|
||||
const {ui, editor, preview} = this.props.config
|
||||
this.currentConfig = {ui, editor, preview}
|
||||
if (_.isEqual(this.currentConfig, this.state.config)) {
|
||||
this.props.haveToSave()
|
||||
} else {
|
||||
this.props.haveToSave({
|
||||
tab: 'UI',
|
||||
type: 'warning',
|
||||
message: 'You have to save!'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleSaveUIClick (e) {
|
||||
@@ -111,6 +123,7 @@ class UiTab extends React.Component {
|
||||
config: newConfig
|
||||
})
|
||||
this.clearMessage()
|
||||
this.props.haveToSave()
|
||||
}
|
||||
|
||||
clearMessage () {
|
||||
@@ -161,6 +174,16 @@ class UiTab extends React.Component {
|
||||
Show "Saved to Clipboard" notification when copying
|
||||
</label>
|
||||
</div>
|
||||
<div styleName='group-checkBoxSection'>
|
||||
<label>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
checked={this.state.config.ui.confirmDeletion}
|
||||
ref='confirmDeletion'
|
||||
type='checkbox'
|
||||
/>
|
||||
Show a confirmation dialog when deleting notes
|
||||
</label>
|
||||
</div>
|
||||
{
|
||||
global.process.platform === 'win32'
|
||||
? <div styleName='group-checkBoxSection'>
|
||||
@@ -170,7 +193,7 @@ class UiTab extends React.Component {
|
||||
refs='uiD2w'
|
||||
disabled={OSX}
|
||||
type='checkbox'
|
||||
/>
|
||||
/>
|
||||
Disable Direct Write(It will be applied after restarting)
|
||||
</label>
|
||||
</div>
|
||||
@@ -412,7 +435,8 @@ UiTab.propTypes = {
|
||||
user: PropTypes.shape({
|
||||
name: PropTypes.string
|
||||
}),
|
||||
dispatch: PropTypes.func
|
||||
dispatch: PropTypes.func,
|
||||
haveToSave: PropTypes.func
|
||||
}
|
||||
|
||||
export default CSSModules(UiTab, styles)
|
||||
|
||||
@@ -17,7 +17,9 @@ class Preferences extends React.Component {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
currentTab: 'STORAGES'
|
||||
currentTab: 'STORAGES',
|
||||
UIAlert: '',
|
||||
HotkeyAlert: ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +60,7 @@ class Preferences extends React.Component {
|
||||
<HotkeyTab
|
||||
dispatch={dispatch}
|
||||
config={config}
|
||||
haveToSave={alert => this.setState({HotkeyAlert: alert})}
|
||||
/>
|
||||
)
|
||||
case 'UI':
|
||||
@@ -65,6 +68,7 @@ class Preferences extends React.Component {
|
||||
<UiTab
|
||||
dispatch={dispatch}
|
||||
config={config}
|
||||
haveToSave={alert => this.setState({UIAlert: alert})}
|
||||
/>
|
||||
)
|
||||
case 'CROWDFUNDING':
|
||||
@@ -94,19 +98,26 @@ class Preferences extends React.Component {
|
||||
return node.getBoundingClientRect()
|
||||
}
|
||||
|
||||
haveToSaveNotif (type, message) {
|
||||
return (
|
||||
<p styleName={`saving--${type}`}>{message}</p>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const content = this.renderContent()
|
||||
|
||||
const tabs = [
|
||||
{target: 'STORAGES', label: 'Storages'},
|
||||
{target: 'HOTKEY', label: 'Hotkey'},
|
||||
{target: 'UI', label: 'UI'},
|
||||
{target: 'HOTKEY', label: 'Hotkey', Hotkey: this.state.HotkeyAlert},
|
||||
{target: 'UI', label: 'UI', UI: this.state.UIAlert},
|
||||
{target: 'INFO', label: 'Community / Info'},
|
||||
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
|
||||
]
|
||||
|
||||
const navButtons = tabs.map((tab) => {
|
||||
const isActive = this.state.currentTab === tab.target
|
||||
const isUiHotkeyTab = _.isObject(tab[tab.label]) && tab.label === tab[tab.label].tab
|
||||
return (
|
||||
<button styleName={isActive
|
||||
? 'nav-button--active'
|
||||
@@ -118,6 +129,7 @@ class Preferences extends React.Component {
|
||||
<span styleName='nav-button-label'>
|
||||
{tab.label}
|
||||
</span>
|
||||
{isUiHotkeyTab ? this.haveToSaveNotif(tab[tab.label].type, tab[tab.label].message) : null}
|
||||
</button>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -87,8 +87,13 @@ function data (state = defaultDataMap(), action) {
|
||||
state.trashedSet = new Set(state.trashedSet)
|
||||
if (note.isTrashed) {
|
||||
state.trashedSet.add(uniqueKey)
|
||||
state.starredSet.delete(uniqueKey)
|
||||
} else {
|
||||
state.trashedSet.delete(uniqueKey)
|
||||
|
||||
if (note.isStarred) {
|
||||
state.starredSet.add(uniqueKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +354,13 @@ function data (state = defaultDataMap(), action) {
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
return state
|
||||
case 'EXPORT_FOLDER':
|
||||
{
|
||||
state = Object.assign({}, state)
|
||||
state.storageMap = new Map(state.storageMap)
|
||||
state.storageMap.set(action.storage.key, action.storage)
|
||||
}
|
||||
return state
|
||||
case 'DELETE_FOLDER':
|
||||
{
|
||||
state = Object.assign({}, state)
|
||||
|
||||
@@ -46,6 +46,7 @@ tooltip()
|
||||
// UI Input
|
||||
$ui-input--focus-borderColor = #369DCD
|
||||
$ui-input--disabled-backgroundColor = #DDD
|
||||
$ui-input--create-folder-modal = #C9C9C9
|
||||
|
||||
// Parts
|
||||
$ui-favorite-star-button-color = #FFC216
|
||||
@@ -340,3 +341,11 @@ $ui-solarized-dark-button--active-color = #93a1a1
|
||||
$ui-solarized-dark-button--active-backgroundColor = #073642
|
||||
$ui-solarized-dark-button--hover-backgroundColor = lighten($ui-dark-backgroundColor, 10%)
|
||||
$ui-solarized-dark-button--focus-borderColor = lighten(#369DCD, 25%)
|
||||
|
||||
modalSolarizedDark()
|
||||
position relative
|
||||
z-index $modal-z-index
|
||||
width 100%
|
||||
background-color $ui-solarized-dark-backgroundColor
|
||||
overflow hidden
|
||||
border-radius $modal-border-radius
|
||||
@@ -28,12 +28,14 @@
|
||||
|
||||
<script src="../node_modules/codemirror/lib/codemirror.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/loadmode.js"></script>
|
||||
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
|
||||
<script src="../node_modules/codemirror/keymap/vim.js"></script>
|
||||
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/runmode/runmode.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
|
||||
|
||||
<script src="../node_modules/raphael/raphael.min.js"></script>
|
||||
<script src="../node_modules/flowchart.js/release/flowchart.min.js"></script>
|
||||
|
||||
@@ -65,14 +65,6 @@ updater.autoUpdater.on('error', (err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
ipc.on('update-check', function (event, msg) {
|
||||
if (isUpdateReady) {
|
||||
mainWindow.webContents.send('update-ready', 'Update available!')
|
||||
} else {
|
||||
checkUpdate()
|
||||
}
|
||||
})
|
||||
|
||||
ipc.on('update-app-confirm', function (event, msg) {
|
||||
if (isUpdateReady) {
|
||||
mainWindow.removeAllListeners()
|
||||
@@ -102,12 +94,11 @@ app.on('ready', function () {
|
||||
Menu.setApplicationMenu(menu)
|
||||
break
|
||||
case 'win32':
|
||||
/* eslint-disable */
|
||||
finderWindow = require('./finder-window')
|
||||
/* eslint-disable */
|
||||
require('./finder-window')
|
||||
mainWindow.setMenu(menu)
|
||||
break
|
||||
case 'linux':
|
||||
require('./finder-window')
|
||||
Menu.setApplicationMenu(menu)
|
||||
mainWindow.setMenu(menu)
|
||||
}
|
||||
@@ -115,9 +106,20 @@ app.on('ready', function () {
|
||||
// Check update every hour
|
||||
setInterval(function () {
|
||||
checkUpdate()
|
||||
}, 1000 * 60 * 60)
|
||||
}, 1000 * 60 * 60 * 24)
|
||||
|
||||
// Check update after 10 secs to prevent file locking of Windows
|
||||
setTimeout(() => {
|
||||
checkUpdate()
|
||||
|
||||
ipc.on('update-check', function (event, msg) {
|
||||
if (isUpdateReady) {
|
||||
mainWindow.webContents.send('update-ready', 'Update available!')
|
||||
} else {
|
||||
checkUpdate()
|
||||
}
|
||||
})
|
||||
}, 10000)
|
||||
ipcServer = require('./ipcServer')
|
||||
ipcServer.server.start()
|
||||
})
|
||||
|
||||
@@ -108,6 +108,13 @@ const file = {
|
||||
mainWindow.webContents.send('list:isMarkdownNote')
|
||||
mainWindow.webContents.send('export:save-md')
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'HTML (.html)',
|
||||
click () {
|
||||
mainWindow.webContents.send('list:isMarkdownNote')
|
||||
mainWindow.webContents.send('export:save-html')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
|
||||
<script src="../node_modules/codemirror/lib/codemirror.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/loadmode.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/mode/simple.js"></script>
|
||||
@@ -79,6 +80,8 @@
|
||||
|
||||
<script src="../node_modules/codemirror/addon/edit/continuelist.js"></script>
|
||||
|
||||
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
|
||||
|
||||
<script src="../node_modules/codemirror/addon/search/search.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/search/searchcursor.js"></script>
|
||||
<script src="../node_modules/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "boost",
|
||||
"productName": "Boostnote",
|
||||
"version": "0.8.19",
|
||||
"version": "0.8.20",
|
||||
"main": "index.js",
|
||||
"description": "Boostnote",
|
||||
"license": "GPL-3.0",
|
||||
@@ -16,7 +16,7 @@
|
||||
"dev-start": "concurrently --kill-others \"npm run webpack\" \"npm run hot\""
|
||||
},
|
||||
"config": {
|
||||
"electron-version": "1.6.15"
|
||||
"electron-version": "1.7.10"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -53,6 +53,7 @@
|
||||
"aws-sdk": "^2.48.0",
|
||||
"aws-sdk-mobile-analytics": "^0.9.2",
|
||||
"codemirror": "^5.19.0",
|
||||
"codemirror-mode-elixir": "^1.1.1",
|
||||
"electron-config": "^0.2.1",
|
||||
"electron-gh-releases": "^2.0.2",
|
||||
"flowchart.js": "^1.6.5",
|
||||
@@ -102,7 +103,7 @@
|
||||
"css-loader": "^0.19.0",
|
||||
"devtron": "^1.1.0",
|
||||
"dom-storage": "^2.0.2",
|
||||
"electron": "^1.6.15",
|
||||
"electron": "1.7.10",
|
||||
"electron-packager": "^6.0.0",
|
||||
"eslint": "^3.13.1",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
@@ -111,7 +112,7 @@
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"faker": "^3.1.0",
|
||||
"grunt": "^0.4.5",
|
||||
"grunt-electron-installer": "^1.2.0",
|
||||
"grunt-electron-installer": "2.1.0",
|
||||
"history": "^1.17.0",
|
||||
"jsdom": "^9.4.2",
|
||||
"json-loader": "^0.5.4",
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
New:zap:
|
||||
|
||||
Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mobile)!
|
||||
:mega: Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mobile)!
|
||||
|
||||

|
||||
|
||||
@@ -12,7 +10,6 @@ Open sourcing our [Android and iOS apps](https://github.com/BoostIO/Boostnote-mo
|
||||
|
||||
## Authors & Maintainers
|
||||
- [Rokt33r](https://github.com/rokt33r)
|
||||
- [sota1235](https://github.com/sota1235)
|
||||
- [Kohei TAKATA](https://github.com/kohei-takata)
|
||||
- [Sosuke](https://github.com/sosukesuzuki)
|
||||
- [Kazz](https://github.com/kazup01)
|
||||
@@ -29,7 +26,7 @@ Boostnote is an open source project. It's an independent project with its ongoin
|
||||
## Community
|
||||
- [Facebook Group](https://www.facebook.com/groups/boostnote/)
|
||||
- [Twitter](https://twitter.com/boostnoteapp)
|
||||
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMjc2MDM0MDEyODk2LThlZDlhYmYwMjdkMmJjMGM5MGFiMGJmNzk5ZTdhNzFhMmNmMDFlY2M2YTE1MTZkOThiOGZmNTI3YzJiOTBhMTQ)
|
||||
- [Slack Group](https://join.slack.com/t/boostnote-group/shared_invite/enQtMjkxMzMwODYxMDI1LTgwZmRiODg0NzA5MWRmOTJjNzBjZjAwMmMyZGQ4Y2RkOGE0MDg0YjcyMjA5OGUzMmZhNmFiNTMzOTlkYWNlMTM)
|
||||
- [Blog](https://medium.com/boostnote)
|
||||
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
||||
|
||||
|
||||
62
tests/dataApi/exportFolder-test.js
Normal file
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))
|
||||
})
|
||||
})
|
||||
61
yarn.lock
61
yarn.lock
@@ -245,18 +245,6 @@ asar@^0.9.0:
|
||||
mkdirp "^0.5.0"
|
||||
mksnapshot "0.1.0"
|
||||
|
||||
asar@~0.8.0:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/asar/-/asar-0.8.3.tgz#c2e03f9054516dbbf56759e854e9ce8d1a9d39d3"
|
||||
dependencies:
|
||||
chromium-pickle-js "0.1.0"
|
||||
commander "2.3.0"
|
||||
cuint "0.1.5"
|
||||
glob "^5.0.5"
|
||||
minimatch "2.0.4"
|
||||
mkdirp "^0.5.0"
|
||||
mksnapshot "0.1.0"
|
||||
|
||||
asn1@0.1.11:
|
||||
version "0.1.11"
|
||||
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.1.11.tgz#559be18376d08a4ec4dbe80877d27818639b2df7"
|
||||
@@ -1183,6 +1171,10 @@ bluebird@^3.0.0, bluebird@^3.1.1, bluebird@^3.4.1:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c"
|
||||
|
||||
bluebird@^3.3.4:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
|
||||
|
||||
boom@2.x.x:
|
||||
version "2.10.1"
|
||||
resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
|
||||
@@ -1503,10 +1495,20 @@ code-point-at@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
|
||||
codemirror-mode-elixir@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/codemirror-mode-elixir/-/codemirror-mode-elixir-1.1.1.tgz#cc5b79bf5f93b6da426e32364a673a681391416c"
|
||||
dependencies:
|
||||
codemirror "^5.20.2"
|
||||
|
||||
codemirror@^5.18.2, codemirror@^5.19.0:
|
||||
version "5.26.0"
|
||||
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.26.0.tgz#bcbee86816ed123870c260461c2b5c40b68746e5"
|
||||
|
||||
codemirror@^5.20.2:
|
||||
version "5.33.0"
|
||||
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.33.0.tgz#462ad9a6fe8d38b541a9536a3997e1ef93b40c6a"
|
||||
|
||||
coffee-script@^1.10.0:
|
||||
version "1.12.6"
|
||||
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.6.tgz#285a3f7115689065064d6bf9ef4572db66695cbf"
|
||||
@@ -2199,9 +2201,20 @@ electron-to-chromium@^1.2.7:
|
||||
version "1.3.11"
|
||||
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.11.tgz#744761df1d67b492b322ce9aa0aba5393260eb61"
|
||||
|
||||
electron@^1.6.15:
|
||||
version "1.7.9"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-1.7.9.tgz#add54e9f8f83ed02f6519ec10135f698b19336cf"
|
||||
electron-winstaller@^2.2.0:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/electron-winstaller/-/electron-winstaller-2.6.3.tgz#d54f77c0cececc4fc55eeb5968d345cf69645ea4"
|
||||
dependencies:
|
||||
asar "^0.11.0"
|
||||
bluebird "^3.3.4"
|
||||
debug "^2.2.0"
|
||||
fs-extra "^0.26.7"
|
||||
lodash.template "^4.2.2"
|
||||
temp "^0.8.3"
|
||||
|
||||
electron@1.7.10:
|
||||
version "1.7.10"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-1.7.10.tgz#3a3e83d965fd7fafe473be8ddf8f472561b6253d"
|
||||
dependencies:
|
||||
"@types/node" "^7.0.18"
|
||||
electron-download "^3.0.1"
|
||||
@@ -2885,7 +2898,7 @@ fs-extra@0.18.2:
|
||||
jsonfile "^2.0.0"
|
||||
rimraf "^2.2.8"
|
||||
|
||||
fs-extra@0.26.7, fs-extra@^0.26.0, fs-extra@^0.26.5:
|
||||
fs-extra@0.26.7, fs-extra@^0.26.0, fs-extra@^0.26.5, fs-extra@^0.26.7:
|
||||
version "0.26.7"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9"
|
||||
dependencies:
|
||||
@@ -3156,13 +3169,11 @@ grunt-electron-installer-redhat@^0.3.1:
|
||||
dependencies:
|
||||
electron-installer-redhat "^0.3.0"
|
||||
|
||||
grunt-electron-installer@^1.2.0:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/grunt-electron-installer/-/grunt-electron-installer-1.2.3.tgz#50652ec4d0248233da76b4ac2ca69f3894c7240e"
|
||||
grunt-electron-installer@2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/grunt-electron-installer/-/grunt-electron-installer-2.1.0.tgz#b39e7eb1abb4488a1d8b7587fd4e72d68a741030"
|
||||
dependencies:
|
||||
asar "~0.8.0"
|
||||
temp "^0.8.1"
|
||||
underscore "^1.7.0"
|
||||
electron-winstaller "^2.2.0"
|
||||
|
||||
grunt-legacy-log-utils@~0.1.1:
|
||||
version "0.1.1"
|
||||
@@ -4016,7 +4027,7 @@ lodash.some@^4.5.1:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d"
|
||||
|
||||
lodash.template@^4.3.0:
|
||||
lodash.template@^4.2.2, lodash.template@^4.3.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0"
|
||||
dependencies:
|
||||
@@ -6336,7 +6347,7 @@ tar@^2.2.1:
|
||||
fstream "^1.0.2"
|
||||
inherits "2"
|
||||
|
||||
temp@^0.8.1, temp@^0.8.3:
|
||||
temp@^0.8.3:
|
||||
version "0.8.3"
|
||||
resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
|
||||
dependencies:
|
||||
@@ -6540,7 +6551,7 @@ underscore.string@~2.4.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/underscore.string/-/underscore.string-2.4.0.tgz#8cdd8fbac4e2d2ea1e7e2e8097c42f442280f85b"
|
||||
|
||||
underscore@^1.7.0, underscore@^1.8.2:
|
||||
underscore@^1.8.2:
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user