mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
218 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a594332ffb | ||
|
|
ceb18ebf1c | ||
|
|
8910c26ee2 | ||
|
|
7549a7bbbe | ||
|
|
17fbe6e232 | ||
|
|
ccdac8f604 | ||
|
|
88a828c9ef | ||
|
|
ae3291b90e | ||
|
|
2c6f0452b8 | ||
|
|
4651acd6f4 | ||
|
|
bba7babce3 | ||
|
|
73dc6a4a92 | ||
|
|
992f5a525a | ||
|
|
b38d5789f3 | ||
|
|
47b5945e17 | ||
|
|
73544b0f06 | ||
|
|
e7d9311e23 | ||
|
|
c97c65b707 | ||
|
|
1c02b4e62a | ||
|
|
d23156d11a | ||
|
|
bd013adb4d | ||
|
|
c0368ce713 | ||
|
|
80283b5f55 | ||
|
|
4078645958 | ||
|
|
955ade0b8a | ||
|
|
4b158af9f6 | ||
|
|
642fae3ac7 | ||
|
|
d249967aee | ||
|
|
7b6b7f05e0 | ||
|
|
35b9bf5d34 | ||
|
|
59f0cc4f98 | ||
|
|
a29ca73fb4 | ||
|
|
59b658f059 | ||
|
|
3397b3108f | ||
|
|
cae7baa5e1 | ||
|
|
4af71fd1dd | ||
|
|
4194b61373 | ||
|
|
c91fd6783d | ||
|
|
89bbed1dfd | ||
|
|
2aeb53920c | ||
|
|
fe51c232b6 | ||
|
|
57b054794c | ||
|
|
8318c56046 | ||
|
|
0d52417ee7 | ||
|
|
6f3b1b8d6f | ||
|
|
a460d7722e | ||
|
|
d770208d4c | ||
|
|
0434109908 | ||
|
|
289d3a4e6b | ||
|
|
ffb9be63c7 | ||
|
|
bf2b53cbce | ||
|
|
1d9bf65c31 | ||
|
|
4744b918d3 | ||
|
|
588b1809a9 | ||
|
|
dc1c19293d | ||
|
|
1f548959e3 | ||
|
|
8cae5670fc | ||
|
|
07c0982d4f | ||
|
|
2f9e4b3198 | ||
|
|
89dba149a3 | ||
|
|
aa71b4c1b8 | ||
|
|
43110f8f2a | ||
|
|
e48540713d | ||
|
|
cfd13139e0 | ||
|
|
ac5cdf384f | ||
|
|
e9d858d902 | ||
|
|
1beae4403a | ||
|
|
dedf36f704 | ||
|
|
1477de3899 | ||
|
|
0d947c7dd8 | ||
|
|
ebfd8f40e3 | ||
|
|
3159cc0ded | ||
|
|
10dcbfb891 | ||
|
|
19dc16e14a | ||
|
|
95586b3156 | ||
|
|
0637daf645 | ||
|
|
fdcd62617d | ||
|
|
0f3e5ee4ed | ||
|
|
7b171ecc67 | ||
|
|
7a4052ede3 | ||
|
|
3f53a1f629 | ||
|
|
31daec5fe2 | ||
|
|
0d7155bda6 | ||
|
|
35beec3e39 | ||
|
|
ff4b96b622 | ||
|
|
9b60814292 | ||
|
|
3c4fa83161 | ||
|
|
e8564f6540 | ||
|
|
a22e97d4bd | ||
|
|
046e6af489 | ||
|
|
f805e8a688 | ||
|
|
2fddc32eb7 | ||
|
|
6018cd5d81 | ||
|
|
3533903be3 | ||
|
|
d867292f66 | ||
|
|
7691b662d6 | ||
|
|
86270dd856 | ||
|
|
012e2dde4f | ||
|
|
ad7a3c49f9 | ||
|
|
e8abd43c8a | ||
|
|
3192ce9d39 | ||
|
|
d09de09fef | ||
|
|
4689ddeb98 | ||
|
|
e300b33a4f | ||
|
|
0ca87ea407 | ||
|
|
2886da4f63 | ||
|
|
bf9ecb02e5 | ||
|
|
852617726c | ||
|
|
c2aa35104c | ||
|
|
95e237d4a3 | ||
|
|
59e5c547e9 | ||
|
|
06bd2b2b79 | ||
|
|
faede48217 | ||
|
|
ad0ac19d3d | ||
|
|
3154110de1 | ||
|
|
5248c05e61 | ||
|
|
8311030bec | ||
|
|
c429fc6b2c | ||
|
|
590aa9ab17 | ||
|
|
f9a7c2d457 | ||
|
|
b4506168fb | ||
|
|
f203ab3aaf | ||
|
|
c197dd0a4b | ||
|
|
457e596851 | ||
|
|
d274563b2c | ||
|
|
2003bea3cf | ||
|
|
f9b3284852 | ||
|
|
9bca133d88 | ||
|
|
03fc453608 | ||
|
|
3027cc81b3 | ||
|
|
2415fbf676 | ||
|
|
725c6a7ba9 | ||
|
|
c33da0cf8e | ||
|
|
d915d19425 | ||
|
|
f3370242bf | ||
|
|
0e312ba929 | ||
|
|
6440395197 | ||
|
|
5433abddaf | ||
|
|
0ccb465288 | ||
|
|
8fd4deb3eb | ||
|
|
fe8045c51d | ||
|
|
b890c59134 | ||
|
|
f39caeb967 | ||
|
|
7ab482184b | ||
|
|
78b12ae686 | ||
|
|
caa5deac4e | ||
|
|
af3083825e | ||
|
|
5255708ff2 | ||
|
|
9331f2034b | ||
|
|
fc6a5c22bf | ||
|
|
51c397d177 | ||
|
|
7c9596308e | ||
|
|
15dc424ade | ||
|
|
49243a8010 | ||
|
|
93e188d118 | ||
|
|
df3195fc1e | ||
|
|
da6b8c30a0 | ||
|
|
70468b6b7d | ||
|
|
2511512d94 | ||
|
|
4418617d3b | ||
|
|
6e480ba146 | ||
|
|
b4f5913a80 | ||
|
|
6a3062709c | ||
|
|
d66bc1faef | ||
|
|
bef7d45c3e | ||
|
|
bb9489a8d3 | ||
|
|
700eeb8f5a | ||
|
|
7e2f0049b6 | ||
|
|
b2388544d8 | ||
|
|
d772551c60 | ||
|
|
31dca6f06b | ||
|
|
83f68fe153 | ||
|
|
08a2ae0fd3 | ||
|
|
53d3f51c74 | ||
|
|
f7cdafb087 | ||
|
|
5b17808569 | ||
|
|
a7328e21f1 | ||
|
|
e64370e9a2 | ||
|
|
d5b37b2418 | ||
|
|
4da08d93fd | ||
|
|
c39e5c67f5 | ||
|
|
00d5cf13c9 | ||
|
|
1120bcfc0c | ||
|
|
8e506cb7c2 | ||
|
|
145b66d375 | ||
|
|
82e4a8bbc3 | ||
|
|
bca9bfb960 | ||
|
|
d8e19d9c17 | ||
|
|
95d74d1ca2 | ||
|
|
ebdd6d77f7 | ||
|
|
d56bcc4fdf | ||
|
|
4bb18cfc9a | ||
|
|
6f30692534 | ||
|
|
e249c1ec65 | ||
|
|
c2b4c77003 | ||
|
|
e64733827a | ||
|
|
ea81b0d414 | ||
|
|
000cf2a864 | ||
|
|
dc13b919b3 | ||
|
|
a0c8ec3171 | ||
|
|
80c13f7c4f | ||
|
|
1a6f3d808b | ||
|
|
ec8fac1199 | ||
|
|
98c93d3248 | ||
|
|
a053706c24 | ||
|
|
43c49f54d2 | ||
|
|
a15dfffa44 | ||
|
|
59985dee72 | ||
|
|
6b7132f134 | ||
|
|
5c2c99282d | ||
|
|
94e6f89d07 | ||
|
|
3804a746df | ||
|
|
5c2d7e2d2a | ||
|
|
c34dd462b6 | ||
|
|
9141b1a641 | ||
|
|
0fea85e2f2 | ||
|
|
a58c191ded | ||
|
|
77089a1178 |
BIN
.snapcraft/travis_snapcraft.cfg
Normal file
BIN
.snapcraft/travis_snapcraft.cfg
Normal file
Binary file not shown.
22
.travis.yml
22
.travis.yml
@@ -1,6 +1,20 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 'stable'
|
- stable
|
||||||
- 'lts/*'
|
- 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'
|
||||||
|
after_success:
|
||||||
|
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
||||||
|
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
|
||||||
|
sudo: required
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
deploy:
|
||||||
|
'on':
|
||||||
|
branch: master
|
||||||
|
provider: script
|
||||||
|
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
|
||||||
|
skip_cleanup: true
|
||||||
|
|||||||
42
Backers.md
42
Backers.md
@@ -1,9 +1,39 @@
|
|||||||
Become a [backer](https://salt.bountysource.com/teams/boostnote) and support Boostnote!
|
Dear all,
|
||||||
You can support Boostnote from $ 5 a month!
|
|
||||||
|
|
||||||
# Backers
|
Thanks for your using!
|
||||||
[Kazu Yokomizo](https://twitter.com/kazup_bot)
|
Boostnote is used in about 200 countries and regions, it is a awesome developer community.
|
||||||
|
|
||||||
kolchan11
|
To continue supporting this growth, and to satisfy community expectations,
|
||||||
|
we would like to invest more time in this project.
|
||||||
|
|
||||||
RonWalker22
|
If you like this project and see its potential, you can help!
|
||||||
|
|
||||||
|
Thanks,
|
||||||
|
Boostnote maintainers.
|
||||||
|
|
||||||
|
### >> [Support via OpenCollective](https://opencollective.com/boostnoteio)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backers
|
||||||
|
[Kazz](https://twitter.com/kazup_bot) - $65
|
||||||
|
|
||||||
|
Intense Raiden - $45
|
||||||
|
|
||||||
|
ravy22 - $25
|
||||||
|
|
||||||
|
trentpolack - $20
|
||||||
|
|
||||||
|
hikariru - $10
|
||||||
|
|
||||||
|
kolchan11 - $10
|
||||||
|
|
||||||
|
RonWalker22 - $10
|
||||||
|
|
||||||
|
hocchuc - $5
|
||||||
|
|
||||||
|
Adam - $5
|
||||||
|
|
||||||
|
Steve - $5
|
||||||
|
|
||||||
|
evmin - $5
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
Please paste some **screenshots** with the **developer tool** open if you report a bug.
|
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug.
|
||||||
|
|
||||||
|
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import CodeMirror from 'codemirror'
|
|||||||
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'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
@@ -232,9 +233,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
||||||
const imageName = Math.random().toString(36).slice(-16)
|
const imageName = Math.random().toString(36).slice(-16)
|
||||||
const storagePath = findStorage(this.props.storageKey).path
|
const storagePath = findStorage(this.props.storageKey).path
|
||||||
const imagePath = path.join(`${storagePath}`, 'images', `${imageName}.png`)
|
const imageDir = path.join(storagePath, 'images')
|
||||||
|
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
|
||||||
require('fs').writeFile(imagePath, binaryData, 'binary')
|
const imagePath = path.join(imageDir, `${imageName}.png`)
|
||||||
|
fs.writeFile(imagePath, binaryData, 'binary')
|
||||||
const imageMd = `})`
|
const imageMd = `})`
|
||||||
this.insertImageMd(imageMd)
|
this.insertImageMd(imageMd)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
||||||
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
||||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||||
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import eventEmitter from 'browser/main/lib/eventEmitter'
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
|
import mdurl from 'mdurl'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { app } = remote
|
const { app } = remote
|
||||||
@@ -34,13 +35,12 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber) {
|
|||||||
}
|
}
|
||||||
${markdownStyle}
|
${markdownStyle}
|
||||||
body {
|
body {
|
||||||
font-family: ${fontFamily.join(', ')};
|
font-family: '${fontFamily.join("','")}';
|
||||||
font-size: ${fontSize}px;
|
font-size: ${fontSize}px;
|
||||||
}
|
}
|
||||||
code {
|
code {
|
||||||
font-family: ${codeBlockFontFamily.join(', ')};
|
font-family: ${codeBlockFontFamily.join(', ')};
|
||||||
background-color: rgba(0,0,0,0.04);
|
background-color: rgba(0,0,0,0.04);
|
||||||
color: #CC305F;
|
|
||||||
}
|
}
|
||||||
.lineNumber {
|
.lineNumber {
|
||||||
${lineNumber && 'display: block !important;'}
|
${lineNumber && 'display: block !important;'}
|
||||||
@@ -184,6 +184,14 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fixDecodedURI (node) {
|
||||||
|
const { innerText, href } = node
|
||||||
|
|
||||||
|
node.innerText = mdurl.decode(href) === innerText
|
||||||
|
? href
|
||||||
|
: innerText
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
||||||
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
|
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
|
||||||
@@ -224,6 +232,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
||||||
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.theme !== this.props.theme) {
|
prevProps.theme !== this.props.theme) {
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
@@ -262,7 +271,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.removeEventListener('click', this.linkClickHandler)
|
el.removeEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
let { value, theme, indentSize, codeBlockTheme, storagePath } = this.props
|
let { value, theme, indentSize, codeBlockTheme, showCopyNotification, storagePath } = this.props
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
|
|
||||||
@@ -279,6 +288,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
|
this.fixDecodedURI(el)
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
el.addEventListener('click', this.anchorClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -309,11 +319,13 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
|
copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
|
||||||
copyIcon.onclick = (e) => {
|
copyIcon.onclick = (e) => {
|
||||||
copy(content)
|
copy(content)
|
||||||
|
if (showCopyNotification) {
|
||||||
this.notify('Saved to Clipboard!', {
|
this.notify('Saved to Clipboard!', {
|
||||||
body: 'Paste it wherever you want!',
|
body: 'Paste it wherever you want!',
|
||||||
silent: true
|
silent: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
el.parentNode.appendChild(copyIcon)
|
el.parentNode.appendChild(copyIcon)
|
||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
|
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
|
||||||
@@ -426,5 +438,6 @@ MarkdownPreview.propTypes = {
|
|||||||
onMouseDown: PropTypes.func,
|
onMouseDown: PropTypes.func,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
|
showCopyNotification: PropTypes.bool,
|
||||||
storagePath: PropTypes.string
|
storagePath: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|||||||
29
browser/components/NavToggleButton.js
Normal file
29
browser/components/NavToggleButton.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for toggle SideNav
|
||||||
|
*/
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import styles from './NavToggleButton.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} isFolded
|
||||||
|
* @param {Function} handleToggleButtonClick
|
||||||
|
*/
|
||||||
|
|
||||||
|
const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
|
||||||
|
<button styleName='navToggle'
|
||||||
|
onClick={(e) => handleToggleButtonClick(e)}
|
||||||
|
>
|
||||||
|
{isFolded
|
||||||
|
? <i className='fa fa-angle-double-right' />
|
||||||
|
: <i className='fa fa-angle-double-left' />
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
NavToggleButton.propTypes = {
|
||||||
|
isFolded: PropTypes.bool.isRequired,
|
||||||
|
handleToggleButtonClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(NavToggleButton, styles)
|
||||||
19
browser/components/NavToggleButton.styl
Normal file
19
browser/components/NavToggleButton.styl
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.navToggle
|
||||||
|
navButtonColor()
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
left 5px
|
||||||
|
bottom 5px
|
||||||
|
border-radius 16.5px
|
||||||
|
height 34px
|
||||||
|
width 34px
|
||||||
|
line-height 32px
|
||||||
|
padding 0
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.navToggle
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
@@ -41,16 +41,18 @@ const TagElementList = (tags) => {
|
|||||||
* @param {boolean} isActive
|
* @param {boolean} isActive
|
||||||
* @param {Object} note
|
* @param {Object} note
|
||||||
* @param {Function} handleNoteClick
|
* @param {Function} handleNoteClick
|
||||||
|
* @param {Function} handleNoteContextMenu
|
||||||
* @param {Function} handleDragStart
|
* @param {Function} handleDragStart
|
||||||
* @param {string} dateDisplay
|
* @param {string} dateDisplay
|
||||||
*/
|
*/
|
||||||
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStart }) => (
|
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
|
||||||
<div styleName={isActive
|
<div styleName={isActive
|
||||||
? 'item--active'
|
? 'item--active'
|
||||||
: 'item'
|
: 'item'
|
||||||
}
|
}
|
||||||
key={`${note.storage}-${note.key}`}
|
key={`${note.storage}-${note.key}`}
|
||||||
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
||||||
|
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
||||||
onDragStart={e => handleDragStart(e, note)}
|
onDragStart={e => handleDragStart(e, note)}
|
||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
@@ -70,6 +72,9 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStar
|
|||||||
{note.isStarred
|
{note.isStarred
|
||||||
? <i styleName='item-star' className='fa fa-star' /> : ''
|
? <i styleName='item-star' className='fa fa-star' /> : ''
|
||||||
}
|
}
|
||||||
|
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
|
||||||
|
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
|
||||||
|
}
|
||||||
{note.type === 'MARKDOWN_NOTE'
|
{note.type === 'MARKDOWN_NOTE'
|
||||||
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
||||||
: ''
|
: ''
|
||||||
@@ -99,6 +104,7 @@ NoteItem.propTypes = {
|
|||||||
isTrashed: PropTypes.bool.isRequired
|
isTrashed: PropTypes.bool.isRequired
|
||||||
}),
|
}),
|
||||||
handleNoteClick: PropTypes.func.isRequired,
|
handleNoteClick: PropTypes.func.isRequired,
|
||||||
|
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||||
handleDragStart: PropTypes.func.isRequired,
|
handleDragStart: PropTypes.func.isRequired,
|
||||||
handleDragEnd: PropTypes.func.isRequired
|
handleDragEnd: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,17 @@ $control-height = 30px
|
|||||||
padding 0
|
padding 0
|
||||||
border-radius 17px
|
border-radius 17px
|
||||||
|
|
||||||
|
.item-pin
|
||||||
|
position absolute
|
||||||
|
right -21px
|
||||||
|
bottom 28px
|
||||||
|
width 34px
|
||||||
|
height 34px
|
||||||
|
color #E54D42
|
||||||
|
font-size 14px
|
||||||
|
padding 0
|
||||||
|
border-radius 17px
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class RealtimeNotification extends React.Component {
|
|||||||
: ''
|
: ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div styleName='notification-area'>{link}</div>
|
<div styleName='notification-area' style={this.props.style}>{link}</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,21 +3,20 @@
|
|||||||
font-size 12px
|
font-size 12px
|
||||||
position absolute
|
position absolute
|
||||||
bottom 0px
|
bottom 0px
|
||||||
|
right 0px
|
||||||
background-color #EBEBEB
|
background-color #EBEBEB
|
||||||
width 100vw
|
|
||||||
height 30px
|
height 30px
|
||||||
text-align center
|
|
||||||
|
|
||||||
.notification-link
|
.notification-link
|
||||||
|
position absolute
|
||||||
|
right 5px
|
||||||
|
top 5px
|
||||||
text-decoration none
|
text-decoration none
|
||||||
color #282A36
|
color #282A36
|
||||||
border 1px solid #6FA8E6
|
border 1px solid #6FA8E6
|
||||||
background-color alpha(#6FA8E6, 0.2)
|
background-color alpha(#6FA8E6, 0.2)
|
||||||
padding 3px 9px
|
padding 3px 9px
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
position absolute
|
|
||||||
bottom 4px
|
|
||||||
margin-left -10%
|
|
||||||
transition 0.2s
|
transition 0.2s
|
||||||
&:hover
|
&:hover
|
||||||
color #1378BD
|
color #1378BD
|
||||||
|
|||||||
@@ -17,12 +17,12 @@
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
color #e74c3c
|
color #e74c3c
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
&:active, &:active:hover
|
&:active, &:active:hover
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
color #e74c3c
|
color #e74c3c
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
@@ -34,12 +34,12 @@
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
color #F9BF3B
|
color #F9BF3B
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
&:active, &:active:hover
|
&:active, &:active:hover
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
color #F9BF3B
|
color #F9BF3B
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
@@ -88,7 +88,7 @@ body[data-theme="dark"]
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
color #c0392b
|
color #c0392b
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
@@ -99,7 +99,7 @@ body[data-theme="dark"]
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
color $ui-favorite-star-button-color
|
color $ui-favorite-star-button-color
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
background-color $ui-button--active-backgroundColor
|
background-color $ui-button--active-backgroundColor
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
|
||||||
.folderList-item-name
|
.folderList-item-name
|
||||||
display block
|
display block
|
||||||
@@ -86,7 +86,7 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
&:active
|
&:active
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
23
browser/components/StorageList.js
Normal file
23
browser/components/StorageList.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for showing StorageList
|
||||||
|
*/
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import styles from './StorgaeList.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array} storgaeList
|
||||||
|
*/
|
||||||
|
|
||||||
|
const StorageList = ({storageList}) => (
|
||||||
|
<div styleName='storageList'>
|
||||||
|
{storageList.length > 0 ? storageList : (
|
||||||
|
<div styleName='storgaeList-empty'>No storage mount.</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
StorageList.propTypes = {
|
||||||
|
storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired
|
||||||
|
}
|
||||||
|
export default CSSModules(StorageList, styles)
|
||||||
20
browser/components/StorgaeList.styl
Normal file
20
browser/components/StorgaeList.styl
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.storageList
|
||||||
|
absolute left right
|
||||||
|
bottom 37px
|
||||||
|
top 160px
|
||||||
|
overflow-y auto
|
||||||
|
|
||||||
|
.storageList-empty
|
||||||
|
padding 0 10px
|
||||||
|
margin-top 15px
|
||||||
|
line-height 24px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.storageList-empty
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
.root-folded
|
||||||
|
.storageList-empty
|
||||||
|
white-space nowrap
|
||||||
|
transform rotate(90deg)
|
||||||
27
browser/components/TagListItem.js
Normal file
27
browser/components/TagListItem.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for showing TagList.
|
||||||
|
*/
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import styles from './TagListItem.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Function} handleClickTagListItem
|
||||||
|
* @param {bool} isActive
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TagListItem = ({name, handleClickTagListItem, isActive}) => (
|
||||||
|
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
||||||
|
<span styleName='tagList-item-name'>
|
||||||
|
{`# ${name}`}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
TagListItem.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
handleClickTagListItem: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(TagListItem, styles)
|
||||||
67
browser/components/TagListItem.styl
Normal file
67
browser/components/TagListItem.styl
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
.tagList-item
|
||||||
|
display flex
|
||||||
|
width 100%
|
||||||
|
height 26px
|
||||||
|
background-color transparent
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding 0
|
||||||
|
margin-bottom 5px
|
||||||
|
text-align left
|
||||||
|
border none
|
||||||
|
overflow ellipsis
|
||||||
|
font-size 12px
|
||||||
|
&:first-child
|
||||||
|
margin-top 0
|
||||||
|
&:hover
|
||||||
|
color $ui-text-color
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
|
transition background-color 0.15s
|
||||||
|
&:active
|
||||||
|
color $ui-text-color
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.tagList-item-active
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
display flex
|
||||||
|
width 100%
|
||||||
|
height 26px
|
||||||
|
padding 0
|
||||||
|
margin-bottom 5px
|
||||||
|
text-align left
|
||||||
|
border none
|
||||||
|
overflow ellipsis
|
||||||
|
font-size 12px
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
transition 0.2s
|
||||||
|
|
||||||
|
.tagList-item-name
|
||||||
|
display block
|
||||||
|
flex 1
|
||||||
|
padding 0 25px
|
||||||
|
height 26px
|
||||||
|
line-height 26px
|
||||||
|
border-width 0 0 0 2px
|
||||||
|
border-style solid
|
||||||
|
border-color transparent
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.tagList-item
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
&:active
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.tagList-item-active
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:active
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
|
&:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
@@ -193,6 +193,7 @@ ol
|
|||||||
&>li>ul, &>li>ol
|
&>li>ul, &>li>ol
|
||||||
margin 0
|
margin 0
|
||||||
code
|
code
|
||||||
|
color #CC305F
|
||||||
padding 0.2em 0.4em
|
padding 0.2em 0.4em
|
||||||
background-color #f7f7f7
|
background-color #f7f7f7
|
||||||
border-radius 3px
|
border-radius 3px
|
||||||
@@ -268,6 +269,16 @@ table
|
|||||||
border-color borderColor
|
border-color borderColor
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px borderColor
|
border-right solid 1px borderColor
|
||||||
|
kbd
|
||||||
|
background-color #fafbfc
|
||||||
|
border solid 1px borderColor
|
||||||
|
border-bottom-color btnColor
|
||||||
|
border-radius 3px
|
||||||
|
box-shadow inset 0 -1px 0 #959da5
|
||||||
|
display inline-block
|
||||||
|
font-size .8em
|
||||||
|
line-height 1
|
||||||
|
padding 3px 5px
|
||||||
|
|
||||||
themeDarkBackground = darken(#21252B, 10%)
|
themeDarkBackground = darken(#21252B, 10%)
|
||||||
themeDarkText = #f9f9f9
|
themeDarkText = #f9f9f9
|
||||||
@@ -316,3 +327,6 @@ body[data-theme="dark"]
|
|||||||
border-color themeDarkTableBorder
|
border-color themeDarkTableBorder
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px themeDarkTableBorder
|
border-right solid 1px themeDarkTableBorder
|
||||||
|
kbd
|
||||||
|
background-color themeDarkBorder
|
||||||
|
color themeDarkText
|
||||||
@@ -196,6 +196,7 @@ class NoteDetail extends React.Component {
|
|||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
value={note.content}
|
value={note.content}
|
||||||
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const themes = fs.readdirSync(themePath)
|
|||||||
.map((themePath) => {
|
.map((themePath) => {
|
||||||
return themePath.substring(0, themePath.lastIndexOf('.'))
|
return themePath.substring(0, themePath.lastIndexOf('.'))
|
||||||
})
|
})
|
||||||
|
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
||||||
|
|
||||||
const consts = {
|
const consts = {
|
||||||
FOLDER_COLORS: [
|
FOLDER_COLORS: [
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ md.use(require('markdown-it-named-headers'), {
|
|||||||
.replace(/\-+$/, '')
|
.replace(/\-+$/, '')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
md.use(require('markdown-it-kbd'))
|
||||||
// Override task item
|
// Override task item
|
||||||
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||||
let content, terminate, i, l, token
|
let content, terminate, i, l, token
|
||||||
|
|||||||
@@ -2,15 +2,16 @@ import _ from 'lodash'
|
|||||||
|
|
||||||
export default function searchFromNotes (notes, search) {
|
export default function searchFromNotes (notes, search) {
|
||||||
if (search.trim().length === 0) return []
|
if (search.trim().length === 0) return []
|
||||||
let searchBlocks = search.split(' ')
|
const searchBlocks = search.split(' ').filter(block => { return block !== '' })
|
||||||
|
|
||||||
|
let foundNotes = findByWord(notes, searchBlocks[0])
|
||||||
searchBlocks.forEach((block) => {
|
searchBlocks.forEach((block) => {
|
||||||
|
foundNotes = findByWord(foundNotes, block)
|
||||||
if (block.match(/^#.+/)) {
|
if (block.match(/^#.+/)) {
|
||||||
notes = findByTag(notes, block)
|
foundNotes = foundNotes.concat(findByTag(notes, block))
|
||||||
} else {
|
|
||||||
notes = findByWord(notes, block)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return notes
|
return foundNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
function findByTag (notes, block) {
|
function findByTag (notes, block) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './InfoPanel.styl'
|
import styles from './InfoPanel.styl'
|
||||||
|
|
||||||
const InfoPanel = ({
|
const InfoPanel = ({
|
||||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type
|
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type, print
|
||||||
}) => (
|
}) => (
|
||||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
@@ -79,9 +79,9 @@ const InfoPanel = ({
|
|||||||
<p>.txt</p>
|
<p>.txt</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--unable'>
|
<button styleName='export--enable' onClick={(e) => print(e)}>
|
||||||
<i className='fa fa-file-pdf-o fa-fw' />
|
<i className='fa fa-print fa-fw' />
|
||||||
<p>.pdf</p>
|
<p>Print</p>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,7 +97,8 @@ InfoPanel.propTypes = {
|
|||||||
exportAsTxt: PropTypes.func.isRequired,
|
exportAsTxt: PropTypes.func.isRequired,
|
||||||
wordCount: PropTypes.number,
|
wordCount: PropTypes.number,
|
||||||
letterCount: PropTypes.number,
|
letterCount: PropTypes.number,
|
||||||
type: PropTypes.string.isRequired
|
type: PropTypes.string.isRequired,
|
||||||
|
print: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(InfoPanel, styles)
|
export default CSSModules(InfoPanel, styles)
|
||||||
|
|||||||
@@ -256,6 +256,10 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print (e) {
|
||||||
|
ee.emit('print')
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { data, config, location } = this.props
|
let { data, config, location } = this.props
|
||||||
let { note } = this.state
|
let { note } = this.state
|
||||||
@@ -330,7 +334,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
>
|
>
|
||||||
<i className={faClassName} styleName='lock-button' />
|
<i className={faClassName} styleName='lock-button' />
|
||||||
<span styleName='control-lockButton-tooltip'>
|
<span styleName='control-lockButton-tooltip'>
|
||||||
{this.state.isLocked ? 'Unlock' : 'Lock'}
|
{this.state.isLocked ? 'Unlock Editor' : 'Keep Editor Locked'}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
return (
|
return (
|
||||||
@@ -357,6 +361,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
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}
|
||||||
|
print={this.print}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -266,11 +266,16 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renameSnippetByIndex (index, name) {
|
renameSnippetByIndex (index, name) {
|
||||||
let snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].name = name
|
snippets[index].name = name
|
||||||
let syntax = CodeMirror.findModeByFileName(name.trim())
|
const syntax = CodeMirror.findModeByFileName(name.trim())
|
||||||
let mode = syntax != null ? syntax.name : null
|
const mode = syntax != null ? syntax.name : null
|
||||||
if (mode != null) snippets[index].mode = mode
|
if (mode != null) {
|
||||||
|
snippets[index].mode = mode
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SNIPPET_LANG', {
|
||||||
|
name: mode
|
||||||
|
})
|
||||||
|
}
|
||||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -291,6 +296,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}, () => {
|
}, () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SELECT_LANG', {
|
||||||
|
name
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
margin 2px 0 15px 2px
|
margin 2px 0 15px 2px
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
height 18px
|
height 18px
|
||||||
box-sizing borde-box
|
box-sizing border-box
|
||||||
border none
|
border none
|
||||||
background-color transparent
|
background-color transparent
|
||||||
outline none
|
outline none
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ function focused () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Main extends React.Component {
|
class Main extends React.Component {
|
||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
@@ -189,6 +190,17 @@ class Main extends React.Component {
|
|||||||
render () {
|
render () {
|
||||||
let { config } = this.props
|
let { config } = this.props
|
||||||
|
|
||||||
|
// the width of the navigation bar when it is folded/collapsed
|
||||||
|
const foldedNavigationWidth = 44
|
||||||
|
let notificationBarOffsetLeft
|
||||||
|
if (this.state.fullScreen) {
|
||||||
|
notificationBarOffsetLeft = 0
|
||||||
|
} else if (config.isSideNavFolded) {
|
||||||
|
notificationBarOffsetLeft = foldedNavigationWidth
|
||||||
|
} else {
|
||||||
|
notificationBarOffsetLeft = this.state.navWidth
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className='Main'
|
className='Main'
|
||||||
@@ -217,7 +229,7 @@ class Main extends React.Component {
|
|||||||
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
||||||
id='main-body'
|
id='main-body'
|
||||||
ref='body'
|
ref='body'
|
||||||
style={{left: config.isSideNavFolded ? 44 : this.state.navWidth}}
|
style={{left: config.isSideNavFolded ? foldedNavigationWidth : this.state.navWidth}}
|
||||||
>
|
>
|
||||||
<TopBar style={{width: this.state.listWidth}}
|
<TopBar style={{width: this.state.listWidth}}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
@@ -256,7 +268,9 @@ class Main extends React.Component {
|
|||||||
ignorePreviewPointerEvents={this.state.isRightSliderFocused}
|
ignorePreviewPointerEvents={this.state.isRightSliderFocused}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<RealtimeNotification />
|
<RealtimeNotification
|
||||||
|
style={{left: notificationBarOffsetLeft}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
height $topBar-height - 1
|
height $topBar-height - 1
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
width: 64px;
|
width: 64px;
|
||||||
margin-right: -15px;
|
|
||||||
|
|
||||||
.root--expanded
|
.root--expanded
|
||||||
@extend .root
|
@extend .root
|
||||||
@@ -14,10 +13,8 @@ $control-height = 34px
|
|||||||
.control
|
.control
|
||||||
position absolute
|
position absolute
|
||||||
top 13px
|
top 13px
|
||||||
left 8px
|
right 7px
|
||||||
right 8px
|
|
||||||
height $control-height
|
height $control-height
|
||||||
overflow hidden
|
|
||||||
display flex
|
display flex
|
||||||
|
|
||||||
.control-newNoteButton
|
.control-newNoteButton
|
||||||
@@ -35,10 +32,11 @@ $control-height = 34px
|
|||||||
|
|
||||||
.control-newNoteButton-tooltip
|
.control-newNoteButton-tooltip
|
||||||
tooltip()
|
tooltip()
|
||||||
position fixed
|
position absolute
|
||||||
pointer-events none
|
pointer-events none
|
||||||
top 50px
|
top 26px
|
||||||
left 433px
|
right -43px
|
||||||
|
width 124px
|
||||||
z-index 200
|
z-index 200
|
||||||
padding 5px
|
padding 5px
|
||||||
line-height normal
|
line-height normal
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { hashHistory } from 'react-router'
|
|||||||
import markdown from 'browser/lib/markdown'
|
import markdown from 'browser/lib/markdown'
|
||||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||||
import stripgtags from 'striptags'
|
import stripgtags from 'striptags'
|
||||||
|
import store from 'browser/main/store'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { Menu, MenuItem, dialog } = remote
|
||||||
@@ -52,6 +53,8 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.contextNotes = []
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@@ -90,6 +93,7 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
if (this.notes.length > 0 && location.query.key == null) {
|
if (this.notes.length > 0 && location.query.key == null) {
|
||||||
let { router } = this.context
|
let { router } = this.context
|
||||||
|
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
|
||||||
router.replace({
|
router.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
@@ -101,9 +105,7 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
// Auto scroll
|
// Auto scroll
|
||||||
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
|
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
const targetIndex = this.getTargetIndex()
|
||||||
return note != null && note.storage + '-' + note.key === location.query.key
|
|
||||||
})
|
|
||||||
if (targetIndex > -1) {
|
if (targetIndex > -1) {
|
||||||
let list = this.refs.list
|
let list = this.refs.list
|
||||||
let item = list.childNodes[targetIndex]
|
let item = list.childNodes[targetIndex]
|
||||||
@@ -129,9 +131,7 @@ class NoteList extends React.Component {
|
|||||||
let { router } = this.context
|
let { router } = this.context
|
||||||
let { location } = this.props
|
let { location } = this.props
|
||||||
|
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
let targetIndex = this.getTargetIndex()
|
||||||
return note.storage + '-' + note.key === location.query.key
|
|
||||||
})
|
|
||||||
|
|
||||||
if (targetIndex === 0) {
|
if (targetIndex === 0) {
|
||||||
return
|
return
|
||||||
@@ -154,9 +154,7 @@ class NoteList extends React.Component {
|
|||||||
let { router } = this.context
|
let { router } = this.context
|
||||||
let { location } = this.props
|
let { location } = this.props
|
||||||
|
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
let targetIndex = this.getTargetIndex()
|
||||||
return note.storage + '-' + note.key === location.query.key
|
|
||||||
})
|
|
||||||
|
|
||||||
if (targetIndex === this.notes.length - 1) {
|
if (targetIndex === this.notes.length - 1) {
|
||||||
targetIndex = 0
|
targetIndex = 0
|
||||||
@@ -184,9 +182,7 @@ class NoteList extends React.Component {
|
|||||||
const { router } = this.context
|
const { router } = this.context
|
||||||
const { location } = this.props
|
const { location } = this.props
|
||||||
|
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
let targetIndex = this.getTargetIndex()
|
||||||
return note.storage + '-' + note.key === noteHash
|
|
||||||
})
|
|
||||||
|
|
||||||
if (targetIndex < 0) targetIndex = 0
|
if (targetIndex < 0) targetIndex = 0
|
||||||
|
|
||||||
@@ -233,49 +229,67 @@ class NoteList extends React.Component {
|
|||||||
let { data, params, location } = this.props
|
let { data, params, location } = this.props
|
||||||
let { router } = this.context
|
let { router } = this.context
|
||||||
|
|
||||||
if (location.pathname.match(/\/home/)) {
|
if (location.pathname.match(/\/home/) || location.pathname.match(/\alltags/)) {
|
||||||
return data.noteMap.map((note) => note)
|
const allNotes = data.noteMap.map((note) => note)
|
||||||
|
this.contextNotes = allNotes
|
||||||
|
return allNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.pathname.match(/\/starred/)) {
|
if (location.pathname.match(/\/starred/)) {
|
||||||
return data.starredSet.toJS()
|
const starredNotes = data.starredSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
this.contextNotes = starredNotes
|
||||||
|
return starredNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.pathname.match(/\/searched/)) {
|
if (location.pathname.match(/\/searched/)) {
|
||||||
const searchInputText = document.getElementsByClassName('searchInput')[0].value
|
const searchInputText = document.getElementsByClassName('searchInput')[0].value
|
||||||
if (searchInputText === '') {
|
if (searchInputText === '') {
|
||||||
router.push('/home')
|
return this.sortByPin(this.contextNotes)
|
||||||
}
|
}
|
||||||
return searchFromNotes(this.notes, searchInputText)
|
return searchFromNotes(this.contextNotes, searchInputText)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.pathname.match(/\/trashed/)) {
|
if (location.pathname.match(/\/trashed/)) {
|
||||||
return data.trashedSet.toJS()
|
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
this.contextNotes = trashedNotes
|
||||||
|
return trashedNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
let storageKey = params.storageKey
|
if (location.pathname.match(/\/tags/)) {
|
||||||
let folderKey = params.folderKey
|
return data.noteMap.map(note => {
|
||||||
let storage = data.storageMap.get(storageKey)
|
return note
|
||||||
if (storage == null) return []
|
}).filter(note => {
|
||||||
|
return note.tags.includes(params.tagname)
|
||||||
let folder = _.find(storage.folders, {key: folderKey})
|
})
|
||||||
if (folder == null) {
|
|
||||||
let storageNoteSet = data.storageNoteMap
|
|
||||||
.get(storage.key)
|
|
||||||
if (storageNoteSet == null) storageNoteSet = []
|
|
||||||
return storageNoteSet
|
|
||||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let folderNoteKeyList = data.folderNoteMap
|
return this.getContextNotes()
|
||||||
.get(storage.key + '-' + folder.key)
|
}
|
||||||
|
|
||||||
return folderNoteKeyList != null
|
// get notes in the current folder
|
||||||
? folderNoteKeyList
|
getContextNotes () {
|
||||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
const { data, params } = this.props
|
||||||
: []
|
const storageKey = params.storageKey
|
||||||
|
const folderKey = params.folderKey
|
||||||
|
const storage = data.storageMap.get(storageKey)
|
||||||
|
if (storage === undefined) return []
|
||||||
|
|
||||||
|
const folder = _.find(storage.folders, {key: folderKey})
|
||||||
|
if (folder === undefined) {
|
||||||
|
const storageNoteSet = data.storageNoteMap.get(storage.key) || []
|
||||||
|
return storageNoteSet.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
const folderNoteKeyList = data.folderNoteMap.get(`${storage.key}-${folder.key}`) || []
|
||||||
|
return folderNoteKeyList.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
sortByPin (unorderedNotes) {
|
||||||
|
const pinnedNotes = unorderedNotes.filter((note) => {
|
||||||
|
return note.isPinned
|
||||||
|
})
|
||||||
|
|
||||||
|
return pinnedNotes.concat(unorderedNotes)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNoteClick (e, uniqueKey) {
|
handleNoteClick (e, uniqueKey) {
|
||||||
@@ -319,10 +333,7 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
alertIfSnippet () {
|
alertIfSnippet () {
|
||||||
let { location } = this.props
|
const targetIndex = this.getTargetIndex()
|
||||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
|
||||||
return `${note.storage}-${note.key}` === location.query.key
|
|
||||||
})
|
|
||||||
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
|
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
@@ -338,6 +349,53 @@ class NoteList extends React.Component {
|
|||||||
e.dataTransfer.setData('note', noteData)
|
e.dataTransfer.setData('note', noteData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleNoteContextMenu (e, uniqueKey) {
|
||||||
|
const { location } = this.props
|
||||||
|
const targetIndex = this.getTargetIndex()
|
||||||
|
let note = this.notes[targetIndex]
|
||||||
|
const label = note.isPinned ? 'Remove pin' : 'Pin to Top'
|
||||||
|
|
||||||
|
let menu = new Menu()
|
||||||
|
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: label,
|
||||||
|
click: (e) => this.pinToTop(e, uniqueKey)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: 'Delete Note',
|
||||||
|
click: (e) => this.deleteNote(e, uniqueKey)
|
||||||
|
}))
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
|
|
||||||
|
pinToTop (e, uniqueKey) {
|
||||||
|
const { data, params } = this.props
|
||||||
|
const storageKey = params.storageKey
|
||||||
|
const folderKey = params.folderKey
|
||||||
|
|
||||||
|
const currentStorage = data.storageMap.get(storageKey)
|
||||||
|
const currentFolder = _.find(currentStorage.folders, {key: folderKey})
|
||||||
|
|
||||||
|
const targetIndex = this.getTargetIndex()
|
||||||
|
let note = this.notes[targetIndex]
|
||||||
|
note.isPinned = !note.isPinned
|
||||||
|
|
||||||
|
dataApi
|
||||||
|
.updateNote(note.storage, note.key, note)
|
||||||
|
.then((note) => {
|
||||||
|
store.dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteNote (e, uniqueKey) {
|
||||||
|
this.handleNoteClick(e, uniqueKey)
|
||||||
|
ee.emit('detail:delete')
|
||||||
|
}
|
||||||
|
|
||||||
importFromFile () {
|
importFromFile () {
|
||||||
const { dispatch, location } = this.props
|
const { dispatch, location } = this.props
|
||||||
|
|
||||||
@@ -364,9 +422,7 @@ class NoteList extends React.Component {
|
|||||||
addNotesFromFiles (filepaths) {
|
addNotesFromFiles (filepaths) {
|
||||||
const { dispatch, location } = this.props
|
const { dispatch, location } = this.props
|
||||||
|
|
||||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
const targetIndex = this.getTargetIndex()
|
||||||
return note !== null && `${note.storage}-${note.key}` === location.query.key
|
|
||||||
})
|
|
||||||
|
|
||||||
const storageKey = this.notes[targetIndex].storage
|
const storageKey = this.notes[targetIndex].storage
|
||||||
const folderKey = this.notes[targetIndex].folder
|
const folderKey = this.notes[targetIndex].folder
|
||||||
@@ -397,6 +453,14 @@ class NoteList extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTargetIndex () {
|
||||||
|
const { location } = this.props
|
||||||
|
const targetIndex = _.findIndex(this.notes, (note) => {
|
||||||
|
return `${note.storage}-${note.key}` === location.query.key
|
||||||
|
})
|
||||||
|
return targetIndex
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { location, notes, config, dispatch } = this.props
|
let { location, notes, config, dispatch } = this.props
|
||||||
let sortFunc = config.sortBy === 'CREATED_AT'
|
let sortFunc = config.sortBy === 'CREATED_AT'
|
||||||
@@ -404,9 +468,10 @@ class NoteList extends React.Component {
|
|||||||
: config.sortBy === 'ALPHABETICAL'
|
: config.sortBy === 'ALPHABETICAL'
|
||||||
? sortByAlphabetical
|
? sortByAlphabetical
|
||||||
: sortByUpdatedAt
|
: sortByUpdatedAt
|
||||||
this.notes = notes = this.getNotes()
|
const sortedNotes = location.pathname.match(/\/home|\/starred|\/trash/)
|
||||||
.sort(sortFunc)
|
? this.getNotes().sort(sortFunc)
|
||||||
.filter((note) => {
|
: this.sortByPin(this.getNotes().sort(sortFunc))
|
||||||
|
this.notes = notes = sortedNotes.filter((note) => {
|
||||||
// this is for the trash box
|
// this is for the trash box
|
||||||
if (note.isTrashed !== true || location.pathname === '/trashed') return true
|
if (note.isTrashed !== true || location.pathname === '/trashed') return true
|
||||||
})
|
})
|
||||||
@@ -432,8 +497,10 @@ class NoteList extends React.Component {
|
|||||||
note={note}
|
note={note}
|
||||||
dateDisplay={dateDisplay}
|
dateDisplay={dateDisplay}
|
||||||
key={key}
|
key={key}
|
||||||
|
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
|
||||||
handleNoteClick={this.handleNoteClick.bind(this)}
|
handleNoteClick={this.handleNoteClick.bind(this)}
|
||||||
handleDragStart={this.handleDragStart.bind(this)}
|
handleDragStart={this.handleDragStart.bind(this)}
|
||||||
|
pathname={location.pathname}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -457,13 +524,13 @@ class NoteList extends React.Component {
|
|||||||
>
|
>
|
||||||
<div styleName='control'>
|
<div styleName='control'>
|
||||||
<div styleName='control-sortBy'>
|
<div styleName='control-sortBy'>
|
||||||
<i className='fa fa-bolt' />
|
<i className='fa fa-angle-down' />
|
||||||
<select styleName='control-sortBy-select'
|
<select styleName='control-sortBy-select'
|
||||||
value={config.sortBy}
|
value={config.sortBy}
|
||||||
onChange={(e) => this.handleSortByChange(e)}
|
onChange={(e) => this.handleSortByChange(e)}
|
||||||
>
|
>
|
||||||
<option value='UPDATED_AT'>Last Updated</option>
|
<option value='UPDATED_AT'>Updated</option>
|
||||||
<option value='CREATED_AT'>Creation Time</option>
|
<option value='CREATED_AT'>Created</option>
|
||||||
<option value='ALPHABETICAL'>Alphabetically</option>
|
<option value='ALPHABETICAL'>Alphabetically</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,59 +4,81 @@
|
|||||||
background-color #f9f9f9
|
background-color #f9f9f9
|
||||||
user-select none
|
user-select none
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
|
height: 100vh
|
||||||
|
display: flex
|
||||||
|
flex-direction column
|
||||||
|
|
||||||
.top
|
.top
|
||||||
height $topBar-height
|
padding-bottom 15px
|
||||||
|
|
||||||
.top-menu
|
.top-menu
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
height $topBar-height
|
position absolute
|
||||||
padding 0 15px
|
top 22px
|
||||||
font-size 12px
|
right 5px
|
||||||
width 100%
|
height 23px
|
||||||
text-align left
|
width 2em
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
&:active, &:active:hover
|
&:active, &:active:hover
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
|
|
||||||
|
.switch-buttons
|
||||||
|
background-color transparent
|
||||||
|
border 1px solid $ui-borderColor
|
||||||
|
width 110px
|
||||||
|
height 25px
|
||||||
|
margin 20px auto 0px auto
|
||||||
|
border-radius 1px
|
||||||
|
|
||||||
|
.non-active-button
|
||||||
|
navButtonColor()
|
||||||
|
font-weight 600
|
||||||
|
width 54px
|
||||||
|
height 23px
|
||||||
|
|
||||||
|
.active-button
|
||||||
|
@extend .non-active-button
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 70%)
|
||||||
|
color alpha($ui-text-color, 70%)
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
.top-menu-label
|
.top-menu-label
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
|
opacity 0
|
||||||
|
|
||||||
.storageList
|
.tabBody
|
||||||
absolute left right
|
flex 1
|
||||||
bottom 37px
|
display flex
|
||||||
top 160px
|
flex-direction column
|
||||||
|
|
||||||
|
.tag-title
|
||||||
|
padding-left 15px
|
||||||
|
padding-bottom 13px
|
||||||
|
p
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.tagList
|
||||||
overflow-y auto
|
overflow-y auto
|
||||||
|
flex: 1
|
||||||
.storageList-empty
|
|
||||||
padding 0 10px
|
|
||||||
margin-top 15px
|
|
||||||
line-height 24px
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.navToggle
|
|
||||||
navButtonColor()
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
right 5px
|
|
||||||
bottom 5px
|
|
||||||
border-radius 16.5px
|
|
||||||
height 34px
|
|
||||||
width 34px
|
|
||||||
line-height 32px
|
|
||||||
padding 0
|
|
||||||
|
|
||||||
.root--folded
|
.root--folded
|
||||||
@extend .root
|
height 100vh
|
||||||
width 44px
|
width $sideNav--folded-width
|
||||||
.storageList-empty
|
.switch-buttons
|
||||||
white-space nowrap
|
display none
|
||||||
transform rotate(90deg)
|
.top
|
||||||
|
height 60px
|
||||||
.top-menu
|
.top-menu
|
||||||
width 44px - 1
|
position static
|
||||||
|
width $sideNav--folded-width
|
||||||
|
height 60px
|
||||||
text-align center
|
text-align center
|
||||||
&:hover .top-menu-label
|
&:hover .top-menu-label
|
||||||
transition opacity 0.15s
|
transition opacity 0.15s
|
||||||
@@ -65,7 +87,7 @@
|
|||||||
position fixed
|
position fixed
|
||||||
display inline-block
|
display inline-block
|
||||||
height 30px
|
height 30px
|
||||||
left 32px
|
left $sideNav--folded-width
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
margin-top -8px
|
margin-top -8px
|
||||||
opacity 0
|
opacity 0
|
||||||
@@ -79,30 +101,6 @@
|
|||||||
border-bottom-right-radius 2px
|
border-bottom-right-radius 2px
|
||||||
pointer-events none
|
pointer-events none
|
||||||
font-size 12px
|
font-size 12px
|
||||||
.menu-button, .menu-button--active
|
|
||||||
text-align center
|
|
||||||
&:hover .menu-button-label
|
|
||||||
transition opacity 0.15s
|
|
||||||
opacity 1
|
|
||||||
|
|
||||||
.menu-button-label
|
|
||||||
position fixed
|
|
||||||
display inline-block
|
|
||||||
height 32px
|
|
||||||
left 44px
|
|
||||||
padding 0 10px
|
|
||||||
margin-top -8px
|
|
||||||
margin-left 0
|
|
||||||
overflow ellipsis
|
|
||||||
background-color $ui-tooltip-backgroundColor
|
|
||||||
z-index 10
|
|
||||||
color white
|
|
||||||
line-height 32px
|
|
||||||
border-top-right-radius 2px
|
|
||||||
border-bottom-right-radius 2px
|
|
||||||
pointer-events none
|
|
||||||
opacity 0
|
|
||||||
font-size 12px
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root, .root--folded
|
.root, .root--folded
|
||||||
@@ -120,11 +118,26 @@ body[data-theme="dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
|
||||||
.storageList-empty
|
.switch-buttons
|
||||||
color $ui-dark-inactive-text-color
|
border-color $ui-dark-borderColor
|
||||||
|
|
||||||
.navToggle
|
.non-active-button
|
||||||
&:hover
|
navDarkButtonColor()
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
|
||||||
transition 0.15s
|
.active-button
|
||||||
|
@extend .non-active-button
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 70%)
|
||||||
|
color alpha($ui-dark-text-color, 70%)
|
||||||
|
&:active
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
&:active, &:active:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.tag-title
|
||||||
|
p
|
||||||
|
color alpha($ui-dark-text-color, 60%)
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import { openModal } from 'browser/main/lib/modal'
|
|||||||
import PreferencesModal from '../modals/PreferencesModal'
|
import PreferencesModal from '../modals/PreferencesModal'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import StorageItem from './StorageItem'
|
import StorageItem from './StorageItem'
|
||||||
|
import TagListItem from 'browser/components/TagListItem'
|
||||||
import SideNavFilter from 'browser/components/SideNavFilter'
|
import SideNavFilter from 'browser/components/SideNavFilter'
|
||||||
|
import StorageList from 'browser/components/StorageList'
|
||||||
|
import NavToggleButton from 'browser/components/NavToggleButton'
|
||||||
|
|
||||||
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
|
||||||
@@ -38,13 +41,85 @@ class SideNav extends React.Component {
|
|||||||
router.push('/trashed')
|
router.push('/trashed')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSwitchFoldersButtonClick () {
|
||||||
|
const { router } = this.context
|
||||||
|
router.push('/home')
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSwitchTagsButtonClick () {
|
||||||
|
const { router } = this.context
|
||||||
|
router.push('/alltags')
|
||||||
|
}
|
||||||
|
|
||||||
|
SideNavComponent (isFolded, storageList) {
|
||||||
|
let { location, data } = this.props
|
||||||
|
|
||||||
|
const isHomeActive = !!location.pathname.match(/^\/home$/)
|
||||||
|
const isStarredActive = !!location.pathname.match(/^\/starred$/)
|
||||||
|
const isTrashedActive = !!location.pathname.match(/^\/trashed$/)
|
||||||
|
|
||||||
|
let component
|
||||||
|
|
||||||
|
// TagsMode is not selected
|
||||||
|
if (!location.pathname.match('/tags') && !location.pathname.match('/alltags')) {
|
||||||
|
component = (
|
||||||
|
<div>
|
||||||
|
<SideNavFilter
|
||||||
|
isFolded={isFolded}
|
||||||
|
isHomeActive={isHomeActive}
|
||||||
|
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
|
||||||
|
isStarredActive={isStarredActive}
|
||||||
|
isTrashedActive={isTrashedActive}
|
||||||
|
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
|
||||||
|
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StorageList storageList={storageList} />
|
||||||
|
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
component = (
|
||||||
|
<div styleName='tabBody'>
|
||||||
|
<div styleName='tag-title'>
|
||||||
|
<p>Tags</p>
|
||||||
|
</div>
|
||||||
|
<div styleName='tagList'>
|
||||||
|
{this.tagListComponent(data)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return component
|
||||||
|
}
|
||||||
|
|
||||||
|
tagListComponent () {
|
||||||
|
const { data, location } = this.props
|
||||||
|
let tagList = data.tagNoteMap.map((tag, key) => {
|
||||||
|
return key
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
tagList.map(tag => (
|
||||||
|
<TagListItem
|
||||||
|
name={tag}
|
||||||
|
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
|
||||||
|
isActive={!!location.pathname.match(tag)}
|
||||||
|
key={tag}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClickTagListItem (name) {
|
||||||
|
const { router } = this.context
|
||||||
|
router.push(`/tags/${name}`)
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { data, location, config, dispatch } = this.props
|
let { data, location, config, dispatch } = this.props
|
||||||
|
|
||||||
let isFolded = config.isSideNavFolded
|
let isFolded = config.isSideNavFolded
|
||||||
let isHomeActive = !!location.pathname.match(/^\/home$/)
|
|
||||||
let isStarredActive = !!location.pathname.match(/^\/starred$/)
|
|
||||||
let isTrashedActive = !!location.pathname.match(/^\/trashed$/)
|
|
||||||
|
|
||||||
let storageList = data.storageMap.map((storage, key) => {
|
let storageList = data.storageMap.map((storage, key) => {
|
||||||
return <StorageItem
|
return <StorageItem
|
||||||
@@ -58,6 +133,7 @@ class SideNav extends React.Component {
|
|||||||
})
|
})
|
||||||
let style = {}
|
let style = {}
|
||||||
if (!isFolded) style.width = this.props.width
|
if (!isFolded) style.width = this.props.width
|
||||||
|
const isTagActive = location.pathname.match(/tag/)
|
||||||
return (
|
return (
|
||||||
<div className='SideNav'
|
<div className='SideNav'
|
||||||
styleName={isFolded ? 'root--folded' : 'root'}
|
styleName={isFolded ? 'root--folded' : 'root'}
|
||||||
@@ -65,6 +141,11 @@ class SideNav extends React.Component {
|
|||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<div styleName='top'>
|
<div styleName='top'>
|
||||||
|
<div styleName='switch-buttons'>
|
||||||
|
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={this.handleSwitchFoldersButtonClick.bind(this)}>Folders</button>
|
||||||
|
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={this.handleSwitchTagsButtonClick.bind(this)}>Tags</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<button styleName='top-menu'
|
<button styleName='top-menu'
|
||||||
onClick={(e) => this.handleMenuButtonClick(e)}
|
onClick={(e) => this.handleMenuButtonClick(e)}
|
||||||
>
|
>
|
||||||
@@ -72,30 +153,8 @@ class SideNav extends React.Component {
|
|||||||
<span styleName='top-menu-label'>Preferences</span>
|
<span styleName='top-menu-label'>Preferences</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SideNavFilter
|
|
||||||
isFolded={isFolded}
|
|
||||||
isHomeActive={isHomeActive}
|
|
||||||
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
|
|
||||||
isStarredActive={isStarredActive}
|
|
||||||
isTrashedActive={isTrashedActive}
|
|
||||||
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
|
|
||||||
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div styleName='storageList'>
|
|
||||||
{storageList.length > 0 ? storageList : (
|
|
||||||
<div styleName='storageList-empty'>No storage mount.</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<button styleName='navToggle'
|
{this.SideNavComponent(isFolded, storageList)}
|
||||||
onClick={(e) => this.handleToggleButtonClick(e)}
|
|
||||||
>
|
|
||||||
{isFolded
|
|
||||||
? <i className='fa fa-angle-double-right' />
|
|
||||||
: <i className='fa fa-angle-double-left' />
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ class TopBar extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
search: '',
|
search: '',
|
||||||
searchOptions: [],
|
searchOptions: [],
|
||||||
isSearching: false
|
isSearching: false,
|
||||||
|
isAlphabet: false,
|
||||||
|
isIME: false,
|
||||||
|
isConfirmTranslation: false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.focusSearchHandler = () => {
|
this.focusSearchHandler = () => {
|
||||||
@@ -34,13 +37,56 @@ class TopBar extends React.Component {
|
|||||||
ee.off('top:focus-search', this.focusSearchHandler)
|
ee.off('top:focus-search', this.focusSearchHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchChange (e) {
|
handleKeyDown (e) {
|
||||||
let { router } = this.context
|
// reset states
|
||||||
|
this.setState({
|
||||||
|
isAlphabet: false,
|
||||||
|
isIME: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// When the key is an alphabet, del, enter or ctr
|
||||||
|
if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) {
|
||||||
|
this.setState({
|
||||||
|
isAlphabet: true
|
||||||
|
})
|
||||||
|
// When the key is an IME input (Japanese, Chinese)
|
||||||
|
} else if (e.keyCode === 229) {
|
||||||
|
this.setState({
|
||||||
|
isIME: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyUp (e) {
|
||||||
|
const { router } = this.context
|
||||||
|
// reset states
|
||||||
|
this.setState({
|
||||||
|
isConfirmTranslation: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// When the key is translation confirmation (Enter, Space)
|
||||||
|
if (this.state.isIME && (e.keyCode === 32 || e.keyCode === 13)) {
|
||||||
|
this.setState({
|
||||||
|
isConfirmTranslation: true
|
||||||
|
})
|
||||||
router.push('/searched')
|
router.push('/searched')
|
||||||
this.setState({
|
this.setState({
|
||||||
search: this.refs.searchInput.value
|
search: this.refs.searchInput.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearchChange (e) {
|
||||||
|
const { router } = this.context
|
||||||
|
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
|
||||||
|
router.push('/searched')
|
||||||
|
} else {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
search: this.refs.searchInput.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleSearchFocus (e) {
|
handleSearchFocus (e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -93,6 +139,8 @@ class TopBar extends React.Component {
|
|||||||
ref='searchInput'
|
ref='searchInput'
|
||||||
value={this.state.search}
|
value={this.state.search}
|
||||||
onChange={(e) => this.handleSearchChange(e)}
|
onChange={(e) => this.handleSearchChange(e)}
|
||||||
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
|
onKeyUp={(e) => this.handleKeyUp(e)}
|
||||||
placeholder='Search'
|
placeholder='Search'
|
||||||
type='text'
|
type='text'
|
||||||
className='searchInput'
|
className='searchInput'
|
||||||
|
|||||||
@@ -102,3 +102,10 @@ body[data-theme="dark"]
|
|||||||
background #B1D7FE
|
background #B1D7FE
|
||||||
::selection
|
::selection
|
||||||
background #B1D7FE
|
background #B1D7FE
|
||||||
|
|
||||||
|
.sortableItemHelper
|
||||||
|
z-index modalZIndex + 5
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.sortableItemHelper
|
||||||
|
color: $ui-dark-text-color
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ document.addEventListener('click', function (e) {
|
|||||||
const className = e.target.className
|
const className = e.target.className
|
||||||
if (!className && typeof (className) !== 'string') return
|
if (!className && typeof (className) !== 'string') return
|
||||||
const isInfoButton = className.includes('infoButton')
|
const isInfoButton = className.includes('infoButton')
|
||||||
const isInfoPanel = e.target.offsetParent.className.includes('infoPanel')
|
const offsetParent = e.target.offsetParent
|
||||||
|
const isInfoPanel = offsetParent !== null
|
||||||
|
? offsetParent.className.includes('infoPanel')
|
||||||
|
: false
|
||||||
if (isInfoButton || isInfoPanel) return
|
if (isInfoButton || isInfoPanel) return
|
||||||
const infoPanel = document.querySelector('.infoPanel')
|
const infoPanel = document.querySelector('.infoPanel')
|
||||||
if (infoPanel) infoPanel.style.display = 'none'
|
if (infoPanel) infoPanel.style.display = 'none'
|
||||||
@@ -62,6 +65,11 @@ ReactDOM.render((
|
|||||||
<Route path='starred' />
|
<Route path='starred' />
|
||||||
<Route path='searched' />
|
<Route path='searched' />
|
||||||
<Route path='trashed' />
|
<Route path='trashed' />
|
||||||
|
<Route path='alltags' />
|
||||||
|
<Route path='tags'>
|
||||||
|
<IndexRedirect to='/alltags' />
|
||||||
|
<Route path=':tagname' />
|
||||||
|
</Route>
|
||||||
<Route path='storages'>
|
<Route path='storages'>
|
||||||
<IndexRedirect to='/home' />
|
<IndexRedirect to='/home' />
|
||||||
<Route path=':storageKey'>
|
<Route path=':storageKey'>
|
||||||
|
|||||||
@@ -2,17 +2,37 @@ const AWS = require('aws-sdk')
|
|||||||
const AMA = require('aws-sdk-mobile-analytics')
|
const AMA = require('aws-sdk-mobile-analytics')
|
||||||
const ConfigManager = require('browser/main/lib/ConfigManager')
|
const ConfigManager = require('browser/main/lib/ConfigManager')
|
||||||
|
|
||||||
|
const remote = require('electron').remote
|
||||||
|
const os = require('os')
|
||||||
|
|
||||||
AWS.config.region = 'us-east-1'
|
AWS.config.region = 'us-east-1'
|
||||||
if (process.env.NODE_ENV === 'production' && ConfigManager.default.get().amaEnabled) {
|
if (process.env.NODE_ENV === 'production' && ConfigManager.default.get().amaEnabled) {
|
||||||
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
|
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
|
||||||
IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx'
|
IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const validPlatformName = convertPlatformName(os.platform())
|
||||||
|
|
||||||
const mobileAnalyticsClient = new AMA.Manager({
|
const mobileAnalyticsClient = new AMA.Manager({
|
||||||
appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
appTitle: 'xxxxxxxxxx'
|
appTitle: 'xxxxxxxxxx',
|
||||||
|
appVersionName: remote.app.getVersion().toString(),
|
||||||
|
platform: validPlatformName
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convertPlatformName (platformName) {
|
||||||
|
if (platformName === 'darwin') {
|
||||||
|
return 'MacOS'
|
||||||
|
} else if (platformName === 'win32') {
|
||||||
|
return 'Windows'
|
||||||
|
} else if (platformName === 'linux') {
|
||||||
|
return 'Linux'
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initAwsMobileAnalytics () {
|
function initAwsMobileAnalytics () {
|
||||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
||||||
AWS.config.credentials.get((err) => {
|
AWS.config.credentials.get((err) => {
|
||||||
@@ -24,16 +44,28 @@ function initAwsMobileAnalytics () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordDynamicCustomEvent (type) {
|
function recordDynamicCustomEvent (type, options = {}) {
|
||||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
||||||
mobileAnalyticsClient.recordEvent(type)
|
try {
|
||||||
|
mobileAnalyticsClient.recordEvent(type, options)
|
||||||
|
} catch (analyticsError) {
|
||||||
|
if (analyticsError instanceof ReferenceError) {
|
||||||
|
console.log(analyticsError.name + ': ' + analyticsError.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordStaticCustomEvent () {
|
function recordStaticCustomEvent () {
|
||||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
||||||
|
try {
|
||||||
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
|
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
|
||||||
uiColorTheme: ConfigManager.default.get().ui.theme
|
uiColorTheme: ConfigManager.default.get().ui.theme
|
||||||
})
|
})
|
||||||
|
} catch (analyticsError) {
|
||||||
|
if (analyticsError instanceof ReferenceError) {
|
||||||
|
console.log(analyticsError.name + ': ' + analyticsError.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const DEFAULT_CONFIG = {
|
|||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
|
showCopyNotification: true,
|
||||||
disableDirectWrite: false,
|
disableDirectWrite: false,
|
||||||
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
|
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const dataApi = {
|
|||||||
createFolder: require('./createFolder'),
|
createFolder: require('./createFolder'),
|
||||||
updateFolder: require('./updateFolder'),
|
updateFolder: require('./updateFolder'),
|
||||||
deleteFolder: require('./deleteFolder'),
|
deleteFolder: require('./deleteFolder'),
|
||||||
|
reorderFolder: require('./reorderFolder'),
|
||||||
createNote: require('./createNote'),
|
createNote: require('./createNote'),
|
||||||
updateNote: require('./updateNote'),
|
updateNote: require('./updateNote'),
|
||||||
deleteNote: require('./deleteNote'),
|
deleteNote: require('./deleteNote'),
|
||||||
|
|||||||
43
browser/main/lib/dataApi/reorderFolder.js
Normal file
43
browser/main/lib/dataApi/reorderFolder.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
const _ = require('lodash')
|
||||||
|
_.move = require('lodash-move').default
|
||||||
|
const path = require('path')
|
||||||
|
const resolveStorageData = require('./resolveStorageData')
|
||||||
|
const CSON = require('@rokt33r/season')
|
||||||
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} storageKey
|
||||||
|
* @param {number} oldIndex
|
||||||
|
* @param {number} newIndex
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
* ```
|
||||||
|
* {
|
||||||
|
* storage: Object
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
function reorderFolder (storageKey, oldIndex, newIndex) {
|
||||||
|
let rawStorages
|
||||||
|
let targetStorage
|
||||||
|
try {
|
||||||
|
if (!_.isNumber(oldIndex)) throw new Error('oldIndex must be a number.')
|
||||||
|
if (!_.isNumber(newIndex)) throw new Error('newIndex must be a number.')
|
||||||
|
|
||||||
|
targetStorage = findStorage(storageKey)
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolveStorageData(targetStorage)
|
||||||
|
.then(function reorderFolder (storage) {
|
||||||
|
storage.folders = _.move(storage.folders, oldIndex, newIndex)
|
||||||
|
CSON.writeFileSync(path.join(storage.path, 'boostnote.json'), _.pick(storage, ['folders', 'version']))
|
||||||
|
|
||||||
|
return {
|
||||||
|
storage
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = reorderFolder
|
||||||
@@ -2,6 +2,7 @@ const _ = require('lodash')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const resolveStorageData = require('./resolveStorageData')
|
const resolveStorageData = require('./resolveStorageData')
|
||||||
const CSON = require('@rokt33r/season')
|
const CSON = require('@rokt33r/season')
|
||||||
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} storageKey
|
* @param {String} storageKey
|
||||||
@@ -29,11 +30,7 @@ function updateFolder (storageKey, folderKey, input) {
|
|||||||
if (!_.isString(input.name)) throw new Error('Name must be a string.')
|
if (!_.isString(input.name)) throw new Error('Name must be a string.')
|
||||||
if (!_.isString(input.color)) throw new Error('Color must be a string.')
|
if (!_.isString(input.color)) throw new Error('Color must be a string.')
|
||||||
|
|
||||||
rawStorages = JSON.parse(localStorage.getItem('storages'))
|
targetStorage = findStorage(storageKey)
|
||||||
if (!_.isArray(rawStorages)) throw new Error('Target storage doesn\'t exist.')
|
|
||||||
|
|
||||||
targetStorage = _.find(rawStorages, {key: storageKey})
|
|
||||||
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Promise.reject(e)
|
return Promise.reject(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ function validateInput (input) {
|
|||||||
validatedInput.isTrashed = !!input.isTrashed
|
validatedInput.isTrashed = !!input.isTrashed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (input.isPinned !== undefined) {
|
||||||
|
validatedInput.isPinned = !!input.isPinned
|
||||||
|
}
|
||||||
|
|
||||||
validatedInput.type = input.type
|
validatedInput.type = input.type
|
||||||
switch (input.type) {
|
switch (input.type) {
|
||||||
case 'MARKDOWN_NOTE':
|
case 'MARKDOWN_NOTE':
|
||||||
@@ -104,6 +108,7 @@ function updateNote (storageKey, noteKey, input) {
|
|||||||
noteData.isStarred = false
|
noteData.isStarred = false
|
||||||
noteData.isTrashed = false
|
noteData.isTrashed = false
|
||||||
noteData.tags = []
|
noteData.tags = []
|
||||||
|
noteData.isPinned = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteData.type === 'SNIPPET_NOTE') {
|
if (noteData.type === 'SNIPPET_NOTE') {
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ class InitModal extends React.Component {
|
|||||||
type: 'MARKDOWN_NOTE',
|
type: 'MARKDOWN_NOTE',
|
||||||
folder: data.storage.folders[0].key,
|
folder: data.storage.folders[0].key,
|
||||||
title: 'Welcome to Boostnote!',
|
title: 'Welcome to Boostnote!',
|
||||||
content: '# Welcome to Boostnote! \n### _Click to edit this note._\n\n---\n\nBoostnote is an *open source* note-taking app. \nRepository is published on [GitHub](https://github.com/BoostIO/Boostnote), and tweeting everyday on [@Boostnoteapp](https://twitter.com/boostnoteapp)!\n\n## Features \n- [x] No Internet and Registration Required. \n- [ ] Quick search and copy the content of note. `macOS: Cmd + Alt + S / windows: Ctrl + Alt + S` \n- [ ] Markdown & Snippet note. \n- [ ] Available for `vim` and `emacs` mode. \n- [ ] Choose your favorite theme on UI, Editor and Code Block! \n--- \n\n- Copy Codeblock on Markdown Preview.\n```javascript\nvar boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)\n```'
|
content: '# Welcome to Boostnote! \n### _Click to edit this note._\n\n---\n\nBoostnote is an *open source* note-taking app. \nRepository is published on [GitHub](https://github.com/BoostIO/Boostnote), and tweeting everyday on [@Boostnoteapp](https://twitter.com/boostnoteapp)!\n\n## Features \n- [x] No internet or registration required. \n- [ ] Quick search and copy the content of note. macOS: <kbd>Cmd</kbd> + <kbd>Alt</kbd> + <kbd>S</kbd> / windows: <kbd>Ctrl</kbd> + <kbd>Alt</kbd> + <kbd>S</kbd> \n- [ ] Markdown & Snippet note. \n- [ ] Available for `vim` and `emacs` mode. \n- [ ] Choose your favorite theme on UI, Editor and Code Block! \n--- \n\n- Copy Codeblock on Markdown Preview.\n```javascript\nvar boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)\n```'
|
||||||
})
|
})
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
@@ -194,13 +194,13 @@ class InitModal extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div styleName='root'
|
<div styleName='root'
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
onKeyDown={this.props.close}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div styleName='header'>
|
<div styleName='header'>
|
||||||
<div styleName='header-title'>Initialize Storage</div>
|
<div styleName='header-title'>Initialize Storage</div>
|
||||||
</div>
|
</div>
|
||||||
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
|
<ModalEscButton handleEscButtonClick={this.props.close} />
|
||||||
<div styleName='body'>
|
<div styleName='body'>
|
||||||
<div styleName='body-welcome'>
|
<div styleName='body-welcome'>
|
||||||
Welcome!
|
Welcome!
|
||||||
|
|||||||
@@ -67,10 +67,17 @@
|
|||||||
text-align right
|
text-align right
|
||||||
:global
|
:global
|
||||||
.alert
|
.alert
|
||||||
font-size 12px
|
display inline-block
|
||||||
line-height 30px
|
position absolute
|
||||||
padding 0 5px
|
top 60px
|
||||||
float right
|
right 15px
|
||||||
|
font-size 14px
|
||||||
|
.success
|
||||||
|
color green
|
||||||
|
.error
|
||||||
|
color red
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.group-control-leftButton
|
.group-control-leftButton
|
||||||
colorDefaultButton()
|
colorDefaultButton()
|
||||||
|
|||||||
49
browser/main/modals/PreferencesModal/Crowdfunding.js
Normal file
49
browser/main/modals/PreferencesModal/Crowdfunding.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './Crowdfunding.styl'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const { shell } = electron
|
||||||
|
|
||||||
|
class Crowdfunding extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLinkClick (e) {
|
||||||
|
shell.openExternal(e.currentTarget.href)
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='header'>Crowdfunding</div>
|
||||||
|
<p>Dear all,</p>
|
||||||
|
<br />
|
||||||
|
<p>Thanks for your using!</p>
|
||||||
|
<p>Boostnote is used in about 200 countries and regions, it is a awesome developer community.</p>
|
||||||
|
<br />
|
||||||
|
<p>To continue supporting this growth, and to satisfy community expectations,</p>
|
||||||
|
<p>we would like to invest more time in this project.</p>
|
||||||
|
<br />
|
||||||
|
<p>If you like this project and see its potential, you can help!</p>
|
||||||
|
<br />
|
||||||
|
<p>Thanks,</p>
|
||||||
|
<p>Boostnote maintainers.</p>
|
||||||
|
<br />
|
||||||
|
<button styleName='cf-link'>
|
||||||
|
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>Support via OpenCollective</a>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Crowdfunding.propTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(Crowdfunding, styles)
|
||||||
30
browser/main/modals/PreferencesModal/Crowdfunding.styl
Normal file
30
browser/main/modals/PreferencesModal/Crowdfunding.styl
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
@import('./Tab')
|
||||||
|
|
||||||
|
.root
|
||||||
|
padding 15px
|
||||||
|
white-space pre
|
||||||
|
line-height 1.4
|
||||||
|
color alpha($ui-text-color, 90%)
|
||||||
|
width 100%
|
||||||
|
font-size 14px
|
||||||
|
p
|
||||||
|
font-size 16px
|
||||||
|
|
||||||
|
.cf-link
|
||||||
|
width 250px
|
||||||
|
height 35px
|
||||||
|
border-radius 2px
|
||||||
|
border none
|
||||||
|
background-color alpha(#1EC38B, 90%)
|
||||||
|
&:hover
|
||||||
|
background-color #1EC38B
|
||||||
|
transition 0.2s
|
||||||
|
a
|
||||||
|
text-decoration none
|
||||||
|
color white
|
||||||
|
font-weight 600
|
||||||
|
font-size 16px
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
p
|
||||||
|
color $ui-dark-text-color
|
||||||
303
browser/main/modals/PreferencesModal/FolderItem.js
Normal file
303
browser/main/modals/PreferencesModal/FolderItem.js
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import styles from './FolderItem.styl'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import store from 'browser/main/store'
|
||||||
|
import { SketchPicker } from 'react-color'
|
||||||
|
import { SortableElement, SortableHandle } from 'react-sortable-hoc'
|
||||||
|
|
||||||
|
class FolderItem extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
status: 'IDLE',
|
||||||
|
folder: {
|
||||||
|
showColumnPicker: false,
|
||||||
|
colorPickerPos: { left: 0, top: 0 },
|
||||||
|
color: props.color,
|
||||||
|
name: props.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEditChange (e) {
|
||||||
|
let { folder } = this.state
|
||||||
|
|
||||||
|
folder.name = this.refs.nameInput.value
|
||||||
|
this.setState({
|
||||||
|
folder
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConfirmButtonClick (e) {
|
||||||
|
this.confirm()
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm () {
|
||||||
|
let { storage, folder } = this.props
|
||||||
|
dataApi
|
||||||
|
.updateFolder(storage.key, folder.key, {
|
||||||
|
color: this.state.folder.color,
|
||||||
|
name: this.state.folder.name
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
store.dispatch({
|
||||||
|
type: 'UPDATE_FOLDER',
|
||||||
|
storage: data.storage
|
||||||
|
})
|
||||||
|
this.setState({
|
||||||
|
status: 'IDLE'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleColorButtonClick (e) {
|
||||||
|
const folder = Object.assign({}, this.state.folder, { showColumnPicker: true, colorPickerPos: { left: 0, top: 0 } })
|
||||||
|
this.setState({ folder }, function () {
|
||||||
|
// After the color picker has been painted, re-calculate its position
|
||||||
|
// by comparing its dimensions to the host dimensions.
|
||||||
|
const { hostBoundingBox } = this.props
|
||||||
|
const colorPickerNode = ReactDOM.findDOMNode(this.refs.colorPicker)
|
||||||
|
const colorPickerBox = colorPickerNode.getBoundingClientRect()
|
||||||
|
const offsetTop = hostBoundingBox.bottom - colorPickerBox.bottom
|
||||||
|
const folder = Object.assign({}, this.state.folder, {
|
||||||
|
colorPickerPos: {
|
||||||
|
left: 25,
|
||||||
|
top: offsetTop < 0 ? offsetTop - 5 : 0 // subtract 5px for aestetics
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.setState({ folder })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleColorChange (color) {
|
||||||
|
const folder = Object.assign({}, this.state.folder, { color: color.hex })
|
||||||
|
this.setState({ folder })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleColorPickerClose (event) {
|
||||||
|
const folder = Object.assign({}, this.state.folder, { showColumnPicker: false })
|
||||||
|
this.setState({ folder })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCancelButtonClick (e) {
|
||||||
|
this.setState({
|
||||||
|
status: 'IDLE'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFolderItemBlur (e) {
|
||||||
|
let el = e.relatedTarget
|
||||||
|
while (el != null) {
|
||||||
|
if (el === this.refs.root) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
el = el.parentNode
|
||||||
|
}
|
||||||
|
this.confirm()
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEdit (e) {
|
||||||
|
const popover = { position: 'absolute', zIndex: 2 }
|
||||||
|
const cover = {
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
const pickerStyle = Object.assign({}, {
|
||||||
|
position: 'absolute'
|
||||||
|
}, this.state.folder.colorPickerPos)
|
||||||
|
return (
|
||||||
|
<div styleName='folderItem'
|
||||||
|
onBlur={(e) => this.handleFolderItemBlur(e)}
|
||||||
|
tabIndex='-1'
|
||||||
|
ref='root'
|
||||||
|
>
|
||||||
|
<div styleName='folderItem-left'>
|
||||||
|
<button styleName='folderItem-left-colorButton' style={{color: this.state.folder.color}}
|
||||||
|
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}
|
||||||
|
>
|
||||||
|
{this.state.folder.showColumnPicker
|
||||||
|
? <div style={popover}>
|
||||||
|
<div style={cover}
|
||||||
|
onClick={() => this.handleColorPickerClose()}
|
||||||
|
/>
|
||||||
|
<div style={pickerStyle}>
|
||||||
|
<SketchPicker
|
||||||
|
ref='colorPicker'
|
||||||
|
color={this.state.folder.color}
|
||||||
|
onChange={(color) => this.handleColorChange(color)}
|
||||||
|
onChangeComplete={(color) => this.handleColorChange(color)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<i className='fa fa-square' />
|
||||||
|
</button>
|
||||||
|
<input styleName='folderItem-left-nameInput'
|
||||||
|
value={this.state.folder.name}
|
||||||
|
ref='nameInput'
|
||||||
|
onChange={(e) => this.handleEditChange(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div styleName='folderItem-right'>
|
||||||
|
<button styleName='folderItem-right-confirmButton'
|
||||||
|
onClick={(e) => this.handleConfirmButtonClick(e)}
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
<button styleName='folderItem-right-button'
|
||||||
|
onClick={(e) => this.handleCancelButtonClick(e)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteConfirmButtonClick (e) {
|
||||||
|
let { storage, folder } = this.props
|
||||||
|
dataApi
|
||||||
|
.deleteFolder(storage.key, folder.key)
|
||||||
|
.then((data) => {
|
||||||
|
store.dispatch({
|
||||||
|
type: 'DELETE_FOLDER',
|
||||||
|
storage: data.storage,
|
||||||
|
folderKey: data.folderKey
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDelete () {
|
||||||
|
return (
|
||||||
|
<div styleName='folderItem'>
|
||||||
|
<div styleName='folderItem-left'>
|
||||||
|
Are you sure to <span styleName='folderItem-left-danger'>delete</span> this folder?
|
||||||
|
</div>
|
||||||
|
<div styleName='folderItem-right'>
|
||||||
|
<button styleName='folderItem-right-dangerButton'
|
||||||
|
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
<button styleName='folderItem-right-button'
|
||||||
|
onClick={(e) => this.handleCancelButtonClick(e)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEditButtonClick (e) {
|
||||||
|
let { folder: propsFolder } = this.props
|
||||||
|
let { folder: stateFolder } = this.state
|
||||||
|
const folder = Object.assign({}, stateFolder, propsFolder)
|
||||||
|
this.setState({
|
||||||
|
status: 'EDIT',
|
||||||
|
folder
|
||||||
|
}, () => {
|
||||||
|
this.refs.nameInput.select()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteButtonClick (e) {
|
||||||
|
this.setState({
|
||||||
|
status: 'DELETE'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderIdle () {
|
||||||
|
let { folder } = this.props
|
||||||
|
return (
|
||||||
|
<div styleName='folderItem'
|
||||||
|
onDoubleClick={(e) => this.handleEditButtonClick(e)}
|
||||||
|
>
|
||||||
|
<div styleName='folderItem-left'
|
||||||
|
style={{borderColor: folder.color}}
|
||||||
|
>
|
||||||
|
<span styleName='folderItem-left-name'>{folder.name}</span>
|
||||||
|
<span styleName='folderItem-left-key'>({folder.key})</span>
|
||||||
|
</div>
|
||||||
|
<div styleName='folderItem-right'>
|
||||||
|
<button styleName='folderItem-right-button'
|
||||||
|
onClick={(e) => this.handleEditButtonClick(e)}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
<button styleName='folderItem-right-button'
|
||||||
|
onClick={(e) => this.handleDeleteButtonClick(e)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
switch (this.state.status) {
|
||||||
|
case 'DELETE':
|
||||||
|
return this.renderDelete()
|
||||||
|
case 'EDIT':
|
||||||
|
return this.renderEdit()
|
||||||
|
case 'IDLE':
|
||||||
|
default:
|
||||||
|
return this.renderIdle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FolderItem.propTypes = {
|
||||||
|
hostBoundingBox: PropTypes.shape({
|
||||||
|
bottom: PropTypes.number,
|
||||||
|
height: PropTypes.number,
|
||||||
|
left: PropTypes.number,
|
||||||
|
right: PropTypes.number,
|
||||||
|
top: PropTypes.number,
|
||||||
|
width: PropTypes.number
|
||||||
|
}),
|
||||||
|
storage: PropTypes.shape({
|
||||||
|
key: PropTypes.string
|
||||||
|
}),
|
||||||
|
folder: PropTypes.shape({
|
||||||
|
key: PropTypes.string,
|
||||||
|
color: PropTypes.string,
|
||||||
|
name: PropTypes.string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class Handle extends React.Component {
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<div styleName='folderItem-drag-handle'>
|
||||||
|
<i className='fa fa-reorder' />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SortableFolderItemComponent extends React.Component {
|
||||||
|
render () {
|
||||||
|
const StyledHandle = CSSModules(Handle, this.props.styles)
|
||||||
|
const DragHandle = SortableHandle(StyledHandle)
|
||||||
|
|
||||||
|
const StyledFolderItem = CSSModules(FolderItem, this.props.styles)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<DragHandle />
|
||||||
|
<StyledFolderItem {...this.props} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(SortableElement(SortableFolderItemComponent), styles)
|
||||||
103
browser/main/modals/PreferencesModal/FolderItem.styl
Normal file
103
browser/main/modals/PreferencesModal/FolderItem.styl
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
.folderItem
|
||||||
|
height 35px
|
||||||
|
box-sizing border-box
|
||||||
|
padding 2.5px 15px
|
||||||
|
&:hover
|
||||||
|
background-color darken(white, 3%)
|
||||||
|
|
||||||
|
.folderItem-drag-handle
|
||||||
|
height 35px
|
||||||
|
border none
|
||||||
|
padding 0 10px
|
||||||
|
line-height 35px
|
||||||
|
float left
|
||||||
|
cursor row-resize
|
||||||
|
|
||||||
|
.folderItem-left
|
||||||
|
height 30px
|
||||||
|
border-left solid 2px transparent
|
||||||
|
padding 0 10px
|
||||||
|
line-height 30px
|
||||||
|
float left
|
||||||
|
.folderItem-left-danger
|
||||||
|
color $danger-color
|
||||||
|
font-weight bold
|
||||||
|
|
||||||
|
.folderItem-left-key
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
font-size 10px
|
||||||
|
margin 0 5px
|
||||||
|
border none
|
||||||
|
|
||||||
|
.folderItem-left-colorButton
|
||||||
|
colorDefaultButton()
|
||||||
|
height 25px
|
||||||
|
width 25px
|
||||||
|
line-height 23px
|
||||||
|
padding 0
|
||||||
|
box-sizing border-box
|
||||||
|
vertical-align middle
|
||||||
|
border $ui-border
|
||||||
|
border-radius 2px
|
||||||
|
margin-right 5px
|
||||||
|
margin-left -15px
|
||||||
|
|
||||||
|
.folderItem-left-nameInput
|
||||||
|
height 25px
|
||||||
|
box-sizing border-box
|
||||||
|
vertical-align middle
|
||||||
|
border $ui-border
|
||||||
|
border-radius 2px
|
||||||
|
padding 0 5px
|
||||||
|
outline none
|
||||||
|
|
||||||
|
.folderItem-right
|
||||||
|
float right
|
||||||
|
|
||||||
|
.folderItem-right-button
|
||||||
|
vertical-align middle
|
||||||
|
height 25px
|
||||||
|
margin-top 2.5px
|
||||||
|
colorDefaultButton()
|
||||||
|
border-radius 2px
|
||||||
|
border $ui-border
|
||||||
|
margin-right 5px
|
||||||
|
padding 0 5px
|
||||||
|
&:last-child
|
||||||
|
margin-right 0
|
||||||
|
|
||||||
|
.folderItem-right-confirmButton
|
||||||
|
@extend .folderItem-right-button
|
||||||
|
border none
|
||||||
|
colorPrimaryButton()
|
||||||
|
|
||||||
|
.folderItem-right-dangerButton
|
||||||
|
@extend .folderItem-right-button
|
||||||
|
border none
|
||||||
|
colorDangerButton()
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.folderItem
|
||||||
|
&:hover
|
||||||
|
background-color lighten($ui-dark-button--hover-backgroundColor, 5%)
|
||||||
|
|
||||||
|
.folderItem-left-danger
|
||||||
|
color $danger-color
|
||||||
|
font-weight bold
|
||||||
|
|
||||||
|
.folderItem-left-key
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
.folderItem-left-colorButton
|
||||||
|
colorDarkDefaultButton()
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
|
||||||
|
.folderItem-right-button
|
||||||
|
colorDarkDefaultButton()
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
|
||||||
|
.folderItem-right-confirmButton
|
||||||
|
colorDarkPrimaryButton()
|
||||||
|
|
||||||
|
.folderItem-right-dangerButton
|
||||||
|
colorDarkDangerButton()
|
||||||
84
browser/main/modals/PreferencesModal/FolderList.js
Normal file
84
browser/main/modals/PreferencesModal/FolderList.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import styles from './FolderList.styl'
|
||||||
|
import store from 'browser/main/store'
|
||||||
|
import FolderItem from './FolderItem'
|
||||||
|
import { SortableContainer, arrayMove } from 'react-sortable-hoc'
|
||||||
|
|
||||||
|
class FolderList extends React.Component {
|
||||||
|
render () {
|
||||||
|
let { storage, hostBoundingBox } = this.props
|
||||||
|
|
||||||
|
let folderList = storage.folders.map((folder, index) => {
|
||||||
|
return <FolderItem key={folder.key}
|
||||||
|
folder={folder}
|
||||||
|
storage={storage}
|
||||||
|
index={index}
|
||||||
|
hostBoundingBox={hostBoundingBox}
|
||||||
|
/>
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div styleName='folderList'>
|
||||||
|
{folderList.length > 0
|
||||||
|
? folderList
|
||||||
|
: <div styleName='folderList-empty'>No Folders</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FolderList.propTypes = {
|
||||||
|
hostBoundingBox: PropTypes.shape({
|
||||||
|
bottom: PropTypes.number,
|
||||||
|
height: PropTypes.number,
|
||||||
|
left: PropTypes.number,
|
||||||
|
right: PropTypes.number,
|
||||||
|
top: PropTypes.number,
|
||||||
|
width: PropTypes.number
|
||||||
|
}),
|
||||||
|
storage: PropTypes.shape({
|
||||||
|
key: PropTypes.string
|
||||||
|
}),
|
||||||
|
folder: PropTypes.shape({
|
||||||
|
key: PropTypes.number,
|
||||||
|
color: PropTypes.string,
|
||||||
|
name: PropTypes.string
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class SortableFolderListComponent extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.onSortEnd = ({oldIndex, newIndex}) => {
|
||||||
|
let { storage } = this.props
|
||||||
|
dataApi
|
||||||
|
.reorderFolder(storage.key, oldIndex, newIndex)
|
||||||
|
.then((data) => {
|
||||||
|
store.dispatch({
|
||||||
|
type: 'REORDER_FOLDER',
|
||||||
|
storage: data.storage
|
||||||
|
})
|
||||||
|
this.setState()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const StyledFolderList = CSSModules(FolderList, this.props.styles)
|
||||||
|
const SortableFolderList = SortableContainer(StyledFolderList)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SortableFolderList
|
||||||
|
helperClass='sortableItemHelper'
|
||||||
|
onSortEnd={this.onSortEnd}
|
||||||
|
useDragHandle
|
||||||
|
{...this.props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(SortableFolderListComponent, styles)
|
||||||
@@ -3,6 +3,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './ConfigTab.styl'
|
import styles from './ConfigTab.styl'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import store from 'browser/main/store'
|
import store from 'browser/main/store'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const ipc = electron.ipcRenderer
|
const ipc = electron.ipcRenderer
|
||||||
@@ -50,6 +51,7 @@ class HotkeyTab extends React.Component {
|
|||||||
type: 'SET_UI',
|
type: 'SET_UI',
|
||||||
config: newConfig
|
config: newConfig
|
||||||
})
|
})
|
||||||
|
this.clearMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHintToggleButtonClick (e) {
|
handleHintToggleButtonClick (e) {
|
||||||
@@ -69,6 +71,14 @@ class HotkeyTab extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearMessage () {
|
||||||
|
_.debounce(() => {
|
||||||
|
this.setState({
|
||||||
|
keymapAlert: null
|
||||||
|
})
|
||||||
|
}, 2000)()
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let keymapAlert = this.state.keymapAlert
|
let keymapAlert = this.state.keymapAlert
|
||||||
let keymapAlertElement = keymapAlert != null
|
let keymapAlertElement = keymapAlert != null
|
||||||
@@ -94,7 +104,7 @@ class HotkeyTab extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
<div styleName='group-section-label'>Toggle Finder(popup)</div>
|
<div styleName='group-section-label'>Toggle Finder (Quick search)</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)}
|
||||||
|
|||||||
@@ -74,9 +74,9 @@ class InfoTab extends React.Component {
|
|||||||
>Boostnote Shop</a> : Products are shipped to all over the world 🌏
|
>Boostnote Shop</a> : Products are shipped to all over the world 🌏
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href='https://salt.bountysource.com/teams/boostnote'
|
<a href='https://opencollective.com/boostnoteio'
|
||||||
onClick={(e) => this.handleLinkClick(e)}
|
onClick={(e) => this.handleLinkClick(e)}
|
||||||
>Donate via Bountysource</a> : Thank you for your support 🎉
|
>Crowdfunding</a> : Thank you for your support 🎉
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href='https://github.com/BoostIO/Boostnote/issues'
|
<a href='https://github.com/BoostIO/Boostnote/issues'
|
||||||
@@ -99,7 +99,7 @@ class InfoTab extends React.Component {
|
|||||||
<div styleName='policy'>Data collection policy</div>
|
<div styleName='policy'>Data collection policy</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>We collect only the number of DAU for Boostnote and **DO NOT collect** any detail information such as your note content.</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>These data are only used for Boostnote improvements.</div>
|
<div>This data is only used for Boostnote improvements.</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'
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ top-bar--height = 50px
|
|||||||
absolute top left right
|
absolute top left right
|
||||||
top top-bar--height
|
top top-bar--height
|
||||||
left 0
|
left 0
|
||||||
width 140px
|
width 170px
|
||||||
margin-left 10px
|
margin-left 10px
|
||||||
margin-top 20px
|
margin-top 20px
|
||||||
background-color $ui-backgroundColor
|
background-color $ui-backgroundColor
|
||||||
@@ -33,7 +33,7 @@ top-bar--height = 50px
|
|||||||
.nav-button
|
.nav-button
|
||||||
font-size 14px
|
font-size 14px
|
||||||
text-align left
|
text-align left
|
||||||
width 120px
|
width 150px
|
||||||
margin 5px 0
|
margin 5px 0
|
||||||
padding 7px 0
|
padding 7px 0
|
||||||
padding-left 10px
|
padding-left 10px
|
||||||
@@ -56,7 +56,7 @@ top-bar--height = 50px
|
|||||||
.content
|
.content
|
||||||
absolute left right bottom
|
absolute left right bottom
|
||||||
top top-bar--height
|
top top-bar--height
|
||||||
left 140px
|
left 170px
|
||||||
margin-top 10px
|
margin-top 10px
|
||||||
overflow-y auto
|
overflow-y auto
|
||||||
|
|
||||||
|
|||||||
@@ -1,265 +1,13 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import React, { PropTypes } from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './StorageItem.styl'
|
import styles from './StorageItem.styl'
|
||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import store from 'browser/main/store'
|
import store from 'browser/main/store'
|
||||||
|
import FolderList from './FolderList'
|
||||||
|
|
||||||
const { shell, remote } = require('electron')
|
const { shell, remote } = require('electron')
|
||||||
const { dialog } = remote
|
const { dialog } = remote
|
||||||
import { SketchPicker } from 'react-color'
|
|
||||||
|
|
||||||
class UnstyledFolderItem extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
status: 'IDLE',
|
|
||||||
folder: {
|
|
||||||
showColumnPicker: false,
|
|
||||||
colorPickerPos: { left: 0, top: 0 },
|
|
||||||
color: props.color,
|
|
||||||
name: props.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEditChange (e) {
|
|
||||||
let { folder } = this.state
|
|
||||||
|
|
||||||
folder.name = this.refs.nameInput.value
|
|
||||||
this.setState({
|
|
||||||
folder
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleConfirmButtonClick (e) {
|
|
||||||
this.confirm()
|
|
||||||
}
|
|
||||||
|
|
||||||
confirm () {
|
|
||||||
let { storage, folder } = this.props
|
|
||||||
dataApi
|
|
||||||
.updateFolder(storage.key, folder.key, {
|
|
||||||
color: this.state.folder.color,
|
|
||||||
name: this.state.folder.name
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
store.dispatch({
|
|
||||||
type: 'UPDATE_FOLDER',
|
|
||||||
storage: data.storage
|
|
||||||
})
|
|
||||||
this.setState({
|
|
||||||
status: 'IDLE'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleColorButtonClick (e) {
|
|
||||||
const folder = Object.assign({}, this.state.folder, { showColumnPicker: true, colorPickerPos: { left: 0, top: 0 } })
|
|
||||||
this.setState({ folder }, function () {
|
|
||||||
// After the color picker has been painted, re-calculate its position
|
|
||||||
// by comparing its dimensions to the host dimensions.
|
|
||||||
const { hostBoundingBox } = this.props
|
|
||||||
const colorPickerNode = ReactDOM.findDOMNode(this.refs.colorPicker)
|
|
||||||
const colorPickerBox = colorPickerNode.getBoundingClientRect()
|
|
||||||
const offsetTop = hostBoundingBox.bottom - colorPickerBox.bottom
|
|
||||||
const folder = Object.assign({}, this.state.folder, {
|
|
||||||
colorPickerPos: {
|
|
||||||
left: 25,
|
|
||||||
top: offsetTop < 0 ? offsetTop - 5 : 0 // subtract 5px for aestetics
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.setState({ folder })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleColorChange (color) {
|
|
||||||
const folder = Object.assign({}, this.state.folder, { color: color.hex })
|
|
||||||
this.setState({ folder })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleColorPickerClose (event) {
|
|
||||||
const folder = Object.assign({}, this.state.folder, { showColumnPicker: false })
|
|
||||||
this.setState({ folder })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCancelButtonClick (e) {
|
|
||||||
this.setState({
|
|
||||||
status: 'IDLE'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFolderItemBlur (e) {
|
|
||||||
let el = e.relatedTarget
|
|
||||||
while (el != null) {
|
|
||||||
if (el === this.refs.root) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
el = el.parentNode
|
|
||||||
}
|
|
||||||
this.confirm()
|
|
||||||
}
|
|
||||||
|
|
||||||
renderEdit (e) {
|
|
||||||
const popover = { position: 'absolute', zIndex: 2 }
|
|
||||||
const cover = {
|
|
||||||
position: 'fixed',
|
|
||||||
top: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0
|
|
||||||
}
|
|
||||||
const pickerStyle = Object.assign({}, {
|
|
||||||
position: 'absolute'
|
|
||||||
}, this.state.folder.colorPickerPos)
|
|
||||||
return (
|
|
||||||
<div styleName='folderList-item'
|
|
||||||
onBlur={(e) => this.handleFolderItemBlur(e)}
|
|
||||||
tabIndex='-1'
|
|
||||||
ref='root'
|
|
||||||
>
|
|
||||||
<div styleName='folderList-item-left'>
|
|
||||||
<button styleName='folderList-item-left-colorButton' style={{color: this.state.folder.color}}
|
|
||||||
onClick={(e) => !this.state.folder.showColumnPicker && this.handleColorButtonClick(e)}
|
|
||||||
>
|
|
||||||
{this.state.folder.showColumnPicker
|
|
||||||
? <div style={popover}>
|
|
||||||
<div style={cover}
|
|
||||||
onClick={() => this.handleColorPickerClose()}
|
|
||||||
/>
|
|
||||||
<div style={pickerStyle}>
|
|
||||||
<SketchPicker
|
|
||||||
ref='colorPicker'
|
|
||||||
color={this.state.folder.color}
|
|
||||||
onChange={(color) => this.handleColorChange(color)}
|
|
||||||
onChangeComplete={(color) => this.handleColorChange(color)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
<i className='fa fa-square' />
|
|
||||||
</button>
|
|
||||||
<input styleName='folderList-item-left-nameInput'
|
|
||||||
value={this.state.folder.name}
|
|
||||||
ref='nameInput'
|
|
||||||
onChange={(e) => this.handleEditChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div styleName='folderList-item-right'>
|
|
||||||
<button styleName='folderList-item-right-confirmButton'
|
|
||||||
onClick={(e) => this.handleConfirmButtonClick(e)}
|
|
||||||
>
|
|
||||||
Confirm
|
|
||||||
</button>
|
|
||||||
<button styleName='folderList-item-right-button'
|
|
||||||
onClick={(e) => this.handleCancelButtonClick(e)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteConfirmButtonClick (e) {
|
|
||||||
let { storage, folder } = this.props
|
|
||||||
dataApi
|
|
||||||
.deleteFolder(storage.key, folder.key)
|
|
||||||
.then((data) => {
|
|
||||||
store.dispatch({
|
|
||||||
type: 'DELETE_FOLDER',
|
|
||||||
storage: data.storage,
|
|
||||||
folderKey: data.folderKey
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDelete () {
|
|
||||||
return (
|
|
||||||
<div styleName='folderList-item'>
|
|
||||||
<div styleName='folderList-item-left'>
|
|
||||||
Are you sure to <span styleName='folderList-item-left-danger'>delete</span> this folder?
|
|
||||||
</div>
|
|
||||||
<div styleName='folderList-item-right'>
|
|
||||||
<button styleName='folderList-item-right-dangerButton'
|
|
||||||
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
|
|
||||||
>
|
|
||||||
Confirm
|
|
||||||
</button>
|
|
||||||
<button styleName='folderList-item-right-button'
|
|
||||||
onClick={(e) => this.handleCancelButtonClick(e)}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEditButtonClick (e) {
|
|
||||||
let { folder: propsFolder } = this.props
|
|
||||||
let { folder: stateFolder } = this.state
|
|
||||||
const folder = Object.assign({}, stateFolder, propsFolder)
|
|
||||||
this.setState({
|
|
||||||
status: 'EDIT',
|
|
||||||
folder
|
|
||||||
}, () => {
|
|
||||||
this.refs.nameInput.select()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteButtonClick (e) {
|
|
||||||
this.setState({
|
|
||||||
status: 'DELETE'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
renderIdle () {
|
|
||||||
let { folder } = this.props
|
|
||||||
return (
|
|
||||||
<div styleName='folderList-item'
|
|
||||||
onDoubleClick={(e) => this.handleEditButtonClick(e)}
|
|
||||||
>
|
|
||||||
<div styleName='folderList-item-left'
|
|
||||||
style={{borderColor: folder.color}}
|
|
||||||
>
|
|
||||||
<span styleName='folderList-item-left-name'>{folder.name}</span>
|
|
||||||
<span styleName='folderList-item-left-key'>({folder.key})</span>
|
|
||||||
</div>
|
|
||||||
<div styleName='folderList-item-right'>
|
|
||||||
<button styleName='folderList-item-right-button'
|
|
||||||
onClick={(e) => this.handleEditButtonClick(e)}
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
<button styleName='folderList-item-right-button'
|
|
||||||
onClick={(e) => this.handleDeleteButtonClick(e)}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
switch (this.state.status) {
|
|
||||||
case 'DELETE':
|
|
||||||
return this.renderDelete()
|
|
||||||
case 'EDIT':
|
|
||||||
return this.renderEdit()
|
|
||||||
case 'IDLE':
|
|
||||||
default:
|
|
||||||
return this.renderIdle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const FolderItem = CSSModules(UnstyledFolderItem, styles)
|
|
||||||
|
|
||||||
class StorageItem extends React.Component {
|
class StorageItem extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -349,13 +97,7 @@ class StorageItem extends React.Component {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { storage, hostBoundingBox } = this.props
|
let { storage, hostBoundingBox } = this.props
|
||||||
let folderList = storage.folders.map((folder) => {
|
|
||||||
return <FolderItem key={folder.key}
|
|
||||||
folder={folder}
|
|
||||||
storage={storage}
|
|
||||||
hostBoundingBox={hostBoundingBox}
|
|
||||||
/>
|
|
||||||
})
|
|
||||||
return (
|
return (
|
||||||
<div styleName='root' key={storage.key}>
|
<div styleName='root' key={storage.key}>
|
||||||
<div styleName='header'>
|
<div styleName='header'>
|
||||||
@@ -404,12 +146,9 @@ class StorageItem extends React.Component {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='folderList'>
|
<FolderList storage={storage}
|
||||||
{folderList.length > 0
|
hostBoundingBox={hostBoundingBox}
|
||||||
? folderList
|
/>
|
||||||
: <div styleName='folderList-empty'>No Folders</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -426,11 +165,6 @@ StorageItem.propTypes = {
|
|||||||
}),
|
}),
|
||||||
storage: PropTypes.shape({
|
storage: PropTypes.shape({
|
||||||
key: PropTypes.string
|
key: PropTypes.string
|
||||||
}),
|
|
||||||
folder: PropTypes.shape({
|
|
||||||
key: PropTypes.string,
|
|
||||||
color: PropTypes.string,
|
|
||||||
name: PropTypes.string
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,75 +63,6 @@
|
|||||||
z-index 10
|
z-index 10
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
|
|
||||||
.folderList-item
|
|
||||||
height 35px
|
|
||||||
box-sizing border-box
|
|
||||||
padding 2.5px 15px
|
|
||||||
&:hover
|
|
||||||
background-color darken(white, 3%)
|
|
||||||
.folderList-item-left
|
|
||||||
height 30px
|
|
||||||
border-left solid 2px transparent
|
|
||||||
padding 0 10px
|
|
||||||
line-height 30px
|
|
||||||
float left
|
|
||||||
.folderList-item-left-danger
|
|
||||||
color $danger-color
|
|
||||||
font-weight bold
|
|
||||||
|
|
||||||
.folderList-item-left-key
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
font-size 10px
|
|
||||||
margin 0 5px
|
|
||||||
border none
|
|
||||||
|
|
||||||
.folderList-item-left-colorButton
|
|
||||||
colorDefaultButton()
|
|
||||||
height 25px
|
|
||||||
width 25px
|
|
||||||
line-height 23px
|
|
||||||
padding 0
|
|
||||||
box-sizing border-box
|
|
||||||
vertical-align middle
|
|
||||||
border $ui-border
|
|
||||||
border-radius 2px
|
|
||||||
margin-right 5px
|
|
||||||
margin-left -15px
|
|
||||||
|
|
||||||
.folderList-item-left-nameInput
|
|
||||||
height 25px
|
|
||||||
box-sizing border-box
|
|
||||||
vertical-align middle
|
|
||||||
border $ui-border
|
|
||||||
border-radius 2px
|
|
||||||
padding 0 5px
|
|
||||||
outline none
|
|
||||||
|
|
||||||
.folderList-item-right
|
|
||||||
float right
|
|
||||||
|
|
||||||
.folderList-item-right-button
|
|
||||||
vertical-align middle
|
|
||||||
height 25px
|
|
||||||
margin-top 2.5px
|
|
||||||
colorDefaultButton()
|
|
||||||
border-radius 2px
|
|
||||||
border $ui-border
|
|
||||||
margin-right 5px
|
|
||||||
padding 0 5px
|
|
||||||
&:last-child
|
|
||||||
margin-right 0
|
|
||||||
|
|
||||||
.folderList-item-right-confirmButton
|
|
||||||
@extend .folderList-item-right-button
|
|
||||||
border none
|
|
||||||
colorPrimaryButton()
|
|
||||||
|
|
||||||
.folderList-item-right-dangerButton
|
|
||||||
@extend .folderList-item-right-button
|
|
||||||
border none
|
|
||||||
colorDangerButton()
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.header
|
.header
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
@@ -153,28 +84,3 @@ body[data-theme="dark"]
|
|||||||
top 25px
|
top 25px
|
||||||
z-index 10
|
z-index 10
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
|
|
||||||
.folderList-item
|
|
||||||
&:hover
|
|
||||||
background-color lighten($ui-dark-button--hover-backgroundColor, 5%)
|
|
||||||
|
|
||||||
.folderList-item-left-danger
|
|
||||||
color $danger-color
|
|
||||||
font-weight bold
|
|
||||||
|
|
||||||
.folderList-item-left-key
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
|
|
||||||
.folderList-item-left-colorButton
|
|
||||||
colorDarkDefaultButton()
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
|
|
||||||
.folderList-item-right-button
|
|
||||||
colorDarkDefaultButton()
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
|
|
||||||
.folderList-item-right-confirmButton
|
|
||||||
colorDarkPrimaryButton()
|
|
||||||
|
|
||||||
.folderList-item-right-dangerButton
|
|
||||||
colorDarkDangerButton()
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class UiTab extends React.Component {
|
|||||||
const newConfig = {
|
const newConfig = {
|
||||||
ui: {
|
ui: {
|
||||||
theme: this.refs.uiTheme.value,
|
theme: this.refs.uiTheme.value,
|
||||||
|
showCopyNotification: this.refs.showCopyNotification.checked,
|
||||||
disableDirectWrite: this.refs.uiD2w != null
|
disableDirectWrite: this.refs.uiD2w != null
|
||||||
? this.refs.uiD2w.checked
|
? this.refs.uiD2w.checked
|
||||||
: false
|
: false
|
||||||
@@ -60,7 +61,7 @@ class UiTab extends React.Component {
|
|||||||
const newCodemirrorTheme = this.refs.editorTheme.value
|
const newCodemirrorTheme = this.refs.editorTheme.value
|
||||||
|
|
||||||
if (newCodemirrorTheme !== codemirrorTheme) {
|
if (newCodemirrorTheme !== codemirrorTheme) {
|
||||||
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme}.css`)
|
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme })
|
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme })
|
||||||
@@ -102,6 +103,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.ui.showCopyNotification}
|
||||||
|
ref='showCopyNotification'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
Show "Saved to Clipboard" notification when copying
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
{
|
{
|
||||||
global.process.platform === 'win32'
|
global.process.platform === 'win32'
|
||||||
? <div styleName='group-checkBoxSection'>
|
? <div styleName='group-checkBoxSection'>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { connect } from 'react-redux'
|
|||||||
import HotkeyTab from './HotkeyTab'
|
import HotkeyTab from './HotkeyTab'
|
||||||
import UiTab from './UiTab'
|
import UiTab from './UiTab'
|
||||||
import InfoTab from './InfoTab'
|
import InfoTab from './InfoTab'
|
||||||
|
import Crowdfunding from './Crowdfunding'
|
||||||
import StoragesTab from './StoragesTab'
|
import StoragesTab from './StoragesTab'
|
||||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
@@ -64,6 +65,10 @@ class Preferences extends React.Component {
|
|||||||
config={config}
|
config={config}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
case 'CROWDFUNDING':
|
||||||
|
return (
|
||||||
|
<Crowdfunding />
|
||||||
|
)
|
||||||
case 'STORAGES':
|
case 'STORAGES':
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
@@ -94,7 +99,8 @@ class Preferences extends React.Component {
|
|||||||
{target: 'STORAGES', label: 'Storages'},
|
{target: 'STORAGES', label: 'Storages'},
|
||||||
{target: 'HOTKEY', label: 'Hotkey'},
|
{target: 'HOTKEY', label: 'Hotkey'},
|
||||||
{target: 'UI', label: 'UI'},
|
{target: 'UI', label: 'UI'},
|
||||||
{target: 'INFO', label: 'Info'}
|
{target: 'INFO', label: 'Info'},
|
||||||
|
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
|
||||||
]
|
]
|
||||||
|
|
||||||
let navButtons = tabs.map((tab) => {
|
let navButtons = tabs.map((tab) => {
|
||||||
|
|||||||
@@ -346,6 +346,13 @@ function data (state = defaultDataMap(), action) {
|
|||||||
state.storageMap.set(action.storage.key, action.storage)
|
state.storageMap.set(action.storage.key, action.storage)
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
|
case 'REORDER_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)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/b
|
|||||||
* npm: 4.x
|
* npm: 4.x
|
||||||
* node: 7.x
|
* node: 7.x
|
||||||
|
|
||||||
You should use `npm v4.x` because `$ grand pre-build` fails on `v5.x`.
|
You should use `npm v4.x` because `$ grunt pre-build` fails on `v5.x`.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# How to debug Boostnote (Electron app)
|
# How to debug Boostnote (Electron app)
|
||||||
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), and [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md)
|
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), and [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md)
|
||||||
|
|
||||||
Boostnote is an Electron app so it's based on Chromium; developers can use `Developer Tools` just like Google Chrome.
|
Boostnote is an Electron app so it's based on Chromium; developers can use `Developer Tools` just like Google Chrome.
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,49 @@
|
|||||||
# Build
|
# Build
|
||||||
|
|
||||||
|
## 환경
|
||||||
|
* npm: 4.x
|
||||||
|
* node: 7.x
|
||||||
|
|
||||||
|
`$ grunt pre-build`를 `npm v5.x`에서 실행할 수 없기 때문에, 반드시 `npm v4.x`를 사용하셔야 합니다.
|
||||||
|
|
||||||
## 개발
|
## 개발
|
||||||
|
|
||||||
Webpack HRM을 개발을 위해 사용합니다.
|
개발에 있어서 Webpack HRM을 사용합니다.
|
||||||
다음 명령을 통해 저희가 해둔 설정을 사용 할 수 있습니다.
|
다음과 같은 명령을 프로젝트 디렉토리에서 실행하면, 기본 설정을 사용 할 수 있습니다.
|
||||||
|
|
||||||
|
먼저, yarn을 이용해서 필요한 패키지들을 설치합니다.
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn run webpack
|
$ yarn
|
||||||
```
|
```
|
||||||
|
|
||||||
몇 초 후, 다음 메세지를 보게 될겁니다.
|
그 다음, 아래의 명령으로 빌드를 끝내고 자동적으로 어플리케이션을 실행합니다.
|
||||||
|
|
||||||
```
|
```
|
||||||
webpack: bundle is now VALID.
|
$ yarn run dev-start
|
||||||
```
|
```
|
||||||
|
|
||||||
그럼 앱을 실행합시다.
|
이 명령은 `yarn run webpack` 과 `yarn run hot`을 동시에 실행합니다. 이는 두개의 터미널에서 각각의 명령을 동시에 실행하는 것과 같습니다.
|
||||||
|
|
||||||
```
|
`Webpack`은 코드의 변화를 자동으로 탐지하여 적용시키는 역할을 합니다.
|
||||||
yarn run hot
|
|
||||||
```
|
|
||||||
|
|
||||||
> 원래 앱은 `yarn start`로 실행가능합니다. 하지만 이 경우, 컴파일된 스크립트를 사용할 것입니다.
|
만약, `Failed to load resource: net::ERR_CONNECTION_REFUSED`과 같은 에러가 나타난다면 Boostnote를 리로드해주세요.
|
||||||
|
|
||||||
이로써 웹팩이 자동적으로 코드변경을 확인하고 적용해줄 것입니다.
|

|
||||||
|
|
||||||
> ### 주의
|
> ### 주의
|
||||||
> 가끔 직접 리프레쉬를 해주어야 하는 경우가 있습니다.
|
> 가끔 직접 리프레쉬를 해주어야 하는 경우가 있습니다.
|
||||||
> 1. 콤포넌트의 컨스트럭터 함수를 수정할 경우샐
|
> 1. 콤포넌트의 컨스트럭터 함수를 수정할 경우
|
||||||
> 2. 새로운 CSS코드를 추가할 경우(1.과 같은 이유: CSS클래스는 콤포넌트마다 다시 만들어 지는데, 이 작업은 컨스트럭터에서 일어납니다.)
|
> 2. 새로운 CSS코드를 추가할 경우(1.과 같은 이유: CSS클래스는 콤포넌트마다 다시 만들어 지는데, 이 작업은 컨스트럭터에서 일어납니다.)
|
||||||
|
|
||||||
## 배포
|
## 배포
|
||||||
|
|
||||||
그런트를 사용합니다.
|
Boostnote에서는 배포 자동화를 위하여 그런트를 사용합니다.
|
||||||
실제 디플로이는 `grunt`로 실행할 수 있습니다. 하지만, 여기엔 Codesign과 Authenticode의 과정이 포함되어있기 때문에 사용 하셔선 안됩니다.
|
실제 배포는 `grunt`로 실행할 수 있습니다. 하지만, 여기엔 Codesign과 Authenticode의 과정이 포함되어있기 때문에 사용 하셔선 안됩니다.
|
||||||
|
|
||||||
그래서, 실행파일만을 만드는 스크립트를 준비해 뒀습니다.
|
그래서, 실행파일만을 만드는 스크립트를 준비해 뒀습니다.
|
||||||
|
|
||||||
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
|
이 빌드는 npm v5.3.0에서는 작동하지 않습니다. 그러므로, 성공적으로 빌드하기 위해서는 v5.2.0을 사용해야 합니다.
|
||||||
|
|
||||||
```
|
```
|
||||||
grunt pre-build
|
grunt pre-build
|
||||||
|
|||||||
21
docs/ko/debug.md
Normal file
21
docs/ko/debug.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Boostnote의 디버그 방법(Electron app)
|
||||||
|
|
||||||
|
Boostnote는 Electron 애플리케이션이므로 Chromium위에서 작동합니다. 그렇기 때문에 개발자분들은 Google Chrome 브라우저에서 처럼 `Developer Tools`를 사용하실 수 있습니다.
|
||||||
|
|
||||||
|
다음과 같이 `Developer Tools`를 실행할 수 있습니다:
|
||||||
|

|
||||||
|
|
||||||
|
`Developer Tools`는 다음과 같이 나타납니다:
|
||||||
|

|
||||||
|
|
||||||
|
에러가 발생할 때에는, 에러메시지가 `console`위에 표시 됩니다.
|
||||||
|
|
||||||
|
## 디버깅
|
||||||
|
예를들면 `debugger`를 사용하여 코드 안에서 다음과 같이 일시 정지지점을 설정할 수 있습니다:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
이는 단순한 하나의 예시에 불과합니다. 자기자신에게 가장 잘 맞는 디버그 방법을 찾는 것도 좋을 것 입니다.
|
||||||
|
|
||||||
|
## 참고
|
||||||
|
* [디버그에 관한 Google Chrome의 공식 문서](https://developer.chrome.com/devtools)
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* npm: 4.x
|
* npm: 4.x
|
||||||
* node: 7.x
|
* node: 7.x
|
||||||
|
|
||||||
Вы должны использовать `npm v4.x`, так как `$ grand pre-build` не работает в `v5.x`.
|
Вы должны использовать `npm v4.x`, так как `$ grunt pre-build` не работает в `v5.x`.
|
||||||
|
|
||||||
## Разработка
|
## Разработка
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* npm: 4.x
|
* npm: 4.x
|
||||||
* node: 7.x
|
* node: 7.x
|
||||||
|
|
||||||
因为`$ grand pre-build`的问题,您只能使用`npm v4.x`而不能使用`npm v5.x`。
|
因为`$ grunt pre-build`的问题,您只能使用`npm v4.x`而不能使用`npm v5.x`。
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"productName": "Boostnote",
|
"productName": "Boostnote",
|
||||||
"version": "0.8.15",
|
"version": "0.8.16",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"description": "Boostnote",
|
"description": "Boostnote",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@@ -61,14 +61,17 @@
|
|||||||
"js-sequence-diagrams": "^1000000.0.6",
|
"js-sequence-diagrams": "^1000000.0.6",
|
||||||
"katex": "^0.7.1",
|
"katex": "^0.7.1",
|
||||||
"lodash": "^4.11.1",
|
"lodash": "^4.11.1",
|
||||||
|
"lodash-move": "^1.1.1",
|
||||||
"markdown-it": "^6.0.1",
|
"markdown-it": "^6.0.1",
|
||||||
"markdown-it-checkbox": "^1.1.0",
|
"markdown-it-checkbox": "^1.1.0",
|
||||||
"markdown-it-emoji": "^1.1.1",
|
"markdown-it-emoji": "^1.1.1",
|
||||||
"markdown-it-footnote": "^3.0.0",
|
"markdown-it-footnote": "^3.0.0",
|
||||||
"markdown-it-imsize": "^2.0.1",
|
"markdown-it-imsize": "^2.0.1",
|
||||||
|
"markdown-it-kbd": "^1.1.0",
|
||||||
"markdown-it-multimd-table": "^2.0.1",
|
"markdown-it-multimd-table": "^2.0.1",
|
||||||
"markdown-it-named-headers": "^0.0.4",
|
"markdown-it-named-headers": "^0.0.4",
|
||||||
"md5": "^2.0.0",
|
"md5": "^2.0.0",
|
||||||
|
"mdurl": "^1.0.1",
|
||||||
"mixpanel": "^0.4.1",
|
"mixpanel": "^0.4.1",
|
||||||
"moment": "^2.10.3",
|
"moment": "^2.10.3",
|
||||||
"node-ipc": "^8.1.0",
|
"node-ipc": "^8.1.0",
|
||||||
@@ -77,6 +80,7 @@
|
|||||||
"react-codemirror": "^0.3.0",
|
"react-codemirror": "^0.3.0",
|
||||||
"react-dom": "^15.0.2",
|
"react-dom": "^15.0.2",
|
||||||
"react-redux": "^4.4.5",
|
"react-redux": "^4.4.5",
|
||||||
|
"react-sortable-hoc": "^0.6.7",
|
||||||
"redux": "^3.5.2",
|
"redux": "^3.5.2",
|
||||||
"sander": "^0.5.1",
|
"sander": "^0.5.1",
|
||||||
"striptags": "^2.2.1",
|
"striptags": "^2.2.1",
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ New:zap:
|
|||||||
[Great contributors](https://github.com/BoostIO/Boostnote/graphs/contributors) :tada:
|
[Great contributors](https://github.com/BoostIO/Boostnote/graphs/contributors) :tada:
|
||||||
|
|
||||||
## Slack Group
|
## Slack Group
|
||||||
Let's talk about Boostnote's great features, new feature requests and things like Japanese gourmet. 🍣 <br>
|
Let's talk about Boostnote! <br>
|
||||||
[Join us](https://join.slack.com/t/boostnote-group/shared_invite/enQtMjQ0MjYxNTMxNjY5LTI0NWFkZDEyYWYxYWE0YTMyYjI4NzA2YjBlNmFmOGVhNGFjY2I1MTNmZWJjYmMxYTdkM2VjNWNjYTFiYWQ3ZmQ)
|
[Join us](https://join.slack.com/t/boostnote-group/shared_invite/enQtMjU5NTUwNjcwMjkyLWI3N2I1YWIzNWRlZDZmZjVlYzRiNDc1YTcxZWNmY2UyZjc3MTQwMDUxMzAxZjg0NjNmZmIwNDFhMDkwZDlmZDc)
|
||||||
|
|
||||||
## More Information
|
## More Information
|
||||||
* [Website](https://boostnote.io)
|
* [Website](https://boostnote.io)
|
||||||
* [10hz](https://boostnote.io/team/) : Boostnote for the creative hacker teams. Share your markdown notes and snippets instantly with your team. **We will release it at October!** 🏃💨
|
* [10hz](https://boostnote.io/team/) : Boostnote for the creative hacker teams. Share your markdown notes and snippets instantly with your team. **We will release it at October!** 🏃💨
|
||||||
* [Support us via Bountysource](https://salt.bountysource.com/teams/boostnote) : Thank you for your support 🎉
|
* [Support us via OpenCollective](https://opencollective.com/boostnoteio) : Thank you for your support 🎉
|
||||||
* [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) 2017 Maisin&Co.
|
||||||
|
|
||||||
|
|||||||
9
snap/gui/boostnote.desktop
Executable file
9
snap/gui/boostnote.desktop
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Type=Application
|
||||||
|
Name=Boostnote
|
||||||
|
Comment=A note-taking app for programmers
|
||||||
|
Exec=$SNAP/etc/boostnote/Boostnote
|
||||||
|
Icon=resources/app.png
|
||||||
|
MimeType=image/x-foo;
|
||||||
|
NotShowIn=KDE;
|
||||||
48
snap/snapcraft.yaml
Normal file
48
snap/snapcraft.yaml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: boostnote
|
||||||
|
version: '0.1'
|
||||||
|
summary: A note-taking app for programmers
|
||||||
|
description: |
|
||||||
|
Boostnote is an open source note-taking app made for programmers just like you. https://boostnote.io
|
||||||
|
https://github.com/BoostIO/Boostnote
|
||||||
|
|
||||||
|
grade: stable
|
||||||
|
confinement: strict
|
||||||
|
|
||||||
|
apps:
|
||||||
|
asmstnote:
|
||||||
|
command: desktop-launch $SNAP/etc/boostnote/Boostnote
|
||||||
|
plugs:
|
||||||
|
- browser-support
|
||||||
|
- network
|
||||||
|
- unity7
|
||||||
|
- gsettings
|
||||||
|
|
||||||
|
parts:
|
||||||
|
src:
|
||||||
|
plugin: nodejs
|
||||||
|
source: .
|
||||||
|
|
||||||
|
deps:
|
||||||
|
plugin: nil
|
||||||
|
stage-packages:
|
||||||
|
- libgconf-2-4
|
||||||
|
- libnss3
|
||||||
|
- libxss1
|
||||||
|
- fontconfig-config
|
||||||
|
|
||||||
|
desktop-integration:
|
||||||
|
plugin: nil
|
||||||
|
stage-packages:
|
||||||
|
- libappindicator1
|
||||||
|
- libdbusmenu-glib4
|
||||||
|
- libnotify4
|
||||||
|
- libunity9
|
||||||
|
|
||||||
|
launcher:
|
||||||
|
plugin: dump
|
||||||
|
source: .
|
||||||
|
stage:
|
||||||
|
- etc/boostnote
|
||||||
|
organize:
|
||||||
|
dist/Boostnote-linux-x64: etc/boostnote
|
||||||
|
after: [desktop-glib-only]
|
||||||
48
tests/dataApi/reorderFolder-test.js
Normal file
48
tests/dataApi/reorderFolder-test.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const reorderFolder = require('browser/main/lib/dataApi/reorderFolder')
|
||||||
|
|
||||||
|
global.document = require('jsdom').jsdom('<body></body>')
|
||||||
|
global.window = document.defaultView
|
||||||
|
global.navigator = window.navigator
|
||||||
|
|
||||||
|
const Storage = require('dom-storage')
|
||||||
|
const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true })
|
||||||
|
const path = require('path')
|
||||||
|
const _ = require('lodash')
|
||||||
|
const TestDummy = require('../fixtures/TestDummy')
|
||||||
|
const sander = require('sander')
|
||||||
|
const os = require('os')
|
||||||
|
const CSON = require('@rokt33r/season')
|
||||||
|
|
||||||
|
const storagePath = path.join(os.tmpdir(), 'test/reorder-folder')
|
||||||
|
|
||||||
|
test.beforeEach((t) => {
|
||||||
|
t.context.storage = TestDummy.dummyStorage(storagePath)
|
||||||
|
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
|
||||||
|
})
|
||||||
|
|
||||||
|
test.serial('Reorder a folder', (t) => {
|
||||||
|
const storageKey = t.context.storage.cache.key
|
||||||
|
const firstFolderKey = t.context.storage.json.folders[0].key
|
||||||
|
const secondFolderKey = t.context.storage.json.folders[1].key
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(function doTest () {
|
||||||
|
return reorderFolder(storageKey, 0, 1)
|
||||||
|
})
|
||||||
|
.then(function assert (data) {
|
||||||
|
t.true(_.nth(data.storage.folders, 0).key === secondFolderKey)
|
||||||
|
t.true(_.nth(data.storage.folders, 1).key === firstFolderKey)
|
||||||
|
|
||||||
|
let jsonData = CSON.readFileSync(path.join(data.storage.path, 'boostnote.json'))
|
||||||
|
console.log(path.join(data.storage.path, 'boostnote.json'))
|
||||||
|
|
||||||
|
t.true(_.nth(jsonData.folders, 0).key === secondFolderKey)
|
||||||
|
t.true(_.nth(jsonData.folders, 1).key === firstFolderKey)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.after(function after () {
|
||||||
|
localStorage.clear()
|
||||||
|
sander.rimrafSync(storagePath)
|
||||||
|
})
|
||||||
@@ -6,26 +6,30 @@ import _ from 'lodash'
|
|||||||
const pickContents = (notes) => notes.map((note) => { return note.content })
|
const pickContents = (notes) => notes.map((note) => { return note.content })
|
||||||
|
|
||||||
let notes = []
|
let notes = []
|
||||||
let note1, note2
|
let note1, note2, note3
|
||||||
|
|
||||||
test.before(t => {
|
test.before(t => {
|
||||||
const data1 = { type: 'MARKDOWN_NOTE', content: 'content1', tags: ['tag1'] }
|
const data1 = { type: 'MARKDOWN_NOTE', content: 'content1', tags: ['tag1'] }
|
||||||
const data2 = { type: 'MARKDOWN_NOTE', content: 'content1\ncontent2', tags: ['tag1', 'tag2'] }
|
const data2 = { type: 'MARKDOWN_NOTE', content: 'content1\ncontent2', tags: ['tag1', 'tag2'] }
|
||||||
|
const data3 = { type: 'MARKDOWN_NOTE', content: '#content4', tags: ['tag1'] }
|
||||||
|
|
||||||
note1 = dummyNote(data1)
|
note1 = dummyNote(data1)
|
||||||
note2 = dummyNote(data2)
|
note2 = dummyNote(data2)
|
||||||
|
note3 = dummyNote(data3)
|
||||||
|
|
||||||
notes = [note1, note2]
|
notes = [note1, note2, note3]
|
||||||
})
|
})
|
||||||
|
|
||||||
test('it can find notes by tags or words', t => {
|
test('it can find notes by tags or words', t => {
|
||||||
// [input, expected content (Array)]
|
// [input, expected content (Array)]
|
||||||
const testCases = [
|
const testCases = [
|
||||||
['#tag1', [note1.content, note2.content]],
|
['#tag1', [note1.content, note2.content, note3.content]],
|
||||||
['#tag1 #tag2', [note2.content]],
|
['#tag1 #tag2', [note2.content]],
|
||||||
['#tag1 #tag2 #tag3', []],
|
['#tag1 #tag2 #tag3', []],
|
||||||
['content1', [note1.content, note2.content]],
|
['content1', [note1.content, note2.content]],
|
||||||
['content1 content2', [note2.content]],
|
['content1 content2', [note2.content]],
|
||||||
['content1 content2 content3', []]
|
['content1 content2 content3', []],
|
||||||
|
['#content4', [note3.content]]
|
||||||
]
|
]
|
||||||
|
|
||||||
testCases.forEach((testCase) => {
|
testCases.forEach((testCase) => {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ var config = {
|
|||||||
'fs-jetpack',
|
'fs-jetpack',
|
||||||
'@rokt33r/markdown-it-math',
|
'@rokt33r/markdown-it-math',
|
||||||
'markdown-it-checkbox',
|
'markdown-it-checkbox',
|
||||||
|
'markdown-it-kbd',
|
||||||
'devtron',
|
'devtron',
|
||||||
'mixpanel',
|
'mixpanel',
|
||||||
'@rokt33r/season',
|
'@rokt33r/season',
|
||||||
|
|||||||
Reference in New Issue
Block a user