mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
1203 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7a5b789fa | ||
|
|
10b7d58dc6 | ||
|
|
16f0e95e32 | ||
|
|
55395d3a2d | ||
|
|
4e0fa63fad | ||
|
|
b9ea7696d8 | ||
|
|
7de3308f52 | ||
|
|
b2b2373c7b | ||
|
|
1c54f40a28 | ||
|
|
4735992835 | ||
|
|
cba3519458 | ||
|
|
c64a5e1cca | ||
|
|
47ee8b8ce7 | ||
|
|
455b424429 | ||
|
|
7d67ac3f12 | ||
|
|
34f377eb5c | ||
|
|
b7f4af8c78 | ||
|
|
a6c7dde194 | ||
|
|
43ebe4ecfd | ||
|
|
1d38f1abb4 | ||
|
|
061a0cd219 | ||
|
|
f0ed20ee2c | ||
|
|
edfc8d95c8 | ||
|
|
c33f9d8307 | ||
|
|
eee212f5b8 | ||
|
|
b690147b0b | ||
|
|
10879d0f67 | ||
|
|
b48b8f39fc | ||
|
|
3d0b3e759b | ||
|
|
3e7f4a41e2 | ||
|
|
b89b888129 | ||
|
|
cba9afc9ba | ||
|
|
a66a11b81e | ||
|
|
22cc2791b6 | ||
|
|
369cf16b68 | ||
|
|
0d38baf194 | ||
|
|
5b63c95f40 | ||
|
|
52497999a0 | ||
|
|
6bab108a35 | ||
|
|
eec22e6b7d | ||
|
|
edaa0713e8 | ||
|
|
6b6a415dd5 | ||
|
|
414e0dbc05 | ||
|
|
081a3da9e1 | ||
|
|
3fbc749395 | ||
|
|
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 | ||
|
|
460437397f | ||
|
|
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 | ||
|
|
a36e779980 | ||
|
|
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 | ||
|
|
84f18ced47 | ||
|
|
037ff2e749 | ||
|
|
f0f23ede3d | ||
|
|
c8763063c0 | ||
|
|
e57fef2413 | ||
|
|
f1a90f4a11 | ||
|
|
990d7edba4 | ||
|
|
13a1da91be | ||
|
|
930b58d2a8 | ||
|
|
b88b1065ee | ||
|
|
5fbb802b32 | ||
|
|
a40f8d25ef | ||
|
|
0a28798d54 | ||
|
|
6047987c25 | ||
|
|
2ce96186f2 | ||
|
|
26357bd4bc | ||
|
|
e94b45a7e4 | ||
|
|
4be1eb7a28 | ||
|
|
f4bbbb640d | ||
|
|
94b4e70b0f | ||
|
|
b31e3f7783 | ||
|
|
9095fe934d | ||
|
|
9139495f02 | ||
|
|
6375ac857a | ||
|
|
5eac08430f | ||
|
|
ef0b109ad4 | ||
|
|
7fbe6c0955 | ||
|
|
c86e43597c | ||
|
|
123a73a5e6 | ||
|
|
706b5d253f | ||
|
|
8fb8c7a40b | ||
|
|
21220f93b1 | ||
|
|
705e64377b | ||
|
|
5c3f0cd060 | ||
|
|
bcb1fb4331 | ||
|
|
a238be5b7c | ||
|
|
2351bb78da | ||
|
|
6bbc5a91fe | ||
|
|
70b69a3bc9 | ||
|
|
2f52233bd0 | ||
|
|
a7e458b784 | ||
|
|
a504a45d99 | ||
|
|
2d0e14c1cc | ||
|
|
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 | ||
|
|
2027f60014 | ||
|
|
076edd375f | ||
|
|
ab1aa56059 | ||
|
|
46f7dfdfeb | ||
|
|
fcaa5e21cf | ||
|
|
1e202db50f | ||
|
|
9405b95825 | ||
|
|
f05e256afc | ||
|
|
731ffd4a22 | ||
|
|
8f4566b7e1 | ||
|
|
95aec54f60 | ||
|
|
f14ce0d68e | ||
|
|
cc1c7f3820 | ||
|
|
2df901288a | ||
|
|
821a7c780e | ||
|
|
6e2e48fa64 | ||
|
|
2864ac88f5 | ||
|
|
4a292d6518 | ||
|
|
e934182e86 | ||
|
|
d8fa73287b | ||
|
|
35938c09e8 | ||
|
|
9eaa90c691 | ||
|
|
049835d426 | ||
|
|
af91c40406 | ||
|
|
4940ad6825 | ||
|
|
d02b740300 | ||
|
|
9cb443dc2f | ||
|
|
1c7cba2951 | ||
|
|
473b80710d | ||
|
|
2247c0835d | ||
|
|
b7b715ba3d | ||
|
|
6c43fb2325 | ||
|
|
a6fe3c27d4 | ||
|
|
d47ff96b13 | ||
|
|
a0def654bd | ||
|
|
4873b40e49 | ||
|
|
0a758f20a7 | ||
|
|
5e58d457a3 | ||
|
|
0f745361ad | ||
|
|
bf6cae9a0e | ||
|
|
ab640a7676 | ||
|
|
820171e19e | ||
|
|
f1e9d0ab81 | ||
|
|
0646484c83 | ||
|
|
a27b79c213 | ||
|
|
773a9b4b7f | ||
|
|
07b838ef7b | ||
|
|
85217a7171 | ||
|
|
886d7b7227 | ||
|
|
6987b762dd | ||
|
|
f32ac81f84 | ||
|
|
87ea66bb92 | ||
|
|
ff6fd62932 | ||
|
|
76728448ff | ||
|
|
3b7225e0fa | ||
|
|
d6280f4397 | ||
|
|
8df867046f | ||
|
|
331c822816 | ||
|
|
6219173945 | ||
|
|
6207e02e7f | ||
|
|
537ba537dc | ||
|
|
3e919241e6 | ||
|
|
2324327e7e | ||
|
|
b8374494ea | ||
|
|
a480ca7b55 | ||
|
|
bfd67fb7f1 | ||
|
|
e6bd6a5077 | ||
|
|
350af844ca | ||
|
|
a49d53179a | ||
|
|
e68069fd2f | ||
|
|
a193ba3e6c | ||
|
|
9034b3ab54 | ||
|
|
f39b7594ab | ||
|
|
f79734391e | ||
|
|
e54f516418 | ||
|
|
e86464535d | ||
|
|
e8553caa65 | ||
|
|
044e6b7180 | ||
|
|
0266770657 | ||
|
|
2d6f7c08e8 | ||
|
|
0a27819a7f | ||
|
|
3185c25ee1 | ||
|
|
820802cdc2 | ||
|
|
2c169e6f15 | ||
|
|
49be1320f9 | ||
|
|
6bc4ecce48 | ||
|
|
0290d23832 | ||
|
|
e001c97e01 | ||
|
|
976d1bb4f3 | ||
|
|
dee6495b08 | ||
|
|
4a77f250f1 | ||
|
|
c697f19642 | ||
|
|
f7d1f9e949 | ||
|
|
27b9952f8e | ||
|
|
961dab4a97 | ||
|
|
930b6bc927 | ||
|
|
ed7d8258cf | ||
|
|
762425125f | ||
|
|
03bdf0c653 | ||
|
|
7002026190 | ||
|
|
b35395e6bd | ||
|
|
b74fb76d03 | ||
|
|
72d2df465b | ||
|
|
2592c943f7 | ||
|
|
7b3a3ba200 | ||
|
|
8369832585 | ||
|
|
d94a674343 | ||
|
|
260611ffd6 | ||
|
|
690549b57f | ||
|
|
5d9aeb4c04 | ||
|
|
eddfdea2ca | ||
|
|
a40385f87f | ||
|
|
0123526c98 | ||
|
|
275e3317a3 | ||
|
|
d0b835a825 | ||
|
|
f450260ff8 | ||
|
|
5f7b119e5c | ||
|
|
bb9eb494e9 | ||
|
|
6ea2b5e1d9 | ||
|
|
b21baf1ce5 | ||
|
|
4c3fd461e4 | ||
|
|
8365a60d5d | ||
|
|
804ac1aa96 | ||
|
|
49fd2cfc4d | ||
|
|
2bb2a52f27 | ||
|
|
8d71b28afa | ||
|
|
86eac7054d | ||
|
|
dccb92d72b | ||
|
|
2e628de9c6 | ||
|
|
2d243abc12 | ||
|
|
ae08bf4d7a | ||
|
|
cbff5fb585 | ||
|
|
2650cc2f1c | ||
|
|
ec560ceab1 | ||
|
|
ad17cb8837 | ||
|
|
4f98d9641a | ||
|
|
17535ccd4c | ||
|
|
110c1ea337 | ||
|
|
7e087bfbab | ||
|
|
85e0d4b922 | ||
|
|
265262ccbf | ||
|
|
345008e9b6 | ||
|
|
849aa05d76 | ||
|
|
5ff5ec6a71 | ||
|
|
684c3f64aa | ||
|
|
4ff73ede59 | ||
|
|
7302d83f60 | ||
|
|
f9836fd66e | ||
|
|
c0f05695fe | ||
|
|
e50a1172d6 | ||
|
|
7a4234e73c | ||
|
|
4b9640e5a3 | ||
|
|
4b58054100 | ||
|
|
8951dbf4ed | ||
|
|
5047bc94eb | ||
|
|
75fb29c594 | ||
|
|
bd75f7fd2c | ||
|
|
8b8cb3c9b4 | ||
|
|
b479b21c37 | ||
|
|
525c490704 | ||
|
|
2e3599d005 | ||
|
|
f005bb1e46 | ||
|
|
21f96febdb | ||
|
|
805829be78 | ||
|
|
a7bd3f253f | ||
|
|
81e8a290f0 | ||
|
|
f46a967b6e | ||
|
|
b78d9534aa | ||
|
|
bdefaf7427 | ||
|
|
43550c7dec | ||
|
|
1b07d393f2 | ||
|
|
44fc356775 | ||
|
|
5623c68170 | ||
|
|
d5f82943cf | ||
|
|
96a020341d | ||
|
|
99b5f75763 | ||
|
|
a128ff7cd8 | ||
|
|
59329cc5da | ||
|
|
ecd5daaf55 | ||
|
|
514232d720 | ||
|
|
93abfe3202 | ||
|
|
4d9e0e3bd7 | ||
|
|
75d9bf8e38 | ||
|
|
ccb9752254 | ||
|
|
0f2ca19c6c | ||
|
|
d5a77aade4 | ||
|
|
3c89e6a128 | ||
|
|
a36be79977 | ||
|
|
617f0e0f9c | ||
|
|
0db1a94105 | ||
|
|
f29f97a00f | ||
|
|
032b5a59f2 | ||
|
|
1e5bc395ac | ||
|
|
fef4afe660 | ||
|
|
679ed1eacf | ||
|
|
78a9320017 | ||
|
|
927981bf30 | ||
|
|
70ea2f6c14 | ||
|
|
a6779414ff | ||
|
|
b62fcf523a | ||
|
|
d0bbea5a97 | ||
|
|
212d7027fc | ||
|
|
be1a5b09da | ||
|
|
3de2db4459 | ||
|
|
f2405a5b34 | ||
|
|
94abb8f959 | ||
|
|
2e30db05bc | ||
|
|
c93f3cc5dd | ||
|
|
068f8f2ba9 | ||
|
|
0980c3b012 | ||
|
|
5288d6768f | ||
|
|
34491f4ea4 | ||
|
|
8c73ca8854 | ||
|
|
0786d8eab6 | ||
|
|
209518c815 | ||
|
|
b2753b6457 | ||
|
|
4775a920c0 | ||
|
|
0e94dc8740 | ||
|
|
cf68d202d5 | ||
|
|
d29ba2bf16 | ||
|
|
720686cef5 | ||
|
|
0625c65cf0 | ||
|
|
acaefe22d1 | ||
|
|
df1f083ebf | ||
|
|
5a1dfc2ca9 | ||
|
|
d84894f1bf | ||
|
|
0fdc444c4c | ||
|
|
4d216c6f13 | ||
|
|
1c0af8eede | ||
|
|
f443f9264a | ||
|
|
d9b2981327 | ||
|
|
68e36d2a6d | ||
|
|
68b91bf98c | ||
|
|
b91ddfad05 | ||
|
|
a110cbfb5d | ||
|
|
d69f45b0c9 | ||
|
|
07df26d5c4 | ||
|
|
d24bcb7f86 | ||
|
|
9f383ba491 | ||
|
|
c38a76d587 | ||
|
|
994ca5dd02 | ||
|
|
9281f8f6cb | ||
|
|
324f579474 | ||
|
|
10bd2d4547 | ||
|
|
d1942868e4 | ||
|
|
a409b3e48d | ||
|
|
21004aab6a | ||
|
|
eaf88c6491 | ||
|
|
17dcd1b6f1 | ||
|
|
244a06eea6 | ||
|
|
afb13af7a1 | ||
|
|
ff9b935e98 | ||
|
|
4250d6fe52 | ||
|
|
5bc2094f10 | ||
|
|
552653c0ed | ||
|
|
c9bbc61c95 | ||
|
|
41a04aa3f1 | ||
|
|
cd421c4662 | ||
|
|
063e2e02bd | ||
|
|
0c3019b52e | ||
|
|
2ee2494dc4 | ||
|
|
df6ff1fffe | ||
|
|
0d2e6a6a12 | ||
|
|
79ed55a76f | ||
|
|
cbe58b9437 | ||
|
|
afc729b1c3 | ||
|
|
5d0cb0302e | ||
|
|
29e0d121cd | ||
|
|
48ca13f82c | ||
|
|
278061e4f1 | ||
|
|
186a815821 | ||
|
|
2fea9eb874 | ||
|
|
f8b6453be9 | ||
|
|
be625f8884 | ||
|
|
2271def5d3 | ||
|
|
275a8ea7cc | ||
|
|
dc236f33b1 | ||
|
|
678d739e75 | ||
|
|
8fe05a4c24 | ||
|
|
77adfdb9f0 | ||
|
|
30c94028fb | ||
|
|
b5bf0780fa | ||
|
|
ad838d82ee | ||
|
|
d5ec0c0cdd | ||
|
|
ff29f1e0c6 | ||
|
|
1f58698a04 | ||
|
|
a275f331d0 | ||
|
|
0862c6e059 | ||
|
|
46fddfd26c | ||
|
|
c547ccb4cb | ||
|
|
86e82fb149 | ||
|
|
00a284bfbd | ||
|
|
f6fbec0a2e | ||
|
|
b24c06bee6 | ||
|
|
95e7f4f645 | ||
|
|
4b75d501f5 | ||
|
|
b28d5c8b25 | ||
|
|
86e61661e6 | ||
|
|
cdd5717e63 | ||
|
|
42bd7fb4fb | ||
|
|
66e478a001 | ||
|
|
c8259abcac | ||
|
|
d6d16a63a4 | ||
|
|
df8d1f0714 | ||
|
|
fbbc6411c3 | ||
|
|
bc1e94fcab | ||
|
|
418439e3d5 | ||
|
|
46cbd04ba7 | ||
|
|
0ade6d9ece | ||
|
|
823599192f | ||
|
|
9496ab88f7 | ||
|
|
290e6ab170 | ||
|
|
a9e3572f4f | ||
|
|
92b2b7dde3 | ||
|
|
dad1e40234 | ||
|
|
b75c8b0373 | ||
|
|
fc4c471a87 | ||
|
|
24de71f240 | ||
|
|
805c39e60c | ||
|
|
10e75041e8 | ||
|
|
2364348df4 | ||
|
|
e643443660 | ||
|
|
d72e876b23 | ||
|
|
f3612774ba | ||
|
|
67d8b02c49 | ||
|
|
7abf53009a | ||
|
|
90bb4632b6 | ||
|
|
630da00235 | ||
|
|
245e0dd7ee | ||
|
|
824e288a80 | ||
|
|
99228f2e60 | ||
|
|
0f71139eba | ||
|
|
2bbe7056d1 | ||
|
|
005d8f84fd | ||
|
|
908cd7a890 | ||
|
|
d4865adf6a | ||
|
|
20411a2fd5 | ||
|
|
19f8930f5a | ||
|
|
ffc4ca1dd4 | ||
|
|
0bc9c6fb5a | ||
|
|
983f453afd | ||
|
|
60a0293495 | ||
|
|
4807d22763 | ||
|
|
e8d451bcbb | ||
|
|
4809bb6f06 | ||
|
|
d6d694cf66 | ||
|
|
132e3c3088 | ||
|
|
3c7ff78549 | ||
|
|
adbdc2cb1c | ||
|
|
e52b74bbc9 | ||
|
|
575f1b4b33 | ||
|
|
a528c99900 | ||
|
|
e6047ed383 | ||
|
|
381b7d960a | ||
|
|
045a8bde22 | ||
|
|
e528182c2a | ||
|
|
b86cdb461a | ||
|
|
7265f76770 | ||
|
|
2ed092279d | ||
|
|
7d71819be0 | ||
|
|
e848c74511 | ||
|
|
968ed146b2 | ||
|
|
5e03f12875 | ||
|
|
09f8e5f25a | ||
|
|
89f4ed3006 | ||
|
|
8d14a9557d | ||
|
|
513042d769 | ||
|
|
c28980c2a9 | ||
|
|
1c31ff4e98 | ||
|
|
7c9d3904b3 | ||
|
|
e23707f0a0 | ||
|
|
a952b18b96 | ||
|
|
35cc07bf63 | ||
|
|
118bf18434 | ||
|
|
34da15208b | ||
|
|
e0dc62c00b | ||
|
|
b58df2f172 | ||
|
|
90846fab81 | ||
|
|
efd80d5c0c | ||
|
|
e37e28a22e | ||
|
|
a1e71b318c | ||
|
|
aee6541d45 | ||
|
|
50ad5e3791 | ||
|
|
b34d72f21a | ||
|
|
e5ae420ef6 | ||
|
|
7269750264 | ||
|
|
33fe3ff733 | ||
|
|
aa77180957 | ||
|
|
3d80b6a4cd | ||
|
|
d76f9243a3 | ||
|
|
88f1a0d5cd | ||
|
|
26c435d6da | ||
|
|
e66abdea2d | ||
|
|
c7d2eeb71a | ||
|
|
0e8d681954 | ||
|
|
433d110cf0 | ||
|
|
4a514cd7bd | ||
|
|
417fee16bd | ||
|
|
49a821d9ee | ||
|
|
6a5ce098e0 | ||
|
|
1af73eebea | ||
|
|
b7ba29ac92 | ||
|
|
133c2ec308 | ||
|
|
a70fe1bba8 | ||
|
|
6dcd653eac | ||
|
|
db2f90b1ce | ||
|
|
60e5665133 | ||
|
|
7aa982849f | ||
|
|
d94f7626c2 | ||
|
|
549c289f81 | ||
|
|
b35953d1f9 | ||
|
|
941c4aeb19 | ||
|
|
11f7fcbaef | ||
|
|
6b8488ae0f | ||
|
|
ee5268a07e | ||
|
|
28bc331318 | ||
|
|
098153b6ba | ||
|
|
878f31e91e | ||
|
|
b40d09fa86 | ||
|
|
eb48f48f6b | ||
|
|
7961008500 | ||
|
|
ff87c6b226 | ||
|
|
0c2807a08b | ||
|
|
f0cf369317 | ||
|
|
5e9954c060 | ||
|
|
0c7bdf20af | ||
|
|
9c1179a6f9 | ||
|
|
43cb290c80 | ||
|
|
f8b7b7df9f | ||
|
|
b73f0a8012 | ||
|
|
75b2c7bd2e | ||
|
|
5fd9866eef | ||
|
|
4e7204bdbc | ||
|
|
ff7024e38f | ||
|
|
8c11a0b42d | ||
|
|
2df295dc1d | ||
|
|
b695d27817 | ||
|
|
8e2fd300f6 | ||
|
|
1722e103fc | ||
|
|
71464112ce | ||
|
|
003d8a1b21 | ||
|
|
adf81175f3 | ||
|
|
39274ce483 | ||
|
|
1daf07edeb | ||
|
|
cd2dc471c7 | ||
|
|
6229ca7ac9 | ||
|
|
10043cc755 | ||
|
|
e60a5430b4 | ||
|
|
f7e0cb655f | ||
|
|
04097ecfcd | ||
|
|
2222192bcd | ||
|
|
ae93c38d46 | ||
|
|
9ff9dcbe6d | ||
|
|
94d442af7d | ||
|
|
6a711d6a71 | ||
|
|
9d8e71aeb3 | ||
|
|
cfeeba209e | ||
|
|
455029851a | ||
|
|
d0f7baaad0 | ||
|
|
e3fb236139 | ||
|
|
42296e421a | ||
|
|
c6f4ed7c8f | ||
|
|
e1fb36d64d | ||
|
|
48cf695e11 | ||
|
|
5b0e0c71a0 | ||
|
|
c1a76b6fb4 | ||
|
|
945a6306ec | ||
|
|
313bacf9dc | ||
|
|
9027f48dda | ||
|
|
eea8f7cdf4 | ||
|
|
ffef239aa7 | ||
|
|
50fc15feea | ||
|
|
2d7a37c872 | ||
|
|
db468fc095 | ||
|
|
9a9f0035c2 | ||
|
|
b9270cd040 | ||
|
|
65b1bd18c4 | ||
|
|
c87ecc3d40 | ||
|
|
e39e1648f9 | ||
|
|
091d2618a2 | ||
|
|
d039b17715 | ||
|
|
e350dca72c | ||
|
|
c07e334f03 | ||
|
|
1ccfd8e392 | ||
|
|
d22c40f0a5 | ||
|
|
3d37db6e54 | ||
|
|
22bd92916b | ||
|
|
de303cf072 | ||
|
|
b22aad0678 | ||
|
|
bf02f9b256 | ||
|
|
6440ab423e | ||
|
|
fceab73226 | ||
|
|
fded0ad3e8 | ||
|
|
294eb64686 | ||
|
|
851b28c482 | ||
|
|
63fa2f1149 | ||
|
|
816d8decbd | ||
|
|
85b4eedcd6 | ||
|
|
901f40a669 | ||
|
|
bae5f202a5 | ||
|
|
131f6780c2 | ||
|
|
a0e067cacf | ||
|
|
8cdcf537be | ||
|
|
575f5f160c | ||
|
|
d8fbac584c | ||
|
|
b73e1b04dc | ||
|
|
3dd49c287d | ||
|
|
6ce6a7036b | ||
|
|
c120633f14 | ||
|
|
6e84b24309 | ||
|
|
343e35bb54 | ||
|
|
09cf94d807 | ||
|
|
a5531e20f2 | ||
|
|
0f311120af | ||
|
|
614506cada | ||
|
|
7891d14a0a | ||
|
|
2df12b6891 | ||
|
|
97b42d6be1 | ||
|
|
39baadeb04 | ||
|
|
a6f5452a85 | ||
|
|
29dc3bd550 | ||
|
|
e103605956 | ||
|
|
c510c2e540 | ||
|
|
41d65e4132 | ||
|
|
b6cb532568 | ||
|
|
a034ea3a05 | ||
|
|
adeb45a9ce | ||
|
|
4ada755793 | ||
|
|
c4be052a49 | ||
|
|
54c2d7bac9 | ||
|
|
233ab17992 | ||
|
|
e251ec64dc | ||
|
|
32bd6d76ee | ||
|
|
3fc17634aa | ||
|
|
555d725e7b | ||
|
|
35416796b5 | ||
|
|
6c737fe25f | ||
|
|
1b58e320aa | ||
|
|
658a90bf15 | ||
|
|
d092e75f3c | ||
|
|
162fae19cc | ||
|
|
58f5035ec6 | ||
|
|
c5076e4e95 | ||
|
|
28ef1e625c | ||
|
|
97441ccacb | ||
|
|
6b29aed6c4 | ||
|
|
31eb9caee4 | ||
|
|
64cf34e673 | ||
|
|
8996ebb819 | ||
|
|
a36c62044b | ||
|
|
13418109ea | ||
|
|
fe7c05aaa5 | ||
|
|
116e27e0db | ||
|
|
6b135afe1a | ||
|
|
7a9c4951a2 | ||
|
|
041a51a70f | ||
|
|
7b4ff9906e | ||
|
|
11ab5c7598 | ||
|
|
64d53c611b | ||
|
|
657d69a0fb | ||
|
|
62d39e6715 | ||
|
|
a27d8192ee | ||
|
|
41b69afe03 | ||
|
|
54b5af0741 | ||
|
|
d4060f8a5a | ||
|
|
2935d41aba | ||
|
|
af6cd10e28 | ||
|
|
435c80d870 | ||
|
|
775cec32da | ||
|
|
e8cc7abadc | ||
|
|
c1051afdc0 | ||
|
|
573d3ce11e | ||
|
|
0a89dcc6d8 | ||
|
|
d45033ae8e | ||
|
|
313e8b8c98 | ||
|
|
25685dc8b0 | ||
|
|
d6a78dfe28 | ||
|
|
de45852790 | ||
|
|
c6a505cb44 | ||
|
|
44427a40b7 | ||
|
|
8a2ac08c0a | ||
|
|
7babf66d5f | ||
|
|
50cd0b794b | ||
|
|
720f07f62c | ||
|
|
6daffbcafa | ||
|
|
b76729e836 | ||
|
|
b2294e0fc9 | ||
|
|
3559737e8e | ||
|
|
40b7ce607b | ||
|
|
1de67a00cb | ||
|
|
6b4b44dba8 | ||
|
|
c424cc5d33 | ||
|
|
5b71d010b4 | ||
|
|
b9457d3e33 | ||
|
|
60e267409c | ||
|
|
e6a2521143 | ||
|
|
ae36ed2b46 | ||
|
|
fb2ed81fd3 | ||
|
|
a74651b515 | ||
|
|
6904c192e4 | ||
|
|
6540d2670c | ||
|
|
966ba06bc4 | ||
|
|
3b4921b848 | ||
|
|
b11e10ac07 | ||
|
|
2ec7ba04f5 | ||
|
|
095910d156 | ||
|
|
c39463aea8 | ||
|
|
87e47c7ffb | ||
|
|
359f6734c5 | ||
|
|
1d3e71cf49 | ||
|
|
1ab449cecf | ||
|
|
44dd609134 | ||
|
|
1e8e161a33 | ||
|
|
c56d232e58 | ||
|
|
893a92c87b | ||
|
|
7065fad69b | ||
|
|
6fcbca6b10 | ||
|
|
93b15f2a7a | ||
|
|
df9d8ff735 | ||
|
|
fda17e044e | ||
|
|
b4e54fc149 | ||
|
|
0f3230110c | ||
|
|
65573bd4db | ||
|
|
928df018dc | ||
|
|
b2187b72ab | ||
|
|
aae2bddd32 | ||
|
|
b6eddf0821 | ||
|
|
fac0abaed6 | ||
|
|
d07c62e266 | ||
|
|
5a201dd1b9 | ||
|
|
c65db4e2b0 | ||
|
|
d6171dc502 | ||
|
|
7b5a7aabed | ||
|
|
77eb19af40 | ||
|
|
e68d535fa2 | ||
|
|
743b220953 | ||
|
|
b6304a04e6 |
33
.boostnoterc.sample
Normal file
33
.boostnoterc.sample
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"amaEnabled": true,
|
||||||
|
"editor": {
|
||||||
|
"fontFamily": "Monaco, Consolas",
|
||||||
|
"fontSize": "14",
|
||||||
|
"indentSize": "2",
|
||||||
|
"indentType": "space",
|
||||||
|
"keyMap": "vim",
|
||||||
|
"switchPreview": "BLUR",
|
||||||
|
"theme": "monokai"
|
||||||
|
},
|
||||||
|
"hotkey": {
|
||||||
|
"toggleFinder": "Cmd + Alt + S",
|
||||||
|
"toggleMain": "Cmd + Alt + L"
|
||||||
|
},
|
||||||
|
"isSideNavFolded": false,
|
||||||
|
"listStyle": "DEFAULT",
|
||||||
|
"listWidth": 174,
|
||||||
|
"navWidth": 200,
|
||||||
|
"preview": {
|
||||||
|
"codeBlockTheme": "dracula",
|
||||||
|
"fontFamily": "Lato",
|
||||||
|
"fontSize": "14",
|
||||||
|
"lineNumber": true
|
||||||
|
},
|
||||||
|
"sortBy": "UPDATED_AT",
|
||||||
|
"ui": {
|
||||||
|
"defaultNote": "ALWAYS_ASK",
|
||||||
|
"disableDirectWrite": false,
|
||||||
|
"theme": "default"
|
||||||
|
},
|
||||||
|
"zoom": 1
|
||||||
|
}
|
||||||
19
.eslintrc
19
.eslintrc
@@ -1,6 +1,21 @@
|
|||||||
{
|
{
|
||||||
"extends": ["standard", "standard-jsx"],
|
"extends": ["standard", "standard-jsx", "plugin:react/recommended"],
|
||||||
|
"plugins": ["react"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-useless-escape": 0
|
"no-useless-escape": 0,
|
||||||
|
"prefer-const": "warn",
|
||||||
|
"no-unused-vars": "warn",
|
||||||
|
"no-undef": "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"
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"FileReader": true,
|
||||||
|
"localStorage": true,
|
||||||
|
"fetch": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
.snapcraft/travis_snapcraft.cfg
Normal file
BIN
.snapcraft/travis_snapcraft.cfg
Normal file
Binary file not shown.
22
.travis.yml
22
.travis.yml
@@ -1,6 +1,20 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 'stable'
|
- stable
|
||||||
- 'lts/*'
|
- lts/*
|
||||||
|
script:
|
||||||
script: npm run lint && npm run test
|
- npm run lint && npm run test
|
||||||
|
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
|
||||||
|
after_success:
|
||||||
|
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
||||||
|
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
|
||||||
|
sudo: required
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
deploy:
|
||||||
|
'on':
|
||||||
|
branch: master
|
||||||
|
provider: script
|
||||||
|
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
|
||||||
|
&& cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
|
||||||
|
skip_cleanup: true
|
||||||
|
|||||||
72
Backers.md
Normal file
72
Backers.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<h1 align="center">Sponsors & Backers</h1>
|
||||||
|
|
||||||
|
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. If you'd like to join them, please consider:
|
||||||
|
|
||||||
|
- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backers via OpenCollective
|
||||||
|
|
||||||
|
### [Gold Sponsors / $1,000 per month](https://opencollective.com/boostnoteio/order/2259)
|
||||||
|
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
|
||||||
|
|
||||||
|
### [Silver Sponsors / $250 per month](https://opencollective.com/boostnoteio/order/2257)
|
||||||
|
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
|
||||||
|
|
||||||
|
### [Bronze Sponsors / $50 per month](https://opencollective.com/boostnoteio/order/2258)
|
||||||
|
- Get your name and Url (or E-mail) on Readme.md on GitHub.
|
||||||
|
|
||||||
|
### [Backers3 / $10 per month](https://opencollective.com/boostnoteio/order/2176)
|
||||||
|
- [Ralph03](https://opencollective.com/ralph03)
|
||||||
|
|
||||||
|
- [Nikolas Dan](https://opencollective.com/nikolas-dan)
|
||||||
|
|
||||||
|
### [Backers2 / $5 per month](https://opencollective.com/boostnoteio/order/2175)
|
||||||
|
- [Yeojong Kim](https://twitter.com/yeojoy)
|
||||||
|
|
||||||
|
- [Scotia Draven](https://opencollective.com/scotia-draven)
|
||||||
|
|
||||||
|
- [A. J. Vargas](https://opencollective.com/aj-vargas)
|
||||||
|
|
||||||
|
### [Backers1](https://opencollective.com/boostnoteio/order/2563) and One-time sponsors
|
||||||
|
- Ryosuke Tamura - $30
|
||||||
|
|
||||||
|
- tatoosh11 - $10
|
||||||
|
|
||||||
|
- Alexander Borovkov - $10
|
||||||
|
|
||||||
|
- spoonhoop - $5
|
||||||
|
|
||||||
|
- Drew Williams - $2
|
||||||
|
|
||||||
|
- Andy Shaw - $2
|
||||||
|
|
||||||
|
- mysafesky -$2
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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
|
||||||
10
ISSUE_TEMPLATE.md
Normal file
10
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<!--
|
||||||
|
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
|
||||||
|
-->
|
||||||
4
LICENSE
4
LICENSE
@@ -1,8 +1,8 @@
|
|||||||
GPL-3.0
|
GPL-3.0
|
||||||
|
|
||||||
Boostnote - the simplest note app
|
Boostnote - an open source note-taking app made for programmers just like you.
|
||||||
|
|
||||||
Copyright (C) 2016 MAISIN&CO.
|
Copyright (C) 2017 Maisin&Co., Inc.
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
import path from 'path'
|
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'
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
@@ -38,6 +42,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
this.props.onBlur != null && this.props.onBlur(e)
|
this.props.onBlur != null && this.props.onBlur(e)
|
||||||
}
|
}
|
||||||
|
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||||
this.loadStyleHandler = (e) => {
|
this.loadStyleHandler = (e) => {
|
||||||
this.editor.refresh()
|
this.editor.refresh()
|
||||||
}
|
}
|
||||||
@@ -58,17 +63,37 @@ export default class CodeEditor extends React.Component {
|
|||||||
dragDrop: false,
|
dragDrop: false,
|
||||||
extraKeys: {
|
extraKeys: {
|
||||||
Tab: function (cm) {
|
Tab: function (cm) {
|
||||||
|
const cursor = cm.getCursor()
|
||||||
|
const line = cm.getLine(cursor.line)
|
||||||
if (cm.somethingSelected()) cm.indentSelection('add')
|
if (cm.somethingSelected()) cm.indentSelection('add')
|
||||||
else {
|
else {
|
||||||
if (cm.getOption('indentWithTabs')) {
|
const tabs = cm.getOption('indentWithTabs')
|
||||||
cm.execCommand('insertTab')
|
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)\] )?$/)) {
|
||||||
|
cm.execCommand('goLineStart')
|
||||||
|
if (tabs) {
|
||||||
|
cm.execCommand('insertTab')
|
||||||
|
} else {
|
||||||
|
cm.execCommand('insertSoftTab')
|
||||||
|
}
|
||||||
|
cm.execCommand('goLineEnd')
|
||||||
} else {
|
} else {
|
||||||
cm.execCommand('insertSoftTab')
|
if (tabs) {
|
||||||
|
cm.execCommand('insertTab')
|
||||||
|
} else {
|
||||||
|
cm.execCommand('insertSoftTab')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Cmd-T': function (cm) {
|
'Cmd-T': function (cm) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
},
|
||||||
|
Enter: 'newlineAndIndentContinueMarkdownList',
|
||||||
|
'Ctrl-C': (cm) => {
|
||||||
|
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
||||||
|
document.execCommand('copy')
|
||||||
|
}
|
||||||
|
return CodeMirror.Pass
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -77,15 +102,27 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
this.editor.on('blur', this.blurHandler)
|
this.editor.on('blur', this.blurHandler)
|
||||||
this.editor.on('change', this.changeHandler)
|
this.editor.on('change', this.changeHandler)
|
||||||
|
this.editor.on('paste', this.pasteHandler)
|
||||||
|
|
||||||
let editorTheme = document.getElementById('editorTheme')
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
editorTheme.addEventListener('load', this.loadStyleHandler)
|
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 () {
|
componentWillUnmount () {
|
||||||
this.editor.off('blur', this.blurHandler)
|
this.editor.off('blur', this.blurHandler)
|
||||||
this.editor.off('change', this.changeHandler)
|
this.editor.off('change', this.changeHandler)
|
||||||
let editorTheme = document.getElementById('editorTheme')
|
this.editor.off('paste', this.pasteHandler)
|
||||||
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,10 +194,11 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.setValue(this.props.value)
|
this.editor.setValue(this.props.value)
|
||||||
this.editor.clearHistory()
|
this.editor.clearHistory()
|
||||||
this.editor.on('change', this.changeHandler)
|
this.editor.on('change', this.changeHandler)
|
||||||
|
this.editor.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue (value) {
|
setValue (value) {
|
||||||
let cursor = this.editor.getCursor()
|
const cursor = this.editor.getCursor()
|
||||||
this.editor.setValue(value)
|
this.editor.setValue(value)
|
||||||
this.editor.setCursor(cursor)
|
this.editor.setCursor(cursor)
|
||||||
}
|
}
|
||||||
@@ -169,17 +207,44 @@ export default class CodeEditor extends React.Component {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const imagePath = e.dataTransfer.files[0].path
|
const imagePath = e.dataTransfer.files[0].path
|
||||||
const filename = path.basename(imagePath)
|
const filename = path.basename(imagePath)
|
||||||
const imageMd = `})`
|
|
||||||
this.insertImage(imageMd)
|
copyImage(imagePath, this.props.storageKey).then((imagePath) => {
|
||||||
|
const imageMd = `})`
|
||||||
|
this.insertImageMd(imageMd)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
insertImage (imageMd) {
|
insertImageMd (imageMd) {
|
||||||
const textarea = this.editor.getInputField()
|
this.editor.replaceSelection(imageMd)
|
||||||
textarea.value = textarea.value.substr(0, textarea.selectionStart) + imageMd + textarea.value.substr(textarea.selectionEnd)
|
}
|
||||||
|
|
||||||
|
handlePaste (editor, e) {
|
||||||
|
const dataTransferItem = e.clipboardData.items[0]
|
||||||
|
if (!dataTransferItem.type.match('image')) return
|
||||||
|
|
||||||
|
const blob = dataTransferItem.getAsFile()
|
||||||
|
const 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 () {
|
render () {
|
||||||
let { className, fontFamily, fontSize } = this.props
|
const { className, fontSize } = this.props
|
||||||
|
let fontFamily = this.props.className
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||||
? [fontFamily].concat(defaultEditorFontFamily)
|
? [fontFamily].concat(defaultEditorFontFamily)
|
||||||
: defaultEditorFontFamily
|
: defaultEditorFontFamily
|
||||||
|
|||||||
@@ -1,24 +1,26 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './MarkdownEditor.styl'
|
import styles from './MarkdownEditor.styl'
|
||||||
import CodeEditor from 'browser/components/CodeEditor'
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
|
||||||
class MarkdownEditor extends React.Component {
|
class MarkdownEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.escapeFromEditor = ['Control', 'w']
|
// char codes for ctrl + w
|
||||||
|
this.escapeFromEditor = [17, 87]
|
||||||
|
|
||||||
this.supportMdBold = ['Control', 'b']
|
// ctrl + shift + ;
|
||||||
|
this.supportMdSelectionBold = [16, 17, 186]
|
||||||
this.supportMdWordBold = ['Control', ':']
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
status: 'PREVIEW',
|
status: 'PREVIEW',
|
||||||
renderValue: props.value,
|
renderValue: props.value,
|
||||||
keyPressed: {},
|
keyPressed: new Set(),
|
||||||
isLocked: false
|
isLocked: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +70,9 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu (e) {
|
||||||
let { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
||||||
let newStatus = this.state.status === 'PREVIEW'
|
const newStatus = this.state.status === 'PREVIEW'
|
||||||
? 'CODE'
|
? 'CODE'
|
||||||
: 'PREVIEW'
|
: 'PREVIEW'
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -79,7 +81,6 @@ class MarkdownEditor extends React.Component {
|
|||||||
if (newStatus === 'CODE') {
|
if (newStatus === 'CODE') {
|
||||||
this.refs.code.focus()
|
this.refs.code.focus()
|
||||||
} else {
|
} else {
|
||||||
this.refs.code.blur()
|
|
||||||
this.refs.preview.focus()
|
this.refs.preview.focus()
|
||||||
}
|
}
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
@@ -89,10 +90,10 @@ class MarkdownEditor extends React.Component {
|
|||||||
|
|
||||||
handleBlur (e) {
|
handleBlur (e) {
|
||||||
if (this.state.isLocked) return
|
if (this.state.isLocked) return
|
||||||
this.setState({ keyPressed: [] })
|
this.setState({ keyPressed: new Set() })
|
||||||
let { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'BLUR') {
|
if (config.editor.switchPreview === 'BLUR') {
|
||||||
let cursorPosition = this.refs.code.editor.getCursor()
|
const cursorPosition = this.refs.code.editor.getCursor()
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'PREVIEW'
|
status: 'PREVIEW'
|
||||||
}, () => {
|
}, () => {
|
||||||
@@ -108,7 +109,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePreviewMouseUp (e) {
|
handlePreviewMouseUp (e) {
|
||||||
let { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
|
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'CODE'
|
status: 'CODE'
|
||||||
@@ -122,15 +123,15 @@ class MarkdownEditor extends React.Component {
|
|||||||
handleCheckboxClick (e) {
|
handleCheckboxClick (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
let idMatch = /checkbox-([0-9]+)/
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
let checkedMatch = /\[x\]/i
|
const checkedMatch = /\[x\]/i
|
||||||
let uncheckedMatch = /\[ \]/
|
const uncheckedMatch = /\[ \]/
|
||||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
let lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
let lines = this.refs.code.value
|
const lines = this.refs.code.value
|
||||||
.split('\n')
|
.split('\n')
|
||||||
|
|
||||||
let targetLine = lines[lineIndex]
|
const targetLine = lines[lineIndex]
|
||||||
|
|
||||||
if (targetLine.match(checkedMatch)) {
|
if (targetLine.match(checkedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
||||||
@@ -161,32 +162,29 @@ class MarkdownEditor extends React.Component {
|
|||||||
this.renderPreview(this.props.value)
|
this.renderPreview(this.props.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown(e) {
|
handleKeyDown (e) {
|
||||||
|
const { config } = this.props
|
||||||
if (this.state.status !== 'CODE') return false
|
if (this.state.status !== 'CODE') return false
|
||||||
const keyPressed = Object.assign(this.state.keyPressed, {
|
const keyPressed = this.state.keyPressed
|
||||||
[e.key]: true
|
keyPressed.add(e.keyCode)
|
||||||
})
|
|
||||||
this.setState({ keyPressed })
|
this.setState({ keyPressed })
|
||||||
let isNoteHandlerKey = (el) => { return this.state.keyPressed[el] }
|
const isNoteHandlerKey = (el) => { return keyPressed.has(el) }
|
||||||
if (!this.state.isLocked && this.state.status === 'CODE' && this.escapeFromEditor.every(isNoteHandlerKey)) {
|
// These conditions are for ctrl-e and ctrl-w
|
||||||
document.activeElement.blur()
|
if (keyPressed.size === this.escapeFromEditor.length &&
|
||||||
|
!this.state.isLocked && this.state.status === 'CODE' &&
|
||||||
|
this.escapeFromEditor.every(isNoteHandlerKey)) {
|
||||||
|
this.handleContextMenu()
|
||||||
|
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
|
||||||
}
|
}
|
||||||
if (this.supportMdBold.every(isNoteHandlerKey)) {
|
if (keyPressed.size === this.supportMdSelectionBold.length && this.supportMdSelectionBold.every(isNoteHandlerKey)) {
|
||||||
this.addMdAndMoveCaretToCenter('****')
|
this.addMdAroundWord('**')
|
||||||
}
|
|
||||||
if (this.supportMdWordBold.every(isNoteHandlerKey)) {
|
|
||||||
this.addMdBetweenWord('**')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addMdAndMoveCaretToCenter (mdElement) {
|
addMdAroundWord (mdElement) {
|
||||||
const currentCaret = this.refs.code.editor.getCursor()
|
if (this.refs.code.editor.getSelection()) {
|
||||||
const cmDoc = this.refs.code.editor.getDoc()
|
return this.addMdAroundSelection(mdElement)
|
||||||
cmDoc.replaceRange(mdElement, currentCaret)
|
}
|
||||||
this.refs.code.editor.setCursor({line: currentCaret.line, ch: currentCaret.ch + mdElement.length/2})
|
|
||||||
}
|
|
||||||
|
|
||||||
addMdBetweenWord (mdElement) {
|
|
||||||
const currentCaret = this.refs.code.editor.getCursor()
|
const currentCaret = this.refs.code.editor.getCursor()
|
||||||
const word = this.refs.code.editor.findWordAt(currentCaret)
|
const word = this.refs.code.editor.findWordAt(currentCaret)
|
||||||
const cmDoc = this.refs.code.editor.getDoc()
|
const cmDoc = this.refs.code.editor.getDoc()
|
||||||
@@ -194,10 +192,13 @@ class MarkdownEditor extends React.Component {
|
|||||||
cmDoc.replaceRange(mdElement, { line: word.head.line, ch: word.head.ch + mdElement.length })
|
cmDoc.replaceRange(mdElement, { line: word.head.line, ch: word.head.ch + mdElement.length })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addMdAroundSelection (mdElement) {
|
||||||
|
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
|
||||||
|
}
|
||||||
|
|
||||||
handleKeyUp (e) {
|
handleKeyUp (e) {
|
||||||
const keyPressed = Object.assign(this.state.keyPressed, {
|
const keyPressed = this.state.keyPressed
|
||||||
[e.key]: false
|
keyPressed.delete(e.keyCode)
|
||||||
})
|
|
||||||
this.setState({ keyPressed })
|
this.setState({ keyPressed })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,16 +207,18 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className, value, config } = this.props
|
const { className, value, config, storageKey } = this.props
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
|
||||||
let previewStyle = {}
|
const previewStyle = {}
|
||||||
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
||||||
|
|
||||||
|
const storage = findStorage(storageKey)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className == null
|
<div className={className == null
|
||||||
? 'MarkdownEditor'
|
? 'MarkdownEditor'
|
||||||
@@ -226,7 +229,10 @@ class MarkdownEditor extends React.Component {
|
|||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
onKeyUp={(e) => this.handleKeyUp(e)}
|
onKeyUp={(e) => this.handleKeyUp(e)}
|
||||||
>
|
>
|
||||||
<CodeEditor styleName='codeEditor'
|
<CodeEditor styleName={this.state.status === 'CODE'
|
||||||
|
? 'codeEditor'
|
||||||
|
: 'codeEditor--hide'
|
||||||
|
}
|
||||||
ref='code'
|
ref='code'
|
||||||
mode='GitHub Flavored Markdown'
|
mode='GitHub Flavored Markdown'
|
||||||
value={value}
|
value={value}
|
||||||
@@ -236,6 +242,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
fontSize={editorFontSize}
|
fontSize={editorFontSize}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
|
storageKey={storageKey}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
onBlur={(e) => this.handleBlur(e)}
|
||||||
/>
|
/>
|
||||||
@@ -259,6 +266,8 @@ class MarkdownEditor extends React.Component {
|
|||||||
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
||||||
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
||||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||||
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
|
storagePath={storage.path}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,8 +4,14 @@
|
|||||||
.codeEditor
|
.codeEditor
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
|
|
||||||
|
.hide
|
||||||
|
z-index 0
|
||||||
|
opacity 0
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
.codeEditor--hide
|
.codeEditor--hide
|
||||||
@extend .codeEditor
|
@extend .codeEditor
|
||||||
|
@extend .hide
|
||||||
|
|
||||||
.preview
|
.preview
|
||||||
display block
|
display block
|
||||||
@@ -17,7 +23,5 @@
|
|||||||
|
|
||||||
.preview--hide
|
.preview--hide
|
||||||
@extend .preview
|
@extend .preview
|
||||||
z-index 0
|
@extend .hide
|
||||||
opacity 0
|
|
||||||
pointer-events none
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import markdown from 'browser/lib/markdown'
|
import markdown from 'browser/lib/markdown'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
@@ -8,21 +9,9 @@ import flowchart from 'flowchart'
|
|||||||
import SequenceDiagram from 'js-sequence-diagrams'
|
import SequenceDiagram from 'js-sequence-diagrams'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||||
function decodeHTMLEntities (text) {
|
import copy from 'copy-to-clipboard'
|
||||||
var entities = [
|
import mdurl from 'mdurl'
|
||||||
['apos', '\''],
|
|
||||||
['amp', '&'],
|
|
||||||
['lt', '<'],
|
|
||||||
['gt', '>']
|
|
||||||
]
|
|
||||||
|
|
||||||
for (var i = 0, max = entities.length; i < max; ++i) {
|
|
||||||
text = text.replace(new RegExp('&' + entities[i][0] + ';', 'g'), entities[i][1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { app } = remote
|
const { app } = remote
|
||||||
@@ -45,21 +34,49 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber) {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-rendering: optimizeLegibility;
|
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}
|
${markdownStyle}
|
||||||
body {
|
body {
|
||||||
font-family: ${fontFamily.join(', ')};
|
font-family: '${fontFamily.join("','")}';
|
||||||
font-size: ${fontSize}px;
|
font-size: ${fontSize}px;
|
||||||
}
|
}
|
||||||
code {
|
code {
|
||||||
font-family: ${codeBlockFontFamily.join(', ')};
|
font-family: ${codeBlockFontFamily.join(', ')};
|
||||||
background-color: rgba(0,0,0,0.04);
|
background-color: rgba(0,0,0,0.04);
|
||||||
color: #CC305F;
|
|
||||||
}
|
}
|
||||||
.lineNumber {
|
.lineNumber {
|
||||||
${lineNumber && 'display: block !important;'}
|
${lineNumber && 'display: block !important;'}
|
||||||
font-family: ${codeBlockFontFamily.join(', ')};
|
font-family: ${codeBlockFontFamily.join(', ')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clipboardButton {
|
||||||
|
color: rgba(147,147,149,0.8);;
|
||||||
|
fill: rgba(147,147,149,1);;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 0px 10px;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
outline: none;
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clipboardButton:hover {
|
||||||
|
transition: 0.2s;
|
||||||
|
color: #939395;
|
||||||
|
fill: #939395;
|
||||||
|
background-color: rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
h1, h2 {
|
h1, h2 {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@@ -73,6 +90,10 @@ h2 {
|
|||||||
padding-bottom: 0.2em;
|
padding-bottom: 0.2em;
|
||||||
margin: 1em 0 0.37em;
|
margin: 1em 0 0.37em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body p {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +102,7 @@ const OSX = global.process.platform === 'darwin'
|
|||||||
|
|
||||||
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
||||||
if (!OSX) {
|
if (!OSX) {
|
||||||
defaultFontFamily.unshift('\'Microsoft YaHei\'')
|
defaultFontFamily.unshift('Microsoft YaHei')
|
||||||
defaultFontFamily.unshift('meiryo')
|
defaultFontFamily.unshift('meiryo')
|
||||||
}
|
}
|
||||||
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||||
@@ -97,16 +118,19 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||||
|
this.printHandler = () => this.handlePrint()
|
||||||
|
|
||||||
|
this.linkClickHandler = this.handlelinkClick.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePreviewAnchorClick (e) {
|
handlePreviewAnchorClick (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
let anchor = e.target.closest('a')
|
const anchor = e.target.closest('a')
|
||||||
let href = anchor.getAttribute('href')
|
const href = anchor.getAttribute('href')
|
||||||
if (_.isString(href) && href.match(/^#/)) {
|
if (_.isString(href) && href.match(/^#/)) {
|
||||||
let targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
|
const targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
|
||||||
if (targetElement != null) {
|
if (targetElement != null) {
|
||||||
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
||||||
}
|
}
|
||||||
@@ -149,10 +173,14 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.exportAsDocument('md')
|
this.exportAsDocument('md')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handlePrint () {
|
||||||
|
this.refs.root.contentWindow.print()
|
||||||
|
}
|
||||||
|
|
||||||
exportAsDocument (fileType) {
|
exportAsDocument (fileType) {
|
||||||
const options = {
|
const options = {
|
||||||
filters: [
|
filters: [
|
||||||
{ name: 'Documents', extensions: [fileType]}
|
{ name: 'Documents', extensions: [fileType] }
|
||||||
],
|
],
|
||||||
properties: ['openFile', 'createDirectory']
|
properties: ['openFile', 'createDirectory']
|
||||||
}
|
}
|
||||||
@@ -166,6 +194,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 () {
|
componentDidMount () {
|
||||||
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
||||||
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
|
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
|
||||||
@@ -185,6 +223,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
|
||||||
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
||||||
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||||
|
eventEmitter.on('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
@@ -195,6 +234,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
|
||||||
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
||||||
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
||||||
|
eventEmitter.off('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
@@ -204,6 +244,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
||||||
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
||||||
prevProps.lineNumber !== this.props.lineNumber ||
|
prevProps.lineNumber !== this.props.lineNumber ||
|
||||||
|
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
||||||
prevProps.theme !== this.props.theme) {
|
prevProps.theme !== this.props.theme) {
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
@@ -211,7 +252,8 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyStyle () {
|
applyStyle () {
|
||||||
let { fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme } = this.props
|
const { fontSize, lineNumber, codeBlockTheme } = this.props
|
||||||
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
? [fontFamily].concat(defaultFontFamily)
|
? [fontFamily].concat(defaultFontFamily)
|
||||||
: defaultFontFamily
|
: defaultFontFamily
|
||||||
@@ -227,7 +269,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
|
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
|
||||||
? theme
|
? theme
|
||||||
: 'elegant'
|
: '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 () {
|
rewriteIframe () {
|
||||||
@@ -238,9 +282,21 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.removeEventListener('click', this.checkboxClickHandler)
|
el.removeEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
let { value, theme, indentSize, codeBlockTheme } = this.props
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
|
el.removeEventListener('click', this.linkClickHandler)
|
||||||
|
})
|
||||||
|
|
||||||
|
const { theme, indentSize, showCopyNotification, storagePath } = this.props
|
||||||
|
let { value, codeBlockTheme } = this.props
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
|
|
||||||
|
const codeBlocks = value.match(/(```)(.|[\n])*?(```)/g)
|
||||||
|
if (codeBlocks !== null) {
|
||||||
|
codeBlocks.forEach((codeBlock) => {
|
||||||
|
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
|
||||||
|
})
|
||||||
|
}
|
||||||
this.refs.root.contentWindow.document.body.innerHTML = markdown.render(value)
|
this.refs.root.contentWindow.document.body.innerHTML = markdown.render(value)
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.taskListItem'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.taskListItem'), (el) => {
|
||||||
@@ -248,6 +304,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
|
this.fixDecodedURI(el)
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
el.addEventListener('click', this.anchorClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -255,6 +312,16 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.addEventListener('click', this.checkboxClickHandler)
|
el.addEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
|
})
|
||||||
|
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
|
||||||
|
el.src = markdown.normalizeLinkText(el.src)
|
||||||
|
if (!/\/:storage/.test(el.src)) return
|
||||||
|
el.src = `file:///${markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
|
||||||
|
})
|
||||||
|
|
||||||
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
||||||
? codeBlockTheme
|
? codeBlockTheme
|
||||||
: 'default'
|
: 'default'
|
||||||
@@ -263,15 +330,32 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
let syntax = CodeMirror.findModeByName(el.className)
|
let syntax = CodeMirror.findModeByName(el.className)
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
CodeMirror.requireMode(syntax.mode, () => {
|
CodeMirror.requireMode(syntax.mode, () => {
|
||||||
let content = decodeHTMLEntities(el.innerHTML)
|
const content = htmlTextHelper.decodeEntities(el.innerHTML)
|
||||||
|
const copyIcon = document.createElement('i')
|
||||||
|
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 = ''
|
el.innerHTML = ''
|
||||||
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
|
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, {
|
CodeMirror.runMode(content, syntax.mime, el, {
|
||||||
tabSize: indentSize
|
tabSize: indentSize
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
let opts = {}
|
const opts = {}
|
||||||
// if (this.props.theme === 'dark') {
|
// if (this.props.theme === 'dark') {
|
||||||
// opts['font-color'] = '#DDD'
|
// opts['font-color'] = '#DDD'
|
||||||
// opts['line-color'] = '#DDD'
|
// opts['line-color'] = '#DDD'
|
||||||
@@ -281,7 +365,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => {
|
||||||
Raphael.setWindow(this.getWindow())
|
Raphael.setWindow(this.getWindow())
|
||||||
try {
|
try {
|
||||||
let diagram = flowchart.parse(decodeHTMLEntities(el.innerHTML))
|
const diagram = flowchart.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
diagram.drawSVG(el, opts)
|
diagram.drawSVG(el, opts)
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
_.forEach(el.querySelectorAll('a'), (el) => {
|
||||||
@@ -297,7 +381,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.sequence'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.sequence'), (el) => {
|
||||||
Raphael.setWindow(this.getWindow())
|
Raphael.setWindow(this.getWindow())
|
||||||
try {
|
try {
|
||||||
let diagram = SequenceDiagram.parse(decodeHTMLEntities(el.innerHTML))
|
const diagram = SequenceDiagram.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
diagram.drawSVG(el, {theme: 'simple'})
|
diagram.drawSVG(el, {theme: 'simple'})
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
_.forEach(el.querySelectorAll('a'), (el) => {
|
||||||
@@ -320,11 +404,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollTo (targetRow) {
|
scrollTo (targetRow) {
|
||||||
let blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
|
const blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
|
||||||
|
|
||||||
for (let index = 0; index < blocks.length; index++) {
|
for (let index = 0; index < blocks.length; index++) {
|
||||||
let block = blocks[index]
|
let block = blocks[index]
|
||||||
let row = parseInt(block.getAttribute('data-line'))
|
const row = parseInt(block.getAttribute('data-line'))
|
||||||
if (row > targetRow || index === blocks.length - 1) {
|
if (row > targetRow || index === blocks.length - 1) {
|
||||||
block = blocks[index - 1]
|
block = blocks[index - 1]
|
||||||
block != null && this.getWindow().scrollTo(0, block.offsetTop)
|
block != null && this.getWindow().scrollTo(0, block.offsetTop)
|
||||||
@@ -333,13 +417,28 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preventImageDroppedHandler(e) {
|
preventImageDroppedHandler (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notify (title, options) {
|
||||||
|
if (global.process.platform === 'win32') {
|
||||||
|
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
||||||
|
}
|
||||||
|
return new window.Notification(title, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
handlelinkClick (e) {
|
||||||
|
const noteHash = e.target.href.split('/').pop()
|
||||||
|
const regexIsNoteLink = /^(.{20})-(.{20})$/
|
||||||
|
if (regexIsNoteLink.test(noteHash)) {
|
||||||
|
eventEmitter.emit('list:jump', noteHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className, style, tabIndex } = this.props
|
const { className, style, tabIndex } = this.props
|
||||||
return (
|
return (
|
||||||
<iframe className={className != null
|
<iframe className={className != null
|
||||||
? 'MarkdownPreview ' + className
|
? 'MarkdownPreview ' + className
|
||||||
@@ -359,5 +458,7 @@ MarkdownPreview.propTypes = {
|
|||||||
onMouseUp: PropTypes.func,
|
onMouseUp: PropTypes.func,
|
||||||
onMouseDown: PropTypes.func,
|
onMouseDown: PropTypes.func,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
value: PropTypes.string
|
value: PropTypes.string,
|
||||||
|
showCopyNotification: PropTypes.bool,
|
||||||
|
storagePath: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|||||||
19
browser/components/ModalEscButton.js
Normal file
19
browser/components/ModalEscButton.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './ModalEscButton.styl'
|
||||||
|
|
||||||
|
const ModalEscButton = ({
|
||||||
|
handleEscButtonClick
|
||||||
|
}) => (
|
||||||
|
<button styleName='escButton' onClick={handleEscButtonClick}>
|
||||||
|
<div styleName='esc-mark'>×</div>
|
||||||
|
<div styleName='esc-text'>esc</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
ModalEscButton.propTypes = {
|
||||||
|
handleEscButtonClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(ModalEscButton, styles)
|
||||||
16
browser/components/ModalEscButton.styl
Normal file
16
browser/components/ModalEscButton.styl
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.escButton
|
||||||
|
height 50px
|
||||||
|
position absolute
|
||||||
|
background-color transparent
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
border none
|
||||||
|
top 1px
|
||||||
|
right 10px
|
||||||
|
text-align center
|
||||||
|
width top-bar--height
|
||||||
|
height top-bar-height
|
||||||
|
|
||||||
|
.esc-mark
|
||||||
|
font-size 28px
|
||||||
|
margin-top -5px
|
||||||
|
margin-bottom -7px
|
||||||
30
browser/components/NavToggleButton.js
Normal file
30
browser/components/NavToggleButton.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for toggle SideNav
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React 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
|
||||||
|
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Note item component.
|
* @fileoverview Note item component.
|
||||||
*/
|
*/
|
||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import { isArray } from 'lodash'
|
import { isArray } from 'lodash'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import { getTodoStatus } from 'browser/lib/getTodoStatus'
|
||||||
import styles from './NoteItem.styl'
|
import styles from './NoteItem.styl'
|
||||||
|
import TodoProcess from './TodoProcess'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Tag element component.
|
* @description Tag element component.
|
||||||
@@ -13,7 +16,7 @@ import styles from './NoteItem.styl'
|
|||||||
*/
|
*/
|
||||||
const TagElement = ({ tagName }) => (
|
const TagElement = ({ tagName }) => (
|
||||||
<span styleName='item-bottom-tagList-item' key={tagName}>
|
<span styleName='item-bottom-tagList-item' key={tagName}>
|
||||||
{tagName}
|
#{tagName}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,9 +43,10 @@ const TagElementList = (tags) => {
|
|||||||
* @param {Object} note
|
* @param {Object} note
|
||||||
* @param {Function} handleNoteClick
|
* @param {Function} handleNoteClick
|
||||||
* @param {Function} handleNoteContextMenu
|
* @param {Function} handleNoteContextMenu
|
||||||
|
* @param {Function} handleDragStart
|
||||||
* @param {string} dateDisplay
|
* @param {string} dateDisplay
|
||||||
*/
|
*/
|
||||||
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu }) => (
|
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
|
||||||
<div styleName={isActive
|
<div styleName={isActive
|
||||||
? 'item--active'
|
? 'item--active'
|
||||||
: 'item'
|
: 'item'
|
||||||
@@ -50,10 +54,14 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteCont
|
|||||||
key={`${note.storage}-${note.key}`}
|
key={`${note.storage}-${note.key}`}
|
||||||
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
||||||
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
||||||
|
onDragStart={e => handleDragStart(e, note)}
|
||||||
|
draggable='true'
|
||||||
>
|
>
|
||||||
<div styleName='item-wrapper'>
|
<div styleName='item-wrapper'>
|
||||||
<div styleName='item-bottom-time'>{dateDisplay}</div>
|
{note.type === 'SNIPPET_NOTE'
|
||||||
|
? <i styleName='item-title-icon' className='fa fa-fw fa-code' />
|
||||||
|
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
|
||||||
|
}
|
||||||
<div styleName='item-title'>
|
<div styleName='item-title'>
|
||||||
{note.title.trim().length > 0
|
{note.title.trim().length > 0
|
||||||
? note.title
|
? note.title
|
||||||
@@ -61,23 +69,25 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteCont
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div styleName='item-bottom-time'>{dateDisplay}</div>
|
||||||
|
{note.isStarred
|
||||||
|
? <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)} />
|
||||||
|
: ''
|
||||||
|
}
|
||||||
<div styleName='item-bottom'>
|
<div styleName='item-bottom'>
|
||||||
<div styleName='item-bottom-tagList'>
|
<div styleName='item-bottom-tagList'>
|
||||||
{note.tags.length > 0
|
{note.tags.length > 0
|
||||||
? TagElementList(note.tags)
|
? TagElementList(note.tags)
|
||||||
: ''
|
: <span styleName='item-bottom-tagList-empty' />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{note.type === 'SNIPPET_NOTE'
|
|
||||||
? <i styleName='item-title-icon' className='fa fa-fw fa-code' />
|
|
||||||
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
|
|
||||||
}
|
|
||||||
|
|
||||||
{note.isStarred
|
|
||||||
? <i styleName='item-star' className='fa fa-star' /> : ''
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -91,10 +101,13 @@ NoteItem.propTypes = {
|
|||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
title: PropTypes.string.isrequired,
|
title: PropTypes.string.isrequired,
|
||||||
tags: PropTypes.array,
|
tags: PropTypes.array,
|
||||||
isStarred: PropTypes.bool.isRequired
|
isStarred: PropTypes.bool.isRequired,
|
||||||
|
isTrashed: PropTypes.bool.isRequired
|
||||||
}),
|
}),
|
||||||
handleNoteClick: PropTypes.func.isRequired,
|
handleNoteClick: PropTypes.func.isRequired,
|
||||||
handleNoteContextMenu: PropTypes.func.isRequired
|
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||||
|
handleDragStart: PropTypes.func.isRequired,
|
||||||
|
handleDragEnd: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(NoteItem, styles)
|
export default CSSModules(NoteItem, styles)
|
||||||
|
|||||||
@@ -7,63 +7,83 @@ $control-height = 30px
|
|||||||
|
|
||||||
.item
|
.item
|
||||||
position relative
|
position relative
|
||||||
padding 0 25px
|
padding 0 20px
|
||||||
user-select none
|
user-select none
|
||||||
cursor pointer
|
cursor pointer
|
||||||
background-color $ui-noteList-backgroundColor
|
background-color $ui-noteList-backgroundColor
|
||||||
transition background-color 0.15s
|
transition 0.2s
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-active-color, 20%)
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
&:active
|
|
||||||
background-color $ui-active-color
|
|
||||||
color white
|
|
||||||
.item-title
|
.item-title
|
||||||
.item-title-empty
|
|
||||||
.item-bottom-tagList-empty
|
|
||||||
.item-bottom-time
|
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
color white
|
.item-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-text-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color transparent
|
background-color alpha(white, 0.6)
|
||||||
color white
|
color $ui-text-color
|
||||||
|
.item-star
|
||||||
|
color $ui-favorite-star-button-color
|
||||||
|
&:active
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color $ui-text-color
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(white, 0.6)
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
.item-wrapper
|
.item-wrapper
|
||||||
padding 20px 0
|
padding 15px 0
|
||||||
border-bottom $ui-border
|
border-bottom $ui-border
|
||||||
|
position relative
|
||||||
|
|
||||||
.item--active
|
.item--active
|
||||||
@extend .item
|
@extend .item
|
||||||
background-color $ui-active-color
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
color white
|
color $ui-text-color
|
||||||
.item-title
|
.item-title
|
||||||
.item-title-empty
|
.item-title-empty
|
||||||
.item-bottom-tagList-empty
|
.item-bottom-tagList-empty
|
||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
color white
|
color $ui-text-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color transparent
|
background-color alpha(white, 0.6)
|
||||||
color white
|
color $ui-text-color
|
||||||
.item-wrapper
|
.item-wrapper
|
||||||
border-color transparent
|
border-color transparent
|
||||||
|
.item-star
|
||||||
|
color $ui-favorite-star-button-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-active-color
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color #e74c3c
|
||||||
.item-title
|
.menu-button-label
|
||||||
font-size 14px
|
color $ui-text-color
|
||||||
height 40px
|
&:active, &:active:hover
|
||||||
box-sizing border-box
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
line-height 24px
|
color #e74c3c
|
||||||
padding-top 5px
|
.menu-button-label
|
||||||
padding-bottom 20px
|
color $ui-text-color
|
||||||
overflow ellipsis
|
|
||||||
color $ui-text-color
|
|
||||||
|
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
position absolute
|
position relative
|
||||||
top 20px
|
font-size 12px
|
||||||
right 25px
|
color $ui-inactive-text-color
|
||||||
font-size 14px
|
top 2px
|
||||||
|
|
||||||
|
.item-title
|
||||||
|
font-size 15px
|
||||||
|
font-weight 700
|
||||||
|
position relative
|
||||||
|
top -12px
|
||||||
|
left 20px
|
||||||
|
padding 0px 15px 0px 0px
|
||||||
|
margin-bottom 4px
|
||||||
|
overflow ellipsis
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.item-title-empty
|
.item-title-empty
|
||||||
@@ -73,8 +93,7 @@ $control-height = 30px
|
|||||||
.item-bottom
|
.item-bottom
|
||||||
position relative
|
position relative
|
||||||
bottom 0px
|
bottom 0px
|
||||||
margin-top 2px
|
margin-top 10px
|
||||||
height 20px
|
|
||||||
font-size 12px
|
font-size 12px
|
||||||
line-height 20px
|
line-height 20px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
@@ -83,41 +102,63 @@ $control-height = 30px
|
|||||||
.item-bottom-tagList
|
.item-bottom-tagList
|
||||||
flex 1
|
flex 1
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
line-height 20px
|
line-height 25px
|
||||||
color #FFFFFF
|
padding-left 2px
|
||||||
|
margin-right 40px
|
||||||
|
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
font-size 12px
|
font-size 11px
|
||||||
margin-right 8px
|
margin-right 8px
|
||||||
padding 0 10px
|
padding 0
|
||||||
height 20px
|
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
border-radius 20px
|
border-radius 2px
|
||||||
|
padding 4px
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
background-color $ui-tag-backgroundColor
|
background-color white
|
||||||
color #FFFFFF
|
|
||||||
|
|
||||||
.item-bottom-tagList-empty
|
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
vertical-align middle
|
|
||||||
font-size 10px
|
|
||||||
margin-left 5px
|
|
||||||
|
|
||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
font-size 12px
|
font-size 13px
|
||||||
|
padding-left 2px
|
||||||
|
padding-bottom 2px
|
||||||
|
|
||||||
.item-star
|
.item-star
|
||||||
position absolute
|
position absolute
|
||||||
top 20px
|
right -6px
|
||||||
right 29px
|
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
|
width 34px
|
||||||
height 34px
|
height 34px
|
||||||
color $ui-favorite-star-button-color
|
color #E54D42
|
||||||
font-size 14px
|
font-size 14px
|
||||||
padding 0
|
padding 0
|
||||||
border-radius 17px
|
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"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
@@ -126,42 +167,66 @@ body[data-theme="dark"]
|
|||||||
.item
|
.item
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
&:active
|
|
||||||
background-color $ui-active-color
|
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-active-color, 20%)
|
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
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-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-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.item-wrapper
|
.item-wrapper
|
||||||
border-color $ui-dark-borderColor
|
border-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
.item--active
|
.item--active
|
||||||
@extend .item
|
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
background-color $ui-active-color
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
.item-wrapper
|
.item-wrapper
|
||||||
border-color transparent
|
border-color transparent
|
||||||
.item-title
|
.item-title
|
||||||
color white
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
color $ui-dark-text-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color transparent
|
background-color alpha(white, 10%)
|
||||||
color white
|
color $ui-dark-text-color
|
||||||
.item-bottom-tagList-empty
|
|
||||||
color white
|
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-active-color
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
.item-title
|
.item-title
|
||||||
color $ui-dark-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
color $ui-darkinactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.item-title-empty
|
.item-title-empty
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color $ui-dark-tag-backgroundColor
|
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
||||||
color $ui-dark-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.item-bottom-tagList-empty
|
.item-bottom-tagList-empty
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Note item component with simple display mode.
|
* @fileoverview Note item component with simple display mode.
|
||||||
*/
|
*/
|
||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './NoteItemSimple.styl'
|
import styles from './NoteItemSimple.styl'
|
||||||
|
|
||||||
@@ -11,8 +12,9 @@ import styles from './NoteItemSimple.styl'
|
|||||||
* @param {Object} note
|
* @param {Object} note
|
||||||
* @param {Function} handleNoteClick
|
* @param {Function} handleNoteClick
|
||||||
* @param {Function} handleNoteContextMenu
|
* @param {Function} handleNoteContextMenu
|
||||||
|
* @param {Function} handleDragStart
|
||||||
*/
|
*/
|
||||||
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu }) => (
|
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu, handleDragStart }) => (
|
||||||
<div styleName={isActive
|
<div styleName={isActive
|
||||||
? 'item-simple--active'
|
? 'item-simple--active'
|
||||||
: 'item-simple'
|
: 'item-simple'
|
||||||
@@ -20,6 +22,8 @@ const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu
|
|||||||
key={`${note.storage}-${note.key}`}
|
key={`${note.storage}-${note.key}`}
|
||||||
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
||||||
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
||||||
|
onDragStart={e => handleDragStart(e, note)}
|
||||||
|
draggable='true'
|
||||||
>
|
>
|
||||||
<div styleName='item-simple-title'>
|
<div styleName='item-simple-title'>
|
||||||
{note.type === 'SNIPPET_NOTE'
|
{note.type === 'SNIPPET_NOTE'
|
||||||
@@ -43,7 +47,8 @@ NoteItemSimple.propTypes = {
|
|||||||
title: PropTypes.string.isrequired
|
title: PropTypes.string.isrequired
|
||||||
}),
|
}),
|
||||||
handleNoteClick: PropTypes.func.isRequired,
|
handleNoteClick: PropTypes.func.isRequired,
|
||||||
handleNoteContextMenu: PropTypes.func.isRequired
|
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||||
|
handleDragStart: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(NoteItemSimple, styles)
|
export default CSSModules(NoteItemSimple, styles)
|
||||||
|
|||||||
@@ -7,43 +7,56 @@ $control-height = 30px
|
|||||||
|
|
||||||
.item-simple
|
.item-simple
|
||||||
position relative
|
position relative
|
||||||
padding 0 25px
|
padding 0 20px
|
||||||
user-select none
|
user-select none
|
||||||
cursor pointer
|
cursor pointer
|
||||||
background-color $ui-noteList-backgroundColor
|
background-color $ui-noteList-backgroundColor
|
||||||
transition background-color 0.15s
|
transition 0.2s
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-active-color, 20%)
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
&:active
|
|
||||||
background-color $ui-active-color
|
|
||||||
color white
|
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
.item-simple-title-empty
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
color white
|
color $ui-text-color
|
||||||
|
&:active
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color $ui-text-color
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
.item-simple--active
|
.item-simple--active
|
||||||
@extend .item-simple
|
@extend .item-simple
|
||||||
background-color $ui-active-color
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
color white
|
color $ui-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
.item-simple-title-empty
|
.item-simple-title-empty
|
||||||
border-color transparent
|
border-color transparent
|
||||||
color white
|
color $ui-text-color
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
color white
|
color $ui-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-active-color
|
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
|
.item-simple-title
|
||||||
font-size 14px
|
font-size 13px
|
||||||
height 40px
|
height 40px
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
line-height 24px
|
line-height 24px
|
||||||
padding-top 8px
|
padding-top 8px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
color $ui-text-color
|
color $ui-inactive-text-color
|
||||||
border-bottom $ui-border
|
border-bottom $ui-border
|
||||||
|
position relative
|
||||||
|
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
font-size 12px
|
font-size 12px
|
||||||
@@ -54,6 +67,20 @@ $control-height = 30px
|
|||||||
font-weight normal
|
font-weight normal
|
||||||
color $ui-inactive-text-color
|
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"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
@@ -62,25 +89,54 @@ body[data-theme="dark"]
|
|||||||
.item-simple
|
.item-simple
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
&:active
|
|
||||||
background-color $ui-active-color
|
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-active-color, 20%)
|
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
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.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
|
.item-simple--active
|
||||||
@extend .item-simple
|
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
background-color $ui-active-color
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
.item-simple-title
|
.item-simple-wrapper
|
||||||
.item-simple-title-empty
|
|
||||||
color white
|
|
||||||
border-color transparent
|
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 alpha(white, 10%)
|
||||||
|
color $ui-dark-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-active-color
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
color $ui-dark-text-color
|
color $ui-inactive-text-color
|
||||||
border-color $ui-dark-borderColor
|
border-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
color $ui-darkinactive-text-color
|
color $ui-darkinactive-text-color
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import md5 from 'md5'
|
|
||||||
|
|
||||||
export default class ProfileImage extends React.Component {
|
|
||||||
render () {
|
|
||||||
let className = this.props.className == null ? 'ProfileImage' : 'ProfileImage ' + this.props.className
|
|
||||||
let email = this.props.email != null ? this.props.email : ''
|
|
||||||
let src = 'http://www.gravatar.com/avatar/' + md5(email.trim().toLowerCase()) + '?s=' + this.props.size
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
className={className}
|
|
||||||
width={this.props.size}
|
|
||||||
height={this.props.size}
|
|
||||||
src={src} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ProfileImage.propTypes = {
|
|
||||||
email: PropTypes.string,
|
|
||||||
size: PropTypes.string,
|
|
||||||
className: PropTypes.string
|
|
||||||
}
|
|
||||||
55
browser/components/RealtimeNotification.js
Normal file
55
browser/components/RealtimeNotification.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React 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)
|
||||||
31
browser/components/RealtimeNotification.styl
Normal file
31
browser/components/RealtimeNotification.styl
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
.notification-area
|
||||||
|
z-index 1000
|
||||||
|
font-size 12px
|
||||||
|
position: relative
|
||||||
|
top: 12px
|
||||||
|
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
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Filter for all notes.
|
* @fileoverview Filter for all notes.
|
||||||
*/
|
*/
|
||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './SideNavFilter.styl'
|
import styles from './SideNavFilter.styl'
|
||||||
|
|
||||||
@@ -15,21 +16,53 @@ import styles from './SideNavFilter.styl'
|
|||||||
*/
|
*/
|
||||||
const SideNavFilter = ({
|
const SideNavFilter = ({
|
||||||
isFolded, isHomeActive, handleAllNotesButtonClick,
|
isFolded, isHomeActive, handleAllNotesButtonClick,
|
||||||
isStarredActive, handleStarredButtonClick
|
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
|
||||||
|
counterTotalNote, counterStarredNote
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
||||||
|
|
||||||
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
||||||
onClick={handleAllNotesButtonClick}
|
onClick={handleAllNotesButtonClick}
|
||||||
>
|
>
|
||||||
<i className='fa fa-book 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='menu-button-label'>All Notes</span>
|
||||||
|
<span styleName='counters'>{counterTotalNote}</span>
|
||||||
</button>
|
</button>
|
||||||
<button styleName={isStarredActive ? 'menu-button--active' : 'menu-button'}
|
|
||||||
|
<button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
|
||||||
onClick={handleStarredButtonClick}
|
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='menu-button-label'>Starred</span>
|
||||||
|
<span styleName='counters'>{counterStarredNote}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
||||||
|
onClick={handleTrashedButtonClick}
|
||||||
|
>
|
||||||
|
<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>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,7 +71,9 @@ SideNavFilter.propTypes = {
|
|||||||
isHomeActive: PropTypes.bool.isRequired,
|
isHomeActive: PropTypes.bool.isRequired,
|
||||||
handleAllNotesButtonClick: PropTypes.func.isRequired,
|
handleAllNotesButtonClick: PropTypes.func.isRequired,
|
||||||
isStarredActive: PropTypes.bool.isRequired,
|
isStarredActive: PropTypes.bool.isRequired,
|
||||||
handleStarredButtonClick: PropTypes.func.isRequired
|
isTrashedActive: PropTypes.bool.isRequired,
|
||||||
|
handleStarredButtonClick: PropTypes.func.isRequired,
|
||||||
|
handleTrashdButtonClick: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(SideNavFilter, styles)
|
export default CSSModules(SideNavFilter, styles)
|
||||||
|
|||||||
@@ -3,30 +3,68 @@
|
|||||||
|
|
||||||
.menu-button
|
.menu-button
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
height 32px
|
height 36px
|
||||||
padding 0 15px
|
padding 0 15px 0 20px
|
||||||
font-size 14px
|
font-size 14px
|
||||||
width 100%
|
width 100%
|
||||||
text-align left
|
text-align left
|
||||||
overflow ellipsis
|
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
|
.menu-button--active
|
||||||
@extend .menu-button
|
@extend .menu-button
|
||||||
background-color $ui-button--active-backgroundColor
|
SideNavFilter()
|
||||||
color $ui-button--active-color
|
color #1EC38B
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
.menu-button-label, .counters
|
||||||
|
color #1EC38B
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-button--active-backgroundColor
|
color #1EC38B
|
||||||
|
|
||||||
|
.menu-button-star--active
|
||||||
|
@extend .menu-button
|
||||||
|
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
|
.menu-button-label
|
||||||
margin-left 5px
|
margin-left 10px
|
||||||
|
flex 1
|
||||||
|
|
||||||
.menu--folded
|
.menu--folded
|
||||||
@extend .menu
|
@extend .menu
|
||||||
.menu-button, .menu-button--active
|
.menu-button, .menu-button--active
|
||||||
text-align center
|
text-align center
|
||||||
|
padding 0 12px
|
||||||
&:hover .menu-button-label
|
&:hover .menu-button-label
|
||||||
transition opacity 0.15s
|
transition opacity 0.15s
|
||||||
opacity 1
|
opacity 1
|
||||||
|
color $ui-tooltip-text-color
|
||||||
|
background-color $ui-tooltip-backgroundColor
|
||||||
|
|
||||||
|
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
position fixed
|
position fixed
|
||||||
display inline-block
|
display inline-block
|
||||||
@@ -36,23 +74,112 @@
|
|||||||
margin-top -8px
|
margin-top -8px
|
||||||
margin-left 0
|
margin-left 0
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
background-color $ui-tooltip-backgroundColor
|
|
||||||
z-index 10
|
z-index 10
|
||||||
color white
|
|
||||||
line-height 32px
|
line-height 32px
|
||||||
border-top-right-radius 2px
|
border-top-right-radius 2px
|
||||||
border-bottom-right-radius 2px
|
border-bottom-right-radius 2px
|
||||||
pointer-events none
|
pointer-events none
|
||||||
opacity 0
|
opacity 0
|
||||||
font-size 12px
|
font-size 13px
|
||||||
|
.counters
|
||||||
|
display none
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="white"]
|
||||||
.menu-button
|
.menu-button
|
||||||
navDarkButtonColor()
|
navWhiteButtonColor()
|
||||||
|
|
||||||
|
.counters
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.menu-button--active
|
.menu-button--active
|
||||||
@extend .menu-button
|
@extend .menu-button
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
color #e74c3c
|
||||||
color $ui-dark-button--active-color
|
background-color $ui-button--active-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
&:hover
|
&: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
|
||||||
|
&:active
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.menu-button--active
|
||||||
|
color #c0392b
|
||||||
|
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 #c0392b
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.menu-button-star--active
|
||||||
|
color $ui-favorite-star-button-color
|
||||||
|
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 $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
|
||||||
@@ -86,7 +86,7 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { isActive, snippet, isDeletable } = this.props
|
const { isActive, snippet, isDeletable } = this.props
|
||||||
return (
|
return (
|
||||||
<div styleName={isActive
|
<div styleName={isActive
|
||||||
? 'root--active'
|
? 'root--active'
|
||||||
@@ -96,6 +96,7 @@ class SnippetTab extends React.Component {
|
|||||||
{!this.state.isRenaming
|
{!this.state.isRenaming
|
||||||
? <button styleName='button'
|
? <button styleName='button'
|
||||||
onClick={(e) => this.handleClick(e)}
|
onClick={(e) => this.handleClick(e)}
|
||||||
|
onDoubleClick={(e) => this.handleRenameClick(e)}
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
>
|
>
|
||||||
{snippet.name.trim().length > 0
|
{snippet.name.trim().length > 0
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ body[data-theme="dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
|
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
|
||||||
&:active
|
&:active
|
||||||
color white
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
.root--active
|
.root--active
|
||||||
@@ -73,7 +73,7 @@ body[data-theme="dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
|
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
|
||||||
&:active
|
&:active
|
||||||
color white
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
.button
|
.button
|
||||||
@@ -83,9 +83,9 @@ body[data-theme="dark"]
|
|||||||
transition color background-color 0.15s
|
transition color background-color 0.15s
|
||||||
border-left 4px solid transparent
|
border-left 4px solid transparent
|
||||||
&:hover
|
&:hover
|
||||||
color white
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
|
||||||
.input
|
.input
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
color white
|
color $ui-dark-text-color
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Micro component for showing storage.
|
* @fileoverview Micro component for showing storage.
|
||||||
*/
|
*/
|
||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import styles from './StorageItem.styl'
|
import styles from './StorageItem.styl'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import { isNumber } from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} isActive
|
* @param {boolean} isActive
|
||||||
@@ -14,11 +15,14 @@ import { isNumber } from 'lodash'
|
|||||||
* @param {string} folderColor
|
* @param {string} folderColor
|
||||||
* @param {boolean} isFolded
|
* @param {boolean} isFolded
|
||||||
* @param {number} noteCount
|
* @param {number} noteCount
|
||||||
|
* @param {Function} handleDrop
|
||||||
|
* @param {Function} handleDragEnter
|
||||||
|
* @param {Function} handleDragOut
|
||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const StorageItem = ({
|
const StorageItem = ({
|
||||||
isActive, handleButtonClick, handleContextMenu, folderName,
|
isActive, handleButtonClick, handleContextMenu, folderName,
|
||||||
folderColor, isFolded, noteCount
|
folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave
|
||||||
}) => (
|
}) => (
|
||||||
<button styleName={isActive
|
<button styleName={isActive
|
||||||
? 'folderList-item--active'
|
? 'folderList-item--active'
|
||||||
@@ -26,15 +30,16 @@ const StorageItem = ({
|
|||||||
}
|
}
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
>
|
>
|
||||||
<span styleName={isFolded
|
<span styleName={isFolded
|
||||||
? 'folderList-item-name--folded' : 'folderList-item-name'
|
? 'folderList-item-name--folded' : 'folderList-item-name'
|
||||||
}
|
}>
|
||||||
style={{borderColor: folderColor}}
|
<text style={{color: folderColor, paddingRight: '10px'}}>{isActive ? <i className='fa fa-folder-open-o' /> : <i className='fa fa-folder-o' />}</text>{isFolded ? _.truncate(folderName, {length: 1, omission: ''}) : folderName}
|
||||||
>
|
|
||||||
{isFolded ? folderName.substring(0, 1) : folderName}
|
|
||||||
</span>
|
</span>
|
||||||
{(!isFolded && isNumber(noteCount)) &&
|
{(!isFolded && _.isNumber(noteCount)) &&
|
||||||
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
||||||
}
|
}
|
||||||
{isFolded &&
|
{isFolded &&
|
||||||
@@ -52,6 +57,8 @@ StorageItem.propTypes = {
|
|||||||
folderName: PropTypes.string.isRequired,
|
folderName: PropTypes.string.isRequired,
|
||||||
folderColor: PropTypes.string,
|
folderColor: PropTypes.string,
|
||||||
isFolded: PropTypes.bool.isRequired,
|
isFolded: PropTypes.bool.isRequired,
|
||||||
|
handleDragEnter: PropTypes.func.isRequired,
|
||||||
|
handleDragLeave: PropTypes.func.isRequired,
|
||||||
noteCount: PropTypes.number
|
noteCount: PropTypes.number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
.folderList-item
|
.folderList-item
|
||||||
display flex
|
display flex
|
||||||
width 100%
|
width 100%
|
||||||
height 26px
|
height 34px
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
padding 0
|
padding 0
|
||||||
margin-bottom 5px
|
|
||||||
text-align left
|
text-align left
|
||||||
border none
|
border none
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
@@ -17,26 +16,28 @@
|
|||||||
&:first-child
|
&:first-child
|
||||||
margin-top 0
|
margin-top 0
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-button--hover-backgroundColor
|
color #1EC38B;
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
transition background-color 0.15s
|
||||||
&:active
|
&:active
|
||||||
color $ui-button--active-color
|
color $$ui-button-default-color
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
|
||||||
.folderList-item--active
|
.folderList-item--active
|
||||||
@extend .folderList-item
|
@extend .folderList-item
|
||||||
color $ui-button--active-color
|
color #1EC38B
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-button--active-color
|
color #1EC38B;
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button-default--active-backgroundColor, 50%)
|
||||||
|
|
||||||
.folderList-item-name
|
.folderList-item-name
|
||||||
display block
|
display block
|
||||||
flex 1
|
flex 1
|
||||||
padding 0 30px
|
padding 0 12px
|
||||||
height 26px
|
height 26px
|
||||||
line-height 26px
|
line-height 26px
|
||||||
border-width 0 0 0 3px
|
border-width 0 0 0 2px
|
||||||
border-style solid
|
border-style solid
|
||||||
border-color transparent
|
border-color transparent
|
||||||
overflow hidden
|
overflow hidden
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
float right
|
float right
|
||||||
line-height 26px
|
line-height 26px
|
||||||
padding-right 15px
|
padding-right 15px
|
||||||
font-size 12px
|
font-size 13px
|
||||||
|
|
||||||
.folderList-item-tooltip
|
.folderList-item-tooltip
|
||||||
tooltip()
|
tooltip()
|
||||||
@@ -67,4 +68,44 @@
|
|||||||
|
|
||||||
.folderList-item-name--folded
|
.folderList-item-name--folded
|
||||||
@extend .folderList-item-name
|
@extend .folderList-item-name
|
||||||
padding-left 14px
|
padding-left 7px
|
||||||
|
text
|
||||||
|
font-size 9px
|
||||||
|
|
||||||
|
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
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:active
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.folderList-item--active
|
||||||
|
@extend .folderList-item
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
&: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%)
|
||||||
24
browser/components/StorageList.js
Normal file
24
browser/components/StorageList.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for showing StorageList
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React 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)
|
||||||
28
browser/components/TagListItem.js
Normal file
28
browser/components/TagListItem.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for showing TagList.
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React 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%)
|
||||||
28
browser/components/TodoListPercentage.js
Normal file
28
browser/components/TodoListPercentage.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Percentage of todo achievement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './TodoListPercentage.styl'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} percentageOfTodo
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TodoListPercentage = ({
|
||||||
|
percentageOfTodo
|
||||||
|
}) => (
|
||||||
|
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
|
||||||
|
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
|
||||||
|
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
TodoListPercentage.propTypes = {
|
||||||
|
percentageOfTodo: PropTypes.number.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(TodoListPercentage, styles)
|
||||||
32
browser/components/TodoListPercentage.styl
Normal file
32
browser/components/TodoListPercentage.styl
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
.percentageBar
|
||||||
|
position absolute
|
||||||
|
top 50px
|
||||||
|
right 0px
|
||||||
|
left 0px
|
||||||
|
background-color #DADFE1
|
||||||
|
width 100%
|
||||||
|
height: 17px
|
||||||
|
font-size: 12px
|
||||||
|
z-index 100
|
||||||
|
border-radius 2px
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: #1EC38B
|
||||||
|
height 17px
|
||||||
|
border-radius 2px
|
||||||
|
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
|
||||||
|
background-color #363A3D
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: alpha(#939395, 50%)
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color $ui-dark-text-color
|
||||||
34
browser/components/TodoProcess.js
Normal file
34
browser/components/TodoProcess.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Percentage of todo achievement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './TodoProcess.styl'
|
||||||
|
|
||||||
|
const TodoProcess = ({
|
||||||
|
todoStatus: {
|
||||||
|
total: totalTodo,
|
||||||
|
completed: completedTodo
|
||||||
|
}
|
||||||
|
}) => (
|
||||||
|
<div styleName='todo-process' style={{display: totalTodo > 0 ? '' : 'none'}}>
|
||||||
|
<div styleName='todo-process-text'>
|
||||||
|
<i className='fa fa-fw fa-check-square-o' />
|
||||||
|
{completedTodo} of {totalTodo}
|
||||||
|
</div>
|
||||||
|
<div styleName='todo-process-bar'>
|
||||||
|
<div styleName='todo-process-bar--inner' style={{width: parseInt(completedTodo / totalTodo * 100) + '%'}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
TodoProcess.propTypes = {
|
||||||
|
todoStatus: {
|
||||||
|
total: PropTypes.number.isRequired,
|
||||||
|
completed: PropTypes.number.isRequired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(TodoProcess, styles)
|
||||||
45
browser/components/TodoProcess.styl
Normal file
45
browser/components/TodoProcess.styl
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
.todo-process
|
||||||
|
font-size 12px
|
||||||
|
display flex
|
||||||
|
padding-top 15px
|
||||||
|
width 85%
|
||||||
|
|
||||||
|
.todo-process-text
|
||||||
|
display inline-block
|
||||||
|
padding-right 10px
|
||||||
|
white-space nowrap
|
||||||
|
text-overflow ellipsis
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
i
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding-right 5px
|
||||||
|
|
||||||
|
.todo-process-bar
|
||||||
|
display inline-block
|
||||||
|
margin auto
|
||||||
|
height 4px
|
||||||
|
border-radius 10px
|
||||||
|
background-color #DADFE1
|
||||||
|
border-radius 2px
|
||||||
|
flex-grow 1
|
||||||
|
border 1px solid alpha(#6C7A89, 10%)
|
||||||
|
|
||||||
|
.todo-process-bar--inner
|
||||||
|
height 100%
|
||||||
|
border-radius 5px
|
||||||
|
background-color #6C7A89
|
||||||
|
transition 0.3s
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.todo-process
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.todo-process-bar
|
||||||
|
background-color #363A3D
|
||||||
|
|
||||||
|
.todo-process-text
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.todo-process-bar--inner
|
||||||
|
background-color: alpha(#939395, 50%)
|
||||||
@@ -118,7 +118,7 @@ hr
|
|||||||
h1, h2, h3, h4, h5, h6
|
h1, h2, h3, h4, h5, h6
|
||||||
font-weight bold
|
font-weight bold
|
||||||
h1
|
h1
|
||||||
font-size 2.25em
|
font-size 2.55em
|
||||||
padding-bottom 0.3em
|
padding-bottom 0.3em
|
||||||
line-height 1.2em
|
line-height 1.2em
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
@@ -193,6 +193,7 @@ ol
|
|||||||
&>li>ul, &>li>ol
|
&>li>ul, &>li>ol
|
||||||
margin 0
|
margin 0
|
||||||
code
|
code
|
||||||
|
color #CC305F
|
||||||
padding 0.2em 0.4em
|
padding 0.2em 0.4em
|
||||||
background-color #f7f7f7
|
background-color #f7f7f7
|
||||||
border-radius 3px
|
border-radius 3px
|
||||||
@@ -268,9 +269,19 @@ table
|
|||||||
border-color borderColor
|
border-color borderColor
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px borderColor
|
border-right solid 1px borderColor
|
||||||
|
kbd
|
||||||
|
background-color #fafbfc
|
||||||
|
border solid 1px borderColor
|
||||||
|
border-bottom-color btnColor
|
||||||
|
border-radius 3px
|
||||||
|
box-shadow inset 0 -1px 0 #959da5
|
||||||
|
display inline-block
|
||||||
|
font-size .8em
|
||||||
|
line-height 1
|
||||||
|
padding 3px 5px
|
||||||
|
|
||||||
themeDarkBackground = darken(#21252B, 10%)
|
themeDarkBackground = darken(#21252B, 10%)
|
||||||
themeDarkText = #DDDDDD
|
themeDarkText = #f9f9f9
|
||||||
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
||||||
themeDarkPreview = $ui-dark-noteDetail-backgroundColor
|
themeDarkPreview = $ui-dark-noteDetail-backgroundColor
|
||||||
themeDarkTableOdd = themeDarkPreview
|
themeDarkTableOdd = themeDarkPreview
|
||||||
@@ -288,8 +299,9 @@ body[data-theme="dark"]
|
|||||||
background-color alpha(lighten(brandColor, 30%), 0.2) !important
|
background-color alpha(lighten(brandColor, 30%), 0.2) !important
|
||||||
|
|
||||||
code
|
code
|
||||||
|
color #EA6730
|
||||||
border-color darken(themeDarkBorder, 10%)
|
border-color darken(themeDarkBorder, 10%)
|
||||||
background-color lighten(themeDarkPreview, 10%)
|
background-color lighten(themeDarkPreview, 5%)
|
||||||
|
|
||||||
pre
|
pre
|
||||||
border-color lighten(#21252B, 20%)
|
border-color lighten(#21252B, 20%)
|
||||||
@@ -315,3 +327,6 @@ body[data-theme="dark"]
|
|||||||
border-color themeDarkTableBorder
|
border-color themeDarkTableBorder
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px themeDarkTableBorder
|
border-right solid 1px themeDarkTableBorder
|
||||||
|
kbd
|
||||||
|
background-color themeDarkBorder
|
||||||
|
color themeDarkText
|
||||||
@@ -64,7 +64,7 @@ $list-width = 250px
|
|||||||
|
|
||||||
.result-nav-storageList
|
.result-nav-storageList
|
||||||
absolute bottom left right
|
absolute bottom left right
|
||||||
top 80px + 32px + 10px + 10px
|
top 110px + 32px + 10px + 10px + 20px
|
||||||
overflow-y auto
|
overflow-y auto
|
||||||
|
|
||||||
.result-list
|
.result-list
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import MarkdownPreview from 'browser/components/MarkdownPreview'
|
|||||||
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||||
import CodeEditor from 'browser/components/CodeEditor'
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { clipboard } = electron
|
const { clipboard } = electron
|
||||||
@@ -55,7 +56,7 @@ class NoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectPriorSnippet () {
|
selectPriorSnippet () {
|
||||||
let { note } = this.props
|
const { note } = this.props
|
||||||
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
||||||
this.setState({
|
this.setState({
|
||||||
snippetIndex: (this.state.snippetIndex + note.snippets.length - 1) % note.snippets.length
|
snippetIndex: (this.state.snippetIndex + note.snippets.length - 1) % note.snippets.length
|
||||||
@@ -64,7 +65,7 @@ class NoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectNextSnippet () {
|
selectNextSnippet () {
|
||||||
let { note } = this.props
|
const { note } = this.props
|
||||||
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
||||||
this.setState({
|
this.setState({
|
||||||
snippetIndex: (this.state.snippetIndex + 1) % note.snippets.length
|
snippetIndex: (this.state.snippetIndex + 1) % note.snippets.length
|
||||||
@@ -73,7 +74,7 @@ class NoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveToClipboard () {
|
saveToClipboard () {
|
||||||
let { note } = this.props
|
const { note } = this.props
|
||||||
|
|
||||||
if (note.type === 'MARKDOWN_NOTE') {
|
if (note.type === 'MARKDOWN_NOTE') {
|
||||||
clipboard.writeText(note.content)
|
clipboard.writeText(note.content)
|
||||||
@@ -94,7 +95,7 @@ class NoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { note, config } = this.props
|
const { note, config } = this.props
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
return (
|
return (
|
||||||
<div styleName='root' />
|
<div styleName='root' />
|
||||||
@@ -106,9 +107,11 @@ class NoteDetail extends React.Component {
|
|||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
|
||||||
|
const storage = findStorage(note.storage)
|
||||||
|
|
||||||
if (note.type === 'SNIPPET_NOTE') {
|
if (note.type === 'SNIPPET_NOTE') {
|
||||||
let tabList = note.snippets.map((snippet, index) => {
|
const tabList = note.snippets.map((snippet, index) => {
|
||||||
let isActive = this.state.snippetIndex === index
|
const isActive = this.state.snippetIndex === index
|
||||||
return <div styleName={isActive
|
return <div styleName={isActive
|
||||||
? 'tabList-item--active'
|
? 'tabList-item--active'
|
||||||
: 'tabList-item'
|
: 'tabList-item'
|
||||||
@@ -128,8 +131,8 @@ class NoteDetail extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
})
|
})
|
||||||
|
|
||||||
let viewList = note.snippets.map((snippet, index) => {
|
const viewList = note.snippets.map((snippet, index) => {
|
||||||
let isActive = this.state.snippetIndex === index
|
const isActive = this.state.snippetIndex === index
|
||||||
|
|
||||||
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
@@ -143,6 +146,7 @@ class NoteDetail extends React.Component {
|
|||||||
config={config}
|
config={config}
|
||||||
value={snippet.content}
|
value={snippet.content}
|
||||||
ref={'code-' + index}
|
ref={'code-' + index}
|
||||||
|
storageKey={note.storage}
|
||||||
/>
|
/>
|
||||||
: <CodeEditor styleName='tabView-content'
|
: <CodeEditor styleName='tabView-content'
|
||||||
mode={snippet.mode}
|
mode={snippet.mode}
|
||||||
@@ -192,6 +196,8 @@ class NoteDetail extends React.Component {
|
|||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
value={note.content}
|
value={note.content}
|
||||||
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
|
storagePath={storage.path}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
.root
|
.root
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
left $note-detail-left-margin
|
bottom 30px
|
||||||
right $note-detail-right-margin
|
margin 0 25px
|
||||||
height 100%
|
height 100%
|
||||||
width 365px
|
width 365px
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|||||||
@@ -18,18 +18,18 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate () {
|
||||||
let { index } = this.props
|
const { index } = this.props
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
let list = this.refs.root
|
const list = this.refs.root
|
||||||
let item = list.childNodes[index]
|
const item = list.childNodes[index]
|
||||||
if (item == null) return null
|
if (item == null) return null
|
||||||
|
|
||||||
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
const overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
||||||
if (overflowBelow) {
|
if (overflowBelow) {
|
||||||
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
|
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
|
||||||
}
|
}
|
||||||
let overflowAbove = list.scrollTop > item.offsetTop
|
const overflowAbove = list.scrollTop > item.offsetTop
|
||||||
if (overflowAbove) {
|
if (overflowAbove) {
|
||||||
list.scrollTop = item.offsetTop
|
list.scrollTop = item.offsetTop
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleScroll (e) {
|
handleScroll (e) {
|
||||||
let { notes } = this.props
|
const { notes } = this.props
|
||||||
|
|
||||||
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 100 && notes.length > this.state.range * 10 + 10) {
|
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 100 && notes.length > this.state.range * 10 + 10) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -54,9 +54,9 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { notes, index } = this.props
|
const { notes, index } = this.props
|
||||||
|
|
||||||
let notesList = notes
|
const notesList = notes
|
||||||
.slice(0, 10 + 10 * this.state.range)
|
.slice(0, 10 + 10 * this.state.range)
|
||||||
.map((note, _index) => {
|
.map((note, _index) => {
|
||||||
const isActive = (index === _index)
|
const isActive = (index === _index)
|
||||||
@@ -70,7 +70,6 @@ class NoteList extends React.Component {
|
|||||||
dateDisplay={dateDisplay}
|
dateDisplay={dateDisplay}
|
||||||
key={key}
|
key={key}
|
||||||
handleNoteClick={(e) => this.props.handleNoteClick(e, _index)}
|
handleNoteClick={(e) => this.props.handleNoteClick(e, _index)}
|
||||||
handleNoteContextMenu={() => ''}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,18 +19,18 @@ class StorageSection extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleHeaderClick (e) {
|
handleHeaderClick (e) {
|
||||||
let { storage } = this.props
|
const { storage } = this.props
|
||||||
this.props.handleStorageButtonClick(e, storage.key)
|
this.props.handleStorageButtonClick(e, storage.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderClick (e, folder) {
|
handleFolderClick (e, folder) {
|
||||||
let { storage } = this.props
|
const { storage } = this.props
|
||||||
this.props.handleFolderButtonClick(e, storage.key, folder.key)
|
this.props.handleFolderButtonClick(e, storage.key, folder.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { storage, filter } = this.props
|
const { storage, filter } = this.props
|
||||||
let folderList = storage.folders
|
const folderList = storage.folders
|
||||||
.map(folder => (
|
.map(folder => (
|
||||||
<StorageItem
|
<StorageItem
|
||||||
key={folder.key}
|
key={folder.key}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import { connect, Provider } from 'react-redux'
|
import { connect, Provider } from 'react-redux'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ipc from './ipcClient'
|
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './FinderMain.styl'
|
import styles from './FinderMain.styl'
|
||||||
@@ -10,15 +10,17 @@ import StorageSection from './StorageSection'
|
|||||||
import NoteList from './NoteList'
|
import NoteList from './NoteList'
|
||||||
import NoteDetail from './NoteDetail'
|
import NoteDetail from './NoteDetail'
|
||||||
import SideNavFilter from 'browser/components/SideNavFilter'
|
import SideNavFilter from 'browser/components/SideNavFilter'
|
||||||
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
require('!!style!css!stylus?sourceMap!../main/global.styl')
|
require('!!style!css!stylus?sourceMap!../main/global.styl')
|
||||||
require('../lib/customMeta')
|
require('../lib/customMeta')
|
||||||
|
require('./ipcClient.js')
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const { Menu } = remote
|
const { Menu } = remote
|
||||||
|
|
||||||
function hideFinder () {
|
function hideFinder () {
|
||||||
let finderWindow = remote.getCurrentWindow()
|
const finderWindow = remote.getCurrentWindow()
|
||||||
if (global.process.platform === 'win32') {
|
if (global.process.platform === 'win32') {
|
||||||
finderWindow.blur()
|
finderWindow.blur()
|
||||||
finderWindow.hide()
|
finderWindow.hide()
|
||||||
@@ -68,7 +70,7 @@ class FinderMain extends React.Component {
|
|||||||
|
|
||||||
handleWindowBlur (e) {
|
handleWindowBlur (e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
search: '',
|
search: ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +96,7 @@ class FinderMain extends React.Component {
|
|||||||
|
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
this.refs.detail.saveToClipboard()
|
this.refs.detail.saveToClipboard()
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('COPY_FINDER')
|
||||||
hideFinder()
|
hideFinder()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
@@ -134,7 +137,7 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleOnlySnippetCheckboxChange (e) {
|
handleOnlySnippetCheckboxChange (e) {
|
||||||
let { filter } = this.state
|
const { filter } = this.state
|
||||||
filter.includeSnippet = e.target.checked
|
filter.includeSnippet = e.target.checked
|
||||||
this.setState({
|
this.setState({
|
||||||
filter: filter,
|
filter: filter,
|
||||||
@@ -145,7 +148,7 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleOnlyMarkdownCheckboxChange (e) {
|
handleOnlyMarkdownCheckboxChange (e) {
|
||||||
let { filter } = this.state
|
const { filter } = this.state
|
||||||
filter.includeMarkdown = e.target.checked
|
filter.includeMarkdown = e.target.checked
|
||||||
this.refs.list.resetScroll()
|
this.refs.list.resetScroll()
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -157,7 +160,7 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleAllNotesButtonClick (e) {
|
handleAllNotesButtonClick (e) {
|
||||||
let { filter } = this.state
|
const { filter } = this.state
|
||||||
filter.type = 'ALL'
|
filter.type = 'ALL'
|
||||||
this.refs.list.resetScroll()
|
this.refs.list.resetScroll()
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -169,7 +172,7 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleStarredButtonClick (e) {
|
handleStarredButtonClick (e) {
|
||||||
let { filter } = this.state
|
const { filter } = this.state
|
||||||
filter.type = 'STARRED'
|
filter.type = 'STARRED'
|
||||||
this.refs.list.resetScroll()
|
this.refs.list.resetScroll()
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -181,7 +184,7 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleStorageButtonClick (e, storage) {
|
handleStorageButtonClick (e, storage) {
|
||||||
let { filter } = this.state
|
const { filter } = this.state
|
||||||
filter.type = 'STORAGE'
|
filter.type = 'STORAGE'
|
||||||
filter.storage = storage
|
filter.storage = storage
|
||||||
this.refs.list.resetScroll()
|
this.refs.list.resetScroll()
|
||||||
@@ -194,7 +197,7 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFolderButtonClick (e, storage, folder) {
|
handleFolderButtonClick (e, storage, folder) {
|
||||||
let { filter } = this.state
|
const { filter } = this.state
|
||||||
filter.type = 'FOLDER'
|
filter.type = 'FOLDER'
|
||||||
filter.storage = storage
|
filter.storage = storage
|
||||||
filter.folder = folder
|
filter.folder = folder
|
||||||
@@ -216,12 +219,12 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { data, config } = this.props
|
const { data, config } = this.props
|
||||||
let { filter, search } = this.state
|
const { filter, search } = this.state
|
||||||
let storageList = []
|
const storageList = []
|
||||||
for (let key in data.storageMap) {
|
for (const key in data.storageMap) {
|
||||||
let storage = data.storageMap[key]
|
const storage = data.storageMap[key]
|
||||||
let item = (
|
const item = (
|
||||||
<StorageSection
|
<StorageSection
|
||||||
filter={filter}
|
filter={filter}
|
||||||
storage={storage}
|
storage={storage}
|
||||||
@@ -250,7 +253,7 @@ class FinderMain extends React.Component {
|
|||||||
notes.push(data.noteMap[id])
|
notes.push(data.noteMap[id])
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
for (let key in data.noteMap) {
|
for (const key in data.noteMap) {
|
||||||
notes.push(data.noteMap[key])
|
notes.push(data.noteMap[key])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,13 +265,13 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (search.trim().length > 0) {
|
if (search.trim().length > 0) {
|
||||||
let needle = new RegExp(_.escapeRegExp(search.trim()), 'i')
|
const needle = new RegExp(_.escapeRegExp(search.trim()), 'i')
|
||||||
notes = notes.filter((note) => note.title.match(needle))
|
notes = notes.filter((note) => note.title.match(needle))
|
||||||
}
|
}
|
||||||
notes = notes
|
notes = notes
|
||||||
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
||||||
|
|
||||||
let activeNote = notes[this.state.index]
|
const activeNote = notes[this.state.index]
|
||||||
this.noteCount = notes.length
|
this.noteCount = notes.length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ nodeIpc.config.retry = 1500
|
|||||||
nodeIpc.config.silent = true
|
nodeIpc.config.silent = true
|
||||||
|
|
||||||
function killFinder () {
|
function killFinder () {
|
||||||
let finderWindow = remote.getCurrentWindow()
|
const finderWindow = remote.getCurrentWindow()
|
||||||
finderWindow.removeAllListeners()
|
finderWindow.removeAllListeners()
|
||||||
if (global.process.platform === 'darwin') {
|
if (global.process.platform === 'darwin') {
|
||||||
// Only OSX has another app process.
|
// Only OSX has another app process.
|
||||||
@@ -21,7 +21,7 @@ function killFinder () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggleFinder () {
|
function toggleFinder () {
|
||||||
let finderWindow = remote.getCurrentWindow()
|
const finderWindow = remote.getCurrentWindow()
|
||||||
if (global.process.platform === 'darwin') {
|
if (global.process.platform === 'darwin') {
|
||||||
if (finderWindow.isVisible()) {
|
if (finderWindow.isVisible()) {
|
||||||
finderWindow.hide()
|
finderWindow.hide()
|
||||||
@@ -84,6 +84,8 @@ nodeIpc.connectTo(
|
|||||||
const { config } = payload
|
const { config } = payload
|
||||||
if (config.ui.theme === 'dark') {
|
if (config.ui.theme === 'dark') {
|
||||||
document.body.setAttribute('data-theme', 'dark')
|
document.body.setAttribute('data-theme', 'dark')
|
||||||
|
} else if (config.ui.theme === 'white') {
|
||||||
|
document.body.setAttribute('data-theme', 'white')
|
||||||
} else {
|
} else {
|
||||||
document.body.setAttribute('data-theme', 'default')
|
document.body.setAttribute('data-theme', 'default')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { combineReducers, createStore } from 'redux'
|
|||||||
import { routerReducer } from 'react-router-redux'
|
import { routerReducer } from 'react-router-redux'
|
||||||
import { DEFAULT_CONFIG } from 'browser/main/lib/ConfigManager'
|
import { DEFAULT_CONFIG } from 'browser/main/lib/ConfigManager'
|
||||||
|
|
||||||
let defaultData = {
|
const defaultData = {
|
||||||
storageMap: {},
|
storageMap: {},
|
||||||
noteMap: {},
|
noteMap: {},
|
||||||
starredSet: [],
|
starredSet: [],
|
||||||
@@ -40,12 +40,12 @@ function config (state = DEFAULT_CONFIG, action) {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
let reducer = combineReducers({
|
const reducer = combineReducers({
|
||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
routing: routerReducer
|
routing: routerReducer
|
||||||
})
|
})
|
||||||
|
|
||||||
let store = createStore(reducer)
|
const store = createStore(reducer)
|
||||||
|
|
||||||
export default store
|
export default store
|
||||||
|
|||||||
@@ -38,15 +38,15 @@ class MutableMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
map (cb) {
|
map (cb) {
|
||||||
let result = []
|
const result = []
|
||||||
for (let [key, value] of this._map) {
|
for (const [key, value] of this._map) {
|
||||||
result.push(cb(value, key))
|
result.push(cb(value, key))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
toJS () {
|
toJS () {
|
||||||
let result = {}
|
const result = {}
|
||||||
for (let [key, value] of this._map) {
|
for (let [key, value] of this._map) {
|
||||||
if (value instanceof MutableSet || value instanceof MutableMap) {
|
if (value instanceof MutableSet || value instanceof MutableMap) {
|
||||||
value = value.toJS()
|
value = value.toJS()
|
||||||
@@ -85,7 +85,7 @@ class MutableSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
map (cb) {
|
map (cb) {
|
||||||
let result = []
|
const result = []
|
||||||
this._set.forEach(function (value, key) {
|
this._set.forEach(function (value, key) {
|
||||||
result.push(cb(value, key))
|
result.push(cb(value, key))
|
||||||
})
|
})
|
||||||
|
|||||||
21
browser/lib/RcParser.js
Normal file
21
browser/lib/RcParser.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import sander from 'sander'
|
||||||
|
|
||||||
|
const BOOSTNOTERC = '.boostnoterc'
|
||||||
|
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
|
||||||
|
const _boostnotercPath = path.join(homePath, BOOSTNOTERC)
|
||||||
|
|
||||||
|
export function parse (boostnotercPath = _boostnotercPath) {
|
||||||
|
if (!sander.existsSync(boostnotercPath)) return {}
|
||||||
|
try {
|
||||||
|
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e)
|
||||||
|
console.warn('Your .boostnoterc is broken so it\'s not used.')
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
parse
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ const themes = fs.readdirSync(themePath)
|
|||||||
.map((themePath) => {
|
.map((themePath) => {
|
||||||
return themePath.substring(0, themePath.lastIndexOf('.'))
|
return themePath.substring(0, themePath.lastIndexOf('.'))
|
||||||
})
|
})
|
||||||
|
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
||||||
|
|
||||||
const consts = {
|
const consts = {
|
||||||
FOLDER_COLORS: [
|
FOLDER_COLORS: [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import moment from 'moment'
|
|||||||
* @param {mixed}
|
* @param {mixed}
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
export function getLastUpdated (date) {
|
export function formatDate (date) {
|
||||||
const m = moment(date)
|
const m = moment(date)
|
||||||
if (!m.isValid()) {
|
if (!m.isValid()) {
|
||||||
throw Error('Invalid argument.')
|
throw Error('Invalid argument.')
|
||||||
|
|||||||
33
browser/lib/findNoteTitle.js
Normal file
33
browser/lib/findNoteTitle.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
export function findNoteTitle (value) {
|
||||||
|
const splitted = value.split('\n')
|
||||||
|
let title = null
|
||||||
|
let isInsideCodeBlock = false
|
||||||
|
|
||||||
|
splitted.some((line, index) => {
|
||||||
|
const trimmedLine = line.trim()
|
||||||
|
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
||||||
|
if (trimmedLine.match('```')) {
|
||||||
|
isInsideCodeBlock = !isInsideCodeBlock
|
||||||
|
}
|
||||||
|
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
|
||||||
|
title = trimmedLine
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (title === null) {
|
||||||
|
title = ''
|
||||||
|
splitted.some((line) => {
|
||||||
|
if (line.trim().length > 0) {
|
||||||
|
title = line.trim()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
findNoteTitle
|
||||||
|
}
|
||||||
14
browser/lib/findStorage.js
Normal file
14
browser/lib/findStorage.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
|
export function findStorage (storageKey) {
|
||||||
|
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
|
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||||
|
const storage = _.find(cachedStorageList, {key: storageKey})
|
||||||
|
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
||||||
|
|
||||||
|
return storage
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
findStorage
|
||||||
|
}
|
||||||
25
browser/lib/getTodoStatus.js
Normal file
25
browser/lib/getTodoStatus.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export function getTodoStatus (content) {
|
||||||
|
const splitted = content.split('\n')
|
||||||
|
let numberOfTodo = 0
|
||||||
|
let numberOfCompletedTodo = 0
|
||||||
|
|
||||||
|
splitted.forEach((line) => {
|
||||||
|
const trimmedLine = line.trim()
|
||||||
|
if (trimmedLine.match(/^[\+\-\*] \[\s|x\] ./)) {
|
||||||
|
numberOfTodo++
|
||||||
|
}
|
||||||
|
if (trimmedLine.match(/^[\+\-\*] \[x\] ./)) {
|
||||||
|
numberOfCompletedTodo++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: numberOfTodo,
|
||||||
|
completed: numberOfCompletedTodo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTodoPercentageOfCompleted (content) {
|
||||||
|
const state = getTodoStatus(content)
|
||||||
|
return Math.floor(state.completed / state.total * 100)
|
||||||
|
}
|
||||||
45
browser/lib/htmlTextHelper.js
Normal file
45
browser/lib/htmlTextHelper.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Text trimmer for html.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function decodeEntities (text) {
|
||||||
|
var entities = [
|
||||||
|
['apos', '\''],
|
||||||
|
['amp', '&'],
|
||||||
|
['lt', '<'],
|
||||||
|
['gt', '>'],
|
||||||
|
['#63', '\\?'],
|
||||||
|
['#36', '\\$']
|
||||||
|
]
|
||||||
|
|
||||||
|
for (var i = 0, max = entities.length; i < max; ++i) {
|
||||||
|
text = text.replace(new RegExp(`&${entities[i][0]};`, 'g'), entities[i][1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodeEntities (text) {
|
||||||
|
const entities = [
|
||||||
|
['\'', 'apos'],
|
||||||
|
['<', 'lt'],
|
||||||
|
['>', 'gt'],
|
||||||
|
['\\?', '#63'],
|
||||||
|
['\\$', '#36']
|
||||||
|
]
|
||||||
|
|
||||||
|
entities.forEach((entity) => {
|
||||||
|
text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`)
|
||||||
|
})
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
decodeEntities,
|
||||||
|
encodeEntities
|
||||||
|
}
|
||||||
@@ -3,11 +3,12 @@ import emoji from 'markdown-it-emoji'
|
|||||||
import math from '@rokt33r/markdown-it-math'
|
import math from '@rokt33r/markdown-it-math'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
// FIXME We should not depend on global variable.
|
||||||
const katex = window.katex
|
const katex = window.katex
|
||||||
|
|
||||||
function createGutter (str) {
|
function createGutter (str) {
|
||||||
let lc = (str.match(/\n/g) || []).length
|
const lc = (str.match(/\n/g) || []).length
|
||||||
let lines = []
|
const lines = []
|
||||||
for (let i = 1; i <= lc; i++) {
|
for (let i = 1; i <= lc; i++) {
|
||||||
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
|
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
|
||||||
}
|
}
|
||||||
@@ -19,6 +20,7 @@ var md = markdownit({
|
|||||||
linkify: true,
|
linkify: true,
|
||||||
html: true,
|
html: true,
|
||||||
xhtmlOut: true,
|
xhtmlOut: true,
|
||||||
|
breaks: true,
|
||||||
highlight: function (str, lang) {
|
highlight: function (str, lang) {
|
||||||
if (lang === 'flowchart') {
|
if (lang === 'flowchart') {
|
||||||
return `<pre class="flowchart">${str}</pre>`
|
return `<pre class="flowchart">${str}</pre>`
|
||||||
@@ -56,13 +58,26 @@ md.use(math, {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
md.use(require('markdown-it-imsize'))
|
||||||
md.use(require('markdown-it-footnote'))
|
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
|
// Override task item
|
||||||
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||||
let content, terminate, i, l, token
|
let content, terminate, i, l, token
|
||||||
let nextLine = startLine + 1
|
let nextLine = startLine + 1
|
||||||
let terminatorRules = state.md.block.ruler.getRules('paragraph')
|
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||||
let endLine = state.lineMax
|
const endLine = state.lineMax
|
||||||
|
|
||||||
// jump line-by-line until empty one or EOF
|
// jump line-by-line until empty one or EOF
|
||||||
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
||||||
@@ -92,7 +107,7 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
|||||||
token.map = [ startLine, state.line ]
|
token.map = [ startLine, state.line ]
|
||||||
|
|
||||||
if (state.parentType === 'list') {
|
if (state.parentType === 'list') {
|
||||||
let match = content.match(/^\[( |x)\] ?(.+)/i)
|
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
||||||
if (match) {
|
if (match) {
|
||||||
content = `<label class='taskListItem' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
|
content = `<label class='taskListItem' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
|
||||||
}
|
}
|
||||||
@@ -109,7 +124,7 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Add line number attribute for scrolling
|
// Add line number attribute for scrolling
|
||||||
let originalRender = md.renderer.render
|
const originalRender = md.renderer.render
|
||||||
md.renderer.render = function render (tokens, options, env) {
|
md.renderer.render = function render (tokens, options, env) {
|
||||||
tokens.forEach((token) => {
|
tokens.forEach((token) => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
@@ -120,45 +135,23 @@ md.renderer.render = function render (tokens, options, env) {
|
|||||||
token.attrPush(['data-line', token.map[0]])
|
token.attrPush(['data-line', token.map[0]])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
let result = originalRender.call(md.renderer, tokens, options, env)
|
const result = originalRender.call(md.renderer, tokens, options, env)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
// FIXME We should not depend on global variable.
|
||||||
window.md = md
|
window.md = md
|
||||||
|
|
||||||
function strip (input) {
|
function normalizeLinkText (linkText) {
|
||||||
var output = input
|
return md.normalizeLinkText(linkText)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const markdown = {
|
const markdown = {
|
||||||
render: function markdown (content) {
|
render: function markdown (content) {
|
||||||
if (!_.isString(content)) content = ''
|
if (!_.isString(content)) content = ''
|
||||||
return md.render(content)
|
const renderedContent = md.render(content)
|
||||||
|
return renderedContent
|
||||||
},
|
},
|
||||||
strip
|
normalizeLinkText
|
||||||
}
|
}
|
||||||
|
|
||||||
export default markdown
|
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
|
||||||
|
}
|
||||||
43
browser/lib/search.js
Normal file
43
browser/lib/search.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
export default function searchFromNotes (notes, search) {
|
||||||
|
if (search.trim().length === 0) return []
|
||||||
|
const searchBlocks = search.split(' ').filter(block => { return block !== '' })
|
||||||
|
|
||||||
|
let foundNotes = findByWord(notes, searchBlocks[0])
|
||||||
|
searchBlocks.forEach((block) => {
|
||||||
|
foundNotes = findByWord(foundNotes, block)
|
||||||
|
if (block.match(/^#.+/)) {
|
||||||
|
foundNotes = foundNotes.concat(findByTag(notes, block))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return foundNotes
|
||||||
|
}
|
||||||
|
|
||||||
|
function findByTag (notes, block) {
|
||||||
|
const tag = block.match(/#(.+)/)[1]
|
||||||
|
const regExp = new RegExp(_.escapeRegExp(tag), 'i')
|
||||||
|
return notes.filter((note) => {
|
||||||
|
if (!_.isArray(note.tags)) return false
|
||||||
|
return note.tags.some((_tag) => {
|
||||||
|
return _tag.match(regExp)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function findByWord (notes, block) {
|
||||||
|
const regExp = new RegExp(_.escapeRegExp(block), 'i')
|
||||||
|
return notes.filter((note) => {
|
||||||
|
if (_.isArray(note.tags) && note.tags.some((_tag) => {
|
||||||
|
return _tag.match(regExp)
|
||||||
|
})) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (note.type === 'SNIPPET_NOTE') {
|
||||||
|
return note.description.match(regExp)
|
||||||
|
} else if (note.type === 'MARKDOWN_NOTE') {
|
||||||
|
return note.content.match(regExp)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
.root
|
.root
|
||||||
absolute top bottom right
|
absolute top bottom right
|
||||||
border-width 1px 0
|
display flex
|
||||||
border-style solid
|
align-items center
|
||||||
border-color $ui-borderColor
|
justify-content center
|
||||||
|
|
||||||
.empty
|
.empty
|
||||||
height 320px
|
height 320px
|
||||||
@@ -11,14 +11,14 @@
|
|||||||
|
|
||||||
.empty-message
|
.empty-message
|
||||||
width 100%
|
width 100%
|
||||||
font-size 42px
|
font-size 36px
|
||||||
line-height 72px
|
font-weight 600
|
||||||
|
line-height 56px
|
||||||
text-align center
|
text-align center
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
background-color $ui-dark-backgroundColor
|
background-color $ui-dark-backgroundColor
|
||||||
.empty-message
|
.empty-message
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Margin on the left side and the right side for NoteDetail component.
|
// Margin on the left side and the right side for NoteDetail component.
|
||||||
$note-detail-left-margin = 25px
|
$note-detail-left-margin = 100px
|
||||||
$note-detail-right-margin = 25px
|
$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
|
$note-detail-box-shadow = 2px 0 15px -8px #b1b1b1 inset
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './FolderSelect.styl'
|
import styles from './FolderSelect.styl'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
@@ -73,8 +74,8 @@ class FolderSelect extends React.Component {
|
|||||||
case 9:
|
case 9:
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
let tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
|
const tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
|
||||||
let previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
const previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
||||||
if (previousEl != null) previousEl.focus()
|
if (previousEl != null) previousEl.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,9 +90,9 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSearchInputChange (e) {
|
handleSearchInputChange (e) {
|
||||||
let { folders } = this.props
|
const { folders } = this.props
|
||||||
let search = this.refs.search.value
|
const search = this.refs.search.value
|
||||||
let optionIndex = search.length > 0
|
const optionIndex = search.length > 0
|
||||||
? _.findIndex(folders, (folder) => {
|
? _.findIndex(folders, (folder) => {
|
||||||
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
|
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
|
||||||
})
|
})
|
||||||
@@ -129,7 +130,7 @@ class FolderSelect extends React.Component {
|
|||||||
|
|
||||||
nextOption () {
|
nextOption () {
|
||||||
let { optionIndex } = this.state
|
let { optionIndex } = this.state
|
||||||
let { folders } = this.props
|
const { folders } = this.props
|
||||||
|
|
||||||
optionIndex++
|
optionIndex++
|
||||||
if (optionIndex >= folders.length) optionIndex = 0
|
if (optionIndex >= folders.length) optionIndex = 0
|
||||||
@@ -140,7 +141,7 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
previousOption () {
|
previousOption () {
|
||||||
let { folders } = this.props
|
const { folders } = this.props
|
||||||
let { optionIndex } = this.state
|
let { optionIndex } = this.state
|
||||||
|
|
||||||
optionIndex--
|
optionIndex--
|
||||||
@@ -152,10 +153,10 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectOption () {
|
selectOption () {
|
||||||
let { folders } = this.props
|
const { folders } = this.props
|
||||||
let optionIndex = this.state.optionIndex
|
const optionIndex = this.state.optionIndex
|
||||||
|
|
||||||
let folder = folders[optionIndex]
|
const folder = folders[optionIndex]
|
||||||
if (folder != null) {
|
if (folder != null) {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'FOCUS'
|
status: 'FOCUS'
|
||||||
@@ -184,10 +185,10 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className, data, value } = this.props
|
const { className, data, value } = this.props
|
||||||
let splitted = value.split('-')
|
const splitted = value.split('-')
|
||||||
let storageKey = splitted.shift()
|
const storageKey = splitted.shift()
|
||||||
let folderKey = splitted.shift()
|
const folderKey = splitted.shift()
|
||||||
let options = []
|
let options = []
|
||||||
data.storageMap.forEach((storage, index) => {
|
data.storageMap.forEach((storage, index) => {
|
||||||
storage.folders.forEach((folder) => {
|
storage.folders.forEach((folder) => {
|
||||||
@@ -198,14 +199,14 @@ class FolderSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||||
|
|
||||||
if (this.state.search.trim().length > 0) {
|
if (this.state.search.trim().length > 0) {
|
||||||
let filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
||||||
options = options.filter((option) => filter.test(option.folder.name))
|
options = options.filter((option) => filter.test(option.folder.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
let optionList = options
|
const optionList = options
|
||||||
.map((option, index) => {
|
.map((option, index) => {
|
||||||
return (
|
return (
|
||||||
<div styleName={index === this.state.optionIndex
|
<div styleName={index === this.state.optionIndex
|
||||||
@@ -259,12 +260,11 @@ class FolderSelect extends React.Component {
|
|||||||
{optionList}
|
{optionList}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: <div styleName='idle'>
|
: <div styleName='idle' style={{color: currentOption.folder.color}}>
|
||||||
<div styleName='idle-label'>
|
<div styleName='idle-label'>
|
||||||
<span styleName='idle-label-name'
|
<i className='fa fa-folder' />
|
||||||
style={{color: currentOption.folder.color}}
|
<span styleName='idle-label-name'>
|
||||||
>
|
{currentOption.folder.name}
|
||||||
{currentOption.folder.name} /
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
.root
|
.root
|
||||||
position relative
|
position relative
|
||||||
border solid 1px transparent
|
border solid 1px transparent
|
||||||
line-height 34px
|
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
user-select none
|
user-select none
|
||||||
|
margin-right 10px
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-button--hover-backgroundColor
|
background-color $ui-button--hover-backgroundColor
|
||||||
|
|
||||||
.root--search, .root--focus
|
.root--search, .root--focus
|
||||||
@extend .root
|
@extend .root
|
||||||
background-color $ui-noteDetail-backgroundColor = #F4F4F4
|
background-color $ui-noteDetail-backgroundColor = #fff
|
||||||
border-color $ui-input--focus-borderColor
|
border-color $ui-input--focus-borderColor
|
||||||
width 100px
|
width 154px
|
||||||
|
height 30px
|
||||||
&:hover
|
&:hover
|
||||||
border-color $ui-input--focus-borderColor
|
border-color $ui-input--focus-borderColor = #fff
|
||||||
|
|
||||||
.idle
|
.idle
|
||||||
position relative
|
position relative
|
||||||
@@ -24,13 +25,16 @@
|
|||||||
.idle-label
|
.idle-label
|
||||||
right 20px
|
right 20px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
|
||||||
.idle-label-name
|
.idle-label-name
|
||||||
font-size 16px
|
font-size 13px
|
||||||
padding 2px
|
font-weight 600
|
||||||
|
margin-left 4px
|
||||||
|
|
||||||
.idle-label-name-surfix
|
.idle-label-name-surfix
|
||||||
font-size 10px
|
font-size 15px
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
.idle-caret
|
.idle-caret
|
||||||
@@ -39,35 +43,37 @@
|
|||||||
width 20px
|
width 20px
|
||||||
line-height 34px
|
line-height 34px
|
||||||
|
|
||||||
.search
|
|
||||||
absolute top left right bottom
|
|
||||||
line-height 34px
|
|
||||||
|
|
||||||
.search-input
|
.search-input
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
position relative
|
position relative
|
||||||
top -2px
|
top 0
|
||||||
|
font-size 14px
|
||||||
outline none
|
outline none
|
||||||
border none
|
border none
|
||||||
height 20px
|
width 100%
|
||||||
line-height 20px
|
|
||||||
background-color transparent
|
background-color transparent
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
|
|
||||||
.search-optionList
|
.search-optionList
|
||||||
position fixed
|
position absolute
|
||||||
|
top 30px
|
||||||
max-height 450px
|
max-height 450px
|
||||||
|
min-width 150px
|
||||||
overflow auto
|
overflow auto
|
||||||
z-index 200
|
z-index 200
|
||||||
border $ui-border
|
border $ui-border
|
||||||
background-color white
|
background-color white
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
|
padding 10px 6px
|
||||||
|
|
||||||
.search-optionList-item
|
.search-optionList-item
|
||||||
|
width 140px
|
||||||
height 34px
|
height 34px
|
||||||
width 250px
|
display flex
|
||||||
|
align-items center
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
padding 0 5px
|
padding 0
|
||||||
|
margin-bottom 10px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
cursor pointer
|
cursor pointer
|
||||||
&:hover
|
&:hover
|
||||||
@@ -81,13 +87,17 @@
|
|||||||
background-color $ui-button--active-backgroundColor
|
background-color $ui-button--active-backgroundColor
|
||||||
color $ui-button--active-color
|
color $ui-button--active-color
|
||||||
.search-optionList-item-name
|
.search-optionList-item-name
|
||||||
border-left solid 4px transparent
|
border-left solid 3px transparent
|
||||||
padding 2px 5px
|
padding 6px
|
||||||
.search-optionList-item-name-surfix
|
.search-optionList-item-name-surfix
|
||||||
font-size 10px
|
font-size 10px
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|||||||
20
browser/main/Detail/InfoButton.js
Normal file
20
browser/main/Detail/InfoButton.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './InfoButton.styl'
|
||||||
|
|
||||||
|
const InfoButton = ({
|
||||||
|
onClick
|
||||||
|
}) => (
|
||||||
|
<button styleName='control-infoButton'
|
||||||
|
onClick={(e) => onClick(e)}
|
||||||
|
>
|
||||||
|
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
InfoButton.propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(InfoButton, styles)
|
||||||
11
browser/main/Detail/InfoButton.styl
Normal file
11
browser/main/Detail/InfoButton.styl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.control-infoButton
|
||||||
|
top 10px
|
||||||
|
margin-bottom 10px
|
||||||
|
topBarButtonLight()
|
||||||
|
|
||||||
|
.infoButton
|
||||||
|
padding 0px
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.control-infoButton
|
||||||
|
topBarButtonDark()
|
||||||
91
browser/main/Detail/InfoPanel.js
Normal file
91
browser/main/Detail/InfoPanel.js
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './InfoPanel.styl'
|
||||||
|
|
||||||
|
const InfoPanel = ({
|
||||||
|
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type, print
|
||||||
|
}) => (
|
||||||
|
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||||
|
<div>
|
||||||
|
<p styleName='modification-date'>{updatedAt}</p>
|
||||||
|
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
}
|
||||||
|
|
||||||
|
{type === 'SNIPPET_NOTE'
|
||||||
|
? ''
|
||||||
|
: <hr />
|
||||||
|
}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{storageName}</p>
|
||||||
|
<p styleName='infoPanel-sub'>STORAGE</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{folderName}</p>
|
||||||
|
<p styleName='infoPanel-sub'>FOLDER</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{createdAt}</p>
|
||||||
|
<p styleName='infoPanel-sub'>CREATION DATE</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input styleName='infoPanel-noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
|
||||||
|
<p styleName='infoPanel-sub'>NOTE LINK</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div id='export-wrap'>
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||||
|
<i className='fa fa-file-code-o fa-fw' />
|
||||||
|
<p>.md</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
||||||
|
<i className='fa fa-file-text-o fa-fw' />
|
||||||
|
<p>.txt</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--enable' onClick={(e) => print(e)}>
|
||||||
|
<i className='fa fa-print fa-fw' />
|
||||||
|
<p>Print</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
InfoPanel.propTypes = {
|
||||||
|
storageName: PropTypes.string.isRequired,
|
||||||
|
folderName: PropTypes.string.isRequired,
|
||||||
|
noteLink: PropTypes.string.isRequired,
|
||||||
|
updatedAt: PropTypes.string.isRequired,
|
||||||
|
createdAt: PropTypes.string.isRequired,
|
||||||
|
exportAsMd: 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)
|
||||||
163
browser/main/Detail/InfoPanel.styl
Normal file
163
browser/main/Detail/InfoPanel.styl
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
.control-infoPanel
|
||||||
|
position fixed
|
||||||
|
pointer-events none
|
||||||
|
top 50px
|
||||||
|
z-index 200
|
||||||
|
line-height normal
|
||||||
|
border-radius 4px
|
||||||
|
opacity 0
|
||||||
|
transition 0.2s
|
||||||
|
|
||||||
|
.control-infoButton-panel
|
||||||
|
z-index 200
|
||||||
|
margin-top 0px
|
||||||
|
right 0
|
||||||
|
position absolute
|
||||||
|
padding 20px 25px 0 25px
|
||||||
|
width 300px
|
||||||
|
height 350px
|
||||||
|
overflow auto
|
||||||
|
background-color $ui-noteList-backgroundColor
|
||||||
|
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||||
|
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 0px
|
||||||
|
right 0px
|
||||||
|
position absolute
|
||||||
|
padding 20px 25px 0 25px
|
||||||
|
width 300px
|
||||||
|
background-color $ui-noteList-backgroundColor
|
||||||
|
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||||
|
border-radius 2px
|
||||||
|
|
||||||
|
.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
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.infoPanel-sub-count
|
||||||
|
font-size 16px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding-bottom 8px
|
||||||
|
|
||||||
|
.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
|
||||||
|
background-color rgba(226,33,113,0.1)
|
||||||
|
border none
|
||||||
|
outline none
|
||||||
|
border-radius 2px
|
||||||
|
margin-right 5px
|
||||||
|
padding 2px 5px
|
||||||
|
|
||||||
|
[id=export-wrap]
|
||||||
|
height 90px
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
margin 20px 0 10px 0
|
||||||
|
button
|
||||||
|
outline none
|
||||||
|
font-size 48px
|
||||||
|
color #A0A0A0
|
||||||
|
background-color transparent
|
||||||
|
border none
|
||||||
|
margin 0 5px
|
||||||
|
border-radius 5px
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
background-color alpha($ui-button--hover-backgroundColor, 30%)
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
p
|
||||||
|
font-size 13px
|
||||||
|
color #A0A0A0
|
||||||
|
font-weight light
|
||||||
|
&:hover
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.export--enable
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
.export--unable
|
||||||
|
cursor not-allowed
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.control-infoButton-panel
|
||||||
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control-infoButton-panel-trash
|
||||||
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.modification-date
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.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]
|
||||||
|
button
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-borderColor, 20%)
|
||||||
|
color $ui-dark-text-color
|
||||||
|
p
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
60
browser/main/Detail/InfoPanelTrashed.js
Normal file
60
browser/main/Detail/InfoPanelTrashed.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './InfoPanel.styl'
|
||||||
|
|
||||||
|
const InfoPanelTrashed = ({
|
||||||
|
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt
|
||||||
|
}) => (
|
||||||
|
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||||
|
<div>
|
||||||
|
<p styleName='modification-date'>{updatedAt}</p>
|
||||||
|
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<p styleName='infoPanel-default'>{createdAt}</p>
|
||||||
|
<p styleName='infoPanel-sub'>CREATION DATE</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id='export-wrap'>
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||||
|
<i className='fa fa-file-code-o fa-fw' />
|
||||||
|
<p>.md</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
||||||
|
<i className='fa fa-file-text-o fa-fw' />
|
||||||
|
<p>.txt</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--unable'>
|
||||||
|
<i className='fa fa-file-pdf-o fa-fw' />
|
||||||
|
<p>.pdf</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
InfoPanelTrashed.propTypes = {
|
||||||
|
storageName: PropTypes.string.isRequired,
|
||||||
|
folderName: PropTypes.string.isRequired,
|
||||||
|
updatedAt: PropTypes.string.isRequired,
|
||||||
|
createdAt: PropTypes.string.isRequired,
|
||||||
|
exportAsMd: PropTypes.func.isRequired,
|
||||||
|
exportAsTxt: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(InfoPanelTrashed, styles)
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/**
|
|
||||||
* @fileoverview Component for show updated date of the detail.
|
|
||||||
*/
|
|
||||||
import React, { PropTypes } from 'react'
|
|
||||||
import { getLastUpdated } from 'browser/lib/date-formatter'
|
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
|
||||||
import styles from './LastUpdatedString.styl'
|
|
||||||
|
|
||||||
const LastUpdatedString = ({ date }) => {
|
|
||||||
let text = ''
|
|
||||||
|
|
||||||
try {
|
|
||||||
text = `Last updated at ${getLastUpdated(date)}`
|
|
||||||
} catch (e) {
|
|
||||||
text = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p styleName='info-right-date'>{text}</p>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
LastUpdatedString.propTypes = {
|
|
||||||
date: PropTypes.string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CSSModules(LastUpdatedString, styles)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
.info-right-date
|
|
||||||
display inline
|
|
||||||
line-height 24px
|
|
||||||
padding-right 25px
|
|
||||||
font-size 11px
|
|
||||||
color $ui-button-color
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
|
||||||
.info-right-date
|
|
||||||
color $ui-dark-button-color
|
|
||||||
@@ -1,20 +1,32 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './MarkdownNoteDetail.styl'
|
import styles from './MarkdownNoteDetail.styl'
|
||||||
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||||
|
import TodoListPercentage from 'browser/components/TodoListPercentage'
|
||||||
import StarButton from './StarButton'
|
import StarButton from './StarButton'
|
||||||
import TagSelect from './TagSelect'
|
import TagSelect from './TagSelect'
|
||||||
import FolderSelect from './FolderSelect'
|
import FolderSelect from './FolderSelect'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import { hashHistory } from 'react-router'
|
import { hashHistory } from 'react-router'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import markdown from 'browser/lib/markdown'
|
import markdown from 'browser/lib/markdownTextHelper'
|
||||||
import StatusBar from '../StatusBar'
|
import StatusBar from '../StatusBar'
|
||||||
import _ from 'lodash'
|
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 electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { dialog } = remote
|
||||||
|
|
||||||
class MarkdownNoteDetail extends React.Component {
|
class MarkdownNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -49,7 +61,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
note: Object.assign({}, nextProps.note)
|
note: Object.assign({}, nextProps.note)
|
||||||
}, () => {
|
}, () => {
|
||||||
this.refs.content.reload()
|
this.refs.content.reload()
|
||||||
this.refs.tags.reset()
|
if (this.refs.tags) this.refs.tags.reset()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,45 +74,12 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
findTitle (value) {
|
|
||||||
let splitted = value.split('\n')
|
|
||||||
let title = null
|
|
||||||
let isMarkdownInCode = false
|
|
||||||
|
|
||||||
for (let i = 0; i < splitted.length; i++) {
|
|
||||||
let trimmedLine = splitted[i].trim()
|
|
||||||
if (trimmedLine.match('```')) {
|
|
||||||
isMarkdownInCode = !isMarkdownInCode
|
|
||||||
} else if (isMarkdownInCode === false && trimmedLine.match(/^# +/)) {
|
|
||||||
title = trimmedLine.substring(1, trimmedLine.length).trim()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title == null) {
|
|
||||||
for (let i = 0; i < splitted.length; i++) {
|
|
||||||
let trimmedLine = splitted[i].trim()
|
|
||||||
if (trimmedLine.length > 0) {
|
|
||||||
title = trimmedLine
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (title == null) {
|
|
||||||
title = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
title = markdown.strip(title)
|
|
||||||
|
|
||||||
return title
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange (e) {
|
handleChange (e) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
note.content = this.refs.content.value
|
note.content = this.refs.content.value
|
||||||
note.tags = this.refs.tags.value
|
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||||
note.title = this.findTitle(note.content)
|
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
|
||||||
note.updatedAt = new Date()
|
note.updatedAt = new Date()
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -118,7 +97,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveNow () {
|
saveNow () {
|
||||||
let { note, dispatch } = this.props
|
const { note, dispatch } = this.props
|
||||||
clearTimeout(this.saveQueue)
|
clearTimeout(this.saveQueue)
|
||||||
this.saveQueue = null
|
this.saveQueue = null
|
||||||
|
|
||||||
@@ -129,15 +108,16 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderChange (e) {
|
handleFolderChange (e) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
let value = this.refs.folder.value
|
const value = this.refs.folder.value
|
||||||
let splitted = value.split('-')
|
const splitted = value.split('-')
|
||||||
let newStorageKey = splitted.shift()
|
const newStorageKey = splitted.shift()
|
||||||
let newFolderKey = splitted.shift()
|
const newFolderKey = splitted.shift()
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||||
@@ -146,7 +126,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
isMovingNote: true,
|
isMovingNote: true,
|
||||||
note: Object.assign({}, newNote)
|
note: Object.assign({}, newNote)
|
||||||
}, () => {
|
}, () => {
|
||||||
let { dispatch, location } = this.props
|
const { dispatch, location } = this.props
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
originNote: note,
|
originNote: note,
|
||||||
@@ -166,7 +146,8 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleStarButtonClick (e) {
|
handleStarButtonClick (e) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||||
|
|
||||||
note.isStarred = !note.isStarred
|
note.isStarred = !note.isStarred
|
||||||
|
|
||||||
@@ -181,28 +162,31 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextButtonClick (e) {
|
exportAsMd () {
|
||||||
let menu = new Menu()
|
ee.emit('export:save-md')
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: 'Delete',
|
|
||||||
click: (e) => this.handleDeleteMenuClick(e)
|
|
||||||
}))
|
|
||||||
menu.popup(remote.getCurrentWindow())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteMenuClick (e) {
|
exportAsTxt () {
|
||||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
ee.emit('export:save-text')
|
||||||
type: 'warning',
|
}
|
||||||
message: 'Delete a note',
|
|
||||||
detail: 'This work cannot be undone.',
|
handleTrashButtonClick (e) {
|
||||||
buttons: ['Confirm', 'Cancel']
|
const { note } = this.state
|
||||||
})
|
const { isTrashed } = note
|
||||||
if (index === 0) {
|
|
||||||
let { note, dispatch } = this.props
|
if (isTrashed) {
|
||||||
|
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Confirm note deletion',
|
||||||
|
detail: 'This will permanently remove this note.',
|
||||||
|
buttons: ['Confirm', 'Cancel']
|
||||||
|
})
|
||||||
|
if (dialogueButtonIndex === 1) return
|
||||||
|
const { note, dispatch } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.deleteNote(note.storage, note.key)
|
.deleteNote(note.storage, note.key)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
let dispatchHandler = () => {
|
const dispatchHandler = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'DELETE_NOTE',
|
type: 'DELETE_NOTE',
|
||||||
storageKey: data.storageKey,
|
storageKey: data.storageKey,
|
||||||
@@ -210,9 +194,35 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
ee.once('list:moved', dispatchHandler)
|
ee.once('list:moved', dispatchHandler)
|
||||||
ee.emit('list:next')
|
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
note.isTrashed = true
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
note
|
||||||
|
}, () => {
|
||||||
|
this.save()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
ee.emit('list:next')
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUndoButtonClick (e) {
|
||||||
|
const { note } = this.state
|
||||||
|
|
||||||
|
note.isTrashed = false
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
note
|
||||||
|
}, () => {
|
||||||
|
this.save()
|
||||||
|
this.refs.content.reload()
|
||||||
|
ee.emit('list:next')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFullScreenButton (e) {
|
||||||
|
ee.emit('editor:fullscreen')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLockButtonMouseDown (e) {
|
handleLockButtonMouseDown (e) {
|
||||||
@@ -223,7 +233,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getToggleLockButton () {
|
getToggleLockButton () {
|
||||||
return this.state.isLocked ? 'fa-lock' : 'fa-unlock-alt'
|
return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg'
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteKeyDown (e) {
|
handleDeleteKeyDown (e) {
|
||||||
@@ -243,68 +253,131 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
this.focus()
|
this.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleInfoButtonClick (e) {
|
||||||
|
const infoPanel = document.querySelector('.infoPanel')
|
||||||
|
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
print (e) {
|
||||||
|
ee.emit('print')
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { data, config } = this.props
|
const { data, config, location } = this.props
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
const storageKey = note.storage
|
||||||
|
const folderKey = note.folder
|
||||||
|
|
||||||
|
const options = []
|
||||||
|
data.storageMap.forEach((storage, index) => {
|
||||||
|
storage.folders.forEach((folder) => {
|
||||||
|
options.push({
|
||||||
|
storage: storage,
|
||||||
|
folder: folder
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||||
|
|
||||||
|
const trashTopBar = <div styleName='info'>
|
||||||
|
<div styleName='info-left'>
|
||||||
|
<i styleName='undo-button'
|
||||||
|
className='fa fa-undo fa-fw'
|
||||||
|
onClick={(e) => this.handleUndoButtonClick(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div styleName='info-right'>
|
||||||
|
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||||
|
<InfoButton
|
||||||
|
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||||
|
/>
|
||||||
|
<InfoPanelTrashed
|
||||||
|
storageName={currentOption.storage.name}
|
||||||
|
folderName={currentOption.folder.name}
|
||||||
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
|
createdAt={formatDate(note.createdAt)}
|
||||||
|
exportAsMd={this.exportAsMd}
|
||||||
|
exportAsTxt={this.exportAsTxt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
const detailTopBar = <div styleName='info'>
|
||||||
|
<div styleName='info-left'>
|
||||||
|
<div styleName='info-left-top'>
|
||||||
|
<FolderSelect styleName='info-left-top-folderSelect'
|
||||||
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
|
ref='folder'
|
||||||
|
data={data}
|
||||||
|
onChange={(e) => this.handleFolderChange(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TagSelect
|
||||||
|
ref='tags'
|
||||||
|
value={this.state.note.tags}
|
||||||
|
onChange={(e) => this.handleChange(e)}
|
||||||
|
/>
|
||||||
|
<TodoListPercentage
|
||||||
|
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div styleName='info-right'>
|
||||||
|
<InfoButton
|
||||||
|
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StarButton
|
||||||
|
onClick={(e) => this.handleStarButtonClick(e)}
|
||||||
|
isActive={note.isStarred}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{(() => {
|
||||||
|
const imgSrc = `${this.getToggleLockButton()}`
|
||||||
|
const lockButtonComponent =
|
||||||
|
<button styleName='control-lockButton'
|
||||||
|
onFocus={(e) => this.handleFocus(e)}
|
||||||
|
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
||||||
|
>
|
||||||
|
<img styleName='iconInfo' src={imgSrc} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
|
||||||
|
<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}
|
||||||
|
noteLink={`[${note.title}](${location.query.key})`}
|
||||||
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
|
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>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='NoteDetail'
|
<div className='NoteDetail'
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
styleName='root'
|
styleName='root'
|
||||||
>
|
>
|
||||||
<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}
|
|
||||||
ref='folder'
|
|
||||||
data={data}
|
|
||||||
onChange={(e) => this.handleFolderChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<TagSelect
|
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||||
ref='tags'
|
|
||||||
value={this.state.note.tags}
|
|
||||||
onChange={(e) => this.handleChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div styleName='info-right'>
|
|
||||||
{(() => {
|
|
||||||
const faClassName=`fa ${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>
|
|
||||||
</button>
|
|
||||||
return (
|
|
||||||
this.state.isLockButtonShown ? lockButtonComponent : ''
|
|
||||||
)
|
|
||||||
})()}
|
|
||||||
<button styleName='control-trashButton'
|
|
||||||
onClick={(e) => this.handleContextButtonClick(e)}
|
|
||||||
>
|
|
||||||
<svg height="17px" id="Capa_1" style={{"enableBackground":"new 0 0 753.23 753.23"}} width="17px" version="1.1" viewBox="0 0 753.23 753.23" x="0px" y="0px" xmlSpace="preserve">
|
|
||||||
<g>
|
|
||||||
<g id="_x34__19_">
|
|
||||||
<g>
|
|
||||||
<path d="M494.308,659.077c12.993,0,23.538-10.546,23.538-23.539V353.077c0-12.993-10.545-23.539-23.538-23.539
				s-23.538,10.545-23.538,23.539v282.461C470.77,648.531,481.314,659.077,494.308,659.077z M635.538,94.154h-141.23V47.077
				C494.308,21.067,473.24,0,447.23,0H306c-26.01,0-47.077,21.067-47.077,47.077v47.077h-141.23
				c-26.01,0-47.077,21.067-47.077,47.077v47.077c0,25.986,21.067,47.077,47.077,47.077v423.692
				c0,51.996,42.157,94.153,94.154,94.153h329.539c51.996,0,94.153-42.157,94.153-94.153V235.385
				c26.01,0,47.077-21.091,47.077-47.077V141.23C682.615,115.221,661.548,94.154,635.538,94.154z M306,70.615
				c0-12.993,10.545-23.539,23.538-23.539h94.154c12.993,0,23.538,10.545,23.538,23.539v23.539c-22.809,0-141.23,0-141.23,0V70.615z
				 M588.461,659.077c0,25.986-21.066,47.076-47.076,47.076H211.846c-26.01,0-47.077-21.09-47.077-47.076V235.385h423.692V659.077z
				 M612,188.308H141.23c-12.993,0-23.538-10.545-23.538-23.539s10.545-23.539,23.538-23.539H612
				c12.993,0,23.538,10.545,23.538,23.539S624.993,188.308,612,188.308z M258.923,659.077c12.993,0,23.539-10.546,23.539-23.539
				V353.077c0-12.993-10.545-23.539-23.539-23.539s-23.539,10.545-23.539,23.539v282.461
				C235.384,648.531,245.93,659.077,258.923,659.077z M376.615,659.077c12.993,0,23.538-10.546,23.538-23.539V353.077
				c0-12.993-10.545-23.539-23.538-23.539s-23.539,10.545-23.539,23.539v282.461C353.077,648.531,363.622,659.077,376.615,659.077z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div styleName='body'>
|
<div styleName='body'>
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
@@ -312,6 +385,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
styleName='body-noteEditor'
|
styleName='body-noteEditor'
|
||||||
config={config}
|
config={config}
|
||||||
value={this.state.note.content}
|
value={this.state.note.content}
|
||||||
|
storageKey={this.state.note.storage}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,49 +3,47 @@
|
|||||||
|
|
||||||
.root
|
.root
|
||||||
absolute top right bottom
|
absolute top right bottom
|
||||||
border-width 0 0 1px
|
border-left 1px solid alpha(#DEDEDE, 60%)
|
||||||
border-style solid
|
|
||||||
border-color $ui-borderColor
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
box-shadow $note-detail-box-shadow
|
box-shadow none
|
||||||
|
padding 20px 40px
|
||||||
|
|
||||||
.lock-button
|
.lock-button
|
||||||
padding-bottom 3px
|
padding-bottom 3px
|
||||||
|
|
||||||
.control-lockButton
|
.control-lockButton
|
||||||
|
top 160px
|
||||||
|
margin-bottom 10px
|
||||||
topBarButtonLight()
|
topBarButtonLight()
|
||||||
|
|
||||||
.control-lockButton-tooltip
|
.trashed-infopanel
|
||||||
tooltip()
|
top 40px
|
||||||
position fixed
|
position relative
|
||||||
pointer-events none
|
|
||||||
top 50px
|
|
||||||
z-index 200
|
|
||||||
padding 5px
|
|
||||||
line-height normal
|
|
||||||
border-radius 2px
|
|
||||||
opacity 0
|
|
||||||
transition 0.1s
|
|
||||||
|
|
||||||
.control-trashButton
|
.control-fullScreenButton
|
||||||
float right
|
top 80px
|
||||||
topBarButtonLight()
|
topBarButtonLight()
|
||||||
|
|
||||||
.body
|
.body
|
||||||
absolute left right
|
absolute left right
|
||||||
left $note-detail-left-margin
|
left 0
|
||||||
right $note-detail-right-margin
|
right 0
|
||||||
top $info-height + $info-margin-under-border
|
top $info-height + $info-margin-under-border
|
||||||
bottom $statusBar-height
|
bottom $statusBar-height
|
||||||
|
margin 0 45px
|
||||||
.body-noteEditor
|
.body-noteEditor
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.root
|
||||||
|
box-shadow $note-detail-box-shadow
|
||||||
|
border none
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
box-shadow none
|
box-shadow none
|
||||||
|
border none
|
||||||
|
|
||||||
.control-lockButton
|
.control-lockButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
@@ -53,5 +51,5 @@ body[data-theme="dark"]
|
|||||||
.control-lockButton-tooltip
|
.control-lockButton-tooltip
|
||||||
darkTooltip()
|
darkTooltip()
|
||||||
|
|
||||||
.control-trashButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|||||||
@@ -1,33 +1,30 @@
|
|||||||
@import('DetailVars')
|
@import('DetailVars')
|
||||||
|
|
||||||
$info-height = 60px
|
$info-height = 50px
|
||||||
$info-margin-under-border = 27px
|
$info-margin-under-border = 30px
|
||||||
|
|
||||||
.info
|
.info
|
||||||
absolute top left right
|
absolute top left right
|
||||||
left $note-detail-left-margin
|
left 0
|
||||||
right $note-detail-right-margin
|
right 0
|
||||||
height $info-height
|
height $info-height
|
||||||
border-bottom $ui-border
|
border-bottom 1px solid #eee
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
width 100%
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
|
||||||
.info-left
|
.info-left
|
||||||
float left
|
padding 0 10px
|
||||||
padding 0 5px
|
width 100%
|
||||||
margin 0px 2px
|
display flex
|
||||||
|
align-items center
|
||||||
|
|
||||||
.info-left-top
|
|
||||||
display inline-block
|
|
||||||
height $info-height
|
|
||||||
line-height $info-height
|
|
||||||
|
|
||||||
.info-left-top-folderSelect
|
.info-left-top-folderSelect
|
||||||
display inline-block
|
display flex
|
||||||
padding 0 3px
|
align-items center
|
||||||
height 34px
|
justify-content center
|
||||||
line-height 34px
|
|
||||||
vertical-align middle
|
|
||||||
border-radius 3px
|
|
||||||
|
|
||||||
.info-left-button
|
.info-left-button
|
||||||
width 34px
|
width 34px
|
||||||
@@ -35,6 +32,7 @@ $info-margin-under-border = 27px
|
|||||||
navButtonColor()
|
navButtonColor()
|
||||||
color $ui-favorite-star-button-color
|
color $ui-favorite-star-button-color
|
||||||
font-size 14px
|
font-size 14px
|
||||||
|
line-height 0
|
||||||
margin 13px 2px
|
margin 13px 2px
|
||||||
padding 0
|
padding 0
|
||||||
border-radius 17px
|
border-radius 17px
|
||||||
@@ -44,15 +42,37 @@ $info-margin-under-border = 27px
|
|||||||
border-color $ui-favorite-star-button-color
|
border-color $ui-favorite-star-button-color
|
||||||
&:active, &:active:hover
|
&:active, &:active:hover
|
||||||
background-color $ui-favorite-star-button-color
|
background-color $ui-favorite-star-button-color
|
||||||
color $ui-button--active-color
|
color $ui-button--color
|
||||||
|
|
||||||
.info-right
|
.info-right
|
||||||
position absolute
|
position absolute
|
||||||
right 0
|
right 40px
|
||||||
top 0
|
top 60px
|
||||||
background $ui-noteDetail-backgroundColor
|
|
||||||
bottom 1px
|
bottom 1px
|
||||||
padding-left 30px
|
padding-left 30px
|
||||||
|
z-index 101
|
||||||
|
|
||||||
|
.undo-button
|
||||||
|
width 34px
|
||||||
|
height 34px
|
||||||
|
border-radius 17px
|
||||||
|
font-size 14px
|
||||||
|
margin 5px 0px
|
||||||
|
border none
|
||||||
|
color $ui-button-color
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
fill $ui-button-color
|
||||||
|
background-color transparent
|
||||||
|
cursor pointer
|
||||||
|
&:active
|
||||||
|
border-color $ui-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--hover-backgroundColor, 60%)
|
||||||
|
transition 0.2s
|
||||||
|
.control-lockButton-tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.info
|
.info
|
||||||
@@ -73,3 +93,6 @@ body[data-theme="dark"]
|
|||||||
|
|
||||||
.info-right
|
.info-right
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.undo-button
|
||||||
|
topBarButtonDark()
|
||||||
20
browser/main/Detail/PermanentDeleteButton.js
Normal file
20
browser/main/Detail/PermanentDeleteButton.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React 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)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './SnippetNoteDetail.styl'
|
import styles from './SnippetNoteDetail.styl'
|
||||||
import CodeEditor from 'browser/components/CodeEditor'
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
@@ -15,6 +16,14 @@ import StatusBar from '../StatusBar'
|
|||||||
import context from 'browser/lib/context'
|
import context from 'browser/lib/context'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import _ from 'lodash'
|
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'
|
||||||
|
|
||||||
function pass (name) {
|
function pass (name) {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
@@ -51,9 +60,9 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key) {
|
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
let nextNote = Object.assign({
|
const nextNote = Object.assign({
|
||||||
description: ''
|
description: ''
|
||||||
}, nextProps.note, {
|
}, nextProps.note, {
|
||||||
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
|
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
|
||||||
@@ -62,11 +71,11 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
snippetIndex: 0,
|
snippetIndex: 0,
|
||||||
note: nextNote
|
note: nextNote
|
||||||
}, () => {
|
}, () => {
|
||||||
let { snippets } = this.state.note
|
const { snippets } = this.state.note
|
||||||
snippets.forEach((snippet, index) => {
|
snippets.forEach((snippet, index) => {
|
||||||
this.refs['code-' + index].reload()
|
this.refs['code-' + index].reload()
|
||||||
})
|
})
|
||||||
this.refs.tags.reset()
|
if (this.refs.tags) this.refs.tags.reset()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,41 +84,13 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
findTitle (value) {
|
|
||||||
let splitted = value.split('\n')
|
|
||||||
let title = null
|
|
||||||
|
|
||||||
for (let i = 0; i < splitted.length; i++) {
|
|
||||||
let trimmedLine = splitted[i].trim()
|
|
||||||
if (trimmedLine.match(/^# .+/)) {
|
|
||||||
title = trimmedLine.substring(1, trimmedLine.length).trim()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (title == null) {
|
|
||||||
for (let i = 0; i < splitted.length; i++) {
|
|
||||||
let trimmedLine = splitted[i].trim()
|
|
||||||
if (trimmedLine.length > 0) {
|
|
||||||
title = trimmedLine
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (title == null) {
|
|
||||||
title = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return title
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange (e) {
|
handleChange (e) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
note.tags = this.refs.tags.value
|
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||||
note.description = this.refs.description.value
|
note.description = this.refs.description.value
|
||||||
note.updatedAt = new Date()
|
note.updatedAt = new Date()
|
||||||
note.title = this.findTitle(note.description)
|
note.title = findNoteTitle(note.description)
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
note
|
note
|
||||||
@@ -126,7 +107,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveNow () {
|
saveNow () {
|
||||||
let { note, dispatch } = this.props
|
const { note, dispatch } = this.props
|
||||||
clearTimeout(this.saveQueue)
|
clearTimeout(this.saveQueue)
|
||||||
this.saveQueue = null
|
this.saveQueue = null
|
||||||
|
|
||||||
@@ -137,15 +118,16 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
note: note
|
note: note
|
||||||
})
|
})
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderChange (e) {
|
handleFolderChange (e) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
let value = this.refs.folder.value
|
const value = this.refs.folder.value
|
||||||
let splitted = value.split('-')
|
const splitted = value.split('-')
|
||||||
let newStorageKey = splitted.shift()
|
const newStorageKey = splitted.shift()
|
||||||
let newFolderKey = splitted.shift()
|
const newFolderKey = splitted.shift()
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||||
@@ -154,7 +136,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
isMovingNote: true,
|
isMovingNote: true,
|
||||||
note: Object.assign({}, newNote)
|
note: Object.assign({}, newNote)
|
||||||
}, () => {
|
}, () => {
|
||||||
let { dispatch, location } = this.props
|
const { dispatch, location } = this.props
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
originNote: note,
|
originNote: note,
|
||||||
@@ -174,7 +156,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleStarButtonClick (e) {
|
handleStarButtonClick (e) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||||
|
|
||||||
note.isStarred = !note.isStarred
|
note.isStarred = !note.isStarred
|
||||||
|
|
||||||
@@ -189,26 +172,23 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextButtonClick (e) {
|
handleTrashButtonClick (e) {
|
||||||
context.popup([{
|
const { note } = this.state
|
||||||
label: 'Delete',
|
const { isTrashed } = note
|
||||||
click: (e) => this.handleDeleteMenuClick(e)
|
|
||||||
}])
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteMenuClick (e) {
|
if (isTrashed) {
|
||||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Delete a note',
|
message: 'Confirm note deletion',
|
||||||
detail: 'This work cannot be undone.',
|
detail: 'This will permanently remove this note.',
|
||||||
buttons: ['Confirm', 'Cancel']
|
buttons: ['Confirm', 'Cancel']
|
||||||
})
|
})
|
||||||
if (index === 0) {
|
if (dialogueButtonIndex === 1) return
|
||||||
let { note, dispatch } = this.props
|
const { note, dispatch } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.deleteNote(note.storage, note.key)
|
.deleteNote(note.storage, note.key)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
let dispatchHandler = () => {
|
const dispatchHandler = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'DELETE_NOTE',
|
type: 'DELETE_NOTE',
|
||||||
storageKey: data.storageKey,
|
storageKey: data.storageKey,
|
||||||
@@ -216,9 +196,34 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
ee.once('list:moved', dispatchHandler)
|
ee.once('list:moved', dispatchHandler)
|
||||||
ee.emit('list:next')
|
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
note.isTrashed = true
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
note
|
||||||
|
}, () => {
|
||||||
|
this.save()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
ee.emit('list:next')
|
||||||
|
}
|
||||||
|
|
||||||
|
handleUndoButtonClick (e) {
|
||||||
|
const { note } = this.state
|
||||||
|
|
||||||
|
note.isTrashed = false
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
note
|
||||||
|
}, () => {
|
||||||
|
this.save()
|
||||||
|
ee.emit('list:next')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFullScreenButton (e) {
|
||||||
|
ee.emit('editor:fullscreen')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTabPlusButtonClick (e) {
|
handleTabPlusButtonClick (e) {
|
||||||
@@ -234,7 +239,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
handleTabDeleteButtonClick (e, index) {
|
handleTabDeleteButtonClick (e, index) {
|
||||||
if (this.state.note.snippets.length > 1) {
|
if (this.state.note.snippets.length > 1) {
|
||||||
if (this.state.note.snippets[index].content.trim().length > 0) {
|
if (this.state.note.snippets[index].content.trim().length > 0) {
|
||||||
let dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Delete a snippet',
|
message: 'Delete a snippet',
|
||||||
detail: 'This work cannot be undone.',
|
detail: 'This work cannot be undone.',
|
||||||
@@ -250,27 +255,30 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteSnippetByIndex (index) {
|
deleteSnippetByIndex (index) {
|
||||||
let snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets.splice(index, 1)
|
snippets.splice(index, 1)
|
||||||
this.state.note.snippets = snippets
|
const note = Object.assign({}, this.state.note, {snippets})
|
||||||
let snippetIndex = this.state.snippetIndex >= snippets.length
|
const snippetIndex = this.state.snippetIndex >= snippets.length
|
||||||
? snippets.length - 1
|
? snippets.length - 1
|
||||||
: this.state.snippetIndex
|
: this.state.snippetIndex
|
||||||
this.setState({
|
this.setState({ note, snippetIndex }, () => {
|
||||||
note: this.state.note,
|
|
||||||
snippetIndex
|
|
||||||
}, () => {
|
|
||||||
this.save()
|
this.save()
|
||||||
|
this.refs['code-' + this.state.snippetIndex].reload()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
renameSnippetByIndex (index, name) {
|
renameSnippetByIndex (index, name) {
|
||||||
let snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].name = name
|
snippets[index].name = name
|
||||||
let syntax = CodeMirror.findModeByFileName(name.trim())
|
const syntax = CodeMirror.findModeByFileName(name.trim())
|
||||||
let mode = syntax != null ? syntax.name : null
|
const mode = syntax != null ? syntax.name : null
|
||||||
if (mode != null) snippets[index].mode = mode
|
if (mode != null) {
|
||||||
this.state.note.snippets = snippets
|
snippets[index].mode = mode
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SNIPPET_LANG', {
|
||||||
|
name: mode
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
note: this.state.note
|
note: this.state.note
|
||||||
@@ -281,23 +289,27 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
handleModeOptionClick (index, name) {
|
handleModeOptionClick (index, name) {
|
||||||
return (e) => {
|
return (e) => {
|
||||||
let snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].mode = name
|
snippets[index].mode = name
|
||||||
this.state.note.snippets = snippets
|
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
note: this.state.note
|
note: this.state.note
|
||||||
}, () => {
|
}, () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SELECT_LANG', {
|
||||||
|
name
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCodeChange (index) {
|
handleCodeChange (index) {
|
||||||
return (e) => {
|
return (e) => {
|
||||||
let snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].content = this.refs['code-' + index].value
|
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({
|
this.setState({
|
||||||
note: this.state.note
|
note: this.state.note
|
||||||
}, () => {
|
}, () => {
|
||||||
@@ -322,7 +334,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
break
|
break
|
||||||
case 76:
|
case 76:
|
||||||
{
|
{
|
||||||
let isSuper = global.process.platform === 'darwin'
|
const isSuper = global.process.platform === 'darwin'
|
||||||
? e.metaKey
|
? e.metaKey
|
||||||
: e.ctrlKey
|
: e.ctrlKey
|
||||||
if (isSuper) {
|
if (isSuper) {
|
||||||
@@ -333,7 +345,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
break
|
break
|
||||||
case 84:
|
case 84:
|
||||||
{
|
{
|
||||||
let isSuper = global.process.platform === 'darwin'
|
const isSuper = global.process.platform === 'darwin'
|
||||||
? e.metaKey
|
? e.metaKey
|
||||||
: e.ctrlKey
|
: e.ctrlKey
|
||||||
if (isSuper) {
|
if (isSuper) {
|
||||||
@@ -346,7 +358,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleModeButtonClick (e, index) {
|
handleModeButtonClick (e, index) {
|
||||||
let menu = new Menu()
|
const menu = new Menu()
|
||||||
CodeMirror.modeInfo.forEach((mode) => {
|
CodeMirror.modeInfo.forEach((mode) => {
|
||||||
menu.append(new MenuItem({
|
menu.append(new MenuItem({
|
||||||
label: mode.name,
|
label: mode.name,
|
||||||
@@ -387,8 +399,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleIndentSizeItemClick (e, indentSize) {
|
handleIndentSizeItemClick (e, indentSize) {
|
||||||
let { config, dispatch } = this.props
|
const { config, dispatch } = this.props
|
||||||
let editor = Object.assign({}, config.editor, {
|
const editor = Object.assign({}, config.editor, {
|
||||||
indentSize
|
indentSize
|
||||||
})
|
})
|
||||||
ConfigManager.set({
|
ConfigManager.set({
|
||||||
@@ -403,8 +415,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleIndentTypeItemClick (e, indentType) {
|
handleIndentTypeItemClick (e, indentType) {
|
||||||
let { config, dispatch } = this.props
|
const { config, dispatch } = this.props
|
||||||
let editor = Object.assign({}, config.editor, {
|
const editor = Object.assign({}, config.editor, {
|
||||||
indentType
|
indentType
|
||||||
})
|
})
|
||||||
ConfigManager.set({
|
ConfigManager.set({
|
||||||
@@ -423,14 +435,14 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addSnippet () {
|
addSnippet () {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
note.snippets = note.snippets.concat([{
|
note.snippets = note.snippets.concat([{
|
||||||
name: '',
|
name: '',
|
||||||
mode: 'Plain Text',
|
mode: 'Plain Text',
|
||||||
content: ''
|
content: ''
|
||||||
}])
|
}])
|
||||||
let snippetIndex = note.snippets.length - 1
|
const snippetIndex = note.snippets.length - 1
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
note,
|
note,
|
||||||
@@ -461,17 +473,34 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
this.refs['code-' + this.state.snippetIndex].focus()
|
this.refs['code-' + this.state.snippetIndex].focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleInfoButtonClick (e) {
|
||||||
|
const infoPanel = document.querySelector('.infoPanel')
|
||||||
|
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
showWarning () {
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Sorry!',
|
||||||
|
detail: 'md/text import is available only a markdown note.',
|
||||||
|
buttons: ['OK']
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { data, config } = this.props
|
const { data, config, location } = this.props
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
|
const storageKey = note.storage
|
||||||
|
const folderKey = note.folder
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
|
||||||
let tabList = note.snippets.map((snippet, index) => {
|
const tabList = note.snippets.map((snippet, index) => {
|
||||||
let isActive = this.state.snippetIndex === index
|
const isActive = this.state.snippetIndex === index
|
||||||
|
|
||||||
return <SnippetTab
|
return <SnippetTab
|
||||||
key={index}
|
key={index}
|
||||||
@@ -485,8 +514,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
|
|
||||||
let viewList = note.snippets.map((snippet, index) => {
|
const viewList = note.snippets.map((snippet, index) => {
|
||||||
let isActive = this.state.snippetIndex === index
|
const isActive = this.state.snippetIndex === index
|
||||||
|
|
||||||
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
@@ -502,6 +531,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||||
ref={'code-' + index}
|
ref={'code-' + index}
|
||||||
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
||||||
|
storageKey={storageKey}
|
||||||
/>
|
/>
|
||||||
: <CodeEditor styleName='tabView-content'
|
: <CodeEditor styleName='tabView-content'
|
||||||
mode={snippet.mode}
|
mode={snippet.mode}
|
||||||
@@ -519,49 +549,93 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const options = []
|
||||||
|
data.storageMap.forEach((storage, index) => {
|
||||||
|
storage.folders.forEach((folder) => {
|
||||||
|
options.push({
|
||||||
|
storage: storage,
|
||||||
|
folder: folder
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||||
|
|
||||||
|
const trashTopBar = <div styleName='info'>
|
||||||
|
<div styleName='info-left'>
|
||||||
|
<i styleName='undo-button'
|
||||||
|
className='fa fa-undo fa-fw'
|
||||||
|
onClick={(e) => this.handleUndoButtonClick(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div styleName='info-right'>
|
||||||
|
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||||
|
<InfoButton
|
||||||
|
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||||
|
/>
|
||||||
|
<InfoPanelTrashed
|
||||||
|
storageName={currentOption.storage.name}
|
||||||
|
folderName={currentOption.folder.name}
|
||||||
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
|
createdAt={formatDate(note.createdAt)}
|
||||||
|
exportAsMd={this.showWarning}
|
||||||
|
exportAsTxt={this.showWarning}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
const detailTopBar = <div styleName='info'>
|
||||||
|
<div styleName='info-left'>
|
||||||
|
<div styleName='info-left-top'>
|
||||||
|
<FolderSelect styleName='info-left-top-folderSelect'
|
||||||
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
|
ref='folder'
|
||||||
|
data={data}
|
||||||
|
onChange={(e) => this.handleFolderChange(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TagSelect
|
||||||
|
ref='tags'
|
||||||
|
value={this.state.note.tags}
|
||||||
|
onChange={(e) => this.handleChange(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div styleName='info-right'>
|
||||||
|
<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}
|
||||||
|
noteLink={`[${note.title}](${location.query.key})`}
|
||||||
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
|
createdAt={formatDate(note.createdAt)}
|
||||||
|
exportAsMd={this.showWarning}
|
||||||
|
exportAsTxt={this.showWarning}
|
||||||
|
type={note.type}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='NoteDetail'
|
<div className='NoteDetail'
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
styleName='root'
|
styleName='root'
|
||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
>
|
>
|
||||||
<div styleName='info'>
|
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||||
<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}
|
|
||||||
ref='folder'
|
|
||||||
data={data}
|
|
||||||
onChange={(e) => this.handleFolderChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<TagSelect
|
|
||||||
ref='tags'
|
|
||||||
value={this.state.note.tags}
|
|
||||||
onChange={(e) => this.handleChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div styleName='info-right'>
|
|
||||||
<button styleName='control-trashButton'
|
|
||||||
onClick={(e) => this.handleContextButtonClick(e)}
|
|
||||||
>
|
|
||||||
<svg height="17px" id="Capa_1" style={{"enableBackground":"new 0 0 753.23 753.23"}} width="17px" version="1.1" viewBox="0 0 753.23 753.23" x="0px" y="0px" xmlSpace="preserve">
|
|
||||||
<g>
|
|
||||||
<g id="_x34__19_">
|
|
||||||
<g>
|
|
||||||
<path d="M494.308,659.077c12.993,0,23.538-10.546,23.538-23.539V353.077c0-12.993-10.545-23.539-23.538-23.539
				s-23.538,10.545-23.538,23.539v282.461C470.77,648.531,481.314,659.077,494.308,659.077z M635.538,94.154h-141.23V47.077
				C494.308,21.067,473.24,0,447.23,0H306c-26.01,0-47.077,21.067-47.077,47.077v47.077h-141.23
				c-26.01,0-47.077,21.067-47.077,47.077v47.077c0,25.986,21.067,47.077,47.077,47.077v423.692
				c0,51.996,42.157,94.153,94.154,94.153h329.539c51.996,0,94.153-42.157,94.153-94.153V235.385
				c26.01,0,47.077-21.091,47.077-47.077V141.23C682.615,115.221,661.548,94.154,635.538,94.154z M306,70.615
				c0-12.993,10.545-23.539,23.538-23.539h94.154c12.993,0,23.538,10.545,23.538,23.539v23.539c-22.809,0-141.23,0-141.23,0V70.615z
				 M588.461,659.077c0,25.986-21.066,47.076-47.076,47.076H211.846c-26.01,0-47.077-21.09-47.077-47.076V235.385h423.692V659.077z
				 M612,188.308H141.23c-12.993,0-23.538-10.545-23.538-23.539s10.545-23.539,23.538-23.539H612
				c12.993,0,23.538,10.545,23.538,23.539S624.993,188.308,612,188.308z M258.923,659.077c12.993,0,23.539-10.546,23.539-23.539
				V353.077c0-12.993-10.545-23.539-23.539-23.539s-23.539,10.545-23.539,23.539v282.461
				C235.384,648.531,245.93,659.077,258.923,659.077z M376.615,659.077c12.993,0,23.538-10.546,23.538-23.539V353.077
				c0-12.993-10.545-23.539-23.538-23.539s-23.539,10.545-23.539,23.539v282.461C353.077,648.531,363.622,659.077,376.615,659.077z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div styleName='body'>
|
<div styleName='body'>
|
||||||
<div styleName='description'>
|
<div styleName='description'>
|
||||||
|
|||||||
@@ -3,37 +3,36 @@
|
|||||||
|
|
||||||
.root
|
.root
|
||||||
absolute top bottom right
|
absolute top bottom right
|
||||||
border-width 0 0 1px
|
border-left 1px solid alpha(#DEDEDE, 60%)
|
||||||
border-style solid
|
|
||||||
border-color $ui-borderColor
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
box-shadow $note-detail-box-shadow
|
box-shadow none
|
||||||
|
|
||||||
.body
|
.body
|
||||||
absolute left right
|
absolute left right
|
||||||
left $note-detail-left-margin
|
left $snippet-note-detail-left-margin
|
||||||
right $note-detail-right-margin
|
right $snippet-note-detail-right-margin
|
||||||
top $info-height + $info-margin-under-border
|
top $info-height + $info-margin-under-border
|
||||||
bottom $statusBar-height
|
bottom $statusBar-height
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|
||||||
.body .description
|
.body .description
|
||||||
absolute top left right
|
absolute top left right
|
||||||
height 80px
|
height 50px
|
||||||
|
|
||||||
.body .description textarea
|
.body .description textarea
|
||||||
|
outline none
|
||||||
display block
|
display block
|
||||||
height 100%
|
height 100%
|
||||||
width 100%
|
width 100%
|
||||||
resize none
|
resize none
|
||||||
border none
|
border 1px solid $ui-borderColor
|
||||||
padding 10px
|
padding 2px 5px
|
||||||
line-height 1.6
|
line-height 1.6
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|
||||||
.tabList
|
.tabList
|
||||||
absolute left right
|
absolute left right
|
||||||
top 80px
|
top 55px
|
||||||
height 30px
|
height 30px
|
||||||
display flex
|
display flex
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
@@ -44,37 +43,42 @@
|
|||||||
overflow hidden
|
overflow hidden
|
||||||
|
|
||||||
.tabList .plusButton
|
.tabList .plusButton
|
||||||
navButtonColor()
|
navWhiteButtonColor()
|
||||||
width 30px
|
width 30px
|
||||||
|
|
||||||
.tabView
|
.tabView
|
||||||
absolute left right bottom
|
absolute left right bottom
|
||||||
top 130px
|
top 100px
|
||||||
|
|
||||||
.tabView-content
|
.tabView-content
|
||||||
absolute top left right bottom
|
absolute top left right bottom
|
||||||
|
|
||||||
.override
|
.override
|
||||||
absolute bottom left
|
absolute bottom left
|
||||||
|
bottom 1px
|
||||||
left 60px
|
left 60px
|
||||||
height 23px
|
z-index 101
|
||||||
z-index 1
|
|
||||||
button
|
button
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
height 24px
|
padding 0 6px
|
||||||
border-width 0 1px 0 0
|
&:hover
|
||||||
border-style solid
|
color $ui-active-color
|
||||||
border-color $ui-borderColor
|
|
||||||
&:active .update-icon
|
&:active .update-icon
|
||||||
color white
|
color $ui-active-color
|
||||||
|
|
||||||
.control-trashButton
|
.control-fullScreenButton
|
||||||
float right
|
top 80px
|
||||||
|
margin-bottom 10px
|
||||||
topBarButtonLight()
|
topBarButtonLight()
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.root
|
||||||
|
box-shadow $note-detail-box-shadow
|
||||||
|
border none
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
border none
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
box-shadow none
|
box-shadow none
|
||||||
|
|
||||||
@@ -83,7 +87,8 @@ body[data-theme="dark"]
|
|||||||
|
|
||||||
.body .description textarea
|
.body .description textarea
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
color white
|
color $ui-dark-text-color
|
||||||
|
border 1px solid $ui-dark-borderColor
|
||||||
|
|
||||||
.tabList
|
.tabList
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color $ui-button--active-backgroundColor
|
||||||
@@ -97,6 +102,10 @@ body[data-theme="dark"]
|
|||||||
.override
|
.override
|
||||||
button
|
button
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.control-trashButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './StarButton.styl'
|
import styles from './StarButton.styl'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
@@ -31,7 +32,7 @@ class StarButton extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className } = this.props
|
const { className } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={_.isString(className)
|
<button className={_.isString(className)
|
||||||
@@ -47,10 +48,10 @@ class StarButton extends React.Component {
|
|||||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
>
|
>
|
||||||
<i styleName='icon'
|
<img styleName='icon'
|
||||||
className={this.state.isActive || this.props.isActive
|
src={this.state.isActive || this.props.isActive
|
||||||
? 'fa fa-star'
|
? '../resources/icon/icon-starred.svg'
|
||||||
: 'fa fa-star-o'
|
: '../resources/icon/icon-star.svg'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
.root
|
.root
|
||||||
left 7px
|
top 45px
|
||||||
top 0
|
topBarButtonLight()
|
||||||
padding 0
|
|
||||||
&:hover
|
&:hover
|
||||||
.icon
|
transition 0.2s
|
||||||
transform rotate(-72deg)
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
.tooltip
|
|
||||||
opacity 1
|
|
||||||
|
|
||||||
.root--active
|
.root--active
|
||||||
@extend .root
|
@extend .root
|
||||||
|
transition 0.15s
|
||||||
color $ui-favorite-star-button-color
|
color $ui-favorite-star-button-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-favorite-star-button-color
|
transition 0.2s
|
||||||
.icon
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
transform rotate(-72deg)
|
|
||||||
|
|
||||||
.icon
|
.icon
|
||||||
transition transform 0.15s
|
transition transform 0.15s
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
topBarButtonDark()
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './TagSelect.styl'
|
import styles from './TagSelect.styl'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
|
||||||
class TagSelect extends React.Component {
|
class TagSelect extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -36,6 +38,10 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleNewTagBlur (e) {
|
||||||
|
this.submitTag()
|
||||||
|
}
|
||||||
|
|
||||||
removeLastTag () {
|
removeLastTag () {
|
||||||
let { value } = this.props
|
let { value } = this.props
|
||||||
|
|
||||||
@@ -56,8 +62,9 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submitTag () {
|
submitTag () {
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
||||||
let { value } = this.props
|
let { value } = this.props
|
||||||
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
|
const newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
|
||||||
|
|
||||||
if (newTag.length <= 0) {
|
if (newTag.length <= 0) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -99,19 +106,19 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { value, className } = this.props
|
const { value, className } = this.props
|
||||||
|
|
||||||
let tagList = _.isArray(value)
|
const tagList = _.isArray(value)
|
||||||
? value.map((tag) => {
|
? value.map((tag) => {
|
||||||
return (
|
return (
|
||||||
<span styleName='tag'
|
<span styleName='tag'
|
||||||
key={tag}
|
key={tag}
|
||||||
>
|
>
|
||||||
<span styleName='tag-label'>{tag}</span>
|
<span styleName='tag-label'>#{tag}</span>
|
||||||
<button styleName='tag-removeButton'
|
<button styleName='tag-removeButton'
|
||||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)}
|
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>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -132,6 +139,7 @@ class TagSelect extends React.Component {
|
|||||||
placeholder='Add tag...'
|
placeholder='Add tag...'
|
||||||
onChange={(e) => this.handleNewTagInputChange(e)}
|
onChange={(e) => this.handleNewTagInputChange(e)}
|
||||||
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
|
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
|
||||||
|
onBlur={(e) => this.handleNewTagBlur(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
.root
|
.root
|
||||||
display inline-block
|
display flex
|
||||||
top 19px
|
align-items center
|
||||||
user-select none
|
user-select none
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
width 300px
|
width 100%
|
||||||
overflow-x scroll
|
overflow-x scroll
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
|
|
||||||
@@ -11,79 +11,46 @@
|
|||||||
display none
|
display none
|
||||||
|
|
||||||
.tag
|
.tag
|
||||||
display inline-block
|
display flex
|
||||||
margin 0 2px
|
align-items center
|
||||||
padding-left 10px
|
margin 0px 2px
|
||||||
vertical-align middle
|
padding 2px 4px
|
||||||
height 20px
|
background-color alpha($ui-tag-backgroundColor, 3%)
|
||||||
background-color $ui-tag-backgroundColor
|
border-radius 4px
|
||||||
border-radius 20px
|
position relative
|
||||||
overflow hidden
|
|
||||||
clearfix()
|
clearfix()
|
||||||
|
|
||||||
.tag-removeButton
|
.tag-removeButton
|
||||||
float right
|
|
||||||
height 20px
|
|
||||||
width 18px
|
|
||||||
margin 0
|
margin 0
|
||||||
padding 0
|
padding 0
|
||||||
border-style solid
|
border-style solid
|
||||||
border-width 0
|
border-width 0
|
||||||
border-radius 20px
|
border-radius 20px
|
||||||
line-height 18px
|
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-button-color
|
color $ui-button-color
|
||||||
|
position absolute
|
||||||
|
right 6px
|
||||||
|
|
||||||
.tag-removeButton-icon
|
.tag-removeButton-icon
|
||||||
width 5px
|
width 5px
|
||||||
padding-right 4px
|
padding-right 4px
|
||||||
|
|
||||||
.tag-label
|
.tag-label
|
||||||
font-size 12px
|
font-size 13px
|
||||||
font-weight bold
|
color: $ui-text-color
|
||||||
color: #FFFFFF
|
padding 4px 16px 4px 8px
|
||||||
float left
|
|
||||||
height 20px
|
|
||||||
line-height 20px
|
|
||||||
padding 0 6px
|
|
||||||
|
|
||||||
.newTag
|
.newTag
|
||||||
display inline-block
|
box-sizing border-box
|
||||||
margin 0 2px
|
|
||||||
vertical-align middle
|
|
||||||
height 24px
|
|
||||||
box-sizing borde-box
|
|
||||||
border none
|
border none
|
||||||
border-bottom $ui-border
|
|
||||||
background-color transparent
|
background-color transparent
|
||||||
outline none
|
outline none
|
||||||
padding 0 4px
|
padding 0 4px
|
||||||
&:focus
|
font-size 13px
|
||||||
border-color $ui-input--focus-borderColor = #369DCD
|
|
||||||
&:disabled
|
|
||||||
background-color $ui-input--disabled-backgroundColor = #DDD
|
|
||||||
|
|
||||||
.add-tag-button
|
|
||||||
display inline
|
|
||||||
margin-left 5px
|
|
||||||
width 20px
|
|
||||||
height 20px
|
|
||||||
border none
|
|
||||||
border-radius 20px
|
|
||||||
padding 0
|
|
||||||
color #FFFFFF
|
|
||||||
&:hover
|
|
||||||
background-color rgba(0, 0, 0, 0.3)
|
|
||||||
&:active, &:active:hover
|
|
||||||
background-color rgba(0, 0, 0, 0.5)
|
|
||||||
color $ui-button--active-color
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.icon
|
|
||||||
color $ui-dark-button-color
|
|
||||||
|
|
||||||
.tag
|
.tag
|
||||||
background-color $ui-dark-tag-backgroundColor
|
background-color alpha($ui-dark-tag-backgroundColor, 60%)
|
||||||
|
|
||||||
.tag-removeButton
|
.tag-removeButton
|
||||||
border-color $ui-button--focus-borderColor
|
border-color $ui-button--focus-borderColor
|
||||||
@@ -94,17 +61,6 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.newTag
|
.newTag
|
||||||
border-color $ui-dark-borderColor
|
border-color none
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:focus
|
|
||||||
border-color $ui-input--focus-borderColor = #369DCD
|
|
||||||
&:disabled
|
|
||||||
background-color $ui-input--disabled-backgroundColor = #DDD
|
|
||||||
|
|
||||||
.add-tag-button
|
|
||||||
&:hover
|
|
||||||
background-color rgba(255, 255, 255, 0.3)
|
|
||||||
&:active, &:active:hover
|
|
||||||
background-color rgba(255, 255, 255, 0.5)
|
|
||||||
color $ui-button--active-color
|
|
||||||
|
|||||||
20
browser/main/Detail/TrashButton.js
Normal file
20
browser/main/Detail/TrashButton.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './TrashButton.styl'
|
||||||
|
|
||||||
|
const TrashButton = ({
|
||||||
|
onClick
|
||||||
|
}) => (
|
||||||
|
<button styleName='control-trashButton'
|
||||||
|
onClick={(e) => onClick(e)}
|
||||||
|
>
|
||||||
|
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
TrashButton.propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(TrashButton, styles)
|
||||||
15
browser/main/Detail/TrashButton.styl
Normal file
15
browser/main/Detail/TrashButton.styl
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.control-trashButton
|
||||||
|
top 120px
|
||||||
|
margin-bottom 10px
|
||||||
|
topBarButtonLight()
|
||||||
|
|
||||||
|
.control-trashButton--in-trash
|
||||||
|
top 60px
|
||||||
|
topBarButtonLight()
|
||||||
|
|
||||||
|
.trashButton
|
||||||
|
padding 0px
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.control-trashButton
|
||||||
|
topBarButtonDark()
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './Detail.styl'
|
import styles from './Detail.styl'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
@@ -17,7 +18,7 @@ class Detail extends React.Component {
|
|||||||
this.refs.root != null && this.refs.root.focus()
|
this.refs.root != null && this.refs.root.focus()
|
||||||
}
|
}
|
||||||
this.deleteHandler = () => {
|
this.deleteHandler = () => {
|
||||||
this.refs.root != null && this.refs.root.handleDeleteMenuClick()
|
this.refs.root != null && this.refs.root.handleTrashButtonClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,12 +33,12 @@ class Detail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { location, data, config } = this.props
|
const { location, data, config } = this.props
|
||||||
let note = null
|
let note = null
|
||||||
if (location.query.key != null) {
|
if (location.query.key != null) {
|
||||||
let splitted = location.query.key.split('-')
|
const splitted = location.query.key.split('-')
|
||||||
let storageKey = splitted.shift()
|
const storageKey = splitted.shift()
|
||||||
let noteKey = splitted.shift()
|
const noteKey = splitted.shift()
|
||||||
|
|
||||||
note = data.noteMap.get(storageKey + '-' + noteKey)
|
note = data.noteMap.get(storageKey + '-' + noteKey)
|
||||||
}
|
}
|
||||||
@@ -49,7 +50,7 @@ class Detail extends React.Component {
|
|||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
>
|
>
|
||||||
<div styleName='empty'>
|
<div styleName='empty'>
|
||||||
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new post</div>
|
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new note</div>
|
||||||
</div>
|
</div>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './Main.styl'
|
import styles from './Main.styl'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
@@ -11,28 +12,35 @@ import _ from 'lodash'
|
|||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import modal from 'browser/main/lib/modal'
|
import modal from 'browser/main/lib/modal'
|
||||||
import InitModal from 'browser/main/modals/InitModal'
|
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 {
|
class Main extends React.Component {
|
||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
let { config } = props
|
if (process.env.NODE_ENV === 'production') {
|
||||||
|
mobileAnalytics.initAwsMobileAnalytics()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { config } = props
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isRightSliderFocused: false,
|
isRightSliderFocused: false,
|
||||||
listWidth: config.listWidth,
|
listWidth: config.listWidth,
|
||||||
navWidth: config.navWidth,
|
navWidth: config.navWidth,
|
||||||
isLeftSliderFocused: false
|
isLeftSliderFocused: false,
|
||||||
|
fullScreen: false,
|
||||||
|
noteDetailWidth: 0,
|
||||||
|
mainBodyWidth: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.toggleFullScreen = () => this.handleFullScreenButton()
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildContext () {
|
getChildContext () {
|
||||||
let { status, config } = this.props
|
const { status, config } = this.props
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status,
|
status,
|
||||||
@@ -41,10 +49,12 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
let { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
|
||||||
if (config.ui.theme === 'dark') {
|
if (config.ui.theme === 'dark') {
|
||||||
document.body.setAttribute('data-theme', 'dark')
|
document.body.setAttribute('data-theme', 'dark')
|
||||||
|
} else if (config.ui.theme === 'white') {
|
||||||
|
document.body.setAttribute('data-theme', 'white')
|
||||||
} else {
|
} else {
|
||||||
document.body.setAttribute('data-theme', 'default')
|
document.body.setAttribute('data-theme', 'default')
|
||||||
}
|
}
|
||||||
@@ -63,11 +73,11 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
window.addEventListener('focus', focused)
|
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
window.removeEventListener('focus', focused)
|
eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLeftSlideMouseDown (e) {
|
handleLeftSlideMouseDown (e) {
|
||||||
@@ -90,8 +100,8 @@ class Main extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
isRightSliderFocused: false
|
isRightSliderFocused: false
|
||||||
}, () => {
|
}, () => {
|
||||||
let { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
let newListWidth = this.state.listWidth
|
const newListWidth = this.state.listWidth
|
||||||
// TODO: ConfigManager should dispatch itself.
|
// TODO: ConfigManager should dispatch itself.
|
||||||
ConfigManager.set({listWidth: newListWidth})
|
ConfigManager.set({listWidth: newListWidth})
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -106,8 +116,8 @@ class Main extends React.Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
isLeftSliderFocused: false
|
isLeftSliderFocused: false
|
||||||
}, () => {
|
}, () => {
|
||||||
let { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
let navWidth = this.state.navWidth
|
const navWidth = this.state.navWidth
|
||||||
// TODO: ConfigManager should dispatch itself.
|
// TODO: ConfigManager should dispatch itself.
|
||||||
ConfigManager.set({ navWidth })
|
ConfigManager.set({ navWidth })
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -120,7 +130,7 @@ class Main extends React.Component {
|
|||||||
|
|
||||||
handleMouseMove (e) {
|
handleMouseMove (e) {
|
||||||
if (this.state.isRightSliderFocused) {
|
if (this.state.isRightSliderFocused) {
|
||||||
let offset = this.refs.body.getBoundingClientRect().left
|
const offset = this.refs.body.getBoundingClientRect().left
|
||||||
let newListWidth = e.pageX - offset
|
let newListWidth = e.pageX - offset
|
||||||
if (newListWidth < 10) {
|
if (newListWidth < 10) {
|
||||||
newListWidth = 10
|
newListWidth = 10
|
||||||
@@ -144,8 +154,39 @@ class Main extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleFullScreenButton (e) {
|
||||||
|
this.setState({ fullScreen: !this.state.fullScreen }, () => {
|
||||||
|
const noteDetail = document.querySelector('.NoteDetail')
|
||||||
|
const noteList = document.querySelector('.NoteList')
|
||||||
|
const mainBody = document.querySelector('#main-body')
|
||||||
|
|
||||||
|
if (this.state.fullScreen) {
|
||||||
|
this.hideLeftLists(noteDetail, noteList, mainBody)
|
||||||
|
} else {
|
||||||
|
this.showLeftLists(noteDetail, noteList, mainBody)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLeftLists (noteDetail, noteList, mainBody) {
|
||||||
|
this.setState({noteDetailWidth: noteDetail.style.left})
|
||||||
|
this.setState({mainBodyWidth: mainBody.style.left})
|
||||||
|
noteDetail.style.left = '0px'
|
||||||
|
mainBody.style.left = '0px'
|
||||||
|
noteList.style.display = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
showLeftLists (noteDetail, noteList, mainBody) {
|
||||||
|
noteDetail.style.left = this.state.noteDetailWidth
|
||||||
|
mainBody.style.left = this.state.mainBodyWidth
|
||||||
|
noteList.style.display = 'inline'
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { config } = this.props
|
const { config } = this.props
|
||||||
|
|
||||||
|
// the width of the navigation bar when it is folded/collapsed
|
||||||
|
const foldedNavigationWidth = 44
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -173,8 +214,9 @@ class Main extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
|
||||||
|
id='main-body'
|
||||||
ref='body'
|
ref='body'
|
||||||
style={{left: config.isSideNavFolded ? 44 : this.state.navWidth}}
|
style={{left: config.isSideNavFolded ? foldedNavigationWidth : this.state.navWidth}}
|
||||||
>
|
>
|
||||||
<TopBar style={{width: this.state.listWidth}}
|
<TopBar style={{width: this.state.listWidth}}
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
|
|||||||
@@ -13,10 +13,12 @@
|
|||||||
absolute top bottom
|
absolute top bottom
|
||||||
top -2px
|
top -2px
|
||||||
width 0
|
width 0
|
||||||
|
z-index 0
|
||||||
|
|
||||||
.slider-right
|
.slider-right
|
||||||
@extend .slider
|
@extend .slider
|
||||||
width 1px
|
width 1px
|
||||||
|
z-index 0
|
||||||
|
|
||||||
.slider--active
|
.slider--active
|
||||||
@extend .slider
|
@extend .slider
|
||||||
|
|||||||
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()
|
||||||
105
browser/main/NewNoteButton/index.js
Normal file
105
browser/main/NewNoteButton/index.js
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React 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 eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
|
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 { 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 (const 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)
|
||||||
@@ -17,54 +17,75 @@ $control-height = 30px
|
|||||||
|
|
||||||
.control-sortBy
|
.control-sortBy
|
||||||
flex 1
|
flex 1
|
||||||
padding-left 25px
|
padding-left 22px
|
||||||
|
|
||||||
.control-sortBy-select
|
.control-sortBy-select
|
||||||
margin-left 0
|
appearance: none;
|
||||||
font-size 12px
|
margin-left 5px
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
padding 0
|
padding 0
|
||||||
border none
|
border none
|
||||||
background-color transparent
|
background-color transparent
|
||||||
font-size 10px
|
outline none
|
||||||
|
cursor pointer
|
||||||
|
font-size 12px
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.control-button-area
|
||||||
|
margin-right 12px
|
||||||
|
|
||||||
.control-button
|
.control-button
|
||||||
width 25px
|
width 25px
|
||||||
padding 0
|
padding 0
|
||||||
background-color transparent
|
background-color transparent
|
||||||
border none
|
border none
|
||||||
color $ui-inactive-text-color
|
color alpha($ui-inactive-text-color, 60%)
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
&:active, &:active:hover
|
&:active, &:active:hover
|
||||||
color $ui-active-color
|
color $ui-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.control-button--active
|
.control-button--active
|
||||||
@extend .control-button
|
@extend .control-button
|
||||||
color $ui-active-color
|
color $ui-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-active-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.list
|
.list
|
||||||
absolute left right bottom
|
absolute left right bottom
|
||||||
top $control-height
|
top $control-height
|
||||||
overflow auto
|
overflow auto
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.root
|
||||||
|
background-color $ui-white-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control
|
||||||
|
background-color $ui-white-noteList-backgroundColor
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
.control
|
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
|
||||||
|
|
||||||
.control
|
.control
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
|
|
||||||
|
.control-sortBy-select
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.control-button
|
.control-button
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.control-button--active
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:active
|
||||||
|
color $ui-dark-text-color
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './NoteList.styl'
|
import styles from './NoteList.styl'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
@@ -8,6 +9,13 @@ import dataApi from 'browser/main/lib/dataApi'
|
|||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import NoteItem from 'browser/components/NoteItem'
|
import NoteItem from 'browser/components/NoteItem'
|
||||||
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
||||||
|
import searchFromNotes from 'browser/lib/search'
|
||||||
|
import fs from 'fs'
|
||||||
|
import { hashHistory } from 'react-router'
|
||||||
|
import markdown from 'browser/lib/markdown'
|
||||||
|
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||||
|
import store from 'browser/main/store'
|
||||||
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { Menu, MenuItem, dialog } = remote
|
||||||
@@ -24,6 +32,18 @@ function sortByUpdatedAt (a, b) {
|
|||||||
return new Date(b.updatedAt) - new Date(a.updatedAt)
|
return new Date(b.updatedAt) - new Date(a.updatedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findNoteByKey (notes, noteKey) {
|
||||||
|
return notes.find((note) => `${note.storage}-${note.key}` === noteKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNotesByKeys (notes, noteKeys) {
|
||||||
|
return notes.filter((note) => noteKeys.includes(getNoteKey(note)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNoteKey (note) {
|
||||||
|
return `${note.storage}-${note.key}`
|
||||||
|
}
|
||||||
|
|
||||||
class NoteList extends React.Component {
|
class NoteList extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -41,13 +61,21 @@ class NoteList extends React.Component {
|
|||||||
this.alertIfSnippetHandler = () => {
|
this.alertIfSnippetHandler = () => {
|
||||||
this.alertIfSnippet()
|
this.alertIfSnippet()
|
||||||
}
|
}
|
||||||
|
this.importFromFileHandler = this.importFromFile.bind(this)
|
||||||
|
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
|
||||||
|
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
|
||||||
|
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
|
||||||
|
this.deleteNote = this.deleteNote.bind(this)
|
||||||
|
this.focusNote = this.focusNote.bind(this)
|
||||||
|
this.pinToTop = this.pinToTop.bind(this)
|
||||||
|
|
||||||
this.jumpToTopHandler = () => {
|
// TODO: not Selected noteKeys but SelectedNote(for reusing)
|
||||||
this.jumpToTop()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
shiftKeyDown: false,
|
||||||
|
selectedNoteKeys: []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.contextNotes = []
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@@ -56,8 +84,8 @@ class NoteList extends React.Component {
|
|||||||
ee.on('list:prior', this.selectPriorNoteHandler)
|
ee.on('list:prior', this.selectPriorNoteHandler)
|
||||||
ee.on('list:focus', this.focusHandler)
|
ee.on('list:focus', this.focusHandler)
|
||||||
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
|
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
|
||||||
ee.on('list:top', this.jumpToTopHandler)
|
ee.on('import:file', this.importFromFileHandler)
|
||||||
ee.on('list:jumpToTop', this.jumpToTopHandler)
|
ee.on('list:jump', this.jumpNoteByHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
@@ -77,15 +105,16 @@ class NoteList extends React.Component {
|
|||||||
ee.off('list:prior', this.selectPriorNoteHandler)
|
ee.off('list:prior', this.selectPriorNoteHandler)
|
||||||
ee.off('list:focus', this.focusHandler)
|
ee.off('list:focus', this.focusHandler)
|
||||||
ee.off('list:isMarkdownNote', this.alertIfSnippetHandler)
|
ee.off('list:isMarkdownNote', this.alertIfSnippetHandler)
|
||||||
ee.off('list:top', this.jumpToTopHandler)
|
ee.off('import:file', this.importFromFileHandler)
|
||||||
ee.off('list:jumpToTop', this.jumpToTopHandler)
|
ee.off('list:jump', this.jumpNoteByHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
let { location } = this.props
|
const { location } = this.props
|
||||||
|
|
||||||
if (this.notes.length > 0 && location.query.key == null) {
|
if (this.notes.length > 0 && location.query.key == null) {
|
||||||
let { router } = this.context
|
const { router } = this.context
|
||||||
|
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
|
||||||
router.replace({
|
router.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
@@ -97,20 +126,18 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
// Auto scroll
|
// Auto scroll
|
||||||
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
|
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
const targetIndex = this.getTargetIndex()
|
||||||
return note != null && note.storage + '-' + note.key === location.query.key
|
|
||||||
})
|
|
||||||
if (targetIndex > -1) {
|
if (targetIndex > -1) {
|
||||||
let list = this.refs.list
|
const list = this.refs.list
|
||||||
let item = list.childNodes[targetIndex]
|
const item = list.childNodes[targetIndex]
|
||||||
|
|
||||||
if (item == null) return false
|
if (item == null) return false
|
||||||
|
|
||||||
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
const overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
||||||
if (overflowBelow) {
|
if (overflowBelow) {
|
||||||
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
|
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
|
||||||
}
|
}
|
||||||
let overflowAbove = list.scrollTop > item.offsetTop
|
const overflowAbove = list.scrollTop > item.offsetTop
|
||||||
if (overflowAbove) {
|
if (overflowAbove) {
|
||||||
list.scrollTop = item.offsetTop
|
list.scrollTop = item.offsetTop
|
||||||
}
|
}
|
||||||
@@ -118,29 +145,54 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focusNote (selectedNoteKeys, noteKey) {
|
||||||
|
const { router } = this.context
|
||||||
|
const { location } = this.props
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selectedNoteKeys
|
||||||
|
})
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
pathname: location.pathname,
|
||||||
|
query: {
|
||||||
|
key: noteKey
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getNoteKeyFromTargetIndex (targetIndex) {
|
||||||
|
const note = Object.assign({}, this.notes[targetIndex])
|
||||||
|
const noteKey = getNoteKey(note)
|
||||||
|
return noteKey
|
||||||
|
}
|
||||||
|
|
||||||
selectPriorNote () {
|
selectPriorNote () {
|
||||||
if (this.notes == null || this.notes.length === 0) {
|
if (this.notes == null || this.notes.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let { router } = this.context
|
let { router } = this.context
|
||||||
let { location } = this.props
|
let { location } = this.props
|
||||||
|
let { selectedNoteKeys, shiftKeyDown } = this.state
|
||||||
|
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
let targetIndex = this.getTargetIndex()
|
||||||
return note.storage + '-' + note.key === location.query.key
|
|
||||||
})
|
|
||||||
|
|
||||||
if (targetIndex === 0) {
|
if (targetIndex === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
targetIndex--
|
targetIndex--
|
||||||
if (targetIndex < 0) targetIndex = 0
|
|
||||||
|
|
||||||
router.push({
|
if (!shiftKeyDown) { selectedNoteKeys = [] }
|
||||||
pathname: location.pathname,
|
const priorNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
|
||||||
query: {
|
if (selectedNoteKeys.includes(priorNoteKey)) {
|
||||||
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
|
selectedNoteKeys.pop()
|
||||||
}
|
} else {
|
||||||
})
|
selectedNoteKeys.push(priorNoteKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.focusNote(selectedNoteKeys, priorNoteKey)
|
||||||
|
|
||||||
|
ee.emit('list:moved')
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNextNote () {
|
selectNextNote () {
|
||||||
@@ -149,29 +201,58 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
let { router } = this.context
|
let { router } = this.context
|
||||||
let { location } = this.props
|
let { location } = this.props
|
||||||
|
let { selectedNoteKeys, shiftKeyDown } = this.state
|
||||||
|
|
||||||
let targetIndex = _.findIndex(this.notes, (note) => {
|
let targetIndex = this.getTargetIndex()
|
||||||
return note.storage + '-' + note.key === location.query.key
|
const isTargetLastNote = targetIndex === this.notes.length - 1
|
||||||
})
|
|
||||||
|
|
||||||
if (targetIndex === this.notes.length - 1) {
|
if (isTargetLastNote && shiftKeyDown) {
|
||||||
|
return
|
||||||
|
} else if (isTargetLastNote) {
|
||||||
targetIndex = 0
|
targetIndex = 0
|
||||||
} else {
|
} else {
|
||||||
targetIndex++
|
targetIndex++
|
||||||
if (targetIndex < 0) targetIndex = 0
|
if (targetIndex < 0) targetIndex = 0
|
||||||
else if (targetIndex > this.notes.length - 1) targetIndex === this.notes.length - 1
|
else if (targetIndex > this.notes.length - 1) targetIndex = this.notes.length - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push({
|
if (!shiftKeyDown) { selectedNoteKeys = [] }
|
||||||
pathname: location.pathname,
|
const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
|
||||||
query: {
|
if (selectedNoteKeys.includes(nextNoteKey)) {
|
||||||
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
|
selectedNoteKeys.pop()
|
||||||
}
|
} else {
|
||||||
})
|
selectedNoteKeys.push(nextNoteKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.focusNote(selectedNoteKeys, nextNoteKey)
|
||||||
|
|
||||||
|
ee.emit('list:moved')
|
||||||
|
}
|
||||||
|
|
||||||
|
jumpNoteByHashHandler (event, noteHash) {
|
||||||
|
// first argument event isn't used.
|
||||||
|
if (this.notes === null || this.notes.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { router } = this.context
|
||||||
|
const { location } = this.props
|
||||||
|
|
||||||
|
let targetIndex = this.getTargetIndex()
|
||||||
|
|
||||||
|
if (targetIndex < 0) targetIndex = 0
|
||||||
|
|
||||||
|
const selectedNoteKeys = []
|
||||||
|
const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
|
||||||
|
selectedNoteKeys.push(nextNoteKey)
|
||||||
|
|
||||||
|
this.focusNote(selectedNoteKeys, nextNoteKey)
|
||||||
|
|
||||||
ee.emit('list:moved')
|
ee.emit('list:moved')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNoteListKeyDown (e) {
|
handleNoteListKeyDown (e) {
|
||||||
|
const { shiftKeyDown } = this.state
|
||||||
if (e.metaKey || e.ctrlKey) return true
|
if (e.metaKey || e.ctrlKey) return true
|
||||||
|
|
||||||
if (e.keyCode === 65 && !e.shiftKey) {
|
if (e.keyCode === 65 && !e.shiftKey) {
|
||||||
@@ -181,7 +262,7 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
if (e.keyCode === 68) {
|
if (e.keyCode === 68) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
ee.emit('detail:delete')
|
this.deleteNote()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.keyCode === 69) {
|
if (e.keyCode === 69) {
|
||||||
@@ -198,46 +279,110 @@ class NoteList extends React.Component {
|
|||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.selectNextNote()
|
this.selectNextNote()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
this.setState({ shiftKeyDown: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNoteListKeyUp (e) {
|
||||||
|
if (!e.shiftKey) {
|
||||||
|
this.setState({ shiftKeyDown: false })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getNotes () {
|
getNotes () {
|
||||||
let { data, params, location } = this.props
|
const { data, params, location } = this.props
|
||||||
|
|
||||||
if (location.pathname.match(/\/home/)) {
|
if (location.pathname.match(/\/home/) || location.pathname.match(/\alltags/)) {
|
||||||
return data.noteMap.map((note) => note)
|
const allNotes = data.noteMap.map((note) => note)
|
||||||
|
this.contextNotes = allNotes
|
||||||
|
return allNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location.pathname.match(/\/starred/)) {
|
if (location.pathname.match(/\/starred/)) {
|
||||||
return data.starredSet.toJS()
|
const starredNotes = data.starredSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
this.contextNotes = starredNotes
|
||||||
|
return starredNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
let storageKey = params.storageKey
|
if (location.pathname.match(/\/searched/)) {
|
||||||
let folderKey = params.folderKey
|
const searchInputText = document.getElementsByClassName('searchInput')[0].value
|
||||||
let storage = data.storageMap.get(storageKey)
|
if (searchInputText === '') {
|
||||||
if (storage == null) return []
|
return this.sortByPin(this.contextNotes)
|
||||||
|
}
|
||||||
let folder = _.find(storage.folders, {key: folderKey})
|
return searchFromNotes(this.contextNotes, searchInputText)
|
||||||
if (folder == null) {
|
|
||||||
let storageNoteSet = data.storageNoteMap
|
|
||||||
.get(storage.key)
|
|
||||||
if (storageNoteSet == null) storageNoteSet = []
|
|
||||||
return storageNoteSet
|
|
||||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let folderNoteKeyList = data.folderNoteMap
|
if (location.pathname.match(/\/trashed/)) {
|
||||||
.get(storage.key + '-' + folder.key)
|
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
|
||||||
|
this.contextNotes = trashedNotes
|
||||||
|
return trashedNotes
|
||||||
|
}
|
||||||
|
|
||||||
return folderNoteKeyList != null
|
if (location.pathname.match(/\/tags/)) {
|
||||||
? folderNoteKeyList
|
return data.noteMap.map(note => {
|
||||||
.map((uniqueKey) => data.noteMap.get(uniqueKey))
|
return note
|
||||||
: []
|
}).filter(note => {
|
||||||
|
return note.tags.includes(params.tagname)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getContextNotes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
handleNoteClick (e, uniqueKey) {
|
||||||
let { router } = this.context
|
let { router } = this.context
|
||||||
let { location } = this.props
|
let { location } = this.props
|
||||||
|
let { shiftKeyDown, selectedNoteKeys } = this.state
|
||||||
|
|
||||||
|
if (shiftKeyDown && selectedNoteKeys.includes(uniqueKey)) {
|
||||||
|
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
|
||||||
|
this.setState({
|
||||||
|
selectedNoteKeys: newSelectedNoteKeys
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!shiftKeyDown) {
|
||||||
|
selectedNoteKeys = []
|
||||||
|
}
|
||||||
|
selectedNoteKeys.push(uniqueKey)
|
||||||
|
this.setState({
|
||||||
|
selectedNoteKeys
|
||||||
|
})
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
@@ -247,53 +392,10 @@ class NoteList extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNoteContextMenu (e, uniqueKey) {
|
|
||||||
let menu = new Menu()
|
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: 'Delete Note',
|
|
||||||
click: (e) => this.handleDeleteNote(e, uniqueKey)
|
|
||||||
}))
|
|
||||||
menu.popup()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteNote (e, uniqueKey) {
|
|
||||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
|
||||||
type: 'warning',
|
|
||||||
message: 'Delete a note',
|
|
||||||
detail: 'This work cannot be undone.',
|
|
||||||
buttons: ['Confirm', 'Cancel']
|
|
||||||
})
|
|
||||||
if (index === 0) {
|
|
||||||
let { dispatch, location } = this.props
|
|
||||||
let splitted = uniqueKey.split('-')
|
|
||||||
let storageKey = splitted.shift()
|
|
||||||
let noteKey = splitted.shift()
|
|
||||||
|
|
||||||
dataApi
|
|
||||||
.deleteNote(storageKey, noteKey)
|
|
||||||
.then((data) => {
|
|
||||||
let dispatchHandler = () => {
|
|
||||||
dispatch({
|
|
||||||
type: 'DELETE_NOTE',
|
|
||||||
storageKey: data.storageKey,
|
|
||||||
noteKey: data.noteKey
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (location.query.key === uniqueKey) {
|
|
||||||
ee.once('list:moved', dispatchHandler)
|
|
||||||
ee.emit('list:next')
|
|
||||||
} else {
|
|
||||||
dispatchHandler()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSortByChange (e) {
|
handleSortByChange (e) {
|
||||||
let { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
|
|
||||||
let config = {
|
const config = {
|
||||||
sortBy: e.target.value
|
sortBy: e.target.value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,9 +407,9 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleListStyleButtonClick (e, style) {
|
handleListStyleButtonClick (e, style) {
|
||||||
let { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
|
|
||||||
let config = {
|
const config = {
|
||||||
listStyle: style
|
listStyle: style
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,59 +420,278 @@ class NoteList extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
alertIfSnippet() {
|
alertIfSnippet () {
|
||||||
let { location } = this.props
|
const targetIndex = this.getTargetIndex()
|
||||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
|
||||||
return `${note.storage}-${note.key}` === location.query.key
|
|
||||||
})
|
|
||||||
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
|
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Sorry!',
|
message: 'Sorry!',
|
||||||
detail: 'md/text import is available only a markdown note.'
|
detail: 'md/text import is available only a markdown note.',
|
||||||
|
buttons: ['OK', 'Cancel']
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpToTop() {
|
handleDragStart (e, note) {
|
||||||
if (this.notes === null || this.notes.length === 0) {
|
const { selectedNoteKeys } = this.state
|
||||||
return
|
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||||
|
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||||
|
const noteData = JSON.stringify(selectedNotes)
|
||||||
|
e.dataTransfer.setData('note', noteData)
|
||||||
|
this.setState({ selectedNoteKeys: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNoteContextMenu (e, uniqueKey) {
|
||||||
|
const { location } = this.props
|
||||||
|
const { selectedNoteKeys } = this.state
|
||||||
|
const note = findNoteByKey(this.notes, uniqueKey)
|
||||||
|
const noteKey = getNoteKey(note)
|
||||||
|
|
||||||
|
if (selectedNoteKeys.length === 0 || !selectedNoteKeys.includes(noteKey)) {
|
||||||
|
this.handleNoteClick(e, uniqueKey)
|
||||||
}
|
}
|
||||||
let { router } = this.context
|
|
||||||
let { location } = this.props
|
|
||||||
|
|
||||||
const targetIndex = 0
|
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
|
||||||
|
const deleteLabel = 'Delete Note'
|
||||||
|
|
||||||
router.push({
|
const menu = new Menu()
|
||||||
pathname: location.pathname,
|
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
|
||||||
query: {
|
menu.append(new MenuItem({
|
||||||
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
|
label: pinLabel,
|
||||||
|
click: this.pinToTop
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: deleteLabel,
|
||||||
|
click: this.deleteNote
|
||||||
|
}))
|
||||||
|
menu.popup()
|
||||||
|
}
|
||||||
|
|
||||||
|
pinToTop () {
|
||||||
|
const { selectedNoteKeys } = this.state
|
||||||
|
const { dispatch } = this.props
|
||||||
|
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||||
|
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||||
|
|
||||||
|
Promise.all(
|
||||||
|
selectedNotes.map((note) => {
|
||||||
|
note.isPinned = !note.isPinned
|
||||||
|
return dataApi
|
||||||
|
.updateNote(note.storage, note.key, note)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then((updatedNotes) => {
|
||||||
|
updatedNotes.forEach((note) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.setState({ selectedNoteKeys: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteNote () {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
const { selectedNoteKeys } = this.state
|
||||||
|
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||||
|
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||||
|
const firstNote = selectedNotes[0]
|
||||||
|
|
||||||
|
if (firstNote.isTrashed) {
|
||||||
|
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note'
|
||||||
|
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Confirm note deletion',
|
||||||
|
detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`,
|
||||||
|
buttons: ['Confirm', 'Cancel']
|
||||||
|
})
|
||||||
|
if (dialogueButtonIndex === 1) return
|
||||||
|
Promise.all(
|
||||||
|
selectedNoteKeys.map((uniqueKey) => {
|
||||||
|
const storageKey = uniqueKey.split('-')[0]
|
||||||
|
const noteKey = uniqueKey.split('-')[1]
|
||||||
|
return dataApi
|
||||||
|
.deleteNote(storageKey, noteKey)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then((data) => {
|
||||||
|
data.forEach((item) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'DELETE_NOTE',
|
||||||
|
storageKey: item.storageKey,
|
||||||
|
noteKey: item.noteKey
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Cannot Delete note: ' + err)
|
||||||
|
})
|
||||||
|
console.log('Notes were all deleted')
|
||||||
|
} else {
|
||||||
|
Promise.all(
|
||||||
|
selectedNotes.map((note) => {
|
||||||
|
note.isTrashed = true
|
||||||
|
|
||||||
|
return dataApi
|
||||||
|
.updateNote(note.storage, note.key, note)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then((newNotes) => {
|
||||||
|
newNotes.forEach((newNote) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: newNote
|
||||||
|
})
|
||||||
|
})
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||||
|
console.log('Notes went to trash')
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Notes could not go to trash: ' + err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.setState({ selectedNoteKeys: [] })
|
||||||
|
}
|
||||||
|
|
||||||
|
importFromFile () {
|
||||||
|
const options = {
|
||||||
|
filters: [
|
||||||
|
{ name: 'Documents', extensions: ['md', 'txt'] }
|
||||||
|
],
|
||||||
|
properties: ['openFile', 'multiSelections']
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
if (err) throw Error('File reading error: ', err)
|
||||||
|
const content = data.toString()
|
||||||
|
const newNote = {
|
||||||
|
content: content,
|
||||||
|
folder: folder.key,
|
||||||
|
title: markdown.strip(findNoteTitle(content)),
|
||||||
|
type: 'MARKDOWN_NOTE'
|
||||||
|
}
|
||||||
|
dataApi.createNote(storage.key, newNote)
|
||||||
|
.then((note) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
hashHistory.push({
|
||||||
|
pathname: location.pathname,
|
||||||
|
query: {key: getNoteKey(note)}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getTargetIndex () {
|
||||||
|
const { location } = this.props
|
||||||
|
const targetIndex = _.findIndex(this.notes, (note) => {
|
||||||
|
return getNoteKey(note) === 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 (const 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']
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { location, notes, config } = this.props
|
let { location, notes, config, dispatch } = this.props
|
||||||
|
let { selectedNoteKeys } = this.state
|
||||||
let sortFunc = config.sortBy === 'CREATED_AT'
|
let sortFunc = config.sortBy === 'CREATED_AT'
|
||||||
? sortByCreatedAt
|
? sortByCreatedAt
|
||||||
: config.sortBy === 'ALPHABETICAL'
|
: config.sortBy === 'ALPHABETICAL'
|
||||||
? sortByAlphabetical
|
? sortByAlphabetical
|
||||||
: sortByUpdatedAt
|
: sortByUpdatedAt
|
||||||
this.notes = notes = this.getNotes()
|
const sortedNotes = location.pathname.match(/\/home|\/starred|\/trash/)
|
||||||
.sort(sortFunc)
|
? this.getNotes().sort(sortFunc)
|
||||||
|
: 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
|
||||||
|
})
|
||||||
|
|
||||||
let noteList = notes
|
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'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const noteList = notes
|
||||||
.map(note => {
|
.map(note => {
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDefault = config.listStyle === 'DEFAULT'
|
const isDefault = config.listStyle === 'DEFAULT'
|
||||||
const isActive = location.query.key === note.storage + '-' + note.key
|
const uniqueKey = getNoteKey(note)
|
||||||
|
const isActive = selectedNoteKeys.includes(uniqueKey)
|
||||||
const dateDisplay = moment(
|
const dateDisplay = moment(
|
||||||
config.sortBy === 'CREATED_AT'
|
config.sortBy === 'CREATED_AT'
|
||||||
? note.createdAt : note.updatedAt
|
? note.createdAt : note.updatedAt
|
||||||
).fromNow()
|
).fromNow('D')
|
||||||
const key = `${note.storage}-${note.key}`
|
const key = `${note.storage}-${note.key}`
|
||||||
|
|
||||||
if (isDefault) {
|
if (isDefault) {
|
||||||
@@ -379,9 +700,11 @@ class NoteList extends React.Component {
|
|||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
note={note}
|
note={note}
|
||||||
dateDisplay={dateDisplay}
|
dateDisplay={dateDisplay}
|
||||||
key={key}
|
key={uniqueKey}
|
||||||
handleNoteClick={this.handleNoteClick.bind(this)}
|
|
||||||
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
|
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
|
||||||
|
handleNoteClick={this.handleNoteClick.bind(this)}
|
||||||
|
handleDragStart={this.handleDragStart.bind(this)}
|
||||||
|
pathname={location.pathname}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -390,9 +713,10 @@ class NoteList extends React.Component {
|
|||||||
<NoteItemSimple
|
<NoteItemSimple
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
note={note}
|
note={note}
|
||||||
key={key}
|
key={uniqueKey}
|
||||||
handleNoteClick={this.handleNoteClick.bind(this)}
|
|
||||||
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
|
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
|
||||||
|
handleNoteClick={this.handleNoteClick.bind(this)}
|
||||||
|
handleDragStart={this.handleDragStart.bind(this)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -401,40 +725,44 @@ class NoteList extends React.Component {
|
|||||||
<div className='NoteList'
|
<div className='NoteList'
|
||||||
styleName='root'
|
styleName='root'
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
|
onDrop={(e) => this.handleDrop(e)}
|
||||||
>
|
>
|
||||||
<div styleName='control'>
|
<div styleName='control'>
|
||||||
<div styleName='control-sortBy'>
|
<div styleName='control-sortBy'>
|
||||||
Sort by
|
<i className='fa fa-angle-down' />
|
||||||
<select styleName='control-sortBy-select'
|
<select styleName='control-sortBy-select'
|
||||||
value={config.sortBy}
|
value={config.sortBy}
|
||||||
onChange={(e) => this.handleSortByChange(e)}
|
onChange={(e) => this.handleSortByChange(e)}
|
||||||
>
|
>
|
||||||
<option value='UPDATED_AT'>Updated Time</option>
|
<option value='UPDATED_AT'>Updated</option>
|
||||||
<option value='CREATED_AT'>Created Time</option>
|
<option value='CREATED_AT'>Created</option>
|
||||||
<option value='ALPHABETICAL'>Alphabetical</option>
|
<option value='ALPHABETICAL'>Alphabetically</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button styleName={config.listStyle === 'DEFAULT'
|
<div styleName='control-button-area'>
|
||||||
? 'control-button--active'
|
<button styleName={config.listStyle === 'DEFAULT'
|
||||||
: 'control-button'
|
? 'control-button--active'
|
||||||
}
|
: 'control-button'
|
||||||
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
|
}
|
||||||
>
|
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
|
||||||
<i className='fa fa-th-large' />
|
>
|
||||||
</button>
|
<img styleName='iconTag' src='../resources/icon/icon-column.svg' />
|
||||||
<button styleName={config.listStyle === 'SMALL'
|
</button>
|
||||||
? 'control-button--active'
|
<button styleName={config.listStyle === 'SMALL'
|
||||||
: 'control-button'
|
? 'control-button--active'
|
||||||
}
|
: 'control-button'
|
||||||
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
|
}
|
||||||
>
|
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
|
||||||
<i className='fa fa-list-ul' />
|
>
|
||||||
</button>
|
<img styleName='iconTag' src='../resources/icon/icon-column-list.svg' />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='list'
|
<div styleName='list'
|
||||||
ref='list'
|
ref='list'
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
onKeyDown={(e) => this.handleNoteListKeyDown(e)}
|
onKeyDown={(e) => this.handleNoteListKeyDown(e)}
|
||||||
|
onKeyUp={this.handleNoteListKeyUp}
|
||||||
>
|
>
|
||||||
{noteList}
|
{noteList}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,57 +1,84 @@
|
|||||||
.root
|
.root
|
||||||
absolute top left bottom
|
absolute top left bottom
|
||||||
width $sideNav-width
|
width $sideNav-width
|
||||||
background-color $ui-backgroundColor
|
background-color #2E3235
|
||||||
user-select none
|
user-select none
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
|
height: 100vh
|
||||||
|
display: flex
|
||||||
|
flex-direction column
|
||||||
|
|
||||||
.top
|
.top
|
||||||
height $topBar-height
|
padding-bottom 15px
|
||||||
|
|
||||||
.top-menu
|
.top-menu-preference
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
height $topBar-height
|
position absolute
|
||||||
padding 0 15px
|
top 22px
|
||||||
font-size 14px
|
right 10px
|
||||||
width 100%
|
width 2em
|
||||||
text-align left
|
background-color transparent
|
||||||
|
&:hover
|
||||||
|
color $ui-button-default--active-backgroundColor
|
||||||
|
background-color transparent
|
||||||
|
&:active, &:active:hover
|
||||||
|
color $ui-button-default--active-backgroundColor
|
||||||
|
|
||||||
|
.switch-buttons
|
||||||
|
background-color transparent
|
||||||
|
border 0
|
||||||
|
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
|
.top-menu-label
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
|
opacity 0
|
||||||
|
|
||||||
.storageList
|
.tabBody
|
||||||
absolute left right
|
flex 1
|
||||||
bottom 37px
|
display flex
|
||||||
top 160px
|
flex-direction column
|
||||||
|
|
||||||
|
.tag-title
|
||||||
|
padding-left 15px
|
||||||
|
padding-bottom 13px
|
||||||
|
p
|
||||||
|
color $ui-button-default-color
|
||||||
|
|
||||||
|
.tagList
|
||||||
overflow-y auto
|
overflow-y auto
|
||||||
|
flex: 1
|
||||||
.storageList-empty
|
|
||||||
padding 0 10px
|
|
||||||
margin-top 15px
|
|
||||||
line-height 24px
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.navToggle
|
|
||||||
navButtonColor()
|
|
||||||
display block
|
|
||||||
position absolute
|
|
||||||
right 5px
|
|
||||||
bottom 5px
|
|
||||||
border-radius 16.5px
|
|
||||||
height 34px
|
|
||||||
width 34px
|
|
||||||
line-height 32px
|
|
||||||
padding 0
|
|
||||||
|
|
||||||
.root--folded
|
.root--folded
|
||||||
@extend .root
|
height 100vh
|
||||||
width 44px
|
width $sideNav--folded-width
|
||||||
.storageList-empty
|
background-color #2E3235
|
||||||
white-space nowrap
|
.switch-buttons
|
||||||
transform rotate(90deg)
|
display none
|
||||||
|
.top
|
||||||
|
height 60px
|
||||||
.top-menu
|
.top-menu
|
||||||
width 44px - 1
|
position static
|
||||||
|
width $sideNav--folded-width
|
||||||
|
height 60px
|
||||||
text-align center
|
text-align center
|
||||||
&:hover .top-menu-label
|
&:hover .top-menu-label
|
||||||
transition opacity 0.15s
|
transition opacity 0.15s
|
||||||
@@ -60,44 +87,54 @@
|
|||||||
position fixed
|
position fixed
|
||||||
display inline-block
|
display inline-block
|
||||||
height 30px
|
height 30px
|
||||||
left 32px
|
left $sideNav--folded-width
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
margin-top -8px
|
margin-top -8px
|
||||||
opacity 0
|
opacity 0
|
||||||
margin-left 0
|
margin-left 0
|
||||||
overflow hidden
|
overflow hidden
|
||||||
background-color $ui-tooltip-backgroundColor
|
|
||||||
z-index 10
|
z-index 10
|
||||||
color white
|
color white
|
||||||
line-height 30px
|
line-height 30px
|
||||||
border-top-right-radius 2px
|
border-top-right-radius 2px
|
||||||
border-bottom-right-radius 2px
|
border-bottom-right-radius 2px
|
||||||
pointer-events none
|
pointer-events none
|
||||||
font-size 12px
|
font-size 13px
|
||||||
.menu-button, .menu-button--active
|
.top-menu-preference
|
||||||
text-align center
|
position absolute
|
||||||
&:hover .menu-button-label
|
left 7px
|
||||||
transition opacity 0.15s
|
|
||||||
opacity 1
|
|
||||||
|
|
||||||
.menu-button-label
|
body[data-theme="white"]
|
||||||
position fixed
|
.root, .root--folded
|
||||||
display inline-block
|
background-color #f9f9f9
|
||||||
height 32px
|
color $ui-text-color
|
||||||
left 44px
|
|
||||||
padding 0 10px
|
.top-menu-preference
|
||||||
margin-top -8px
|
navWhiteButtonColor()
|
||||||
margin-left 0
|
background-color transparent
|
||||||
overflow ellipsis
|
&:hover
|
||||||
background-color $ui-tooltip-backgroundColor
|
color #0B99F1
|
||||||
z-index 10
|
background-color transparent
|
||||||
color white
|
&:active, &:active:hover
|
||||||
line-height 32px
|
color #0B99F1
|
||||||
border-top-right-radius 2px
|
background-color transparent
|
||||||
border-bottom-right-radius 2px
|
|
||||||
pointer-events none
|
.non-active-button
|
||||||
opacity 0
|
color $ui-inactive-text-color
|
||||||
font-size 12px
|
&:hover
|
||||||
|
color alpha(#0B99F1, 60%)
|
||||||
|
|
||||||
|
.tag-title
|
||||||
|
p
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.non-active-button
|
||||||
|
&:hover
|
||||||
|
color alpha(#0B99F1, 60%)
|
||||||
|
|
||||||
|
.active-button
|
||||||
|
@extend .non-active-button
|
||||||
|
color #0B99F1
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root, .root--folded
|
.root, .root--folded
|
||||||
@@ -108,12 +145,21 @@ body[data-theme="dark"]
|
|||||||
.top
|
.top
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
|
|
||||||
.top-menu
|
.top-menu-preference
|
||||||
navDarkButtonColor()
|
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
|
.non-active-button
|
||||||
color $ui-dark-inactive-text-color
|
color alpha($ui-dark-text-color, 60%)
|
||||||
|
&:hover
|
||||||
.navToggle
|
color alpha(#0B99F1, 60%)
|
||||||
navDarkButtonColor()
|
|
||||||
|
|
||||||
|
.tag-title
|
||||||
|
p
|
||||||
|
color alpha($ui-dark-text-color, 60%)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './StorageItem.styl'
|
import styles from './StorageItem.styl'
|
||||||
import { hashHistory } from 'react-router'
|
import { hashHistory } from 'react-router'
|
||||||
@@ -7,6 +8,8 @@ import CreateFolderModal from 'browser/main/modals/CreateFolderModal'
|
|||||||
import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
|
import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import StorageItemChild from 'browser/components/StorageItem'
|
import StorageItemChild from 'browser/components/StorageItem'
|
||||||
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { Menu, MenuItem, dialog } = remote
|
||||||
@@ -21,7 +24,7 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleHeaderContextMenu (e) {
|
handleHeaderContextMenu (e) {
|
||||||
let menu = new Menu()
|
const menu = new Menu()
|
||||||
menu.append(new MenuItem({
|
menu.append(new MenuItem({
|
||||||
label: 'Add Folder',
|
label: 'Add Folder',
|
||||||
click: (e) => this.handleAddFolderButtonClick(e)
|
click: (e) => this.handleAddFolderButtonClick(e)
|
||||||
@@ -37,7 +40,7 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleUnlinkStorageClick (e) {
|
handleUnlinkStorageClick (e) {
|
||||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Unlink Storage',
|
message: 'Unlink Storage',
|
||||||
detail: 'This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
|
detail: 'This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
|
||||||
@@ -45,7 +48,7 @@ class StorageItem extends React.Component {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
let { storage, dispatch } = this.props
|
const { storage, dispatch } = this.props
|
||||||
dataApi.removeStorage(storage.key)
|
dataApi.removeStorage(storage.key)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -66,7 +69,7 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleAddFolderButtonClick (e) {
|
handleAddFolderButtonClick (e) {
|
||||||
let { storage } = this.props
|
const { storage } = this.props
|
||||||
|
|
||||||
modal.open(CreateFolderModal, {
|
modal.open(CreateFolderModal, {
|
||||||
storage
|
storage
|
||||||
@@ -74,19 +77,19 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleHeaderInfoClick (e) {
|
handleHeaderInfoClick (e) {
|
||||||
let { storage } = this.props
|
const { storage } = this.props
|
||||||
hashHistory.push('/storages/' + storage.key)
|
hashHistory.push('/storages/' + storage.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderButtonClick (folderKey) {
|
handleFolderButtonClick (folderKey) {
|
||||||
return (e) => {
|
return (e) => {
|
||||||
let { storage } = this.props
|
const { storage } = this.props
|
||||||
hashHistory.push('/storages/' + storage.key + '/folders/' + folderKey)
|
hashHistory.push('/storages/' + storage.key + '/folders/' + folderKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderButtonContextMenu (e, folder) {
|
handleFolderButtonContextMenu (e, folder) {
|
||||||
let menu = new Menu()
|
const menu = new Menu()
|
||||||
menu.append(new MenuItem({
|
menu.append(new MenuItem({
|
||||||
label: 'Rename Folder',
|
label: 'Rename Folder',
|
||||||
click: (e) => this.handleRenameFolderClick(e, folder)
|
click: (e) => this.handleRenameFolderClick(e, folder)
|
||||||
@@ -102,7 +105,7 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleRenameFolderClick (e, folder) {
|
handleRenameFolderClick (e, folder) {
|
||||||
let { storage } = this.props
|
const { storage } = this.props
|
||||||
modal.open(RenameFolderModal, {
|
modal.open(RenameFolderModal, {
|
||||||
storage,
|
storage,
|
||||||
folder
|
folder
|
||||||
@@ -110,15 +113,15 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFolderDeleteClick (e, folder) {
|
handleFolderDeleteClick (e, folder) {
|
||||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Delete Folder',
|
message: 'Delete Folder',
|
||||||
detail: 'This work will deletes all notes in the folder and can not be undone.',
|
detail: 'This will delete all notes in the folder and can not be undone.',
|
||||||
buttons: ['Confirm', 'Cancel']
|
buttons: ['Confirm', 'Cancel']
|
||||||
})
|
})
|
||||||
|
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
let { storage, dispatch } = this.props
|
const { storage, dispatch } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.deleteFolder(storage.key, folder.key)
|
.deleteFolder(storage.key, folder.key)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@@ -131,16 +134,77 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
handleDragEnter (e) {
|
||||||
let { storage, location, isFolded, data } = this.props
|
e.dataTransfer.setData('defaultColor', e.target.style.backgroundColor)
|
||||||
let { folderNoteMap } = data
|
e.target.style.backgroundColor = 'rgba(129, 130, 131, 0.08)'
|
||||||
let folderList = storage.folders.map((folder) => {
|
}
|
||||||
let isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
|
|
||||||
let noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
|
||||||
|
|
||||||
let noteCount = noteSet != null
|
handleDragLeave (e) {
|
||||||
? noteSet.size
|
e.target.style.opacity = '1'
|
||||||
: 0
|
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
|
||||||
|
}
|
||||||
|
|
||||||
|
dropNote (storage, folder, dispatch, location, noteData) {
|
||||||
|
noteData = noteData.filter((note) => folder.key !== note.folder)
|
||||||
|
if (noteData.length === 0) return
|
||||||
|
const newNoteData = noteData.map((note) => Object.assign({}, note, {storage: storage, folder: folder.key}))
|
||||||
|
|
||||||
|
Promise.all(
|
||||||
|
newNoteData.map((note) => dataApi.createNote(storage.key, note))
|
||||||
|
)
|
||||||
|
.then((createdNoteData) => {
|
||||||
|
createdNoteData.forEach((note) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(`error on create notes: ${err}`)
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return Promise.all(
|
||||||
|
noteData.map((note) => dataApi.deleteNote(note.storage, note.key))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.then((deletedNoteData) => {
|
||||||
|
deletedNoteData.forEach((note) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'DELETE_NOTE',
|
||||||
|
storageKey: note.storageKey,
|
||||||
|
noteKey: note.noteKey
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(`error on delete notes: ${err}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDrop (e, storage, folder, dispatch, location) {
|
||||||
|
e.target.style.opacity = '1'
|
||||||
|
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
|
||||||
|
const noteData = JSON.parse(e.dataTransfer.getData('note'))
|
||||||
|
this.dropNote(storage, folder, dispatch, location, noteData)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { storage, location, isFolded, data, dispatch } = this.props
|
||||||
|
const { folderNoteMap, trashedSet } = data
|
||||||
|
const folderList = storage.folders.map((folder) => {
|
||||||
|
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
|
||||||
|
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||||
|
|
||||||
|
let noteCount = 0
|
||||||
|
if (noteSet) {
|
||||||
|
let trashedNoteCount = 0
|
||||||
|
const noteKeys = noteSet.map(noteKey => { return noteKey })
|
||||||
|
trashedSet.toJS().forEach(trashedKey => {
|
||||||
|
if (noteKeys.some(noteKey => { return noteKey === trashedKey })) trashedNoteCount++
|
||||||
|
})
|
||||||
|
noteCount = noteSet.size - trashedNoteCount
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<StorageItemChild
|
<StorageItemChild
|
||||||
key={folder.key}
|
key={folder.key}
|
||||||
@@ -151,11 +215,14 @@ class StorageItem extends React.Component {
|
|||||||
folderColor={folder.color}
|
folderColor={folder.color}
|
||||||
isFolded={isFolded}
|
isFolded={isFolded}
|
||||||
noteCount={noteCount}
|
noteCount={noteCount}
|
||||||
|
handleDrop={(e) => this.handleDrop(e, storage, folder, dispatch, location)}
|
||||||
|
handleDragEnter={this.handleDragEnter}
|
||||||
|
handleDragLeave={this.handleDragLeave}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
let isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
|
const isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div styleName={isFolded ? 'root--folded' : 'root'}
|
<div styleName={isFolded ? 'root--folded' : 'root'}
|
||||||
@@ -170,9 +237,9 @@ class StorageItem extends React.Component {
|
|||||||
<button styleName='header-toggleButton'
|
<button styleName='header-toggleButton'
|
||||||
onMouseDown={(e) => this.handleToggleButtonClick(e)}
|
onMouseDown={(e) => this.handleToggleButtonClick(e)}
|
||||||
>
|
>
|
||||||
<i className={this.state.isOpen
|
<img src={this.state.isOpen
|
||||||
? 'fa fa-caret-down'
|
? '../resources/icon/icon-down.svg'
|
||||||
: 'fa fa-caret-right'
|
: '../resources/icon/icon-right.svg'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@@ -181,7 +248,7 @@ class StorageItem extends React.Component {
|
|||||||
<button styleName='header-addFolderButton'
|
<button styleName='header-addFolderButton'
|
||||||
onClick={(e) => this.handleAddFolderButtonClick(e)}
|
onClick={(e) => this.handleAddFolderButtonClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-plus' />
|
<img styleName='iconTag' src='../resources/icon/icon-plus.svg' />
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +256,7 @@ class StorageItem extends React.Component {
|
|||||||
onClick={(e) => this.handleHeaderInfoClick(e)}
|
onClick={(e) => this.handleHeaderInfoClick(e)}
|
||||||
>
|
>
|
||||||
<span styleName='header-info-name'>
|
<span styleName='header-info-name'>
|
||||||
{isFolded ? storage.name.substring(0, 1) : storage.name}
|
{isFolded ? _.truncate(storage.name, {length: 1, omission: ''}) : storage.name}
|
||||||
</span>
|
</span>
|
||||||
{isFolded &&
|
{isFolded &&
|
||||||
<span styleName='header-info--folded-tooltip'>
|
<span styleName='header-info--folded-tooltip'>
|
||||||
|
|||||||
@@ -1,89 +1,86 @@
|
|||||||
.root
|
.root
|
||||||
width 100%
|
width 100%
|
||||||
user-select none
|
user-select none
|
||||||
|
padding-top 20px
|
||||||
|
|
||||||
.header
|
.header
|
||||||
position relative
|
position relative
|
||||||
height 26px
|
height 36px
|
||||||
width 100%
|
width 100%
|
||||||
margin-bottom 5px
|
margin-bottom 5px
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
&:hover
|
display flex
|
||||||
background-color $ui-button--hover-backgroundColor
|
align-items center
|
||||||
&:active
|
|
||||||
.header-toggleButton
|
|
||||||
.header-addFolderButton
|
|
||||||
color white
|
|
||||||
&:active
|
|
||||||
color $ui-active-color
|
|
||||||
|
|
||||||
.header--active
|
.header--active
|
||||||
@extend .header
|
margin-bottom 5px
|
||||||
.header-info
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
color $ui-button--active-color
|
transition color background-color 0.15s
|
||||||
background-color $ui-button--active-backgroundColor
|
display flex
|
||||||
|
align-items center
|
||||||
.header-toggleButton
|
.header-toggleButton
|
||||||
|
.header-info
|
||||||
.header-addFolderButton
|
.header-addFolderButton
|
||||||
color white
|
color #1EC38B
|
||||||
&:active
|
|
||||||
&:hover
|
|
||||||
&:hover:active
|
|
||||||
color white
|
|
||||||
|
|
||||||
.header-toggleButton
|
.header-toggleButton
|
||||||
|
navButtonColor()
|
||||||
position absolute
|
position absolute
|
||||||
left 0
|
left 0
|
||||||
width 25px
|
width 25px
|
||||||
height 26px
|
height 25px
|
||||||
padding 0
|
padding 0
|
||||||
border none
|
border none
|
||||||
color $ui-inactive-text-color
|
border-radius 50%
|
||||||
background-color transparent
|
|
||||||
&:hover
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
background-color alpha($ui-button-default--hover-backgroundColor, 40%)
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
&:active
|
|
||||||
color $ui-active-color
|
|
||||||
|
|
||||||
.header-info
|
.header-info
|
||||||
|
navButtonColor()
|
||||||
display block
|
display block
|
||||||
width 100%
|
width 100%
|
||||||
height 30px
|
height 36px
|
||||||
padding-left 25px
|
padding-left 25px
|
||||||
padding-right 10px
|
padding-right 15px
|
||||||
line-height 26px
|
line-height 22px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
font-size 14px
|
font-size 14px
|
||||||
border none
|
border none
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
text-align left
|
text-align left
|
||||||
|
font-weight 600;
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-inactive-text-color
|
&:hover
|
||||||
&:active
|
color #1EC38B
|
||||||
color $ui-button--active-color
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
background-color $ui-button--active-backgroundColor
|
transition background-color 0.15s
|
||||||
|
&:active, &:active:hover
|
||||||
|
color #1EC38B
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
|
||||||
.header-info-path
|
.header-info-path
|
||||||
font-size 10px
|
font-size 10px
|
||||||
margin 0 5px
|
margin 0 5px
|
||||||
|
|
||||||
.header-addFolderButton
|
.header-addFolderButton
|
||||||
|
navButtonColor()
|
||||||
position absolute
|
position absolute
|
||||||
right 0
|
right 7px
|
||||||
width 25px
|
width 25px
|
||||||
height 26px
|
height 25px
|
||||||
padding 0
|
padding 0
|
||||||
border none
|
border none
|
||||||
color $ui-inactive-text-color
|
border-radius 50%
|
||||||
background-color transparent
|
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-text-color
|
transition 0.2s
|
||||||
&:active
|
|
||||||
color $ui-active-color
|
|
||||||
|
|
||||||
.root--folded
|
.root--folded
|
||||||
@extend .root
|
@extend .root
|
||||||
.header
|
.header
|
||||||
width 100%
|
width 100%
|
||||||
|
padding-left 5px
|
||||||
.header-info
|
.header-info
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
padding 0 0 0 18px
|
padding 0 0 0 18px
|
||||||
@@ -93,6 +90,7 @@
|
|||||||
display none
|
display none
|
||||||
.header-toggleButton
|
.header-toggleButton
|
||||||
width 15px
|
width 15px
|
||||||
|
padding-left 9px
|
||||||
.header-info--folded-tooltip
|
.header-info--folded-tooltip
|
||||||
tooltip()
|
tooltip()
|
||||||
position fixed
|
position fixed
|
||||||
@@ -107,17 +105,78 @@
|
|||||||
font-size 10px
|
font-size 10px
|
||||||
margin 0 5px
|
margin 0 5px
|
||||||
|
|
||||||
body[data-theme="dark"]
|
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
|
.header-toggleButton
|
||||||
.header-addFolderButton
|
navWhiteButtonColor()
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
&:active
|
color $ui-text-color
|
||||||
color $ui-dark-active-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
|
||||||
|
transition color background-color 0.15s
|
||||||
|
|
||||||
.header--active
|
.header--active
|
||||||
.header-toggleButton
|
.header-toggleButton
|
||||||
.header-addFolderButton
|
color $ui-dark-text-color
|
||||||
color white
|
|
||||||
|
.header--active
|
||||||
|
.header-info
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
&:active
|
&:active
|
||||||
color white
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.header--active
|
||||||
|
.header-addFolderButton
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.header-toggleButton
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
&:active, &:active:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.header-info
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
&:active, &:active:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.header-addFolderButton
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
&:active, &:active:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
@@ -1,30 +1,44 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './SideNav.styl'
|
import styles from './SideNav.styl'
|
||||||
import { openModal } from 'browser/main/lib/modal'
|
import { openModal } from 'browser/main/lib/modal'
|
||||||
import PreferencesModal from '../modals/PreferencesModal'
|
import PreferencesModal from '../modals/PreferencesModal'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import StorageItem from './StorageItem'
|
import StorageItem from './StorageItem'
|
||||||
|
import TagListItem from 'browser/components/TagListItem'
|
||||||
import SideNavFilter from 'browser/components/SideNavFilter'
|
import SideNavFilter from 'browser/components/SideNavFilter'
|
||||||
|
import StorageList from 'browser/components/StorageList'
|
||||||
|
import NavToggleButton from 'browser/components/NavToggleButton'
|
||||||
|
import EventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
class SideNav extends React.Component {
|
class SideNav extends React.Component {
|
||||||
// TODO: should not use electron stuff v0.7
|
// TODO: should not use electron stuff v0.7
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
EventEmitter.on('side:preferences', this.handleMenuButtonClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
|
||||||
|
}
|
||||||
|
|
||||||
handleMenuButtonClick (e) {
|
handleMenuButtonClick (e) {
|
||||||
openModal(PreferencesModal)
|
openModal(PreferencesModal)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHomeButtonClick (e) {
|
handleHomeButtonClick (e) {
|
||||||
let { router } = this.context
|
const { router } = this.context
|
||||||
router.push('/home')
|
router.push('/home')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStarredButtonClick (e) {
|
handleStarredButtonClick (e) {
|
||||||
let { router } = this.context
|
const { router } = this.context
|
||||||
router.push('/starred')
|
router.push('/starred')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleToggleButtonClick (e) {
|
handleToggleButtonClick (e) {
|
||||||
let { dispatch, config } = this.props
|
const { dispatch, config } = this.props
|
||||||
|
|
||||||
ConfigManager.set({isSideNavFolded: !config.isSideNavFolded})
|
ConfigManager.set({isSideNavFolded: !config.isSideNavFolded})
|
||||||
dispatch({
|
dispatch({
|
||||||
@@ -33,14 +47,101 @@ class SideNav extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTrashedButtonClick (e) {
|
||||||
|
const { router } = this.context
|
||||||
|
router.push('/trashed')
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSwitchFoldersButtonClick () {
|
||||||
|
const { router } = this.context
|
||||||
|
router.push('/home')
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSwitchTagsButtonClick () {
|
||||||
|
const { router } = this.context
|
||||||
|
router.push('/alltags')
|
||||||
|
}
|
||||||
|
|
||||||
|
SideNavComponent (isFolded, storageList) {
|
||||||
|
const { 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
|
||||||
|
const 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 () {
|
render () {
|
||||||
let { data, location, config, dispatch } = this.props
|
const { data, location, config, dispatch } = this.props
|
||||||
|
|
||||||
let isFolded = config.isSideNavFolded
|
const isFolded = config.isSideNavFolded
|
||||||
let isHomeActive = !!location.pathname.match(/^\/home$/)
|
|
||||||
let isStarredActive = !!location.pathname.match(/^\/starred$/)
|
|
||||||
|
|
||||||
let storageList = data.storageMap.map((storage, key) => {
|
const storageList = data.storageMap.map((storage, key) => {
|
||||||
return <StorageItem
|
return <StorageItem
|
||||||
key={storage.key}
|
key={storage.key}
|
||||||
storage={storage}
|
storage={storage}
|
||||||
@@ -50,8 +151,9 @@ class SideNav extends React.Component {
|
|||||||
dispatch={dispatch}
|
dispatch={dispatch}
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
let style = {}
|
const style = {}
|
||||||
if (!isFolded) style.width = this.props.width
|
if (!isFolded) style.width = this.props.width
|
||||||
|
const isTagActive = location.pathname.match(/tag/)
|
||||||
return (
|
return (
|
||||||
<div className='SideNav'
|
<div className='SideNav'
|
||||||
styleName={isFolded ? 'root--folded' : 'root'}
|
styleName={isFolded ? 'root--folded' : 'root'}
|
||||||
@@ -59,35 +161,31 @@ class SideNav extends React.Component {
|
|||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<div styleName='top'>
|
<div styleName='top'>
|
||||||
<button styleName='top-menu'
|
<div styleName='switch-buttons'>
|
||||||
onClick={(e) => this.handleMenuButtonClick(e)}
|
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={this.handleSwitchFoldersButtonClick.bind(this)}>
|
||||||
>
|
<img src={isTagActive
|
||||||
<i className='fa fa-navicon fa-fw' />
|
? '../resources/icon/icon-list.svg'
|
||||||
<span styleName='top-menu-label'>Menu</span>
|
: '../resources/icon/icon-list-active.svg'
|
||||||
</button>
|
}
|
||||||
|
/>
|
||||||
|
</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)}
|
||||||
|
>
|
||||||
|
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{this.SideNavComponent(isFolded, storageList)}
|
||||||
<SideNavFilter
|
|
||||||
isFolded={isFolded}
|
|
||||||
isHomeActive={isHomeActive}
|
|
||||||
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
|
|
||||||
isStarredActive={isStarredActive}
|
|
||||||
handleStarredButtonClick={(e) => this.handleStarredButtonClick(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>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
@import('../Detail/DetailVars')
|
@import('../Detail/DetailVars')
|
||||||
|
|
||||||
.root
|
.root
|
||||||
absolute bottom left right
|
position absolute
|
||||||
height $statusBar-height
|
bottom 10px
|
||||||
background-color $ui-noteDetail-backgroundColor
|
right 10px
|
||||||
border-top $ui-border
|
z-index 100
|
||||||
display flex
|
display flex
|
||||||
box-shadow $note-detail-box-shadow
|
|
||||||
|
|
||||||
.blank
|
.blank
|
||||||
flex 1
|
flex 1
|
||||||
@@ -23,10 +22,18 @@
|
|||||||
|
|
||||||
.zoom
|
.zoom
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
height 24px
|
color rgba(0,0,0,.54)
|
||||||
border-width 0 1px
|
height 20px
|
||||||
border-style solid
|
display flex
|
||||||
border-color $ui-borderColor
|
padding 0
|
||||||
|
align-items center
|
||||||
|
background-color transparent
|
||||||
|
&:hover
|
||||||
|
color $ui-active-color
|
||||||
|
&:active
|
||||||
|
color $ui-active-color
|
||||||
|
span
|
||||||
|
margin-left 5px
|
||||||
|
|
||||||
.update
|
.update
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
@@ -42,12 +49,16 @@
|
|||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
box-shadow none
|
box-shadow none
|
||||||
|
|
||||||
.zoom
|
.zoom
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
|
background-color transparent
|
||||||
|
color #f9f9f9
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.help
|
.help
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './StatusBar.styl'
|
import styles from './StatusBar.styl'
|
||||||
import ZoomManager from 'browser/main/lib/ZoomManager'
|
import ZoomManager from 'browser/main/lib/ZoomManager'
|
||||||
import LastUpdatedString from '../Detail/LastUpdatedString'
|
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote, ipcRenderer } = electron
|
const { remote, ipcRenderer } = electron
|
||||||
@@ -12,7 +12,7 @@ const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2
|
|||||||
|
|
||||||
class StatusBar extends React.Component {
|
class StatusBar extends React.Component {
|
||||||
updateApp () {
|
updateApp () {
|
||||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Update Boostnote',
|
message: 'Update Boostnote',
|
||||||
detail: 'New Boostnote is ready to be installed.',
|
detail: 'New Boostnote is ready to be installed.',
|
||||||
@@ -25,7 +25,7 @@ class StatusBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleZoomButtonClick (e) {
|
handleZoomButtonClick (e) {
|
||||||
let menu = new Menu()
|
const menu = new Menu()
|
||||||
|
|
||||||
zoomOptions.forEach((zoom) => {
|
zoomOptions.forEach((zoom) => {
|
||||||
menu.append(new MenuItem({
|
menu.append(new MenuItem({
|
||||||
@@ -38,7 +38,7 @@ class StatusBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleZoomMenuItemClick (zoomFactor) {
|
handleZoomMenuItemClick (zoomFactor) {
|
||||||
let { dispatch } = this.props
|
const { dispatch } = this.props
|
||||||
ZoomManager.setZoom(zoomFactor)
|
ZoomManager.setZoom(zoomFactor)
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'SET_ZOOM',
|
type: 'SET_ZOOM',
|
||||||
@@ -47,7 +47,7 @@ class StatusBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { config, status } = this.context
|
const { config, status } = this.context
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='StatusBar'
|
<div className='StatusBar'
|
||||||
@@ -56,20 +56,16 @@ class StatusBar extends React.Component {
|
|||||||
<button styleName='zoom'
|
<button styleName='zoom'
|
||||||
onClick={(e) => this.handleZoomButtonClick(e)}
|
onClick={(e) => this.handleZoomButtonClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-search-plus' />
|
<img src='../resources/icon/icon-zoom.svg' />
|
||||||
{Math.floor(config.zoom * 100)}%
|
<span>{Math.floor(config.zoom * 100)}%</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div styleName='blank' />
|
|
||||||
|
|
||||||
{status.updateReady
|
{status.updateReady
|
||||||
? <button onClick={this.updateApp} styleName='update'>
|
? <button onClick={this.updateApp} styleName='update'>
|
||||||
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
|
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
|
||||||
</button>
|
</button>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
<LastUpdatedString date={this.props.date} />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,37 +19,37 @@ $control-height = 34px
|
|||||||
|
|
||||||
.control-search
|
.control-search
|
||||||
height 32px
|
height 32px
|
||||||
|
width 1px
|
||||||
flex 1
|
flex 1
|
||||||
background-color white
|
background-color white
|
||||||
position relative
|
position relative
|
||||||
|
|
||||||
.control-search-icon
|
|
||||||
absolute top bottom left
|
|
||||||
line-height 32px
|
|
||||||
width 35px
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
background-color $ui-noteList-backgroundColor
|
|
||||||
|
|
||||||
.control-search-input
|
.control-search-input
|
||||||
display block
|
display block
|
||||||
absolute top bottom right
|
absolute top bottom right
|
||||||
left 30px
|
width 100%
|
||||||
|
padding-left 12px
|
||||||
|
background-color $ui-noteList-backgroundColor
|
||||||
input
|
input
|
||||||
width 100%
|
width 100%
|
||||||
height 100%
|
height 100%
|
||||||
outline none
|
outline none
|
||||||
border none
|
border none
|
||||||
|
color $ui-text-color
|
||||||
|
font-size 18px
|
||||||
|
padding-bottom 2px
|
||||||
background-color $ui-noteList-backgroundColor
|
background-color $ui-noteList-backgroundColor
|
||||||
|
|
||||||
.control-search-optionList
|
.control-search-optionList
|
||||||
position fixed
|
position fixed
|
||||||
z-index 200
|
z-index 200
|
||||||
width 275px
|
width 500px
|
||||||
height 175px
|
height 250px
|
||||||
overflow-y auto
|
overflow-y auto
|
||||||
background-color $modal-background
|
background-color $modal-background
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
box-shadow 2px 2px 10px gray
|
border-none
|
||||||
|
box-shadow 0 0 1px rgba(76,86,103,.25), 0 2px 18px rgba(31,37,50,.32)
|
||||||
|
|
||||||
.control-search-optionList-item
|
.control-search-optionList-item
|
||||||
height 50px
|
height 50px
|
||||||
@@ -59,10 +59,10 @@ $control-height = 34px
|
|||||||
cursor pointer
|
cursor pointer
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-active-color, 10%)
|
background-color alpha(#D4D4D4, 30%)
|
||||||
|
|
||||||
.control-search-optionList-item-folder
|
.control-search-optionList-item-folder
|
||||||
border-left 4px solid transparent
|
border-left 2px solid transparent
|
||||||
padding 2px 5px
|
padding 2px 5px
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
@@ -91,9 +91,7 @@ $control-height = 34px
|
|||||||
width 32px
|
width 32px
|
||||||
height $control-height - 2
|
height $control-height - 2
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
border $ui-border
|
font-size 16px
|
||||||
border-radius 32px
|
|
||||||
font-size 14px
|
|
||||||
line-height 28px
|
line-height 28px
|
||||||
padding 0
|
padding 0
|
||||||
&:active
|
&:active
|
||||||
@@ -114,6 +112,21 @@ $control-height = 34px
|
|||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
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"]
|
body[data-theme="dark"]
|
||||||
.root, .root--expanded
|
.root, .root--expanded
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
@@ -131,6 +144,7 @@ body[data-theme="dark"]
|
|||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
.control-search-input
|
.control-search-input
|
||||||
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
input
|
input
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
@@ -144,7 +158,7 @@ body[data-theme="dark"]
|
|||||||
.control-search-optionList-item
|
.control-search-optionList-item
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
&:hover
|
&:hover
|
||||||
background-color lighten($ui-dark-button--hover-backgroundColor, 15%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
.control-search-optionList-item-folder
|
.control-search-optionList-item-folder
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.control-search-optionList-item-folder-surfix
|
.control-search-optionList-item-folder-surfix
|
||||||
@@ -159,10 +173,14 @@ body[data-theme="dark"]
|
|||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.control-newPostButton
|
.control-newPostButton
|
||||||
colorDarkDefaultButton()
|
color $ui-inactive-text-color
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
&:active
|
&:active
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
border-color $ui-dark-button--active-backgroundColor
|
border-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
.control-newPostButton-tooltip
|
.control-newPostButton-tooltip
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './TopBar.styl'
|
import styles from './TopBar.styl'
|
||||||
import _ from 'lodash'
|
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 ee from 'browser/main/lib/eventEmitter'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import NewNoteButton from 'browser/main/NewNoteButton'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
|
||||||
|
|
||||||
const OSX = window.process.platform === 'darwin'
|
|
||||||
|
|
||||||
class TopBar extends React.Component {
|
class TopBar extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -18,11 +13,10 @@ class TopBar extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
search: '',
|
search: '',
|
||||||
searchOptions: [],
|
searchOptions: [],
|
||||||
searchPopupOpen: false
|
isSearching: false,
|
||||||
}
|
isAlphabet: false,
|
||||||
|
isIME: false,
|
||||||
this.newNoteHandler = () => {
|
isConfirmTranslation: false
|
||||||
this.handleNewPostButtonClick()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.focusSearchHandler = () => {
|
this.focusSearchHandler = () => {
|
||||||
@@ -31,150 +25,67 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
ee.on('top:new-note', this.newNoteHandler)
|
|
||||||
ee.on('top:focus-search', this.focusSearchHandler)
|
ee.on('top:focus-search', this.focusSearchHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
ee.off('top:new-note', this.newNoteHandler)
|
|
||||||
ee.off('top:focus-search', this.focusSearchHandler)
|
ee.off('top:focus-search', this.focusSearchHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewPostButtonClick (e) {
|
handleKeyDown (e) {
|
||||||
let { config } = this.props
|
// reset states
|
||||||
|
this.setState({
|
||||||
|
isAlphabet: false,
|
||||||
|
isIME: false
|
||||||
|
})
|
||||||
|
|
||||||
switch (config.ui.defaultNote) {
|
// When the key is an alphabet, del, enter or ctr
|
||||||
case 'MARKDOWN_NOTE':
|
if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) {
|
||||||
this.createNote('MARKDOWN_NOTE')
|
this.setState({
|
||||||
break
|
isAlphabet: true
|
||||||
case 'SNIPPET_NOTE':
|
})
|
||||||
this.createNote('SNIPPET_NOTE')
|
// When the key is an IME input (Japanese, Chinese)
|
||||||
break
|
} else if (e.keyCode === 229) {
|
||||||
case 'ALWAYS_ASK':
|
this.setState({
|
||||||
default:
|
isIME: true
|
||||||
let { dispatch, location } = this.props
|
})
|
||||||
let { storage, folder } = this.resolveTargetFolder()
|
|
||||||
|
|
||||||
modal.open(NewNoteModal, {
|
|
||||||
storage: storage.key,
|
|
||||||
folder: folder.key,
|
|
||||||
dispatch,
|
|
||||||
location
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveTargetFolder () {
|
handleKeyUp (e) {
|
||||||
let { data, params } = this.props
|
const { router } = this.context
|
||||||
let storage = data.storageMap.get(params.storageKey)
|
// reset states
|
||||||
|
this.setState({
|
||||||
|
isConfirmTranslation: false
|
||||||
|
})
|
||||||
|
|
||||||
// Find first storage
|
// When the key is translation confirmation (Enter, Space)
|
||||||
if (storage == null) {
|
if (this.state.isIME && (e.keyCode === 32 || e.keyCode === 13)) {
|
||||||
for (let kv of data.storageMap) {
|
this.setState({
|
||||||
storage = kv[1]
|
isConfirmTranslation: true
|
||||||
break
|
})
|
||||||
}
|
router.push('/searched')
|
||||||
}
|
this.setState({
|
||||||
if (storage == null) window.alert('No storage to create a note')
|
search: this.refs.searchInput.value
|
||||||
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) {
|
handleSearchChange (e) {
|
||||||
|
const { router } = this.context
|
||||||
|
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
|
||||||
|
router.push('/searched')
|
||||||
|
} else {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
search: this.refs.searchInput.value
|
search: this.refs.searchInput.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getOptions () {
|
|
||||||
let { data } = this.props
|
|
||||||
let { search } = this.state
|
|
||||||
let notes = data.noteMap.map((note) => note)
|
|
||||||
if (search.trim().length === 0) return []
|
|
||||||
let searchBlocks = search.split(' ')
|
|
||||||
searchBlocks.forEach((block) => {
|
|
||||||
if (block.match(/^!#.+/)) {
|
|
||||||
let tag = block.match(/^!#(.+)/)[1]
|
|
||||||
let regExp = new RegExp(_.escapeRegExp(tag), 'i')
|
|
||||||
notes = notes
|
|
||||||
.filter((note) => {
|
|
||||||
if (!_.isArray(note.tags)) return false
|
|
||||||
return note.tags.some((_tag) => {
|
|
||||||
return _tag.match(regExp)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else if (block.match(/^!.+/)) {
|
|
||||||
let block = block.match(/^!(.+)/)[1]
|
|
||||||
let regExp = new RegExp(_.escapeRegExp(block), 'i')
|
|
||||||
notes = notes.filter((note) => {
|
|
||||||
if (!_.isArray(note.tags) || !note.tags.some((_tag) => {
|
|
||||||
return _tag.match(regExp)
|
|
||||||
})) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (note.type === 'SNIPPET_NOTE') {
|
|
||||||
return !note.description.match(regExp)
|
|
||||||
} else if (note.type === 'MARKDOWN_NOTE') {
|
|
||||||
return !note.content.match(regExp)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
} else if (block.match(/^#.+/)) {
|
|
||||||
let tag = block.match(/#(.+)/)[1]
|
|
||||||
let regExp = new RegExp(_.escapeRegExp(tag), 'i')
|
|
||||||
notes = notes
|
|
||||||
.filter((note) => {
|
|
||||||
if (!_.isArray(note.tags)) return false
|
|
||||||
return note.tags.some((_tag) => {
|
|
||||||
return _tag.match(regExp)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let regExp = new RegExp(_.escapeRegExp(block), 'i')
|
|
||||||
notes = notes.filter((note) => {
|
|
||||||
if (_.isArray(note.tags) && note.tags.some((_tag) => {
|
|
||||||
return _tag.match(regExp)
|
|
||||||
})) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (note.type === 'SNIPPET_NOTE') {
|
|
||||||
return note.description.match(regExp)
|
|
||||||
} else if (note.type === 'MARKDOWN_NOTE') {
|
|
||||||
return note.content.match(regExp)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return notes
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOptionClick (uniqueKey) {
|
|
||||||
return (e) => {
|
|
||||||
this.setState({
|
|
||||||
searchPopupOpen: false
|
|
||||||
}, () => {
|
|
||||||
let { location } = this.props
|
|
||||||
hashHistory.push({
|
|
||||||
pathname: location.pathname,
|
|
||||||
query: {
|
|
||||||
key: uniqueKey
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearchFocus (e) {
|
handleSearchFocus (e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
searchPopupOpen: true
|
isSearching: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
handleSearchBlur (e) {
|
handleSearchBlur (e) {
|
||||||
@@ -191,67 +102,13 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
if (!isStillFocused) {
|
if (!isStillFocused) {
|
||||||
this.setState({
|
this.setState({
|
||||||
searchPopupOpen: false
|
isSearching: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 () {
|
handleOnSearchFocus () {
|
||||||
if (this.state.searchPopupOpen) {
|
if (this.state.isSearching) {
|
||||||
this.refs.search.childNodes[0].blur()
|
this.refs.search.childNodes[0].blur()
|
||||||
} else {
|
} else {
|
||||||
this.refs.search.childNodes[0].focus()
|
this.refs.search.childNodes[0].focus()
|
||||||
@@ -259,28 +116,7 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { config, style, data } = this.props
|
const { config, style, location } = this.props
|
||||||
let searchOptionList = this.getOptions()
|
|
||||||
.map((note) => {
|
|
||||||
let storage = data.storageMap.get(note.storage)
|
|
||||||
let folder = _.find(storage.folders, {key: note.folder})
|
|
||||||
return <div styleName='control-search-optionList-item'
|
|
||||||
key={note.storage + '-' + note.key}
|
|
||||||
onClick={(e) => this.handleOptionClick(note.storage + '-' + note.key)(e)}
|
|
||||||
>
|
|
||||||
<div styleName='control-search-optionList-item-folder'
|
|
||||||
style={{borderColor: folder.color}}>
|
|
||||||
{folder.name}
|
|
||||||
<span styleName='control-search-optionList-item-folder-surfix'>in {storage.name}</span>
|
|
||||||
</div>
|
|
||||||
{note.type === 'SNIPPET_NOTE'
|
|
||||||
? <i styleName='control-search-optionList-item-type' className='fa fa-code' />
|
|
||||||
: <i styleName='control-search-optionList-item-type' className='fa fa-file-text-o' />
|
|
||||||
}
|
|
||||||
{note.title}
|
|
||||||
</div>
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='TopBar'
|
<div className='TopBar'
|
||||||
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
|
||||||
@@ -288,7 +124,6 @@ class TopBar extends React.Component {
|
|||||||
>
|
>
|
||||||
<div styleName='control'>
|
<div styleName='control'>
|
||||||
<div styleName='control-search'>
|
<div styleName='control-search'>
|
||||||
<i styleName='control-search-icon' className='fa fa-search fa-fw' />
|
|
||||||
<div styleName='control-search-input'
|
<div styleName='control-search-input'
|
||||||
onFocus={(e) => this.handleSearchFocus(e)}
|
onFocus={(e) => this.handleSearchFocus(e)}
|
||||||
onBlur={(e) => this.handleSearchBlur(e)}
|
onBlur={(e) => this.handleSearchBlur(e)}
|
||||||
@@ -299,17 +134,12 @@ class TopBar extends React.Component {
|
|||||||
ref='searchInput'
|
ref='searchInput'
|
||||||
value={this.state.search}
|
value={this.state.search}
|
||||||
onChange={(e) => this.handleSearchChange(e)}
|
onChange={(e) => this.handleSearchChange(e)}
|
||||||
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
|
onKeyUp={(e) => this.handleKeyUp(e)}
|
||||||
placeholder='Search'
|
placeholder='Search'
|
||||||
type='text'
|
type='text'
|
||||||
|
className='searchInput'
|
||||||
/>
|
/>
|
||||||
{this.state.searchPopupOpen &&
|
|
||||||
<div styleName='control-search-optionList'>
|
|
||||||
{searchOptionList.length > 0
|
|
||||||
? searchOptionList
|
|
||||||
: <div styleName='control-search-optionList-empty'>Empty List</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
{this.state.search > 0 &&
|
{this.state.search > 0 &&
|
||||||
<button styleName='left-search-clearButton'
|
<button styleName='left-search-clearButton'
|
||||||
@@ -320,14 +150,17 @@ class TopBar extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<button styleName='control-newPostButton'
|
|
||||||
onClick={(e) => this.handleNewPostButtonClick(e)}>
|
|
||||||
<i className='fa fa-plus' />
|
|
||||||
<span styleName='control-newPostButton-tooltip'>
|
|
||||||
Make a Note {OSX ? '⌘' : '^'} + n
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
{location.pathname === '/trashed' ? ''
|
||||||
|
: <NewNoteButton
|
||||||
|
{..._.pick(this.props, [
|
||||||
|
'dispatch',
|
||||||
|
'data',
|
||||||
|
'config',
|
||||||
|
'params',
|
||||||
|
'location'
|
||||||
|
])}
|
||||||
|
/>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
global-reset()
|
global-reset()
|
||||||
|
|
||||||
DEFAULT_FONTS = 'Lato', helvetica, arial, sans-serif
|
DEFAULT_FONTS = 'OpenSans', helvetica, arial, sans-serif
|
||||||
|
|
||||||
html, body
|
html, body
|
||||||
width 100%
|
width 100%
|
||||||
@@ -11,7 +11,8 @@ body
|
|||||||
font-family DEFAULT_FONTS
|
font-family DEFAULT_FONTS
|
||||||
color textColor
|
color textColor
|
||||||
font-size fontSize
|
font-size fontSize
|
||||||
font-weight 400
|
font-weight 200
|
||||||
|
-webkit-font-smoothing antialiased
|
||||||
|
|
||||||
button, input, select, textarea
|
button, input, select, textarea
|
||||||
font-family DEFAULT_FONTS
|
font-family DEFAULT_FONTS
|
||||||
@@ -64,7 +65,7 @@ textarea.block-input
|
|||||||
fullsize()
|
fullsize()
|
||||||
|
|
||||||
modalZIndex= 1000
|
modalZIndex= 1000
|
||||||
modalBackColor = transparentify(white, 65%)
|
modalBackColor = white
|
||||||
.ace_focus
|
.ace_focus
|
||||||
outline-color rgb(59, 153, 252)
|
outline-color rgb(59, 153, 252)
|
||||||
outline-offset 0px
|
outline-offset 0px
|
||||||
@@ -86,12 +87,12 @@ modalBackColor = transparentify(white, 65%)
|
|||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.ModalBase
|
.ModalBase
|
||||||
.modalBack
|
.modalBack
|
||||||
background-color alpha(black, 60%)
|
background-color $ui-dark-backgroundColor
|
||||||
|
|
||||||
.CodeMirror
|
.CodeMirror
|
||||||
font-family inherit !important
|
font-family inherit !important
|
||||||
line-height 1.4em
|
line-height 1.4em
|
||||||
height 100%
|
height 96%
|
||||||
.CodeMirror > div > textarea
|
.CodeMirror > div > textarea
|
||||||
margin-bottom -1em
|
margin-bottom -1em
|
||||||
.CodeMirror-focused .CodeMirror-selected
|
.CodeMirror-focused .CodeMirror-selected
|
||||||
@@ -102,3 +103,10 @@ body[data-theme="dark"]
|
|||||||
background #B1D7FE
|
background #B1D7FE
|
||||||
::selection
|
::selection
|
||||||
background #B1D7FE
|
background #B1D7FE
|
||||||
|
|
||||||
|
.sortableItemHelper
|
||||||
|
z-index modalZIndex + 5
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.sortableItemHelper
|
||||||
|
color: $ui-dark-text-color
|
||||||
|
|||||||
@@ -23,7 +23,20 @@ document.addEventListener('dragover', function (e) {
|
|||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
})
|
})
|
||||||
|
|
||||||
let el = document.getElementById('content')
|
document.addEventListener('click', function (e) {
|
||||||
|
const className = e.target.className
|
||||||
|
if (!className && typeof (className) !== 'string') return
|
||||||
|
const isInfoButton = className.includes('infoButton')
|
||||||
|
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'
|
||||||
|
})
|
||||||
|
|
||||||
|
const el = document.getElementById('content')
|
||||||
const history = syncHistoryWithStore(hashHistory, store)
|
const history = syncHistoryWithStore(hashHistory, store)
|
||||||
|
|
||||||
function notify (...args) {
|
function notify (...args) {
|
||||||
@@ -31,7 +44,7 @@ function notify (...args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function updateApp () {
|
function updateApp () {
|
||||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Update Boostnote',
|
message: 'Update Boostnote',
|
||||||
detail: 'New Boostnote is ready to be installed.',
|
detail: 'New Boostnote is ready to be installed.',
|
||||||
@@ -50,6 +63,13 @@ ReactDOM.render((
|
|||||||
<IndexRedirect to='/home' />
|
<IndexRedirect to='/home' />
|
||||||
<Route path='home' />
|
<Route path='home' />
|
||||||
<Route path='starred' />
|
<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'>
|
<Route path='storages'>
|
||||||
<IndexRedirect to='/home' />
|
<IndexRedirect to='/home' />
|
||||||
<Route path=':storageKey'>
|
<Route path=':storageKey'>
|
||||||
@@ -61,7 +81,7 @@ ReactDOM.render((
|
|||||||
</Router>
|
</Router>
|
||||||
</Provider>
|
</Provider>
|
||||||
), el, function () {
|
), el, function () {
|
||||||
let loadingCover = document.getElementById('loadingCover')
|
const loadingCover = document.getElementById('loadingCover')
|
||||||
loadingCover.parentNode.removeChild(loadingCover)
|
loadingCover.parentNode.removeChild(loadingCover)
|
||||||
|
|
||||||
ipcRenderer.on('update-ready', function () {
|
ipcRenderer.on('update-ready', function () {
|
||||||
|
|||||||
75
browser/main/lib/AwsMobileAnalyticsConfig.js
Normal file
75
browser/main/lib/AwsMobileAnalyticsConfig.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
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')
|
||||||
|
let mobileAnalyticsClient
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
mobileAnalyticsClient = new AMA.Manager({
|
||||||
|
appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordDynamicCustomEvent (type, options = {}) {
|
||||||
|
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return
|
||||||
|
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 = {
|
||||||
|
initAwsMobileAnalytics,
|
||||||
|
recordDynamicCustomEvent
|
||||||
|
}
|
||||||
@@ -13,10 +13,10 @@ function release (el) {
|
|||||||
|
|
||||||
function fire (command) {
|
function fire (command) {
|
||||||
console.info('COMMAND >>', command)
|
console.info('COMMAND >>', command)
|
||||||
let splitted = command.split(':')
|
const splitted = command.split(':')
|
||||||
let target = splitted[0]
|
const target = splitted[0]
|
||||||
let targetCommand = splitted[1]
|
const targetCommand = splitted[1]
|
||||||
let targetCallees = callees
|
const targetCallees = callees
|
||||||
.filter((callee) => callee.name === target)
|
.filter((callee) => callee.name === target)
|
||||||
|
|
||||||
targetCallees.forEach((callee) => {
|
targetCallees.forEach((callee) => {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import RcParser from 'browser/lib/RcParser'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
const win = global.process.platform === 'win32'
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { ipcRenderer } = electron
|
const { ipcRenderer } = electron
|
||||||
const consts = require('browser/lib/consts')
|
const consts = require('browser/lib/consts')
|
||||||
@@ -14,28 +16,30 @@ export const DEFAULT_CONFIG = {
|
|||||||
navWidth: 200,
|
navWidth: 200,
|
||||||
sortBy: 'UPDATED_AT', // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL'
|
sortBy: 'UPDATED_AT', // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL'
|
||||||
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
||||||
|
amaEnabled: true,
|
||||||
hotkey: {
|
hotkey: {
|
||||||
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
|
toggleFinder: OSX ? 'Cmd + Alt + S' : 'Super + Alt + S',
|
||||||
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E',
|
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
|
showCopyNotification: true,
|
||||||
disableDirectWrite: false,
|
disableDirectWrite: false,
|
||||||
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
|
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
|
||||||
},
|
},
|
||||||
editor: {
|
editor: {
|
||||||
theme: 'default',
|
theme: 'base16-light',
|
||||||
keyMap: 'sublime',
|
keyMap: 'sublime',
|
||||||
fontSize: '14',
|
fontSize: '14',
|
||||||
fontFamily: 'Monaco, Consolas',
|
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas',
|
||||||
indentType: 'space',
|
indentType: 'space',
|
||||||
indentSize: '2',
|
indentSize: '2',
|
||||||
switchPreview: 'BLUR' // Available value: RIGHTCLICK, BLUR
|
switchPreview: 'BLUR' // Available value: RIGHTCLICK, BLUR
|
||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
fontSize: '14',
|
fontSize: '14',
|
||||||
fontFamily: 'Lato',
|
fontFamily: win ? 'Segoe UI' : 'Lato',
|
||||||
codeBlockTheme: 'elegant',
|
codeBlockTheme: 'dracula',
|
||||||
lineNumber: true
|
lineNumber: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,17 +59,17 @@ function _save (config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function get () {
|
function get () {
|
||||||
let config = window.localStorage.getItem('config')
|
const rawStoredConfig = window.localStorage.getItem('config')
|
||||||
|
const storedConfig = Object.assign({}, DEFAULT_CONFIG, JSON.parse(rawStoredConfig))
|
||||||
|
let config = storedConfig
|
||||||
|
|
||||||
try {
|
try {
|
||||||
config = Object.assign({}, DEFAULT_CONFIG, JSON.parse(config))
|
const boostnotercConfig = RcParser.parse()
|
||||||
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, config.hotkey)
|
config = assignConfigValues(storedConfig, boostnotercConfig)
|
||||||
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, config.ui)
|
|
||||||
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, config.editor)
|
|
||||||
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, config.preview)
|
|
||||||
if (!validate(config)) throw new Error('INVALID CONFIG')
|
if (!validate(config)) throw new Error('INVALID CONFIG')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Boostnote resets the malformed configuration.')
|
console.warn('Boostnote resets the invalid configuration.')
|
||||||
config = DEFAULT_CONFIG
|
config = DEFAULT_CONFIG
|
||||||
_save(config)
|
_save(config)
|
||||||
}
|
}
|
||||||
@@ -85,7 +89,11 @@ function get () {
|
|||||||
: 'default'
|
: 'default'
|
||||||
|
|
||||||
if (config.editor.theme !== 'default') {
|
if (config.editor.theme !== 'default') {
|
||||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
|
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')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,13 +101,15 @@ function get () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function set (updates) {
|
function set (updates) {
|
||||||
let currentConfig = get()
|
const currentConfig = get()
|
||||||
let newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, updates)
|
const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, updates)
|
||||||
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
|
if (!validate(newConfig)) throw new Error('INVALID CONFIG')
|
||||||
_save(newConfig)
|
_save(newConfig)
|
||||||
|
|
||||||
if (newConfig.ui.theme === 'dark') {
|
if (newConfig.ui.theme === 'dark') {
|
||||||
document.body.setAttribute('data-theme', 'dark')
|
document.body.setAttribute('data-theme', 'dark')
|
||||||
|
} else if (newConfig.ui.theme === 'white') {
|
||||||
|
document.body.setAttribute('data-theme', 'white')
|
||||||
} else {
|
} else {
|
||||||
document.body.setAttribute('data-theme', 'default')
|
document.body.setAttribute('data-theme', 'default')
|
||||||
}
|
}
|
||||||
@@ -111,12 +121,16 @@ function set (updates) {
|
|||||||
editorTheme.setAttribute('rel', 'stylesheet')
|
editorTheme.setAttribute('rel', 'stylesheet')
|
||||||
document.head.appendChild(editorTheme)
|
document.head.appendChild(editorTheme)
|
||||||
}
|
}
|
||||||
let newTheme = consts.THEMES.some((theme) => theme === newConfig.editor.theme)
|
const newTheme = consts.THEMES.some((theme) => theme === newConfig.editor.theme)
|
||||||
? newConfig.editor.theme
|
? newConfig.editor.theme
|
||||||
: 'default'
|
: 'default'
|
||||||
|
|
||||||
if (newTheme !== 'default') {
|
if (newTheme !== 'default') {
|
||||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + newTheme + '.css')
|
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', {
|
ipcRenderer.send('config-renew', {
|
||||||
@@ -124,6 +138,15 @@ function set (updates) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function assignConfigValues (originalConfig, rcConfig) {
|
||||||
|
const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
|
||||||
|
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey)
|
||||||
|
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
|
||||||
|
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
|
||||||
|
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
get,
|
get,
|
||||||
set,
|
set,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function setZoom (zoomFactor, noSave = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getZoom () {
|
function getZoom () {
|
||||||
let config = ConfigManager.get()
|
const config = ConfigManager.get()
|
||||||
|
|
||||||
return config.zoom
|
return config.zoom
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user