mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
510 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
414e0dbc05 | ||
|
|
081a3da9e1 | ||
|
|
626175b2b8 | ||
|
|
54db0c718a | ||
|
|
ae9175d1b1 | ||
|
|
16e1f0f882 | ||
|
|
179b830d14 | ||
|
|
aeb27f7bff | ||
|
|
0b2b89da0f | ||
|
|
804991b300 | ||
|
|
529f271e07 | ||
|
|
402d577ce0 | ||
|
|
4bb28a358e | ||
|
|
200a9275d2 | ||
|
|
9cb086cbd9 | ||
|
|
3a836aaf34 | ||
|
|
e14a6f2374 | ||
|
|
28794bff79 | ||
|
|
ecc9443c9a | ||
|
|
a1c499c026 | ||
|
|
4e8321268c | ||
|
|
8510325732 | ||
|
|
49102be894 | ||
|
|
1a97488dd7 | ||
|
|
b90c7e5318 | ||
|
|
ece1e37c16 | ||
|
|
3a6e3f5cae | ||
|
|
8e502eec80 | ||
|
|
042ebe8316 | ||
|
|
eee47b1f76 | ||
|
|
7978704f1b | ||
|
|
9392ac438c | ||
|
|
cbcfb57e35 | ||
|
|
29fa512a90 | ||
|
|
059d1fb4f2 | ||
|
|
4d3a538213 | ||
|
|
108a0db799 | ||
|
|
04f632570d | ||
|
|
92b919da6c | ||
|
|
4f0f611e52 | ||
|
|
90f7cf0996 | ||
|
|
ce43e80bdf | ||
|
|
c397752b7a | ||
|
|
2470364571 | ||
|
|
cab122ba8b | ||
|
|
56b01b5a85 | ||
|
|
cbcc9fb411 | ||
|
|
5bbc60d48a | ||
|
|
8fb8aab7b8 | ||
|
|
5b76216a64 | ||
|
|
7f5e372bdc | ||
|
|
01913a9a40 | ||
|
|
62f9fcf171 | ||
|
|
34c667f6b2 | ||
|
|
7a6de387e3 | ||
|
|
b6efbcedef | ||
|
|
3c70b2e5a0 | ||
|
|
62e108d460 | ||
|
|
b7f426b03f | ||
|
|
5ca13b70aa | ||
|
|
4644d107c6 | ||
|
|
06013b2a6a | ||
|
|
7380fc8500 | ||
|
|
81b550e66c | ||
|
|
f5972c273b | ||
|
|
c259ec2bed | ||
|
|
86a04b0dcb | ||
|
|
8b7c0c957c | ||
|
|
0cfe971a48 | ||
|
|
8884db858c | ||
|
|
0fe9dd4fbb | ||
|
|
ae57e99173 | ||
|
|
c238e1b5cb | ||
|
|
55ef998c55 | ||
|
|
166815ccbf | ||
|
|
34a16298d4 | ||
|
|
1613e72d47 | ||
|
|
e933ad89dc | ||
|
|
148fb25a15 | ||
|
|
fe0544dbc9 | ||
|
|
0aaea28e74 | ||
|
|
f4764afbf4 | ||
|
|
2a2b662a6f | ||
|
|
deade1f9f8 | ||
|
|
2b73c17cca | ||
|
|
369f8b6093 | ||
|
|
5391ca161d | ||
|
|
5b224d3b54 | ||
|
|
dad6a93944 | ||
|
|
10ec5c1342 | ||
|
|
8edfc15f45 | ||
|
|
792e41f161 | ||
|
|
9fbdf895af | ||
|
|
dbc0fedf43 | ||
|
|
cb298b8cad | ||
|
|
2459a80e15 | ||
|
|
0a973c4db3 | ||
|
|
a84fddd5fa | ||
|
|
1cd26d2e71 | ||
|
|
f88ac891ff | ||
|
|
17aa9ae85f | ||
|
|
bb46a9ba4c | ||
|
|
4a6c16df8d | ||
|
|
95787fafc6 | ||
|
|
c51d79e4d7 | ||
|
|
d5232a1b0e | ||
|
|
e093c7f982 | ||
|
|
3f53bddf7e | ||
|
|
e03d8175e3 | ||
|
|
7ab2e9f6ca | ||
|
|
6316ddc6a6 | ||
|
|
11c46ede5d | ||
|
|
2ec5238d97 | ||
|
|
b7f359f6cf | ||
|
|
8ef485221c | ||
|
|
afddb6fc33 | ||
|
|
f28ee1bc4b | ||
|
|
b997f0a5a7 | ||
|
|
48dc47ce57 | ||
|
|
f1a90f4a11 | ||
|
|
990d7edba4 | ||
|
|
13a1da91be | ||
|
|
930b58d2a8 | ||
|
|
b88b1065ee | ||
|
|
5fbb802b32 | ||
|
|
a40f8d25ef | ||
|
|
0a28798d54 | ||
|
|
6047987c25 | ||
|
|
2ce96186f2 | ||
|
|
26357bd4bc | ||
|
|
e94b45a7e4 | ||
|
|
4be1eb7a28 | ||
|
|
f4bbbb640d | ||
|
|
94b4e70b0f | ||
|
|
b31e3f7783 | ||
|
|
6375ac857a | ||
|
|
5eac08430f | ||
|
|
ef0b109ad4 | ||
|
|
7fbe6c0955 | ||
|
|
c86e43597c | ||
|
|
123a73a5e6 | ||
|
|
706b5d253f | ||
|
|
8fb8c7a40b | ||
|
|
21220f93b1 | ||
|
|
705e64377b | ||
|
|
5c3f0cd060 | ||
|
|
a238be5b7c | ||
|
|
2351bb78da | ||
|
|
6bbc5a91fe | ||
|
|
2f52233bd0 | ||
|
|
92eccb635a | ||
|
|
1f8acc3afc | ||
|
|
f8eaa9e796 | ||
|
|
7c9fecd943 | ||
|
|
3df8cbb357 | ||
|
|
993cb9cb0b | ||
|
|
5bd4a3f761 | ||
|
|
fcad84ced3 | ||
|
|
0bd48981ec | ||
|
|
0bf7e8b705 | ||
|
|
875c451221 | ||
|
|
c8256bea3a | ||
|
|
7d10b951b7 | ||
|
|
34e9238cc4 | ||
|
|
7d97784a58 | ||
|
|
1265e7c4a1 | ||
|
|
44ece2bf34 | ||
|
|
fde2a8055d | ||
|
|
f9643c2503 | ||
|
|
e6a97e5cb3 | ||
|
|
73b5546ae9 | ||
|
|
f98719ee73 | ||
|
|
7666182ae3 | ||
|
|
05af30f336 | ||
|
|
20de08b625 | ||
|
|
e60f4f4a64 | ||
|
|
6af25d932c | ||
|
|
3f49a8a15a | ||
|
|
bfa8db7b55 | ||
|
|
d6f2e7588c | ||
|
|
a594332ffb | ||
|
|
ceb18ebf1c | ||
|
|
b9038e254e | ||
|
|
b351e42538 | ||
|
|
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 | ||
|
|
e1a75a13e9 | ||
|
|
ee6f4de183 | ||
|
|
475885b3ef | ||
|
|
2d2b2d4c6c | ||
|
|
4d00454539 | ||
|
|
bf590b5614 | ||
|
|
3ef33c065c | ||
|
|
8e89fb8b92 | ||
|
|
8e81cfcf89 | ||
|
|
515736262d | ||
|
|
bd266dc602 | ||
|
|
9b3306157c | ||
|
|
f8e6a939ca | ||
|
|
8c48ee6fc1 | ||
|
|
5e476054d7 | ||
|
|
2fc8547384 | ||
|
|
2af2d71540 | ||
|
|
6aaf9d9eb2 | ||
|
|
42a9caf5a3 | ||
|
|
e6c1d7a383 | ||
|
|
02100bbc0a | ||
|
|
ce7c5f5d40 | ||
|
|
69f1ad6eb3 | ||
|
|
8320fb5024 | ||
|
|
4b79bca6bf | ||
|
|
4a5fd41249 | ||
|
|
4e90a93b30 | ||
|
|
9861fbf7c8 | ||
|
|
c762b9ae00 | ||
|
|
cc667a6edf | ||
|
|
419c57ed3f | ||
|
|
601f0b0de8 | ||
|
|
7b1c6c10b7 | ||
|
|
5a85c257cf | ||
|
|
beceb851c2 | ||
|
|
31485d3387 | ||
|
|
fc552e030a | ||
|
|
bf4c9f920a | ||
|
|
4ebd503664 | ||
|
|
0907bc80ef | ||
|
|
2cf46a3332 | ||
|
|
41868f28e6 | ||
|
|
964b7b62de | ||
|
|
0d34a03fe0 | ||
|
|
a62faa471c | ||
|
|
66f3ce2cb2 | ||
|
|
43c49f54d2 | ||
|
|
a15dfffa44 | ||
|
|
59985dee72 | ||
|
|
6b7132f134 | ||
|
|
e313b5e59d | ||
|
|
bb26d9a0a8 | ||
|
|
5c2c99282d | ||
|
|
94e6f89d07 | ||
|
|
3804a746df | ||
|
|
5c2d7e2d2a | ||
|
|
c34dd462b6 | ||
|
|
9141b1a641 | ||
|
|
0fea85e2f2 | ||
|
|
0ca41fbdb4 | ||
|
|
a58c191ded | ||
|
|
77089a1178 | ||
|
|
2cfb883bad | ||
|
|
ec8c8bb669 | ||
|
|
0b54f01107 | ||
|
|
67be198bee | ||
|
|
32a4a1aae1 | ||
|
|
dac7372839 | ||
|
|
521c261a37 | ||
|
|
27367488c2 | ||
|
|
0d5c3b1be6 | ||
|
|
a67d5ffacb | ||
|
|
687440a7c7 | ||
|
|
bafdc24a6d | ||
|
|
047f9c93c5 | ||
|
|
116fafc117 | ||
|
|
060c92091c | ||
|
|
5802525b73 | ||
|
|
c3580caabc | ||
|
|
08c027acc5 | ||
|
|
4468792346 | ||
|
|
1b16c68cf9 | ||
|
|
b99c1e3b32 | ||
|
|
eb2994e3c2 | ||
|
|
d88dd26186 | ||
|
|
59fcc58e9c | ||
|
|
acba61f36a | ||
|
|
a3a55a8bb4 | ||
|
|
22929d84fc | ||
|
|
9ea9d30947 | ||
|
|
7f08428fe2 | ||
|
|
0d80a7d961 | ||
|
|
2899264b54 | ||
|
|
923de0aa0d | ||
|
|
2b729dad15 | ||
|
|
b9b5bae78a | ||
|
|
a9acde07d1 | ||
|
|
a46b8d3079 | ||
|
|
8b92e2cbb7 | ||
|
|
881f5a5110 | ||
|
|
4e986a6384 | ||
|
|
ce5e1babb7 | ||
|
|
b85790d2fa | ||
|
|
6bc3e7fcf1 | ||
|
|
1ca968201d | ||
|
|
f2a03e4cc7 | ||
|
|
a752730718 | ||
|
|
9d20fd91ec | ||
|
|
70a6a3acb8 | ||
|
|
7f52eed4d5 | ||
|
|
105119e1a4 | ||
|
|
169e30e029 | ||
|
|
a5fa3e9e7a | ||
|
|
8985062d34 | ||
|
|
56eb9c76ae | ||
|
|
e5b6762bf3 | ||
|
|
e8bccaef88 | ||
|
|
afdb038244 | ||
|
|
56942d55eb | ||
|
|
9d742c8435 | ||
|
|
6ee4e48de2 | ||
|
|
184f3dc04b | ||
|
|
b6304a04e6 |
10
.eslintrc
10
.eslintrc
@@ -1,10 +1,16 @@
|
||||
{
|
||||
"extends": ["standard", "standard-jsx"],
|
||||
"extends": ["standard", "standard-jsx", "plugin:react/recommended"],
|
||||
"plugins": ["react"],
|
||||
"rules": {
|
||||
"no-useless-escape": 0,
|
||||
"prefer-const": "warn",
|
||||
"no-unused-vars": "warn",
|
||||
"no-undef": "warn",
|
||||
"no-lone-blocks": "warn"
|
||||
"no-lone-blocks": "warn",
|
||||
"react/prop-types": 0,
|
||||
"react/no-string-refs": 0,
|
||||
"react/no-find-dom-node": "warn",
|
||||
"react/no-render-return-value": "warn",
|
||||
"react/no-deprecated": "warn"
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
node_js:
|
||||
- 'stable'
|
||||
- 'lts/*'
|
||||
|
||||
script: npm run lint && npm run test
|
||||
- stable
|
||||
- lts/*
|
||||
script:
|
||||
- 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
|
||||
|
||||
52
Backers.md
52
Backers.md
@@ -1,5 +1,49 @@
|
||||
Become a [backer](https://salt.bountysource.com/teams/boostnote) and support Boostnote!
|
||||
You can support Boostnote from $ 5 a month!
|
||||
<h1 align="center">Sponsors & Backers</h1>
|
||||
|
||||
# Backers
|
||||
[Kazu Yokomizo](https://twitter.com/kazup_bot)
|
||||
Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/BoostIO/Boostnote/blob/master/Backers.md). If you'd like to join them, please consider:
|
||||
|
||||
- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
|
||||
|
||||
---
|
||||
|
||||
## Backers via OpenCollective
|
||||
<a href="https://opencollective.com/boostnoteio#backers" target="_blank"><img src="https://opencollective.com/boostnoteio/backers.svg?width=890"></a>
|
||||
|
||||
- [Ralph03](https://opencollective.com/ralph03) - $24
|
||||
|
||||
- [Nikolas Dan](https://opencollective.com/nikolas-dan) - $20
|
||||
|
||||
- [tatoosh11](https://twitter.com/ta11) - $10
|
||||
|
||||
- [Alexander Borovkov](https://opencollective.com/alexander-borovkov) - $10
|
||||
|
||||
- [Yeojong Kim](https://twitter.com/yeojoy) - $5
|
||||
|
||||
- [Scotia Draven](https://opencollective.com/scotia-draven) - $5
|
||||
|
||||
- [spoonhoop](https://opencollective.com/spoonhoop) - $5
|
||||
|
||||
## Backers via Bountysource
|
||||
https://salt.bountysource.com/teams/boostnote
|
||||
|
||||
- Kuzz - $65
|
||||
|
||||
- Intense Raiden - $45
|
||||
|
||||
- ravy22 - $25
|
||||
|
||||
- trentpolack - $20
|
||||
|
||||
- hikariru - $10
|
||||
|
||||
- kolchan11 - $10
|
||||
|
||||
- RonWalker22 - $10
|
||||
|
||||
- hocchuc - $5
|
||||
|
||||
- Adam - $5
|
||||
|
||||
- Steve - $5
|
||||
|
||||
- evmin - $5
|
||||
|
||||
@@ -1 +1,10 @@
|
||||
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.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Love Boostnote? Please consider supporting us via OpenCollective:
|
||||
👉 https://opencollective.com/boostnoteio
|
||||
-->
|
||||
|
||||
@@ -3,6 +3,8 @@ import _ from 'lodash'
|
||||
import CodeMirror from 'codemirror'
|
||||
import path from 'path'
|
||||
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
import fs from 'fs'
|
||||
|
||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||
|
||||
@@ -39,6 +41,7 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
this.props.onBlur != null && this.props.onBlur(e)
|
||||
}
|
||||
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||
this.loadStyleHandler = (e) => {
|
||||
this.editor.refresh()
|
||||
}
|
||||
@@ -64,7 +67,7 @@ export default class CodeEditor extends React.Component {
|
||||
if (cm.somethingSelected()) cm.indentSelection('add')
|
||||
else {
|
||||
const tabs = cm.getOption('indentWithTabs')
|
||||
if (line.trimLeft() === '- ' || line.trimLeft() === '* ' || line.trimLeft() === '+ ') {
|
||||
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)\] )?$/)) {
|
||||
cm.execCommand('goLineStart')
|
||||
if (tabs) {
|
||||
cm.execCommand('insertTab')
|
||||
@@ -98,14 +101,26 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
this.editor.on('blur', this.blurHandler)
|
||||
this.editor.on('change', this.changeHandler)
|
||||
this.editor.on('paste', this.pasteHandler)
|
||||
|
||||
let editorTheme = document.getElementById('editorTheme')
|
||||
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||
|
||||
CodeMirror.Vim.defineEx('quit', 'q', this.quitEditor)
|
||||
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
|
||||
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
||||
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
||||
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
||||
}
|
||||
|
||||
quitEditor () {
|
||||
document.querySelector('textarea').blur()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.editor.off('blur', this.blurHandler)
|
||||
this.editor.off('change', this.changeHandler)
|
||||
this.editor.off('paste', this.pasteHandler)
|
||||
let editorTheme = document.getElementById('editorTheme')
|
||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||
}
|
||||
@@ -199,9 +214,31 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
insertImageMd (imageMd) {
|
||||
const textarea = this.editor.getInputField()
|
||||
const cm = this.editor
|
||||
textarea.value = `${textarea.value.substr(0, textarea.selectionStart)}${imageMd}${textarea.value.substr(textarea.selectionEnd)}`
|
||||
this.editor.replaceSelection(imageMd)
|
||||
}
|
||||
|
||||
handlePaste (editor, e) {
|
||||
const dataTransferItem = e.clipboardData.items[0]
|
||||
if (!dataTransferItem.type.match('image')) return
|
||||
|
||||
const blob = dataTransferItem.getAsFile()
|
||||
let reader = new FileReader()
|
||||
let base64data
|
||||
|
||||
reader.readAsDataURL(blob)
|
||||
reader.onloadend = () => {
|
||||
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
||||
base64data += base64data.replace('+', ' ')
|
||||
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
||||
const imageName = Math.random().toString(36).slice(-16)
|
||||
const storagePath = findStorage(this.props.storageKey).path
|
||||
const imageDir = path.join(storagePath, 'images')
|
||||
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
|
||||
const imagePath = path.join(imageDir, `${imageName}.png`)
|
||||
fs.writeFile(imagePath, binaryData, 'binary')
|
||||
const imageMd = `})`
|
||||
this.insertImageMd(imageMd)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
@@ -266,6 +266,7 @@ class MarkdownEditor extends React.Component {
|
||||
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
||||
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||
showCopyNotification={config.ui.showCopyNotification}
|
||||
storagePath={storage.path}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import fs from 'fs'
|
||||
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import mdurl from 'mdurl'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { app } = remote
|
||||
@@ -32,15 +33,23 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber) {
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('${appPath}/resources/fonts/Lato-Black.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('${appPath}/resources/fonts/Lato-Black.woff') format('woff'), /* Modern Browsers */
|
||||
url('${appPath}/resources/fonts/Lato-Black.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
${markdownStyle}
|
||||
body {
|
||||
font-family: ${fontFamily.join(', ')};
|
||||
font-family: '${fontFamily.join("','")}';
|
||||
font-size: ${fontSize}px;
|
||||
}
|
||||
code {
|
||||
font-family: ${codeBlockFontFamily.join(', ')};
|
||||
background-color: rgba(0,0,0,0.04);
|
||||
color: #CC305F;
|
||||
}
|
||||
.lineNumber {
|
||||
${lineNumber && 'display: block !important;'}
|
||||
@@ -51,12 +60,12 @@ code {
|
||||
color: rgba(147,147,149,0.8);;
|
||||
fill: rgba(147,147,149,1);;
|
||||
border-radius: 50%;
|
||||
margin: 7px;
|
||||
margin: 0px 10px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -92,7 +101,7 @@ const OSX = global.process.platform === 'darwin'
|
||||
|
||||
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
||||
if (!OSX) {
|
||||
defaultFontFamily.unshift('\'Microsoft YaHei\'')
|
||||
defaultFontFamily.unshift('Microsoft YaHei')
|
||||
defaultFontFamily.unshift('meiryo')
|
||||
}
|
||||
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||
@@ -184,6 +193,16 @@ export default class MarkdownPreview extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
fixDecodedURI (node) {
|
||||
if (node && node.children.length === 1 && typeof node.children[0] === 'string') {
|
||||
const { innerText, href } = node
|
||||
|
||||
node.innerText = mdurl.decode(href) === innerText
|
||||
? href
|
||||
: innerText
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
||||
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
|
||||
@@ -224,6 +243,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
||||
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
||||
prevProps.lineNumber !== this.props.lineNumber ||
|
||||
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
||||
prevProps.theme !== this.props.theme) {
|
||||
this.applyStyle()
|
||||
this.rewriteIframe()
|
||||
@@ -247,7 +267,9 @@ export default class MarkdownPreview extends React.Component {
|
||||
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
|
||||
? theme
|
||||
: 'elegant'
|
||||
this.getWindow().document.getElementById('codeTheme').href = `${appPath}/node_modules/codemirror/theme/${theme}.css`
|
||||
this.getWindow().document.getElementById('codeTheme').href = theme.startsWith('solarized')
|
||||
? `${appPath}/node_modules/codemirror/theme/solarized.css`
|
||||
: `${appPath}/node_modules/codemirror/theme/${theme}.css`
|
||||
}
|
||||
|
||||
rewriteIframe () {
|
||||
@@ -262,7 +284,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
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)
|
||||
|
||||
@@ -279,6 +301,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
})
|
||||
|
||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||
this.fixDecodedURI(el)
|
||||
el.addEventListener('click', this.anchorClickHandler)
|
||||
})
|
||||
|
||||
@@ -291,8 +314,9 @@ export default class MarkdownPreview extends React.Component {
|
||||
})
|
||||
|
||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
|
||||
el.src = markdown.normalizeLinkText(el.src)
|
||||
if (!/\/:storage/.test(el.src)) return
|
||||
el.src = `file:///${path.join(storagePath, 'images', path.basename(el.src))}`
|
||||
el.src = `file:///${markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
|
||||
})
|
||||
|
||||
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
||||
@@ -308,14 +332,21 @@ 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.onclick = (e) => {
|
||||
copy(content)
|
||||
if (showCopyNotification) {
|
||||
this.notify('Saved to Clipboard!', {
|
||||
body: 'Paste it wherever you want!',
|
||||
silent: true
|
||||
})
|
||||
}
|
||||
}
|
||||
el.parentNode.appendChild(copyIcon)
|
||||
el.innerHTML = ''
|
||||
if (codeBlockTheme.indexOf('solarized') === 0) {
|
||||
const [refThema, color] = codeBlockTheme.split(' ')
|
||||
el.parentNode.className += ` cm-s-${refThema} cm-s-${color} CodeMirror`
|
||||
} else {
|
||||
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
|
||||
}
|
||||
CodeMirror.runMode(content, syntax.mime, el, {
|
||||
tabSize: indentSize
|
||||
})
|
||||
@@ -425,5 +456,6 @@ MarkdownPreview.propTypes = {
|
||||
onMouseDown: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
showCopyNotification: PropTypes.bool,
|
||||
storagePath: PropTypes.string
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ const ModalEscButton = ({
|
||||
handleEscButtonClick
|
||||
}) => (
|
||||
<button styleName='escButton' onClick={handleEscButtonClick}>
|
||||
<div styleName='esc-mark'>x</div>
|
||||
<div styleName='esc-mark'>×</div>
|
||||
<div styleName='esc-text'>esc</div>
|
||||
</button>
|
||||
)
|
||||
|
||||
@@ -11,4 +11,6 @@
|
||||
height top-bar-height
|
||||
|
||||
.esc-mark
|
||||
font-size 15px
|
||||
font-size 28px
|
||||
margin-top -5px
|
||||
margin-bottom -7px
|
||||
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)
|
||||
22
browser/components/NavToggleButton.styl
Normal file
22
browser/components/NavToggleButton.styl
Normal file
@@ -0,0 +1,22 @@
|
||||
.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="white"]
|
||||
navWhiteButtonColor()
|
||||
|
||||
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 {Object} note
|
||||
* @param {Function} handleNoteClick
|
||||
* @param {Function} handleNoteContextMenu
|
||||
* @param {Function} handleDragStart
|
||||
* @param {string} dateDisplay
|
||||
*/
|
||||
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStart }) => (
|
||||
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
|
||||
<div styleName={isActive
|
||||
? 'item--active'
|
||||
: 'item'
|
||||
}
|
||||
key={`${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)}
|
||||
draggable='true'
|
||||
>
|
||||
@@ -68,7 +70,10 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStar
|
||||
|
||||
<div styleName='item-bottom-time'>{dateDisplay}</div>
|
||||
{note.isStarred
|
||||
? <i styleName='item-star' className='fa fa-star' /> : ''
|
||||
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
|
||||
}
|
||||
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
|
||||
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
|
||||
}
|
||||
{note.type === 'MARKDOWN_NOTE'
|
||||
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
||||
@@ -99,6 +104,7 @@ NoteItem.propTypes = {
|
||||
isTrashed: PropTypes.bool.isRequired
|
||||
}),
|
||||
handleNoteClick: PropTypes.func.isRequired,
|
||||
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||
handleDragStart: PropTypes.func.isRequired,
|
||||
handleDragEnd: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ $control-height = 30px
|
||||
user-select none
|
||||
cursor pointer
|
||||
background-color $ui-noteList-backgroundColor
|
||||
transition background-color 0.2s
|
||||
transition 0.2s
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
@@ -25,7 +25,7 @@ $control-height = 30px
|
||||
.item-star
|
||||
color $ui-favorite-star-button-color
|
||||
&:active
|
||||
background-color $ui-button--active-backgroundColor
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
.item-title
|
||||
.item-title-icon
|
||||
@@ -43,7 +43,7 @@ $control-height = 30px
|
||||
|
||||
.item--active
|
||||
@extend .item
|
||||
background-color $ui-button--active-backgroundColor
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
color $ui-text-color
|
||||
.item-title
|
||||
.item-title-empty
|
||||
@@ -59,7 +59,15 @@ $control-height = 30px
|
||||
.item-star
|
||||
color $ui-favorite-star-button-color
|
||||
&:hover
|
||||
background-color $ui-button--active-backgroundColor
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color #e74c3c
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:active, &:active:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color #e74c3c
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
|
||||
.item-title-icon
|
||||
position relative
|
||||
@@ -67,7 +75,8 @@ $control-height = 30px
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.item-title
|
||||
font-size 14px
|
||||
font-size 15px
|
||||
font-weight 700
|
||||
position relative
|
||||
top -12px
|
||||
left 20px
|
||||
@@ -83,7 +92,7 @@ $control-height = 30px
|
||||
.item-bottom
|
||||
position relative
|
||||
bottom 0px
|
||||
margin-top 2px
|
||||
margin-top 10px
|
||||
font-size 12px
|
||||
line-height 20px
|
||||
overflow ellipsis
|
||||
@@ -92,40 +101,63 @@ $control-height = 30px
|
||||
.item-bottom-tagList
|
||||
flex 1
|
||||
overflow ellipsis
|
||||
line-height 20px
|
||||
padding-top 7px
|
||||
line-height 25px
|
||||
padding-left 2px
|
||||
margin-right 27px
|
||||
margin-right 40px
|
||||
|
||||
.item-bottom-tagList-item
|
||||
font-size 11px
|
||||
margin-right 8px
|
||||
padding 0
|
||||
height 20px
|
||||
box-sizing border-box
|
||||
border-radius 2px
|
||||
padding 1px 2px
|
||||
padding 4px
|
||||
vertical-align middle
|
||||
background-color white
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.item-bottom-time
|
||||
color $ui-inactive-text-color
|
||||
font-size 11px
|
||||
font-size 13px
|
||||
padding-left 2px
|
||||
padding-bottom 2px
|
||||
|
||||
.item-star
|
||||
position absolute
|
||||
right -20px
|
||||
bottom 2px
|
||||
width 34px
|
||||
height 34px
|
||||
right -6px
|
||||
bottom 23px
|
||||
width 16px
|
||||
height 16px
|
||||
color alpha($ui-favorite-star-button-color, 60%)
|
||||
font-size 12px
|
||||
padding 0
|
||||
border-radius 17px
|
||||
|
||||
.item-pin
|
||||
position absolute
|
||||
right 0px
|
||||
bottom 2px
|
||||
width 34px
|
||||
height 34px
|
||||
color #E54D42
|
||||
font-size 14px
|
||||
padding 0
|
||||
border-radius 17px
|
||||
|
||||
body[data-theme="white"]
|
||||
.item
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
&:active
|
||||
background-color $ui-button--active-backgroundColor
|
||||
|
||||
.item--active
|
||||
@extend .item
|
||||
background-color $ui-button--active-backgroundColor
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
border-color $ui-dark-borderColor
|
||||
@@ -137,6 +169,7 @@ body[data-theme="dark"]
|
||||
&:hover
|
||||
transition 0.15s
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
color $ui-dark-text-color
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
@@ -144,11 +177,12 @@ body[data-theme="dark"]
|
||||
color $ui-dark-text-color
|
||||
.item-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
||||
background-color alpha(#fff, 20%)
|
||||
color $ui-dark-text-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
.item-title
|
||||
.item-title-icon
|
||||
.item-bottom-time
|
||||
@@ -174,6 +208,11 @@ body[data-theme="dark"]
|
||||
.item-bottom-tagList-item
|
||||
background-color alpha(white, 10%)
|
||||
color $ui-dark-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||
color #c0392b
|
||||
.item-bottom-tagList-item
|
||||
background-color alpha(#fff, 20%)
|
||||
|
||||
.item-title
|
||||
color $ui-inactive-text-color
|
||||
|
||||
@@ -10,15 +10,17 @@ import styles from './NoteItemSimple.styl'
|
||||
* @param {boolean} isActive
|
||||
* @param {Object} note
|
||||
* @param {Function} handleNoteClick
|
||||
* @param {Function} handleNoteContextMenu
|
||||
* @param {Function} handleDragStart
|
||||
*/
|
||||
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleDragStart }) => (
|
||||
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu, handleDragStart }) => (
|
||||
<div styleName={isActive
|
||||
? 'item-simple--active'
|
||||
: 'item-simple'
|
||||
}
|
||||
key={`${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)}
|
||||
draggable='true'
|
||||
>
|
||||
@@ -44,6 +46,7 @@ NoteItemSimple.propTypes = {
|
||||
title: PropTypes.string.isrequired
|
||||
}),
|
||||
handleNoteClick: PropTypes.func.isRequired,
|
||||
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||
handleDragStart: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
|
||||
@@ -11,15 +11,15 @@ $control-height = 30px
|
||||
user-select none
|
||||
cursor pointer
|
||||
background-color $ui-noteList-backgroundColor
|
||||
transition background-color 0.15s
|
||||
transition 0.2s
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
.item-simple-title-icon
|
||||
color $ui-text-color
|
||||
&:active
|
||||
background-color $ui-button--active-backgroundColor
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
@@ -28,7 +28,7 @@ $control-height = 30px
|
||||
|
||||
.item-simple--active
|
||||
@extend .item-simple
|
||||
background-color $ui-button--active-backgroundColor
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
color $ui-text-color
|
||||
.item-simple-title
|
||||
.item-simple-title-empty
|
||||
@@ -37,7 +37,15 @@ $control-height = 30px
|
||||
.item-simple-title-icon
|
||||
color $ui-text-color
|
||||
&:hover
|
||||
background-color $ui-button--active-backgroundColor
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color #e74c3c
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:active, &:active:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color #e74c3c
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
|
||||
.item-simple-title
|
||||
font-size 13px
|
||||
@@ -59,6 +67,20 @@ $control-height = 30px
|
||||
font-weight normal
|
||||
color $ui-inactive-text-color
|
||||
|
||||
body[data-theme="white"]
|
||||
.item-simple
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
&:active
|
||||
background-color $ui-button--active-backgroundColor
|
||||
|
||||
.item-simple--active
|
||||
@extend .item-simple
|
||||
background-color $ui-button--active-backgroundColor
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
border-color $ui-dark-borderColor
|
||||
@@ -67,33 +89,50 @@ body[data-theme="dark"]
|
||||
.item-simple
|
||||
border-color $ui-dark-borderColor
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
&:active
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
&:hover
|
||||
transition 0.15s
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
color $ui-dark-text-color
|
||||
.item-simple-title
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
color $ui-dark-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(#fff, 20%)
|
||||
color $ui-dark-text-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
.item-simple-title
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
transition 0.15s
|
||||
color $ui-dark-text-color
|
||||
.item-simple-bottom-tagList-item
|
||||
transition 0.15s
|
||||
background-color alpha(white, 10%)
|
||||
color $ui-dark-text-color
|
||||
|
||||
.item-simple--active
|
||||
border-color $ui-dark-borderColor
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
.item-simple-wrapper
|
||||
border-color transparent
|
||||
.item-simple-title
|
||||
.item-simple-title-icon
|
||||
.item-simple-bottom-time
|
||||
color $ui-dark-text-color
|
||||
.item-simple-bottom-tagList-item
|
||||
background-color transparent
|
||||
background-color alpha(white, 10%)
|
||||
color $ui-dark-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||
color #c0392b
|
||||
.item-simple-bottom-tagList-item
|
||||
background-color alpha(#fff, 20%)
|
||||
|
||||
.item-simple-title
|
||||
color $ui-inactive-text-color
|
||||
|
||||
55
browser/components/RealtimeNotification.js
Normal file
55
browser/components/RealtimeNotification.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './RealtimeNotification.styl'
|
||||
|
||||
const electron = require('electron')
|
||||
const { shell } = electron
|
||||
|
||||
class RealtimeNotification extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
notifications: []
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.fetchNotifications()
|
||||
}
|
||||
|
||||
fetchNotifications () {
|
||||
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
|
||||
fetch(notificationsUrl)
|
||||
.then(response => {
|
||||
return response.json()
|
||||
})
|
||||
.then(json => {
|
||||
this.setState({notifications: json.notifications})
|
||||
})
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
render () {
|
||||
const { notifications } = this.state
|
||||
const link = notifications.length > 0
|
||||
? <a styleName='notification-link' href={notifications[0].linkUrl}
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>
|
||||
Info: {notifications[0].text}
|
||||
</a>
|
||||
: ''
|
||||
|
||||
return (
|
||||
<div styleName='notification-area' style={this.props.style}>{link}</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
RealtimeNotification.propTypes = {}
|
||||
|
||||
export default CSSModules(RealtimeNotification, styles)
|
||||
34
browser/components/RealtimeNotification.styl
Normal file
34
browser/components/RealtimeNotification.styl
Normal file
@@ -0,0 +1,34 @@
|
||||
.notification-area
|
||||
z-index 1000
|
||||
font-size 12px
|
||||
position absolute
|
||||
bottom 20px
|
||||
width 100%
|
||||
float left
|
||||
height 30px
|
||||
background-color none
|
||||
|
||||
.notification-link
|
||||
position absolute
|
||||
text-decoration none
|
||||
color #282A36
|
||||
font-size 14px
|
||||
border 1px solid #6FA8E6
|
||||
background-color alpha(#6FA8E6, 0.2)
|
||||
padding 5px 12px
|
||||
border-radius 2px
|
||||
transition 0.2s
|
||||
&:hover
|
||||
color #1378BD
|
||||
|
||||
body[data-theme="dark"]
|
||||
.notification-area
|
||||
background-color none
|
||||
|
||||
.notification-link
|
||||
color #fff
|
||||
border 1px solid alpha(#5CB85C, 0.6)
|
||||
background-color alpha(#5CB85C, 0.2)
|
||||
transition 0.2s
|
||||
&:hover
|
||||
color #5CB85C
|
||||
@@ -15,27 +15,53 @@ import styles from './SideNavFilter.styl'
|
||||
*/
|
||||
const SideNavFilter = ({
|
||||
isFolded, isHomeActive, handleAllNotesButtonClick,
|
||||
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick
|
||||
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
|
||||
counterTotalNote, counterStarredNote
|
||||
}) => (
|
||||
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
||||
|
||||
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
||||
onClick={handleAllNotesButtonClick}
|
||||
>
|
||||
<i className='fa fa-archive fa-fw' />
|
||||
<div styleName='iconWrap'>
|
||||
<img src={isHomeActive
|
||||
? '../resources/icon/icon-all-active.svg'
|
||||
: '../resources/icon/icon-all.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span styleName='menu-button-label'>All Notes</span>
|
||||
<span styleName='counters'>{counterTotalNote}</span>
|
||||
</button>
|
||||
|
||||
<button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
|
||||
onClick={handleStarredButtonClick}
|
||||
>
|
||||
<i className='fa fa-star fa-fw' />
|
||||
<div styleName='iconWrap'>
|
||||
<img src={isStarredActive
|
||||
? '../resources/icon/icon-star-active.svg'
|
||||
: '../resources/icon/icon-star.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span styleName='menu-button-label'>Starred</span>
|
||||
<span styleName='counters'>{counterStarredNote}</span>
|
||||
</button>
|
||||
<button styleName={isTrashedActive ? 'menu-button--active' : 'menu-button'}
|
||||
|
||||
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
||||
onClick={handleTrashedButtonClick}
|
||||
>
|
||||
<i className='fa fa-trash fa-fw' />
|
||||
<div styleName='iconWrap'>
|
||||
<img src={isTrashedActive
|
||||
? '../resources/icon/icon-trash-active.svg'
|
||||
: '../resources/icon/icon-trash.svg'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<span styleName='menu-button-label'>Trash</span>
|
||||
<span styleName='counters'>{counterDelNote}</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -3,49 +3,55 @@
|
||||
|
||||
.menu-button
|
||||
navButtonColor()
|
||||
height 32px
|
||||
padding 0 15px
|
||||
font-size 12px
|
||||
height 36px
|
||||
padding 0 15px 0 20px
|
||||
font-size 14px
|
||||
width 100%
|
||||
text-align left
|
||||
overflow ellipsis
|
||||
display flex
|
||||
align-items center
|
||||
&:hover, &:active, &:active:hover
|
||||
color #1EC38B
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
|
||||
.iconWrap
|
||||
width 20px
|
||||
text-align center
|
||||
|
||||
.counters
|
||||
float right
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.menu-button--active
|
||||
@extend .menu-button
|
||||
color #e74c3c
|
||||
background-color $ui-button--active-backgroundColor
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
SideNavFilter()
|
||||
color #1EC38B
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
.menu-button-label, .counters
|
||||
color #1EC38B
|
||||
&:hover
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color #e74c3c
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:active, &:active:hover
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color #e74c3c
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
color #1EC38B
|
||||
|
||||
.menu-button-star--active
|
||||
@extend .menu-button
|
||||
color #F9BF3B
|
||||
background-color $ui-button--active-backgroundColor
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:hover
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color #F9BF3B
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:active, &:active:hover
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color #F9BF3B
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
SideNavFilter()
|
||||
color #1EC38B
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
.menu-button-label, .counters
|
||||
color #1EC38B
|
||||
|
||||
.menu-button-trash--active
|
||||
@extend .menu-button
|
||||
SideNavFilter()
|
||||
color #1EC38B
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
.menu-button-label, .counters
|
||||
color #1EC38B
|
||||
|
||||
.menu-button-label
|
||||
margin-left 5px
|
||||
margin-left 10px
|
||||
flex 1
|
||||
|
||||
.menu--folded
|
||||
@extend .menu
|
||||
@@ -54,6 +60,7 @@
|
||||
&:hover .menu-button-label
|
||||
transition opacity 0.15s
|
||||
opacity 1
|
||||
|
||||
.menu-button-label
|
||||
position fixed
|
||||
display inline-block
|
||||
@@ -63,15 +70,73 @@
|
||||
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
|
||||
font-size 13px
|
||||
.counters
|
||||
display none
|
||||
|
||||
body[data-theme="white"]
|
||||
.menu-button
|
||||
navWhiteButtonColor()
|
||||
|
||||
.counters
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.menu-button--active
|
||||
@extend .menu-button
|
||||
color #e74c3c
|
||||
background-color $ui-button--active-backgroundColor
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||
color #e74c3c
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:active, &:active:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||
color #e74c3c
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
|
||||
.menu-button-star--active
|
||||
@extend .menu-button
|
||||
color #F9BF3B
|
||||
background-color $ui-button--active-backgroundColor
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||
color #F9BF3B
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:active, &:active:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||
color #F9BF3B
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
|
||||
.menu-button-trash--active
|
||||
@extend .menu-button
|
||||
color #5D9E36
|
||||
background-color $ui-button--active-backgroundColor
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||
color #5D9E36
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
&:active, &:active:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||
color #5D9E36
|
||||
.menu-button-label
|
||||
color $ui-text-color
|
||||
|
||||
body[data-theme="dark"]
|
||||
.menu-button
|
||||
@@ -88,7 +153,7 @@ body[data-theme="dark"]
|
||||
.menu-button-label
|
||||
color $ui-dark-text-color
|
||||
&:hover
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||
color #c0392b
|
||||
.menu-button-label
|
||||
color $ui-dark-text-color
|
||||
@@ -99,7 +164,18 @@ body[data-theme="dark"]
|
||||
.menu-button-label
|
||||
color $ui-dark-text-color
|
||||
&:hover
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||
color $ui-favorite-star-button-color
|
||||
.menu-button-label
|
||||
color $ui-dark-text-color
|
||||
|
||||
.menu-button-trash--active
|
||||
color #5D9E36
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
.menu-button-label
|
||||
color $ui-dark-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||
color #5D9E36
|
||||
.menu-button-label
|
||||
color $ui-dark-text-color
|
||||
@@ -35,10 +35,8 @@ const StorageItem = ({
|
||||
>
|
||||
<span styleName={isFolded
|
||||
? 'folderList-item-name--folded' : 'folderList-item-name'
|
||||
}
|
||||
style={{borderColor: folderColor}}
|
||||
>
|
||||
{isFolded ? folderName.substring(0, 1) : folderName}
|
||||
}>
|
||||
<text style={{color: folderColor, paddingRight: '10px'}}>{isActive ? <i className='fa fa-folder-open-o' /> : <i className='fa fa-folder-o' />}</text>{isFolded ? folderName.substring(0, 1) : folderName}
|
||||
</span>
|
||||
{(!isFolded && isNumber(noteCount)) &&
|
||||
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
||||
|
||||
@@ -5,37 +5,36 @@
|
||||
.folderList-item
|
||||
display flex
|
||||
width 100%
|
||||
height 26px
|
||||
height 34px
|
||||
background-color transparent
|
||||
color $ui-inactive-text-color
|
||||
padding 0
|
||||
margin-bottom 5px
|
||||
text-align left
|
||||
border none
|
||||
overflow ellipsis
|
||||
font-size 12px
|
||||
font-size 14px
|
||||
&:first-child
|
||||
margin-top 0
|
||||
&:hover
|
||||
color $ui-text-color
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
color #1EC38B;
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
transition background-color 0.15s
|
||||
&:active
|
||||
color $ui-text-color
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color $$ui-button-default-color
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
|
||||
.folderList-item--active
|
||||
@extend .folderList-item
|
||||
color $ui-text-color
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color #1EC38B
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
&:hover
|
||||
color $ui-text-color
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color #1EC38B;
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 50%)
|
||||
|
||||
.folderList-item-name
|
||||
display block
|
||||
flex 1
|
||||
padding 0 25px
|
||||
padding 0 12px
|
||||
height 26px
|
||||
line-height 26px
|
||||
border-width 0 0 0 2px
|
||||
@@ -48,7 +47,7 @@
|
||||
float right
|
||||
line-height 26px
|
||||
padding-right 15px
|
||||
font-size 12px
|
||||
font-size 13px
|
||||
|
||||
.folderList-item-tooltip
|
||||
tooltip()
|
||||
@@ -69,8 +68,28 @@
|
||||
|
||||
.folderList-item-name--folded
|
||||
@extend .folderList-item-name
|
||||
padding-left 12px
|
||||
padding-left 17px
|
||||
text
|
||||
display none
|
||||
|
||||
body[data-theme="white"]
|
||||
.folderList-item
|
||||
color $ui-inactive-text-color
|
||||
&: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
|
||||
|
||||
.folderList-item--active
|
||||
@extend .folderList-item
|
||||
color $ui-text-color
|
||||
background-color $ui-button--active-backgroundColor
|
||||
&:hover
|
||||
color $ui-text-color
|
||||
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||
|
||||
body[data-theme="dark"]
|
||||
.folderList-item
|
||||
@@ -86,7 +105,7 @@ body[data-theme="dark"]
|
||||
color $ui-dark-text-color
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
&:active
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||
&:hover
|
||||
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 './StorageList.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/StorageList.styl
Normal file
20
browser/components/StorageList.styl
Normal file
@@ -0,0 +1,20 @@
|
||||
.storageList
|
||||
absolute left right
|
||||
bottom 37px
|
||||
top 180px
|
||||
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)
|
||||
84
browser/components/TagListItem.styl
Normal file
84
browser/components/TagListItem.styl
Normal file
@@ -0,0 +1,84 @@
|
||||
.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 13px
|
||||
&:first-child
|
||||
margin-top 0
|
||||
&:hover
|
||||
color $ui-button-default-color
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
transition background-color 0.15s
|
||||
&:active, &:active:hover
|
||||
color $ui-button-default-color
|
||||
background-color $ui-button-default--active-backgroundColor
|
||||
|
||||
.tagList-item-active
|
||||
background-color $ui-button-default--active-backgroundColor
|
||||
display flex
|
||||
width 100%
|
||||
height 26px
|
||||
padding 0
|
||||
margin-bottom 5px
|
||||
text-align left
|
||||
border none
|
||||
overflow ellipsis
|
||||
font-size 13px
|
||||
color $ui-button-default-color
|
||||
&:hover
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 60%)
|
||||
transition 0.2s
|
||||
|
||||
.tagList-item-name
|
||||
display block
|
||||
flex 1
|
||||
padding 0 15px
|
||||
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="white"]
|
||||
.tagList-item
|
||||
color $ui-inactive-text-color
|
||||
&:hover
|
||||
color $ui-text-color
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
&:active
|
||||
color $ui-text-color
|
||||
background-color $ui-button--active-backgroundColor
|
||||
|
||||
.tagList-item-active
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color $ui-text-color
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||
|
||||
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%)
|
||||
@@ -1,24 +1,25 @@
|
||||
.percentageBar
|
||||
position absolute
|
||||
top 58px
|
||||
right: 0px
|
||||
top 50px
|
||||
right 0px
|
||||
left 0px
|
||||
background-color #DADFE1
|
||||
width 100%
|
||||
height: 15px
|
||||
height: 17px
|
||||
font-size: 12px
|
||||
z-index 100
|
||||
border-radius 2px
|
||||
|
||||
.progressBar
|
||||
background-color: #6C7A89
|
||||
height 15px
|
||||
background-color: #1EC38B
|
||||
height 17px
|
||||
border-radius 2px
|
||||
transition 0.3s
|
||||
transition 0.4s cubic-bezier(0.4, 0.4, 0, 1)
|
||||
|
||||
.percentageText
|
||||
color #f4f4f4
|
||||
padding: 2px 43%
|
||||
font-weight 600
|
||||
|
||||
body[data-theme="dark"]
|
||||
.percentageBar
|
||||
|
||||
@@ -118,7 +118,7 @@ hr
|
||||
h1, h2, h3, h4, h5, h6
|
||||
font-weight bold
|
||||
h1
|
||||
font-size 2.25em
|
||||
font-size 2.55em
|
||||
padding-bottom 0.3em
|
||||
line-height 1.2em
|
||||
border-bottom solid 1px borderColor
|
||||
@@ -193,6 +193,7 @@ ol
|
||||
&>li>ul, &>li>ol
|
||||
margin 0
|
||||
code
|
||||
color #CC305F
|
||||
padding 0.2em 0.4em
|
||||
background-color #f7f7f7
|
||||
border-radius 3px
|
||||
@@ -268,6 +269,16 @@ table
|
||||
border-color borderColor
|
||||
&:last-child
|
||||
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%)
|
||||
themeDarkText = #f9f9f9
|
||||
@@ -316,3 +327,6 @@ body[data-theme="dark"]
|
||||
border-color themeDarkTableBorder
|
||||
&:last-child
|
||||
border-right solid 1px themeDarkTableBorder
|
||||
kbd
|
||||
background-color themeDarkBorder
|
||||
color themeDarkText
|
||||
@@ -64,7 +64,7 @@ $list-width = 250px
|
||||
|
||||
.result-nav-storageList
|
||||
absolute bottom left right
|
||||
top 110px + 32px + 10px + 10px
|
||||
top 110px + 32px + 10px + 10px + 20px
|
||||
overflow-y auto
|
||||
|
||||
.result-list
|
||||
|
||||
@@ -196,6 +196,7 @@ class NoteDetail extends React.Component {
|
||||
lineNumber={config.preview.lineNumber}
|
||||
indentSize={editorIndentSize}
|
||||
value={note.content}
|
||||
showCopyNotification={config.ui.showCopyNotification}
|
||||
storagePath={storage.path}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
.root
|
||||
absolute top bottom left right
|
||||
bottom 30px
|
||||
left $note-detail-left-margin
|
||||
right $note-detail-right-margin
|
||||
height 100%
|
||||
|
||||
@@ -84,6 +84,8 @@ nodeIpc.connectTo(
|
||||
const { config } = payload
|
||||
if (config.ui.theme === 'dark') {
|
||||
document.body.setAttribute('data-theme', 'dark')
|
||||
} else if (config.ui.theme === 'white') {
|
||||
document.body.setAttribute('data-theme', 'white')
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'default')
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ const themes = fs.readdirSync(themePath)
|
||||
.map((themePath) => {
|
||||
return themePath.substring(0, themePath.lastIndexOf('.'))
|
||||
})
|
||||
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
||||
|
||||
const consts = {
|
||||
FOLDER_COLORS: [
|
||||
|
||||
@@ -3,6 +3,7 @@ import emoji from 'markdown-it-emoji'
|
||||
import math from '@rokt33r/markdown-it-math'
|
||||
import _ from 'lodash'
|
||||
|
||||
// FIXME We should not depend on global variable.
|
||||
const katex = window.katex
|
||||
|
||||
function createGutter (str) {
|
||||
@@ -59,6 +60,18 @@ md.use(math, {
|
||||
})
|
||||
md.use(require('markdown-it-imsize'))
|
||||
md.use(require('markdown-it-footnote'))
|
||||
md.use(require('markdown-it-multimd-table'))
|
||||
md.use(require('markdown-it-named-headers'), {
|
||||
slugify: (header) => {
|
||||
return encodeURI(header.trim()
|
||||
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
||||
.replace(/\s+/g, '-'))
|
||||
.replace(/\-+$/, '')
|
||||
}
|
||||
})
|
||||
md.use(require('markdown-it-kbd'))
|
||||
md.use(require('markdown-it-plantuml'))
|
||||
|
||||
// Override task item
|
||||
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||
let content, terminate, i, l, token
|
||||
@@ -125,43 +138,20 @@ md.renderer.render = function render (tokens, options, env) {
|
||||
let result = originalRender.call(md.renderer, tokens, options, env)
|
||||
return result
|
||||
}
|
||||
// FIXME We should not depend on global variable.
|
||||
window.md = md
|
||||
|
||||
function strip (input) {
|
||||
var output = input
|
||||
try {
|
||||
output = output
|
||||
.replace(/^([\s\t]*)([\*\-\+]|\d\.)\s+/gm, '$1')
|
||||
.replace(/\n={2,}/g, '\n')
|
||||
.replace(/~~/g, '')
|
||||
.replace(/`{3}.*\n/g, '')
|
||||
.replace(/<(.*?)>/g, '$1')
|
||||
.replace(/^[=\-]{2,}\s*$/g, '')
|
||||
.replace(/\[\^.+?\](: .*?$)?/g, '')
|
||||
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
|
||||
.replace(/!\[.*?\][\[\(].*?[\]\)]/g, '')
|
||||
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
||||
.replace(/>/g, '')
|
||||
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
||||
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
|
||||
.replace(/([\*_]{1,3})(\S.*?\S)\1/g, '$2')
|
||||
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
||||
.replace(/^-{3,}\s*$/g, '')
|
||||
.replace(/`(.+?)`/g, '$1')
|
||||
.replace(/\n{2,}/g, '\n\n')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return input
|
||||
}
|
||||
return output
|
||||
function normalizeLinkText (linkText) {
|
||||
return md.normalizeLinkText(linkText)
|
||||
}
|
||||
|
||||
const markdown = {
|
||||
render: function markdown (content) {
|
||||
if (!_.isString(content)) content = ''
|
||||
const renderedContent = md.render(content)
|
||||
return md.normalizeLinkText(renderedContent)
|
||||
return renderedContent
|
||||
},
|
||||
strip
|
||||
normalizeLinkText
|
||||
}
|
||||
|
||||
export default markdown
|
||||
|
||||
39
browser/lib/markdownTextHelper.js
Normal file
39
browser/lib/markdownTextHelper.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @fileoverview Text trimmer for markdown note.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @return {string}
|
||||
*/
|
||||
export function strip (input) {
|
||||
let output = input
|
||||
try {
|
||||
output = output
|
||||
.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, '$1')
|
||||
.replace(/\n={2,}/g, '\n')
|
||||
.replace(/~~/g, '')
|
||||
.replace(/`{3}.*\n/g, '')
|
||||
.replace(/<(.*?)>/g, '$1')
|
||||
.replace(/^[=\-]{2,}\s*$/g, '')
|
||||
.replace(/\[\^.+?\](: .*?$)?/g, '')
|
||||
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
|
||||
.replace(/!\[.*?\][\[\(].*?[\]\)]/g, '')
|
||||
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
||||
.replace(/>/g, '')
|
||||
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
||||
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
|
||||
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
||||
.replace(/^-{3,}\s*$/g, '')
|
||||
.replace(/`(.+?)`/g, '$1')
|
||||
.replace(/\n{2,}/g, '\n\n')
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return input
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
export default {
|
||||
strip
|
||||
}
|
||||
@@ -2,15 +2,16 @@ import _ from 'lodash'
|
||||
|
||||
export default function searchFromNotes (notes, search) {
|
||||
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) => {
|
||||
foundNotes = findByWord(foundNotes, block)
|
||||
if (block.match(/^#.+/)) {
|
||||
notes = findByTag(notes, block)
|
||||
} else {
|
||||
notes = findByWord(notes, block)
|
||||
foundNotes = foundNotes.concat(findByTag(notes, block))
|
||||
}
|
||||
})
|
||||
return notes
|
||||
return foundNotes
|
||||
}
|
||||
|
||||
function findByTag (notes, block) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
.root
|
||||
absolute top bottom right
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
|
||||
.empty
|
||||
height 320px
|
||||
@@ -8,8 +11,9 @@
|
||||
|
||||
.empty-message
|
||||
width 100%
|
||||
font-size 42px
|
||||
line-height 72px
|
||||
font-size 36px
|
||||
font-weight 600
|
||||
line-height 56px
|
||||
text-align center
|
||||
color $ui-inactive-text-color
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
*/
|
||||
|
||||
// Margin on the left side and the right side for NoteDetail component.
|
||||
$note-detail-left-margin = 25px
|
||||
$note-detail-right-margin = 25px
|
||||
$note-detail-left-margin = 100px
|
||||
$note-detail-right-margin = 120px
|
||||
$snippet-note-detail-left-margin = 60px
|
||||
$snippet-note-detail-right-margin = 80px
|
||||
|
||||
$note-detail-box-shadow = 2px 0 15px -8px #b1b1b1 inset
|
||||
|
||||
@@ -259,12 +259,11 @@ class FolderSelect extends React.Component {
|
||||
{optionList}
|
||||
</div>
|
||||
</div>
|
||||
: <div styleName='idle'>
|
||||
: <div styleName='idle' style={{color: currentOption.folder.color}}>
|
||||
<div styleName='idle-label'>
|
||||
<span styleName='idle-label-name'
|
||||
style={{color: currentOption.folder.color}}
|
||||
>
|
||||
{currentOption.folder.name} /
|
||||
<i className='fa fa-folder' />
|
||||
<span styleName='idle-label-name'>
|
||||
{currentOption.folder.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
.root
|
||||
position relative
|
||||
border solid 1px transparent
|
||||
line-height 26px
|
||||
vertical-align middle
|
||||
border-radius 2px
|
||||
transition 0.15s
|
||||
user-select none
|
||||
margin-right 10px
|
||||
&:hover
|
||||
background-color $ui-button--hover-backgroundColor
|
||||
|
||||
.root--search, .root--focus
|
||||
@extend .root
|
||||
background-color $ui-noteDetail-backgroundColor = #F4F4F4
|
||||
background-color $ui-noteDetail-backgroundColor = #fff
|
||||
border-color $ui-input--focus-borderColor
|
||||
width 100px
|
||||
width 154px
|
||||
height 30px
|
||||
&:hover
|
||||
border-color $ui-input--focus-borderColor
|
||||
border-color $ui-input--focus-borderColor = #fff
|
||||
|
||||
.idle
|
||||
position relative
|
||||
@@ -24,13 +25,16 @@
|
||||
.idle-label
|
||||
right 20px
|
||||
overflow ellipsis
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
.idle-label-name
|
||||
font-size 14px
|
||||
padding 2px
|
||||
font-size 13px
|
||||
font-weight 600
|
||||
margin-left 4px
|
||||
|
||||
.idle-label-name-surfix
|
||||
font-size 10px
|
||||
font-size 15px
|
||||
color $ui-inactive-text-color
|
||||
margin-left 5px
|
||||
.idle-caret
|
||||
@@ -39,35 +43,37 @@
|
||||
width 20px
|
||||
line-height 34px
|
||||
|
||||
.search
|
||||
absolute top left right bottom
|
||||
line-height 34px
|
||||
|
||||
.search-input
|
||||
vertical-align middle
|
||||
position relative
|
||||
top -2px
|
||||
top 0
|
||||
font-size 14px
|
||||
outline none
|
||||
border none
|
||||
height 20px
|
||||
line-height 20px
|
||||
width 100%
|
||||
background-color transparent
|
||||
padding 0 10px
|
||||
|
||||
.search-optionList
|
||||
position fixed
|
||||
position absolute
|
||||
top 30px
|
||||
max-height 450px
|
||||
min-width 150px
|
||||
overflow auto
|
||||
z-index 200
|
||||
border $ui-border
|
||||
background-color white
|
||||
border-radius 2px
|
||||
padding 10px 6px
|
||||
|
||||
.search-optionList-item
|
||||
width 140px
|
||||
height 34px
|
||||
width 250px
|
||||
display flex
|
||||
align-items center
|
||||
box-sizing border-box
|
||||
padding 0 5px
|
||||
padding 0
|
||||
margin-bottom 10px
|
||||
overflow ellipsis
|
||||
cursor pointer
|
||||
&:hover
|
||||
@@ -81,13 +87,17 @@
|
||||
background-color $ui-button--active-backgroundColor
|
||||
color $ui-button--active-color
|
||||
.search-optionList-item-name
|
||||
border-left solid 2px transparent
|
||||
padding 2px 5px
|
||||
border-left solid 3px transparent
|
||||
padding 6px
|
||||
.search-optionList-item-name-surfix
|
||||
font-size 10px
|
||||
color $ui-inactive-text-color
|
||||
margin-left 5px
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
color $ui-dark-text-color
|
||||
|
||||
@@ -6,9 +6,9 @@ const InfoButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-infoButton'
|
||||
onClick={onClick}
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<i className='fa fa-info infoButton' styleName='info-button' />
|
||||
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
.control-infoButton
|
||||
float right
|
||||
top 10px
|
||||
margin-bottom 10px
|
||||
topBarButtonLight()
|
||||
|
||||
.control-infoPanel
|
||||
position fixed
|
||||
pointer-events none
|
||||
top 50px
|
||||
z-index 200
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
.infoButton
|
||||
padding 0px
|
||||
margin 15px 0
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-infoButton
|
||||
|
||||
@@ -3,50 +3,57 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoPanel.styl'
|
||||
|
||||
const InfoPanel = ({
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type, print
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Storage
|
||||
<div>
|
||||
<p styleName='modification-date'>{updatedAt}</p>
|
||||
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{storageName}
|
||||
|
||||
<hr />
|
||||
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <div styleName='count-wrap'>
|
||||
<div styleName='count-number'>
|
||||
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
||||
<p styleName='infoPanel-sub-count'>Words</p>
|
||||
</div>
|
||||
<div styleName='count-number'>
|
||||
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
|
||||
<p styleName='infoPanel-sub-count'>Letters</p>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Folder
|
||||
}
|
||||
|
||||
{type === 'SNIPPET_NOTE'
|
||||
? ''
|
||||
: <hr />
|
||||
}
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{storageName}</p>
|
||||
<p styleName='infoPanel-sub'>STORAGE</p>
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{folderName}
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{folderName}</p>
|
||||
<p styleName='infoPanel-sub'>FOLDER</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{createdAt}</p>
|
||||
<p styleName='infoPanel-sub'>CREATION DATE</p>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Created
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{createdAt}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Updated
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{updatedAt}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Note Link
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input value={noteLink} onClick={(e) => { e.target.select() }} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input styleName='infoPanel-noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
|
||||
<p styleName='infoPanel-sub'>NOTE LINK</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div id='export-wrap'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||
<i className='fa fa-file-code-o fa-fw' />
|
||||
@@ -58,9 +65,9 @@ const InfoPanel = ({
|
||||
<p>.txt</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--unable'>
|
||||
<i className='fa fa-file-pdf-o fa-fw' />
|
||||
<p>.pdf</p>
|
||||
<button styleName='export--enable' onClick={(e) => print(e)}>
|
||||
<i className='fa fa-print fa-fw' />
|
||||
<p>Print</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,7 +80,11 @@ InfoPanel.propTypes = {
|
||||
updatedAt: PropTypes.string.isRequired,
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
wordCount: PropTypes.number,
|
||||
letterCount: PropTypes.number,
|
||||
type: PropTypes.string.isRequired,
|
||||
print: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(InfoPanel, styles)
|
||||
|
||||
@@ -10,52 +10,81 @@
|
||||
|
||||
.control-infoButton-panel
|
||||
z-index 200
|
||||
margin-top 45px
|
||||
margin-left -210px
|
||||
margin-top 0px
|
||||
right 0
|
||||
position absolute
|
||||
padding 20px 20px 0 20px
|
||||
width 340px
|
||||
padding 20px 25px 0 25px
|
||||
width 300px
|
||||
height 350px
|
||||
overflow auto
|
||||
background-color $ui-noteList-backgroundColor
|
||||
border 1px solid $border-color
|
||||
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||
border-radius 2px
|
||||
|
||||
.modification-date
|
||||
font-size 18px
|
||||
line-height 30px
|
||||
color $ui-text-color
|
||||
|
||||
.modification-date-desc
|
||||
font-size 18px
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
z-index 200
|
||||
margin-top 45px
|
||||
margin-left -230px
|
||||
margin-top 0px
|
||||
right 0px
|
||||
position absolute
|
||||
padding 20px 20px 0 20px
|
||||
width 320px
|
||||
padding 20px 25px 0 25px
|
||||
width 300px
|
||||
background-color $ui-noteList-backgroundColor
|
||||
border 1px solid $border-color
|
||||
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||
border-radius 2px
|
||||
|
||||
.group-section
|
||||
.count-wrap
|
||||
display flex
|
||||
position relative
|
||||
width 100%
|
||||
|
||||
.count-number
|
||||
position relative
|
||||
display block
|
||||
width 50%
|
||||
overflow hidden
|
||||
margin 0
|
||||
padding 0
|
||||
|
||||
.infoPanel-defaul-count
|
||||
font-size 16px
|
||||
line-height 30px
|
||||
font-size 13px
|
||||
|
||||
.group-section-label
|
||||
width 70px
|
||||
height 20px
|
||||
text-align left
|
||||
margin-right 50px
|
||||
font-size 13px
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.group-section-control
|
||||
flex 1
|
||||
font-size 13px
|
||||
color $ui-text-color
|
||||
|
||||
.group-section-control input
|
||||
width 160px
|
||||
height 25px
|
||||
.infoPanel-sub-count
|
||||
font-size 16px
|
||||
color $ui-inactive-text-color
|
||||
padding-bottom 8px
|
||||
|
||||
.group-section-control text
|
||||
.infoPanel-default
|
||||
font-size 14px
|
||||
line-height 30px
|
||||
color $ui-text-color
|
||||
|
||||
.infoPanel-sub
|
||||
font-size 14px
|
||||
color $ui-inactive-text-color
|
||||
padding-bottom 8px
|
||||
|
||||
.infoPanel-noteLink
|
||||
padding-right 5px
|
||||
width 200px
|
||||
height 25px
|
||||
margin-bottom 6px
|
||||
|
||||
.infoPanel-trash
|
||||
color #EA4447
|
||||
font-weight 600
|
||||
font-size 14px
|
||||
width 70px
|
||||
height 25px
|
||||
background-color rgba(226,33,113,0.1)
|
||||
border none
|
||||
outline none
|
||||
@@ -96,20 +125,30 @@
|
||||
body[data-theme="dark"]
|
||||
.control-infoButton-panel
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
border 1px solid $ui-dark-borderColor
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
border 1px solid $ui-dark-borderColor
|
||||
|
||||
.group-section-label
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.group-section-control
|
||||
.modification-date
|
||||
color $ui-dark-text-color
|
||||
|
||||
.group-section-control input
|
||||
background-color alpha($ui-dark-borderColor, 80%)
|
||||
.modification-date-desc
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-defaul-count
|
||||
color $ui-dark-text-color
|
||||
|
||||
.infoPanel-sub-count
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-default
|
||||
color $ui-dark-text-color
|
||||
|
||||
.infoPanel-sub
|
||||
color $ui-inactive-text-color
|
||||
|
||||
.infoPanel-noteLink
|
||||
background-color alpha($ui-dark-borderColor, 60%)
|
||||
color $ui-dark-text-color
|
||||
|
||||
[id=export-wrap]
|
||||
|
||||
@@ -6,37 +6,26 @@ const InfoPanelTrashed = ({
|
||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Storage
|
||||
<div>
|
||||
<p styleName='modification-date'>{updatedAt}</p>
|
||||
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{storageName}
|
||||
|
||||
<hr />
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{storageName}</p>
|
||||
<p styleName='infoPanel-sub'>STORAGE</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
|
||||
<p styleName='infoPanel-sub'>FOLDER</p>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Folder
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<text>Trash</text>{folderName}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Created
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{createdAt}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Updated
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{updatedAt}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p styleName='infoPanel-default'>{createdAt}</p>
|
||||
<p styleName='infoPanel-sub'>CREATION DATE</p>
|
||||
</div>
|
||||
|
||||
<div id='export-wrap'>
|
||||
|
||||
@@ -9,17 +9,19 @@ import FolderSelect from './FolderSelect'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import { hashHistory } from 'react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import markdown from 'browser/lib/markdown'
|
||||
import markdown from 'browser/lib/markdownTextHelper'
|
||||
import StatusBar from '../StatusBar'
|
||||
import _ from 'lodash'
|
||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import TrashButton from './TrashButton'
|
||||
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||
import InfoButton from './InfoButton'
|
||||
import InfoPanel from './InfoPanel'
|
||||
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||
import { formatDate } from 'browser/lib/date-formatter'
|
||||
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||
import striptags from 'striptags'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
@@ -76,7 +78,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
note.content = this.refs.content.value
|
||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||
note.title = markdown.strip(findNoteTitle(note.content))
|
||||
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
|
||||
note.updatedAt = new Date()
|
||||
|
||||
this.setState({
|
||||
@@ -230,7 +232,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
getToggleLockButton () {
|
||||
return this.state.isLocked ? 'fa-lock' : 'fa-unlock'
|
||||
return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg'
|
||||
}
|
||||
|
||||
handleDeleteKeyDown (e) {
|
||||
@@ -255,6 +257,10 @@ class MarkdownNoteDetail extends React.Component {
|
||||
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||
}
|
||||
|
||||
print (e) {
|
||||
ee.emit('print')
|
||||
}
|
||||
|
||||
render () {
|
||||
let { data, config, location } = this.props
|
||||
let { note } = this.state
|
||||
@@ -280,7 +286,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
@@ -297,10 +303,6 @@ class MarkdownNoteDetail extends React.Component {
|
||||
|
||||
const detailTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<StarButton styleName='info-left-button'
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
<div styleName='info-left-top'>
|
||||
<FolderSelect styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
@@ -320,31 +322,38 @@ class MarkdownNoteDetail extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
|
||||
<StarButton
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
|
||||
{(() => {
|
||||
const faClassName = `fa ${this.getToggleLockButton()}`
|
||||
const imgSrc = `${this.getToggleLockButton()}`
|
||||
const lockButtonComponent =
|
||||
<button styleName='control-lockButton'
|
||||
onFocus={(e) => this.handleFocus(e)}
|
||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
||||
>
|
||||
<i className={faClassName} styleName='lock-button' />
|
||||
<span styleName='control-lockButton-tooltip'>
|
||||
{this.state.isLocked ? 'Unlock' : 'Lock'}
|
||||
</span>
|
||||
<img styleName='iconInfo' src={imgSrc} />
|
||||
</button>
|
||||
|
||||
return (
|
||||
this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||
)
|
||||
})()}
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
|
||||
<button styleName='control-fullScreenButton'
|
||||
onMouseDown={(e) => this.handleFullScreenButton(e)}
|
||||
>
|
||||
<i className='fa fa-expand' styleName='fullScreen-button' />
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
|
||||
</button>
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
@@ -353,6 +362,10 @@ class MarkdownNoteDetail extends React.Component {
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
wordCount={note.content.split(' ').length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
print={this.print}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,50 +3,48 @@
|
||||
|
||||
.root
|
||||
absolute top right bottom
|
||||
border-width 0 0 1px
|
||||
border-style solid
|
||||
border-color $ui-borderColor
|
||||
border-left 1px solid alpha(#DEDEDE, 60%)
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
box-shadow $note-detail-box-shadow
|
||||
box-shadow none
|
||||
padding 20px 40px
|
||||
|
||||
.lock-button
|
||||
padding-bottom 3px
|
||||
|
||||
.control-lockButton
|
||||
top 160px
|
||||
margin-bottom 10px
|
||||
topBarButtonLight()
|
||||
|
||||
.control-lockButton-tooltip
|
||||
tooltip()
|
||||
position fixed
|
||||
pointer-events none
|
||||
top 50px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
.trashed-infopanel
|
||||
top 40px
|
||||
position relative
|
||||
|
||||
.control-fullScreenButton
|
||||
float right
|
||||
padding 0 0 2px 0
|
||||
top 80px
|
||||
topBarButtonLight()
|
||||
|
||||
.body
|
||||
absolute left right
|
||||
left $note-detail-left-margin
|
||||
right $note-detail-right-margin
|
||||
left 0
|
||||
right 0
|
||||
top $info-height + $info-margin-under-border
|
||||
bottom $statusBar-height
|
||||
|
||||
max-width 600px
|
||||
margin 0 auto
|
||||
.body-noteEditor
|
||||
absolute top bottom left right
|
||||
|
||||
body[data-theme="white"]
|
||||
.root
|
||||
box-shadow $note-detail-box-shadow
|
||||
border none
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
border-color $ui-dark-borderColor
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
box-shadow none
|
||||
border none
|
||||
|
||||
.control-lockButton
|
||||
topBarButtonDark()
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
@import('DetailVars')
|
||||
|
||||
$info-height = 60px
|
||||
$info-margin-under-border = 27px
|
||||
$info-height = 50px
|
||||
$info-margin-under-border = 30px
|
||||
|
||||
.info
|
||||
absolute top left right
|
||||
left $note-detail-left-margin
|
||||
right $note-detail-right-margin
|
||||
left 0
|
||||
right 0
|
||||
height $info-height
|
||||
border-bottom $ui-border
|
||||
border-bottom 1px solid #eee
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
width 100%
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
.info-left
|
||||
float left
|
||||
padding 0 5px
|
||||
margin 0px 2px
|
||||
.info-left-top
|
||||
display inline-block
|
||||
height $info-height
|
||||
line-height $info-height
|
||||
padding 0 10px
|
||||
width 100%
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
|
||||
.info-left-top-folderSelect
|
||||
padding 0 3px
|
||||
height 34px
|
||||
line-height 26px
|
||||
display flex
|
||||
align-items center
|
||||
justify-content center
|
||||
border-radius 3px
|
||||
|
||||
.info-left-button
|
||||
width 34px
|
||||
height 34px
|
||||
@@ -48,18 +46,18 @@ $info-margin-under-border = 27px
|
||||
|
||||
.info-right
|
||||
position absolute
|
||||
right 0
|
||||
top 0
|
||||
background $ui-noteDetail-backgroundColor
|
||||
right 40px
|
||||
top 60px
|
||||
bottom 1px
|
||||
padding-left 30px
|
||||
z-index 101
|
||||
|
||||
.undo-button
|
||||
width 34px
|
||||
height 34px
|
||||
border-radius 17px
|
||||
font-size 14px
|
||||
margin 15px 7px
|
||||
margin 5px 0px
|
||||
border none
|
||||
color $ui-button-color
|
||||
display flex
|
||||
@@ -72,6 +70,7 @@ $info-margin-under-border = 27px
|
||||
border-color $ui-button--active-backgroundColor
|
||||
&:hover
|
||||
background-color alpha($ui-button--hover-backgroundColor, 60%)
|
||||
transition 0.2s
|
||||
.control-lockButton-tooltip
|
||||
opacity 1
|
||||
|
||||
|
||||
19
browser/main/Detail/PermanentDeleteButton.js
Normal file
19
browser/main/Detail/PermanentDeleteButton.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TrashButton.styl'
|
||||
|
||||
const PermanentDeleteButton = ({
|
||||
onClick
|
||||
}) => (
|
||||
<button styleName='control-trashButton--in-trash'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||
</button>
|
||||
)
|
||||
|
||||
PermanentDeleteButton.propTypes = {
|
||||
onClick: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(PermanentDeleteButton, styles)
|
||||
@@ -18,6 +18,7 @@ import _ from 'lodash'
|
||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import TrashButton from './TrashButton'
|
||||
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||
import InfoButton from './InfoButton'
|
||||
import InfoPanel from './InfoPanel'
|
||||
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||
@@ -266,12 +267,17 @@ class SnippetNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
renameSnippetByIndex (index, name) {
|
||||
let snippets = this.state.note.snippets.slice()
|
||||
const snippets = this.state.note.snippets.slice()
|
||||
snippets[index].name = name
|
||||
let syntax = CodeMirror.findModeByFileName(name.trim())
|
||||
let mode = syntax != null ? syntax.name : null
|
||||
if (mode != null) snippets[index].mode = mode
|
||||
this.state.note.snippets = snippets
|
||||
const syntax = CodeMirror.findModeByFileName(name.trim())
|
||||
const mode = syntax != null ? syntax.name : null
|
||||
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: this.state.note
|
||||
@@ -284,13 +290,17 @@ class SnippetNoteDetail extends React.Component {
|
||||
return (e) => {
|
||||
let snippets = this.state.note.snippets.slice()
|
||||
snippets[index].mode = name
|
||||
this.state.note.snippets = snippets
|
||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||
|
||||
this.setState({
|
||||
note: this.state.note
|
||||
}, () => {
|
||||
this.save()
|
||||
})
|
||||
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SELECT_LANG', {
|
||||
name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +308,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
return (e) => {
|
||||
let snippets = this.state.note.snippets.slice()
|
||||
snippets[index].content = this.refs['code-' + index].value
|
||||
this.state.note.snippets = snippets
|
||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||
this.setState({
|
||||
note: this.state.note
|
||||
}, () => {
|
||||
@@ -557,7 +567,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
@@ -574,10 +584,6 @@ class SnippetNoteDetail extends React.Component {
|
||||
|
||||
const detailTopBar = <div styleName='info'>
|
||||
<div styleName='info-left'>
|
||||
<StarButton styleName='info-left-button'
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
<div styleName='info-left-top'>
|
||||
<FolderSelect styleName='info-left-top-folderSelect'
|
||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||
@@ -594,15 +600,21 @@ class SnippetNoteDetail extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<button styleName='control-fullScreenButton'
|
||||
onMouseDown={(e) => this.handleFullScreenButton(e)}
|
||||
>
|
||||
<i className='fa fa-expand' styleName='fullScreen-button' />
|
||||
</button>
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
|
||||
<StarButton
|
||||
onClick={(e) => this.handleStarButtonClick(e)}
|
||||
isActive={note.isStarred}
|
||||
/>
|
||||
|
||||
<button styleName='control-fullScreenButton'
|
||||
onMouseDown={(e) => this.handleFullScreenButton(e)}>
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
|
||||
</button>
|
||||
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
@@ -611,6 +623,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.showWarning}
|
||||
exportAsTxt={this.showWarning}
|
||||
type={note.type}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,23 +3,21 @@
|
||||
|
||||
.root
|
||||
absolute top bottom right
|
||||
border-width 0 0 1px
|
||||
border-style solid
|
||||
border-color $ui-borderColor
|
||||
border-left 1px solid alpha(#DEDEDE, 60%)
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
box-shadow $note-detail-box-shadow
|
||||
box-shadow none
|
||||
|
||||
.body
|
||||
absolute left right
|
||||
left $note-detail-left-margin
|
||||
right $note-detail-right-margin
|
||||
left $snippet-note-detail-left-margin
|
||||
right $snippet-note-detail-right-margin
|
||||
top $info-height + $info-margin-under-border
|
||||
bottom $statusBar-height
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
|
||||
.body .description
|
||||
absolute top left right
|
||||
height 80px
|
||||
height 50px
|
||||
|
||||
.body .description textarea
|
||||
outline none
|
||||
@@ -27,14 +25,14 @@
|
||||
height 100%
|
||||
width 100%
|
||||
resize none
|
||||
border none
|
||||
padding 10px
|
||||
border 1px solid $ui-borderColor
|
||||
padding 2px 5px
|
||||
line-height 1.6
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
|
||||
.tabList
|
||||
absolute left right
|
||||
top 80px
|
||||
top 55px
|
||||
height 30px
|
||||
display flex
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
@@ -45,35 +43,42 @@
|
||||
overflow hidden
|
||||
|
||||
.tabList .plusButton
|
||||
navButtonColor()
|
||||
navWhiteButtonColor()
|
||||
width 30px
|
||||
|
||||
.tabView
|
||||
absolute left right bottom
|
||||
top 130px
|
||||
top 100px
|
||||
|
||||
.tabView-content
|
||||
absolute top left right bottom
|
||||
|
||||
.override
|
||||
absolute bottom left
|
||||
bottom 1px
|
||||
left 60px
|
||||
height 23px
|
||||
z-index 1
|
||||
z-index 101
|
||||
button
|
||||
navButtonColor()
|
||||
height 24px
|
||||
padding 0 6px
|
||||
&:hover
|
||||
color $ui-active-color
|
||||
&:active .update-icon
|
||||
color white
|
||||
color $ui-active-color
|
||||
|
||||
.control-fullScreenButton
|
||||
float right
|
||||
padding 0 0 2px 0
|
||||
top 80px
|
||||
margin-bottom 10px
|
||||
topBarButtonLight()
|
||||
|
||||
body[data-theme="white"]
|
||||
.root
|
||||
box-shadow $note-detail-box-shadow
|
||||
border none
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
border-color $ui-dark-borderColor
|
||||
border none
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
box-shadow none
|
||||
|
||||
@@ -83,6 +88,7 @@ body[data-theme="dark"]
|
||||
.body .description textarea
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
color $ui-dark-text-color
|
||||
border 1px solid $ui-dark-borderColor
|
||||
|
||||
.tabList
|
||||
background-color $ui-button--active-backgroundColor
|
||||
|
||||
@@ -47,10 +47,10 @@ class StarButton extends React.Component {
|
||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
||||
onClick={this.props.onClick}
|
||||
>
|
||||
<i styleName='icon'
|
||||
className={this.state.isActive || this.props.isActive
|
||||
? 'fa fa-star'
|
||||
: 'fa fa-star-o'
|
||||
<img styleName='icon'
|
||||
src={this.state.isActive || this.props.isActive
|
||||
? '../resources/icon/icon-starred.svg'
|
||||
: '../resources/icon/icon-star.svg'
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -1,47 +1,24 @@
|
||||
.root
|
||||
left 7px
|
||||
top 0
|
||||
padding 0
|
||||
color alpha($ui-favorite-star-button-color, 60%)
|
||||
top 45px
|
||||
topBarButtonLight()
|
||||
&:hover
|
||||
transition 0.15s
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-favorite-star-button-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-favorite-star-button-color
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
|
||||
.root--active
|
||||
@extend .root
|
||||
transition 0.15s
|
||||
color $ui-favorite-star-button-color
|
||||
&:hover
|
||||
transition 0.15s
|
||||
color $ui-favorite-star-button-color
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
&:active
|
||||
transition 0.15s
|
||||
color $ui-favorite-star-button-color
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
|
||||
.icon
|
||||
transition transform 0.15s
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
topBarButtonDark()
|
||||
&:hover
|
||||
transition 0.15s
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
color $ui-favorite-star-button-color
|
||||
&:active
|
||||
transition 0.15s
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
color $ui-favorite-star-button-color
|
||||
|
||||
.root--active
|
||||
@extend .root
|
||||
color $ui-favorite-star-button-color
|
||||
&:hover
|
||||
transition 0.15s
|
||||
color $ui-favorite-star-button-color
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
transition 0.2s
|
||||
color alpha($ui-favorite-star-button-color, 0.6)
|
||||
@@ -113,7 +113,7 @@ class TagSelect extends React.Component {
|
||||
<button styleName='tag-removeButton'
|
||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)}
|
||||
>
|
||||
<i className='fa fa-times fa-fw tag-removeButton-icon' />
|
||||
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.root
|
||||
display inline-block
|
||||
display flex
|
||||
align-items center
|
||||
user-select none
|
||||
height 23px
|
||||
vertical-align middle
|
||||
width 300px
|
||||
width 100%
|
||||
overflow-x scroll
|
||||
white-space nowrap
|
||||
|
||||
@@ -11,51 +11,42 @@
|
||||
display none
|
||||
|
||||
.tag
|
||||
display inline-block
|
||||
margin 1px 3px
|
||||
padding 0
|
||||
height 20px
|
||||
background-color alpha($ui-tag-backgroundColor, 10%)
|
||||
border-radius 3px
|
||||
overflow hidden
|
||||
display flex
|
||||
align-items center
|
||||
margin 0px 2px
|
||||
padding 2px 4px
|
||||
background-color alpha($ui-tag-backgroundColor, 3%)
|
||||
border-radius 4px
|
||||
position relative
|
||||
clearfix()
|
||||
|
||||
.tag-removeButton
|
||||
float right
|
||||
height 20px
|
||||
width 18px
|
||||
margin 0
|
||||
padding 0
|
||||
border-style solid
|
||||
border-width 0
|
||||
border-radius 20px
|
||||
line-height 18px
|
||||
background-color transparent
|
||||
color $ui-button-color
|
||||
position absolute
|
||||
right 6px
|
||||
|
||||
.tag-removeButton-icon
|
||||
width 5px
|
||||
padding-right 4px
|
||||
|
||||
.tag-label
|
||||
font-size 11px
|
||||
font-weight 600
|
||||
color: alpha($ui-text-color, 80%)
|
||||
float left
|
||||
height 20px
|
||||
line-height 20px
|
||||
padding 0 6px
|
||||
font-size 13px
|
||||
color: $ui-text-color
|
||||
padding 4px 16px 4px 8px
|
||||
|
||||
.newTag
|
||||
display inline-block
|
||||
margin 2px 0 15px 2px
|
||||
vertical-align middle
|
||||
height 18px
|
||||
box-sizing borde-box
|
||||
box-sizing border-box
|
||||
border none
|
||||
background-color transparent
|
||||
outline none
|
||||
padding 0 4px
|
||||
font-size 13px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.tag
|
||||
|
||||
@@ -8,7 +8,7 @@ const TrashButton = ({
|
||||
<button styleName='control-trashButton'
|
||||
onClick={(e) => onClick(e)}
|
||||
>
|
||||
<i className='fa fa-trash trashButton' styleName='info-button' />
|
||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||
</button>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
.control-trashButton
|
||||
float right
|
||||
top 120px
|
||||
margin-bottom 10px
|
||||
topBarButtonLight()
|
||||
|
||||
.control-trashButton--in-trash
|
||||
top 60px
|
||||
topBarButtonLight()
|
||||
|
||||
.trashButton
|
||||
padding 0px
|
||||
margin 15px 0
|
||||
|
||||
body[data-theme="dark"]
|
||||
.control-trashButton
|
||||
|
||||
@@ -11,15 +11,11 @@ import _ from 'lodash'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import modal from 'browser/main/lib/modal'
|
||||
import InitModal from 'browser/main/modals/InitModal'
|
||||
import mixpanel from 'browser/main/lib/mixpanel'
|
||||
import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
|
||||
function focused () {
|
||||
mixpanel.track('MAIN_FOCUSED')
|
||||
}
|
||||
|
||||
class Main extends React.Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
@@ -56,6 +52,8 @@ class Main extends React.Component {
|
||||
|
||||
if (config.ui.theme === 'dark') {
|
||||
document.body.setAttribute('data-theme', 'dark')
|
||||
} else if (config.ui.theme === 'white') {
|
||||
document.body.setAttribute('data-theme', 'white')
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'default')
|
||||
}
|
||||
@@ -75,11 +73,9 @@ class Main extends React.Component {
|
||||
})
|
||||
|
||||
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
||||
window.addEventListener('focus', focused)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('focus', focused)
|
||||
eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
|
||||
}
|
||||
|
||||
@@ -172,8 +168,8 @@ class Main extends React.Component {
|
||||
}
|
||||
|
||||
hideLeftLists (noteDetail, noteList, mainBody) {
|
||||
this.state.noteDetailWidth = noteDetail.style.left
|
||||
this.state.mainBodyWidth = mainBody.style.left
|
||||
this.setState({noteDetailWidth: noteDetail.style.left})
|
||||
this.setState({mainBodyWidth: mainBody.style.left})
|
||||
noteDetail.style.left = '0px'
|
||||
mainBody.style.left = '0px'
|
||||
noteList.style.display = 'none'
|
||||
@@ -188,6 +184,9 @@ class Main extends React.Component {
|
||||
render () {
|
||||
let { config } = this.props
|
||||
|
||||
// the width of the navigation bar when it is folded/collapsed
|
||||
const foldedNavigationWidth = 44
|
||||
|
||||
return (
|
||||
<div
|
||||
className='Main'
|
||||
@@ -216,7 +215,7 @@ class Main extends React.Component {
|
||||
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
||||
id='main-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}}
|
||||
{..._.pick(this.props, [
|
||||
|
||||
73
browser/main/NewNoteButton/NewNoteButton.styl
Normal file
73
browser/main/NewNoteButton/NewNoteButton.styl
Normal file
@@ -0,0 +1,73 @@
|
||||
.root
|
||||
position relative
|
||||
background-color $ui-noteList-backgroundColor
|
||||
height $topBar-height - 1
|
||||
margin-left: auto;
|
||||
width: 64px;
|
||||
|
||||
.root--expanded
|
||||
@extend .root
|
||||
|
||||
$control-height = 34px
|
||||
|
||||
.control
|
||||
position absolute
|
||||
top 13px
|
||||
right 7px
|
||||
height $control-height
|
||||
display flex
|
||||
|
||||
.control-newNoteButton
|
||||
display block
|
||||
width 32px
|
||||
height $control-height - 2
|
||||
navButtonColor()
|
||||
font-size 16px
|
||||
line-height 28px
|
||||
padding 0
|
||||
&:active
|
||||
border-color $ui-button--active-backgroundColor
|
||||
&:hover .control-newNoteButton-tooltip
|
||||
opacity 1
|
||||
|
||||
.control-newNoteButton-tooltip
|
||||
tooltip()
|
||||
position absolute
|
||||
pointer-events none
|
||||
top 26px
|
||||
right -43px
|
||||
width 124px
|
||||
z-index 200
|
||||
padding 5px
|
||||
line-height normal
|
||||
border-radius 2px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="white"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
.control-newNoteButton
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
|
||||
.control
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.control-newNoteButton
|
||||
color $ui-inactive-text-color
|
||||
border-color $ui-dark-borderColor
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
&:hover
|
||||
transition 0.15s
|
||||
color $ui-dark-text-color
|
||||
&:active
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
border-color $ui-dark-button--active-backgroundColor
|
||||
|
||||
.control-newNoteButton-tooltip
|
||||
darkTooltip()
|
||||
106
browser/main/NewNoteButton/index.js
Normal file
106
browser/main/NewNoteButton/index.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './NewNoteButton.styl'
|
||||
import _ from 'lodash'
|
||||
import modal from 'browser/main/lib/modal'
|
||||
import NewNoteModal from 'browser/main/modals/NewNoteModal'
|
||||
import { hashHistory } from 'react-router'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
|
||||
const OSX = window.process.platform === 'darwin'
|
||||
|
||||
class NewNoteButton extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
}
|
||||
|
||||
this.newNoteHandler = () => {
|
||||
this.handleNewNoteButtonClick()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
eventEmitter.on('top:new-note', this.newNoteHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
eventEmitter.off('top:new-note', this.newNoteHandler)
|
||||
}
|
||||
|
||||
handleNewNoteButtonClick (e) {
|
||||
const { config, location, dispatch } = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
modal.open(NewNoteModal, {
|
||||
storage: storage.key,
|
||||
folder: folder.key,
|
||||
dispatch,
|
||||
location
|
||||
})
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
const { data, params } = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
|
||||
// Find first storage
|
||||
if (storage == null) {
|
||||
for (let kv of data.storageMap) {
|
||||
storage = kv[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (storage == null) this.showMessageBox('No storage to create a note')
|
||||
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
|
||||
if (folder == null) this.showMessageBox('No folder to create a note')
|
||||
|
||||
return {
|
||||
storage,
|
||||
folder
|
||||
}
|
||||
}
|
||||
|
||||
showMessageBox (message) {
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: message,
|
||||
buttons: ['OK']
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const { config, style } = this.props
|
||||
return (
|
||||
<div className='NewNoteButton'
|
||||
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
||||
style={style}
|
||||
>
|
||||
<div styleName='control'>
|
||||
<button styleName='control-newNoteButton'
|
||||
onClick={(e) => this.handleNewNoteButtonClick(e)}>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
|
||||
<span styleName='control-newNoteButton-tooltip'>
|
||||
Make a Note {OSX ? '⌘' : '^'} + n
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
NewNoteButton.propTypes = {
|
||||
dispatch: PropTypes.func,
|
||||
config: PropTypes.shape({
|
||||
isSideNavFolded: PropTypes.bool
|
||||
})
|
||||
}
|
||||
|
||||
export default CSSModules(NewNoteButton, styles)
|
||||
@@ -21,14 +21,14 @@ $control-height = 30px
|
||||
|
||||
.control-sortBy-select
|
||||
appearance: none;
|
||||
margin-left 3px
|
||||
margin-left 5px
|
||||
color $ui-inactive-text-color
|
||||
padding 0
|
||||
border none
|
||||
background-color transparent
|
||||
outline none
|
||||
cursor pointer
|
||||
font-size 11px
|
||||
font-size 12px
|
||||
&:hover
|
||||
transition 0.2s
|
||||
color $ui-text-color
|
||||
@@ -59,6 +59,13 @@ $control-height = 30px
|
||||
top $control-height
|
||||
overflow auto
|
||||
|
||||
body[data-theme="white"]
|
||||
.root
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
.control
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
@@ -13,6 +13,8 @@ import fs from 'fs'
|
||||
import { hashHistory } from 'react-router'
|
||||
import markdown from 'browser/lib/markdown'
|
||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||
import stripgtags from 'striptags'
|
||||
import store from 'browser/main/store'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { Menu, MenuItem, dialog } = remote
|
||||
@@ -51,6 +53,8 @@ class NoteList extends React.Component {
|
||||
|
||||
this.state = {
|
||||
}
|
||||
|
||||
this.contextNotes = []
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
@@ -89,6 +93,7 @@ class NoteList extends React.Component {
|
||||
|
||||
if (this.notes.length > 0 && location.query.key == null) {
|
||||
let { router } = this.context
|
||||
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
|
||||
router.replace({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
@@ -100,9 +105,7 @@ class NoteList extends React.Component {
|
||||
|
||||
// Auto scroll
|
||||
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
|
||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note != null && note.storage + '-' + note.key === location.query.key
|
||||
})
|
||||
const targetIndex = this.getTargetIndex()
|
||||
if (targetIndex > -1) {
|
||||
let list = this.refs.list
|
||||
let item = list.childNodes[targetIndex]
|
||||
@@ -128,9 +131,7 @@ class NoteList extends React.Component {
|
||||
let { router } = this.context
|
||||
let { location } = this.props
|
||||
|
||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note.storage + '-' + note.key === location.query.key
|
||||
})
|
||||
let targetIndex = this.getTargetIndex()
|
||||
|
||||
if (targetIndex === 0) {
|
||||
return
|
||||
@@ -153,9 +154,7 @@ class NoteList extends React.Component {
|
||||
let { router } = this.context
|
||||
let { location } = this.props
|
||||
|
||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note.storage + '-' + note.key === location.query.key
|
||||
})
|
||||
let targetIndex = this.getTargetIndex()
|
||||
|
||||
if (targetIndex === this.notes.length - 1) {
|
||||
targetIndex = 0
|
||||
@@ -183,9 +182,7 @@ class NoteList extends React.Component {
|
||||
const { router } = this.context
|
||||
const { location } = this.props
|
||||
|
||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note.storage + '-' + note.key === noteHash
|
||||
})
|
||||
let targetIndex = this.getTargetIndex()
|
||||
|
||||
if (targetIndex < 0) targetIndex = 0
|
||||
|
||||
@@ -232,49 +229,74 @@ class NoteList extends React.Component {
|
||||
let { data, params, location } = this.props
|
||||
let { router } = this.context
|
||||
|
||||
if (location.pathname.match(/\/home/)) {
|
||||
return data.noteMap.map((note) => note)
|
||||
if (location.pathname.match(/\/home/) || location.pathname.match(/\alltags/)) {
|
||||
const allNotes = data.noteMap.map((note) => note)
|
||||
this.contextNotes = allNotes
|
||||
return allNotes
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/starred/)) {
|
||||
return data.starredSet.toJS()
|
||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
const starredNotes = data.starredSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
this.contextNotes = starredNotes
|
||||
return starredNotes
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/searched/)) {
|
||||
const searchInputText = document.getElementsByClassName('searchInput')[0].value
|
||||
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/)) {
|
||||
return data.trashedSet.toJS()
|
||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
this.contextNotes = trashedNotes
|
||||
return trashedNotes
|
||||
}
|
||||
|
||||
let storageKey = params.storageKey
|
||||
let folderKey = params.folderKey
|
||||
let storage = data.storageMap.get(storageKey)
|
||||
if (storage == null) return []
|
||||
|
||||
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))
|
||||
if (location.pathname.match(/\/tags/)) {
|
||||
return data.noteMap.map(note => {
|
||||
return note
|
||||
}).filter(note => {
|
||||
return note.tags.includes(params.tagname)
|
||||
})
|
||||
}
|
||||
|
||||
let folderNoteKeyList = data.folderNoteMap
|
||||
.get(storage.key + '-' + folder.key)
|
||||
return this.getContextNotes()
|
||||
}
|
||||
|
||||
return folderNoteKeyList != null
|
||||
? folderNoteKeyList
|
||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||
: []
|
||||
// get notes in the current folder
|
||||
getContextNotes () {
|
||||
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 = []
|
||||
const unpinnedNotes = []
|
||||
|
||||
unorderedNotes.forEach((note) => {
|
||||
if (note.isPinned) {
|
||||
pinnedNotes.push(note)
|
||||
} else {
|
||||
unpinnedNotes.push(note)
|
||||
}
|
||||
})
|
||||
|
||||
return pinnedNotes.concat(unpinnedNotes)
|
||||
}
|
||||
|
||||
handleNoteClick (e, uniqueKey) {
|
||||
@@ -318,10 +340,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
alertIfSnippet () {
|
||||
let { location } = this.props
|
||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return `${note.storage}-${note.key}` === location.query.key
|
||||
})
|
||||
const targetIndex = this.getTargetIndex()
|
||||
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
@@ -337,6 +356,58 @@ class NoteList extends React.Component {
|
||||
e.dataTransfer.setData('note', noteData)
|
||||
}
|
||||
|
||||
handleNoteContextMenu (e, uniqueKey) {
|
||||
const { location } = this.props
|
||||
const note = this.notes.find((note) => {
|
||||
const noteKey = `${note.storage}-${note.key}`
|
||||
return noteKey === uniqueKey
|
||||
})
|
||||
|
||||
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
|
||||
const deleteLabel = 'Delete Note'
|
||||
|
||||
const menu = new Menu()
|
||||
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
|
||||
menu.append(new MenuItem({
|
||||
label: pinLabel,
|
||||
click: (e) => this.pinToTop(e, uniqueKey)
|
||||
}))
|
||||
}
|
||||
menu.append(new MenuItem({
|
||||
label: deleteLabel,
|
||||
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})
|
||||
|
||||
this.handleNoteClick(e, uniqueKey)
|
||||
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 () {
|
||||
const { dispatch, location } = this.props
|
||||
|
||||
@@ -347,14 +418,23 @@ class NoteList extends React.Component {
|
||||
properties: ['openFile', 'multiSelections']
|
||||
}
|
||||
|
||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return note !== null && `${note.storage}-${note.key}` === location.query.key
|
||||
})
|
||||
|
||||
const storageKey = this.notes[targetIndex].storage
|
||||
const folderKey = this.notes[targetIndex].folder
|
||||
|
||||
dialog.showOpenDialog(remote.getCurrentWindow(), options, (filepaths) => {
|
||||
this.addNotesFromFiles(filepaths)
|
||||
})
|
||||
}
|
||||
|
||||
handleDrop (e) {
|
||||
e.preventDefault()
|
||||
const { location } = this.props
|
||||
const filepaths = Array.from(e.dataTransfer.files).map(file => { return file.path })
|
||||
if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths)
|
||||
}
|
||||
|
||||
// Add notes to the current folder
|
||||
addNotesFromFiles (filepaths) {
|
||||
const { dispatch, location } = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
if (filepaths === undefined) return
|
||||
filepaths.forEach((filepath) => {
|
||||
fs.readFile(filepath, (err, data) => {
|
||||
@@ -362,11 +442,11 @@ class NoteList extends React.Component {
|
||||
const content = data.toString()
|
||||
const newNote = {
|
||||
content: content,
|
||||
folder: folderKey,
|
||||
folder: folder.key,
|
||||
title: markdown.strip(findNoteTitle(content)),
|
||||
type: 'MARKDOWN_NOTE'
|
||||
}
|
||||
dataApi.createNote(storageKey, newNote)
|
||||
dataApi.createNote(storage.key, newNote)
|
||||
.then((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
@@ -379,6 +459,43 @@ 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
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
const { data, params } = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
|
||||
// Find first storage
|
||||
if (storage == null) {
|
||||
for (let kv of data.storageMap) {
|
||||
storage = kv[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (storage == null) this.showMessageBox('No storage for importing note(s)')
|
||||
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
|
||||
if (folder == null) this.showMessageBox('No folder for importing note(s)')
|
||||
|
||||
return {
|
||||
storage,
|
||||
folder
|
||||
}
|
||||
}
|
||||
|
||||
showMessageBox (message) {
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: message,
|
||||
buttons: ['OK']
|
||||
})
|
||||
}
|
||||
|
||||
@@ -389,13 +506,33 @@ class NoteList extends React.Component {
|
||||
: config.sortBy === 'ALPHABETICAL'
|
||||
? sortByAlphabetical
|
||||
: sortByUpdatedAt
|
||||
this.notes = notes = this.getNotes()
|
||||
.sort(sortFunc)
|
||||
.filter((note) => {
|
||||
const sortedNotes = location.pathname.match(/\/home|\/starred|\/trash/)
|
||||
? this.getNotes().sort(sortFunc)
|
||||
: this.sortByPin(this.getNotes().sort(sortFunc))
|
||||
this.notes = notes = sortedNotes.filter((note) => {
|
||||
// this is for the trash box
|
||||
if (note.isTrashed !== true || location.pathname === '/trashed') return true
|
||||
})
|
||||
|
||||
moment.locale('en', {
|
||||
relativeTime: {
|
||||
future: 'in %s',
|
||||
past: '%s ago',
|
||||
s: '%ds',
|
||||
ss: '%ss',
|
||||
m: '1m',
|
||||
mm: '%dm',
|
||||
h: 'an hour',
|
||||
hh: '%dh',
|
||||
d: '1d',
|
||||
dd: '%dd',
|
||||
M: '1M',
|
||||
MM: '%dM',
|
||||
y: '1Y',
|
||||
yy: '%dY'
|
||||
}
|
||||
})
|
||||
|
||||
let noteList = notes
|
||||
.map(note => {
|
||||
if (note == null) {
|
||||
@@ -407,7 +544,7 @@ class NoteList extends React.Component {
|
||||
const dateDisplay = moment(
|
||||
config.sortBy === 'CREATED_AT'
|
||||
? note.createdAt : note.updatedAt
|
||||
).fromNow()
|
||||
).fromNow('D')
|
||||
const key = `${note.storage}-${note.key}`
|
||||
|
||||
if (isDefault) {
|
||||
@@ -417,8 +554,10 @@ class NoteList extends React.Component {
|
||||
note={note}
|
||||
dateDisplay={dateDisplay}
|
||||
key={key}
|
||||
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
|
||||
handleNoteClick={this.handleNoteClick.bind(this)}
|
||||
handleDragStart={this.handleDragStart.bind(this)}
|
||||
pathname={location.pathname}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -428,6 +567,7 @@ class NoteList extends React.Component {
|
||||
isActive={isActive}
|
||||
note={note}
|
||||
key={key}
|
||||
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
|
||||
handleNoteClick={this.handleNoteClick.bind(this)}
|
||||
handleDragStart={this.handleDragStart.bind(this)}
|
||||
/>
|
||||
@@ -438,16 +578,17 @@ class NoteList extends React.Component {
|
||||
<div className='NoteList'
|
||||
styleName='root'
|
||||
style={this.props.style}
|
||||
onDrop={(e) => this.handleDrop(e)}
|
||||
>
|
||||
<div styleName='control'>
|
||||
<div styleName='control-sortBy'>
|
||||
<i className='fa fa-bolt' />
|
||||
<i className='fa fa-angle-down' />
|
||||
<select styleName='control-sortBy-select'
|
||||
value={config.sortBy}
|
||||
onChange={(e) => this.handleSortByChange(e)}
|
||||
>
|
||||
<option value='UPDATED_AT'>Last Updated</option>
|
||||
<option value='CREATED_AT'>Creation Time</option>
|
||||
<option value='UPDATED_AT'>Updated</option>
|
||||
<option value='CREATED_AT'>Created</option>
|
||||
<option value='ALPHABETICAL'>Alphabetically</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -458,7 +599,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
|
||||
>
|
||||
<i className='fa fa-th-large' />
|
||||
<img styleName='iconTag' src='../resources/icon/icon-column.svg' />
|
||||
</button>
|
||||
<button styleName={config.listStyle === 'SMALL'
|
||||
? 'control-button--active'
|
||||
@@ -466,7 +607,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
|
||||
>
|
||||
<i className='fa fa-list-ul' />
|
||||
<img styleName='iconTag' src='../resources/icon/icon-column-list.svg' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,62 +1,84 @@
|
||||
.root
|
||||
absolute top left bottom
|
||||
width $sideNav-width
|
||||
background-color #f9f9f9
|
||||
background-color #2E3235
|
||||
user-select none
|
||||
color $ui-text-color
|
||||
height: 100vh
|
||||
display: flex
|
||||
flex-direction column
|
||||
|
||||
.top
|
||||
height $topBar-height
|
||||
padding-bottom 15px
|
||||
|
||||
.top-menu
|
||||
.top-menu-preference
|
||||
navButtonColor()
|
||||
height $topBar-height
|
||||
padding 0 15px
|
||||
font-size 12px
|
||||
width 100%
|
||||
text-align left
|
||||
position absolute
|
||||
top 22px
|
||||
right 10px
|
||||
width 2em
|
||||
background-color transparent
|
||||
&:hover
|
||||
color $ui-text-color
|
||||
color $ui-button-default--active-backgroundColor
|
||||
background-color transparent
|
||||
&:active, &:active:hover
|
||||
color $ui-text-color
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
color $ui-button-default--active-backgroundColor
|
||||
|
||||
.switch-buttons
|
||||
background-color transparent
|
||||
border 0
|
||||
margin 24px auto 4px 14px
|
||||
display flex
|
||||
text-align center
|
||||
|
||||
.non-active-button
|
||||
color $ui-inactive-text-color
|
||||
font-size 16px
|
||||
border 0
|
||||
background-color transparent
|
||||
transition 0.2s
|
||||
display flex
|
||||
text-align center
|
||||
margin-right 4px;
|
||||
&:hover
|
||||
color alpha(#239F86, 60%)
|
||||
|
||||
.active-button
|
||||
@extend .non-active-button
|
||||
color $ui-button-default--active-backgroundColor
|
||||
|
||||
.top-menu-label
|
||||
margin-left 5px
|
||||
overflow ellipsis
|
||||
opacity 0
|
||||
|
||||
.storageList
|
||||
absolute left right
|
||||
bottom 37px
|
||||
top 160px
|
||||
.tabBody
|
||||
flex 1
|
||||
display flex
|
||||
flex-direction column
|
||||
|
||||
.tag-title
|
||||
padding-left 15px
|
||||
padding-bottom 13px
|
||||
p
|
||||
color $ui-button-default-color
|
||||
|
||||
.tagList
|
||||
overflow-y auto
|
||||
|
||||
.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
|
||||
flex: 1
|
||||
|
||||
.root--folded
|
||||
@extend .root
|
||||
width 44px
|
||||
.storageList-empty
|
||||
white-space nowrap
|
||||
transform rotate(90deg)
|
||||
height 100vh
|
||||
width $sideNav--folded-width
|
||||
background-color #2E3235
|
||||
.switch-buttons
|
||||
display none
|
||||
.top
|
||||
height 60px
|
||||
.top-menu
|
||||
width 44px - 1
|
||||
position static
|
||||
width $sideNav--folded-width
|
||||
height 60px
|
||||
text-align center
|
||||
&:hover .top-menu-label
|
||||
transition opacity 0.15s
|
||||
@@ -65,44 +87,54 @@
|
||||
position fixed
|
||||
display inline-block
|
||||
height 30px
|
||||
left 32px
|
||||
left $sideNav--folded-width
|
||||
padding 0 10px
|
||||
margin-top -8px
|
||||
opacity 0
|
||||
margin-left 0
|
||||
overflow hidden
|
||||
background-color $ui-tooltip-backgroundColor
|
||||
z-index 10
|
||||
color white
|
||||
line-height 30px
|
||||
border-top-right-radius 2px
|
||||
border-bottom-right-radius 2px
|
||||
pointer-events none
|
||||
font-size 12px
|
||||
.menu-button, .menu-button--active
|
||||
text-align center
|
||||
&:hover .menu-button-label
|
||||
transition opacity 0.15s
|
||||
opacity 1
|
||||
font-size 13px
|
||||
.top-menu-preference
|
||||
position absolute
|
||||
left 11px
|
||||
|
||||
.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="white"]
|
||||
.root, .root--folded
|
||||
background-color #f9f9f9
|
||||
color $ui-text-color
|
||||
|
||||
.top-menu-preference
|
||||
navWhiteButtonColor()
|
||||
background-color transparent
|
||||
&:hover
|
||||
color #0B99F1
|
||||
background-color transparent
|
||||
&:active, &:active:hover
|
||||
color #0B99F1
|
||||
background-color transparent
|
||||
|
||||
.non-active-button
|
||||
color $ui-inactive-text-color
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.tag-title
|
||||
p
|
||||
color $ui-text-color
|
||||
|
||||
.non-active-button
|
||||
&:hover
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.active-button
|
||||
@extend .non-active-button
|
||||
color #0B99F1
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root, .root--folded
|
||||
@@ -113,18 +145,21 @@ body[data-theme="dark"]
|
||||
.top
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.top-menu
|
||||
.top-menu-preference
|
||||
navDarkButtonColor()
|
||||
background-color transparent
|
||||
&:active
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
background-color transparent
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
background-color transparent
|
||||
|
||||
.storageList-empty
|
||||
color $ui-dark-inactive-text-color
|
||||
|
||||
.navToggle
|
||||
.non-active-button
|
||||
color alpha($ui-dark-text-color, 60%)
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
transition 0.15s
|
||||
color $ui-dark-text-color
|
||||
color alpha(#0B99F1, 60%)
|
||||
|
||||
.tag-title
|
||||
p
|
||||
color alpha($ui-dark-text-color, 60%)
|
||||
|
||||
@@ -226,9 +226,9 @@ class StorageItem extends React.Component {
|
||||
<button styleName='header-toggleButton'
|
||||
onMouseDown={(e) => this.handleToggleButtonClick(e)}
|
||||
>
|
||||
<i className={this.state.isOpen
|
||||
? 'fa fa-caret-down'
|
||||
: 'fa fa-caret-right'
|
||||
<img src={this.state.isOpen
|
||||
? '../resources/icon/icon-down.svg'
|
||||
: '../resources/icon/icon-right.svg'
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
@@ -237,7 +237,7 @@ class StorageItem extends React.Component {
|
||||
<button styleName='header-addFolderButton'
|
||||
onClick={(e) => this.handleAddFolderButtonClick(e)}
|
||||
>
|
||||
<i className='fa fa-plus' />
|
||||
<img styleName='iconTag' src='../resources/icon/icon-plus.svg' />
|
||||
</button>
|
||||
}
|
||||
|
||||
|
||||
@@ -5,27 +5,23 @@
|
||||
|
||||
.header
|
||||
position relative
|
||||
height 25px
|
||||
height 36px
|
||||
width 100%
|
||||
margin-bottom 5px
|
||||
transition 0.15s
|
||||
display flex
|
||||
align-items center
|
||||
|
||||
.header--active
|
||||
margin-bottom 5px
|
||||
background-color $ui-button--active-backgroundColor
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
transition color background-color 0.15s
|
||||
|
||||
.header--active
|
||||
display flex
|
||||
align-items center
|
||||
.header-toggleButton
|
||||
color $ui-text-color
|
||||
|
||||
.header--active
|
||||
.header-info
|
||||
color $ui-text-color
|
||||
|
||||
.header--active
|
||||
.header-addFolderButton
|
||||
color $ui-text-color
|
||||
color #1EC38B
|
||||
|
||||
.header-toggleButton
|
||||
navButtonColor()
|
||||
@@ -38,23 +34,31 @@
|
||||
border-radius 50%
|
||||
&:hover
|
||||
transition 0.2s
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
background-color alpha($ui-button-default--hover-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
|
||||
.header-info
|
||||
navButtonColor()
|
||||
display block
|
||||
width 100%
|
||||
height 25px
|
||||
padding-left 23px
|
||||
padding-right 10px
|
||||
height 36px
|
||||
padding-left 25px
|
||||
padding-right 15px
|
||||
line-height 22px
|
||||
cursor pointer
|
||||
font-size 13px
|
||||
font-size 14px
|
||||
border none
|
||||
overflow ellipsis
|
||||
text-align left
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
font-weight 600;
|
||||
background-color transparent
|
||||
&:hover
|
||||
color #1EC38B
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
transition background-color 0.15s
|
||||
&:active, &:active:hover
|
||||
color #1EC38B
|
||||
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||
|
||||
.header-info-path
|
||||
font-size 10px
|
||||
@@ -63,17 +67,14 @@
|
||||
.header-addFolderButton
|
||||
navButtonColor()
|
||||
position absolute
|
||||
right 0
|
||||
right 7px
|
||||
width 25px
|
||||
height 25px
|
||||
padding 0
|
||||
border none
|
||||
margin-right 5px
|
||||
border-radius 50%
|
||||
&:hover
|
||||
transition 0.2s
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
|
||||
.root--folded
|
||||
@extend .root
|
||||
@@ -102,6 +103,33 @@
|
||||
font-size 10px
|
||||
margin 0 5px
|
||||
|
||||
body[data-theme="white"]
|
||||
.header--active
|
||||
background-color $ui-button--active-backgroundColor
|
||||
transition color background-color 0.15s
|
||||
.header-toggleButton
|
||||
color $ui-text-color
|
||||
.header-info
|
||||
color $ui-text-color
|
||||
.header-addFolderButton
|
||||
color $ui-text-color
|
||||
|
||||
.header-toggleButton
|
||||
navWhiteButtonColor()
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
|
||||
.header-info
|
||||
navWhiteButtonColor()
|
||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||
|
||||
.header-addFolderButton
|
||||
navWhiteButtonColor()
|
||||
&:hover
|
||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||
color $ui-text-color
|
||||
|
||||
body[data-theme="dark"]
|
||||
.header--active
|
||||
background-color $ui-dark-button--active-backgroundColor
|
||||
|
||||
@@ -5,10 +5,23 @@ import { openModal } from 'browser/main/lib/modal'
|
||||
import PreferencesModal from '../modals/PreferencesModal'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import StorageItem from './StorageItem'
|
||||
import TagListItem from 'browser/components/TagListItem'
|
||||
import SideNavFilter from 'browser/components/SideNavFilter'
|
||||
import StorageList from 'browser/components/StorageList'
|
||||
import NavToggleButton from 'browser/components/NavToggleButton'
|
||||
import EventEmitter from 'browser/main/lib/eventEmitter'
|
||||
|
||||
class SideNav extends React.Component {
|
||||
// TODO: should not use electron stuff v0.7
|
||||
|
||||
componentDidMount () {
|
||||
EventEmitter.on('side:preferences', this.handleMenuButtonClick)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
|
||||
}
|
||||
|
||||
handleMenuButtonClick (e) {
|
||||
openModal(PreferencesModal)
|
||||
}
|
||||
@@ -38,13 +51,94 @@ class SideNav extends React.Component {
|
||||
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)}
|
||||
counterTotalNote={data.noteMap._map.size}
|
||||
counterStarredNote={data.starredSet._set.size}
|
||||
counterDelNote={data.trashedSet._set.size}
|
||||
/>
|
||||
|
||||
<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={this.getTagActive(location.pathname, tag)}
|
||||
key={tag}
|
||||
/>
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
getTagActive (path, tag) {
|
||||
const pathSegments = path.split('/')
|
||||
const pathTag = pathSegments[pathSegments.length - 1]
|
||||
return pathTag === tag
|
||||
}
|
||||
|
||||
handleClickTagListItem (name) {
|
||||
const { router } = this.context
|
||||
router.push(`/tags/${name}`)
|
||||
}
|
||||
|
||||
render () {
|
||||
let { data, location, config, dispatch } = this.props
|
||||
|
||||
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) => {
|
||||
return <StorageItem
|
||||
@@ -58,6 +152,7 @@ class SideNav extends React.Component {
|
||||
})
|
||||
let style = {}
|
||||
if (!isFolded) style.width = this.props.width
|
||||
const isTagActive = location.pathname.match(/tag/)
|
||||
return (
|
||||
<div className='SideNav'
|
||||
styleName={isFolded ? 'root--folded' : 'root'}
|
||||
@@ -65,37 +160,31 @@ class SideNav extends React.Component {
|
||||
style={style}
|
||||
>
|
||||
<div styleName='top'>
|
||||
<button styleName='top-menu'
|
||||
<div styleName='switch-buttons'>
|
||||
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={this.handleSwitchFoldersButtonClick.bind(this)}>
|
||||
<img src={isTagActive
|
||||
? '../resources/icon/icon-list.svg'
|
||||
: '../resources/icon/icon-list-active.svg'
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={this.handleSwitchTagsButtonClick.bind(this)}>
|
||||
<img src={isTagActive
|
||||
? '../resources/icon/icon-tag-active.svg'
|
||||
: '../resources/icon/icon-tag.svg'
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button styleName='top-menu-preference'
|
||||
onClick={(e) => this.handleMenuButtonClick(e)}
|
||||
>
|
||||
<i className='fa fa-wrench fa-fw' />
|
||||
<span styleName='top-menu-label'>Preferences</span>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
|
||||
</button>
|
||||
</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>
|
||||
<button styleName='navToggle'
|
||||
onClick={(e) => this.handleToggleButtonClick(e)}
|
||||
>
|
||||
{isFolded
|
||||
? <i className='fa fa-angle-double-right' />
|
||||
: <i className='fa fa-angle-double-left' />
|
||||
}
|
||||
</button>
|
||||
{this.SideNavComponent(isFolded, storageList)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@import('../Detail/DetailVars')
|
||||
|
||||
.root
|
||||
absolute bottom left right
|
||||
height $statusBar-height
|
||||
background-color $ui-noteDetail-backgroundColor
|
||||
position absolute
|
||||
bottom 10px
|
||||
right 10px
|
||||
z-index 100
|
||||
display flex
|
||||
|
||||
.blank
|
||||
@@ -21,7 +22,18 @@
|
||||
|
||||
.zoom
|
||||
navButtonColor()
|
||||
height 24px
|
||||
color rgba(0,0,0,.54)
|
||||
height 20px
|
||||
display flex
|
||||
padding 0
|
||||
align-items center
|
||||
background-color transparent
|
||||
&:hover
|
||||
color $ui-active-color
|
||||
&:active
|
||||
color $ui-active-color
|
||||
span
|
||||
margin-left 5px
|
||||
|
||||
.update
|
||||
navButtonColor()
|
||||
@@ -37,14 +49,14 @@
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
background-color $ui-dark-noteDetail-backgroundColor
|
||||
border-color $ui-dark-borderColor
|
||||
box-shadow none
|
||||
|
||||
.zoom
|
||||
border-color $ui-dark-borderColor
|
||||
background-color transparent
|
||||
color #f9f9f9
|
||||
&:hover
|
||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||
transition 0.15s
|
||||
color $ui-dark-text-color
|
||||
|
||||
|
||||
@@ -55,12 +55,10 @@ class StatusBar extends React.Component {
|
||||
<button styleName='zoom'
|
||||
onClick={(e) => this.handleZoomButtonClick(e)}
|
||||
>
|
||||
<i className='fa fa-search-plus' />
|
||||
{Math.floor(config.zoom * 100)}%
|
||||
<img src='../resources/icon/icon-zoom.svg' />
|
||||
<span>{Math.floor(config.zoom * 100)}%</span>
|
||||
</button>
|
||||
|
||||
<div styleName='blank' />
|
||||
|
||||
{status.updateReady
|
||||
? <button onClick={this.updateApp} styleName='update'>
|
||||
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
|
||||
|
||||
@@ -36,7 +36,7 @@ $control-height = 34px
|
||||
outline none
|
||||
border none
|
||||
color $ui-text-color
|
||||
font-size 16px
|
||||
font-size 18px
|
||||
padding-bottom 2px
|
||||
background-color $ui-noteList-backgroundColor
|
||||
|
||||
@@ -112,6 +112,21 @@ $control-height = 34px
|
||||
opacity 0
|
||||
transition 0.1s
|
||||
|
||||
body[data-theme="white"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
.control
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.control-search
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
.control-search-input
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
input
|
||||
background-color $ui-white-noteList-backgroundColor
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root, .root--expanded
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
|
||||
@@ -2,12 +2,9 @@ import React, { PropTypes } from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './TopBar.styl'
|
||||
import _ from 'lodash'
|
||||
import modal from 'browser/main/lib/modal'
|
||||
import NewNoteModal from 'browser/main/modals/NewNoteModal'
|
||||
import { hashHistory } from 'react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import NewNoteButton from 'browser/main/NewNoteButton'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
@@ -21,11 +18,10 @@ class TopBar extends React.Component {
|
||||
this.state = {
|
||||
search: '',
|
||||
searchOptions: [],
|
||||
isSearching: false
|
||||
}
|
||||
|
||||
this.newNoteHandler = () => {
|
||||
this.handleNewPostButtonClick()
|
||||
isSearching: false,
|
||||
isAlphabet: false,
|
||||
isIME: false,
|
||||
isConfirmTranslation: false
|
||||
}
|
||||
|
||||
this.focusSearchHandler = () => {
|
||||
@@ -34,93 +30,62 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
ee.on('top:new-note', this.newNoteHandler)
|
||||
ee.on('top:focus-search', this.focusSearchHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ee.off('top:new-note', this.newNoteHandler)
|
||||
ee.off('top:focus-search', this.focusSearchHandler)
|
||||
}
|
||||
|
||||
handleNewPostButtonClick (e) {
|
||||
let { config, location } = this.props
|
||||
|
||||
if (location.pathname === '/trashed') {
|
||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Cannot create new note',
|
||||
detail: 'You cannot create new note in trash box.',
|
||||
buttons: ['OK']
|
||||
handleKeyDown (e) {
|
||||
// reset states
|
||||
this.setState({
|
||||
isAlphabet: false,
|
||||
isIME: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
switch (config.ui.defaultNote) {
|
||||
case 'MARKDOWN_NOTE':
|
||||
this.createNote('MARKDOWN_NOTE')
|
||||
break
|
||||
case 'SNIPPET_NOTE':
|
||||
this.createNote('SNIPPET_NOTE')
|
||||
break
|
||||
case 'ALWAYS_ASK':
|
||||
default:
|
||||
let { dispatch, location } = this.props
|
||||
let { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
modal.open(NewNoteModal, {
|
||||
storage: storage.key,
|
||||
folder: folder.key,
|
||||
dispatch,
|
||||
location
|
||||
// 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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
let { data, params } = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
handleKeyUp (e) {
|
||||
const { router } = this.context
|
||||
// reset states
|
||||
this.setState({
|
||||
isConfirmTranslation: false
|
||||
})
|
||||
|
||||
// Find first storage
|
||||
if (storage == null) {
|
||||
for (let kv of data.storageMap) {
|
||||
storage = kv[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (storage == null) window.alert('No storage to create a note')
|
||||
let folder = _.find(storage.folders, {key: params.folderKey})
|
||||
if (folder == null) folder = storage.folders[0]
|
||||
if (folder == null) window.alert('No folder to create a note')
|
||||
|
||||
return {
|
||||
storage,
|
||||
folder
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchChange (e) {
|
||||
let { router } = this.context
|
||||
// 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')
|
||||
this.setState({
|
||||
search: this.refs.searchInput.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleOptionClick (uniqueKey) {
|
||||
return (e) => {
|
||||
handleSearchChange (e) {
|
||||
const { router } = this.context
|
||||
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
|
||||
router.push('/searched')
|
||||
} else {
|
||||
e.preventDefault()
|
||||
}
|
||||
this.setState({
|
||||
isSearching: false
|
||||
}, () => {
|
||||
let { location } = this.props
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
key: uniqueKey
|
||||
}
|
||||
search: this.refs.searchInput.value
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleSearchFocus (e) {
|
||||
@@ -147,60 +112,6 @@ class TopBar extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
createNote (noteType) {
|
||||
let { dispatch, location } = this.props
|
||||
if (noteType !== 'MARKDOWN_NOTE' && noteType !== 'SNIPPET_NOTE') throw new Error('Invalid note type.')
|
||||
|
||||
let { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
let newNote = noteType === 'MARKDOWN_NOTE'
|
||||
? {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: folder.key,
|
||||
title: '',
|
||||
content: ''
|
||||
}
|
||||
: {
|
||||
type: 'SNIPPET_NOTE',
|
||||
folder: folder.key,
|
||||
title: '',
|
||||
description: '',
|
||||
snippets: [{
|
||||
name: '',
|
||||
mode: 'text',
|
||||
content: ''
|
||||
}]
|
||||
}
|
||||
|
||||
dataApi
|
||||
.createNote(storage.key, newNote)
|
||||
.then((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: note.storage + '-' + note.key}
|
||||
})
|
||||
ee.emit('detail:focus')
|
||||
})
|
||||
}
|
||||
|
||||
setDefaultNote (defaultNote) {
|
||||
let { config, dispatch } = this.props
|
||||
let ui = Object.assign(config.ui)
|
||||
ui.defaultNote = defaultNote
|
||||
ConfigManager.set({
|
||||
ui
|
||||
})
|
||||
|
||||
dispatch({
|
||||
type: 'SET_UI',
|
||||
config: ConfigManager.get()
|
||||
})
|
||||
}
|
||||
|
||||
handleOnSearchFocus () {
|
||||
if (this.state.isSearching) {
|
||||
this.refs.search.childNodes[0].blur()
|
||||
@@ -210,7 +121,7 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
let { config, style, data } = this.props
|
||||
let { config, style, data, location } = this.props
|
||||
return (
|
||||
<div className='TopBar'
|
||||
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
||||
@@ -228,6 +139,8 @@ class TopBar extends React.Component {
|
||||
ref='searchInput'
|
||||
value={this.state.search}
|
||||
onChange={(e) => this.handleSearchChange(e)}
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyUp={(e) => this.handleKeyUp(e)}
|
||||
placeholder='Search'
|
||||
type='text'
|
||||
className='searchInput'
|
||||
@@ -242,14 +155,17 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
</div>
|
||||
<button styleName='control-newPostButton'
|
||||
onClick={(e) => this.handleNewPostButtonClick(e)}>
|
||||
<i className='fa fa-pencil-square-o' />
|
||||
<span styleName='control-newPostButton-tooltip'>
|
||||
Make a Note {OSX ? '⌘' : '^'} + n
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
{location.pathname === '/trashed' ? ''
|
||||
: <NewNoteButton
|
||||
{..._.pick(this.props, [
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'params',
|
||||
'location'
|
||||
])}
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ body
|
||||
color textColor
|
||||
font-size fontSize
|
||||
font-weight 200
|
||||
-webkit-font-smoothing antialiased
|
||||
|
||||
button, input, select, textarea
|
||||
font-family DEFAULT_FONTS
|
||||
@@ -64,7 +65,7 @@ textarea.block-input
|
||||
fullsize()
|
||||
|
||||
modalZIndex= 1000
|
||||
modalBackColor = transparentify(white, 65%)
|
||||
modalBackColor = white
|
||||
.ace_focus
|
||||
outline-color rgb(59, 153, 252)
|
||||
outline-offset 0px
|
||||
@@ -86,12 +87,12 @@ modalBackColor = transparentify(white, 65%)
|
||||
body[data-theme="dark"]
|
||||
.ModalBase
|
||||
.modalBack
|
||||
background-color alpha(black, 60%)
|
||||
background-color $ui-dark-backgroundColor
|
||||
|
||||
.CodeMirror
|
||||
font-family inherit !important
|
||||
line-height 1.4em
|
||||
height 100%
|
||||
height 96%
|
||||
.CodeMirror > div > textarea
|
||||
margin-bottom -1em
|
||||
.CodeMirror-focused .CodeMirror-selected
|
||||
@@ -102,3 +103,10 @@ body[data-theme="dark"]
|
||||
background #B1D7FE
|
||||
::selection
|
||||
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
|
||||
if (!className && typeof (className) !== 'string') return
|
||||
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
|
||||
const infoPanel = document.querySelector('.infoPanel')
|
||||
if (infoPanel) infoPanel.style.display = 'none'
|
||||
@@ -62,6 +65,11 @@ ReactDOM.render((
|
||||
<Route path='starred' />
|
||||
<Route path='searched' />
|
||||
<Route path='trashed' />
|
||||
<Route path='alltags' />
|
||||
<Route path='tags'>
|
||||
<IndexRedirect to='/alltags' />
|
||||
<Route path=':tagname' />
|
||||
</Route>
|
||||
<Route path='storages'>
|
||||
<IndexRedirect to='/home' />
|
||||
<Route path=':storageKey'>
|
||||
|
||||
@@ -2,37 +2,70 @@ const AWS = require('aws-sdk')
|
||||
const AMA = require('aws-sdk-mobile-analytics')
|
||||
const ConfigManager = require('browser/main/lib/ConfigManager')
|
||||
|
||||
const remote = require('electron').remote
|
||||
const os = require('os')
|
||||
|
||||
AWS.config.region = 'us-east-1'
|
||||
if (process.env.NODE_ENV === 'production' && ConfigManager.default.get().amaEnabled) {
|
||||
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
|
||||
IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
})
|
||||
|
||||
const validPlatformName = convertPlatformName(os.platform())
|
||||
|
||||
const mobileAnalyticsClient = new AMA.Manager({
|
||||
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 () {
|
||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
||||
AWS.config.credentials.get((err) => {
|
||||
if (!err) {
|
||||
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
|
||||
recordDynamicCustomEvent('APP_STARTED')
|
||||
recordStaticCustomEvent()
|
||||
}
|
||||
})
|
||||
recordStaticCustomEvent()
|
||||
}
|
||||
|
||||
function recordDynamicCustomEvent (type) {
|
||||
function recordDynamicCustomEvent (type, options = {}) {
|
||||
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 () {
|
||||
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
||||
try {
|
||||
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
|
||||
uiColorTheme: ConfigManager.default.get().ui.theme
|
||||
})
|
||||
} catch (analyticsError) {
|
||||
if (analyticsError instanceof ReferenceError) {
|
||||
console.log(analyticsError.name + ': ' + analyticsError.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -25,6 +25,7 @@ export const DEFAULT_CONFIG = {
|
||||
},
|
||||
ui: {
|
||||
theme: 'default',
|
||||
showCopyNotification: true,
|
||||
disableDirectWrite: false,
|
||||
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
|
||||
},
|
||||
@@ -90,9 +91,13 @@ function get () {
|
||||
: 'default'
|
||||
|
||||
if (config.editor.theme !== 'default') {
|
||||
if (config.editor.theme.startsWith('solarized')) {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css')
|
||||
} else {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
@@ -105,6 +110,8 @@ function set (updates) {
|
||||
|
||||
if (newConfig.ui.theme === 'dark') {
|
||||
document.body.setAttribute('data-theme', 'dark')
|
||||
} else if (newConfig.ui.theme === 'white') {
|
||||
document.body.setAttribute('data-theme', 'white')
|
||||
} else {
|
||||
document.body.setAttribute('data-theme', 'default')
|
||||
}
|
||||
@@ -121,8 +128,12 @@ function set (updates) {
|
||||
: 'default'
|
||||
|
||||
if (newTheme !== 'default') {
|
||||
if (newTheme.startsWith('solarized')) {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css')
|
||||
} else {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + newTheme + '.css')
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.send('config-renew', {
|
||||
config: get()
|
||||
|
||||
@@ -6,6 +6,7 @@ const dataApi = {
|
||||
createFolder: require('./createFolder'),
|
||||
updateFolder: require('./updateFolder'),
|
||||
deleteFolder: require('./deleteFolder'),
|
||||
reorderFolder: require('./reorderFolder'),
|
||||
createNote: require('./createNote'),
|
||||
updateNote: require('./updateNote'),
|
||||
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 resolveStorageData = require('./resolveStorageData')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
/**
|
||||
* @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.color)) throw new Error('Color must be a string.')
|
||||
|
||||
rawStorages = JSON.parse(localStorage.getItem('storages'))
|
||||
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.')
|
||||
targetStorage = findStorage(storageKey)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,10 @@ function validateInput (input) {
|
||||
validatedInput.isTrashed = !!input.isTrashed
|
||||
}
|
||||
|
||||
if (input.isPinned !== undefined) {
|
||||
validatedInput.isPinned = !!input.isPinned
|
||||
}
|
||||
|
||||
validatedInput.type = input.type
|
||||
switch (input.type) {
|
||||
case 'MARKDOWN_NOTE':
|
||||
@@ -104,6 +108,7 @@ function updateNote (storageKey, noteKey, input) {
|
||||
noteData.isStarred = false
|
||||
noteData.isTrashed = false
|
||||
noteData.tags = []
|
||||
noteData.isPinned = false
|
||||
}
|
||||
|
||||
if (noteData.type === 'SNIPPET_NOTE') {
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
import store from 'browser/main/store'
|
||||
|
||||
const _ = require('lodash')
|
||||
const keygen = require('browser/lib/keygen')
|
||||
const Mixpanel = require('mixpanel')
|
||||
const mixpanel = Mixpanel.init('7a0aca437d72dfd07cbcbf58d3b61f27', {key: 'fde4fd23f4d550f1b646bcd7d4374b1f'})
|
||||
const moment = require('moment')
|
||||
const electron = require('electron')
|
||||
|
||||
function _getClientKey () {
|
||||
let clientKey = localStorage.getItem('clientKey')
|
||||
if (!_.isString(clientKey) || clientKey.length !== 40) {
|
||||
clientKey = keygen(20)
|
||||
_setClientKey(clientKey)
|
||||
}
|
||||
|
||||
return clientKey
|
||||
}
|
||||
|
||||
function _setClientKey (newKey) {
|
||||
localStorage.setItem('clientKey', newKey)
|
||||
}
|
||||
|
||||
function _fetch () {
|
||||
let events
|
||||
try {
|
||||
events = JSON.parse(localStorage.getItem('events'))
|
||||
if (!_.isArray(events)) throw new Error('events is not an array.')
|
||||
} catch (err) {
|
||||
console.warn(err)
|
||||
events = []
|
||||
localStorage.setItem('events', JSON.stringify(events))
|
||||
console.info('Events cache initialzed')
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
function _keep (name, properties) {
|
||||
let events = _fetch()
|
||||
properties.time = new Date()
|
||||
events.push({
|
||||
name,
|
||||
properties
|
||||
})
|
||||
localStorage.setItem('events', JSON.stringify(events))
|
||||
}
|
||||
|
||||
function _keepUnique (name, properties) {
|
||||
let events = _fetch()
|
||||
properties.time = new Date()
|
||||
events = events.filter((event) => event.name !== name)
|
||||
events.push({
|
||||
name,
|
||||
properties
|
||||
})
|
||||
localStorage.setItem('events', JSON.stringify(events))
|
||||
}
|
||||
|
||||
function _flush () {
|
||||
let events = _fetch()
|
||||
let spliced = events.splice(0, 50)
|
||||
localStorage.setItem('events', JSON.stringify(events))
|
||||
|
||||
if (spliced.length > 0) {
|
||||
let parsedEvents = spliced
|
||||
.filter((event) => {
|
||||
if (!_.isObject(event)) return false
|
||||
if (!_.isString(event.name)) return false
|
||||
if (!_.isObject(event.properties)) return false
|
||||
if (!moment(event.properties.time).isValid()) return false
|
||||
if (new Date() - moment(event.properties.time).toDate() > 1000 * 3600 * 24 * 3) return false
|
||||
return true
|
||||
})
|
||||
.map((event) => {
|
||||
return {
|
||||
event: event.name,
|
||||
properties: event.properties
|
||||
}
|
||||
})
|
||||
|
||||
mixpanel.import_batch(parsedEvents, {}, (errs) => {
|
||||
if (errs.length > 0) {
|
||||
let events = _fetch()
|
||||
events = events.concat(spliced)
|
||||
localStorage.setItem('events', JSON.stringify(events))
|
||||
} else {
|
||||
_flush()
|
||||
}
|
||||
})
|
||||
|
||||
let state = store.getState()
|
||||
mixpanel.people.set(_getClientKey(), {
|
||||
storage_count: state.data.storageMap.size,
|
||||
note_count: state.data.noteMap.size,
|
||||
version: electron.remote.app.getVersion()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(_flush, 1000 * 60 * 60)
|
||||
|
||||
function track (name, properties) {
|
||||
switch (name) {
|
||||
case 'MAIN_FOCUSED':
|
||||
properties = Object.assign({}, properties, {
|
||||
distinct_id: _getClientKey()
|
||||
})
|
||||
_keepUnique(name, properties)
|
||||
break
|
||||
default:
|
||||
properties = Object.assign({}, properties, {
|
||||
distinct_id: _getClientKey()
|
||||
})
|
||||
_keep(name, properties)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
_mp: mixpanel,
|
||||
track
|
||||
}
|
||||
@@ -4,10 +4,9 @@
|
||||
height 270px
|
||||
overflow hidden
|
||||
position relative
|
||||
padding 0 40px
|
||||
|
||||
.header
|
||||
height 70px
|
||||
height 80px
|
||||
margin-bottom 10px
|
||||
margin-top 20px
|
||||
font-size 18px
|
||||
@@ -15,23 +14,27 @@
|
||||
background-color $ui-backgroundColor
|
||||
color $ui-text-color
|
||||
|
||||
.title
|
||||
font-size 36px
|
||||
font-weight 600
|
||||
|
||||
.control-folder-label
|
||||
text-align left
|
||||
font-size 12px
|
||||
font-size 14px
|
||||
color $ui-text-color
|
||||
|
||||
.control-folder-input
|
||||
display block
|
||||
height 30px
|
||||
width 420px
|
||||
height 40px
|
||||
width 490px
|
||||
padding 0 5px
|
||||
margin 10px auto 15px
|
||||
margin 10px 0
|
||||
border 1px solid #C9C9C9 // TODO: use variable.
|
||||
border-radius 2px
|
||||
background-color transparent
|
||||
outline none
|
||||
vertical-align middle
|
||||
font-size 14px
|
||||
font-size 16px
|
||||
&:disabled
|
||||
background-color $ui-input--disabled-backgroundColor
|
||||
&:focus, &:active
|
||||
@@ -39,14 +42,13 @@
|
||||
|
||||
.control-confirmButton
|
||||
display block
|
||||
float right
|
||||
height 30px
|
||||
width 100px
|
||||
height 35px
|
||||
width 140px
|
||||
border none
|
||||
border-radius 2px
|
||||
padding 0 25px
|
||||
margin 20px auto
|
||||
font-size 12px
|
||||
font-size 14px
|
||||
colorPrimaryButton()
|
||||
|
||||
body[data-theme="dark"]
|
||||
@@ -56,7 +58,6 @@ body[data-theme="dark"]
|
||||
height 270px
|
||||
overflow hidden
|
||||
position relative
|
||||
padding 0 40px
|
||||
|
||||
.header
|
||||
background-color transparent
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
|
||||
export default class DeleteArticleModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.confirmHandler = (e) => this.handleYesButtonClick()
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
ReactDOM.findDOMNode(this.refs.no).focus()
|
||||
ipc.on('modal-confirm', this.confirmHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ipc.removeListener('modal-confirm', this.confirmHandler)
|
||||
}
|
||||
|
||||
handleNoButtonClick (e) {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
handleYesButtonClick (e) {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='DeleteArticleModal modal'>
|
||||
<div className='title'><i className='fa fa-fw fa-trash' /> Delete an article.</div>
|
||||
|
||||
<div className='message'>Do you really want to delete?</div>
|
||||
|
||||
<div className='control'>
|
||||
<button ref='no' onClick={(e) => this.handleNoButtonClick(e)}><i className='fa fa-fw fa-close' /> No</button>
|
||||
<button ref='yes' onClick={(e) => this.handleYesButtonClick(e)} className='danger'><i className='fa fa-fw fa-check' /> Yes</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DeleteArticleModal.propTypes = {
|
||||
action: PropTypes.object,
|
||||
articleKey: PropTypes.string,
|
||||
close: PropTypes.func
|
||||
}
|
||||
@@ -152,7 +152,7 @@ class InitModal extends React.Component {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
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 here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
|
||||
})
|
||||
.then((note) => {
|
||||
store.dispatch({
|
||||
@@ -194,13 +194,13 @@ class InitModal extends React.Component {
|
||||
return (
|
||||
<div styleName='root'
|
||||
tabIndex='-1'
|
||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||
onKeyDown={this.props.close}
|
||||
>
|
||||
|
||||
<div styleName='header'>
|
||||
<div styleName='header-title'>Initialize Storage</div>
|
||||
</div>
|
||||
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
|
||||
<ModalEscButton handleEscButtonClick={this.props.close} />
|
||||
<div styleName='body'>
|
||||
<div styleName='body-welcome'>
|
||||
Welcome!
|
||||
|
||||
@@ -9,16 +9,19 @@
|
||||
font-size 18px
|
||||
line-height 50px
|
||||
padding 0 15px
|
||||
background-color $ui-backgroundColor
|
||||
border-bottom solid 1px $ui-borderColor
|
||||
color $ui-text-color
|
||||
margin-bottom 20px
|
||||
|
||||
.title
|
||||
font-size 36px
|
||||
font-weight 600
|
||||
|
||||
.control
|
||||
padding 25px 15px 15px
|
||||
padding 25px 0px
|
||||
text-align center
|
||||
|
||||
.control-button
|
||||
width 220px
|
||||
width 240px
|
||||
height 220px
|
||||
margin 0 15px
|
||||
border $ui-border
|
||||
@@ -30,8 +33,8 @@
|
||||
colorPrimaryButton()
|
||||
|
||||
.control-button-icon
|
||||
font-size 50px
|
||||
margin-bottom 15px
|
||||
font-size 48px
|
||||
margin-bottom 25px
|
||||
|
||||
.control-button-label
|
||||
font-size 18px
|
||||
@@ -49,8 +52,6 @@ body[data-theme="dark"]
|
||||
modalDark()
|
||||
|
||||
.header
|
||||
background-color $ui-dark-button--hover-backgroundColor
|
||||
border-color $ui-dark-borderColor
|
||||
color $ui-dark-text-color
|
||||
|
||||
.control-button
|
||||
|
||||
@@ -31,10 +31,15 @@
|
||||
|
||||
.group-section-control
|
||||
flex 1
|
||||
margin-left 5px
|
||||
|
||||
.group-section-control select
|
||||
outline none
|
||||
border 1px solid $ui-borderColor
|
||||
font-size 16px
|
||||
height 30px
|
||||
width 250px
|
||||
margin-bottom 5px
|
||||
background-color transparent
|
||||
|
||||
.group-section-control-input
|
||||
@@ -62,10 +67,17 @@
|
||||
text-align right
|
||||
:global
|
||||
.alert
|
||||
font-size 12px
|
||||
line-height 30px
|
||||
padding 0 5px
|
||||
float right
|
||||
display inline-block
|
||||
position absolute
|
||||
top 60px
|
||||
right 15px
|
||||
font-size 14px
|
||||
.success
|
||||
color #1EC38B
|
||||
.error
|
||||
color red
|
||||
|
||||
|
||||
|
||||
.group-control-leftButton
|
||||
colorDefaultButton()
|
||||
@@ -77,14 +89,16 @@
|
||||
margin-right 10px
|
||||
|
||||
.group-control-rightButton
|
||||
float right
|
||||
position absolute
|
||||
top 10px
|
||||
right 20px
|
||||
colorPrimaryButton()
|
||||
border none
|
||||
border-radius 2px
|
||||
font-size $tab--button-font-size
|
||||
height 35px
|
||||
width 100px
|
||||
margin-right 10px
|
||||
height 40px
|
||||
width 120px
|
||||
padding 0 15px
|
||||
|
||||
.group-hint
|
||||
border $ui-border
|
||||
@@ -101,7 +115,6 @@
|
||||
line-height 1.2
|
||||
|
||||
.note-for-keymap
|
||||
margin-left: 10px
|
||||
font-size: 12px
|
||||
|
||||
.code-mirror
|
||||
|
||||
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 13px
|
||||
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 ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import _ from 'lodash'
|
||||
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
@@ -50,6 +51,7 @@ class HotkeyTab extends React.Component {
|
||||
type: 'SET_UI',
|
||||
config: newConfig
|
||||
})
|
||||
this.clearMessage()
|
||||
}
|
||||
|
||||
handleHintToggleButtonClick (e) {
|
||||
@@ -69,6 +71,14 @@ class HotkeyTab extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
clearMessage () {
|
||||
_.debounce(() => {
|
||||
this.setState({
|
||||
keymapAlert: null
|
||||
})
|
||||
}, 2000)()
|
||||
}
|
||||
|
||||
render () {
|
||||
let keymapAlert = this.state.keymapAlert
|
||||
let keymapAlertElement = keymapAlert != null
|
||||
@@ -94,7 +104,7 @@ class HotkeyTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
<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'>
|
||||
<input styleName='group-section-control-input'
|
||||
onChange={(e) => this.handleHotkeyChange(e)}
|
||||
|
||||
@@ -4,6 +4,7 @@ import styles from './InfoTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import _ from 'lodash'
|
||||
|
||||
const electron = require('electron')
|
||||
const { shell, remote } = electron
|
||||
@@ -36,8 +37,21 @@ class InfoTab extends React.Component {
|
||||
|
||||
if (!newConfig.amaEnabled) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
|
||||
this.setState({
|
||||
amaMessage: 'We hope we will gain your trust'
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
amaMessage: 'Thank\'s for trust us'
|
||||
})
|
||||
}
|
||||
|
||||
_.debounce(() => {
|
||||
this.setState({
|
||||
amaMessage: ''
|
||||
})
|
||||
}, 3000)()
|
||||
|
||||
ConfigManager.set(newConfig)
|
||||
|
||||
store.dispatch({
|
||||
@@ -46,10 +60,49 @@ class InfoTab extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
infoMessage () {
|
||||
const { amaMessage } = this.state
|
||||
return amaMessage ? <p styleName='policy-confirm'>{amaMessage}</p> : null
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div styleName='root'>
|
||||
<div styleName='header'>Info</div>
|
||||
|
||||
<div styleName='header--sub'>Community</div>
|
||||
<div styleName='top'>
|
||||
<ul styleName='list'>
|
||||
<li>
|
||||
<a href='https://github.com/BoostIO/Boostnote/issues'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>GitHub</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://medium.com/boostnote'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Blog</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://www.reddit.com/r/Boostnote/'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Reddit</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://www.facebook.com/groups/boostnote'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Facebook Group</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://twitter.com/boostnoteapp'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Twitter</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div styleName='header--sub'>Info</div>
|
||||
|
||||
<div styleName='top'>
|
||||
<div styleName='icon-space'>
|
||||
@@ -62,31 +115,17 @@ class InfoTab extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul styleName='list'>
|
||||
<li>
|
||||
<a href='https://boostnote.io'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Website</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://boostnote.paintory.com/'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Boostnote Shop</a> : Products are shipped to all over the world 🌏
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://salt.bountysource.com/teams/boostnote'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Donate via Bountysource</a> : Thank you for your support 🎉
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://github.com/BoostIO/Boostnote/issues'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>GitHub Issues</a> : We'd love to hear your feedback 🙌
|
||||
</li>
|
||||
<li>
|
||||
<a href='https://github.com/BoostIO/Boostnote/blob/master/docs/build.md'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Development</a> : Development configurations for Boostnote 🚀
|
||||
>Development</a> : Development configurations for Boostnote.
|
||||
</li>
|
||||
<li styleName='cc'>
|
||||
Copyright (C) 2017 Maisin&Co.
|
||||
@@ -95,11 +134,13 @@ class InfoTab extends React.Component {
|
||||
License: GPL v3
|
||||
</li>
|
||||
</ul>
|
||||
<hr />
|
||||
|
||||
<hr styleName='separate-line' />
|
||||
|
||||
<div styleName='policy'>Data collection policy</div>
|
||||
<p>We collect only the number of DAU for Boostnote and DO NOT collect any detail information</p>
|
||||
<p>such as your note content. You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</p>
|
||||
<p>These data are only used for Boostnote improvements.</p>
|
||||
<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>This data is only used for Boostnote improvements.</div>
|
||||
<input onChange={(e) => this.handleConfigChange(e)}
|
||||
checked={this.state.config.amaEnabled}
|
||||
ref='amaEnabled'
|
||||
@@ -107,6 +148,7 @@ class InfoTab extends React.Component {
|
||||
/>
|
||||
Enable to send analytics to our servers<br />
|
||||
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>Save</button>
|
||||
{this.infoMessage()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -42,13 +42,21 @@
|
||||
color #4E8EC6
|
||||
text-decoration none
|
||||
|
||||
.separate-line
|
||||
margin 40px 0
|
||||
|
||||
.policy
|
||||
width 100%
|
||||
font-size 20px
|
||||
margin-bottom 10px
|
||||
|
||||
.policy-submit
|
||||
margin-top 10px
|
||||
|
||||
.policy-confirm
|
||||
margin-top 10px
|
||||
font-size 12px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
color alpha($tab--dark-text-color, 80%)
|
||||
|
||||
@@ -4,9 +4,10 @@ top-bar--height = 50px
|
||||
|
||||
.root
|
||||
modal()
|
||||
max-width 800px
|
||||
min-height 500px
|
||||
height 80%
|
||||
max-width 100vw
|
||||
min-height 100vh
|
||||
height 100vh
|
||||
width 100vw
|
||||
overflow hidden
|
||||
position relative
|
||||
|
||||
@@ -24,23 +25,23 @@ top-bar--height = 50px
|
||||
absolute top left right
|
||||
top top-bar--height
|
||||
left 0
|
||||
width 140px
|
||||
margin-left 30px
|
||||
width 170px
|
||||
margin-left 10px
|
||||
margin-top 20px
|
||||
background-color $ui-backgroundColor
|
||||
|
||||
.nav-button
|
||||
font-size 14px
|
||||
text-align left
|
||||
width 100px
|
||||
margin 4px 0
|
||||
padding 5px 0
|
||||
width 150px
|
||||
margin 5px 0
|
||||
padding 7px 0
|
||||
padding-left 10px
|
||||
border none
|
||||
border-radius 2px
|
||||
background-color transparent
|
||||
color $ui-text-color
|
||||
font-size 14px
|
||||
font-size 16px
|
||||
|
||||
.nav-button--active
|
||||
@extend .nav-button
|
||||
@@ -55,7 +56,7 @@ top-bar--height = 50px
|
||||
.content
|
||||
absolute left right bottom
|
||||
top top-bar--height
|
||||
left 140px
|
||||
left 170px
|
||||
margin-top 10px
|
||||
overflow-y auto
|
||||
|
||||
|
||||
@@ -1,265 +1,13 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StorageItem.styl'
|
||||
import consts from 'browser/lib/consts'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import FolderList from './FolderList'
|
||||
|
||||
const { shell, remote } = require('electron')
|
||||
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 {
|
||||
constructor (props) {
|
||||
@@ -349,13 +97,7 @@ class StorageItem extends React.Component {
|
||||
|
||||
render () {
|
||||
let { storage, hostBoundingBox } = this.props
|
||||
let folderList = storage.folders.map((folder) => {
|
||||
return <FolderItem key={folder.key}
|
||||
folder={folder}
|
||||
storage={storage}
|
||||
hostBoundingBox={hostBoundingBox}
|
||||
/>
|
||||
})
|
||||
|
||||
return (
|
||||
<div styleName='root' key={storage.key}>
|
||||
<div styleName='header'>
|
||||
@@ -404,12 +146,9 @@ class StorageItem extends React.Component {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='folderList'>
|
||||
{folderList.length > 0
|
||||
? folderList
|
||||
: <div styleName='folderList-empty'>No Folders</div>
|
||||
}
|
||||
</div>
|
||||
<FolderList storage={storage}
|
||||
hostBoundingBox={hostBoundingBox}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -426,11 +165,6 @@ StorageItem.propTypes = {
|
||||
}),
|
||||
storage: PropTypes.shape({
|
||||
key: PropTypes.string
|
||||
}),
|
||||
folder: PropTypes.shape({
|
||||
key: PropTypes.string,
|
||||
color: PropTypes.string,
|
||||
name: PropTypes.string
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -63,75 +63,6 @@
|
||||
z-index 10
|
||||
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"]
|
||||
.header
|
||||
border-color $ui-dark-borderColor
|
||||
@@ -153,28 +84,3 @@ body[data-theme="dark"]
|
||||
top 25px
|
||||
z-index 10
|
||||
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()
|
||||
|
||||
@@ -5,7 +5,7 @@ import dataApi from 'browser/main/lib/dataApi'
|
||||
import StorageItem from './StorageItem'
|
||||
|
||||
const electron = require('electron')
|
||||
const remote = electron.remote
|
||||
const { shell, remote } = electron
|
||||
|
||||
function browseFolder () {
|
||||
let dialog = remote.dialog
|
||||
@@ -50,6 +50,11 @@ class StoragesTab extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
handleLinkClick (e) {
|
||||
shell.openExternal(e.currentTarget.href)
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
renderList () {
|
||||
let { data, boundingBox } = this.props
|
||||
|
||||
@@ -161,7 +166,10 @@ class StoragesTab extends React.Component {
|
||||
<option value='FILESYSTEM'>File System</option>
|
||||
</select>
|
||||
<div styleName='addStorage-body-section-type-description'>
|
||||
3rd party cloud integration(such as Google Drive and Dropbox) will be available soon.
|
||||
3rd party cloud integration:
|
||||
<a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing'
|
||||
onClick={(e) => this.handleLinkClick(e)}
|
||||
>Cloud-Syncing</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,8 +10,12 @@ $tab--button-font-size = 14px
|
||||
$tab--dark-text-color = #E5E5E5
|
||||
|
||||
.header
|
||||
font-size 24px
|
||||
margin-bottom 30px
|
||||
font-size 36px
|
||||
margin-bottom 60px
|
||||
|
||||
.header--sub
|
||||
font-size 36px
|
||||
margin-bottom 20px
|
||||
|
||||
body[data-theme="dark"]
|
||||
.header
|
||||
|
||||
@@ -9,6 +9,11 @@ import CodeMirror from 'codemirror'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
import _ from 'lodash'
|
||||
|
||||
const electron = require('electron')
|
||||
const ipc = electron.ipcRenderer
|
||||
|
||||
class UiTab extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
@@ -18,10 +23,32 @@ class UiTab extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.handleSettingDone = () => {
|
||||
this.setState({UiAlert: {
|
||||
type: 'success',
|
||||
message: 'Successfully applied!'
|
||||
}})
|
||||
}
|
||||
this.handleSettingError = (err) => {
|
||||
this.setState({UiAlert: {
|
||||
type: 'error',
|
||||
message: err.message != null ? err.message : 'Error occurs!'
|
||||
}})
|
||||
}
|
||||
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
CodeMirror.autoLoadMode(ReactCodeMirror, 'javascript')
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ipc.removeListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||
ipc.removeListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||
}
|
||||
|
||||
handleUIChange (e) {
|
||||
const { codemirrorTheme } = this.state
|
||||
let checkHighLight = document.getElementById('checkHighLight')
|
||||
@@ -36,6 +63,7 @@ class UiTab extends React.Component {
|
||||
const newConfig = {
|
||||
ui: {
|
||||
theme: this.refs.uiTheme.value,
|
||||
showCopyNotification: this.refs.showCopyNotification.checked,
|
||||
disableDirectWrite: this.refs.uiD2w != null
|
||||
? this.refs.uiD2w.checked
|
||||
: false
|
||||
@@ -60,7 +88,7 @@ class UiTab extends React.Component {
|
||||
const newCodemirrorTheme = this.refs.editorTheme.value
|
||||
|
||||
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 })
|
||||
@@ -79,9 +107,25 @@ class UiTab extends React.Component {
|
||||
type: 'SET_UI',
|
||||
config: newConfig
|
||||
})
|
||||
this.clearMessage()
|
||||
}
|
||||
|
||||
clearMessage () {
|
||||
_.debounce(() => {
|
||||
this.setState({
|
||||
UiAlert: null
|
||||
})
|
||||
}, 2000)()
|
||||
}
|
||||
|
||||
render () {
|
||||
let UiAlert = this.state.UiAlert
|
||||
let UiAlertElement = UiAlert != null
|
||||
? <p className={`alert ${UiAlert.type}`}>
|
||||
{UiAlert.message}
|
||||
</p>
|
||||
: null
|
||||
|
||||
const themes = consts.THEMES
|
||||
const { config, codemirrorTheme } = this.state
|
||||
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
|
||||
@@ -90,19 +134,29 @@ class UiTab extends React.Component {
|
||||
<div styleName='group'>
|
||||
<div styleName='group-header'>UI</div>
|
||||
|
||||
<div styleName='group-header2'>Theme</div>
|
||||
|
||||
<div styleName='group-section'>
|
||||
Color Theme
|
||||
<div styleName='group-section-control'>
|
||||
<select value={config.ui.theme}
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
ref='uiTheme'
|
||||
>
|
||||
<option value='default'>Light</option>
|
||||
<option value='default'>Default</option>
|
||||
<option value='white'>White</option>
|
||||
<option value='dark'>Dark</option>
|
||||
</select>
|
||||
</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'
|
||||
? <div styleName='group-checkBoxSection'>
|
||||
@@ -218,7 +272,7 @@ class UiTab extends React.Component {
|
||||
<option value='vim'>vim</option>
|
||||
<option value='emacs'>emacs</option>
|
||||
</select>
|
||||
<span styleName='note-for-keymap'>Please restart boostnote after you change the keymap</span>
|
||||
<p styleName='note-for-keymap'>⚠️ Please restart boostnote after you change the keymap</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -275,12 +329,11 @@ class UiTab extends React.Component {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='group-control'>
|
||||
<div styleName='group-control'>
|
||||
<button styleName='group-control-rightButton'
|
||||
onClick={(e) => this.handleSaveUIClick(e)}
|
||||
>
|
||||
Save
|
||||
onClick={(e) => this.handleSaveUIClick(e)}>Save
|
||||
</button>
|
||||
{UiAlertElement}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,10 +4,12 @@ import { connect } from 'react-redux'
|
||||
import HotkeyTab from './HotkeyTab'
|
||||
import UiTab from './UiTab'
|
||||
import InfoTab from './InfoTab'
|
||||
import Crowdfunding from './Crowdfunding'
|
||||
import StoragesTab from './StoragesTab'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './PreferencesModal.styl'
|
||||
import RealtimeNotification from 'browser/components/RealtimeNotification'
|
||||
|
||||
class Preferences extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -64,6 +66,10 @@ class Preferences extends React.Component {
|
||||
config={config}
|
||||
/>
|
||||
)
|
||||
case 'CROWDFUNDING':
|
||||
return (
|
||||
<Crowdfunding />
|
||||
)
|
||||
case 'STORAGES':
|
||||
default:
|
||||
return (
|
||||
@@ -94,7 +100,8 @@ class Preferences extends React.Component {
|
||||
{target: 'STORAGES', label: 'Storages'},
|
||||
{target: 'HOTKEY', label: 'Hotkey'},
|
||||
{target: 'UI', label: 'UI'},
|
||||
{target: 'INFO', label: 'Info'}
|
||||
{target: 'INFO', label: 'Community / Info'},
|
||||
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
|
||||
]
|
||||
|
||||
let navButtons = tabs.map((tab) => {
|
||||
@@ -130,6 +137,7 @@ class Preferences extends React.Component {
|
||||
<div ref='content' styleName='content'>
|
||||
{content}
|
||||
</div>
|
||||
<RealtimeNotification />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user