Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d98f0cb03 | ||
|
|
3503233631 | ||
|
|
c39393c453 | ||
|
|
8e1bf48cd1 | ||
|
|
8dd82e1a3b | ||
|
|
4418bfe965 | ||
|
|
39c4d710bc | ||
|
|
51a8c47afd | ||
|
|
922570bb5c | ||
|
|
ca282d5635 | ||
|
|
ff67043210 | ||
|
|
31da231c1c | ||
|
|
eb698a7430 | ||
|
|
03be809ba9 | ||
|
|
69601bf15a | ||
|
|
7cad3d403b | ||
|
|
cc84af3346 | ||
|
|
33ef54a162 | ||
|
|
ac43ff886a | ||
|
|
a64f73ca0c | ||
|
|
314477d2fc | ||
|
|
c63fc93daa | ||
|
|
2183c4bda6 | ||
|
|
6ba91c1515 | ||
|
|
d0559c16b5 | ||
|
|
eb693f7b48 | ||
|
|
ef5639ff4b | ||
|
|
34a335797c | ||
|
|
edda3a4d23 | ||
|
|
184839423f | ||
|
|
c0f3600a52 | ||
|
|
2b1302aa07 | ||
|
|
470c071344 | ||
|
|
4bd639c6c4 | ||
|
|
4cb7e63421 | ||
|
|
9f14a503d8 | ||
|
|
d5da6de86c | ||
|
|
4c2b233722 | ||
|
|
ca5b1eea13 | ||
|
|
614e9b6d55 | ||
|
|
27b2530b8d | ||
|
|
2259167200 | ||
|
|
2e05214828 | ||
|
|
cfb996039b | ||
|
|
44c4d56214 | ||
|
|
8e6be91f7c | ||
|
|
00816fb2c8 | ||
|
|
535356b77f | ||
|
|
5e558746ce | ||
|
|
f7bd52ac0c | ||
|
|
9165f518a9 | ||
|
|
01605aa221 | ||
|
|
8b0b29c424 | ||
|
|
7a116966fa | ||
|
|
e7e8f11a74 | ||
|
|
f235d832d5 | ||
|
|
7730b5e20b | ||
|
|
8c3ba4ce48 | ||
|
|
e9a126f586 | ||
|
|
097e7d2ff2 | ||
|
|
81265f1238 | ||
|
|
2b507e6e20 | ||
|
|
747d3a8f13 | ||
|
|
30f6f07434 | ||
|
|
6de5488a15 | ||
|
|
5413647166 | ||
|
|
e83fe73b18 | ||
|
|
87a289ec65 | ||
|
|
8a0a118dba | ||
|
|
564cc80ef7 | ||
|
|
687126ce87 | ||
|
|
8a05d577da | ||
|
|
4c3ebfc0f8 | ||
|
|
6093f25f9a | ||
|
|
ecab68d676 | ||
|
|
77f7144fbf | ||
|
|
1cb4f37c95 | ||
|
|
14318528b9 | ||
|
|
9c0e1f8f1a | ||
|
|
2034ce9e4d | ||
|
|
657489caf6 | ||
|
|
94be3d1fe5 | ||
|
|
f6eae41cee | ||
|
|
69c64434e3 | ||
|
|
256cabfce1 | ||
|
|
e8b8272cf9 | ||
|
|
bd5ab4881c | ||
|
|
9630744bdb | ||
|
|
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 | ||
|
|
da1098e441 | ||
|
|
85065357e2 | ||
|
|
1f5f6c3b0e | ||
|
|
4f7026969f | ||
|
|
16d264ebfa | ||
|
|
04ffbe945b | ||
|
|
974a1a1e7d | ||
|
|
ca2c07244f | ||
|
|
e262d2f19b | ||
|
|
aabfe820ac | ||
|
|
3bba5442bd | ||
|
|
6ce1922fb3 | ||
|
|
9367a404ef | ||
|
|
775200bdd6 | ||
|
|
795fe8ae1d | ||
|
|
6fba62d062 | ||
|
|
6906c0ab0d | ||
|
|
5d46adf8fd | ||
|
|
4cdfc738c0 | ||
|
|
46d46f21e4 | ||
|
|
8c8a0ab46d | ||
|
|
959b75bddd | ||
|
|
6a9d4ae0fd | ||
|
|
cb59458c79 | ||
|
|
125a493400 | ||
|
|
83910b55d2 | ||
|
|
f4fd131100 | ||
|
|
d1e5781c24 | ||
|
|
c86e451198 | ||
|
|
b4a7b547f0 | ||
|
|
7a4258bb20 |
@@ -10,7 +10,6 @@
|
|||||||
"theme": "monokai"
|
"theme": "monokai"
|
||||||
},
|
},
|
||||||
"hotkey": {
|
"hotkey": {
|
||||||
"toggleFinder": "Cmd + Alt + S",
|
|
||||||
"toggleMain": "Cmd + Alt + L"
|
"toggleMain": "Cmd + Alt + L"
|
||||||
},
|
},
|
||||||
"isSideNavFolded": false,
|
"isSideNavFolded": false,
|
||||||
|
|||||||
4
.gitignore
vendored
@@ -8,5 +8,5 @@ node_modules/*
|
|||||||
/compiled
|
/compiled
|
||||||
/secret
|
/secret
|
||||||
*.log
|
*.log
|
||||||
.vscode
|
.idea
|
||||||
.idea
|
.vscode
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- stable
|
- 6
|
||||||
- lts/*
|
|
||||||
script:
|
script:
|
||||||
- npm run lint && npm run test
|
- npm run lint && npm run test
|
||||||
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
|
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
|
||||||
@@ -18,3 +17,4 @@ deploy:
|
|||||||
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
|
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
|
||||||
&& cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
|
&& cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
|
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -50,9 +51,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
|
|
||||||
this.editor = CodeMirror(this.refs.root, {
|
this.editor = CodeMirror(this.refs.root, {
|
||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
lineNumbers: true,
|
lineNumbers: this.props.displayLineNumbers,
|
||||||
lineWrapping: true,
|
lineWrapping: true,
|
||||||
theme: this.props.theme,
|
theme: this.props.theme,
|
||||||
indentUnit: this.props.indentSize,
|
indentUnit: this.props.indentSize,
|
||||||
@@ -62,6 +64,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
scrollPastEnd: this.props.scrollPastEnd,
|
scrollPastEnd: this.props.scrollPastEnd,
|
||||||
inputStyle: 'textarea',
|
inputStyle: 'textarea',
|
||||||
dragDrop: false,
|
dragDrop: false,
|
||||||
|
autoCloseBrackets: true,
|
||||||
extraKeys: {
|
extraKeys: {
|
||||||
Tab: function (cm) {
|
Tab: function (cm) {
|
||||||
const cursor = cm.getCursor()
|
const cursor = cm.getCursor()
|
||||||
@@ -69,7 +72,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
if (cm.somethingSelected()) cm.indentSelection('add')
|
if (cm.somethingSelected()) cm.indentSelection('add')
|
||||||
else {
|
else {
|
||||||
const tabs = cm.getOption('indentWithTabs')
|
const tabs = cm.getOption('indentWithTabs')
|
||||||
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)\] )?$/)) {
|
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
|
||||||
cm.execCommand('goLineStart')
|
cm.execCommand('goLineStart')
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
cm.execCommand('insertTab')
|
cm.execCommand('insertTab')
|
||||||
@@ -154,6 +157,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
|
this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevProps.displayLineNumbers !== this.props.displayLineNumbers) {
|
||||||
|
this.editor.setOption('lineNumbers', this.props.displayLineNumbers)
|
||||||
|
}
|
||||||
|
|
||||||
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
|
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
|
||||||
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
|
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
|
||||||
}
|
}
|
||||||
@@ -228,7 +235,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
if (!dataTransferItem.type.match('image')) return
|
if (!dataTransferItem.type.match('image')) return
|
||||||
|
|
||||||
const blob = dataTransferItem.getAsFile()
|
const blob = dataTransferItem.getAsFile()
|
||||||
const reader = new FileReader()
|
const reader = new window.FileReader()
|
||||||
let base64data
|
let base64data
|
||||||
|
|
||||||
reader.readAsDataURL(blob)
|
reader.readAsDataURL(blob)
|
||||||
@@ -249,7 +256,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
|
||||||
|
|||||||
@@ -242,6 +242,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
fontSize={editorFontSize}
|
fontSize={editorFontSize}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
@@ -260,7 +261,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
codeBlockFontFamily={config.editor.fontFamily}
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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'
|
||||||
@@ -23,7 +24,7 @@ const appPath = 'file://' + (process.env.NODE_ENV === 'production'
|
|||||||
? app.getAppPath()
|
? app.getAppPath()
|
||||||
: path.resolve())
|
: path.resolve())
|
||||||
|
|
||||||
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber) {
|
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) {
|
||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
@@ -47,6 +48,7 @@ ${markdownStyle}
|
|||||||
body {
|
body {
|
||||||
font-family: '${fontFamily.join("','")}';
|
font-family: '${fontFamily.join("','")}';
|
||||||
font-size: ${fontSize}px;
|
font-size: ${fontSize}px;
|
||||||
|
${scrollPastEnd && 'padding-bottom: 90vh;'}
|
||||||
}
|
}
|
||||||
code {
|
code {
|
||||||
font-family: '${codeBlockFontFamily.join("','")}';
|
font-family: '${codeBlockFontFamily.join("','")}';
|
||||||
@@ -118,6 +120,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
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)
|
||||||
@@ -176,21 +179,29 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.exportAsDocument('md')
|
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) {
|
exportAsDocument (fileType, formatter) {
|
||||||
const options = {
|
const options = {
|
||||||
filters: [
|
filters: [
|
||||||
{ name: 'Documents', extensions: [fileType] }
|
{ name: 'Documents', extensions: [fileType] }
|
||||||
],
|
],
|
||||||
properties: ['openFile', 'createDirectory']
|
properties: ['openFile', 'createDirectory']
|
||||||
}
|
}
|
||||||
|
const value = formatter ? formatter.call(this, this.props.value) : this.props.value
|
||||||
|
|
||||||
dialog.showSaveDialog(remote.getCurrentWindow(), options,
|
dialog.showSaveDialog(remote.getCurrentWindow(), options,
|
||||||
(filename) => {
|
(filename) => {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
fs.writeFile(filename, this.props.value, (err) => {
|
fs.writeFile(filename, value, (err) => {
|
||||||
if (err) throw err
|
if (err) throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -216,6 +227,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
<link rel="stylesheet" href="${appPath}/node_modules/katex/dist/katex.min.css">
|
<link rel="stylesheet" href="${appPath}/node_modules/katex/dist/katex.min.css">
|
||||||
<link rel="stylesheet" href="${appPath}/node_modules/codemirror/lib/codemirror.css">
|
<link rel="stylesheet" href="${appPath}/node_modules/codemirror/lib/codemirror.css">
|
||||||
<link rel="stylesheet" id="codeTheme">
|
<link rel="stylesheet" id="codeTheme">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
`
|
`
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
@@ -226,6 +238,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
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-text', this.saveAsTextHandler)
|
||||||
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||||
|
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
||||||
eventEmitter.on('print', this.printHandler)
|
eventEmitter.on('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,6 +250,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
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-text', this.saveAsTextHandler)
|
||||||
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
||||||
|
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
||||||
eventEmitter.off('print', this.printHandler)
|
eventEmitter.off('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,14 +262,15 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
||||||
prevProps.lineNumber !== this.props.lineNumber ||
|
prevProps.lineNumber !== this.props.lineNumber ||
|
||||||
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
||||||
prevProps.theme !== this.props.theme) {
|
prevProps.theme !== this.props.theme ||
|
||||||
|
prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyle () {
|
applyStyle () {
|
||||||
const { fontSize, lineNumber, codeBlockTheme } = this.props
|
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
|
||||||
let { fontFamily, codeBlockFontFamily } = this.props
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
||||||
@@ -265,7 +280,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
: defaultCodeBlockFontFamily
|
: defaultCodeBlockFontFamily
|
||||||
|
|
||||||
this.setCodeTheme(codeBlockTheme)
|
this.setCodeTheme(codeBlockTheme)
|
||||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
|
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
setCodeTheme (theme) {
|
setCodeTheme (theme) {
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
render () {
|
render () {
|
||||||
const { config, value, storageKey } = this.props
|
const { config, value, storageKey } = this.props
|
||||||
const storage = findStorage(storageKey)
|
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 = {}
|
const previewStyle = {}
|
||||||
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
||||||
return (
|
return (
|
||||||
@@ -57,7 +61,10 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
theme={config.editor.theme}
|
theme={config.editor.theme}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
fontFamily={config.editor.fontFamily}
|
fontFamily={config.editor.fontFamily}
|
||||||
|
fontSize={editorFontSize}
|
||||||
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
onChange={this.handleOnChange.bind(this)}
|
onChange={this.handleOnChange.bind(this)}
|
||||||
@@ -72,6 +79,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
codeBlockTheme={config.preview.codeBlockTheme}
|
codeBlockTheme={config.preview.codeBlockTheme}
|
||||||
codeBlockFontFamily={config.editor.fontFamily}
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
tabInde='0'
|
tabInde='0'
|
||||||
value={value}
|
value={value}
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ body[data-theme="white"]
|
|||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.menu-button--active
|
.menu-button--active
|
||||||
@extend .menu-button
|
|
||||||
color #e74c3c
|
color #e74c3c
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color $ui-button--active-backgroundColor
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
@@ -109,7 +108,6 @@ body[data-theme="white"]
|
|||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
|
|
||||||
.menu-button-star--active
|
.menu-button-star--active
|
||||||
@extend .menu-button
|
|
||||||
color #F9BF3B
|
color #F9BF3B
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color $ui-button--active-backgroundColor
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
@@ -126,7 +124,6 @@ body[data-theme="white"]
|
|||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
|
|
||||||
.menu-button-trash--active
|
.menu-button-trash--active
|
||||||
@extend .menu-button
|
|
||||||
color #5D9E36
|
color #5D9E36
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color $ui-button--active-backgroundColor
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.percentageBar
|
.percentageBar
|
||||||
position absolute
|
position absolute
|
||||||
top 50px
|
top 72px
|
||||||
right 0px
|
right 0px
|
||||||
left 0px
|
left 0px
|
||||||
background-color #DADFE1
|
background-color #DADFE1
|
||||||
|
|||||||
@@ -105,7 +105,6 @@ a
|
|||||||
border-radius 5px
|
border-radius 5px
|
||||||
margin -5px
|
margin -5px
|
||||||
transition .1s
|
transition .1s
|
||||||
display inline-block
|
|
||||||
img
|
img
|
||||||
vertical-align sub
|
vertical-align sub
|
||||||
&:hover
|
&:hover
|
||||||
@@ -336,8 +335,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
|
||||||
|
|||||||
@@ -1,156 +0,0 @@
|
|||||||
$search-height = 50px
|
|
||||||
$nav-width = 175px
|
|
||||||
$list-width = 250px
|
|
||||||
|
|
||||||
.root
|
|
||||||
absolute top left right bottom
|
|
||||||
|
|
||||||
.search
|
|
||||||
height $search-height
|
|
||||||
padding 10px
|
|
||||||
box-sizing border-box
|
|
||||||
border-bottom $ui-border
|
|
||||||
text-align center
|
|
||||||
|
|
||||||
.search-input
|
|
||||||
height 30px
|
|
||||||
width 100%
|
|
||||||
margin 0 auto
|
|
||||||
font-size 18px
|
|
||||||
border none
|
|
||||||
outline none
|
|
||||||
text-align center
|
|
||||||
background-color transparent
|
|
||||||
|
|
||||||
.result
|
|
||||||
absolute left right bottom
|
|
||||||
top $search-height
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.result-nav
|
|
||||||
user-select none
|
|
||||||
absolute left top bottom
|
|
||||||
width $nav-width
|
|
||||||
background-color $ui-backgroundColor
|
|
||||||
|
|
||||||
.result-nav-filter
|
|
||||||
margin-bottom 10px
|
|
||||||
|
|
||||||
.result-nav-filter-option
|
|
||||||
height 25px
|
|
||||||
line-height 25px
|
|
||||||
padding 0 10px
|
|
||||||
label
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
.result-nav-menu
|
|
||||||
navButtonColor()
|
|
||||||
height 32px
|
|
||||||
padding 0 10px
|
|
||||||
font-size 14px
|
|
||||||
width 100%
|
|
||||||
outline none
|
|
||||||
text-align left
|
|
||||||
line-height 32px
|
|
||||||
box-sizing border-box
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
.result-nav-menu--active
|
|
||||||
@extend .result-nav-menu
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color $ui-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
|
|
||||||
.result-nav-storageList
|
|
||||||
absolute bottom left right
|
|
||||||
top 110px + 32px + 10px + 10px + 20px
|
|
||||||
overflow-y auto
|
|
||||||
|
|
||||||
.result-list
|
|
||||||
user-select none
|
|
||||||
absolute top bottom
|
|
||||||
left $nav-width
|
|
||||||
width $list-width
|
|
||||||
box-sizing border-box
|
|
||||||
overflow-y auto
|
|
||||||
box-shadow 2px 0 15px -8px #b1b1b1
|
|
||||||
z-index 1
|
|
||||||
|
|
||||||
.result-detail
|
|
||||||
absolute top bottom right
|
|
||||||
left $nav-width + $list-width
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
|
||||||
.root
|
|
||||||
background-color $ui-dark-backgroundColor
|
|
||||||
.search
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
.search-input
|
|
||||||
color $ui-dark-text-color
|
|
||||||
|
|
||||||
.result
|
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
|
||||||
|
|
||||||
.result-nav
|
|
||||||
background-color $ui-dark-backgroundColor
|
|
||||||
label
|
|
||||||
color $ui-dark-text-color
|
|
||||||
|
|
||||||
.result-nav-menu
|
|
||||||
navDarkButtonColor()
|
|
||||||
|
|
||||||
.result-nav-menu--active
|
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
|
||||||
color $ui-dark-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
|
||||||
|
|
||||||
.result-list
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
box-shadow none
|
|
||||||
top 0
|
|
||||||
|
|
||||||
.result-detail
|
|
||||||
absolute top bottom right
|
|
||||||
left $nav-width + $list-width
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
|
||||||
.root
|
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
|
||||||
.search
|
|
||||||
border-color $ui-solarized-dark-borderColor
|
|
||||||
.search-input
|
|
||||||
color $ui-dark-text-color
|
|
||||||
|
|
||||||
.result
|
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
|
||||||
|
|
||||||
.result-nav
|
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
|
||||||
label
|
|
||||||
color $ui-dark-text-color
|
|
||||||
|
|
||||||
.result-nav-menu
|
|
||||||
navDarkButtonColor()
|
|
||||||
|
|
||||||
.result-nav-menu--active
|
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
|
||||||
color $ui-dark-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
|
||||||
|
|
||||||
.result-list
|
|
||||||
border-color $ui-solarized-dark-borderColor
|
|
||||||
box-shadow none
|
|
||||||
top 0
|
|
||||||
|
|
||||||
.result-detail
|
|
||||||
absolute top bottom right
|
|
||||||
left $nav-width + $list-width
|
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
|
||||||
|
|
||||||
@@ -1,210 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
|
||||||
import styles from './NoteDetail.styl'
|
|
||||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
|
||||||
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
|
||||||
import CodeEditor from 'browser/components/CodeEditor'
|
|
||||||
import CodeMirror from 'codemirror'
|
|
||||||
import { findStorage } from 'browser/lib/findStorage'
|
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const { clipboard } = electron
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
function pass (name) {
|
|
||||||
switch (name) {
|
|
||||||
case 'ejs':
|
|
||||||
return 'Embedded Javascript'
|
|
||||||
case 'html_ruby':
|
|
||||||
return 'Embedded Ruby'
|
|
||||||
case 'objectivec':
|
|
||||||
return 'Objective C'
|
|
||||||
case 'text':
|
|
||||||
return 'Plain Text'
|
|
||||||
default:
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function notify (title, options) {
|
|
||||||
if (global.process.platform === 'win32') {
|
|
||||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
|
||||||
}
|
|
||||||
return new window.Notification(title, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
class NoteDetail extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
snippetIndex: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (nextProps.note !== this.props.note) {
|
|
||||||
this.setState({
|
|
||||||
snippetIndex: 0
|
|
||||||
}, () => {
|
|
||||||
if (nextProps.note.type === 'SNIPPET_NOTE') {
|
|
||||||
nextProps.note.snippets.forEach((snippet, index) => {
|
|
||||||
this.refs['code-' + index].reload()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectPriorSnippet () {
|
|
||||||
const { note } = this.props
|
|
||||||
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
|
||||||
this.setState({
|
|
||||||
snippetIndex: (this.state.snippetIndex + note.snippets.length - 1) % note.snippets.length
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectNextSnippet () {
|
|
||||||
const { note } = this.props
|
|
||||||
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
|
||||||
this.setState({
|
|
||||||
snippetIndex: (this.state.snippetIndex + 1) % note.snippets.length
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToClipboard () {
|
|
||||||
const { note } = this.props
|
|
||||||
|
|
||||||
if (note.type === 'MARKDOWN_NOTE') {
|
|
||||||
clipboard.writeText(note.content)
|
|
||||||
} else {
|
|
||||||
clipboard.writeText(note.snippets[this.state.snippetIndex].content)
|
|
||||||
}
|
|
||||||
|
|
||||||
notify('Saved to Clipboard!', {
|
|
||||||
body: 'Paste it wherever you want!',
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTabButtonClick (e, index) {
|
|
||||||
this.setState({
|
|
||||||
snippetIndex: index
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { note, config } = this.props
|
|
||||||
if (note == null) {
|
|
||||||
return (
|
|
||||||
<div styleName='root' />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 storage = findStorage(note.storage)
|
|
||||||
|
|
||||||
if (note.type === 'SNIPPET_NOTE') {
|
|
||||||
const tabList = note.snippets.map((snippet, index) => {
|
|
||||||
const isActive = this.state.snippetIndex === index
|
|
||||||
return <div styleName={isActive
|
|
||||||
? 'tabList-item--active'
|
|
||||||
: 'tabList-item'
|
|
||||||
}
|
|
||||||
key={index}
|
|
||||||
>
|
|
||||||
<button styleName='tabList-item-button'
|
|
||||||
onClick={(e) => this.handleTabButtonClick(e, index)}
|
|
||||||
>
|
|
||||||
{snippet.name.trim().length > 0
|
|
||||||
? snippet.name
|
|
||||||
: <span styleName='tabList-item-unnamed'>
|
|
||||||
Unnamed
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
})
|
|
||||||
|
|
||||||
const viewList = note.snippets.map((snippet, index) => {
|
|
||||||
const isActive = this.state.snippetIndex === index
|
|
||||||
|
|
||||||
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
|
||||||
|
|
||||||
return <div styleName='tabView'
|
|
||||||
key={index}
|
|
||||||
style={{zIndex: isActive ? 5 : 4}}
|
|
||||||
>
|
|
||||||
{snippet.mode === 'markdown'
|
|
||||||
? <MarkdownEditor styleName='tabView-content'
|
|
||||||
config={config}
|
|
||||||
value={snippet.content}
|
|
||||||
ref={'code-' + index}
|
|
||||||
storageKey={note.storage}
|
|
||||||
/>
|
|
||||||
: <CodeEditor styleName='tabView-content'
|
|
||||||
mode={snippet.mode}
|
|
||||||
value={snippet.content}
|
|
||||||
theme={config.editor.theme}
|
|
||||||
fontFamily={config.editor.fontFamily}
|
|
||||||
fontSize={editorFontSize}
|
|
||||||
indentType={config.editor.indentType}
|
|
||||||
indentSize={editorIndentSize}
|
|
||||||
keyMap={config.editor.keyMap}
|
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
|
||||||
readOnly
|
|
||||||
ref={'code-' + index}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div styleName='root'>
|
|
||||||
<div styleName='description'>
|
|
||||||
<textarea styleName='description-textarea'
|
|
||||||
style={{
|
|
||||||
fontFamily: config.preview.fontFamily,
|
|
||||||
fontSize: parseInt(config.preview.fontSize, 10)
|
|
||||||
}}
|
|
||||||
ref='description'
|
|
||||||
placeholder='Description...'
|
|
||||||
value={note.description}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div styleName='tabList'>
|
|
||||||
{tabList}
|
|
||||||
</div>
|
|
||||||
{viewList}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MarkdownPreview styleName='root'
|
|
||||||
theme={config.ui.theme}
|
|
||||||
fontSize={config.preview.fontSize}
|
|
||||||
fontFamily={config.preview.fontFamily}
|
|
||||||
codeBlockTheme={config.preview.codeBlockTheme}
|
|
||||||
codeBlockFontFamily={config.editor.fontFamily}
|
|
||||||
lineNumber={config.preview.lineNumber}
|
|
||||||
indentSize={editorIndentSize}
|
|
||||||
value={note.content}
|
|
||||||
showCopyNotification={config.ui.showCopyNotification}
|
|
||||||
storagePath={storage.path}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NoteDetail.propTypes = {
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CSSModules(NoteDetail, styles)
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
@import('../main/Detail/DetailVars.styl')
|
|
||||||
|
|
||||||
.root
|
|
||||||
absolute top bottom left right
|
|
||||||
bottom 30px
|
|
||||||
margin 0 25px
|
|
||||||
height 100%
|
|
||||||
width 365px
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.description
|
|
||||||
absolute top left right
|
|
||||||
height 80px
|
|
||||||
box-sizing border-box
|
|
||||||
|
|
||||||
.description-textarea
|
|
||||||
display block
|
|
||||||
height 100%
|
|
||||||
width 100%
|
|
||||||
resize none
|
|
||||||
border none
|
|
||||||
padding 10px
|
|
||||||
line-height 1.6
|
|
||||||
box-sizing border-box
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.tabList
|
|
||||||
absolute left right
|
|
||||||
top 80px
|
|
||||||
box-sizing border-box
|
|
||||||
height 30px
|
|
||||||
display flex
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.tabList-item
|
|
||||||
position relative
|
|
||||||
flex 1
|
|
||||||
overflow hidden
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--hover-backgroundColorg
|
|
||||||
|
|
||||||
.tabList-item--active
|
|
||||||
@extend .tabList-item
|
|
||||||
border-bottom $ui-border
|
|
||||||
|
|
||||||
.tabList-item-button
|
|
||||||
width 100%
|
|
||||||
height 29px
|
|
||||||
overflow ellipsis
|
|
||||||
text-align left
|
|
||||||
padding-right 30px
|
|
||||||
padding-left 10px
|
|
||||||
border none
|
|
||||||
background-color transparent
|
|
||||||
transition 0.15s
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--hover-backgroundColor
|
|
||||||
|
|
||||||
.tabView
|
|
||||||
absolute left right bottom
|
|
||||||
top 130px
|
|
||||||
|
|
||||||
.tabView-content
|
|
||||||
absolute top left right bottom
|
|
||||||
box-sizing border-box
|
|
||||||
height 100%
|
|
||||||
width 100%
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
|
||||||
.root
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.description
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.description-textarea
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
color white
|
|
||||||
|
|
||||||
.tabList
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.tabList-item
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
|
||||||
|
|
||||||
.tabList-item-button
|
|
||||||
border none
|
|
||||||
color $ui-dark-text-color
|
|
||||||
background-color transparent
|
|
||||||
transition color background-color 0.15s
|
|
||||||
border-left 4px solid transparent
|
|
||||||
&:hover
|
|
||||||
color white
|
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
|
||||||
.root
|
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
|
||||||
|
|
||||||
.description
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
|
||||||
|
|
||||||
.description-textarea
|
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
|
||||||
color white
|
|
||||||
|
|
||||||
.tabList
|
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
|
||||||
|
|
||||||
.tabList-item
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
|
||||||
|
|
||||||
.tabList-item-button
|
|
||||||
border none
|
|
||||||
color $ui-dark-text-color
|
|
||||||
background-color transparent
|
|
||||||
transition color background-color 0.15s
|
|
||||||
border-left 4px solid transparent
|
|
||||||
&:hover
|
|
||||||
color white
|
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
|
||||||
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import NoteItem from 'browser/components/NoteItem'
|
|
||||||
import moment from 'moment'
|
|
||||||
|
|
||||||
class NoteList extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
range: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (this.props.search !== nextProps.search) {
|
|
||||||
this.resetScroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate () {
|
|
||||||
const { index } = this.props
|
|
||||||
|
|
||||||
if (index > -1) {
|
|
||||||
const list = this.refs.root
|
|
||||||
const item = list.childNodes[index]
|
|
||||||
if (item == null) return null
|
|
||||||
|
|
||||||
const overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
|
||||||
if (overflowBelow) {
|
|
||||||
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
|
|
||||||
}
|
|
||||||
const overflowAbove = list.scrollTop > item.offsetTop
|
|
||||||
if (overflowAbove) {
|
|
||||||
list.scrollTop = item.offsetTop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resetScroll () {
|
|
||||||
this.refs.root.scrollTop = 0
|
|
||||||
this.setState({
|
|
||||||
range: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleScroll (e) {
|
|
||||||
const { notes } = this.props
|
|
||||||
|
|
||||||
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 100 && notes.length > this.state.range * 10 + 10) {
|
|
||||||
this.setState({
|
|
||||||
range: this.state.range + 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { notes, index } = this.props
|
|
||||||
|
|
||||||
const notesList = notes
|
|
||||||
.slice(0, 10 + 10 * this.state.range)
|
|
||||||
.map((note, _index) => {
|
|
||||||
const isActive = (index === _index)
|
|
||||||
const key = `${note.storage}-${note.key}`
|
|
||||||
const dateDisplay = moment(note.updatedAt).fromNow()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NoteItem
|
|
||||||
isActive={isActive}
|
|
||||||
note={note}
|
|
||||||
dateDisplay={dateDisplay}
|
|
||||||
key={key}
|
|
||||||
handleNoteClick={(e) => this.props.handleNoteClick(e, _index)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<div className={this.props.className}
|
|
||||||
onScroll={(e) => this.handleScroll(e)}
|
|
||||||
ref='root'
|
|
||||||
>
|
|
||||||
{notesList}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NoteList.propTypes = {
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NoteList
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
|
||||||
import styles from './StorageSection.styl'
|
|
||||||
import StorageItem from 'browser/components/StorageItem'
|
|
||||||
|
|
||||||
class StorageSection extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isOpen: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleToggleButtonClick (e) {
|
|
||||||
this.setState({
|
|
||||||
isOpen: !this.state.isOpen
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleHeaderClick (e) {
|
|
||||||
const { storage } = this.props
|
|
||||||
this.props.handleStorageButtonClick(e, storage.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFolderClick (e, folder) {
|
|
||||||
const { storage } = this.props
|
|
||||||
this.props.handleFolderButtonClick(e, storage.key, folder.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { storage, filter } = this.props
|
|
||||||
const folderList = storage.folders
|
|
||||||
.map(folder => (
|
|
||||||
<StorageItem
|
|
||||||
key={folder.key}
|
|
||||||
isActive={filter.type === 'FOLDER' && filter.folder === folder.key && filter.storage === storage.key}
|
|
||||||
handleButtonClick={(e) => this.handleFolderClick(e, folder)}
|
|
||||||
folderName={folder.name}
|
|
||||||
folderColor={folder.color}
|
|
||||||
isFolded={false}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div styleName='root'>
|
|
||||||
<div styleName='header'>
|
|
||||||
<button styleName='header-toggleButton'
|
|
||||||
onClick={(e) => this.handleToggleButtonClick(e)}
|
|
||||||
>
|
|
||||||
<i className={this.state.isOpen
|
|
||||||
? 'fa fa-caret-down'
|
|
||||||
: 'fa fa-caret-right'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button styleName={filter.type === 'STORAGE' && filter.storage === storage.key
|
|
||||||
? 'header-name--active'
|
|
||||||
: 'header-name'
|
|
||||||
}
|
|
||||||
onClick={(e) => this.handleHeaderClick(e)}
|
|
||||||
>{storage.name}</button>
|
|
||||||
</div>
|
|
||||||
{this.state.isOpen &&
|
|
||||||
<div styleName='folderList'>
|
|
||||||
{folderList}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageSection.propTypes = {
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CSSModules(StorageSection, styles)
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
.root
|
|
||||||
position relative
|
|
||||||
|
|
||||||
.header
|
|
||||||
height 26px
|
|
||||||
.header-toggleButton
|
|
||||||
absolute top left
|
|
||||||
width 25px
|
|
||||||
height 26px
|
|
||||||
navButtonColor()
|
|
||||||
border none
|
|
||||||
outline none
|
|
||||||
.header-name
|
|
||||||
display block
|
|
||||||
height 26px
|
|
||||||
navButtonColor()
|
|
||||||
padding 0 10px 0 25px
|
|
||||||
font-size 14px
|
|
||||||
width 100%
|
|
||||||
text-align left
|
|
||||||
line-height 26px
|
|
||||||
box-sizing border-box
|
|
||||||
cursor pointer
|
|
||||||
outline none
|
|
||||||
|
|
||||||
.header-name--active
|
|
||||||
@extend .header-name
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color $ui-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
|
|
||||||
.folderList-item
|
|
||||||
display block
|
|
||||||
width 100%
|
|
||||||
height 26px
|
|
||||||
navButtonColor()
|
|
||||||
padding 0 10px 0 25px
|
|
||||||
font-size 14px
|
|
||||||
width 100%
|
|
||||||
text-align left
|
|
||||||
line-height 26px
|
|
||||||
box-sizing border-box
|
|
||||||
cursor pointer
|
|
||||||
outline none
|
|
||||||
padding 0 10px
|
|
||||||
margin 2px 0
|
|
||||||
height 26px
|
|
||||||
line-height 26px
|
|
||||||
border-width 0 0 0 6px
|
|
||||||
border-style solid
|
|
||||||
border-color transparent
|
|
||||||
|
|
||||||
.folderList-item--active
|
|
||||||
@extend .folderList-item
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color $ui-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
|
||||||
.header-toggleButton
|
|
||||||
navDarkButtonColor()
|
|
||||||
.header-name
|
|
||||||
navDarkButtonColor()
|
|
||||||
|
|
||||||
.header-name--active
|
|
||||||
@extend .header-name
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color $ui-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
|
|
||||||
.folderList-item
|
|
||||||
navDarkButtonColor()
|
|
||||||
border-width 0 0 0 6px
|
|
||||||
border-style solid
|
|
||||||
border-color transparent
|
|
||||||
|
|
||||||
.folderList-item--active
|
|
||||||
@extend .folderList-item
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color $ui-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
@@ -1,357 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
import React from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import { connect, Provider } from 'react-redux'
|
|
||||||
import _ from 'lodash'
|
|
||||||
import store from './store'
|
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
|
||||||
import styles from './FinderMain.styl'
|
|
||||||
import StorageSection from './StorageSection'
|
|
||||||
import NoteList from './NoteList'
|
|
||||||
import NoteDetail from './NoteDetail'
|
|
||||||
import SideNavFilter from 'browser/components/SideNavFilter'
|
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
|
||||||
require('!!style!css!stylus?sourceMap!../main/global.styl')
|
|
||||||
require('../lib/customMeta')
|
|
||||||
require('./ipcClient.js')
|
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const { remote } = electron
|
|
||||||
const { Menu } = remote
|
|
||||||
|
|
||||||
function hideFinder () {
|
|
||||||
const finderWindow = remote.getCurrentWindow()
|
|
||||||
if (global.process.platform === 'win32') {
|
|
||||||
finderWindow.blur()
|
|
||||||
finderWindow.hide()
|
|
||||||
}
|
|
||||||
if (global.process.platform === 'darwin') {
|
|
||||||
Menu.sendActionToFirstResponder('hide:')
|
|
||||||
}
|
|
||||||
remote.getCurrentWindow().hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
require('!!style!css!stylus?sourceMap!../styles/finder/index.styl')
|
|
||||||
|
|
||||||
class FinderMain extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
search: '',
|
|
||||||
index: 0,
|
|
||||||
filter: {
|
|
||||||
includeSnippet: true,
|
|
||||||
includeMarkdown: false,
|
|
||||||
type: 'ALL',
|
|
||||||
storage: null,
|
|
||||||
folder: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.focusHandler = (e) => this.handleWindowFocus(e)
|
|
||||||
this.blurHandler = (e) => this.handleWindowBlur(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.refs.search.focus()
|
|
||||||
window.addEventListener('focus', this.focusHandler)
|
|
||||||
window.addEventListener('blur', this.blurHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
window.removeEventListener('focus', this.focusHandler)
|
|
||||||
window.removeEventListener('blur', this.blurHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleWindowFocus (e) {
|
|
||||||
this.refs.search.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleWindowBlur (e) {
|
|
||||||
this.setState({
|
|
||||||
search: ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown (e) {
|
|
||||||
this.refs.search.focus()
|
|
||||||
if (e.keyCode === 9) {
|
|
||||||
if (e.shiftKey) {
|
|
||||||
this.refs.detail.selectPriorSnippet()
|
|
||||||
} else {
|
|
||||||
this.refs.detail.selectNextSnippet()
|
|
||||||
}
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
if (e.keyCode === 38) {
|
|
||||||
this.selectPrevious()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 40) {
|
|
||||||
this.selectNext()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
this.refs.detail.saveToClipboard()
|
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('COPY_FINDER')
|
|
||||||
hideFinder()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
if (e.keyCode === 27) {
|
|
||||||
hideFinder()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
if (e.keyCode === 91 || e.metaKey) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearchChange (e) {
|
|
||||||
this.setState({
|
|
||||||
search: e.target.value,
|
|
||||||
index: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
selectArticle (article) {
|
|
||||||
this.setState({currentArticle: article})
|
|
||||||
}
|
|
||||||
|
|
||||||
selectPrevious () {
|
|
||||||
if (this.state.index > 0) {
|
|
||||||
this.setState({
|
|
||||||
index: this.state.index - 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectNext () {
|
|
||||||
if (this.state.index < this.noteCount - 1) {
|
|
||||||
this.setState({
|
|
||||||
index: this.state.index + 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnlySnippetCheckboxChange (e) {
|
|
||||||
const { filter } = this.state
|
|
||||||
filter.includeSnippet = e.target.checked
|
|
||||||
this.setState({
|
|
||||||
filter: filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnlyMarkdownCheckboxChange (e) {
|
|
||||||
const { filter } = this.state
|
|
||||||
filter.includeMarkdown = e.target.checked
|
|
||||||
this.refs.list.resetScroll()
|
|
||||||
this.setState({
|
|
||||||
filter: filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAllNotesButtonClick (e) {
|
|
||||||
const { filter } = this.state
|
|
||||||
filter.type = 'ALL'
|
|
||||||
this.refs.list.resetScroll()
|
|
||||||
this.setState({
|
|
||||||
filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleStarredButtonClick (e) {
|
|
||||||
const { filter } = this.state
|
|
||||||
filter.type = 'STARRED'
|
|
||||||
this.refs.list.resetScroll()
|
|
||||||
this.setState({
|
|
||||||
filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleStorageButtonClick (e, storage) {
|
|
||||||
const { filter } = this.state
|
|
||||||
filter.type = 'STORAGE'
|
|
||||||
filter.storage = storage
|
|
||||||
this.refs.list.resetScroll()
|
|
||||||
this.setState({
|
|
||||||
filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFolderButtonClick (e, storage, folder) {
|
|
||||||
const { filter } = this.state
|
|
||||||
filter.type = 'FOLDER'
|
|
||||||
filter.storage = storage
|
|
||||||
filter.folder = folder
|
|
||||||
this.refs.list.resetScroll()
|
|
||||||
this.setState({
|
|
||||||
filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNoteClick (e, index) {
|
|
||||||
this.setState({
|
|
||||||
index
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { data, config } = this.props
|
|
||||||
const { filter, search } = this.state
|
|
||||||
const storageList = []
|
|
||||||
for (const key in data.storageMap) {
|
|
||||||
const storage = data.storageMap[key]
|
|
||||||
const item = (
|
|
||||||
<StorageSection
|
|
||||||
filter={filter}
|
|
||||||
storage={storage}
|
|
||||||
key={storage.key}
|
|
||||||
handleStorageButtonClick={(e, storage) => this.handleStorageButtonClick(e, storage)}
|
|
||||||
handleFolderButtonClick={(e, storage, folder) => this.handleFolderButtonClick(e, storage, folder)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
storageList.push(item)
|
|
||||||
}
|
|
||||||
let notes = []
|
|
||||||
let noteIds
|
|
||||||
|
|
||||||
switch (filter.type) {
|
|
||||||
case 'STORAGE':
|
|
||||||
noteIds = data.storageNoteMap[filter.storage]
|
|
||||||
break
|
|
||||||
case 'FOLDER':
|
|
||||||
noteIds = data.folderNoteMap[filter.storage + '-' + filter.folder]
|
|
||||||
break
|
|
||||||
case 'STARRED':
|
|
||||||
noteIds = data.starredSet
|
|
||||||
}
|
|
||||||
if (noteIds != null) {
|
|
||||||
noteIds.forEach((id) => {
|
|
||||||
notes.push(data.noteMap[id])
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
for (const key in data.noteMap) {
|
|
||||||
notes.push(data.noteMap[key])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filter.includeSnippet && filter.includeMarkdown) {
|
|
||||||
notes = notes.filter((note) => note.type === 'MARKDOWN_NOTE')
|
|
||||||
} else if (filter.includeSnippet && !filter.includeMarkdown) {
|
|
||||||
notes = notes.filter((note) => note.type === 'SNIPPET_NOTE')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search.trim().length > 0) {
|
|
||||||
const needle = new RegExp(_.escapeRegExp(search.trim()), 'i')
|
|
||||||
notes = notes.filter((note) => note.title.match(needle))
|
|
||||||
}
|
|
||||||
notes = notes
|
|
||||||
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
|
||||||
|
|
||||||
const activeNote = notes[this.state.index]
|
|
||||||
this.noteCount = notes.length
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='Finder'
|
|
||||||
styleName='root'
|
|
||||||
ref='-1'
|
|
||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
|
||||||
>
|
|
||||||
<div styleName='search'>
|
|
||||||
<input
|
|
||||||
styleName='search-input'
|
|
||||||
ref='search'
|
|
||||||
value={search}
|
|
||||||
placeholder='Search...'
|
|
||||||
onChange={(e) => this.handleSearchChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div styleName='result'>
|
|
||||||
<div styleName='result-nav'>
|
|
||||||
<div styleName='result-nav-filter'>
|
|
||||||
<div styleName='result-nav-filter-option'>
|
|
||||||
<label>
|
|
||||||
<input type='checkbox'
|
|
||||||
checked={filter.includeSnippet}
|
|
||||||
onChange={(e) => this.handleOnlySnippetCheckboxChange(e)}
|
|
||||||
/> Only Snippets</label>
|
|
||||||
</div>
|
|
||||||
<div styleName='result-nav-filter-option'>
|
|
||||||
<label>
|
|
||||||
<input type='checkbox'
|
|
||||||
checked={filter.includeMarkdown}
|
|
||||||
onChange={(e) => this.handleOnlyMarkdownCheckboxChange(e)}
|
|
||||||
/> Only Markdown</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SideNavFilter
|
|
||||||
isHomeActive={filter.type === 'ALL'}
|
|
||||||
handleAllNotesButtonClick={(e) => this.handleAllNotesButtonClick(e)}
|
|
||||||
isStarredActive={filter.type === 'STARRED'}
|
|
||||||
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
|
|
||||||
/>
|
|
||||||
<div styleName='result-nav-storageList'>
|
|
||||||
{storageList}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<NoteList styleName='result-list'
|
|
||||||
storageMap={data.storageMap}
|
|
||||||
notes={notes}
|
|
||||||
ref='list'
|
|
||||||
search={search}
|
|
||||||
index={this.state.index}
|
|
||||||
handleNoteClick={(e, _index) => this.handleNoteClick(e, _index)}
|
|
||||||
/>
|
|
||||||
<div styleName='result-detail'>
|
|
||||||
<NoteDetail
|
|
||||||
note={activeNote}
|
|
||||||
config={config}
|
|
||||||
ref='detail'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderMain.propTypes = {
|
|
||||||
dispatch: PropTypes.func
|
|
||||||
}
|
|
||||||
|
|
||||||
var Finder = connect((x) => x)(CSSModules(FinderMain, styles))
|
|
||||||
|
|
||||||
function refreshData () {
|
|
||||||
// let data = dataStore.getData(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactDOM.render((
|
|
||||||
<Provider store={store}>
|
|
||||||
<Finder />
|
|
||||||
</Provider>
|
|
||||||
), document.getElementById('content'), function () {
|
|
||||||
refreshData()
|
|
||||||
})
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
const nodeIpc = require('node-ipc')
|
|
||||||
const { remote, ipcRenderer } = require('electron')
|
|
||||||
const { app, Menu } = remote
|
|
||||||
const path = require('path')
|
|
||||||
const store = require('./store')
|
|
||||||
const consts = require('browser/lib/consts')
|
|
||||||
|
|
||||||
nodeIpc.config.id = 'finder'
|
|
||||||
nodeIpc.config.retry = 1500
|
|
||||||
nodeIpc.config.silent = true
|
|
||||||
|
|
||||||
function killFinder () {
|
|
||||||
const finderWindow = remote.getCurrentWindow()
|
|
||||||
finderWindow.removeAllListeners()
|
|
||||||
if (global.process.platform === 'darwin') {
|
|
||||||
// Only OSX has another app process.
|
|
||||||
nodeIpc.of.node.emit('quit-from-finder')
|
|
||||||
} else {
|
|
||||||
finderWindow.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleFinder () {
|
|
||||||
const finderWindow = remote.getCurrentWindow()
|
|
||||||
if (global.process.platform === 'darwin') {
|
|
||||||
if (finderWindow.isVisible()) {
|
|
||||||
finderWindow.hide()
|
|
||||||
Menu.sendActionToFirstResponder('hide:')
|
|
||||||
} else {
|
|
||||||
nodeIpc.of.node.emit('request-data-from-finder')
|
|
||||||
finderWindow.show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (finderWindow.isVisible()) {
|
|
||||||
finderWindow.blur()
|
|
||||||
finderWindow.hide()
|
|
||||||
} else {
|
|
||||||
nodeIpc.of.node.emit('request-data-from-finder')
|
|
||||||
finderWindow.show()
|
|
||||||
finderWindow.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeIpc.connectTo(
|
|
||||||
'node',
|
|
||||||
path.join(app.getPath('userData'), 'boostnote.service'),
|
|
||||||
function () {
|
|
||||||
nodeIpc.of.node.on('error', function (err) {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
nodeIpc.of.node.on('connect', function () {
|
|
||||||
console.log('Conncted successfully')
|
|
||||||
})
|
|
||||||
nodeIpc.of.node.on('disconnect', function () {
|
|
||||||
console.log('disconnected')
|
|
||||||
})
|
|
||||||
|
|
||||||
nodeIpc.of.node.on('open-finder', function () {
|
|
||||||
toggleFinder()
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcRenderer.on('open-finder-from-tray', function () {
|
|
||||||
toggleFinder()
|
|
||||||
})
|
|
||||||
ipcRenderer.on('open-main-from-tray', function () {
|
|
||||||
nodeIpc.of.node.emit('open-main-from-finder')
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcRenderer.on('quit-from-tray', function () {
|
|
||||||
nodeIpc.of.node.emit('quit-from-finder')
|
|
||||||
killFinder()
|
|
||||||
})
|
|
||||||
|
|
||||||
nodeIpc.of.node.on('throttle-data', function (payload) {
|
|
||||||
console.log('Received data from Main renderer')
|
|
||||||
store.default.dispatch({
|
|
||||||
type: 'THROTTLE_DATA',
|
|
||||||
data: payload
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
nodeIpc.of.node.on('config-renew', function (payload) {
|
|
||||||
const { config } = payload
|
|
||||||
if (config.ui.theme === 'dark') {
|
|
||||||
document.body.setAttribute('data-theme', 'dark')
|
|
||||||
} else if (config.ui.theme === 'white') {
|
|
||||||
document.body.setAttribute('data-theme', 'white')
|
|
||||||
} else if (config.ui.theme === 'solarized-dark') {
|
|
||||||
document.body.setAttribute('data-theme', 'solarized-dark')
|
|
||||||
} else {
|
|
||||||
document.body.setAttribute('data-theme', 'default')
|
|
||||||
}
|
|
||||||
|
|
||||||
let editorTheme = document.getElementById('editorTheme')
|
|
||||||
if (editorTheme == null) {
|
|
||||||
editorTheme = document.createElement('link')
|
|
||||||
editorTheme.setAttribute('id', 'editorTheme')
|
|
||||||
editorTheme.setAttribute('rel', 'stylesheet')
|
|
||||||
document.head.appendChild(editorTheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.editor.theme = consts.THEMES.some((theme) => theme === config.editor.theme)
|
|
||||||
? config.editor.theme
|
|
||||||
: 'default'
|
|
||||||
|
|
||||||
if (config.editor.theme !== 'default') {
|
|
||||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
|
|
||||||
}
|
|
||||||
|
|
||||||
store.default.dispatch({
|
|
||||||
type: 'SET_CONFIG',
|
|
||||||
config: config
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
nodeIpc.of.node.on('quit-finder-app', function () {
|
|
||||||
nodeIpc.of.node.emit('quit-finder-app-confirm')
|
|
||||||
killFinder()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const ipc = {}
|
|
||||||
|
|
||||||
module.exports = ipc
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { combineReducers, createStore } from 'redux'
|
|
||||||
import { routerReducer } from 'react-router-redux'
|
|
||||||
import { DEFAULT_CONFIG } from 'browser/main/lib/ConfigManager'
|
|
||||||
|
|
||||||
const defaultData = {
|
|
||||||
storageMap: {},
|
|
||||||
noteMap: {},
|
|
||||||
starredSet: [],
|
|
||||||
storageNoteMap: {},
|
|
||||||
folderNoteMap: {},
|
|
||||||
tagNoteMap: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function data (state = defaultData, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'THROTTLE_DATA':
|
|
||||||
console.log(action)
|
|
||||||
state = action.data
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
function config (state = DEFAULT_CONFIG, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'INIT_CONFIG':
|
|
||||||
case 'SET_CONFIG':
|
|
||||||
return Object.assign({}, state, action.config)
|
|
||||||
case 'SET_IS_SIDENAV_FOLDED':
|
|
||||||
state.isSideNavFolded = action.isFolded
|
|
||||||
return Object.assign({}, state)
|
|
||||||
case 'SET_ZOOM':
|
|
||||||
state.zoom = action.zoom
|
|
||||||
return Object.assign({}, state)
|
|
||||||
case 'SET_LIST_WIDTH':
|
|
||||||
state.listWidth = action.listWidth
|
|
||||||
return Object.assign({}, state)
|
|
||||||
case 'SET_UI':
|
|
||||||
return Object.assign({}, state, action.config)
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
const reducer = combineReducers({
|
|
||||||
data,
|
|
||||||
config,
|
|
||||||
routing: routerReducer
|
|
||||||
})
|
|
||||||
|
|
||||||
const store = createStore(reducer)
|
|
||||||
|
|
||||||
export default store
|
|
||||||
@@ -2,7 +2,7 @@ const { remote } = require('electron')
|
|||||||
const { Menu, MenuItem } = remote
|
const { Menu, MenuItem } = remote
|
||||||
|
|
||||||
function popup (templates) {
|
function popup (templates) {
|
||||||
let menu = new Menu()
|
const menu = new Menu()
|
||||||
templates.forEach((item) => {
|
templates.forEach((item) => {
|
||||||
menu.append(new MenuItem(item))
|
menu.append(new MenuItem(item))
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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']})
|
||||||
|
|||||||
@@ -31,10 +31,10 @@ var md = markdownit({
|
|||||||
return `<pre class="sequence">${str}</pre>`
|
return `<pre class="sequence">${str}</pre>`
|
||||||
}
|
}
|
||||||
return '<pre class="code">' +
|
return '<pre class="code">' +
|
||||||
createGutter(str) +
|
createGutter(str) +
|
||||||
'<code class="' + lang + '">' +
|
'<code class="' + lang + '">' +
|
||||||
str +
|
str +
|
||||||
'</code></pre>'
|
'</code></pre>'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
md.use(emoji, {
|
md.use(emoji, {
|
||||||
@@ -57,7 +57,7 @@ md.use(math, {
|
|||||||
blockRenderer: function (str) {
|
blockRenderer: function (str) {
|
||||||
let output = ''
|
let output = ''
|
||||||
try {
|
try {
|
||||||
output = katex.renderToString(str.trim(), {displayMode: true})
|
output = katex.renderToString(str.trim(), { displayMode: true })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
output = `<div class="katex-error">${err.message}</div>`
|
output = `<div class="katex-error">${err.message}</div>`
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,17 @@ md.use(require('markdown-it-named-headers'), {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
md.use(require('markdown-it-kbd'))
|
md.use(require('markdown-it-kbd'))
|
||||||
md.use(require('markdown-it-plantuml'))
|
|
||||||
|
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||||
|
md.use(require('markdown-it-plantuml'), '', {
|
||||||
|
generateSource: function (umlCode) {
|
||||||
|
const s = unescape(encodeURIComponent(umlCode))
|
||||||
|
const zippedCode = deflate.encode64(
|
||||||
|
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
|
||||||
|
)
|
||||||
|
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Override task item
|
// Override task item
|
||||||
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||||
@@ -110,7 +120,7 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
|||||||
state.line = nextLine
|
state.line = nextLine
|
||||||
|
|
||||||
token = state.push('paragraph_open', 'p', 1)
|
token = state.push('paragraph_open', 'p', 1)
|
||||||
token.map = [ startLine, state.line ]
|
token.map = [startLine, state.line]
|
||||||
|
|
||||||
if (state.parentType === 'list') {
|
if (state.parentType === 'list') {
|
||||||
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
||||||
@@ -121,7 +131,7 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
|||||||
|
|
||||||
token = state.push('inline', '', 0)
|
token = state.push('inline', '', 0)
|
||||||
token.content = content
|
token.content = content
|
||||||
token.map = [ startLine, state.line ]
|
token.map = [startLine, state.line]
|
||||||
token.children = []
|
token.children = []
|
||||||
|
|
||||||
token = state.push('paragraph_close', 'p', -1)
|
token = state.push('paragraph_close', 'p', -1)
|
||||||
|
|||||||
@@ -20,11 +20,13 @@
|
|||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
background-color $ui-dark-backgroundColor
|
background-color $ui-dark-backgroundColor
|
||||||
|
border-left 1px solid $ui-dark-borderColor
|
||||||
.empty-message
|
.empty-message
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.root
|
.root
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
border-left 1px solid $ui-solarized-dark-borderColor
|
||||||
.empty-message
|
.empty-message
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|||||||
@@ -3,20 +3,14 @@
|
|||||||
border solid 1px transparent
|
border solid 1px transparent
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
|
height 30px
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
user-select none
|
user-select none
|
||||||
margin-right 10px
|
margin-right 10px
|
||||||
&:hover
|
|
||||||
background-color $ui-button--hover-backgroundColor
|
|
||||||
|
|
||||||
.root--search, .root--focus
|
.root--search, .root--focus
|
||||||
@extend .root
|
@extend .root
|
||||||
background-color $ui-noteDetail-backgroundColor = #fff
|
|
||||||
border-color $ui-input--focus-borderColor
|
border-color $ui-input--focus-borderColor
|
||||||
width 154px
|
|
||||||
height 30px
|
|
||||||
&:hover
|
|
||||||
border-color $ui-input--focus-borderColor = #fff
|
|
||||||
|
|
||||||
.idle
|
.idle
|
||||||
position relative
|
position relative
|
||||||
|
|||||||
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 50px
|
||||||
|
right 70px
|
||||||
|
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 50px
|
||||||
|
right 20px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
.infoButton
|
.infoButton
|
||||||
padding 0px
|
padding 0px
|
||||||
|
|||||||
@@ -2,77 +2,97 @@ import PropTypes from 'prop-types'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './InfoPanel.styl'
|
import styles from './InfoPanel.styl'
|
||||||
|
import copy from 'copy-to-clipboard'
|
||||||
|
|
||||||
const InfoPanel = ({
|
class InfoPanel extends React.Component {
|
||||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type, print
|
copyNoteLink () {
|
||||||
}) => (
|
const {noteLink} = this.props
|
||||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
this.refs.noteLink.select()
|
||||||
<div>
|
copy(noteLink)
|
||||||
<p styleName='modification-date'>{updatedAt}</p>
|
}
|
||||||
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
render () {
|
||||||
|
const {
|
||||||
{type === 'SNIPPET_NOTE'
|
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
|
||||||
? ''
|
} = this.props
|
||||||
: <div styleName='count-wrap'>
|
return (
|
||||||
<div styleName='count-number'>
|
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||||
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
<div>
|
||||||
<p styleName='infoPanel-sub-count'>Words</p>
|
<p styleName='modification-date'>{updatedAt}</p>
|
||||||
|
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='count-number'>
|
|
||||||
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
|
<hr />
|
||||||
<p styleName='infoPanel-sub-count'>Letters</p>
|
|
||||||
|
{type === 'SNIPPET_NOTE'
|
||||||
|
? ''
|
||||||
|
: <div styleName='count-wrap'>
|
||||||
|
<div styleName='count-number'>
|
||||||
|
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
||||||
|
<p styleName='infoPanel-sub-count'>Words</p>
|
||||||
|
</div>
|
||||||
|
<div styleName='count-number'>
|
||||||
|
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
|
||||||
|
<p styleName='infoPanel-sub-count'>Letters</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{type === 'SNIPPET_NOTE'
|
||||||
|
? ''
|
||||||
|
: <hr />
|
||||||
|
}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{storageName}</p>
|
||||||
|
<p styleName='infoPanel-sub'>STORAGE</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{folderName}</p>
|
||||||
|
<p styleName='infoPanel-sub'>FOLDER</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{createdAt}</p>
|
||||||
|
<p styleName='infoPanel-sub'>CREATION DATE</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input styleName='infoPanel-noteLink' ref='noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
|
||||||
|
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
|
||||||
|
<i className='fa fa-clipboard' />
|
||||||
|
</button>
|
||||||
|
<p styleName='infoPanel-sub'>NOTE LINK</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div id='export-wrap'>
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||||
|
<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' />
|
||||||
|
<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' />
|
||||||
|
<p>Print</p>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)
|
||||||
|
}
|
||||||
{type === 'SNIPPET_NOTE'
|
}
|
||||||
? ''
|
|
||||||
: <hr />
|
|
||||||
}
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p styleName='infoPanel-default'>{storageName}</p>
|
|
||||||
<p styleName='infoPanel-sub'>STORAGE</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p styleName='infoPanel-default'>{folderName}</p>
|
|
||||||
<p styleName='infoPanel-sub'>FOLDER</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p styleName='infoPanel-default'>{createdAt}</p>
|
|
||||||
<p styleName='infoPanel-sub'>CREATION DATE</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<input styleName='infoPanel-noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
|
|
||||||
<p styleName='infoPanel-sub'>NOTE LINK</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<div id='export-wrap'>
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
|
||||||
<i className='fa fa-file-code-o fa-fw' />
|
|
||||||
<p>.md</p>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
|
||||||
<i className='fa fa-file-text-o fa-fw' />
|
|
||||||
<p>.txt</p>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => print(e)}>
|
|
||||||
<i className='fa fa-print fa-fw' />
|
|
||||||
<p>Print</p>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
InfoPanel.propTypes = {
|
InfoPanel.propTypes = {
|
||||||
storageName: PropTypes.string.isRequired,
|
storageName: PropTypes.string.isRequired,
|
||||||
@@ -82,6 +102,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,
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
.control-infoButton-panel
|
.control-infoButton-panel
|
||||||
z-index 200
|
z-index 200
|
||||||
margin-top 0px
|
margin-top 0px
|
||||||
right 0
|
right 25px
|
||||||
position absolute
|
position absolute
|
||||||
padding 20px 25px 0 25px
|
padding 20px 25px 0 25px
|
||||||
width 300px
|
width 300px
|
||||||
height 350px
|
|
||||||
overflow auto
|
overflow auto
|
||||||
background-color $ui-noteList-backgroundColor
|
background-color $ui-noteList-backgroundColor
|
||||||
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)
|
||||||
@@ -41,12 +40,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%
|
||||||
@@ -70,26 +69,41 @@
|
|||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
|
|
||||||
.infoPanel-sub
|
.infoPanel-sub
|
||||||
font-size 14px
|
font-size 12px
|
||||||
|
font-weight 600
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
padding-bottom 8px
|
padding-bottom 8px
|
||||||
|
|
||||||
.infoPanel-noteLink
|
.infoPanel-noteLink
|
||||||
padding-right 5px
|
padding-right 5px
|
||||||
width 200px
|
width 210px
|
||||||
height 25px
|
height 25px
|
||||||
margin-bottom 6px
|
margin 6px 0
|
||||||
|
|
||||||
|
.infoPanel-copyButton
|
||||||
|
outline none
|
||||||
|
font-size 16px
|
||||||
|
color #A0A0A0
|
||||||
|
background-color transparent
|
||||||
|
border none
|
||||||
|
margin 0 5px
|
||||||
|
border-radius 5px
|
||||||
|
cursor pointer
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
background-color alpha($ui-button--hover-backgroundColor, 30%)
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.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 +174,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)
|
||||||
|
|||||||
@@ -18,18 +18,16 @@ 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 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'
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const { remote } = electron
|
|
||||||
const { dialog } = remote
|
|
||||||
|
|
||||||
class MarkdownNoteDetail extends React.Component {
|
class MarkdownNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -77,17 +75,22 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -173,41 +176,44 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
ee.emit('export:save-text')
|
ee.emit('export:save-text')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportAsHtml () {
|
||||||
|
ee.emit('export:save-html')
|
||||||
|
}
|
||||||
|
|
||||||
handleTrashButtonClick (e) {
|
handleTrashButtonClick (e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
const { isTrashed } = note
|
const { isTrashed } = note
|
||||||
|
const { confirmDeletion } = this.props
|
||||||
|
|
||||||
if (isTrashed) {
|
if (isTrashed) {
|
||||||
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
if (confirmDeletion(true)) {
|
||||||
type: 'warning',
|
const {note, dispatch} = this.props
|
||||||
message: 'Confirm note deletion',
|
dataApi
|
||||||
detail: 'This will permanently remove this note.',
|
.deleteNote(note.storage, note.key)
|
||||||
buttons: ['Confirm', 'Cancel']
|
.then((data) => {
|
||||||
})
|
const dispatchHandler = () => {
|
||||||
if (dialogueButtonIndex === 1) return
|
dispatch({
|
||||||
const { note, dispatch } = this.props
|
type: 'DELETE_NOTE',
|
||||||
dataApi
|
storageKey: data.storageKey,
|
||||||
.deleteNote(note.storage, note.key)
|
noteKey: data.noteKey
|
||||||
.then((data) => {
|
})
|
||||||
const dispatchHandler = () => {
|
}
|
||||||
dispatch({
|
ee.once('list:moved', dispatchHandler)
|
||||||
type: 'DELETE_NOTE',
|
})
|
||||||
storageKey: data.storageKey,
|
}
|
||||||
noteKey: data.noteKey
|
|
||||||
})
|
|
||||||
}
|
|
||||||
ee.once('list:moved', dispatchHandler)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
note.isTrashed = true
|
if (confirmDeletion()) {
|
||||||
|
note.isTrashed = true
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
note
|
note
|
||||||
}, () => {
|
}, () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ee.emit('list:next')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ee.emit('list:next')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUndoButtonClick (e) {
|
handleUndoButtonClick (e) {
|
||||||
@@ -283,7 +289,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
config={config}
|
config={config}
|
||||||
value={note.content}
|
value={note.content}
|
||||||
storageKey={note.storage}
|
storageKey={note.storage}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={this.handleUpdateContent.bind(this)}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
/>
|
/>
|
||||||
} else {
|
} else {
|
||||||
@@ -292,7 +298,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
config={config}
|
config={config}
|
||||||
value={note.content}
|
value={note.content}
|
||||||
storageKey={note.storage}
|
storageKey={note.storage}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={this.handleUpdateContent.bind(this)}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -332,6 +338,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}
|
||||||
/>
|
/>
|
||||||
@@ -352,27 +359,14 @@ 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={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>
|
|
||||||
|
|
||||||
<TodoListPercentage
|
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||||
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<InfoButton
|
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<StarButton
|
<StarButton
|
||||||
onClick={(e) => this.handleStarButtonClick(e)}
|
onClick={(e) => this.handleStarButtonClick(e)}
|
||||||
isActive={note.isStarred}
|
isActive={note.isStarred}
|
||||||
@@ -386,6 +380,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
||||||
>
|
>
|
||||||
<img styleName='iconInfo' src={imgSrc} />
|
<img styleName='iconInfo' src={imgSrc} />
|
||||||
|
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -393,14 +388,14 @@ 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-full.svg' />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||||
|
|
||||||
|
<InfoButton
|
||||||
|
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||||
|
/>
|
||||||
|
|
||||||
<InfoPanel
|
<InfoPanel
|
||||||
storageName={currentOption.storage.name}
|
storageName={currentOption.storage.name}
|
||||||
folderName={currentOption.folder.name}
|
folderName={currentOption.folder.name}
|
||||||
@@ -409,6 +404,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}
|
||||||
@@ -447,7 +443,8 @@ MarkdownNoteDetail.propTypes = {
|
|||||||
style: PropTypes.shape({
|
style: PropTypes.shape({
|
||||||
left: PropTypes.number
|
left: PropTypes.number
|
||||||
}),
|
}),
|
||||||
ignorePreviewPointerEvents: PropTypes.bool
|
ignorePreviewPointerEvents: PropTypes.bool,
|
||||||
|
confirmDeletion: PropTypes.bool.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(MarkdownNoteDetail, styles)
|
export default CSSModules(MarkdownNoteDetail, styles)
|
||||||
|
|||||||
@@ -12,46 +12,38 @@
|
|||||||
padding-bottom 3px
|
padding-bottom 3px
|
||||||
|
|
||||||
.control-lockButton
|
.control-lockButton
|
||||||
top 150px
|
|
||||||
topBarButtonRight()
|
topBarButtonRight()
|
||||||
|
position absolute
|
||||||
|
right 225px
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 35px
|
||||||
|
right -10px
|
||||||
|
width 50px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
.trashed-infopanel
|
.trashed-infopanel
|
||||||
top 40px
|
|
||||||
position relative
|
position relative
|
||||||
|
|
||||||
.control-fullScreenButton
|
|
||||||
top 80px
|
|
||||||
topBarButtonRight()
|
|
||||||
|
|
||||||
.body
|
.body
|
||||||
absolute left right
|
absolute left right
|
||||||
left 0
|
left 0
|
||||||
right 0
|
right 0
|
||||||
top $info-height + $info-margin-under-border
|
top $info-height + $info-margin-under-border
|
||||||
bottom $statusBar-height
|
bottom $statusBar-height
|
||||||
margin 0 45px
|
margin 0 30px
|
||||||
.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 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"]
|
body[data-theme="white"]
|
||||||
.root
|
.root
|
||||||
@@ -73,27 +65,8 @@ body[data-theme="dark"]
|
|||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
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"]
|
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
|
||||||
|
|
||||||
.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
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
@import('DetailVars')
|
@import('DetailVars')
|
||||||
|
|
||||||
$info-height = 50px
|
$info-height = 60px
|
||||||
$info-margin-under-border = 30px
|
$info-margin-under-border = 30px
|
||||||
|
|
||||||
.info
|
.info
|
||||||
@@ -8,11 +8,11 @@ $info-margin-under-border = 30px
|
|||||||
left 0
|
left 0
|
||||||
right 0
|
right 0
|
||||||
height $info-height
|
height $info-height
|
||||||
border-bottom 1px solid #eee
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
width 100%
|
width 100%
|
||||||
display flex
|
display flex
|
||||||
align-items center
|
align-items center
|
||||||
|
padding 0 20px
|
||||||
|
|
||||||
.info-left
|
.info-left
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
@@ -20,7 +20,6 @@ $info-margin-under-border = 30px
|
|||||||
display flex
|
display flex
|
||||||
align-items center
|
align-items center
|
||||||
|
|
||||||
|
|
||||||
.info-left-top-folderSelect
|
.info-left-top-folderSelect
|
||||||
display flex
|
display flex
|
||||||
align-items center
|
align-items center
|
||||||
@@ -45,12 +44,9 @@ $info-margin-under-border = 30px
|
|||||||
color $ui-button--color
|
color $ui-button--color
|
||||||
|
|
||||||
.info-right
|
.info-right
|
||||||
position absolute
|
|
||||||
right 40px
|
|
||||||
top 60px
|
|
||||||
bottom 1px
|
|
||||||
padding-left 30px
|
|
||||||
z-index 101
|
z-index 101
|
||||||
|
display inline-flex
|
||||||
|
margin-top 3px
|
||||||
|
|
||||||
.undo-button
|
.undo-button
|
||||||
width 34px
|
width 34px
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -175,38 +176,37 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
handleTrashButtonClick (e) {
|
handleTrashButtonClick (e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
const { isTrashed } = note
|
const { isTrashed } = note
|
||||||
|
const { confirmDeletion } = this.props
|
||||||
|
|
||||||
if (isTrashed) {
|
if (isTrashed) {
|
||||||
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
if (confirmDeletion(true)) {
|
||||||
type: 'warning',
|
const {note, dispatch} = this.props
|
||||||
message: 'Confirm note deletion',
|
dataApi
|
||||||
detail: 'This will permanently remove this note.',
|
.deleteNote(note.storage, note.key)
|
||||||
buttons: ['Confirm', 'Cancel']
|
.then((data) => {
|
||||||
})
|
const dispatchHandler = () => {
|
||||||
if (dialogueButtonIndex === 1) return
|
dispatch({
|
||||||
const { note, dispatch } = this.props
|
type: 'DELETE_NOTE',
|
||||||
dataApi
|
storageKey: data.storageKey,
|
||||||
.deleteNote(note.storage, note.key)
|
noteKey: data.noteKey
|
||||||
.then((data) => {
|
})
|
||||||
const dispatchHandler = () => {
|
}
|
||||||
dispatch({
|
ee.once('list:moved', dispatchHandler)
|
||||||
type: 'DELETE_NOTE',
|
})
|
||||||
storageKey: data.storageKey,
|
}
|
||||||
noteKey: data.noteKey
|
|
||||||
})
|
|
||||||
}
|
|
||||||
ee.once('list:moved', dispatchHandler)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
note.isTrashed = true
|
if (confirmDeletion()) {
|
||||||
|
note.isTrashed = true
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
note
|
note
|
||||||
}, () => {
|
}, () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ee.emit('list:next')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ee.emit('list:next')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUndoButtonClick (e) {
|
handleUndoButtonClick (e) {
|
||||||
@@ -380,7 +380,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)
|
||||||
@@ -564,6 +564,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
fontSize={editorFontSize}
|
fontSize={editorFontSize}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
onChange={(e) => this.handleCodeChange(index)(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>
|
||||||
@@ -625,21 +627,23 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<InfoButton
|
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<StarButton
|
<StarButton
|
||||||
onClick={(e) => this.handleStarButtonClick(e)}
|
onClick={(e) => this.handleStarButtonClick(e)}
|
||||||
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-full.svg' />
|
||||||
|
<span styleName='tooltip'>Fullscreen</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||||
|
|
||||||
|
<InfoButton
|
||||||
|
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||||
|
/>
|
||||||
|
|
||||||
<InfoPanel
|
<InfoPanel
|
||||||
storageName={currentOption.storage.name}
|
storageName={currentOption.storage.name}
|
||||||
folderName={currentOption.folder.name}
|
folderName={currentOption.folder.name}
|
||||||
@@ -729,7 +733,8 @@ SnippetNoteDetail.propTypes = {
|
|||||||
style: PropTypes.shape({
|
style: PropTypes.shape({
|
||||||
left: PropTypes.number
|
left: PropTypes.number
|
||||||
}),
|
}),
|
||||||
ignorePreviewPointerEvents: PropTypes.bool
|
ignorePreviewPointerEvents: PropTypes.bool,
|
||||||
|
confirmDeletion: PropTypes.bool.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(SnippetNoteDetail, styles)
|
export default CSSModules(SnippetNoteDetail, styles)
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
|
|
||||||
.body
|
.body
|
||||||
absolute left right
|
absolute left right
|
||||||
left $snippet-note-detail-left-margin
|
margin 0 30px
|
||||||
right $snippet-note-detail-right-margin
|
|
||||||
top $info-height + $info-margin-under-border
|
top $info-height + $info-margin-under-border
|
||||||
bottom $statusBar-height
|
bottom $statusBar-height
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
@@ -70,6 +69,21 @@
|
|||||||
top 80px
|
top 80px
|
||||||
margin-bottom 10px
|
margin-bottom 10px
|
||||||
topBarButtonRight()
|
topBarButtonRight()
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 50px
|
||||||
|
right 70px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
body[data-theme="white"]
|
body[data-theme="white"]
|
||||||
.root
|
.root
|
||||||
|
|||||||
@@ -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 50px
|
||||||
|
right 115px
|
||||||
|
width 40px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
.root--active
|
.root--active
|
||||||
@extend .root
|
@extend .root
|
||||||
|
|||||||
@@ -64,7 +64,8 @@ class TagSelect extends React.Component {
|
|||||||
submitTag () {
|
submitTag () {
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
||||||
let { value } = this.props
|
let { value } = this.props
|
||||||
const newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
|
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
|
||||||
|
newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag
|
||||||
|
|
||||||
if (newTag.length <= 0) {
|
if (newTag.length <= 0) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
width 100%
|
width 100%
|
||||||
overflow-x scroll
|
overflow-x scroll
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
margin-right 10px
|
margin-top 31px
|
||||||
|
position absolute
|
||||||
|
|
||||||
.root::-webkit-scrollbar
|
.root::-webkit-scrollbar
|
||||||
display none
|
display none
|
||||||
|
|||||||
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-markdown-off-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-split-on-active.svg'} />
|
||||||
|
</div>
|
||||||
|
<span styleName='tooltip'>Toggle Mode</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
ToggleModeButton.propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
editorType: PropTypes.string.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(ToggleModeButton, styles)
|
||||||
58
browser/main/Detail/ToggleModeButton.styl
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
.control-toggleModeButton
|
||||||
|
height 25px
|
||||||
|
border-radius 50px
|
||||||
|
background-color #F4F4F4
|
||||||
|
width 52px
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
position absolute
|
||||||
|
right 165px
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
width 33px
|
||||||
|
height 24px
|
||||||
|
box-shadow 2px 0px 7px #eee
|
||||||
|
z-index 1
|
||||||
|
|
||||||
|
div
|
||||||
|
width 40px
|
||||||
|
height 100%
|
||||||
|
border-radius 50%
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 33px
|
||||||
|
left -10px
|
||||||
|
z-index 200
|
||||||
|
width 80px
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.control-fullScreenButton
|
||||||
|
topBarButtonDark()
|
||||||
|
|
||||||
|
.control-toggleModeButton
|
||||||
|
background-color #3A404C
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
box-shadow 2px 0px 7px #444444
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.control-toggleModeButton
|
||||||
|
background-color #002B36
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
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 50px
|
||||||
|
right 50px
|
||||||
|
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
|
||||||
|
|||||||
@@ -32,6 +32,26 @@ class Detail extends React.Component {
|
|||||||
ee.off('detail:delete', this.deleteHandler)
|
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 () {
|
render () {
|
||||||
const { location, data, config } = this.props
|
const { location, data, config } = this.props
|
||||||
let note = null
|
let note = null
|
||||||
@@ -64,6 +84,7 @@ class Detail extends React.Component {
|
|||||||
<SnippetNoteDetail
|
<SnippetNoteDetail
|
||||||
note={note}
|
note={note}
|
||||||
config={config}
|
config={config}
|
||||||
|
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
|
||||||
ref='root'
|
ref='root'
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
@@ -80,6 +101,7 @@ class Detail extends React.Component {
|
|||||||
<MarkdownNoteDetail
|
<MarkdownNoteDetail
|
||||||
note={note}
|
note={note}
|
||||||
config={config}
|
config={config}
|
||||||
|
confirmDeletion={(permanent) => this.confirmDeletion(permanent)}
|
||||||
ref='root'
|
ref='root'
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ import Detail from './Detail'
|
|||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
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 mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
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 {
|
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 () {
|
componentDidMount () {
|
||||||
const { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
|
||||||
@@ -71,7 +159,7 @@ class Main extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (data.storages.length < 1) {
|
if (data.storages.length < 1) {
|
||||||
modal.open(InitModal)
|
this.init()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class NewNoteButton extends React.Component {
|
|||||||
onClick={(e) => this.handleNewNoteButtonClick(e)}>
|
onClick={(e) => this.handleNewNoteButtonClick(e)}>
|
||||||
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
|
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
|
||||||
<span styleName='control-newNoteButton-tooltip'>
|
<span styleName='control-newNoteButton-tooltip'>
|
||||||
Make a Note {OSX ? '⌘' : '^'} + n
|
Make a note {OSX ? '⌘' : 'Ctrl'} + N
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,10 +11,8 @@ import NoteItem from 'browser/components/NoteItem'
|
|||||||
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
||||||
import searchFromNotes from 'browser/lib/search'
|
import searchFromNotes from 'browser/lib/search'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
import { hashHistory } from 'react-router'
|
import { hashHistory } from 'react-router'
|
||||||
import markdown from 'browser/lib/markdownTextHelper'
|
|
||||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
|
||||||
import store from 'browser/main/store'
|
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
@@ -171,9 +169,8 @@ class NoteList extends React.Component {
|
|||||||
if (this.notes == null || this.notes.length === 0) {
|
if (this.notes == null || this.notes.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let { router } = this.context
|
let { selectedNoteKeys } = this.state
|
||||||
let { location } = this.props
|
const { shiftKeyDown } = this.state
|
||||||
let { selectedNoteKeys, shiftKeyDown } = this.state
|
|
||||||
|
|
||||||
let targetIndex = this.getTargetIndex()
|
let targetIndex = this.getTargetIndex()
|
||||||
|
|
||||||
@@ -199,9 +196,8 @@ class NoteList extends React.Component {
|
|||||||
if (this.notes == null || this.notes.length === 0) {
|
if (this.notes == null || this.notes.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let { router } = this.context
|
let { selectedNoteKeys } = this.state
|
||||||
let { location } = this.props
|
const { shiftKeyDown } = this.state
|
||||||
let { selectedNoteKeys, shiftKeyDown } = this.state
|
|
||||||
|
|
||||||
let targetIndex = this.getTargetIndex()
|
let targetIndex = this.getTargetIndex()
|
||||||
const isTargetLastNote = targetIndex === this.notes.length - 1
|
const isTargetLastNote = targetIndex === this.notes.length - 1
|
||||||
@@ -235,24 +231,13 @@ 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')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNoteListKeyDown (e) {
|
handleNoteListKeyDown (e) {
|
||||||
const { shiftKeyDown } = this.state
|
|
||||||
if (e.metaKey || e.ctrlKey) return true
|
if (e.metaKey || e.ctrlKey) return true
|
||||||
|
|
||||||
if (e.keyCode === 65 && !e.shiftKey) {
|
if (e.keyCode === 65 && !e.shiftKey) {
|
||||||
@@ -294,7 +279,7 @@ class NoteList extends React.Component {
|
|||||||
getNotes () {
|
getNotes () {
|
||||||
const { data, params, location } = this.props
|
const { data, params, location } = this.props
|
||||||
|
|
||||||
if (location.pathname.match(/\/home/) || location.pathname.match(/\alltags/)) {
|
if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
|
||||||
const allNotes = data.noteMap.map((note) => note)
|
const allNotes = data.noteMap.map((note) => note)
|
||||||
this.contextNotes = allNotes
|
this.contextNotes = allNotes
|
||||||
return allNotes
|
return allNotes
|
||||||
@@ -365,9 +350,10 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleNoteClick (e, uniqueKey) {
|
handleNoteClick (e, uniqueKey) {
|
||||||
let { router } = this.context
|
const { router } = this.context
|
||||||
let { location } = this.props
|
const { location } = this.props
|
||||||
let { shiftKeyDown, selectedNoteKeys } = this.state
|
let { selectedNoteKeys } = this.state
|
||||||
|
const { shiftKeyDown } = this.state
|
||||||
|
|
||||||
if (shiftKeyDown && selectedNoteKeys.includes(uniqueKey)) {
|
if (shiftKeyDown && selectedNoteKeys.includes(uniqueKey)) {
|
||||||
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
|
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
|
||||||
@@ -453,6 +439,7 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
|
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
|
||||||
const deleteLabel = 'Delete Note'
|
const deleteLabel = 'Delete Note'
|
||||||
|
const cloneNote = 'Clone Note'
|
||||||
|
|
||||||
const menu = new Menu()
|
const menu = new Menu()
|
||||||
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
|
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
|
||||||
@@ -465,6 +452,10 @@ class NoteList extends React.Component {
|
|||||||
label: deleteLabel,
|
label: deleteLabel,
|
||||||
click: this.deleteNote
|
click: this.deleteNote
|
||||||
}))
|
}))
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: cloneNote,
|
||||||
|
click: this.cloneNote.bind(this)
|
||||||
|
}))
|
||||||
menu.popup()
|
menu.popup()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -555,6 +546,42 @@ class NoteList extends React.Component {
|
|||||||
this.setState({ selectedNoteKeys: [] })
|
this.setState({ selectedNoteKeys: [] })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloneNote () {
|
||||||
|
const { selectedNoteKeys } = this.state
|
||||||
|
const { dispatch, location } = this.props
|
||||||
|
const { storage, folder } = this.resolveTargetFolder()
|
||||||
|
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||||
|
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||||
|
const firstNote = selectedNotes[0]
|
||||||
|
const eventName = firstNote.type === 'MARKDOWN_NOTE' ? 'ADD_MARKDOWN' : 'ADD_SNIPPET'
|
||||||
|
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent(eventName)
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||||
|
dataApi
|
||||||
|
.createNote(storage.key, {
|
||||||
|
type: firstNote.type,
|
||||||
|
folder: folder.key,
|
||||||
|
title: firstNote.title + ' copy',
|
||||||
|
content: firstNote.content
|
||||||
|
})
|
||||||
|
.then((note) => {
|
||||||
|
const uniqueKey = note.storage + '-' + note.key
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedNoteKeys: [uniqueKey]
|
||||||
|
})
|
||||||
|
|
||||||
|
hashHistory.push({
|
||||||
|
pathname: location.pathname,
|
||||||
|
query: {key: uniqueKey}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
importFromFile () {
|
importFromFile () {
|
||||||
const options = {
|
const options = {
|
||||||
filters: [
|
filters: [
|
||||||
@@ -592,7 +619,7 @@ class NoteList extends React.Component {
|
|||||||
const newNote = {
|
const newNote = {
|
||||||
content: content,
|
content: content,
|
||||||
folder: folder.key,
|
folder: folder.key,
|
||||||
title: markdown.strip(findNoteTitle(content)),
|
title: path.basename(filepath, path.extname(filepath)),
|
||||||
type: 'MARKDOWN_NOTE',
|
type: 'MARKDOWN_NOTE',
|
||||||
createdAt: birthtime,
|
createdAt: birthtime,
|
||||||
updatedAt: mtime
|
updatedAt: mtime
|
||||||
@@ -652,9 +679,10 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { location, notes, config, dispatch } = this.props
|
const { location, config } = this.props
|
||||||
let { selectedNoteKeys } = this.state
|
let { notes } = this.props
|
||||||
let sortFunc = config.sortBy === 'CREATED_AT'
|
const { selectedNoteKeys } = this.state
|
||||||
|
const sortFunc = config.sortBy === 'CREATED_AT'
|
||||||
? sortByCreatedAt
|
? sortByCreatedAt
|
||||||
: config.sortBy === 'ALPHABETICAL'
|
: config.sortBy === 'ALPHABETICAL'
|
||||||
? sortByAlphabetical
|
? sortByAlphabetical
|
||||||
@@ -699,7 +727,6 @@ class NoteList extends React.Component {
|
|||||||
config.sortBy === 'CREATED_AT'
|
config.sortBy === 'CREATED_AT'
|
||||||
? note.createdAt : note.updatedAt
|
? note.createdAt : note.updatedAt
|
||||||
).fromNow('D')
|
).fromNow('D')
|
||||||
const key = `${note.storage}-${note.key}`
|
|
||||||
|
|
||||||
if (isDefault) {
|
if (isDefault) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
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
|
||||||
|
|||||||
@@ -8,11 +8,10 @@ import CreateFolderModal from 'browser/main/modals/CreateFolderModal'
|
|||||||
import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
|
import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
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 _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { Menu, dialog } = remote
|
||||||
|
|
||||||
class StorageItem extends React.Component {
|
class StorageItem extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -24,18 +23,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 +90,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 +131,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)}
|
||||||
|
|||||||
@@ -21,19 +21,20 @@
|
|||||||
color white
|
color white
|
||||||
|
|
||||||
.zoom
|
.zoom
|
||||||
navButtonColor()
|
display none
|
||||||
color rgba(0,0,0,.54)
|
// navButtonColor()
|
||||||
height 20px
|
// color rgba(0,0,0,.54)
|
||||||
display flex
|
// height 20px
|
||||||
padding 0
|
// display flex
|
||||||
align-items center
|
// padding 0
|
||||||
background-color transparent
|
// align-items center
|
||||||
&:hover
|
// background-color transparent
|
||||||
color $ui-active-color
|
// &:hover
|
||||||
&:active
|
// color $ui-active-color
|
||||||
color $ui-active-color
|
// &:active
|
||||||
span
|
// color $ui-active-color
|
||||||
margin-left 5px
|
// span
|
||||||
|
// margin-left 5px
|
||||||
|
|
||||||
.update
|
.update
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ body[data-theme="dark"]
|
|||||||
.CodeMirror
|
.CodeMirror
|
||||||
font-family inherit !important
|
font-family inherit !important
|
||||||
line-height 1.4em
|
line-height 1.4em
|
||||||
height 96%
|
height 100%
|
||||||
.CodeMirror > div > textarea
|
.CodeMirror > div > textarea
|
||||||
margin-bottom -1em
|
margin-bottom -1em
|
||||||
.CodeMirror-focused .CodeMirror-selected
|
.CodeMirror-focused .CodeMirror-selected
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export const DEFAULT_CONFIG = {
|
|||||||
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
||||||
amaEnabled: true,
|
amaEnabled: true,
|
||||||
hotkey: {
|
hotkey: {
|
||||||
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
|
|
||||||
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
|
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
@@ -34,6 +33,7 @@ export const DEFAULT_CONFIG = {
|
|||||||
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas',
|
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas',
|
||||||
indentType: 'space',
|
indentType: 'space',
|
||||||
indentSize: '2',
|
indentSize: '2',
|
||||||
|
displayLineNumbers: true,
|
||||||
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
|
||||||
scrollPastEnd: false,
|
scrollPastEnd: false,
|
||||||
type: 'SPLIT'
|
type: 'SPLIT'
|
||||||
@@ -46,7 +46,8 @@ export const DEFAULT_CONFIG = {
|
|||||||
latexInlineOpen: '$',
|
latexInlineOpen: '$',
|
||||||
latexInlineClose: '$',
|
latexInlineClose: '$',
|
||||||
latexBlockOpen: '$$',
|
latexBlockOpen: '$$',
|
||||||
latexBlockClose: '$$'
|
latexBlockClose: '$$',
|
||||||
|
scrollPastEnd: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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'),
|
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'),
|
||||||
|
|||||||
@@ -24,20 +24,6 @@ nodeIpc.connectTo(
|
|||||||
nodeIpc.of.node.on('disconnect', function () {
|
nodeIpc.of.node.on('disconnect', function () {
|
||||||
console.log('disconnected')
|
console.log('disconnected')
|
||||||
})
|
})
|
||||||
|
|
||||||
nodeIpc.of.node.on('request-data-from-finder', function () {
|
|
||||||
console.log('throttle')
|
|
||||||
var { data } = store.getState()
|
|
||||||
console.log(data.starredSet.toJS())
|
|
||||||
nodeIpc.of.node.emit('throttle-data', {
|
|
||||||
storageMap: data.storageMap.toJS(),
|
|
||||||
noteMap: data.noteMap.toJS(),
|
|
||||||
starredSet: data.starredSet.toJS(),
|
|
||||||
storageNoteMap: data.storageNoteMap.toJS(),
|
|
||||||
folderNoteMap: data.folderNoteMap.toJS(),
|
|
||||||
tagNoteMap: data.tagNoteMap.toJS()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
width 490px
|
width 490px
|
||||||
padding 0 5px
|
padding 0 5px
|
||||||
margin 10px 0
|
margin 10px 0
|
||||||
border 1px solid #C9C9C9 // TODO: use variable.
|
border 1px solid $ui-input--create-folder-modal
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
background-color transparent
|
background-color transparent
|
||||||
outline none
|
outline none
|
||||||
@@ -68,7 +68,7 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.control-folder-input
|
.control-folder-input
|
||||||
border 1px solid #C9C9C9 // TODO: use variable.
|
border 1px solid $ui-input--create-folder-modal
|
||||||
color white
|
color white
|
||||||
|
|
||||||
.description
|
.description
|
||||||
@@ -76,3 +76,29 @@ body[data-theme="dark"]
|
|||||||
|
|
||||||
.control-confirmButton
|
.control-confirmButton
|
||||||
colorDarkPrimaryButton()
|
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
|
|
||||||
@@ -106,7 +106,7 @@ class NewNoteModal extends React.Component {
|
|||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
>
|
>
|
||||||
<div styleName='header'>
|
<div styleName='header'>
|
||||||
<div styleName='title'>Make a Note</div>
|
<div styleName='title'>Make a note</div>
|
||||||
</div>
|
</div>
|
||||||
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
|
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
|
||||||
<div styleName='control'>
|
<div styleName='control'>
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -89,9 +89,9 @@
|
|||||||
margin-right 10px
|
margin-right 10px
|
||||||
|
|
||||||
.group-control-rightButton
|
.group-control-rightButton
|
||||||
position absolute
|
position fixed
|
||||||
top 10px
|
top 80px
|
||||||
right 20px
|
right 100px
|
||||||
colorPrimaryButton()
|
colorPrimaryButton()
|
||||||
border none
|
border none
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
|
|||||||
@@ -22,18 +22,18 @@ class Crowdfunding extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div styleName='root'>
|
<div styleName='root'>
|
||||||
<div styleName='header'>Crowdfunding</div>
|
<div styleName='header'>Crowdfunding</div>
|
||||||
<p>Dear all,</p>
|
<p>Dear everyone,</p>
|
||||||
<br />
|
<br />
|
||||||
<p>Thanks for your using!</p>
|
<p>Thank you for using Boostnote!</p>
|
||||||
<p>Boostnote is used in about 200 countries and regions, it is a awesome developer community.</p>
|
<p>Boostnote is used in about 200 different countries and regions by an awesome community of developers.</p>
|
||||||
<br />
|
<br />
|
||||||
<p>To continue supporting this growth, and to satisfy community expectations,</p>
|
<p>To continue supporting this growth, and to satisfy community expectations,</p>
|
||||||
<p>we would like to invest more time in this project.</p>
|
<p>we would like to invest more time and resources in this project.</p>
|
||||||
<br />
|
<br />
|
||||||
<p>If you like this project and see its potential, you can help!</p>
|
<p>If you like this project and see its potential, you can help by supporting us on OpenCollective!</p>
|
||||||
<br />
|
<br />
|
||||||
<p>Thanks,</p>
|
<p>Thanks,</p>
|
||||||
<p>Boostnote maintainers.</p>
|
<p>Boostnote maintainers</p>
|
||||||
<br />
|
<br />
|
||||||
<button styleName='cf-link'>
|
<button styleName='cf-link'>
|
||||||
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>Support via OpenCollective</a>
|
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>Support via OpenCollective</a>
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -64,12 +66,20 @@ class HotkeyTab extends React.Component {
|
|||||||
handleHotkeyChange (e) {
|
handleHotkeyChange (e) {
|
||||||
const { config } = this.state
|
const { config } = this.state
|
||||||
config.hotkey = {
|
config.hotkey = {
|
||||||
toggleFinder: this.refs.toggleFinder.value,
|
|
||||||
toggleMain: this.refs.toggleMain.value
|
toggleMain: this.refs.toggleMain.value
|
||||||
}
|
}
|
||||||
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 () {
|
||||||
@@ -92,9 +102,9 @@ class HotkeyTab extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div styleName='root'>
|
<div styleName='root'>
|
||||||
<div styleName='group'>
|
<div styleName='group'>
|
||||||
<div styleName='group-header'>Hotkey</div>
|
<div styleName='group-header'>Hotkeys</div>
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
<div styleName='group-section-label'>Toggle Main</div>
|
<div styleName='group-section-label'>Show/Hide Boostnote</div>
|
||||||
<div styleName='group-section-control'>
|
<div styleName='group-section-control'>
|
||||||
<input styleName='group-section-control-input'
|
<input styleName='group-section-control-input'
|
||||||
onChange={(e) => this.handleHotkeyChange(e)}
|
onChange={(e) => this.handleHotkeyChange(e)}
|
||||||
@@ -104,24 +114,13 @@ class HotkeyTab extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='group-section'>
|
|
||||||
<div styleName='group-section-label'>Toggle Finder (Quick search)</div>
|
|
||||||
<div styleName='group-section-control'>
|
|
||||||
<input styleName='group-section-control-input'
|
|
||||||
onChange={(e) => this.handleHotkeyChange(e)}
|
|
||||||
ref='toggleFinder'
|
|
||||||
value={config.hotkey.toggleFinder}
|
|
||||||
type='text'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div styleName='group-control'>
|
<div styleName='group-control'>
|
||||||
<button styleName='group-control-leftButton'
|
<button styleName='group-control-leftButton'
|
||||||
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
||||||
>
|
>
|
||||||
{this.state.isHotkeyHintOpen
|
{this.state.isHotkeyHintOpen
|
||||||
? 'Hide Hint'
|
? 'Hide Help'
|
||||||
: 'Hint?'
|
: 'Help'
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
<button styleName='group-control-rightButton'
|
<button styleName='group-control-rightButton'
|
||||||
@@ -161,7 +160,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)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class InfoTab extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSaveButtonClick (e) {
|
handleSaveButtonClick (e) {
|
||||||
let newConfig = {
|
const newConfig = {
|
||||||
amaEnabled: this.state.config.amaEnabled
|
amaEnabled: this.state.config.amaEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ class InfoTab extends React.Component {
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div styleName='header--sub'>Info</div>
|
<div styleName='header--sub'>About</div>
|
||||||
|
|
||||||
<div styleName='top'>
|
<div styleName='top'>
|
||||||
<div styleName='icon-space'>
|
<div styleName='icon-space'>
|
||||||
@@ -137,17 +137,19 @@ class InfoTab extends React.Component {
|
|||||||
|
|
||||||
<hr styleName='separate-line' />
|
<hr styleName='separate-line' />
|
||||||
|
|
||||||
<div styleName='policy'>Data collection policy</div>
|
<div styleName='policy'>Analytics</div>
|
||||||
<div>We collect only the number of DAU for Boostnote and **DO NOT collect** any detail information such as your note content.</div>
|
<div>Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.</div>
|
||||||
<div>You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
|
<div>You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
|
||||||
<div>This data is only used for Boostnote improvements.</div>
|
<br />
|
||||||
|
<div>You can choose to enable or disable this option.</div>
|
||||||
<input onChange={(e) => this.handleConfigChange(e)}
|
<input onChange={(e) => this.handleConfigChange(e)}
|
||||||
checked={this.state.config.amaEnabled}
|
checked={this.state.config.amaEnabled}
|
||||||
ref='amaEnabled'
|
ref='amaEnabled'
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
/>
|
/>
|
||||||
Enable to send analytics to our servers<br />
|
Enable analytics to help improve Boostnote<br />
|
||||||
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>Save</button>
|
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>Save</button>
|
||||||
|
<br />
|
||||||
{this.infoMessage()}
|
{this.infoMessage()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -84,3 +84,17 @@ body[data-theme="dark"]
|
|||||||
top 25px
|
top 25px
|
||||||
z-index 10
|
z-index 10
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.header
|
||||||
|
border-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
|
||||||
|
.header-label-path
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.header-label-editButton
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.header-control-button
|
||||||
|
border-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
@@ -78,7 +78,7 @@ class StoragesTab extends React.Component {
|
|||||||
<button styleName='list-control-addStorageButton'
|
<button styleName='list-control-addStorageButton'
|
||||||
onClick={(e) => this.handleAddStorageButton(e)}
|
onClick={(e) => this.handleAddStorageButton(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-plus' /> Add Storage
|
<i className='fa fa-plus' /> Add Storage Location
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -167,7 +167,7 @@ class StoragesTab extends React.Component {
|
|||||||
<option value='FILESYSTEM'>File System</option>
|
<option value='FILESYSTEM'>File System</option>
|
||||||
</select>
|
</select>
|
||||||
<div styleName='addStorage-body-section-type-description'>
|
<div styleName='addStorage-body-section-type-description'>
|
||||||
3rd party cloud integration:
|
Setting up 3rd-party cloud storage integration:{' '}
|
||||||
<a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup'
|
<a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup'
|
||||||
onClick={(e) => this.handleLinkClick(e)}
|
onClick={(e) => this.handleLinkClick(e)}
|
||||||
>Cloud-Syncing-and-Backup</a>
|
>Cloud-Syncing-and-Backup</a>
|
||||||
@@ -196,7 +196,7 @@ class StoragesTab extends React.Component {
|
|||||||
<div styleName='addStorage-body-control'>
|
<div styleName='addStorage-body-control'>
|
||||||
<button styleName='addStorage-body-control-createButton'
|
<button styleName='addStorage-body-control-createButton'
|
||||||
onClick={(e) => this.handleAddStorageCreateButton(e)}
|
onClick={(e) => this.handleAddStorageCreateButton(e)}
|
||||||
>Create</button>
|
>Add</button>
|
||||||
<button styleName='addStorage-body-control-cancelButton'
|
<button styleName='addStorage-body-control-cancelButton'
|
||||||
onClick={(e) => this.handleAddStorageCancelButton(e)}
|
onClick={(e) => this.handleAddStorageCancelButton(e)}
|
||||||
>Cancel</button>
|
>Cancel</button>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
@@ -62,6 +62,7 @@ class UiTab extends React.Component {
|
|||||||
ui: {
|
ui: {
|
||||||
theme: this.refs.uiTheme.value,
|
theme: this.refs.uiTheme.value,
|
||||||
showCopyNotification: this.refs.showCopyNotification.checked,
|
showCopyNotification: this.refs.showCopyNotification.checked,
|
||||||
|
confirmDeletion: this.refs.confirmDeletion.checked,
|
||||||
disableDirectWrite: this.refs.uiD2w != null
|
disableDirectWrite: this.refs.uiD2w != null
|
||||||
? this.refs.uiD2w.checked
|
? this.refs.uiD2w.checked
|
||||||
: false
|
: false
|
||||||
@@ -72,6 +73,7 @@ class UiTab extends React.Component {
|
|||||||
fontFamily: this.refs.editorFontFamily.value,
|
fontFamily: this.refs.editorFontFamily.value,
|
||||||
indentType: this.refs.editorIndentType.value,
|
indentType: this.refs.editorIndentType.value,
|
||||||
indentSize: this.refs.editorIndentSize.value,
|
indentSize: this.refs.editorIndentSize.value,
|
||||||
|
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
|
||||||
switchPreview: this.refs.editorSwitchPreview.value,
|
switchPreview: this.refs.editorSwitchPreview.value,
|
||||||
keyMap: this.refs.editorKeyMap.value,
|
keyMap: this.refs.editorKeyMap.value,
|
||||||
scrollPastEnd: this.refs.scrollPastEnd.checked
|
scrollPastEnd: this.refs.scrollPastEnd.checked
|
||||||
@@ -84,7 +86,8 @@ class UiTab extends React.Component {
|
|||||||
latexInlineOpen: this.refs.previewLatexInlineOpen.value,
|
latexInlineOpen: this.refs.previewLatexInlineOpen.value,
|
||||||
latexInlineClose: this.refs.previewLatexInlineClose.value,
|
latexInlineClose: this.refs.previewLatexInlineClose.value,
|
||||||
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
|
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
|
||||||
latexBlockClose: this.refs.previewLatexBlockClose.value
|
latexBlockClose: this.refs.previewLatexBlockClose.value,
|
||||||
|
scrollPastEnd: this.refs.previewScrollPastEnd.checked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,8 +96,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 +125,7 @@ class UiTab extends React.Component {
|
|||||||
config: newConfig
|
config: newConfig
|
||||||
})
|
})
|
||||||
this.clearMessage()
|
this.clearMessage()
|
||||||
|
this.props.haveToSave()
|
||||||
}
|
}
|
||||||
|
|
||||||
clearMessage () {
|
clearMessage () {
|
||||||
@@ -135,10 +150,10 @@ class UiTab extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div styleName='root'>
|
<div styleName='root'>
|
||||||
<div styleName='group'>
|
<div styleName='group'>
|
||||||
<div styleName='group-header'>UI</div>
|
<div styleName='group-header'>Interface</div>
|
||||||
|
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
Color Theme
|
Interface Theme
|
||||||
<div styleName='group-section-control'>
|
<div styleName='group-section-control'>
|
||||||
<select value={config.ui.theme}
|
<select value={config.ui.theme}
|
||||||
onChange={(e) => this.handleUIChange(e)}
|
onChange={(e) => this.handleUIChange(e)}
|
||||||
@@ -161,6 +176,16 @@ class UiTab extends React.Component {
|
|||||||
Show "Saved to Clipboard" notification when copying
|
Show "Saved to Clipboard" notification when copying
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</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'
|
global.process.platform === 'win32'
|
||||||
? <div styleName='group-checkBoxSection'>
|
? <div styleName='group-checkBoxSection'>
|
||||||
@@ -170,7 +195,7 @@ class UiTab extends React.Component {
|
|||||||
refs='uiD2w'
|
refs='uiD2w'
|
||||||
disabled={OSX}
|
disabled={OSX}
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
/>
|
/>
|
||||||
Disable Direct Write(It will be applied after restarting)
|
Disable Direct Write(It will be applied after restarting)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -280,6 +305,17 @@ class UiTab extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.editor.displayLineNumbers}
|
||||||
|
ref='editorDisplayLineNumbers'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
Show line numbers in the editor
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div styleName='group-checkBoxSection'>
|
<div styleName='group-checkBoxSection'>
|
||||||
<label>
|
<label>
|
||||||
<input onChange={(e) => this.handleUIChange(e)}
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
@@ -333,6 +369,16 @@ class UiTab extends React.Component {
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.preview.scrollPastEnd}
|
||||||
|
ref='previewScrollPastEnd'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
Allow preview to scroll past the last line
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div styleName='group-checkBoxSection'>
|
<div styleName='group-checkBoxSection'>
|
||||||
<label>
|
<label>
|
||||||
<input onChange={(e) => this.handleUIChange(e)}
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
@@ -412,7 +458,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)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import HotkeyTab from './HotkeyTab'
|
import HotkeyTab from './HotkeyTab'
|
||||||
import UiTab from './UiTab'
|
import UiTab from './UiTab'
|
||||||
@@ -11,13 +10,16 @@ import ModalEscButton from 'browser/components/ModalEscButton'
|
|||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './PreferencesModal.styl'
|
import styles from './PreferencesModal.styl'
|
||||||
import RealtimeNotification from 'browser/components/RealtimeNotification'
|
import RealtimeNotification from 'browser/components/RealtimeNotification'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
class Preferences extends React.Component {
|
class Preferences extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
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':
|
||||||
@@ -90,23 +94,29 @@ class Preferences extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getContentBoundingBox () {
|
getContentBoundingBox () {
|
||||||
const node = ReactDOM.findDOMNode(this.refs.content)
|
return this.refs.content.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: 'Storage'},
|
||||||
{target: 'HOTKEY', label: 'Hotkey'},
|
{target: 'HOTKEY', label: 'Hotkeys', Hotkey: this.state.HotkeyAlert},
|
||||||
{target: 'UI', label: 'UI'},
|
{target: 'UI', label: 'Interface', UI: this.state.UIAlert},
|
||||||
{target: 'INFO', label: 'Community / Info'},
|
{target: 'INFO', label: 'About'},
|
||||||
{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 +128,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,11 @@ 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)
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
@import '../../../node_modules/nib/lib/nib'
|
|
||||||
@import '../vars'
|
|
||||||
@import '../mixins/*'
|
|
||||||
global-reset()
|
|
||||||
@import '../shared/*'
|
|
||||||
@import '../theme/*'
|
|
||||||
|
|
||||||
iptBgColor = #E6E6E6
|
|
||||||
iptFocusBorderColor = #369DCD
|
|
||||||
|
|
||||||
DEFAULT_FONTS = 'Lato', 'MS Gothic', 'Malgun Gothic', 'Sans-serif'
|
|
||||||
|
|
||||||
body
|
|
||||||
font-family DEFAULT_FONTS
|
|
||||||
color textColor
|
|
||||||
font-size fontSize
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
overflow hidden
|
|
||||||
button, input
|
|
||||||
font-family DEFAULT_FONTS
|
|
||||||
|
|
||||||
.Finder
|
|
||||||
absolute top bottom left right
|
|
||||||
.FinderInput
|
|
||||||
padding 11px
|
|
||||||
margin 0 auto
|
|
||||||
height 55px
|
|
||||||
box-sizing border-box
|
|
||||||
border-bottom solid 1px borderColor
|
|
||||||
background-color iptBgColor
|
|
||||||
z-index 200
|
|
||||||
input
|
|
||||||
display block
|
|
||||||
width 100%
|
|
||||||
border solid 1px borderColor
|
|
||||||
padding 0 10px
|
|
||||||
font-size 1em
|
|
||||||
height 33px
|
|
||||||
border-radius 5px
|
|
||||||
box-sizing border-box
|
|
||||||
border-radius 5px
|
|
||||||
&:focus, &.focus
|
|
||||||
border-color iptFocusBorderColor
|
|
||||||
outline none
|
|
||||||
.FinderList
|
|
||||||
absolute left bottom
|
|
||||||
top 55px
|
|
||||||
border-right solid 1px borderColor
|
|
||||||
box-sizing border-box
|
|
||||||
width 250px
|
|
||||||
overflow-y auto
|
|
||||||
z-index 0
|
|
||||||
user-select none
|
|
||||||
&>ul>li
|
|
||||||
.articleItem
|
|
||||||
padding 10px
|
|
||||||
border solid 2px transparent
|
|
||||||
box-sizing border-box
|
|
||||||
cursor pointer
|
|
||||||
white-space nowrap
|
|
||||||
overflow-x hidden
|
|
||||||
text-overflow ellipsis
|
|
||||||
.divider
|
|
||||||
box-sizing border-box
|
|
||||||
border-bottom solid 1px borderColor
|
|
||||||
&.active
|
|
||||||
.articleItem
|
|
||||||
border-color brandColor
|
|
||||||
|
|
||||||
.FinderDetail
|
|
||||||
absolute right bottom
|
|
||||||
top 55px
|
|
||||||
left 250px
|
|
||||||
box-shadow 0px 0px 10px 0 #CCC
|
|
||||||
z-index 100
|
|
||||||
.header
|
|
||||||
absolute top left right
|
|
||||||
height 55px
|
|
||||||
box-sizing border-box
|
|
||||||
padding 0 10px
|
|
||||||
border-bottom solid 1px borderColor
|
|
||||||
line-height 55px
|
|
||||||
font-size 18px
|
|
||||||
white-space nowrap
|
|
||||||
text-overflow ellipsis
|
|
||||||
overflow-x hidden
|
|
||||||
clearfix()
|
|
||||||
.left
|
|
||||||
float left
|
|
||||||
.right
|
|
||||||
float right
|
|
||||||
button
|
|
||||||
border-radius 16.5px
|
|
||||||
cursor pointer
|
|
||||||
height 33px
|
|
||||||
width 33px
|
|
||||||
border none
|
|
||||||
margin-right 5px
|
|
||||||
font-size 18px
|
|
||||||
color inactiveTextColor
|
|
||||||
background-color transparent
|
|
||||||
padding 0
|
|
||||||
.tooltip
|
|
||||||
tooltip()
|
|
||||||
&.clipboardBtn .tooltip
|
|
||||||
margin-left -160px
|
|
||||||
margin-top 25px
|
|
||||||
&:hover
|
|
||||||
color textColor
|
|
||||||
.tooltip
|
|
||||||
opacity 1
|
|
||||||
.content
|
|
||||||
position absolute
|
|
||||||
top 55px
|
|
||||||
padding 10px
|
|
||||||
bottom 0
|
|
||||||
left 0
|
|
||||||
right 0
|
|
||||||
box-sizing border-box
|
|
||||||
overflow-y auto
|
|
||||||
.MarkdownPreview
|
|
||||||
marked()
|
|
||||||
&.empty
|
|
||||||
color lighten(inactiveTextColor, 10%)
|
|
||||||
user-select none
|
|
||||||
font-size 14px
|
|
||||||
.CodeEditor
|
|
||||||
absolute top bottom left right
|
|
||||||
@@ -46,6 +46,7 @@ tooltip()
|
|||||||
// UI Input
|
// UI Input
|
||||||
$ui-input--focus-borderColor = #369DCD
|
$ui-input--focus-borderColor = #369DCD
|
||||||
$ui-input--disabled-backgroundColor = #DDD
|
$ui-input--disabled-backgroundColor = #DDD
|
||||||
|
$ui-input--create-folder-modal = #C9C9C9
|
||||||
|
|
||||||
// Parts
|
// Parts
|
||||||
$ui-favorite-star-button-color = #FFC216
|
$ui-favorite-star-button-color = #FFC216
|
||||||
@@ -187,7 +188,6 @@ modal()
|
|||||||
border-radius $modal-border-radius
|
border-radius $modal-border-radius
|
||||||
|
|
||||||
topBarButtonRight()
|
topBarButtonRight()
|
||||||
position absolute
|
|
||||||
width 34px
|
width 34px
|
||||||
height 34px
|
height 34px
|
||||||
border-radius 17px
|
border-radius 17px
|
||||||
@@ -339,4 +339,12 @@ $ui-solarized-dark-button-backgroundColor = #002b36
|
|||||||
$ui-solarized-dark-button--active-color = #93a1a1
|
$ui-solarized-dark-button--active-color = #93a1a1
|
||||||
$ui-solarized-dark-button--active-backgroundColor = #073642
|
$ui-solarized-dark-button--active-backgroundColor = #073642
|
||||||
$ui-solarized-dark-button--hover-backgroundColor = lighten($ui-dark-backgroundColor, 10%)
|
$ui-solarized-dark-button--hover-backgroundColor = lighten($ui-dark-backgroundColor, 10%)
|
||||||
$ui-solarized-dark-button--focus-borderColor = lighten(#369DCD, 25%)
|
$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
|
||||||
@@ -378,52 +378,10 @@ body[data-theme="dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
color themeDarkFocusButton
|
color themeDarkFocusButton
|
||||||
|
|
||||||
.Finder
|
|
||||||
.FinderInput
|
|
||||||
color themeDarkText
|
|
||||||
border-color themeDarkBorder
|
|
||||||
background-color themeDarkBackground
|
|
||||||
|
|
||||||
input
|
|
||||||
color themeDarkText
|
|
||||||
border-color lighten(themeDarkBackground, 10%)
|
|
||||||
background-color lighten(themeDarkBackground, 10%)
|
|
||||||
|
|
||||||
&:focus
|
|
||||||
border-color themeDarkTopicColor
|
|
||||||
|
|
||||||
.FinderList
|
|
||||||
color themeDarkText
|
|
||||||
border-color themeDarkBorder
|
|
||||||
background-color themeDarkList
|
|
||||||
|
|
||||||
.divider
|
|
||||||
border-color themeDarkBorder
|
|
||||||
|
|
||||||
.FinderDetail
|
|
||||||
color themeDarkText
|
|
||||||
border-color themeDarkBorder
|
|
||||||
background-color themeDarkPreview
|
|
||||||
box-shadow 0px 0px 10px 0 darken(themeDarkBorder, 20%);
|
|
||||||
|
|
||||||
.header
|
|
||||||
border-color themeDarkBorder
|
|
||||||
|
|
||||||
.right
|
|
||||||
.clipboardBtn
|
|
||||||
transition 0.1s
|
|
||||||
|
|
||||||
&:hover
|
|
||||||
color themeDarkFocusButton
|
|
||||||
|
|
||||||
.tooltip
|
|
||||||
background-color themeDarkTooltip
|
|
||||||
|
|
||||||
.ArticleDetail-panel
|
.ArticleDetail-panel
|
||||||
border-radius 0
|
border-radius 0
|
||||||
|
|
||||||
// Markdown Preview
|
// Markdown Preview
|
||||||
.Finder .FinderDetail .content,
|
|
||||||
.ArticleDetail .ArticleDetail-panel .ArticleEditor
|
.ArticleDetail .ArticleDetail-panel .ArticleEditor
|
||||||
.MarkdownPreview
|
.MarkdownPreview
|
||||||
color themeDarkText
|
color themeDarkText
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ module.exports = function (grunt) {
|
|||||||
var initConfig = {
|
var initConfig = {
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
'create-windows-installer': {
|
'create-windows-installer': {
|
||||||
x64: {
|
ia32: {
|
||||||
appDirectory: path.join(__dirname, 'dist', 'Boostnote-win32-x64'),
|
appDirectory: path.join(__dirname, 'dist', 'Boostnote-win32-x64'),
|
||||||
outputDirectory: path.join(__dirname, 'dist'),
|
outputDirectory: path.join(__dirname, 'dist'),
|
||||||
authors: 'MAISIN&CO., Inc.',
|
authors: 'MAISIN&CO., Inc.',
|
||||||
@@ -109,7 +109,7 @@ module.exports = function (grunt) {
|
|||||||
var done = this.async()
|
var done = this.async()
|
||||||
var opts = {
|
var opts = {
|
||||||
name: 'Boostnote',
|
name: 'Boostnote',
|
||||||
arch: 'x64',
|
arch: 'ia32',
|
||||||
dir: __dirname,
|
dir: __dirname,
|
||||||
version: grunt.config.get('pkg.config.electron-version'),
|
version: grunt.config.get('pkg.config.electron-version'),
|
||||||
'app-version': grunt.config.get('pkg.version'),
|
'app-version': grunt.config.get('pkg.version'),
|
||||||
|
|||||||
11
index.js
@@ -4,11 +4,6 @@ const path = require('path')
|
|||||||
|
|
||||||
var error = null
|
var error = null
|
||||||
|
|
||||||
function isFinderCalled () {
|
|
||||||
var argv = process.argv.slice(1)
|
|
||||||
return argv.some((arg) => arg.match(/--finder/))
|
|
||||||
}
|
|
||||||
|
|
||||||
function execMainApp () {
|
function execMainApp () {
|
||||||
const appRootPath = path.join(process.execPath, '../..')
|
const appRootPath = path.join(process.execPath, '../..')
|
||||||
const updateDotExePath = path.join(appRootPath, 'Update.exe')
|
const updateDotExePath = path.join(appRootPath, 'Update.exe')
|
||||||
@@ -78,8 +73,4 @@ function execMainApp () {
|
|||||||
require('./lib/main-app')
|
require('./lib/main-app')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFinderCalled()) {
|
execMainApp()
|
||||||
require('./lib/finder-app')
|
|
||||||
} else {
|
|
||||||
execMainApp()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
const electron = require('electron')
|
|
||||||
const app = electron.app
|
|
||||||
|
|
||||||
app.on('ready', function () {
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
app.dock.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-disable */
|
|
||||||
finderWindow = require('./finder-window')
|
|
||||||
/* eslint-enable */
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = app
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
const electron = require('electron')
|
|
||||||
const { app } = electron
|
|
||||||
const BrowserWindow = electron.BrowserWindow
|
|
||||||
const Menu = electron.Menu
|
|
||||||
const MenuItem = electron.MenuItem
|
|
||||||
const Tray = electron.Tray
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
var config = {
|
|
||||||
width: 840,
|
|
||||||
height: 540,
|
|
||||||
show: false,
|
|
||||||
frame: false,
|
|
||||||
resizable: false,
|
|
||||||
zoomFactor: 1.0,
|
|
||||||
webPreferences: {
|
|
||||||
blinkFeatures: 'OverlayScrollbars'
|
|
||||||
},
|
|
||||||
skipTaskbar: true,
|
|
||||||
standardWindow: false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
config['always-on-top'] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
var finderWindow = new BrowserWindow(config)
|
|
||||||
|
|
||||||
var url = path.resolve(__dirname, './finder.html')
|
|
||||||
|
|
||||||
finderWindow.loadURL('file://' + url)
|
|
||||||
finderWindow.setSkipTaskbar(true)
|
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
finderWindow.setVisibleOnAllWorkspaces(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
finderWindow.on('blur', function () {
|
|
||||||
hideFinder()
|
|
||||||
})
|
|
||||||
|
|
||||||
finderWindow.on('close', function (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
finderWindow.hide()
|
|
||||||
})
|
|
||||||
|
|
||||||
var trayIcon = process.platform === 'darwin' || process.platform === 'win32'
|
|
||||||
? path.join(__dirname, '../resources/tray-icon-default.png')
|
|
||||||
: path.join(__dirname, '../resources/tray-icon.png')
|
|
||||||
var appIcon = new Tray(trayIcon)
|
|
||||||
appIcon.setToolTip('Boostnote')
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
appIcon.setPressedImage(path.join(__dirname, '../resources/tray-icon-dark.png'))
|
|
||||||
}
|
|
||||||
|
|
||||||
var trayMenu = new Menu()
|
|
||||||
trayMenu.append(new MenuItem({
|
|
||||||
label: 'Open Main window',
|
|
||||||
click: function () {
|
|
||||||
finderWindow.webContents.send('open-main-from-tray')
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (process.env.platform !== 'linux' || process.env.DESKTOP_SESSION === 'cinnamon') {
|
|
||||||
trayMenu.append(new MenuItem({
|
|
||||||
label: 'Open Finder window',
|
|
||||||
click: function () {
|
|
||||||
finderWindow.webContents.send('open-finder-from-tray')
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
trayMenu.append(new MenuItem({
|
|
||||||
label: 'Quit',
|
|
||||||
click: function () {
|
|
||||||
finderWindow.webContents.send('quit-from-tray')
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
appIcon.setContextMenu(trayMenu)
|
|
||||||
appIcon.on('click', function (e) {
|
|
||||||
e.preventDefault()
|
|
||||||
appIcon.popUpContextMenu(trayMenu)
|
|
||||||
})
|
|
||||||
|
|
||||||
function hideFinder () {
|
|
||||||
if (process.platform === 'win32') {
|
|
||||||
finderWindow.minimize()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
Menu.sendActionToFirstResponder('hide:')
|
|
||||||
}
|
|
||||||
finderWindow.hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on('before-quit', function (e) {
|
|
||||||
finderWindow.removeAllListeners()
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = finderWindow
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
|
|
||||||
<title>Boostnote Finder</title>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
|
|
||||||
<link rel="shortcut icon" href="favicon.ico">
|
|
||||||
<link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Lato';
|
|
||||||
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
|
||||||
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
|
||||||
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="content"></div>
|
|
||||||
|
|
||||||
<script src="../node_modules/codemirror/lib/codemirror.js"></script>
|
|
||||||
<script src="../node_modules/codemirror/mode/meta.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/raphael/raphael.min.js"></script>
|
|
||||||
<script src="../node_modules/flowchart.js/release/flowchart.min.js"></script>
|
|
||||||
|
|
||||||
<script src="../node_modules/katex/dist/katex.min.js"></script>
|
|
||||||
<script src="../node_modules/react/dist/react.min.js"></script>
|
|
||||||
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
|
|
||||||
<script src="../node_modules/redux/dist/redux.min.js"></script>
|
|
||||||
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
|
|
||||||
<script>
|
|
||||||
window._ = require('lodash');
|
|
||||||
</script>
|
|
||||||
<script src="../node_modules/js-sequence-diagrams/fucknpm/sequence-diagram-min.js"></script>
|
|
||||||
<script>
|
|
||||||
const electron = require('electron')
|
|
||||||
electron.webFrame.setZoomLevelLimits(1, 1)
|
|
||||||
const _ = require('lodash')
|
|
||||||
var scriptUrl = _.find(electron.remote.process.argv, (a) => a === '--hot')
|
|
||||||
? 'http://localhost:8080/assets/finder.js'
|
|
||||||
: '../compiled/finder.js'
|
|
||||||
var scriptEl = document.createElement('script')
|
|
||||||
scriptEl.setAttribute('type', 'text/javascript')
|
|
||||||
scriptEl.setAttribute('src', scriptUrl)
|
|
||||||
document.body.appendChild(scriptEl)
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -26,10 +26,6 @@ function toggleMainWindow () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFinder () {
|
|
||||||
nodeIpc.server.broadcast('open-finder')
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcMain.on('config-renew', (e, payload) => {
|
ipcMain.on('config-renew', (e, payload) => {
|
||||||
nodeIpc.server.broadcast('config-renew', payload)
|
nodeIpc.server.broadcast('config-renew', payload)
|
||||||
|
|
||||||
@@ -37,11 +33,6 @@ ipcMain.on('config-renew', (e, payload) => {
|
|||||||
var { config } = payload
|
var { config } = payload
|
||||||
|
|
||||||
var errors = []
|
var errors = []
|
||||||
try {
|
|
||||||
globalShortcut.register(config.hotkey.toggleFinder, toggleFinder)
|
|
||||||
} catch (err) {
|
|
||||||
errors.push('toggleFinder')
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
globalShortcut.register(config.hotkey.toggleMain, toggleMainWindow)
|
globalShortcut.register(config.hotkey.toggleMain, toggleMainWindow)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -61,12 +52,6 @@ ipcMain.on('config-renew', (e, payload) => {
|
|||||||
nodeIpc.serve(
|
nodeIpc.serve(
|
||||||
path.join(app.getPath('userData'), 'boostnote.service'),
|
path.join(app.getPath('userData'), 'boostnote.service'),
|
||||||
function () {
|
function () {
|
||||||
nodeIpc.server.on('open-main-from-finder', toggleMainWindow)
|
|
||||||
|
|
||||||
nodeIpc.server.on('quit-from-finder', function () {
|
|
||||||
app.quit()
|
|
||||||
})
|
|
||||||
|
|
||||||
nodeIpc.server.on('connect', function (socket) {
|
nodeIpc.server.on('connect', function (socket) {
|
||||||
nodeIpc.log('ipc server >> socket joinned'.rainbow)
|
nodeIpc.log('ipc server >> socket joinned'.rainbow)
|
||||||
socket.on('close', function () {
|
socket.on('close', function () {
|
||||||
@@ -76,14 +61,6 @@ nodeIpc.serve(
|
|||||||
nodeIpc.server.on('error', function (err) {
|
nodeIpc.server.on('error', function (err) {
|
||||||
nodeIpc.log('Node IPC error'.rainbow, err)
|
nodeIpc.log('Node IPC error'.rainbow, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Todo: Direct connection between Main window renderer & Finder window renderer
|
|
||||||
nodeIpc.server.on('request-data-from-finder', function () {
|
|
||||||
nodeIpc.server.broadcast('request-data-from-finder')
|
|
||||||
})
|
|
||||||
nodeIpc.server.on('throttle-data', function (payload) {
|
|
||||||
nodeIpc.server.broadcast('throttle-data', payload)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ const electron = require('electron')
|
|||||||
const app = electron.app
|
const app = electron.app
|
||||||
const Menu = electron.Menu
|
const Menu = electron.Menu
|
||||||
const ipc = electron.ipcMain
|
const ipc = electron.ipcMain
|
||||||
const path = require('path')
|
|
||||||
const ChildProcess = require('child_process')
|
|
||||||
const _ = require('lodash')
|
|
||||||
const GhReleases = require('electron-gh-releases')
|
const GhReleases = require('electron-gh-releases')
|
||||||
// electron.crashReporter.start()
|
// electron.crashReporter.start()
|
||||||
var ipcServer = null
|
var ipcServer = null
|
||||||
@@ -65,14 +62,6 @@ updater.autoUpdater.on('error', (err) => {
|
|||||||
console.log(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) {
|
ipc.on('update-app-confirm', function (event, msg) {
|
||||||
if (isUpdateReady) {
|
if (isUpdateReady) {
|
||||||
mainWindow.removeAllListeners()
|
mainWindow.removeAllListeners()
|
||||||
@@ -80,17 +69,6 @@ ipc.on('update-app-confirm', function (event, msg) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
function spawnFinder () {
|
|
||||||
var finderArgv = [path.join(__dirname, 'finder-app.js'), '--finder']
|
|
||||||
if (_.find(process.argv, a => a === '--hot')) finderArgv.push('--hot')
|
|
||||||
var finderProcess = ChildProcess
|
|
||||||
.execFile(process.execPath, finderArgv)
|
|
||||||
|
|
||||||
app.on('before-quit', function () {
|
|
||||||
finderProcess.kill()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
app.on('ready', function () {
|
app.on('ready', function () {
|
||||||
mainWindow = require('./main-window')
|
mainWindow = require('./main-window')
|
||||||
|
|
||||||
@@ -98,13 +76,9 @@ app.on('ready', function () {
|
|||||||
var menu = Menu.buildFromTemplate(template)
|
var menu = Menu.buildFromTemplate(template)
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case 'darwin':
|
case 'darwin':
|
||||||
spawnFinder()
|
|
||||||
Menu.setApplicationMenu(menu)
|
Menu.setApplicationMenu(menu)
|
||||||
break
|
break
|
||||||
case 'win32':
|
case 'win32':
|
||||||
/* eslint-disable */
|
|
||||||
finderWindow = require('./finder-window')
|
|
||||||
/* eslint-disable */
|
|
||||||
mainWindow.setMenu(menu)
|
mainWindow.setMenu(menu)
|
||||||
break
|
break
|
||||||
case 'linux':
|
case 'linux':
|
||||||
@@ -115,12 +89,22 @@ app.on('ready', function () {
|
|||||||
// Check update every hour
|
// Check update every hour
|
||||||
setInterval(function () {
|
setInterval(function () {
|
||||||
checkUpdate()
|
checkUpdate()
|
||||||
}, 1000 * 60 * 60)
|
}, 1000 * 60 * 60 * 24)
|
||||||
|
|
||||||
checkUpdate()
|
// 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 = require('./ipcServer')
|
||||||
ipcServer.server.start()
|
ipcServer.server.start()
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = app
|
module.exports = app
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const BrowserWindow = electron.BrowserWindow
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const Config = require('electron-config')
|
const Config = require('electron-config')
|
||||||
const config = new Config()
|
const config = new Config()
|
||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
var showMenu = process.platform !== 'win32'
|
var showMenu = process.platform !== 'win32'
|
||||||
const windowSize = config.get('windowsize') || { width: 1080, height: 720 }
|
const windowSize = config.get('windowsize') || { width: 1080, height: 720 }
|
||||||
@@ -39,41 +40,28 @@ mainWindow.webContents.sendInputEvent({
|
|||||||
keyCode: '\u0008'
|
keyCode: '\u0008'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (process.platform !== 'linux' || process.env.DESKTOP_SESSION === 'cinnamon') {
|
if (process.platform === 'darwin' || process.env.DESKTOP_SESSION === 'cinnamon') {
|
||||||
mainWindow.on('close', function (e) {
|
mainWindow.on('close', function (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (process.platform === 'win32') {
|
if (mainWindow.isFullScreen()) {
|
||||||
quitApp()
|
mainWindow.once('leave-full-screen', function () {
|
||||||
} else {
|
|
||||||
if (mainWindow.isFullScreen()) {
|
|
||||||
mainWindow.once('leave-full-screen', function () {
|
|
||||||
mainWindow.hide()
|
|
||||||
})
|
|
||||||
mainWindow.setFullScreen(false)
|
|
||||||
} else {
|
|
||||||
mainWindow.hide()
|
mainWindow.hide()
|
||||||
}
|
})
|
||||||
|
mainWindow.setFullScreen(false)
|
||||||
|
} else {
|
||||||
|
mainWindow.hide()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
app.on('before-quit', function (e) {
|
app.on('before-quit', function (e) {
|
||||||
storeWindowSize()
|
|
||||||
mainWindow.removeAllListeners()
|
mainWindow.removeAllListeners()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
mainWindow.on('close', function () {
|
|
||||||
storeWindowSize()
|
|
||||||
})
|
|
||||||
|
|
||||||
app.on('window-all-closed', function () {
|
app.on('window-all-closed', function () {
|
||||||
app.quit()
|
app.quit()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
mainWindow.on('resize', _.throttle(storeWindowSize, 500))
|
||||||
function quitApp () {
|
|
||||||
storeWindowSize()
|
|
||||||
app.quit()
|
|
||||||
}
|
|
||||||
|
|
||||||
function storeWindowSize () {
|
function storeWindowSize () {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -79,6 +80,8 @@
|
|||||||
|
|
||||||
<script src="../node_modules/codemirror/addon/edit/continuelist.js"></script>
|
<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/search.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/search/searchcursor.js"></script>
|
<script src="../node_modules/codemirror/addon/search/searchcursor.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
<script src="../node_modules/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||||
|
|||||||
15
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"productName": "Boostnote",
|
"productName": "Boostnote",
|
||||||
"version": "0.8.19",
|
"version": "0.9.0",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"description": "Boostnote",
|
"description": "Boostnote",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@@ -10,13 +10,13 @@
|
|||||||
"hot": "electron ./index.js --hot",
|
"hot": "electron ./index.js --hot",
|
||||||
"webpack": "webpack-dev-server --hot --inline --config webpack.config.js",
|
"webpack": "webpack-dev-server --hot --inline --config webpack.config.js",
|
||||||
"compile": "grunt compile",
|
"compile": "grunt compile",
|
||||||
"test": "PWD=$(pwd) NODE_ENV=test ava",
|
"test": "PWD=$(pwd) NODE_ENV=test ava --serial",
|
||||||
"fix": "npm run lint --fix",
|
"fix": "npm run lint --fix",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"dev-start": "concurrently --kill-others \"npm run webpack\" \"npm run hot\""
|
"dev-start": "concurrently --kill-others \"npm run webpack\" \"npm run hot\""
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"electron-version": "1.6.15"
|
"electron-version": "1.7.11"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -53,13 +53,15 @@
|
|||||||
"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",
|
||||||
|
"electron-windows-installer": "^1.7.8",
|
||||||
"flowchart.js": "^1.6.5",
|
"flowchart.js": "^1.6.5",
|
||||||
"font-awesome": "^4.3.0",
|
"font-awesome": "^4.3.0",
|
||||||
"immutable": "^3.8.1",
|
"immutable": "^3.8.1",
|
||||||
"js-sequence-diagrams": "^1000000.0.6",
|
"js-sequence-diagrams": "^1000000.0.6",
|
||||||
"katex": "^0.7.1",
|
"katex": "^0.8.3",
|
||||||
"lodash": "^4.11.1",
|
"lodash": "^4.11.1",
|
||||||
"lodash-move": "^1.1.1",
|
"lodash-move": "^1.1.1",
|
||||||
"markdown-it": "^6.0.1",
|
"markdown-it": "^6.0.1",
|
||||||
@@ -102,7 +104,7 @@
|
|||||||
"css-loader": "^0.19.0",
|
"css-loader": "^0.19.0",
|
||||||
"devtron": "^1.1.0",
|
"devtron": "^1.1.0",
|
||||||
"dom-storage": "^2.0.2",
|
"dom-storage": "^2.0.2",
|
||||||
"electron": "^1.6.15",
|
"electron": "1.7.11",
|
||||||
"electron-packager": "^6.0.0",
|
"electron-packager": "^6.0.0",
|
||||||
"eslint": "^3.13.1",
|
"eslint": "^3.13.1",
|
||||||
"eslint-config-standard": "^6.2.1",
|
"eslint-config-standard": "^6.2.1",
|
||||||
@@ -111,7 +113,8 @@
|
|||||||
"eslint-plugin-standard": "^3.0.1",
|
"eslint-plugin-standard": "^3.0.1",
|
||||||
"faker": "^3.1.0",
|
"faker": "^3.1.0",
|
||||||
"grunt": "^0.4.5",
|
"grunt": "^0.4.5",
|
||||||
"grunt-electron-installer": "^1.2.0",
|
"grunt-electron-installer": "2.1.0",
|
||||||
|
"gulp": "^3.9.1",
|
||||||
"history": "^1.17.0",
|
"history": "^1.17.0",
|
||||||
"jsdom": "^9.4.2",
|
"jsdom": "^9.4.2",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
|
|||||||
15
readme.md
@@ -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,8 +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)
|
|
||||||
- [Sosuke](https://github.com/sosukesuzuki)
|
- [Sosuke](https://github.com/sosukesuzuki)
|
||||||
- [Kazz](https://github.com/kazup01)
|
- [Kazz](https://github.com/kazup01)
|
||||||
|
|
||||||
@@ -29,16 +25,17 @@ 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/enQtMzAzMjI1MTIyNTQ3LTc2MjNiYWU3NTc1YjZlMTk3NzFmOWE1ZWU1MGRhMzBkMGIwMWFjOWMxMDRiM2I2NzkzYzc4OGZhNmVhZjYzZTM)
|
||||||
- [Blog](https://medium.com/boostnote)
|
- [Blog](https://medium.com/boostnote)
|
||||||
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
- [Reddit](https://www.reddit.com/r/Boostnote/)
|
||||||
|
|
||||||
|
|
||||||
#### More Information
|
#### More Information
|
||||||
* [Website](https://boostnote.io)
|
* Website: https://boostnote.io
|
||||||
* [Subscribe to the Newsletter](https://boostnote.io/#community): Get updates on Boostnote progress. No spam, ever :)
|
* Newsletters: https://boostnote.io/#subscribe
|
||||||
* [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote.
|
* [Development](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md): Development configurations for Boostnote.
|
||||||
* Copyright (C) 2017 Maisin&Co.
|
* Copyright (C) 2016 - 2018 BoostIO, Inc.
|
||||||
|
|
||||||
|
|
||||||
#### License
|
#### License
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<svg width="13px" height="13px" 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 -->
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>icon-full</title>
|
<title>icon-full</title>
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 954 B After Width: | Height: | Size: 954 B |
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<svg width="15px" height="15px" 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 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
|
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>icon-info</title>
|
<title>icon-info</title>
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<svg width="15px" 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 -->
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>icon-mode-markdown-off</title>
|
<title>icon-mode-markdown-off</title>
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<svg width="13px" height="13px" 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 -->
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>icon-mode-split-on</title>
|
<title>icon-mode-split-on</title>
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<svg width="14px" height="14px" 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 -->
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>icon-previewoff-off</title>
|
<title>icon-previewoff-off</title>
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<svg width="14px" height="14px" 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 -->
|
<!-- Generator: Sketch 48.1 (47250) - http://www.bohemiancoding.com/sketch -->
|
||||||
<title>icon-previewoff-on</title>
|
<title>icon-previewoff-on</title>
|
||||||
<desc>Created with Sketch.</desc>
|
<desc>Created with Sketch.</desc>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |