mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
1881 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f90786d1c0 | ||
|
|
bdf55568c7 | ||
|
|
7c8e19c681 | ||
|
|
7ccc5eb9b8 | ||
|
|
b4b6d3d07c | ||
|
|
9007bac7b2 | ||
|
|
df13ca3c92 | ||
|
|
d86935acaa | ||
|
|
72b450d526 | ||
|
|
4b7afeeb4f | ||
|
|
0e0e779cbe | ||
|
|
89b2f48a06 | ||
|
|
c80bdb8d0c | ||
|
|
50d89a8ec9 | ||
|
|
f5ccaa7b48 | ||
|
|
e682ee8541 | ||
|
|
caaa7a9e74 | ||
|
|
6ef0e325e2 | ||
|
|
ea064deeb8 | ||
|
|
8e81609a39 | ||
|
|
977e80c829 | ||
|
|
8ba0d10f40 | ||
|
|
2581091652 | ||
|
|
e72a7ceaea | ||
|
|
a17ddf6d54 | ||
|
|
b5e2d21f33 | ||
|
|
d09f8dff18 | ||
|
|
bdb3406dcb | ||
|
|
f54b49db1a | ||
|
|
0cc9f006c5 | ||
|
|
db1398b65f | ||
|
|
e8192e6c3f | ||
|
|
820a2a093c | ||
|
|
d3995b9b10 | ||
|
|
f6867f9338 | ||
|
|
3e2548fcd5 | ||
|
|
745d250787 | ||
|
|
b1063eb38f | ||
|
|
9032a1debb | ||
|
|
e5a908af68 | ||
|
|
6ce16c1cc0 | ||
|
|
6ff2a5ac94 | ||
|
|
fcea16e43a | ||
|
|
7b8e42382e | ||
|
|
a372b5ea39 | ||
|
|
1aafee2a7c | ||
|
|
7afe3d5181 | ||
|
|
eda1f11d42 | ||
|
|
6431a8255d | ||
|
|
48fd1d11e2 | ||
|
|
4c3e62efad | ||
|
|
52a15a5d92 | ||
|
|
57f4aa5995 | ||
|
|
ab9ab004b7 | ||
|
|
ac624eb98f | ||
|
|
1a4c37820d | ||
|
|
685206950b | ||
|
|
eececf8a93 | ||
|
|
9bc3d65554 | ||
|
|
f9b854ce39 | ||
|
|
1416968fe5 | ||
|
|
efc183c709 | ||
|
|
07a2442718 | ||
|
|
f549c50a58 | ||
|
|
8d6ce1a2f7 | ||
|
|
c5b33e025e | ||
|
|
4983605b23 | ||
|
|
9e8d04d806 | ||
|
|
042f935133 | ||
|
|
ed9d3639e2 | ||
|
|
728f105830 | ||
|
|
6f359fa6a8 | ||
|
|
57a88743bc | ||
|
|
667f022086 | ||
|
|
b742a3a4cd | ||
|
|
8d5c9742f8 | ||
|
|
c2a49efe73 | ||
|
|
d29d5105f1 | ||
|
|
38e82872a5 | ||
|
|
15d9ce1d36 | ||
|
|
67f7cdb36c | ||
|
|
6a761c3fb5 | ||
|
|
3baf97e69f | ||
|
|
694dc60481 | ||
|
|
e3c6f0452c | ||
|
|
ed2401a87b | ||
|
|
cfdc880d8c | ||
|
|
7303e8bdd2 | ||
|
|
ecde465d9f | ||
|
|
5c5e70a805 | ||
|
|
4e41d9be55 | ||
|
|
d06d94449c | ||
|
|
1af2c83c63 | ||
|
|
6c75136777 | ||
|
|
31351e34e1 | ||
|
|
a058a774e9 | ||
|
|
e6db28485c | ||
|
|
391bb096d6 | ||
|
|
a7a5b789fa | ||
|
|
10b7d58dc6 | ||
|
|
2b496dc2e5 | ||
|
|
a6e0b30576 | ||
|
|
16f0e95e32 | ||
|
|
55395d3a2d | ||
|
|
4e0fa63fad | ||
|
|
b9ea7696d8 | ||
|
|
7de3308f52 | ||
|
|
b2b2373c7b | ||
|
|
1c54f40a28 | ||
|
|
1ac31264b7 | ||
|
|
ca4b8224fd | ||
|
|
4735992835 | ||
|
|
cba3519458 | ||
|
|
c64a5e1cca | ||
|
|
47ee8b8ce7 | ||
|
|
455b424429 | ||
|
|
7d67ac3f12 | ||
|
|
34f377eb5c | ||
|
|
b7f4af8c78 | ||
|
|
07b395c24a | ||
|
|
a6c7dde194 | ||
|
|
43ebe4ecfd | ||
|
|
53b9630fa5 | ||
|
|
1d38f1abb4 | ||
|
|
061a0cd219 | ||
|
|
f0ed20ee2c | ||
|
|
edfc8d95c8 | ||
|
|
f820c3089e | ||
|
|
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 | ||
|
|
8315b75587 | ||
|
|
562b0592af | ||
|
|
1fd1bed01a | ||
|
|
4f116cba34 | ||
|
|
9d1d57f183 | ||
|
|
6feeee8933 | ||
|
|
5541c0dc38 | ||
|
|
4a8054faed | ||
|
|
bbced7be25 | ||
|
|
893a92c87b | ||
|
|
4055ce19cd | ||
|
|
bcf27233bc | ||
|
|
45111e1610 | ||
|
|
2af86dfa3e | ||
|
|
1b474e1c28 | ||
|
|
c4370694cc | ||
|
|
91f24d96b9 | ||
|
|
bb0b74e889 | ||
|
|
525ab900bd | ||
|
|
31c04de7b6 | ||
|
|
dea0c4287b | ||
|
|
cec4b3132c | ||
|
|
f3ed22dd51 | ||
|
|
6aa9104076 | ||
|
|
e7fd18967b | ||
|
|
8a5558db55 | ||
|
|
4767f15e9b | ||
|
|
b7ca4668e9 | ||
|
|
70e637fada | ||
|
|
459b0ff030 | ||
|
|
2903788fd4 | ||
|
|
af0fdb9277 | ||
|
|
41a58583dc | ||
|
|
c80a26fe0b | ||
|
|
806c3bbaf9 | ||
|
|
fe1c197138 | ||
|
|
b577ca2bc2 | ||
|
|
70a97a6a2a | ||
|
|
034f46792b | ||
|
|
3dc1b59753 | ||
|
|
98b761f1d1 | ||
|
|
712301436d | ||
|
|
4243afb033 | ||
|
|
0f43485606 | ||
|
|
b91b88f16e | ||
|
|
2f2c500e4a | ||
|
|
7065fad69b | ||
|
|
6fcbca6b10 | ||
|
|
93b15f2a7a | ||
|
|
df9d8ff735 | ||
|
|
fda17e044e | ||
|
|
b4e54fc149 | ||
|
|
48514d1020 | ||
|
|
49a4ec5e16 | ||
|
|
c3e92b3b81 | ||
|
|
e78492983a | ||
|
|
0f3230110c | ||
|
|
65573bd4db | ||
|
|
928df018dc | ||
|
|
b2187b72ab | ||
|
|
aae2bddd32 | ||
|
|
b6eddf0821 | ||
|
|
fac0abaed6 | ||
|
|
a6bd239592 | ||
|
|
7845bbd881 | ||
|
|
68bc440749 | ||
|
|
6dbe3cec69 | ||
|
|
850c339bb3 | ||
|
|
57835d0e32 | ||
|
|
ac2c50c8bc | ||
|
|
7296cbe4ec | ||
|
|
16061a7eba | ||
|
|
566fe92589 | ||
|
|
83cef13f1c | ||
|
|
4bb9533049 | ||
|
|
d07c62e266 | ||
|
|
8beb661af4 | ||
|
|
5a201dd1b9 | ||
|
|
aa0ad3bb70 | ||
|
|
f7fb531902 | ||
|
|
c65db4e2b0 | ||
|
|
b32b38bb0d | ||
|
|
d6171dc502 | ||
|
|
7b5a7aabed | ||
|
|
77eb19af40 | ||
|
|
e68d535fa2 | ||
|
|
6e5f6cc739 | ||
|
|
bbeeeccb31 | ||
|
|
e9525fae22 | ||
|
|
672d409bf2 | ||
|
|
6624178864 | ||
|
|
4a66c6717c | ||
|
|
dd76bc027b | ||
|
|
74ee6ae6ce | ||
|
|
1ae3f295f3 | ||
|
|
3cb2ce41fe | ||
|
|
5534319e93 | ||
|
|
c7373c15a5 | ||
|
|
dbf1d6403b | ||
|
|
743b220953 | ||
|
|
95d74c6f5b | ||
|
|
f04b7db9fc | ||
|
|
900fa023fb | ||
|
|
ad9da44afb | ||
|
|
c827717202 | ||
|
|
7d3caa3c2e | ||
|
|
fde7fbccac | ||
|
|
56f06fa7d5 | ||
|
|
c0fba82e73 | ||
|
|
b6304a04e6 | ||
|
|
5438cd14a0 | ||
|
|
0d642b308d | ||
|
|
0b96472f72 | ||
|
|
675d0ed08c | ||
|
|
9c0f5c31c2 | ||
|
|
09ce59fd04 | ||
|
|
98cd83c4e0 | ||
|
|
1aec386656 | ||
|
|
b03cd9cd99 | ||
|
|
27265e210f | ||
|
|
c392c5d178 | ||
|
|
11fe420fac | ||
|
|
9b17a8fb5b | ||
|
|
27f3fd0032 | ||
|
|
1672d9fa5f | ||
|
|
59c9e11879 | ||
|
|
4523743150 | ||
|
|
2fdbe9de96 | ||
|
|
a617976c78 | ||
|
|
7201a98d78 | ||
|
|
472560e2bf | ||
|
|
96753fe0a0 | ||
|
|
83c2fdd161 | ||
|
|
911ce7572f | ||
|
|
0a24d7d4a7 | ||
|
|
c542062d4d | ||
|
|
eb7a195cce | ||
|
|
23a356164e | ||
|
|
de19c51061 | ||
|
|
221b6a2938 | ||
|
|
cda9d53c8e | ||
|
|
f043b0ffb3 | ||
|
|
28e0590327 | ||
|
|
ec6de1b91b | ||
|
|
2b0bdbf1c8 | ||
|
|
f48864a2e7 | ||
|
|
94c6578675 | ||
|
|
2af2399971 | ||
|
|
ebea01cecf | ||
|
|
5d1db1de31 | ||
|
|
6c528625d8 | ||
|
|
7b326b99af | ||
|
|
2a60ba95e0 | ||
|
|
6b98afaa02 | ||
|
|
cdb079dc81 | ||
|
|
2ac0d93caf | ||
|
|
41977e8726 | ||
|
|
b9e6a56a83 | ||
|
|
2468c8311f | ||
|
|
e8e05b20cd | ||
|
|
5bd0a446f1 | ||
|
|
ad4e50d542 | ||
|
|
13131a0d5c | ||
|
|
f007664745 | ||
|
|
87f9589be3 | ||
|
|
e059106a93 | ||
|
|
ada1b4de6b | ||
|
|
96413b9851 | ||
|
|
9699ef6319 | ||
|
|
dd8f4d60f0 | ||
|
|
372f2e7319 | ||
|
|
1957d87dd7 | ||
|
|
a148d17ba1 | ||
|
|
297553c240 | ||
|
|
f0fcaa6be7 | ||
|
|
cfa40f3ec1 | ||
|
|
832c43de88 | ||
|
|
1665e18edb | ||
|
|
fd1717046b | ||
|
|
7fe7c555bc | ||
|
|
411a7a8e80 | ||
|
|
196f5a7bf7 | ||
|
|
3fa326121a | ||
|
|
cce69bea3a | ||
|
|
f70cf7845d | ||
|
|
bd0a326128 | ||
|
|
897d99e043 | ||
|
|
b0f288e103 | ||
|
|
7d26d46c7b | ||
|
|
5c7804fc40 | ||
|
|
836f3af1ab | ||
|
|
67b89d4fe7 | ||
|
|
bc2d9d0fe2 | ||
|
|
79f33b9405 | ||
|
|
ed9ddee5f1 | ||
|
|
0d004b2f0a | ||
|
|
f41ff77d76 | ||
|
|
ae97a76d2e | ||
|
|
3ca18c04c6 | ||
|
|
2b03e6e956 | ||
|
|
010793a478 | ||
|
|
b136512ece | ||
|
|
9179c199fe | ||
|
|
cfa4dfa817 | ||
|
|
6a73a3af97 | ||
|
|
923c24fa6c | ||
|
|
4b1c8a3238 | ||
|
|
76508fbc3b | ||
|
|
2bfda95ed8 | ||
|
|
6e5082a470 | ||
|
|
a6cec44fc4 | ||
|
|
600fab4f23 | ||
|
|
12377b8caf | ||
|
|
250c6e488d | ||
|
|
3a9b57adae | ||
|
|
74415956ac | ||
|
|
33d7ed25a5 | ||
|
|
df2de5c081 | ||
|
|
7f4c58a84a | ||
|
|
a641a7b3e4 | ||
|
|
7437b26e3c | ||
|
|
ee6d41859f | ||
|
|
b368c3b5d8 | ||
|
|
b1ae2b0b6f | ||
|
|
27a6d53c7f | ||
|
|
4caf3a81be | ||
|
|
ab578f768f | ||
|
|
e4212e796a | ||
|
|
95b1ff9b41 | ||
|
|
684d2c411e | ||
|
|
42710cfee5 | ||
|
|
277004fd9b | ||
|
|
14f79c4c21 | ||
|
|
f88cd80dca | ||
|
|
19ada1dbf6 | ||
|
|
7754ab1a2e | ||
|
|
de0a8837eb | ||
|
|
042d059d53 | ||
|
|
cba743c895 | ||
|
|
24ee71ac06 | ||
|
|
0ec4ef3363 | ||
|
|
98120a5e40 | ||
|
|
3cb2a6bf92 | ||
|
|
4b7262cb72 | ||
|
|
eac8b13d7b | ||
|
|
4458c58066 | ||
|
|
245d603ae8 | ||
|
|
e3d959522b | ||
|
|
124544452b | ||
|
|
4534625084 | ||
|
|
3e4342eec4 | ||
|
|
0ed2d26129 | ||
|
|
1b538993db | ||
|
|
d67e4009e7 | ||
|
|
7f066c4443 | ||
|
|
2594ca984a | ||
|
|
0e089fadfb | ||
|
|
e56518e13d | ||
|
|
c9bc0c89ff | ||
|
|
b18b1171e7 | ||
|
|
d65464401c | ||
|
|
e9a9e10c81 | ||
|
|
825cd6a93b | ||
|
|
276471979a | ||
|
|
d6903edac7 | ||
|
|
4e1b4bdd6a | ||
|
|
52f0a5639d | ||
|
|
58181d02b2 | ||
|
|
1fea39f1b7 | ||
|
|
ae97ff0f98 | ||
|
|
b2d7fa9e97 | ||
|
|
93e9235bb2 | ||
|
|
094bce20e2 | ||
|
|
2f9d4c447a | ||
|
|
84eb790d93 | ||
|
|
ebd07694db | ||
|
|
2bbac8a6f4 | ||
|
|
e74f5e835f | ||
|
|
69e753cc71 | ||
|
|
b0978c772e | ||
|
|
fb4dfbadf3 | ||
|
|
d19ff3ff17 | ||
|
|
d0990be856 | ||
|
|
268d66b51d | ||
|
|
c1df311e86 | ||
|
|
cc3bd41df2 | ||
|
|
856979b455 | ||
|
|
47925489fd | ||
|
|
8aefb21123 | ||
|
|
e27751c18c | ||
|
|
a3f3fdcc71 | ||
|
|
a757576920 | ||
|
|
c4eb28d241 | ||
|
|
aba6c2eb4f | ||
|
|
ecb91b3155 | ||
|
|
7374b1cc70 | ||
|
|
e8f2972659 | ||
|
|
5bef19a306 | ||
|
|
54d9e02a42 | ||
|
|
4f479a8baf | ||
|
|
e7e6194cac | ||
|
|
113abbb94d | ||
|
|
d9d0651352 | ||
|
|
e27af9f6c1 | ||
|
|
11d820356d | ||
|
|
0945aab232 | ||
|
|
e4744221ee | ||
|
|
5f71b24f8d | ||
|
|
095a29972a | ||
|
|
76bdb708fa | ||
|
|
7c3c08fd96 | ||
|
|
a4a2e09429 | ||
|
|
4ed0ae5e2d | ||
|
|
4cb5e43357 | ||
|
|
b0fecc6b51 | ||
|
|
dd4236ca89 | ||
|
|
5da908c759 | ||
|
|
fcce1d406d | ||
|
|
5a01f39dc7 | ||
|
|
ea1d76f853 | ||
|
|
2356d8a64f | ||
|
|
969f82b903 | ||
|
|
fb90907abf | ||
|
|
cbd4cd940c | ||
|
|
6b18c6182e | ||
|
|
09164aa0c9 | ||
|
|
b83dadddb7 | ||
|
|
b3263b41ff | ||
|
|
1118149b9e | ||
|
|
b6fc24c6e7 | ||
|
|
441edf4667 | ||
|
|
74068eaa3d | ||
|
|
c11bd9e7eb | ||
|
|
b21a82ea6b | ||
|
|
cf455a13d5 | ||
|
|
c492f3529e | ||
|
|
40d7ba4bcc | ||
|
|
686bc49230 | ||
|
|
6550af698a | ||
|
|
aac5cbf53e | ||
|
|
00636db87c | ||
|
|
6231b8ad57 | ||
|
|
f2a41aa049 | ||
|
|
bb87b80a92 | ||
|
|
ebfbe29217 | ||
|
|
fad837e148 | ||
|
|
7dc84c0d6d | ||
|
|
74807fe251 | ||
|
|
bf9773be20 | ||
|
|
18961ff555 | ||
|
|
1f6e0342d6 | ||
|
|
7d2bc58ba2 | ||
|
|
556b53181f | ||
|
|
ca904d69e5 | ||
|
|
f461d459d2 | ||
|
|
6e535c11fd | ||
|
|
49d3262380 | ||
|
|
1ee3dec0cc | ||
|
|
15637642bb | ||
|
|
3c950c2b9e | ||
|
|
7811039651 | ||
|
|
ec5f7b38d0 | ||
|
|
2bb361dc19 | ||
|
|
3445e484ae | ||
|
|
efd6bf2afe | ||
|
|
cd2e6e1b24 | ||
|
|
99b8d24db3 | ||
|
|
8116b569c1 | ||
|
|
da791c5fed | ||
|
|
fbd9c59bfd | ||
|
|
3a1b3d19c5 | ||
|
|
238076f534 | ||
|
|
214d74ae11 | ||
|
|
30324f6113 | ||
|
|
68f0a25873 | ||
|
|
cd5bc4e930 | ||
|
|
1d8f729c95 | ||
|
|
f0d2fb53d4 | ||
|
|
0445c680ba | ||
|
|
12453942c8 | ||
|
|
b18a5be940 | ||
|
|
c2234747e8 | ||
|
|
d6c9ab43ec | ||
|
|
db16a87f74 | ||
|
|
5b0d2ec97b | ||
|
|
b906db3b24 | ||
|
|
df0af2a11f | ||
|
|
706dd3e616 | ||
|
|
bbec58e049 | ||
|
|
381b7d85f4 | ||
|
|
c17c056af3 | ||
|
|
a26a85cd1f | ||
|
|
9b68b1d327 | ||
|
|
6a6631052e | ||
|
|
d4ad3a953a | ||
|
|
7bb63a78c5 | ||
|
|
26c859f14c | ||
|
|
dd5c9bf3f6 | ||
|
|
3db40fea31 | ||
|
|
8f1c198406 | ||
|
|
ac65a3c86e | ||
|
|
79abcd90f6 | ||
|
|
d614abdec6 | ||
|
|
44d754c59d | ||
|
|
247d1db5d5 | ||
|
|
a52cfb2cd2 | ||
|
|
4e5d923388 | ||
|
|
f9598dd619 | ||
|
|
00f1c62646 | ||
|
|
8997728cbc | ||
|
|
99a0704516 | ||
|
|
434e361d5d | ||
|
|
f112b0bf03 | ||
|
|
0ce67ccd35 | ||
|
|
c947feadeb | ||
|
|
2fa381980d | ||
|
|
4e2c42f65c | ||
|
|
38b7d60af7 | ||
|
|
4714593d52 | ||
|
|
7729ed4f72 | ||
|
|
7107777df3 | ||
|
|
53989b918e | ||
|
|
fdd0c84441 | ||
|
|
8f0789bc6d | ||
|
|
2fdea6cd61 | ||
|
|
ed5f3a6202 | ||
|
|
d98e909256 | ||
|
|
6e0def310f | ||
|
|
823da07a5e | ||
|
|
40d2960562 | ||
|
|
6799f45352 | ||
|
|
1209a044ce | ||
|
|
9a8760c120 | ||
|
|
8d2a7716f3 | ||
|
|
5f240ada59 | ||
|
|
1c86dea4be | ||
|
|
92fd7ac09c | ||
|
|
90b490c28b | ||
|
|
8fd03a09de | ||
|
|
041232fbdd | ||
|
|
f595121ab3 | ||
|
|
bd733312f4 | ||
|
|
70db2f78da | ||
|
|
0298aba86f | ||
|
|
60b344eea8 | ||
|
|
eb63b743b1 | ||
|
|
d09ffc9dd8 | ||
|
|
f1b2643941 | ||
|
|
9e9d3ddc57 | ||
|
|
99a134494c | ||
|
|
d1156f963d | ||
|
|
2a6a2694d4 | ||
|
|
cc24e6b801 | ||
|
|
9c85719270 | ||
|
|
394e86f765 | ||
|
|
6bb9366ec8 | ||
|
|
8a9d4df6c7 | ||
|
|
fdc2e91170 | ||
|
|
40bf026e82 | ||
|
|
975e82710e | ||
|
|
1763dbcff1 | ||
|
|
908281356e | ||
|
|
123d5638cf | ||
|
|
79d0006f3f | ||
|
|
240b5daf3e | ||
|
|
cd0bede2f2 | ||
|
|
c5d984732a | ||
|
|
7358e68394 | ||
|
|
27b09e5b73 | ||
|
|
1705511b10 | ||
|
|
e17fa6d0ed | ||
|
|
9091976337 | ||
|
|
58d098503b | ||
|
|
33fe4f5295 | ||
|
|
0646c4f8bd | ||
|
|
960469f07c | ||
|
|
665f81ac9c | ||
|
|
26f05b343e | ||
|
|
f50968f992 | ||
|
|
4c486c399b | ||
|
|
9f4dd909a8 | ||
|
|
2b85aa1b88 | ||
|
|
8e4c3a3b21 | ||
|
|
a4160d2994 | ||
|
|
27e0252ccd | ||
|
|
0a707b3f02 | ||
|
|
6fc421810f | ||
|
|
22cf7443f4 | ||
|
|
8e7b4d2444 | ||
|
|
54437cec19 | ||
|
|
40fc63ea0c | ||
|
|
6bd81fe12a | ||
|
|
519ea1a33f | ||
|
|
f07f309393 | ||
|
|
7132e9ff24 | ||
|
|
34ae3cd704 | ||
|
|
fba972c98e | ||
|
|
a391ac682d | ||
|
|
4ee49d5991 | ||
|
|
0d573651a3 | ||
|
|
52efc23984 | ||
|
|
aefb84df3b | ||
|
|
ba374e08ff | ||
|
|
33a11ac2e5 | ||
|
|
357c4a382d | ||
|
|
d7e8f26ace | ||
|
|
5c312c1939 | ||
|
|
5163ab134e | ||
|
|
73dd0db529 | ||
|
|
8921db89ab | ||
|
|
8d624459d4 | ||
|
|
ec96021b00 | ||
|
|
25f50b8cdf | ||
|
|
0127d5143a | ||
|
|
ffe3b689c4 | ||
|
|
ff123be895 | ||
|
|
8178ec5671 | ||
|
|
67dd089e67 | ||
|
|
8d96368ea6 | ||
|
|
eb163ef03c | ||
|
|
5558403358 | ||
|
|
db3a4d0f01 | ||
|
|
d01fe62757 | ||
|
|
87cfc8f1de | ||
|
|
3a8bef26d3 | ||
|
|
b550c0e9e3 | ||
|
|
8f1ee30553 | ||
|
|
458174a5f5 | ||
|
|
c5414aadd1 | ||
|
|
eacd01e77e | ||
|
|
089b919a68 | ||
|
|
2e5945642d | ||
|
|
68a5b6fc50 | ||
|
|
88538257ac | ||
|
|
fb8041fb4b | ||
|
|
e685c4302d | ||
|
|
103b56ea3d | ||
|
|
0490b115ad | ||
|
|
5e1dd4a9ad | ||
|
|
93dd97a14a | ||
|
|
52d065a38d | ||
|
|
ad33e9f32d | ||
|
|
b9f00c6971 | ||
|
|
dba5ea156a | ||
|
|
631c86865f | ||
|
|
324b8fc74a | ||
|
|
282ca3ea2a | ||
|
|
17223db3ea | ||
|
|
cff3fdae6e | ||
|
|
108e83a402 | ||
|
|
fc237848c8 | ||
|
|
92b86bfa0b | ||
|
|
b0fd17047c | ||
|
|
bf8f82fbd8 | ||
|
|
5468c60eaa | ||
|
|
8736d87b95 | ||
|
|
dd0440519b | ||
|
|
b7400553fc | ||
|
|
9d2f570515 | ||
|
|
7eeba0c082 | ||
|
|
2e6bb21fda | ||
|
|
e30cfdf942 | ||
|
|
b07fb92e5c | ||
|
|
5729125fbb | ||
|
|
4577038abd | ||
|
|
7649c1df9e | ||
|
|
033bd111ad | ||
|
|
2cbe07b373 | ||
|
|
f6ec5c67a7 | ||
|
|
6bb78d3216 | ||
|
|
fa717a357d | ||
|
|
eed6bcc044 | ||
|
|
a01fd739bd | ||
|
|
3b7ed5ffd7 | ||
|
|
b3d9beea6d | ||
|
|
e256c7a7d9 | ||
|
|
ef866f957a | ||
|
|
58d25415db | ||
|
|
15f51a4064 | ||
|
|
7fbe456e79 | ||
|
|
9d8daac4cf | ||
|
|
ce199374d5 | ||
|
|
4af8615624 | ||
|
|
b6bb438507 | ||
|
|
49acd8a4f3 | ||
|
|
3e699a99d5 | ||
|
|
e4238f9283 | ||
|
|
9cd6d6d4c1 | ||
|
|
49a4b5feb4 | ||
|
|
c6eff157de | ||
|
|
80d16233e7 | ||
|
|
65e1a39027 | ||
|
|
d73b567bd4 | ||
|
|
787bb0a9e6 | ||
|
|
7faf0efb20 | ||
|
|
ed70cb8e3d |
6
.babelrc
6
.babelrc
@@ -3,6 +3,12 @@
|
|||||||
"env": {
|
"env": {
|
||||||
"development": {
|
"development": {
|
||||||
"presets": ["react-hmre"]
|
"presets": ["react-hmre"]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"presets": ["react", "es2015"],
|
||||||
|
"plugins": [
|
||||||
|
[ "babel-plugin-webpack-alias", { "config": "${PWD}/webpack.config.js" } ]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
|
}
|
||||||
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
compiled/
|
||||||
|
dist/
|
||||||
21
.eslintrc
Normal file
21
.eslintrc
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"extends": ["standard", "standard-jsx", "plugin:react/recommended"],
|
||||||
|
"plugins": ["react"],
|
||||||
|
"rules": {
|
||||||
|
"no-useless-escape": 0,
|
||||||
|
"prefer-const": "warn",
|
||||||
|
"no-unused-vars": "warn",
|
||||||
|
"no-undef": "warn",
|
||||||
|
"no-lone-blocks": "warn",
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,3 +8,5 @@ node_modules/*
|
|||||||
/compiled
|
/compiled
|
||||||
/secret
|
/secret
|
||||||
*.log
|
*.log
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
BIN
.snapcraft/travis_snapcraft.cfg
Normal file
BIN
.snapcraft/travis_snapcraft.cfg
Normal file
Binary file not shown.
20
.travis.yml
Normal file
20
.travis.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- stable
|
||||||
|
- lts/*
|
||||||
|
script:
|
||||||
|
- npm run lint && npm run test
|
||||||
|
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
|
||||||
|
after_success:
|
||||||
|
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
||||||
|
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
|
||||||
|
sudo: required
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
deploy:
|
||||||
|
'on':
|
||||||
|
branch: master
|
||||||
|
provider: script
|
||||||
|
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
|
||||||
|
&& cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
|
||||||
|
skip_cleanup: true
|
||||||
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
|
||||||
|
-->
|
||||||
6
LICENSE
6
LICENSE
@@ -1,6 +1,8 @@
|
|||||||
Boostnote - the simplest note app
|
GPL-3.0
|
||||||
|
|
||||||
Copyright (C) 2016 MAISIN&CO.
|
Boostnote - an open source note-taking app made for programmers just like you.
|
||||||
|
|
||||||
|
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,206 +1,187 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
import ReactDOM from 'react-dom'
|
import React from 'react'
|
||||||
import modes from '../lib/modes'
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import CodeMirror from 'codemirror'
|
||||||
|
import path from 'path'
|
||||||
|
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
const ace = window.ace
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||||
|
|
||||||
|
function pass (name) {
|
||||||
|
switch (name) {
|
||||||
|
case 'ejs':
|
||||||
|
return 'Embedded Javascript'
|
||||||
|
case 'html_ruby':
|
||||||
|
return 'Embedded Ruby'
|
||||||
|
case 'objectivec':
|
||||||
|
return 'Objective C'
|
||||||
|
case 'text':
|
||||||
|
return 'Plain Text'
|
||||||
|
default:
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default class CodeEditor extends React.Component {
|
export default class CodeEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.changeHandler = (e) => this.handleChange(e)
|
this.changeHandler = (e) => this.handleChange(e)
|
||||||
this.blurHandler = (e) => {
|
this.blurHandler = (editor, e) => {
|
||||||
e.stopPropagation()
|
if (e == null) return null
|
||||||
let el = e.relatedTarget
|
let el = e.relatedTarget
|
||||||
let isStillFocused = false
|
|
||||||
while (el != null) {
|
while (el != null) {
|
||||||
if (el === this.refs.root) {
|
if (el === this.refs.root) {
|
||||||
isStillFocused = true
|
return
|
||||||
break
|
|
||||||
}
|
}
|
||||||
el = el.parentNode
|
el = el.parentNode
|
||||||
}
|
}
|
||||||
|
this.props.onBlur != null && this.props.onBlur(e)
|
||||||
if (!isStillFocused && this.props.onBlur != null) this.props.onBlur(e)
|
|
||||||
}
|
}
|
||||||
|
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||||
this.killedBuffer = ''
|
this.loadStyleHandler = (e) => {
|
||||||
this.execHandler = (e) => {
|
this.editor.refresh()
|
||||||
console.info('ACE COMMAND >> %s', e.command.name)
|
|
||||||
switch (e.command.name) {
|
|
||||||
case 'gotolinestart':
|
|
||||||
e.preventDefault()
|
|
||||||
{
|
|
||||||
let position = this.editor.getCursorPosition()
|
|
||||||
this.editor.navigateTo(position.row, 0)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'gotolineend':
|
|
||||||
e.preventDefault()
|
|
||||||
let position = this.editor.getCursorPosition()
|
|
||||||
this.editor.navigateTo(position.row, this.editor.getSession().getLine(position.row).length)
|
|
||||||
break
|
|
||||||
case 'jumptomatching':
|
|
||||||
e.preventDefault()
|
|
||||||
this.editor.navigateUp()
|
|
||||||
break
|
|
||||||
case 'removetolineend':
|
|
||||||
e.preventDefault()
|
|
||||||
let range = this.editor.getSelectionRange()
|
|
||||||
let session = this.editor.getSession()
|
|
||||||
if (range.isEmpty()) {
|
|
||||||
range.setEnd(range.start.row, session.getLine(range.start.row).length)
|
|
||||||
this.killedBuffer = session.getTextRange(range)
|
|
||||||
if (this.killedBuffer.length > 0) {
|
|
||||||
console.log('remove to lineend')
|
|
||||||
session.remove(range)
|
|
||||||
} else {
|
|
||||||
if (session.getLength() === range.start.row) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
range.setStart(range.start.row, range.end.col)
|
|
||||||
range.setEnd(range.start.row + 1, 0)
|
|
||||||
this.killedBuffer = '\n'
|
|
||||||
session.remove(range)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.killedBuffer = session.getTextRange(range)
|
|
||||||
session.remove(range)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.afterExecHandler = (e) => {
|
|
||||||
switch (e.command.name) {
|
|
||||||
case 'find':
|
|
||||||
Array.prototype.forEach.call(ReactDOM.findDOMNode(this).querySelectorAll('.ace_search_field, .ace_searchbtn, .ace_replacebtn, .ace_searchbtn_close'), (el) => {
|
|
||||||
el.removeEventListener('blur', this.blurHandler)
|
|
||||||
el.addEventListener('blur', this.blurHandler)
|
|
||||||
})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
}
|
|
||||||
|
|
||||||
this.silentChange = false
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (nextProps.readOnly !== this.props.readOnly) {
|
|
||||||
this.editor.setReadOnly(!!nextProps.readOnly)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
let { mode, value, theme, fontSize } = this.props
|
this.value = this.props.value
|
||||||
this.value = value
|
this.editor = CodeMirror(this.refs.root, {
|
||||||
let el = ReactDOM.findDOMNode(this)
|
value: this.props.value,
|
||||||
let editor = this.editor = ace.edit(el)
|
lineNumbers: true,
|
||||||
editor.$blockScrolling = Infinity
|
lineWrapping: true,
|
||||||
editor.renderer.setShowGutter(true)
|
theme: this.props.theme,
|
||||||
editor.setTheme('ace/theme/' + theme)
|
indentUnit: this.props.indentSize,
|
||||||
editor.moveCursorTo(0, 0)
|
tabSize: this.props.indentSize,
|
||||||
editor.setReadOnly(!!this.props.readOnly)
|
indentWithTabs: this.props.indentType !== 'space',
|
||||||
editor.setFontSize(fontSize)
|
keyMap: this.props.keyMap,
|
||||||
|
scrollPastEnd: this.props.scrollPastEnd,
|
||||||
editor.on('blur', this.blurHandler)
|
inputStyle: 'textarea',
|
||||||
|
dragDrop: false,
|
||||||
editor.commands.addCommand({
|
extraKeys: {
|
||||||
name: 'Emacs cursor up',
|
Tab: function (cm) {
|
||||||
bindKey: {mac: 'Ctrl-P'},
|
const cursor = cm.getCursor()
|
||||||
exec: function (editor) {
|
const line = cm.getLine(cursor.line)
|
||||||
editor.navigateUp(1)
|
if (cm.somethingSelected()) cm.indentSelection('add')
|
||||||
if (editor.getCursorPosition().row < editor.getFirstVisibleRow()) editor.scrollToLine(editor.getCursorPosition().row, false, false)
|
else {
|
||||||
|
const tabs = cm.getOption('indentWithTabs')
|
||||||
|
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)\] )?$/)) {
|
||||||
|
cm.execCommand('goLineStart')
|
||||||
|
if (tabs) {
|
||||||
|
cm.execCommand('insertTab')
|
||||||
|
} else {
|
||||||
|
cm.execCommand('insertSoftTab')
|
||||||
|
}
|
||||||
|
cm.execCommand('goLineEnd')
|
||||||
|
} else {
|
||||||
|
if (tabs) {
|
||||||
|
cm.execCommand('insertTab')
|
||||||
|
} else {
|
||||||
|
cm.execCommand('insertSoftTab')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
readOnly: true
|
'Cmd-T': function (cm) {
|
||||||
})
|
// Do nothing
|
||||||
editor.commands.addCommand({
|
},
|
||||||
name: 'Emacs kill buffer',
|
Enter: 'newlineAndIndentContinueMarkdownList',
|
||||||
bindKey: {mac: 'Ctrl-Y'},
|
'Ctrl-C': (cm) => {
|
||||||
exec: function (editor) {
|
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
||||||
editor.insert(this.killedBuffer)
|
document.execCommand('copy')
|
||||||
}.bind(this),
|
}
|
||||||
readOnly: true
|
return CodeMirror.Pass
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
editor.commands.on('exec', this.execHandler)
|
this.setMode(this.props.mode)
|
||||||
editor.commands.on('afterExec', this.afterExecHandler)
|
|
||||||
|
|
||||||
var session = editor.getSession()
|
this.editor.on('blur', this.blurHandler)
|
||||||
mode = _.find(modes, {name: mode})
|
this.editor.on('change', this.changeHandler)
|
||||||
let syntaxMode = mode != null
|
this.editor.on('paste', this.pasteHandler)
|
||||||
? mode.mode
|
|
||||||
: 'text'
|
|
||||||
session.setMode('ace/mode/' + syntaxMode)
|
|
||||||
|
|
||||||
session.setUseSoftTabs(this.props.indentType === 'space')
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
session.setTabSize(this.props.indentSize)
|
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||||
session.setOption('useWorker', true)
|
|
||||||
session.setUseWrapMode(true)
|
|
||||||
session.setValue(_.isString(value) ? value : '')
|
|
||||||
|
|
||||||
session.on('change', this.changeHandler)
|
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.getSession().removeListener('change', this.changeHandler)
|
this.editor.off('blur', this.blurHandler)
|
||||||
this.editor.removeListener('blur', this.blurHandler)
|
this.editor.off('change', this.changeHandler)
|
||||||
this.editor.commands.removeListener('exec', this.execHandler)
|
this.editor.off('paste', this.pasteHandler)
|
||||||
this.editor.commands.removeListener('afterExec', this.afterExecHandler)
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
|
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps, prevState) {
|
componentDidUpdate (prevProps, prevState) {
|
||||||
let { value } = this.props
|
let needRefresh = false
|
||||||
this.value = value
|
|
||||||
let editor = this.editor
|
|
||||||
let session = this.editor.getSession()
|
|
||||||
|
|
||||||
if (prevProps.mode !== this.props.mode) {
|
if (prevProps.mode !== this.props.mode) {
|
||||||
let mode = _.find(modes, {name: this.props.mode})
|
this.setMode(this.props.mode)
|
||||||
let syntaxMode = mode != null
|
|
||||||
? mode.mode
|
|
||||||
: 'text'
|
|
||||||
session.setMode('ace/mode/' + syntaxMode)
|
|
||||||
}
|
}
|
||||||
if (prevProps.theme !== this.props.theme) {
|
if (prevProps.theme !== this.props.theme) {
|
||||||
editor.setTheme('ace/theme/' + this.props.theme)
|
this.editor.setOption('theme', this.props.theme)
|
||||||
|
// editor should be refreshed after css loaded
|
||||||
}
|
}
|
||||||
if (prevProps.fontSize !== this.props.fontSize) {
|
if (prevProps.fontSize !== this.props.fontSize) {
|
||||||
editor.setFontSize(this.props.fontSize)
|
needRefresh = true
|
||||||
}
|
}
|
||||||
|
if (prevProps.fontFamily !== this.props.fontFamily) {
|
||||||
|
needRefresh = true
|
||||||
|
}
|
||||||
|
if (prevProps.keyMap !== this.props.keyMap) {
|
||||||
|
needRefresh = true
|
||||||
|
}
|
||||||
|
|
||||||
if (prevProps.indentSize !== this.props.indentSize) {
|
if (prevProps.indentSize !== this.props.indentSize) {
|
||||||
session.setTabSize(this.props.indentSize)
|
this.editor.setOption('indentUnit', this.props.indentSize)
|
||||||
|
this.editor.setOption('tabSize', this.props.indentSize)
|
||||||
}
|
}
|
||||||
if (prevProps.indentType !== this.props.indentType) {
|
if (prevProps.indentType !== this.props.indentType) {
|
||||||
session.setUseSoftTabs(this.props.indentType === 'space')
|
this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
|
||||||
|
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needRefresh) {
|
||||||
|
this.editor.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMode (mode) {
|
||||||
|
let syntax = CodeMirror.findModeByName(pass(mode))
|
||||||
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
|
this.editor.setOption('mode', syntax.mime)
|
||||||
|
CodeMirror.autoLoadMode(this.editor, syntax.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange (e) {
|
handleChange (e) {
|
||||||
if (this.props.onChange) {
|
|
||||||
this.value = this.editor.getValue()
|
this.value = this.editor.getValue()
|
||||||
|
if (this.props.onChange) {
|
||||||
this.props.onChange(e)
|
this.props.onChange(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFirstVisibleRow () {
|
|
||||||
return this.editor.getFirstVisibleRow()
|
|
||||||
}
|
|
||||||
|
|
||||||
getCursorPosition () {
|
|
||||||
return this.editor.getCursorPosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
moveCursorTo (row, col) {
|
moveCursorTo (row, col) {
|
||||||
this.editor.moveCursorTo(row, col)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToLine (num) {
|
scrollToLine (num) {
|
||||||
this.editor.scrollToLine(num, false, false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
@@ -212,15 +193,63 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reload () {
|
reload () {
|
||||||
let session = this.editor.getSession()
|
// Change event shouldn't be fired when switch note
|
||||||
session.removeListener('change', this.changeHandler)
|
this.editor.off('change', this.changeHandler)
|
||||||
session.setValue(this.props.value)
|
this.value = this.props.value
|
||||||
session.getUndoManager().reset()
|
this.editor.setValue(this.props.value)
|
||||||
session.on('change', this.changeHandler)
|
this.editor.clearHistory()
|
||||||
|
this.editor.on('change', this.changeHandler)
|
||||||
|
this.editor.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue (value) {
|
||||||
|
const cursor = this.editor.getCursor()
|
||||||
|
this.editor.setValue(value)
|
||||||
|
this.editor.setCursor(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDropImage (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
const imagePath = e.dataTransfer.files[0].path
|
||||||
|
const filename = path.basename(imagePath)
|
||||||
|
|
||||||
|
copyImage(imagePath, this.props.storageKey).then((imagePath) => {
|
||||||
|
const imageMd = `})`
|
||||||
|
this.insertImageMd(imageMd)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
insertImageMd (imageMd) {
|
||||||
|
this.editor.replaceSelection(imageMd)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 } = 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
|
||||||
@@ -233,8 +262,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
ref='root'
|
ref='root'
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
style={{
|
style={{
|
||||||
fontFamily: fontFamily.join(', ')
|
fontFamily: fontFamily.join(', '),
|
||||||
|
fontSize: fontSize
|
||||||
}}
|
}}
|
||||||
|
onDrop={(e) => this.handleDropImage(e)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -252,10 +283,9 @@ CodeEditor.propTypes = {
|
|||||||
CodeEditor.defaultProps = {
|
CodeEditor.defaultProps = {
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
theme: 'xcode',
|
theme: 'xcode',
|
||||||
|
keyMap: 'sublime',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: 'Monaco, Consolas',
|
fontFamily: 'Monaco, Consolas',
|
||||||
indentSize: 4,
|
indentSize: 4,
|
||||||
indentType: 'space'
|
indentType: 'space'
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CodeEditor
|
|
||||||
|
|||||||
@@ -1,35 +1,78 @@
|
|||||||
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 { findStorage } from 'browser/lib/findStorage'
|
||||||
|
|
||||||
class MarkdownEditor extends React.Component {
|
class MarkdownEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
// char codes for ctrl + w
|
||||||
|
this.escapeFromEditor = [17, 87]
|
||||||
|
|
||||||
|
// ctrl + shift + ;
|
||||||
|
this.supportMdSelectionBold = [16, 17, 186]
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
status: 'PREVIEW'
|
status: 'PREVIEW',
|
||||||
|
renderValue: props.value,
|
||||||
|
keyPressed: new Set(),
|
||||||
|
isLocked: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.lockEditorCode = () => this.handleLockEditor()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
|
eventEmitter.on('editor:lock', this.lockEditorCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate () {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (props) {
|
||||||
|
if (props.value !== this.props.value) {
|
||||||
|
this.queueRendering(props.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.cancelQueue()
|
||||||
|
eventEmitter.off('editor:lock', this.lockEditorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
queueRendering (value) {
|
||||||
|
clearTimeout(this.renderTimer)
|
||||||
|
this.renderTimer = setTimeout(() => {
|
||||||
|
this.renderPreview(value)
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelQueue () {
|
||||||
|
clearTimeout(this.renderTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPreview (value) {
|
||||||
|
this.setState({
|
||||||
|
renderValue: value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleChange (e) {
|
handleChange (e) {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
this.props.onChange(e)
|
this.props.onChange(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
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({
|
||||||
@@ -38,23 +81,26 @@ 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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlur (e) {
|
handleBlur (e) {
|
||||||
let { config } = this.props
|
if (this.state.isLocked) return
|
||||||
|
this.setState({ keyPressed: new Set() })
|
||||||
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'BLUR') {
|
if (config.editor.switchPreview === 'BLUR') {
|
||||||
let cursorPosition = this.refs.code.getCursorPosition()
|
const cursorPosition = this.refs.code.editor.getCursor()
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'PREVIEW'
|
status: 'PREVIEW'
|
||||||
}, () => {
|
}, () => {
|
||||||
this.refs.preview.focus()
|
this.refs.preview.focus()
|
||||||
this.refs.preview.scrollTo(cursorPosition.row)
|
this.refs.preview.scrollTo(cursorPosition.line)
|
||||||
})
|
})
|
||||||
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,13 +109,37 @@ 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'
|
||||||
}, () => {
|
}, () => {
|
||||||
this.refs.code.focus()
|
this.refs.code.focus()
|
||||||
})
|
})
|
||||||
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCheckboxClick (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
|
const checkedMatch = /\[x\]/i
|
||||||
|
const uncheckedMatch = /\[ \]/
|
||||||
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
|
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
|
const lines = this.refs.code.value
|
||||||
|
.split('\n')
|
||||||
|
|
||||||
|
const targetLine = lines[lineIndex]
|
||||||
|
|
||||||
|
if (targetLine.match(checkedMatch)) {
|
||||||
|
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
||||||
|
}
|
||||||
|
if (targetLine.match(uncheckedMatch)) {
|
||||||
|
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
|
||||||
|
}
|
||||||
|
this.refs.code.setValue(lines.join('\n'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,22 +153,72 @@ class MarkdownEditor extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
this.refs.code.focus()
|
this.refs.code.focus()
|
||||||
}
|
}
|
||||||
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
reload () {
|
reload () {
|
||||||
this.refs.code.reload()
|
this.refs.code.reload()
|
||||||
|
this.cancelQueue()
|
||||||
|
this.renderPreview(this.props.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown (e) {
|
||||||
|
const { config } = this.props
|
||||||
|
if (this.state.status !== 'CODE') return false
|
||||||
|
const keyPressed = this.state.keyPressed
|
||||||
|
keyPressed.add(e.keyCode)
|
||||||
|
this.setState({ keyPressed })
|
||||||
|
const isNoteHandlerKey = (el) => { return keyPressed.has(el) }
|
||||||
|
// These conditions are for ctrl-e and ctrl-w
|
||||||
|
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 (keyPressed.size === this.supportMdSelectionBold.length && this.supportMdSelectionBold.every(isNoteHandlerKey)) {
|
||||||
|
this.addMdAroundWord('**')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addMdAroundWord (mdElement) {
|
||||||
|
if (this.refs.code.editor.getSelection()) {
|
||||||
|
return this.addMdAroundSelection(mdElement)
|
||||||
|
}
|
||||||
|
const currentCaret = this.refs.code.editor.getCursor()
|
||||||
|
const word = this.refs.code.editor.findWordAt(currentCaret)
|
||||||
|
const cmDoc = this.refs.code.editor.getDoc()
|
||||||
|
cmDoc.replaceRange(mdElement, word.anchor)
|
||||||
|
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) {
|
||||||
|
const keyPressed = this.state.keyPressed
|
||||||
|
keyPressed.delete(e.keyCode)
|
||||||
|
this.setState({ keyPressed })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLockEditor () {
|
||||||
|
this.setState({ isLocked: !this.state.isLocked })
|
||||||
}
|
}
|
||||||
|
|
||||||
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'
|
||||||
@@ -106,16 +226,24 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
|
onKeyUp={(e) => this.handleKeyUp(e)}
|
||||||
>
|
>
|
||||||
<CodeEditor styleName='codeEditor'
|
<CodeEditor styleName={this.state.status === 'CODE'
|
||||||
|
? 'codeEditor'
|
||||||
|
: 'codeEditor--hide'
|
||||||
|
}
|
||||||
ref='code'
|
ref='code'
|
||||||
mode='markdown'
|
mode='GitHub Flavored Markdown'
|
||||||
value={value}
|
value={value}
|
||||||
theme={config.editor.theme}
|
theme={config.editor.theme}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
fontFamily={config.editor.fontFamily}
|
fontFamily={config.editor.fontFamily}
|
||||||
fontSize={editorFontSize}
|
fontSize={editorFontSize}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
|
storageKey={storageKey}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
onBlur={(e) => this.handleBlur(e)}
|
||||||
/>
|
/>
|
||||||
@@ -124,17 +252,24 @@ class MarkdownEditor extends React.Component {
|
|||||||
: 'preview--hide'
|
: 'preview--hide'
|
||||||
}
|
}
|
||||||
style={previewStyle}
|
style={previewStyle}
|
||||||
|
theme={config.ui.theme}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
fontSize={config.preview.fontSize}
|
fontSize={config.preview.fontSize}
|
||||||
fontFamily={config.preview.fontFamily}
|
fontFamily={config.preview.fontFamily}
|
||||||
codeBlockTheme={config.preview.codeBlockTheme}
|
codeBlockTheme={config.preview.codeBlockTheme}
|
||||||
codeBlockFontFamily={config.editor.fontFamily}
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
value={value}
|
value={this.state.renderValue}
|
||||||
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)}
|
||||||
|
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,21 +1,108 @@
|
|||||||
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 hljsTheme from 'browser/lib/hljsThemes'
|
import CodeMirror from 'codemirror'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import Raphael from 'raphael'
|
||||||
|
import flowchart from 'flowchart'
|
||||||
|
import SequenceDiagram from 'js-sequence-diagrams'
|
||||||
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
import fs from 'fs'
|
||||||
|
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||||
|
import copy from 'copy-to-clipboard'
|
||||||
|
import mdurl from 'mdurl'
|
||||||
|
|
||||||
|
const { remote } = require('electron')
|
||||||
|
const { app } = remote
|
||||||
|
const path = require('path')
|
||||||
|
const dialog = remote.dialog
|
||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||||
const { shell } = require('electron')
|
const appPath = 'file://' + (process.env.NODE_ENV === 'production'
|
||||||
const goExternal = function (e) {
|
? app.getAppPath()
|
||||||
e.preventDefault()
|
: path.resolve())
|
||||||
e.stopPropagation()
|
|
||||||
shell.openExternal(e.target.href)
|
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber) {
|
||||||
|
return `
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src: url('${appPath}/resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||||
|
url('${appPath}/resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||||
|
url('${appPath}/resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src: url('${appPath}/resources/fonts/Lato-Black.woff2') format('woff2'), /* Modern Browsers */
|
||||||
|
url('${appPath}/resources/fonts/Lato-Black.woff') format('woff'), /* Modern Browsers */
|
||||||
|
url('${appPath}/resources/fonts/Lato-Black.ttf') format('truetype');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
${markdownStyle}
|
||||||
|
body {
|
||||||
|
font-family: '${fontFamily.join("','")}';
|
||||||
|
font-size: ${fontSize}px;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
font-family: '${codeBlockFontFamily.join("','")}';
|
||||||
|
background-color: rgba(0,0,0,0.04);
|
||||||
|
}
|
||||||
|
.lineNumber {
|
||||||
|
${lineNumber && 'display: block !important;'}
|
||||||
|
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 {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
margin: 1em 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
padding-bottom: 0.2em;
|
||||||
|
margin: 1em 0 0.37em;
|
||||||
|
}
|
||||||
|
|
||||||
|
body p {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
const { shell } = require('electron')
|
||||||
const OSX = global.process.platform === 'darwin'
|
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']
|
||||||
@@ -27,91 +114,287 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
||||||
this.mouseDownHandler = (e) => this.handleMouseDown(e)
|
this.mouseDownHandler = (e) => this.handleMouseDown(e)
|
||||||
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
||||||
|
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
|
||||||
|
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||||
|
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||||
|
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||||
|
this.printHandler = () => this.handlePrint()
|
||||||
|
|
||||||
|
this.linkClickHandler = this.handlelinkClick.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePreviewAnchorClick (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
const anchor = e.target.closest('a')
|
||||||
|
const href = anchor.getAttribute('href')
|
||||||
|
if (_.isString(href) && href.match(/^#/)) {
|
||||||
|
const targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
|
||||||
|
if (targetElement != null) {
|
||||||
|
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shell.openExternal(href)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCheckboxClick (e) {
|
||||||
|
this.props.onCheckboxClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu (e) {
|
||||||
|
if (!this.props.onContextMenu) return
|
||||||
this.props.onContextMenu(e)
|
this.props.onContextMenu(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
handleMouseDown (e) {
|
||||||
|
if (!this.props.onMouseDown) return
|
||||||
|
if (e.target != null) {
|
||||||
|
switch (e.target.tagName) {
|
||||||
|
case 'A':
|
||||||
|
case 'INPUT':
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.props.onMouseDown != null) this.props.onMouseDown(e)
|
if (this.props.onMouseDown != null) this.props.onMouseDown(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp (e) {
|
||||||
|
if (!this.props.onMouseUp) return
|
||||||
|
if (e.target != null && e.target.tagName === 'A') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
|
if (this.props.onMouseUp != null) this.props.onMouseUp(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSaveAsText () {
|
||||||
|
this.exportAsDocument('txt')
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSaveAsMd () {
|
||||||
|
this.exportAsDocument('md')
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePrint () {
|
||||||
|
this.refs.root.contentWindow.print()
|
||||||
|
}
|
||||||
|
|
||||||
|
exportAsDocument (fileType) {
|
||||||
|
const options = {
|
||||||
|
filters: [
|
||||||
|
{ name: 'Documents', extensions: [fileType] }
|
||||||
|
],
|
||||||
|
properties: ['openFile', 'createDirectory']
|
||||||
|
}
|
||||||
|
dialog.showSaveDialog(remote.getCurrentWindow(), options,
|
||||||
|
(filename) => {
|
||||||
|
if (filename) {
|
||||||
|
fs.writeFile(filename, this.props.value, (err) => {
|
||||||
|
if (err) throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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-same-origin')
|
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)
|
||||||
|
|
||||||
|
this.refs.root.contentWindow.document.head.innerHTML = `
|
||||||
|
<style id='style'></style>
|
||||||
|
<link rel="stylesheet" href="${appPath}/node_modules/katex/dist/katex.min.css">
|
||||||
|
<link rel="stylesheet" href="${appPath}/node_modules/codemirror/lib/codemirror.css">
|
||||||
|
<link rel="stylesheet" id="codeTheme">
|
||||||
|
`
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
|
this.applyStyle()
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
|
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
|
||||||
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
|
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
|
||||||
|
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
||||||
|
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||||
|
eventEmitter.on('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
|
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
|
||||||
this.refs.root.contentWindow.document.removeEventListener('mousedown', this.mouseDownHandler)
|
this.refs.root.contentWindow.document.removeEventListener('mousedown', this.mouseDownHandler)
|
||||||
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
|
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
|
||||||
|
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
||||||
|
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
||||||
|
eventEmitter.off('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.value !== this.props.value ||
|
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||||
prevProps.fontFamily !== this.props.fontFamily ||
|
if (prevProps.fontFamily !== this.props.fontFamily ||
|
||||||
prevProps.fontSize !== this.props.fontSize ||
|
prevProps.fontSize !== this.props.fontSize ||
|
||||||
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 ||
|
||||||
) this.rewriteIframe()
|
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
||||||
|
prevProps.theme !== this.props.theme) {
|
||||||
|
this.applyStyle()
|
||||||
|
this.rewriteIframe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyStyle () {
|
||||||
|
const { fontSize, lineNumber, codeBlockTheme } = this.props
|
||||||
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
|
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
|
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
||||||
|
: defaultFontFamily
|
||||||
|
codeBlockFontFamily = _.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
||||||
|
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
|
||||||
|
: defaultCodeBlockFontFamily
|
||||||
|
|
||||||
|
this.setCodeTheme(codeBlockTheme)
|
||||||
|
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
setCodeTheme (theme) {
|
||||||
|
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
|
||||||
|
? theme
|
||||||
|
: 'elegant'
|
||||||
|
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 () {
|
||||||
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
el.removeEventListener('click', goExternal)
|
el.removeEventListener('click', this.anchorClickHandler)
|
||||||
|
})
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
|
el.removeEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
let { value, fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme } = this.props
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
el.removeEventListener('click', this.linkClickHandler)
|
||||||
? [fontFamily].concat(defaultFontFamily)
|
})
|
||||||
: defaultFontFamily
|
|
||||||
codeBlockFontFamily = _.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
|
||||||
? [codeBlockFontFamily].concat(defaultCodeBlockFontFamily)
|
|
||||||
: defaultCodeBlockFontFamily
|
|
||||||
codeBlockTheme = hljsTheme().some((theme) => theme.name === codeBlockTheme) ? codeBlockTheme : 'xcode'
|
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.head.innerHTML = `
|
const { theme, indentSize, showCopyNotification, storagePath } = this.props
|
||||||
<style>
|
let { value, codeBlockTheme } = this.props
|
||||||
@font-face {
|
|
||||||
font-family: 'Lato';
|
|
||||||
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
|
||||||
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
|
||||||
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
${markdownStyle}
|
|
||||||
body {
|
|
||||||
font-family: ${fontFamily.join(', ')};
|
|
||||||
font-size: ${fontSize}px;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
font-family: ${codeBlockFontFamily.join(', ')};
|
|
||||||
}
|
|
||||||
.lineNumber {
|
|
||||||
${lineNumber && 'display: block !important;'}
|
|
||||||
font-family: ${codeBlockFontFamily.join(', ')};
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<link rel="stylesheet" href="../node_modules/highlight.js/styles/${codeBlockTheme}.css">
|
|
||||||
<link rel="stylesheet" href="../resources/katex.min.css">
|
|
||||||
`
|
|
||||||
this.refs.root.contentWindow.document.body.innerHTML = markdown(value)
|
|
||||||
|
|
||||||
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
el.addEventListener('mousedown', goExternal)
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.taskListItem'), (el) => {
|
||||||
|
el.parentNode.parentNode.style.listStyleType = 'none'
|
||||||
|
})
|
||||||
|
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
|
this.fixDecodedURI(el)
|
||||||
|
el.addEventListener('click', this.anchorClickHandler)
|
||||||
|
})
|
||||||
|
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
|
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
|
||||||
|
: 'default'
|
||||||
|
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
|
||||||
|
let syntax = CodeMirror.findModeByName(el.className)
|
||||||
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
CodeMirror.requireMode(syntax.mode, () => {
|
||||||
|
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 = ''
|
||||||
|
if (codeBlockTheme.indexOf('solarized') === 0) {
|
||||||
|
const [refThema, color] = codeBlockTheme.split(' ')
|
||||||
|
el.parentNode.className += ` cm-s-${refThema} cm-s-${color} CodeMirror`
|
||||||
|
} else {
|
||||||
|
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
|
||||||
|
}
|
||||||
|
CodeMirror.runMode(content, syntax.mime, el, {
|
||||||
|
tabSize: indentSize
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const opts = {}
|
||||||
|
// if (this.props.theme === 'dark') {
|
||||||
|
// opts['font-color'] = '#DDD'
|
||||||
|
// opts['line-color'] = '#DDD'
|
||||||
|
// opts['element-color'] = '#DDD'
|
||||||
|
// opts['fill'] = '#3A404C'
|
||||||
|
// }
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => {
|
||||||
|
Raphael.setWindow(this.getWindow())
|
||||||
|
try {
|
||||||
|
const diagram = flowchart.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
||||||
|
el.innerHTML = ''
|
||||||
|
diagram.drawSVG(el, opts)
|
||||||
|
_.forEach(el.querySelectorAll('a'), (el) => {
|
||||||
|
el.addEventListener('click', this.anchorClickHandler)
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
el.className = 'flowchart-error'
|
||||||
|
el.innerHTML = 'Flowchart parse error: ' + e.message
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.sequence'), (el) => {
|
||||||
|
Raphael.setWindow(this.getWindow())
|
||||||
|
try {
|
||||||
|
const diagram = SequenceDiagram.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
||||||
|
el.innerHTML = ''
|
||||||
|
diagram.drawSVG(el, {theme: 'simple'})
|
||||||
|
_.forEach(el.querySelectorAll('a'), (el) => {
|
||||||
|
el.addEventListener('click', this.anchorClickHandler)
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
el.className = 'sequence-error'
|
||||||
|
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,21 +407,41 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollTo (targetRow) {
|
scrollTo (targetRow) {
|
||||||
let lineAnchors = this.getWindow().document.querySelectorAll('a.lineAnchor')
|
const blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
|
||||||
|
|
||||||
for (let index = 0; index < lineAnchors.length; index++) {
|
for (let index = 0; index < blocks.length; index++) {
|
||||||
let lineAnchor = lineAnchors[index]
|
let block = blocks[index]
|
||||||
let row = parseInt(lineAnchor.getAttribute('data-key'))
|
const row = parseInt(block.getAttribute('data-line'))
|
||||||
if (row > targetRow) {
|
if (row > targetRow || index === blocks.length - 1) {
|
||||||
let targetAnchor = lineAnchors[index - 1]
|
block = blocks[index - 1]
|
||||||
this.getWindow().scrollTo(0, targetAnchor.offsetTop)
|
block != null && this.getWindow().scrollTo(0, block.offsetTop)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preventImageDroppedHandler (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
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
|
||||||
@@ -157,7 +460,8 @@ MarkdownPreview.propTypes = {
|
|||||||
onDoubleClick: PropTypes.func,
|
onDoubleClick: PropTypes.func,
|
||||||
onMouseUp: PropTypes.func,
|
onMouseUp: PropTypes.func,
|
||||||
onMouseDown: PropTypes.func,
|
onMouseDown: PropTypes.func,
|
||||||
onMouseMove: PropTypes.func,
|
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
value: PropTypes.string
|
value: PropTypes.string,
|
||||||
|
showCopyNotification: PropTypes.bool,
|
||||||
|
storagePath: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|||||||
87
browser/components/MarkdownSplitEditor.js
Normal file
87
browser/components/MarkdownSplitEditor.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
|
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
|
||||||
|
import styles from './MarkdownSplitEditor.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
class MarkdownSplitEditor extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.value = props.value
|
||||||
|
this.focus = () => this.refs.code.focus()
|
||||||
|
this.reload = () => this.refs.code.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnChange () {
|
||||||
|
this.value = this.refs.code.value
|
||||||
|
this.props.onChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCheckboxClick (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
|
const checkedMatch = /\[x\]/i
|
||||||
|
const uncheckedMatch = /\[ \]/
|
||||||
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
|
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
|
const lines = this.refs.code.value
|
||||||
|
.split('\n')
|
||||||
|
|
||||||
|
const targetLine = lines[lineIndex]
|
||||||
|
|
||||||
|
if (targetLine.match(checkedMatch)) {
|
||||||
|
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
||||||
|
}
|
||||||
|
if (targetLine.match(uncheckedMatch)) {
|
||||||
|
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
|
||||||
|
}
|
||||||
|
this.refs.code.setValue(lines.join('\n'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { config, value, storageKey } = this.props
|
||||||
|
const storage = findStorage(storageKey)
|
||||||
|
const previewStyle = {}
|
||||||
|
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<CodeEditor
|
||||||
|
styleName='codeEditor'
|
||||||
|
ref='code'
|
||||||
|
mode='GitHub Flavored Markdown'
|
||||||
|
value={value}
|
||||||
|
theme={config.editor.theme}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
|
fontFamily={config.editor.fontFamily}
|
||||||
|
indentType={config.editor.indentType}
|
||||||
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
|
storageKey={storageKey}
|
||||||
|
onChange={this.handleOnChange.bind(this)}
|
||||||
|
/>
|
||||||
|
<MarkdownPreview
|
||||||
|
style={previewStyle}
|
||||||
|
styleName='preview'
|
||||||
|
theme={config.ui.theme}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
|
fontSize={config.preview.fontSize}
|
||||||
|
fontFamily={config.preview.fontFamily}
|
||||||
|
codeBlockTheme={config.preview.codeBlockTheme}
|
||||||
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
|
lineNumber={config.preview.lineNumber}
|
||||||
|
ref='preview'
|
||||||
|
tabInde='0'
|
||||||
|
value={value}
|
||||||
|
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||||
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
|
storagePath={storage.path}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(MarkdownSplitEditor, styles)
|
||||||
9
browser/components/MarkdownSplitEditor.styl
Normal file
9
browser/components/MarkdownSplitEditor.styl
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.root
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
font-size 30px
|
||||||
|
display flex
|
||||||
|
.codeEditor
|
||||||
|
width 50%
|
||||||
|
.preview
|
||||||
|
width 50%
|
||||||
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
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
|
|
||||||
export default class ModeIcon extends React.Component {
|
|
||||||
getClassName () {
|
|
||||||
var mode = this.props.mode
|
|
||||||
switch (mode) {
|
|
||||||
// Script
|
|
||||||
case 'javascript':
|
|
||||||
return 'devicon-javascript-plain'
|
|
||||||
case 'jsx':
|
|
||||||
return 'devicon-react-original'
|
|
||||||
case 'coffee':
|
|
||||||
return 'devicon-coffeescript-original'
|
|
||||||
case 'ruby':
|
|
||||||
return 'devicon-ruby-plain'
|
|
||||||
case 'erlang':
|
|
||||||
return 'devicon-erlang-plain'
|
|
||||||
case 'php':
|
|
||||||
return 'devicon-php-plain'
|
|
||||||
|
|
||||||
// HTML
|
|
||||||
case 'html':
|
|
||||||
return 'devicon-html5-plain'
|
|
||||||
|
|
||||||
// Stylesheet
|
|
||||||
case 'css':
|
|
||||||
return 'devicon-css3-plain'
|
|
||||||
case 'less':
|
|
||||||
return 'devicon-less-plain-wordmark'
|
|
||||||
case 'sass':
|
|
||||||
case 'scss':
|
|
||||||
return 'devicon-sass-original'
|
|
||||||
|
|
||||||
// Compile
|
|
||||||
case 'c':
|
|
||||||
return 'devicon-c-plain'
|
|
||||||
case 'cpp':
|
|
||||||
return 'devicon-cplusplus-plain'
|
|
||||||
case 'csharp':
|
|
||||||
return 'devicon-csharp-plain'
|
|
||||||
case 'objc':
|
|
||||||
return 'devicon-apple-original'
|
|
||||||
case 'golang':
|
|
||||||
return 'devicon-go-plain'
|
|
||||||
case 'java':
|
|
||||||
return 'devicon-java-plain'
|
|
||||||
|
|
||||||
// Framework
|
|
||||||
case 'django':
|
|
||||||
return 'devicon-django-plain'
|
|
||||||
|
|
||||||
// Config
|
|
||||||
case 'dockerfile':
|
|
||||||
return 'devicon-docker-plain'
|
|
||||||
case 'gitignore':
|
|
||||||
return 'devicon-git-plain'
|
|
||||||
|
|
||||||
// Shell
|
|
||||||
case 'sh':
|
|
||||||
case 'batchfile':
|
|
||||||
case 'powershell':
|
|
||||||
return 'fa fa-fw fa-terminal'
|
|
||||||
|
|
||||||
case 'text':
|
|
||||||
case 'markdown':
|
|
||||||
return 'fa fa-fw fa-file-text-o'
|
|
||||||
}
|
|
||||||
return 'fa fa-fw fa-code'
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let className = `ModeIcon ${this.getClassName()} ${this.props.className}`
|
|
||||||
return (
|
|
||||||
<i className={className}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ModeIcon.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
mode: PropTypes.string
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import ModeIcon from './ModeIcon'
|
|
||||||
import modes from '../lib/modes'
|
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
const IDLE_MODE = 'IDLE_MODE'
|
|
||||||
const EDIT_MODE = 'EDIT_MODE'
|
|
||||||
|
|
||||||
export default class ModeSelect extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
mode: IDLE_MODE,
|
|
||||||
search: '',
|
|
||||||
focusIndex: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.blurHandler = e => {
|
|
||||||
let searchElement = ReactDOM.findDOMNode(this.refs.search)
|
|
||||||
if (this.state.mode === EDIT_MODE && document.activeElement !== searchElement) {
|
|
||||||
this.handleBlur(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
window.addEventListener('click', this.blurHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
window.removeEventListener('click', this.blurHandler)
|
|
||||||
let searchElement = ReactDOM.findDOMNode(this.refs.search)
|
|
||||||
if (searchElement != null && this.searchKeyDownListener != null) {
|
|
||||||
searchElement.removeEventListener('keydown', this.searchKeyDownListener)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleIdleSelectClick (e) {
|
|
||||||
this.setState({mode: EDIT_MODE, search: this.props.value}, () => {
|
|
||||||
ReactDOM.findDOMNode(this.refs.search).select()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleModeOptionClick (modeName) {
|
|
||||||
return e => {
|
|
||||||
this.props.onChange(modeName)
|
|
||||||
this.setState({
|
|
||||||
mode: IDLE_MODE,
|
|
||||||
search: '',
|
|
||||||
focusIndex: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearchKeyDown (e) {
|
|
||||||
switch (e.keyCode) {
|
|
||||||
// up
|
|
||||||
case 38:
|
|
||||||
e.preventDefault()
|
|
||||||
if (this.state.focusIndex > 0) this.setState({focusIndex: this.state.focusIndex - 1})
|
|
||||||
break
|
|
||||||
// down
|
|
||||||
case 40:
|
|
||||||
e.preventDefault()
|
|
||||||
{
|
|
||||||
let search = _.escapeRegExp(this.state.search)
|
|
||||||
let filteredModes = modes
|
|
||||||
.filter(mode => {
|
|
||||||
let nameMatched = mode.name.match(search)
|
|
||||||
let aliasMatched = _.some(mode.alias, alias => alias.match(search))
|
|
||||||
return nameMatched || aliasMatched
|
|
||||||
})
|
|
||||||
if (filteredModes.length === this.state.focusIndex + 1) this.setState({focusIndex: filteredModes.length - 1})
|
|
||||||
else this.setState({focusIndex: this.state.focusIndex + 1})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
// enter
|
|
||||||
case 13:
|
|
||||||
e.preventDefault()
|
|
||||||
{
|
|
||||||
let search = _.escapeRegExp(this.state.search)
|
|
||||||
let filteredModes = modes
|
|
||||||
.filter(mode => {
|
|
||||||
let nameMatched = mode.name.match(search)
|
|
||||||
let aliasMatched = _.some(mode.alias, alias => alias.match(search))
|
|
||||||
return nameMatched || aliasMatched
|
|
||||||
})
|
|
||||||
let targetMode = filteredModes[this.state.focusIndex]
|
|
||||||
if (targetMode != null) {
|
|
||||||
this.props.onChange(targetMode.name)
|
|
||||||
this.setIdle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
// esc
|
|
||||||
case 27:
|
|
||||||
case 9:
|
|
||||||
e.stopPropagation()
|
|
||||||
this.setIdle()
|
|
||||||
}
|
|
||||||
if (this.props.onKeyDown) this.props.onKeyDown(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearchChange (e) {
|
|
||||||
this.setState({
|
|
||||||
search: e.target.value,
|
|
||||||
focusIndex: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleBlur (e) {
|
|
||||||
if (e.target !== ReactDOM.findDOMNode(this.refs.search)) {
|
|
||||||
this.setIdle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setIdle () {
|
|
||||||
this.setState({
|
|
||||||
mode: IDLE_MODE,
|
|
||||||
search: '',
|
|
||||||
focusIndex: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let className = this.props.className != null
|
|
||||||
? `ModeSelect ${this.props.className}`
|
|
||||||
: this.props.className
|
|
||||||
|
|
||||||
if (this.state.mode === IDLE_MODE) {
|
|
||||||
let mode = _.findWhere(modes, {name: this.props.value})
|
|
||||||
let modeName = mode != null ? mode.name : 'text'
|
|
||||||
let modeLabel = mode != null ? mode.label : this.props.value
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className + ' idle'} onClick={e => this.handleIdleSelectClick(e)}>
|
|
||||||
<ModeIcon mode={modeName}/>{modeLabel}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let search = _.escapeRegExp(this.state.search)
|
|
||||||
let filteredOptions = modes
|
|
||||||
.filter(mode => {
|
|
||||||
let nameMatched = mode.name.match(search)
|
|
||||||
let aliasMatched = _.some(mode.alias, alias => alias.match(search))
|
|
||||||
return nameMatched || aliasMatched
|
|
||||||
})
|
|
||||||
.map((mode, index) => {
|
|
||||||
return (
|
|
||||||
<div key={mode.name} className={index === this.state.focusIndex ? 'ModeSelect-options-item active' : 'ModeSelect-options-item'} onClick={e => this.handleModeOptionClick(mode.name)(e)}><ModeIcon mode={mode.name}/>{mode.label}</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className + ' edit'}>
|
|
||||||
<input onBlur={e => this.handleBlur(e)} onKeyDown={e => this.handleSearchKeyDown(e)} ref='search' onChange={e => this.handleSearchChange(e)} value={this.state.search} type='text'/>
|
|
||||||
<div ref='options' className='ModeSelect-options hide'>
|
|
||||||
{filteredOptions}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ModeSelect.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
value: PropTypes.string,
|
|
||||||
onChange: PropTypes.func,
|
|
||||||
onKeyDown: PropTypes.func
|
|
||||||
}
|
|
||||||
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)
|
||||||
26
browser/components/NavToggleButton.styl
Normal file
26
browser/components/NavToggleButton.styl
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
.navToggle
|
||||||
|
navButtonColor()
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
left 5px
|
||||||
|
bottom 5px
|
||||||
|
border-radius 16.5px
|
||||||
|
height 34px
|
||||||
|
width 34px
|
||||||
|
line-height 32px
|
||||||
|
padding 0
|
||||||
|
&:hover
|
||||||
|
border: 1px solid #1EC38B;
|
||||||
|
background-color: alpha(#1EC38B, 30%)
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
113
browser/components/NoteItem.js
Normal file
113
browser/components/NoteItem.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Note item component.
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import { isArray } from 'lodash'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import { getTodoStatus } from 'browser/lib/getTodoStatus'
|
||||||
|
import styles from './NoteItem.styl'
|
||||||
|
import TodoProcess from './TodoProcess'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Tag element component.
|
||||||
|
* @param {string} tagName
|
||||||
|
* @return {React.Component}
|
||||||
|
*/
|
||||||
|
const TagElement = ({ tagName }) => (
|
||||||
|
<span styleName='item-bottom-tagList-item' key={tagName}>
|
||||||
|
#{tagName}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Tag element list component.
|
||||||
|
* @param {Array|null} tags
|
||||||
|
* @return {React.Component}
|
||||||
|
*/
|
||||||
|
const TagElementList = (tags) => {
|
||||||
|
if (!isArray(tags)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagElements = tags.map(tag => (
|
||||||
|
TagElement({tagName: tag})
|
||||||
|
))
|
||||||
|
|
||||||
|
return tagElements
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Note item component when using normal display mode.
|
||||||
|
* @param {boolean} isActive
|
||||||
|
* @param {Object} note
|
||||||
|
* @param {Function} handleNoteClick
|
||||||
|
* @param {Function} handleNoteContextMenu
|
||||||
|
* @param {Function} handleDragStart
|
||||||
|
* @param {string} dateDisplay
|
||||||
|
*/
|
||||||
|
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
|
||||||
|
<div styleName={isActive
|
||||||
|
? 'item--active'
|
||||||
|
: 'item'
|
||||||
|
}
|
||||||
|
key={`${note.storage}-${note.key}`}
|
||||||
|
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
||||||
|
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
||||||
|
onDragStart={e => handleDragStart(e, note)}
|
||||||
|
draggable='true'
|
||||||
|
>
|
||||||
|
<div styleName='item-wrapper'>
|
||||||
|
{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'>
|
||||||
|
{note.title.trim().length > 0
|
||||||
|
? note.title
|
||||||
|
: <span styleName='item-title-empty'>Empty</span>
|
||||||
|
}
|
||||||
|
</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-tagList'>
|
||||||
|
{note.tags.length > 0
|
||||||
|
? TagElementList(note.tags)
|
||||||
|
: <span styleName='item-bottom-tagList-empty' />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
NoteItem.propTypes = {
|
||||||
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
dateDisplay: PropTypes.string.isRequired,
|
||||||
|
note: PropTypes.shape({
|
||||||
|
storage: PropTypes.string.isRequired,
|
||||||
|
key: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string.isrequired,
|
||||||
|
tags: PropTypes.array,
|
||||||
|
isStarred: PropTypes.bool.isRequired,
|
||||||
|
isTrashed: PropTypes.bool.isRequired
|
||||||
|
}),
|
||||||
|
handleNoteClick: PropTypes.func.isRequired,
|
||||||
|
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||||
|
handleDragStart: PropTypes.func.isRequired,
|
||||||
|
handleDragEnd: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(NoteItem, styles)
|
||||||
307
browser/components/NoteItem.styl
Normal file
307
browser/components/NoteItem.styl
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
$control-height = 30px
|
||||||
|
|
||||||
|
.root
|
||||||
|
absolute left bottom
|
||||||
|
top $topBar-height - 1
|
||||||
|
background-color $ui-noteList-backgroundColor
|
||||||
|
|
||||||
|
.item
|
||||||
|
position relative
|
||||||
|
padding 0 20px
|
||||||
|
user-select none
|
||||||
|
cursor pointer
|
||||||
|
background-color $ui-noteList-backgroundColor
|
||||||
|
transition 0.2s
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
|
.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-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
|
||||||
|
padding 15px 0
|
||||||
|
border-bottom $ui-border
|
||||||
|
position relative
|
||||||
|
|
||||||
|
.item--active
|
||||||
|
@extend .item
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
color $ui-text-color
|
||||||
|
.item-title
|
||||||
|
.item-title-empty
|
||||||
|
.item-bottom-tagList-empty
|
||||||
|
.item-bottom-time
|
||||||
|
.item-title-icon
|
||||||
|
color $ui-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(white, 0.6)
|
||||||
|
color $ui-text-color
|
||||||
|
.item-wrapper
|
||||||
|
border-color transparent
|
||||||
|
.item-star
|
||||||
|
color $ui-favorite-star-button-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.item-title-icon
|
||||||
|
position relative
|
||||||
|
font-size 12px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
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
|
||||||
|
|
||||||
|
.item-title-empty
|
||||||
|
font-weight normal
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom
|
||||||
|
position relative
|
||||||
|
bottom 0px
|
||||||
|
margin-top 10px
|
||||||
|
font-size 12px
|
||||||
|
line-height 20px
|
||||||
|
overflow ellipsis
|
||||||
|
display flex
|
||||||
|
|
||||||
|
.item-bottom-tagList
|
||||||
|
flex 1
|
||||||
|
overflow ellipsis
|
||||||
|
line-height 25px
|
||||||
|
padding-left 2px
|
||||||
|
margin-right 40px
|
||||||
|
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
font-size 11px
|
||||||
|
margin-right 8px
|
||||||
|
padding 0
|
||||||
|
box-sizing border-box
|
||||||
|
border-radius 2px
|
||||||
|
padding 4px
|
||||||
|
vertical-align middle
|
||||||
|
background-color white
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-time
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
font-size 13px
|
||||||
|
padding-left 2px
|
||||||
|
padding-bottom 2px
|
||||||
|
|
||||||
|
.item-star
|
||||||
|
position absolute
|
||||||
|
right -6px
|
||||||
|
bottom 23px
|
||||||
|
width 16px
|
||||||
|
height 16px
|
||||||
|
color alpha($ui-favorite-star-button-color, 60%)
|
||||||
|
font-size 12px
|
||||||
|
padding 0
|
||||||
|
border-radius 17px
|
||||||
|
|
||||||
|
.item-pin
|
||||||
|
position absolute
|
||||||
|
right 0px
|
||||||
|
bottom 2px
|
||||||
|
width 34px
|
||||||
|
height 34px
|
||||||
|
color #E54D42
|
||||||
|
font-size 14px
|
||||||
|
padding 0
|
||||||
|
border-radius 17px
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.item
|
||||||
|
background-color $ui-white-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
&:active
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.item--active
|
||||||
|
@extend .item
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.item
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
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
|
||||||
|
border-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
|
.item--active
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
.item-wrapper
|
||||||
|
border-color transparent
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
|
.item-title
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title-icon
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-tagList-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
vertical-align middle
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.item
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
// background-color alpha($ui-solarized-dark-noteList-backgroundColor, 20%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha($ui-solarized-dark-noteList-backgroundColor, 20%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:active
|
||||||
|
transition 0.15s
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha($ui-solarized-dark-noteList-backgroundColor, 10%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.item-wrapper
|
||||||
|
border-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
|
.item--active
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
.item-wrapper
|
||||||
|
border-color transparent
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
// background-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
|
.item-title
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title-icon
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-tagList-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
vertical-align middle
|
||||||
58
browser/components/NoteItemSimple.js
Normal file
58
browser/components/NoteItemSimple.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Note item component with simple display mode.
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './NoteItemSimple.styl'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Note item component when using simple display mode.
|
||||||
|
* @param {boolean} isActive
|
||||||
|
* @param {Object} note
|
||||||
|
* @param {Function} handleNoteClick
|
||||||
|
* @param {Function} handleNoteContextMenu
|
||||||
|
* @param {Function} handleDragStart
|
||||||
|
*/
|
||||||
|
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
|
||||||
|
<div styleName={isActive
|
||||||
|
? 'item-simple--active'
|
||||||
|
: 'item-simple'
|
||||||
|
}
|
||||||
|
key={`${note.storage}-${note.key}`}
|
||||||
|
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
||||||
|
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
|
||||||
|
onDragStart={e => handleDragStart(e, note)}
|
||||||
|
draggable='true'
|
||||||
|
>
|
||||||
|
<div styleName='item-simple-title'>
|
||||||
|
{note.type === 'SNIPPET_NOTE'
|
||||||
|
? <i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
|
||||||
|
: <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' />
|
||||||
|
}
|
||||||
|
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
|
||||||
|
? <i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
{note.title.trim().length > 0
|
||||||
|
? note.title
|
||||||
|
: <span styleName='item-simple-title-empty'>Empty</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
NoteItemSimple.propTypes = {
|
||||||
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
note: PropTypes.shape({
|
||||||
|
storage: PropTypes.string.isRequired,
|
||||||
|
key: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
title: PropTypes.string.isrequired
|
||||||
|
}),
|
||||||
|
handleNoteClick: PropTypes.func.isRequired,
|
||||||
|
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||||
|
handleDragStart: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(NoteItemSimple, styles)
|
||||||
209
browser/components/NoteItemSimple.styl
Normal file
209
browser/components/NoteItemSimple.styl
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
$control-height = 30px
|
||||||
|
|
||||||
|
.root
|
||||||
|
absolute left bottom
|
||||||
|
top $topBar-height - 1
|
||||||
|
background-color $ui-noteList-backgroundColor
|
||||||
|
|
||||||
|
.item-simple
|
||||||
|
position relative
|
||||||
|
padding 0 20px
|
||||||
|
user-select none
|
||||||
|
cursor pointer
|
||||||
|
background-color $ui-noteList-backgroundColor
|
||||||
|
transition 0.2s
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
color $ui-text-color
|
||||||
|
&:active
|
||||||
|
background-color 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
|
||||||
|
@extend .item-simple
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
color $ui-text-color
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
border-color transparent
|
||||||
|
color $ui-text-color
|
||||||
|
.item-simple-title-icon
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.item-simple-title
|
||||||
|
font-size 13px
|
||||||
|
height 40px
|
||||||
|
padding-right 20px
|
||||||
|
box-sizing border-box
|
||||||
|
line-height 24px
|
||||||
|
padding-top 8px
|
||||||
|
overflow ellipsis
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
border-bottom $ui-border
|
||||||
|
position relative
|
||||||
|
|
||||||
|
.item-simple-title-icon
|
||||||
|
font-size 12px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding-right 6px
|
||||||
|
|
||||||
|
.item-simple-title-empty
|
||||||
|
font-weight normal
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-pin
|
||||||
|
position absolute
|
||||||
|
right 0px
|
||||||
|
top 12px
|
||||||
|
color #E54D42
|
||||||
|
font-size 14px
|
||||||
|
padding 0
|
||||||
|
border-radius 17px
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.item-simple
|
||||||
|
background-color $ui-white-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
&:active
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.item-simple--active
|
||||||
|
@extend .item-simple
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.item-simple
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
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
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
.item-simple-wrapper
|
||||||
|
border-color transparent
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
|
.item-simple-title
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
border-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
|
.item-simple-title-icon
|
||||||
|
color $ui-darkinactive-text-color
|
||||||
|
|
||||||
|
.item-simple-title-empty
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.item-simple
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
// background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:active
|
||||||
|
transition 0.15s
|
||||||
|
background-color $ui-solarized-dark-button--active-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.item-simple--active
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-button--active-backgroundColor
|
||||||
|
.item-simple-wrapper
|
||||||
|
border-color transparent
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
@@ -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)
|
||||||
43
browser/components/RealtimeNotification.styl
Normal file
43
browser/components/RealtimeNotification.styl
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
.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
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.notification-area
|
||||||
|
background-color none
|
||||||
|
|
||||||
|
.notification-link
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
border none
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color #5CB85C
|
||||||
79
browser/components/SideNavFilter.js
Normal file
79
browser/components/SideNavFilter.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Filter for all notes.
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './SideNavFilter.styl'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} isFolded
|
||||||
|
* @param {boolean} isHomeActive
|
||||||
|
* @param {Function} handleAllNotesButtonClick
|
||||||
|
* @param {boolean} isStarredActive
|
||||||
|
* @param {Function} handleStarredButtonClick
|
||||||
|
* @return {React.Component}
|
||||||
|
*/
|
||||||
|
const SideNavFilter = ({
|
||||||
|
isFolded, isHomeActive, handleAllNotesButtonClick,
|
||||||
|
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
|
||||||
|
counterTotalNote, counterStarredNote
|
||||||
|
}) => (
|
||||||
|
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
||||||
|
|
||||||
|
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
||||||
|
onClick={handleAllNotesButtonClick}
|
||||||
|
>
|
||||||
|
<div styleName='iconWrap'>
|
||||||
|
<img src={isHomeActive
|
||||||
|
? '../resources/icon/icon-all-active.svg'
|
||||||
|
: '../resources/icon/icon-all.svg'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span styleName='menu-button-label'>All Notes</span>
|
||||||
|
<span styleName='counters'>{counterTotalNote}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
|
||||||
|
onClick={handleStarredButtonClick}
|
||||||
|
>
|
||||||
|
<div styleName='iconWrap'>
|
||||||
|
<img src={isStarredActive
|
||||||
|
? '../resources/icon/icon-star-active.svg'
|
||||||
|
: '../resources/icon/icon-star-sidenav.svg'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span styleName='menu-button-label'>Starred</span>
|
||||||
|
<span styleName='counters'>{counterStarredNote}</span>
|
||||||
|
</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-sidenav.svg'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span styleName='menu-button-label'>Trash</span>
|
||||||
|
<span styleName='counters'>{counterDelNote}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
SideNavFilter.propTypes = {
|
||||||
|
isFolded: PropTypes.bool,
|
||||||
|
isHomeActive: PropTypes.bool.isRequired,
|
||||||
|
handleAllNotesButtonClick: PropTypes.func.isRequired,
|
||||||
|
isStarredActive: PropTypes.bool.isRequired,
|
||||||
|
isTrashedActive: PropTypes.bool.isRequired,
|
||||||
|
handleStarredButtonClick: PropTypes.func.isRequired,
|
||||||
|
handleTrashdButtonClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(SideNavFilter, styles)
|
||||||
228
browser/components/SideNavFilter.styl
Normal file
228
browser/components/SideNavFilter.styl
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
.menu
|
||||||
|
margin-bottom 30px
|
||||||
|
|
||||||
|
.menu-button
|
||||||
|
navButtonColor()
|
||||||
|
height 36px
|
||||||
|
padding 0 15px 0 20px
|
||||||
|
font-size 14px
|
||||||
|
width 100%
|
||||||
|
text-align left
|
||||||
|
overflow ellipsis
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
&:hover, &:active, &:active:hover
|
||||||
|
color #1EC38B
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
|
||||||
|
.iconWrap
|
||||||
|
width 20px
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
.counters
|
||||||
|
float right
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.menu-button--active
|
||||||
|
@extend .menu-button
|
||||||
|
SideNavFilter()
|
||||||
|
color #1EC38B
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
.menu-button-label, .counters
|
||||||
|
color #1EC38B
|
||||||
|
&:hover
|
||||||
|
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
|
||||||
|
margin-left 10px
|
||||||
|
flex 1
|
||||||
|
|
||||||
|
.menu--folded
|
||||||
|
@extend .menu
|
||||||
|
.menu-button, .menu-button--active, .menu-button-star--active, .menu-button-trash--active
|
||||||
|
text-align center
|
||||||
|
padding 0 12px
|
||||||
|
&:hover .menu-button-label
|
||||||
|
transition opacity 0.15s
|
||||||
|
opacity 1
|
||||||
|
color $ui-tooltip-text-color
|
||||||
|
background-color $ui-tooltip-backgroundColor
|
||||||
|
|
||||||
|
|
||||||
|
.menu-button-label
|
||||||
|
position fixed
|
||||||
|
display inline-block
|
||||||
|
height 32px
|
||||||
|
left 44px
|
||||||
|
padding 0 10px
|
||||||
|
margin-top -8px
|
||||||
|
margin-left 0
|
||||||
|
overflow ellipsis
|
||||||
|
z-index 10
|
||||||
|
line-height 32px
|
||||||
|
border-top-right-radius 2px
|
||||||
|
border-bottom-right-radius 2px
|
||||||
|
pointer-events none
|
||||||
|
opacity 0
|
||||||
|
font-size 13px
|
||||||
|
.counters
|
||||||
|
display none
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.menu-button
|
||||||
|
navWhiteButtonColor()
|
||||||
|
|
||||||
|
.counters
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.menu-button--active
|
||||||
|
@extend .menu-button
|
||||||
|
color #e74c3c
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.menu-button-star--active
|
||||||
|
@extend .menu-button
|
||||||
|
color #F9BF3B
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #F9BF3B
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #F9BF3B
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.menu-button-trash--active
|
||||||
|
@extend .menu-button
|
||||||
|
color #5D9E36
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #5D9E36
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #5D9E36
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.menu-button
|
||||||
|
&:active
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.menu-button
|
||||||
|
&:active
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.menu-button--active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.menu-button-star--active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.menu-button-trash--active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
145
browser/components/SnippetTab.js
Normal file
145
browser/components/SnippetTab.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './SnippetTab.styl'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
|
||||||
|
class SnippetTab extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isRenaming: false,
|
||||||
|
name: props.snippet.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUpdate (nextProps) {
|
||||||
|
if (nextProps.snippet.name !== this.props.snippet.name) {
|
||||||
|
this.setState({
|
||||||
|
name: nextProps.snippet.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick (e) {
|
||||||
|
this.props.onClick(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleContextMenu (e) {
|
||||||
|
context.popup([
|
||||||
|
{
|
||||||
|
label: 'Rename',
|
||||||
|
click: (e) => this.handleRenameClick(e)
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRenameClick (e) {
|
||||||
|
this.startRenaming()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNameInputBlur (e) {
|
||||||
|
this.handleRename()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNameInputChange (e) {
|
||||||
|
this.setState({
|
||||||
|
name: e.target.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNameInputKeyDown (e) {
|
||||||
|
switch (e.keyCode) {
|
||||||
|
case 13:
|
||||||
|
this.handleRename()
|
||||||
|
break
|
||||||
|
case 27:
|
||||||
|
this.setState({
|
||||||
|
name: this.props.snippet.name,
|
||||||
|
isRenaming: false
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRename () {
|
||||||
|
this.setState({
|
||||||
|
isRenaming: false
|
||||||
|
}, () => {
|
||||||
|
if (this.props.snippet.name !== this.state.name) {
|
||||||
|
this.props.onRename(this.state.name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteButtonClick (e) {
|
||||||
|
this.props.onDelete(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
startRenaming () {
|
||||||
|
this.setState({
|
||||||
|
isRenaming: true
|
||||||
|
}, () => {
|
||||||
|
this.refs.name.focus()
|
||||||
|
this.refs.name.select()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDragStart (e) {
|
||||||
|
e.dataTransfer.dropEffect = 'move'
|
||||||
|
this.props.onDragStart(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDrop (e) {
|
||||||
|
this.props.onDrop(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { isActive, snippet, isDeletable } = this.props
|
||||||
|
return (
|
||||||
|
<div styleName={isActive
|
||||||
|
? 'root--active'
|
||||||
|
: 'root'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{!this.state.isRenaming
|
||||||
|
? <button styleName='button'
|
||||||
|
onClick={(e) => this.handleClick(e)}
|
||||||
|
onDoubleClick={(e) => this.handleRenameClick(e)}
|
||||||
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
|
onDragStart={(e) => this.handleDragStart(e)}
|
||||||
|
onDrop={(e) => this.handleDrop(e)}
|
||||||
|
draggable='true'
|
||||||
|
>
|
||||||
|
{snippet.name.trim().length > 0
|
||||||
|
? snippet.name
|
||||||
|
: <span styleName='button-unnamed'>
|
||||||
|
Unnamed
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
: <input styleName='input'
|
||||||
|
ref='name'
|
||||||
|
value={this.state.name}
|
||||||
|
onChange={(e) => this.handleNameInputChange(e)}
|
||||||
|
onBlur={(e) => this.handleNameInputBlur(e)}
|
||||||
|
onKeyDown={(e) => this.handleNameInputKeyDown(e)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
{isDeletable &&
|
||||||
|
<button styleName='deleteButton'
|
||||||
|
onClick={(e) => this.handleDeleteButtonClick(e)}
|
||||||
|
>
|
||||||
|
<i className='fa fa-times' />
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SnippetTab.propTypes = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(SnippetTab, styles)
|
||||||
138
browser/components/SnippetTab.styl
Normal file
138
browser/components/SnippetTab.styl
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
.root
|
||||||
|
position relative
|
||||||
|
flex 1
|
||||||
|
overflow hidden
|
||||||
|
&:hover
|
||||||
|
.deleteButton
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
background-color darken($ui-backgroundColor, 15%)
|
||||||
|
&:active
|
||||||
|
color white
|
||||||
|
background-color $ui-active-color
|
||||||
|
|
||||||
|
.root--active
|
||||||
|
@extend .root
|
||||||
|
min-width 100px
|
||||||
|
border-bottom $ui-border
|
||||||
|
|
||||||
|
.button
|
||||||
|
width 100%
|
||||||
|
height 29px
|
||||||
|
overflow ellipsis
|
||||||
|
text-align left
|
||||||
|
padding-right 30px
|
||||||
|
border none
|
||||||
|
background-color transparent
|
||||||
|
transition 0.15s
|
||||||
|
border-left 4px solid transparent
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--hover-backgroundColor
|
||||||
|
|
||||||
|
.deleteButton
|
||||||
|
position absolute
|
||||||
|
top 5px
|
||||||
|
height 20px
|
||||||
|
right 5px
|
||||||
|
width 20px
|
||||||
|
text-align center
|
||||||
|
border none
|
||||||
|
padding 0
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
background-color transparent
|
||||||
|
border-radius 2px
|
||||||
|
|
||||||
|
.input
|
||||||
|
height 29px
|
||||||
|
border $ui-active-color
|
||||||
|
padding 0 5px
|
||||||
|
width 100%
|
||||||
|
outline none
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
color $ui-dark-text-color
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
.deleteButton
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
|
||||||
|
&:active
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.root--active
|
||||||
|
color $ui-dark-text-color
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
.deleteButton
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
|
||||||
|
&:active
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.button
|
||||||
|
border none
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color transparent
|
||||||
|
transition color background-color 0.15s
|
||||||
|
border-left 4px solid transparent
|
||||||
|
&:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
|
||||||
|
.input
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.deleteButton
|
||||||
|
color alpha($ui-dark-text-color, 30%)
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
.deleteButton
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
|
||||||
|
&:active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.root--active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
.deleteButton
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
|
||||||
|
&:active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.button
|
||||||
|
border none
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color transparent
|
||||||
|
transition color background-color 0.15s
|
||||||
|
border-left 4px solid transparent
|
||||||
|
&:hover
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.input
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.deleteButton
|
||||||
|
color alpha($ui-solarized-dark-text-color, 30%)
|
||||||
65
browser/components/StorageItem.js
Normal file
65
browser/components/StorageItem.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for showing storage.
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import styles from './StorageItem.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} isActive
|
||||||
|
* @param {Function} handleButtonClick
|
||||||
|
* @param {Function} handleContextMenu
|
||||||
|
* @param {string} folderName
|
||||||
|
* @param {string} folderColor
|
||||||
|
* @param {boolean} isFolded
|
||||||
|
* @param {number} noteCount
|
||||||
|
* @param {Function} handleDrop
|
||||||
|
* @param {Function} handleDragEnter
|
||||||
|
* @param {Function} handleDragOut
|
||||||
|
* @return {React.Component}
|
||||||
|
*/
|
||||||
|
const StorageItem = ({
|
||||||
|
isActive, handleButtonClick, handleContextMenu, folderName,
|
||||||
|
folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave
|
||||||
|
}) => (
|
||||||
|
<button styleName={isActive
|
||||||
|
? 'folderList-item--active'
|
||||||
|
: 'folderList-item'
|
||||||
|
}
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
onContextMenu={handleContextMenu}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
>
|
||||||
|
<span styleName={isFolded
|
||||||
|
? 'folderList-item-name--folded' : 'folderList-item-name'
|
||||||
|
}>
|
||||||
|
<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}
|
||||||
|
</span>
|
||||||
|
{(!isFolded && _.isNumber(noteCount)) &&
|
||||||
|
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
||||||
|
}
|
||||||
|
{isFolded &&
|
||||||
|
<span styleName='folderList-item-tooltip'>
|
||||||
|
{folderName}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
StorageItem.propTypes = {
|
||||||
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
handleButtonClick: PropTypes.func,
|
||||||
|
handleContextMenu: PropTypes.func,
|
||||||
|
folderName: PropTypes.string.isRequired,
|
||||||
|
folderColor: PropTypes.string,
|
||||||
|
isFolded: PropTypes.bool.isRequired,
|
||||||
|
handleDragEnter: PropTypes.func.isRequired,
|
||||||
|
handleDragLeave: PropTypes.func.isRequired,
|
||||||
|
noteCount: PropTypes.number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(StorageItem, styles)
|
||||||
130
browser/components/StorageItem.styl
Normal file
130
browser/components/StorageItem.styl
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
.root
|
||||||
|
width 100%
|
||||||
|
user-select none
|
||||||
|
|
||||||
|
.folderList-item
|
||||||
|
display flex
|
||||||
|
width 100%
|
||||||
|
height 34px
|
||||||
|
background-color transparent
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding 0
|
||||||
|
text-align left
|
||||||
|
border none
|
||||||
|
overflow ellipsis
|
||||||
|
font-size 14px
|
||||||
|
&:first-child
|
||||||
|
margin-top 0
|
||||||
|
&:hover
|
||||||
|
color #1EC38B;
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
transition background-color 0.15s
|
||||||
|
&:active
|
||||||
|
color $$ui-button-default-color
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
|
||||||
|
.folderList-item--active
|
||||||
|
@extend .folderList-item
|
||||||
|
color #1EC38B
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
&:hover
|
||||||
|
color #1EC38B;
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 50%)
|
||||||
|
|
||||||
|
.folderList-item-name
|
||||||
|
display block
|
||||||
|
flex 1
|
||||||
|
padding 0 12px
|
||||||
|
height 26px
|
||||||
|
line-height 26px
|
||||||
|
border-width 0 0 0 2px
|
||||||
|
border-style solid
|
||||||
|
border-color transparent
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
|
||||||
|
.folderList-item-noteCount
|
||||||
|
float right
|
||||||
|
line-height 26px
|
||||||
|
padding-right 15px
|
||||||
|
font-size 13px
|
||||||
|
|
||||||
|
.folderList-item-tooltip
|
||||||
|
tooltip()
|
||||||
|
position fixed
|
||||||
|
padding 0 10px
|
||||||
|
left 44px
|
||||||
|
z-index 10
|
||||||
|
pointer-events none
|
||||||
|
opacity 0
|
||||||
|
border-top-right-radius 2px
|
||||||
|
border-bottom-right-radius 2px
|
||||||
|
height 26px
|
||||||
|
line-height 26px
|
||||||
|
|
||||||
|
.folderList-item:hover, .folderList-item--active:hover
|
||||||
|
.folderList-item-tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.folderList-item-name--folded
|
||||||
|
@extend .folderList-item-name
|
||||||
|
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%)
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.folderList-item
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
|
||||||
|
.folderList-item--active
|
||||||
|
@extend .folderList-item
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
&:active
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
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%)
|
||||||
30
browser/components/TodoListPercentage.js
Normal file
30
browser/components/TodoListPercentage.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @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}%`}}>
|
||||||
|
<div styleName='progressBarInner'>
|
||||||
|
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
TodoListPercentage.propTypes = {
|
||||||
|
percentageOfTodo: PropTypes.number.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(TodoListPercentage, styles)
|
||||||
51
browser/components/TodoListPercentage.styl
Normal file
51
browser/components/TodoListPercentage.styl
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
.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)
|
||||||
|
|
||||||
|
.progressBarInner
|
||||||
|
padding 0 10px
|
||||||
|
min-width 1px
|
||||||
|
height 100%
|
||||||
|
display -webkit-box
|
||||||
|
display box
|
||||||
|
justify-content center
|
||||||
|
align-items center
|
||||||
|
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color #f4f4f4
|
||||||
|
font-weight 600
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.percentageBar
|
||||||
|
background-color #444444
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: #1EC38B
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.percentageBar
|
||||||
|
background-color #002b36
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: #2aa198
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color #fdf6e3
|
||||||
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%)
|
||||||
@@ -54,7 +54,7 @@ body
|
|||||||
font-family helvetica, arial, sans-serif
|
font-family helvetica, arial, sans-serif
|
||||||
line-height 1.6
|
line-height 1.6
|
||||||
overflow-x hidden
|
overflow-x hidden
|
||||||
user-select all
|
background-color $ui-noteDetail-backgroundColor
|
||||||
.katex
|
.katex
|
||||||
font 400 1.2em 'KaTeX_Main'
|
font 400 1.2em 'KaTeX_Main'
|
||||||
line-height 1.2em
|
line-height 1.2em
|
||||||
@@ -68,6 +68,18 @@ body
|
|||||||
padding 5px
|
padding 5px
|
||||||
margin -5px
|
margin -5px
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
|
.flowchart-error, .sequence-error
|
||||||
|
background-color errorBackgroundColor
|
||||||
|
color errorTextColor
|
||||||
|
padding 5px
|
||||||
|
border-radius 5px
|
||||||
|
justify-content left
|
||||||
|
li
|
||||||
|
label.taskListItem
|
||||||
|
margin-left -2em
|
||||||
|
&.checked
|
||||||
|
text-decoration line-through
|
||||||
|
opacity 0.5
|
||||||
div.math-rendered
|
div.math-rendered
|
||||||
text-align center
|
text-align center
|
||||||
.math-failed
|
.math-failed
|
||||||
@@ -102,20 +114,15 @@ a
|
|||||||
background-color alpha(#FFC95C, 0.3)
|
background-color alpha(#FFC95C, 0.3)
|
||||||
&:visited
|
&:visited
|
||||||
color brandColor
|
color brandColor
|
||||||
&.lineAnchor
|
|
||||||
padding 0
|
|
||||||
margin 0
|
|
||||||
display block
|
|
||||||
font-size 0
|
|
||||||
height 0
|
|
||||||
hr
|
hr
|
||||||
border-top none
|
border-top none
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
margin 15px 0
|
margin 15px 0
|
||||||
h1, h2, h3, h4, h5, h6
|
h1, h2, h3, h4, h5, h6
|
||||||
font-weight bold
|
font-weight bold
|
||||||
|
word-wrap break-word
|
||||||
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
|
||||||
@@ -147,13 +154,11 @@ h6
|
|||||||
line-height 1.4em
|
line-height 1.4em
|
||||||
margin 1em 0 1em
|
margin 1em 0 1em
|
||||||
color #777
|
color #777
|
||||||
|
|
||||||
*:not(a.lineAnchor) + p, *:not(a.lineAnchor) + blockquote, *:not(a.lineAnchor) + ul, *:not(a.lineAnchor) + ol, *:not(a.lineAnchor) + pre
|
|
||||||
margin-top 1em
|
|
||||||
p
|
p
|
||||||
line-height 1.6em
|
line-height 1.6em
|
||||||
margin 0 0 1em
|
margin 0 0 1em
|
||||||
white-space pre-line
|
white-space pre-line
|
||||||
|
word-wrap break-word
|
||||||
img
|
img
|
||||||
max-width 100%
|
max-width 100%
|
||||||
strong, b
|
strong, b
|
||||||
@@ -174,6 +179,8 @@ ul
|
|||||||
margin-bottom 1em
|
margin-bottom 1em
|
||||||
li
|
li
|
||||||
display list-item
|
display list-item
|
||||||
|
p
|
||||||
|
margin 0
|
||||||
&>li>ul, &>li>ol
|
&>li>ul, &>li>ol
|
||||||
margin 0
|
margin 0
|
||||||
&>li>ul
|
&>li>ul
|
||||||
@@ -186,43 +193,58 @@ ol
|
|||||||
margin-bottom 1em
|
margin-bottom 1em
|
||||||
li
|
li
|
||||||
display list-item
|
display list-item
|
||||||
|
p
|
||||||
|
margin 0
|
||||||
&>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
|
||||||
font-size 0.85em
|
font-size 1em
|
||||||
text-decoration none
|
text-decoration none
|
||||||
margin-right 2px
|
margin-right 2px
|
||||||
*:not(a.lineAnchor) + code
|
|
||||||
margin-left 2px
|
|
||||||
pre
|
pre
|
||||||
padding 0.5em !important
|
padding 0.5em !important
|
||||||
border solid 1px alpha(borderColor, 0.5)
|
border solid 1px #D1D1D1
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
overflow-x auto
|
overflow-x auto
|
||||||
margin 0 0 1em
|
margin 0 0 1em
|
||||||
line-height 1.35
|
display flex
|
||||||
|
line-height 1.4em
|
||||||
|
&.flowchart, &.sequence
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
background-color white
|
||||||
|
&.CodeMirror
|
||||||
|
height initial
|
||||||
|
&>code
|
||||||
|
flex 1
|
||||||
|
overflow-x auto
|
||||||
code
|
code
|
||||||
margin 0
|
|
||||||
background-color inherit
|
background-color inherit
|
||||||
|
margin 0
|
||||||
padding 0
|
padding 0
|
||||||
border none
|
border none
|
||||||
border-radius 0
|
border-radius 0
|
||||||
pre
|
|
||||||
border none
|
|
||||||
margin -5px
|
|
||||||
&>span.lineNumber
|
&>span.lineNumber
|
||||||
display none
|
display none
|
||||||
float left
|
font-size 1em
|
||||||
font-size 0.85em
|
padding 0.5em 0
|
||||||
margin 0 0.5em 0 -0.5em
|
margin -0.5em 0.5em -0.5em -0.5em
|
||||||
border-right 1px solid
|
border-right 1px solid
|
||||||
text-align right
|
text-align right
|
||||||
|
border-top-left-radius 4px
|
||||||
|
border-bottom-left-radius 4px
|
||||||
|
&.CodeMirror-gutters
|
||||||
|
position initial
|
||||||
|
top initial
|
||||||
|
left initial
|
||||||
|
min-height 0 !important
|
||||||
&>span
|
&>span
|
||||||
display block
|
display block
|
||||||
padding 0 .5em 0 1em
|
padding 0 .5em 0
|
||||||
table
|
table
|
||||||
display block
|
display block
|
||||||
width 100%
|
width 100%
|
||||||
@@ -236,6 +258,7 @@ table
|
|||||||
line-height 1.6
|
line-height 1.6
|
||||||
border-width 1px 0 2px 1px
|
border-width 1px 0 2px 1px
|
||||||
border-color borderColor
|
border-color borderColor
|
||||||
|
font-weight bold
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px borderColor
|
border-right solid 1px borderColor
|
||||||
tbody
|
tbody
|
||||||
@@ -251,3 +274,70 @@ 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%)
|
||||||
|
themeDarkText = #f9f9f9
|
||||||
|
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
||||||
|
themeDarkPreview = $ui-dark-noteDetail-backgroundColor
|
||||||
|
themeDarkTableOdd = themeDarkPreview
|
||||||
|
themeDarkTableEven = darken(themeDarkPreview, 10%)
|
||||||
|
themeDarkTableHead = themeDarkTableEven
|
||||||
|
themeDarkTableBorder = themeDarkBorder
|
||||||
|
themeDarkModalButtonDefault = themeDarkPreview
|
||||||
|
themeDarkModalButtonDanger = #BF360C
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
color themeDarkText
|
||||||
|
border-color themeDarkBorder
|
||||||
|
background-color themeDarkPreview
|
||||||
|
a:hover
|
||||||
|
background-color alpha(lighten(brandColor, 30%), 0.2) !important
|
||||||
|
|
||||||
|
code
|
||||||
|
color #EA6730
|
||||||
|
border-color darken(themeDarkBorder, 10%)
|
||||||
|
background-color lighten(themeDarkPreview, 5%)
|
||||||
|
|
||||||
|
pre
|
||||||
|
border-color lighten(#21252B, 20%)
|
||||||
|
code
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
label.taskListItem
|
||||||
|
background-color themeDarkPreview
|
||||||
|
table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
background-color themeDarkTableHead
|
||||||
|
th
|
||||||
|
border-color themeDarkTableBorder
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px themeDarkTableBorder
|
||||||
|
tbody
|
||||||
|
tr:nth-child(2n + 1)
|
||||||
|
background-color themeDarkTableOdd
|
||||||
|
tr:nth-child(2n)
|
||||||
|
background-color themeDarkTableEven
|
||||||
|
td
|
||||||
|
border-color themeDarkTableBorder
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px themeDarkTableBorder
|
||||||
|
kbd
|
||||||
|
background-color themeDarkBorder
|
||||||
|
color themeDarkText
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
border-color themeDarkBorder
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import CodeEditor from 'browser/components/CodeEditor'
|
|
||||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
|
||||||
import ModeIcon from 'browser/components/ModeIcon'
|
|
||||||
|
|
||||||
export default class FinderDetail extends React.Component {
|
|
||||||
render () {
|
|
||||||
let { activeArticle } = this.props
|
|
||||||
|
|
||||||
if (activeArticle != null) {
|
|
||||||
return (
|
|
||||||
<div className='FinderDetail'>
|
|
||||||
<div className='header'>
|
|
||||||
<div className='left'>
|
|
||||||
<ModeIcon mode={activeArticle.mode}/> {activeArticle.title}
|
|
||||||
</div>
|
|
||||||
<div className='right'>
|
|
||||||
<button onClick={this.props.saveToClipboard} className='clipboardBtn'>
|
|
||||||
<i className='fa fa-clipboard fa-fw'/>
|
|
||||||
<span className='tooltip'>Copy to clipboard (Enter)</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='content'>
|
|
||||||
{activeArticle.mode === 'markdown'
|
|
||||||
? <MarkdownPreview content={activeArticle.content}/>
|
|
||||||
: <CodeEditor readOnly article={activeArticle}/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className='FinderDetail'>
|
|
||||||
<div className='nothing'>Nothing selected</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderDetail.propTypes = {
|
|
||||||
activeArticle: PropTypes.shape(),
|
|
||||||
saveToClipboard: PropTypes.func
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
|
|
||||||
export default class FinderInput extends React.Component {
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className='FinderInput'>
|
|
||||||
<input ref='input' value={this.props.value} onChange={this.props.handleSearchChange} type='text'/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderInput.propTypes = {
|
|
||||||
handleSearchChange: PropTypes.func,
|
|
||||||
value: PropTypes.string
|
|
||||||
}
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import ModeIcon from 'browser/components/ModeIcon'
|
|
||||||
import { selectArticle } from './actions'
|
|
||||||
|
|
||||||
export default class FinderList extends React.Component {
|
|
||||||
componentDidUpdate () {
|
|
||||||
var index = this.props.articles.indexOf(this.props.activeArticle)
|
|
||||||
var el = ReactDOM.findDOMNode(this)
|
|
||||||
var li = el.querySelectorAll('li')[index]
|
|
||||||
|
|
||||||
if (li == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var overflowBelow = el.clientHeight + el.scrollTop < li.offsetTop + li.clientHeight
|
|
||||||
if (overflowBelow) {
|
|
||||||
el.scrollTop = li.offsetTop + li.clientHeight - el.clientHeight
|
|
||||||
}
|
|
||||||
var overflowAbove = el.scrollTop > li.offsetTop
|
|
||||||
if (overflowAbove) {
|
|
||||||
el.scrollTop = li.offsetTop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleArticleClick (article) {
|
|
||||||
return (e) => {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
dispatch(selectArticle(article.key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let articleElements = this.props.articles.map(function (article) {
|
|
||||||
if (article == null) {
|
|
||||||
return (
|
|
||||||
<li className={isActive ? 'active' : ''}>
|
|
||||||
<div className='articleItem'>Undefined</div>
|
|
||||||
<div className='divider'/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var isActive = this.props.activeArticle != null && (article.key === this.props.activeArticle.key)
|
|
||||||
return (
|
|
||||||
<li key={'article-' + article.key} onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
|
|
||||||
<div className='articleItem'>
|
|
||||||
<ModeIcon mode={article.mode}/> {article.title}</div>
|
|
||||||
<div className='divider'/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}.bind(this))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='FinderList'>
|
|
||||||
<ul>
|
|
||||||
{articleElements}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderList.propTypes = {
|
|
||||||
articles: PropTypes.array,
|
|
||||||
activeArticle: PropTypes.shape({
|
|
||||||
type: PropTypes.string,
|
|
||||||
key: PropTypes.string
|
|
||||||
}),
|
|
||||||
dispatch: PropTypes.func
|
|
||||||
}
|
|
||||||
156
browser/finder/FinderMain.styl
Normal file
156
browser/finder/FinderMain.styl
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
$search-height = 50px
|
||||||
|
$nav-width = 175px
|
||||||
|
$list-width = 250px
|
||||||
|
|
||||||
|
.root
|
||||||
|
absolute top left right bottom
|
||||||
|
|
||||||
|
.search
|
||||||
|
height $search-height
|
||||||
|
padding 10px
|
||||||
|
box-sizing border-box
|
||||||
|
border-bottom $ui-border
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
.search-input
|
||||||
|
height 30px
|
||||||
|
width 100%
|
||||||
|
margin 0 auto
|
||||||
|
font-size 18px
|
||||||
|
border none
|
||||||
|
outline none
|
||||||
|
text-align center
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.result
|
||||||
|
absolute left right bottom
|
||||||
|
top $search-height
|
||||||
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.result-nav
|
||||||
|
user-select none
|
||||||
|
absolute left top bottom
|
||||||
|
width $nav-width
|
||||||
|
background-color $ui-backgroundColor
|
||||||
|
|
||||||
|
.result-nav-filter
|
||||||
|
margin-bottom 10px
|
||||||
|
|
||||||
|
.result-nav-filter-option
|
||||||
|
height 25px
|
||||||
|
line-height 25px
|
||||||
|
padding 0 10px
|
||||||
|
label
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
.result-nav-menu
|
||||||
|
navButtonColor()
|
||||||
|
height 32px
|
||||||
|
padding 0 10px
|
||||||
|
font-size 14px
|
||||||
|
width 100%
|
||||||
|
outline none
|
||||||
|
text-align left
|
||||||
|
line-height 32px
|
||||||
|
box-sizing border-box
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
.result-nav-menu--active
|
||||||
|
@extend .result-nav-menu
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.result-nav-storageList
|
||||||
|
absolute bottom left right
|
||||||
|
top 110px + 32px + 10px + 10px + 20px
|
||||||
|
overflow-y auto
|
||||||
|
|
||||||
|
.result-list
|
||||||
|
user-select none
|
||||||
|
absolute top bottom
|
||||||
|
left $nav-width
|
||||||
|
width $list-width
|
||||||
|
box-sizing border-box
|
||||||
|
overflow-y auto
|
||||||
|
box-shadow 2px 0 15px -8px #b1b1b1
|
||||||
|
z-index 1
|
||||||
|
|
||||||
|
.result-detail
|
||||||
|
absolute top bottom right
|
||||||
|
left $nav-width + $list-width
|
||||||
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
background-color $ui-dark-backgroundColor
|
||||||
|
.search
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
.search-input
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.result
|
||||||
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.result-nav
|
||||||
|
background-color $ui-dark-backgroundColor
|
||||||
|
label
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.result-nav-menu
|
||||||
|
navDarkButtonColor()
|
||||||
|
|
||||||
|
.result-nav-menu--active
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
color $ui-dark-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.result-list
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
box-shadow none
|
||||||
|
top 0
|
||||||
|
|
||||||
|
.result-detail
|
||||||
|
absolute top bottom right
|
||||||
|
left $nav-width + $list-width
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
|
.search
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
.search-input
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.result
|
||||||
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
|
|
||||||
|
.result-nav
|
||||||
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
|
label
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.result-nav-menu
|
||||||
|
navDarkButtonColor()
|
||||||
|
|
||||||
|
.result-nav-menu--active
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-dark-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.result-list
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
box-shadow none
|
||||||
|
top 0
|
||||||
|
|
||||||
|
.result-detail
|
||||||
|
absolute top bottom right
|
||||||
|
left $nav-width + $list-width
|
||||||
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
|
|
||||||
210
browser/finder/NoteDetail.js
Normal file
210
browser/finder/NoteDetail.js
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './NoteDetail.styl'
|
||||||
|
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||||
|
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||||
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
|
import CodeMirror from 'codemirror'
|
||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const { clipboard } = electron
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
|
function pass (name) {
|
||||||
|
switch (name) {
|
||||||
|
case 'ejs':
|
||||||
|
return 'Embedded Javascript'
|
||||||
|
case 'html_ruby':
|
||||||
|
return 'Embedded Ruby'
|
||||||
|
case 'objectivec':
|
||||||
|
return 'Objective C'
|
||||||
|
case 'text':
|
||||||
|
return 'Plain Text'
|
||||||
|
default:
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function notify (title, options) {
|
||||||
|
if (global.process.platform === 'win32') {
|
||||||
|
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
||||||
|
}
|
||||||
|
return new window.Notification(title, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoteDetail extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
snippetIndex: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (nextProps.note !== this.props.note) {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: 0
|
||||||
|
}, () => {
|
||||||
|
if (nextProps.note.type === 'SNIPPET_NOTE') {
|
||||||
|
nextProps.note.snippets.forEach((snippet, index) => {
|
||||||
|
this.refs['code-' + index].reload()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectPriorSnippet () {
|
||||||
|
const { note } = this.props
|
||||||
|
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: (this.state.snippetIndex + note.snippets.length - 1) % note.snippets.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectNextSnippet () {
|
||||||
|
const { note } = this.props
|
||||||
|
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: (this.state.snippetIndex + 1) % note.snippets.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveToClipboard () {
|
||||||
|
const { note } = this.props
|
||||||
|
|
||||||
|
if (note.type === 'MARKDOWN_NOTE') {
|
||||||
|
clipboard.writeText(note.content)
|
||||||
|
} else {
|
||||||
|
clipboard.writeText(note.snippets[this.state.snippetIndex].content)
|
||||||
|
}
|
||||||
|
|
||||||
|
notify('Saved to Clipboard!', {
|
||||||
|
body: 'Paste it wherever you want!',
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTabButtonClick (e, index) {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: index
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { note, config } = this.props
|
||||||
|
if (note == null) {
|
||||||
|
return (
|
||||||
|
<div styleName='root' />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
|
||||||
|
const storage = findStorage(note.storage)
|
||||||
|
|
||||||
|
if (note.type === 'SNIPPET_NOTE') {
|
||||||
|
const tabList = note.snippets.map((snippet, index) => {
|
||||||
|
const isActive = this.state.snippetIndex === index
|
||||||
|
return <div styleName={isActive
|
||||||
|
? 'tabList-item--active'
|
||||||
|
: 'tabList-item'
|
||||||
|
}
|
||||||
|
key={index}
|
||||||
|
>
|
||||||
|
<button styleName='tabList-item-button'
|
||||||
|
onClick={(e) => this.handleTabButtonClick(e, index)}
|
||||||
|
>
|
||||||
|
{snippet.name.trim().length > 0
|
||||||
|
? snippet.name
|
||||||
|
: <span styleName='tabList-item-unnamed'>
|
||||||
|
Unnamed
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
|
||||||
|
const viewList = note.snippets.map((snippet, index) => {
|
||||||
|
const isActive = this.state.snippetIndex === index
|
||||||
|
|
||||||
|
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
||||||
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
|
return <div styleName='tabView'
|
||||||
|
key={index}
|
||||||
|
style={{zIndex: isActive ? 5 : 4}}
|
||||||
|
>
|
||||||
|
{snippet.mode === 'markdown'
|
||||||
|
? <MarkdownEditor styleName='tabView-content'
|
||||||
|
config={config}
|
||||||
|
value={snippet.content}
|
||||||
|
ref={'code-' + index}
|
||||||
|
storageKey={note.storage}
|
||||||
|
/>
|
||||||
|
: <CodeEditor styleName='tabView-content'
|
||||||
|
mode={snippet.mode}
|
||||||
|
value={snippet.content}
|
||||||
|
theme={config.editor.theme}
|
||||||
|
fontFamily={config.editor.fontFamily}
|
||||||
|
fontSize={editorFontSize}
|
||||||
|
indentType={config.editor.indentType}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
|
readOnly
|
||||||
|
ref={'code-' + index}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='description'>
|
||||||
|
<textarea styleName='description-textarea'
|
||||||
|
style={{
|
||||||
|
fontFamily: config.preview.fontFamily,
|
||||||
|
fontSize: parseInt(config.preview.fontSize, 10)
|
||||||
|
}}
|
||||||
|
ref='description'
|
||||||
|
placeholder='Description...'
|
||||||
|
value={note.description}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div styleName='tabList'>
|
||||||
|
{tabList}
|
||||||
|
</div>
|
||||||
|
{viewList}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MarkdownPreview styleName='root'
|
||||||
|
theme={config.ui.theme}
|
||||||
|
fontSize={config.preview.fontSize}
|
||||||
|
fontFamily={config.preview.fontFamily}
|
||||||
|
codeBlockTheme={config.preview.codeBlockTheme}
|
||||||
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
|
lineNumber={config.preview.lineNumber}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
|
value={note.content}
|
||||||
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
|
storagePath={storage.path}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteDetail.propTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(NoteDetail, styles)
|
||||||
129
browser/finder/NoteDetail.styl
Normal file
129
browser/finder/NoteDetail.styl
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
@import('../main/Detail/DetailVars.styl')
|
||||||
|
|
||||||
|
.root
|
||||||
|
absolute top bottom left right
|
||||||
|
bottom 30px
|
||||||
|
margin 0 25px
|
||||||
|
height 100%
|
||||||
|
width 365px
|
||||||
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.description
|
||||||
|
absolute top left right
|
||||||
|
height 80px
|
||||||
|
box-sizing border-box
|
||||||
|
|
||||||
|
.description-textarea
|
||||||
|
display block
|
||||||
|
height 100%
|
||||||
|
width 100%
|
||||||
|
resize none
|
||||||
|
border none
|
||||||
|
padding 10px
|
||||||
|
line-height 1.6
|
||||||
|
box-sizing border-box
|
||||||
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.tabList
|
||||||
|
absolute left right
|
||||||
|
top 80px
|
||||||
|
box-sizing border-box
|
||||||
|
height 30px
|
||||||
|
display flex
|
||||||
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.tabList-item
|
||||||
|
position relative
|
||||||
|
flex 1
|
||||||
|
overflow hidden
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--hover-backgroundColorg
|
||||||
|
|
||||||
|
.tabList-item--active
|
||||||
|
@extend .tabList-item
|
||||||
|
border-bottom $ui-border
|
||||||
|
|
||||||
|
.tabList-item-button
|
||||||
|
width 100%
|
||||||
|
height 29px
|
||||||
|
overflow ellipsis
|
||||||
|
text-align left
|
||||||
|
padding-right 30px
|
||||||
|
padding-left 10px
|
||||||
|
border none
|
||||||
|
background-color transparent
|
||||||
|
transition 0.15s
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--hover-backgroundColor
|
||||||
|
|
||||||
|
.tabView
|
||||||
|
absolute left right bottom
|
||||||
|
top 130px
|
||||||
|
|
||||||
|
.tabView-content
|
||||||
|
absolute top left right bottom
|
||||||
|
box-sizing border-box
|
||||||
|
height 100%
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.description
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.description-textarea
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
color white
|
||||||
|
|
||||||
|
.tabList
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.tabList-item
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
|
||||||
|
.tabList-item-button
|
||||||
|
border none
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color transparent
|
||||||
|
transition color background-color 0.15s
|
||||||
|
border-left 4px solid transparent
|
||||||
|
&:hover
|
||||||
|
color white
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
|
|
||||||
|
.description
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
|
|
||||||
|
.description-textarea
|
||||||
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
|
color white
|
||||||
|
|
||||||
|
.tabList
|
||||||
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
|
|
||||||
|
.tabList-item
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
|
||||||
|
.tabList-item-button
|
||||||
|
border none
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color transparent
|
||||||
|
transition color background-color 0.15s
|
||||||
|
border-left 4px solid transparent
|
||||||
|
&:hover
|
||||||
|
color white
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
|
||||||
90
browser/finder/NoteList.js
Normal file
90
browser/finder/NoteList.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import NoteItem from 'browser/components/NoteItem'
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
class NoteList extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
range: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (this.props.search !== nextProps.search) {
|
||||||
|
this.resetScroll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate () {
|
||||||
|
const { index } = this.props
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
const list = this.refs.root
|
||||||
|
const item = list.childNodes[index]
|
||||||
|
if (item == null) return null
|
||||||
|
|
||||||
|
const overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
||||||
|
if (overflowBelow) {
|
||||||
|
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
|
||||||
|
}
|
||||||
|
const overflowAbove = list.scrollTop > item.offsetTop
|
||||||
|
if (overflowAbove) {
|
||||||
|
list.scrollTop = item.offsetTop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetScroll () {
|
||||||
|
this.refs.root.scrollTop = 0
|
||||||
|
this.setState({
|
||||||
|
range: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll (e) {
|
||||||
|
const { notes } = this.props
|
||||||
|
|
||||||
|
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 100 && notes.length > this.state.range * 10 + 10) {
|
||||||
|
this.setState({
|
||||||
|
range: this.state.range + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { notes, index } = this.props
|
||||||
|
|
||||||
|
const notesList = notes
|
||||||
|
.slice(0, 10 + 10 * this.state.range)
|
||||||
|
.map((note, _index) => {
|
||||||
|
const isActive = (index === _index)
|
||||||
|
const key = `${note.storage}-${note.key}`
|
||||||
|
const dateDisplay = moment(note.updatedAt).fromNow()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NoteItem
|
||||||
|
isActive={isActive}
|
||||||
|
note={note}
|
||||||
|
dateDisplay={dateDisplay}
|
||||||
|
key={key}
|
||||||
|
handleNoteClick={(e) => this.props.handleNoteClick(e, _index)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div className={this.props.className}
|
||||||
|
onScroll={(e) => this.handleScroll(e)}
|
||||||
|
ref='root'
|
||||||
|
>
|
||||||
|
{notesList}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteList.propTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NoteList
|
||||||
77
browser/finder/StorageSection.js
Normal file
77
browser/finder/StorageSection.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './StorageSection.styl'
|
||||||
|
import StorageItem from 'browser/components/StorageItem'
|
||||||
|
|
||||||
|
class StorageSection extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isOpen: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToggleButtonClick (e) {
|
||||||
|
this.setState({
|
||||||
|
isOpen: !this.state.isOpen
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHeaderClick (e) {
|
||||||
|
const { storage } = this.props
|
||||||
|
this.props.handleStorageButtonClick(e, storage.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFolderClick (e, folder) {
|
||||||
|
const { storage } = this.props
|
||||||
|
this.props.handleFolderButtonClick(e, storage.key, folder.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { storage, filter } = this.props
|
||||||
|
const folderList = storage.folders
|
||||||
|
.map(folder => (
|
||||||
|
<StorageItem
|
||||||
|
key={folder.key}
|
||||||
|
isActive={filter.type === 'FOLDER' && filter.folder === folder.key && filter.storage === storage.key}
|
||||||
|
handleButtonClick={(e) => this.handleFolderClick(e, folder)}
|
||||||
|
folderName={folder.name}
|
||||||
|
folderColor={folder.color}
|
||||||
|
isFolded={false}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='header'>
|
||||||
|
<button styleName='header-toggleButton'
|
||||||
|
onClick={(e) => this.handleToggleButtonClick(e)}
|
||||||
|
>
|
||||||
|
<i className={this.state.isOpen
|
||||||
|
? 'fa fa-caret-down'
|
||||||
|
: 'fa fa-caret-right'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<button styleName={filter.type === 'STORAGE' && filter.storage === storage.key
|
||||||
|
? 'header-name--active'
|
||||||
|
: 'header-name'
|
||||||
|
}
|
||||||
|
onClick={(e) => this.handleHeaderClick(e)}
|
||||||
|
>{storage.name}</button>
|
||||||
|
</div>
|
||||||
|
{this.state.isOpen &&
|
||||||
|
<div styleName='folderList'>
|
||||||
|
{folderList}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StorageSection.propTypes = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(StorageSection, styles)
|
||||||
85
browser/finder/StorageSection.styl
Normal file
85
browser/finder/StorageSection.styl
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
.root
|
||||||
|
position relative
|
||||||
|
|
||||||
|
.header
|
||||||
|
height 26px
|
||||||
|
.header-toggleButton
|
||||||
|
absolute top left
|
||||||
|
width 25px
|
||||||
|
height 26px
|
||||||
|
navButtonColor()
|
||||||
|
border none
|
||||||
|
outline none
|
||||||
|
.header-name
|
||||||
|
display block
|
||||||
|
height 26px
|
||||||
|
navButtonColor()
|
||||||
|
padding 0 10px 0 25px
|
||||||
|
font-size 14px
|
||||||
|
width 100%
|
||||||
|
text-align left
|
||||||
|
line-height 26px
|
||||||
|
box-sizing border-box
|
||||||
|
cursor pointer
|
||||||
|
outline none
|
||||||
|
|
||||||
|
.header-name--active
|
||||||
|
@extend .header-name
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.folderList-item
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
height 26px
|
||||||
|
navButtonColor()
|
||||||
|
padding 0 10px 0 25px
|
||||||
|
font-size 14px
|
||||||
|
width 100%
|
||||||
|
text-align left
|
||||||
|
line-height 26px
|
||||||
|
box-sizing border-box
|
||||||
|
cursor pointer
|
||||||
|
outline none
|
||||||
|
padding 0 10px
|
||||||
|
margin 2px 0
|
||||||
|
height 26px
|
||||||
|
line-height 26px
|
||||||
|
border-width 0 0 0 6px
|
||||||
|
border-style solid
|
||||||
|
border-color transparent
|
||||||
|
|
||||||
|
.folderList-item--active
|
||||||
|
@extend .folderList-item
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.header-toggleButton
|
||||||
|
navDarkButtonColor()
|
||||||
|
.header-name
|
||||||
|
navDarkButtonColor()
|
||||||
|
|
||||||
|
.header-name--active
|
||||||
|
@extend .header-name
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.folderList-item
|
||||||
|
navDarkButtonColor()
|
||||||
|
border-width 0 0 0 6px
|
||||||
|
border-style solid
|
||||||
|
border-color transparent
|
||||||
|
|
||||||
|
.folderList-item--active
|
||||||
|
@extend .folderList-item
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
export const SELECT_ARTICLE = 'SELECT_ARTICLE'
|
|
||||||
export const SEARCH_ARTICLE = 'SEARCH_ARTICLE'
|
|
||||||
export const REFRESH_DATA = 'REFRESH_DATA'
|
|
||||||
|
|
||||||
export function selectArticle (key) {
|
|
||||||
return {
|
|
||||||
type: SELECT_ARTICLE,
|
|
||||||
data: { key }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function searchArticle (input) {
|
|
||||||
return {
|
|
||||||
type: SEARCH_ARTICLE,
|
|
||||||
data: { input }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function refreshData (data) {
|
|
||||||
console.log('refreshing data')
|
|
||||||
let { folders, articles } = data
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: REFRESH_DATA,
|
|
||||||
data: {
|
|
||||||
articles,
|
|
||||||
folders
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
SELECT_ARTICLE,
|
|
||||||
SEARCH_ARTICLE,
|
|
||||||
REFRESH_DATA,
|
|
||||||
selectArticle,
|
|
||||||
searchArticle,
|
|
||||||
refreshData
|
|
||||||
}
|
|
||||||
@@ -1,85 +1,89 @@
|
|||||||
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 reducer from './reducer'
|
|
||||||
import { createStore } from 'redux'
|
|
||||||
import FinderInput from './FinderInput'
|
|
||||||
import FinderList from './FinderList'
|
|
||||||
import FinderDetail from './FinderDetail'
|
|
||||||
import actions, { selectArticle, searchArticle } from './actions'
|
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import dataStore from 'browser/lib/dataStore'
|
import store from './store'
|
||||||
import fetchConfig from '../lib/fetchConfig'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './FinderMain.styl'
|
||||||
|
import StorageSection from './StorageSection'
|
||||||
|
import NoteList from './NoteList'
|
||||||
|
import NoteDetail from './NoteDetail'
|
||||||
|
import SideNavFilter from 'browser/components/SideNavFilter'
|
||||||
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
require('!!style!css!stylus?sourceMap!../main/global.styl')
|
||||||
|
require('../lib/customMeta')
|
||||||
|
require('./ipcClient.js')
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { clipboard, ipcRenderer, remote } = electron
|
const { remote } = electron
|
||||||
const path = require('path')
|
const { Menu } = remote
|
||||||
|
|
||||||
let config = fetchConfig()
|
|
||||||
applyConfig(config)
|
|
||||||
|
|
||||||
ipcRenderer.on('config-apply', function (e, newConfig) {
|
|
||||||
config = newConfig
|
|
||||||
applyConfig(config)
|
|
||||||
})
|
|
||||||
|
|
||||||
function applyConfig () {
|
|
||||||
let body = document.body
|
|
||||||
body.setAttribute('data-theme', config['theme-ui'])
|
|
||||||
|
|
||||||
let hljsCss = document.getElementById('hljs-css')
|
|
||||||
hljsCss.setAttribute('href', '../node_modules/highlight.js/styles/' + config['theme-code'] + '.css')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
window.addEventListener('keydown', function (e) {
|
|
||||||
if (e.keyCode === 73 && e.metaKey && e.altKey) {
|
|
||||||
remote.getCurrentWindow().toggleDevTools()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideFinder () {
|
function hideFinder () {
|
||||||
ipcRenderer.send('hide-finder')
|
const finderWindow = remote.getCurrentWindow()
|
||||||
|
if (global.process.platform === 'win32') {
|
||||||
|
finderWindow.blur()
|
||||||
|
finderWindow.hide()
|
||||||
}
|
}
|
||||||
|
if (global.process.platform === 'darwin') {
|
||||||
function notify (title, options) {
|
Menu.sendActionToFirstResponder('hide:')
|
||||||
if (process.platform === 'win32') {
|
|
||||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
|
||||||
}
|
}
|
||||||
return new window.Notification(title, options)
|
remote.getCurrentWindow().hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
require('!!style!css!stylus?sourceMap!../styles/finder/index.styl')
|
require('!!style!css!stylus?sourceMap!../styles/finder/index.styl')
|
||||||
|
|
||||||
const FOLDER_FILTER = 'FOLDER_FILTER'
|
|
||||||
const FOLDER_EXACT_FILTER = 'FOLDER_EXACT_FILTER'
|
|
||||||
const TEXT_FILTER = 'TEXT_FILTER'
|
|
||||||
const TAG_FILTER = 'TAG_FILTER'
|
|
||||||
|
|
||||||
class FinderMain extends React.Component {
|
class FinderMain extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
search: '',
|
||||||
|
index: 0,
|
||||||
|
filter: {
|
||||||
|
includeSnippet: true,
|
||||||
|
includeMarkdown: false,
|
||||||
|
type: 'ALL',
|
||||||
|
storage: null,
|
||||||
|
folder: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.focusHandler = (e) => this.handleWindowFocus(e)
|
||||||
|
this.blurHandler = (e) => this.handleWindowBlur(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.keyDownHandler = e => this.handleKeyDown(e)
|
this.refs.search.focus()
|
||||||
document.addEventListener('keydown', this.keyDownHandler)
|
|
||||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
|
||||||
this.focusHandler = e => {
|
|
||||||
let { dispatch } = this.props
|
|
||||||
|
|
||||||
dispatch(searchArticle(''))
|
|
||||||
dispatch(selectArticle(null))
|
|
||||||
}
|
|
||||||
window.addEventListener('focus', this.focusHandler)
|
window.addEventListener('focus', this.focusHandler)
|
||||||
|
window.addEventListener('blur', this.blurHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
document.removeEventListener('keydown', this.keyDownHandler)
|
|
||||||
window.removeEventListener('focus', this.focusHandler)
|
window.removeEventListener('focus', this.focusHandler)
|
||||||
|
window.removeEventListener('blur', this.blurHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWindowFocus (e) {
|
||||||
|
this.refs.search.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWindowBlur (e) {
|
||||||
|
this.setState({
|
||||||
|
search: ''
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown (e) {
|
||||||
|
this.refs.search.focus()
|
||||||
|
if (e.keyCode === 9) {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
this.refs.detail.selectPriorSnippet()
|
||||||
|
} else {
|
||||||
|
this.refs.detail.selectNextSnippet()
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
if (e.keyCode === 38) {
|
if (e.keyCode === 38) {
|
||||||
this.selectPrevious()
|
this.selectPrevious()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -91,7 +95,9 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
this.saveToClipboard()
|
this.refs.detail.saveToClipboard()
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('COPY_FINDER')
|
||||||
|
hideFinder()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
if (e.keyCode === 27) {
|
if (e.keyCode === 27) {
|
||||||
@@ -101,26 +107,13 @@ class FinderMain extends React.Component {
|
|||||||
if (e.keyCode === 91 || e.metaKey) {
|
if (e.keyCode === 91 || e.metaKey) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.findDOMNode(this.refs.finderInput.refs.input).focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToClipboard () {
|
|
||||||
let { activeArticle } = this.props
|
|
||||||
clipboard.writeText(activeArticle.content)
|
|
||||||
|
|
||||||
ipcRenderer.send('copy-finder')
|
|
||||||
notify('Saved to Clipboard!', {
|
|
||||||
body: 'Paste it wherever you want!',
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
hideFinder()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchChange (e) {
|
handleSearchChange (e) {
|
||||||
let { dispatch } = this.props
|
this.setState({
|
||||||
|
search: e.target.value,
|
||||||
dispatch(searchArticle(e.target.value))
|
index: 0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
selectArticle (article) {
|
selectArticle (article) {
|
||||||
@@ -128,151 +121,231 @@ class FinderMain extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectPrevious () {
|
selectPrevious () {
|
||||||
let { activeArticle, dispatch } = this.props
|
if (this.state.index > 0) {
|
||||||
let index = this.refs.finderList.props.articles.indexOf(activeArticle)
|
this.setState({
|
||||||
let previousArticle = this.refs.finderList.props.articles[index - 1]
|
index: this.state.index - 1
|
||||||
if (previousArticle != null) dispatch(selectArticle(previousArticle.key))
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectNext () {
|
selectNext () {
|
||||||
let { activeArticle, dispatch } = this.props
|
if (this.state.index < this.noteCount - 1) {
|
||||||
let index = this.refs.finderList.props.articles.indexOf(activeArticle)
|
this.setState({
|
||||||
let previousArticle = this.refs.finderList.props.articles[index + 1]
|
index: this.state.index + 1
|
||||||
if (previousArticle != null) dispatch(selectArticle(previousArticle.key))
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnlySnippetCheckboxChange (e) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.includeSnippet = e.target.checked
|
||||||
|
this.setState({
|
||||||
|
filter: filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnlyMarkdownCheckboxChange (e) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.includeMarkdown = e.target.checked
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter: filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAllNotesButtonClick (e) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.type = 'ALL'
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStarredButtonClick (e) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.type = 'STARRED'
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStorageButtonClick (e, storage) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.type = 'STORAGE'
|
||||||
|
filter.storage = storage
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFolderButtonClick (e, storage, folder) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.type = 'FOLDER'
|
||||||
|
filter.storage = storage
|
||||||
|
filter.folder = folder
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNoteClick (e, index) {
|
||||||
|
this.setState({
|
||||||
|
index
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { articles, activeArticle, status, dispatch } = this.props
|
const { data, config } = this.props
|
||||||
let saveToClipboard = () => this.saveToClipboard()
|
const { filter, search } = this.state
|
||||||
|
const storageList = []
|
||||||
|
for (const key in data.storageMap) {
|
||||||
|
const storage = data.storageMap[key]
|
||||||
|
const item = (
|
||||||
|
<StorageSection
|
||||||
|
filter={filter}
|
||||||
|
storage={storage}
|
||||||
|
key={storage.key}
|
||||||
|
handleStorageButtonClick={(e, storage) => this.handleStorageButtonClick(e, storage)}
|
||||||
|
handleFolderButtonClick={(e, storage, folder) => this.handleFolderButtonClick(e, storage, folder)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
storageList.push(item)
|
||||||
|
}
|
||||||
|
let notes = []
|
||||||
|
let noteIds
|
||||||
|
|
||||||
|
switch (filter.type) {
|
||||||
|
case 'STORAGE':
|
||||||
|
noteIds = data.storageNoteMap[filter.storage]
|
||||||
|
break
|
||||||
|
case 'FOLDER':
|
||||||
|
noteIds = data.folderNoteMap[filter.storage + '-' + filter.folder]
|
||||||
|
break
|
||||||
|
case 'STARRED':
|
||||||
|
noteIds = data.starredSet
|
||||||
|
}
|
||||||
|
if (noteIds != null) {
|
||||||
|
noteIds.forEach((id) => {
|
||||||
|
notes.push(data.noteMap[id])
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for (const key in data.noteMap) {
|
||||||
|
notes.push(data.noteMap[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filter.includeSnippet && filter.includeMarkdown) {
|
||||||
|
notes = notes.filter((note) => note.type === 'MARKDOWN_NOTE')
|
||||||
|
} else if (filter.includeSnippet && !filter.includeMarkdown) {
|
||||||
|
notes = notes.filter((note) => note.type === 'SNIPPET_NOTE')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search.trim().length > 0) {
|
||||||
|
const needle = new RegExp(_.escapeRegExp(search.trim()), 'i')
|
||||||
|
notes = notes.filter((note) => note.title.match(needle))
|
||||||
|
}
|
||||||
|
notes = notes
|
||||||
|
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
||||||
|
|
||||||
|
const activeNote = notes[this.state.index]
|
||||||
|
this.noteCount = notes.length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='Finder'>
|
<div className='Finder'
|
||||||
<FinderInput
|
styleName='root'
|
||||||
handleSearchChange={e => this.handleSearchChange(e)}
|
ref='-1'
|
||||||
ref='finderInput'
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
onChange={this.handleChange}
|
>
|
||||||
value={status.search}
|
<div styleName='search'>
|
||||||
|
<input
|
||||||
|
styleName='search-input'
|
||||||
|
ref='search'
|
||||||
|
value={search}
|
||||||
|
placeholder='Search...'
|
||||||
|
onChange={(e) => this.handleSearchChange(e)}
|
||||||
/>
|
/>
|
||||||
<FinderList
|
</div>
|
||||||
ref='finderList'
|
<div styleName='result'>
|
||||||
activeArticle={activeArticle}
|
<div styleName='result-nav'>
|
||||||
articles={articles}
|
<div styleName='result-nav-filter'>
|
||||||
dispatch={dispatch}
|
<div styleName='result-nav-filter-option'>
|
||||||
selectArticle={article => this.selectArticle(article)}
|
<label>
|
||||||
|
<input type='checkbox'
|
||||||
|
checked={filter.includeSnippet}
|
||||||
|
onChange={(e) => this.handleOnlySnippetCheckboxChange(e)}
|
||||||
|
/> Only Snippets</label>
|
||||||
|
</div>
|
||||||
|
<div styleName='result-nav-filter-option'>
|
||||||
|
<label>
|
||||||
|
<input type='checkbox'
|
||||||
|
checked={filter.includeMarkdown}
|
||||||
|
onChange={(e) => this.handleOnlyMarkdownCheckboxChange(e)}
|
||||||
|
/> Only Markdown</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SideNavFilter
|
||||||
|
isHomeActive={filter.type === 'ALL'}
|
||||||
|
handleAllNotesButtonClick={(e) => this.handleAllNotesButtonClick(e)}
|
||||||
|
isStarredActive={filter.type === 'STARRED'}
|
||||||
|
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
|
||||||
/>
|
/>
|
||||||
<FinderDetail
|
<div styleName='result-nav-storageList'>
|
||||||
activeArticle={activeArticle}
|
{storageList}
|
||||||
saveToClipboard={saveToClipboard}
|
</div>
|
||||||
|
</div>
|
||||||
|
<NoteList styleName='result-list'
|
||||||
|
storageMap={data.storageMap}
|
||||||
|
notes={notes}
|
||||||
|
ref='list'
|
||||||
|
search={search}
|
||||||
|
index={this.state.index}
|
||||||
|
handleNoteClick={(e, _index) => this.handleNoteClick(e, _index)}
|
||||||
/>
|
/>
|
||||||
|
<div styleName='result-detail'>
|
||||||
|
<NoteDetail
|
||||||
|
note={activeNote}
|
||||||
|
config={config}
|
||||||
|
ref='detail'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FinderMain.propTypes = {
|
FinderMain.propTypes = {
|
||||||
articles: PropTypes.array,
|
|
||||||
activeArticle: PropTypes.shape({
|
|
||||||
key: PropTypes.string,
|
|
||||||
tags: PropTypes.array,
|
|
||||||
title: PropTypes.string,
|
|
||||||
content: PropTypes.string
|
|
||||||
}),
|
|
||||||
status: PropTypes.shape(),
|
|
||||||
dispatch: PropTypes.func
|
dispatch: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore invalid key
|
var Finder = connect((x) => x)(CSSModules(FinderMain, styles))
|
||||||
function ignoreInvalidKey (key) {
|
|
||||||
return key.length > 0 && !key.match(/^\/\/$/) && !key.match(/^\/$/) && !key.match(/^#$/)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build filter object by key
|
|
||||||
function buildFilter (key) {
|
|
||||||
if (key.match(/^\/\/.+/)) {
|
|
||||||
return {type: FOLDER_EXACT_FILTER, value: key.match(/^\/\/(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
if (key.match(/^\/.+/)) {
|
|
||||||
return {type: FOLDER_FILTER, value: key.match(/^\/(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
if (key.match(/^#(.+)/)) {
|
|
||||||
return {type: TAG_FILTER, value: key.match(/^#(.+)$/)[1]}
|
|
||||||
}
|
|
||||||
return {type: TEXT_FILTER, value: key}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isContaining (target, needle) {
|
|
||||||
return target.match(new RegExp(_.escapeRegExp(needle), 'i'))
|
|
||||||
}
|
|
||||||
|
|
||||||
function startsWith (target, needle) {
|
|
||||||
return target.match(new RegExp('^' + _.escapeRegExp(needle), 'i'))
|
|
||||||
}
|
|
||||||
|
|
||||||
function remap (state) {
|
|
||||||
let { articles, folders, status } = state
|
|
||||||
|
|
||||||
let filters = status.search.split(' ')
|
|
||||||
.map(key => key.trim())
|
|
||||||
.filter(ignoreInvalidKey)
|
|
||||||
.map(buildFilter)
|
|
||||||
|
|
||||||
let folderExactFilters = filters.filter(filter => filter.type === FOLDER_EXACT_FILTER)
|
|
||||||
let folderFilters = filters.filter(filter => filter.type === FOLDER_FILTER)
|
|
||||||
let textFilters = filters.filter(filter => filter.type === TEXT_FILTER)
|
|
||||||
let tagFilters = filters.filter(filter => filter.type === TAG_FILTER)
|
|
||||||
|
|
||||||
let targetFolders
|
|
||||||
if (folders != null) {
|
|
||||||
let exactTargetFolders = folders.filter(folder => {
|
|
||||||
return _.find(folderExactFilters, filter => filter.value.toLowerCase() === folder.name.toLowerCase())
|
|
||||||
})
|
|
||||||
let fuzzyTargetFolders = folders.filter(folder => {
|
|
||||||
return _.find(folderFilters, filter => startsWith(folder.name.replace(/_/g, ''), filter.value.replace(/_/g, '')))
|
|
||||||
})
|
|
||||||
targetFolders = status.targetFolders = exactTargetFolders.concat(fuzzyTargetFolders)
|
|
||||||
|
|
||||||
if (targetFolders.length > 0) {
|
|
||||||
articles = articles.filter(article => {
|
|
||||||
return _.findWhere(targetFolders, {key: article.FolderKey})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (textFilters.length > 0) {
|
|
||||||
articles = textFilters.reduce((articles, textFilter) => {
|
|
||||||
return articles.filter(article => {
|
|
||||||
return isContaining(article.title, textFilter.value) || isContaining(article.content, textFilter.value)
|
|
||||||
})
|
|
||||||
}, articles)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tagFilters.length > 0) {
|
|
||||||
articles = tagFilters.reduce((articles, tagFilter) => {
|
|
||||||
return articles.filter(article => {
|
|
||||||
return _.find(article.tags, tag => isContaining(tag, tagFilter.value))
|
|
||||||
})
|
|
||||||
}, articles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let activeArticle = _.findWhere(articles, {key: status.articleKey})
|
|
||||||
if (activeArticle == null) activeArticle = articles[0]
|
|
||||||
|
|
||||||
return {
|
|
||||||
articles,
|
|
||||||
activeArticle,
|
|
||||||
status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var Finder = connect(remap)(FinderMain)
|
|
||||||
var store = createStore(reducer)
|
|
||||||
|
|
||||||
function refreshData () {
|
function refreshData () {
|
||||||
let data = dataStore.getData(true)
|
// let data = dataStore.getData(true)
|
||||||
store.dispatch(actions.refreshData(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onfocus = e => {
|
|
||||||
refreshData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render((
|
ReactDOM.render((
|
||||||
|
|||||||
126
browser/finder/ipcClient.js
Normal file
126
browser/finder/ipcClient.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
const nodeIpc = require('node-ipc')
|
||||||
|
const { remote, ipcRenderer } = require('electron')
|
||||||
|
const { app, Menu } = remote
|
||||||
|
const path = require('path')
|
||||||
|
const store = require('./store')
|
||||||
|
const consts = require('browser/lib/consts')
|
||||||
|
|
||||||
|
nodeIpc.config.id = 'finder'
|
||||||
|
nodeIpc.config.retry = 1500
|
||||||
|
nodeIpc.config.silent = true
|
||||||
|
|
||||||
|
function killFinder () {
|
||||||
|
const finderWindow = remote.getCurrentWindow()
|
||||||
|
finderWindow.removeAllListeners()
|
||||||
|
if (global.process.platform === 'darwin') {
|
||||||
|
// Only OSX has another app process.
|
||||||
|
nodeIpc.of.node.emit('quit-from-finder')
|
||||||
|
} else {
|
||||||
|
finderWindow.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFinder () {
|
||||||
|
const finderWindow = remote.getCurrentWindow()
|
||||||
|
if (global.process.platform === 'darwin') {
|
||||||
|
if (finderWindow.isVisible()) {
|
||||||
|
finderWindow.hide()
|
||||||
|
Menu.sendActionToFirstResponder('hide:')
|
||||||
|
} else {
|
||||||
|
nodeIpc.of.node.emit('request-data-from-finder')
|
||||||
|
finderWindow.show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (finderWindow.isVisible()) {
|
||||||
|
finderWindow.blur()
|
||||||
|
finderWindow.hide()
|
||||||
|
} else {
|
||||||
|
nodeIpc.of.node.emit('request-data-from-finder')
|
||||||
|
finderWindow.show()
|
||||||
|
finderWindow.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeIpc.connectTo(
|
||||||
|
'node',
|
||||||
|
path.join(app.getPath('userData'), 'boostnote.service'),
|
||||||
|
function () {
|
||||||
|
nodeIpc.of.node.on('error', function (err) {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
nodeIpc.of.node.on('connect', function () {
|
||||||
|
console.log('Conncted successfully')
|
||||||
|
})
|
||||||
|
nodeIpc.of.node.on('disconnect', function () {
|
||||||
|
console.log('disconnected')
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.of.node.on('open-finder', function () {
|
||||||
|
toggleFinder()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('open-finder-from-tray', function () {
|
||||||
|
toggleFinder()
|
||||||
|
})
|
||||||
|
ipcRenderer.on('open-main-from-tray', function () {
|
||||||
|
nodeIpc.of.node.emit('open-main-from-finder')
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcRenderer.on('quit-from-tray', function () {
|
||||||
|
nodeIpc.of.node.emit('quit-from-finder')
|
||||||
|
killFinder()
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.of.node.on('throttle-data', function (payload) {
|
||||||
|
console.log('Received data from Main renderer')
|
||||||
|
store.default.dispatch({
|
||||||
|
type: 'THROTTLE_DATA',
|
||||||
|
data: payload
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.of.node.on('config-renew', function (payload) {
|
||||||
|
const { config } = payload
|
||||||
|
if (config.ui.theme === 'dark') {
|
||||||
|
document.body.setAttribute('data-theme', 'dark')
|
||||||
|
} else if (config.ui.theme === 'white') {
|
||||||
|
document.body.setAttribute('data-theme', 'white')
|
||||||
|
} else if (config.ui.theme === 'solarized-dark') {
|
||||||
|
document.body.setAttribute('data-theme', 'solarized-dark')
|
||||||
|
} else {
|
||||||
|
document.body.setAttribute('data-theme', 'default')
|
||||||
|
}
|
||||||
|
|
||||||
|
let editorTheme = document.getElementById('editorTheme')
|
||||||
|
if (editorTheme == null) {
|
||||||
|
editorTheme = document.createElement('link')
|
||||||
|
editorTheme.setAttribute('id', 'editorTheme')
|
||||||
|
editorTheme.setAttribute('rel', 'stylesheet')
|
||||||
|
document.head.appendChild(editorTheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.editor.theme = consts.THEMES.some((theme) => theme === config.editor.theme)
|
||||||
|
? config.editor.theme
|
||||||
|
: 'default'
|
||||||
|
|
||||||
|
if (config.editor.theme !== 'default') {
|
||||||
|
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
|
||||||
|
}
|
||||||
|
|
||||||
|
store.default.dispatch({
|
||||||
|
type: 'SET_CONFIG',
|
||||||
|
config: config
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
nodeIpc.of.node.on('quit-finder-app', function () {
|
||||||
|
nodeIpc.of.node.emit('quit-finder-app-confirm')
|
||||||
|
killFinder()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const ipc = {}
|
||||||
|
|
||||||
|
module.exports = ipc
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import { combineReducers } from 'redux'
|
|
||||||
import { SELECT_ARTICLE, SEARCH_ARTICLE, REFRESH_DATA } from './actions'
|
|
||||||
|
|
||||||
let initialArticles = []
|
|
||||||
let initialFolders = []
|
|
||||||
let initialStatus = {
|
|
||||||
articleKey: null,
|
|
||||||
search: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
function status (state = initialStatus, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case SELECT_ARTICLE:
|
|
||||||
state.articleKey = action.data.key
|
|
||||||
return Object.assign({}, state)
|
|
||||||
case SEARCH_ARTICLE:
|
|
||||||
state.search = action.data.input
|
|
||||||
return Object.assign({}, state)
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function articles (state = initialArticles, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case REFRESH_DATA:
|
|
||||||
return action.data.articles
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function folders (state = initialFolders, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case REFRESH_DATA:
|
|
||||||
console.log(action)
|
|
||||||
return action.data.folders
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default combineReducers({
|
|
||||||
status,
|
|
||||||
folders,
|
|
||||||
articles
|
|
||||||
})
|
|
||||||
51
browser/finder/store.js
Normal file
51
browser/finder/store.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { combineReducers, createStore } from 'redux'
|
||||||
|
import { routerReducer } from 'react-router-redux'
|
||||||
|
import { DEFAULT_CONFIG } from 'browser/main/lib/ConfigManager'
|
||||||
|
|
||||||
|
const defaultData = {
|
||||||
|
storageMap: {},
|
||||||
|
noteMap: {},
|
||||||
|
starredSet: [],
|
||||||
|
storageNoteMap: {},
|
||||||
|
folderNoteMap: {},
|
||||||
|
tagNoteMap: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function data (state = defaultData, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'THROTTLE_DATA':
|
||||||
|
console.log(action)
|
||||||
|
state = action.data
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
function config (state = DEFAULT_CONFIG, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'INIT_CONFIG':
|
||||||
|
case 'SET_CONFIG':
|
||||||
|
return Object.assign({}, state, action.config)
|
||||||
|
case 'SET_IS_SIDENAV_FOLDED':
|
||||||
|
state.isSideNavFolded = action.isFolded
|
||||||
|
return Object.assign({}, state)
|
||||||
|
case 'SET_ZOOM':
|
||||||
|
state.zoom = action.zoom
|
||||||
|
return Object.assign({}, state)
|
||||||
|
case 'SET_LIST_WIDTH':
|
||||||
|
state.listWidth = action.listWidth
|
||||||
|
return Object.assign({}, state)
|
||||||
|
case 'SET_UI':
|
||||||
|
return Object.assign({}, state, action.config)
|
||||||
|
}
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
const reducer = combineReducers({
|
||||||
|
data,
|
||||||
|
config,
|
||||||
|
routing: routerReducer
|
||||||
|
})
|
||||||
|
|
||||||
|
const store = createStore(reducer)
|
||||||
|
|
||||||
|
export default store
|
||||||
106
browser/lib/Mutable.js
Normal file
106
browser/lib/Mutable.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
class MutableMap {
|
||||||
|
constructor (iterable) {
|
||||||
|
this._map = new Map(iterable)
|
||||||
|
Object.defineProperty(this, 'size', {
|
||||||
|
get: () => this._map.size,
|
||||||
|
set: function (value) {
|
||||||
|
this['size'] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get (...args) {
|
||||||
|
return this._map.get(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
set (...args) {
|
||||||
|
return this._map.set(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete (...args) {
|
||||||
|
return this._map.delete(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
has (...args) {
|
||||||
|
return this._map.has(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
clear (...args) {
|
||||||
|
return this._map.clear(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach (...args) {
|
||||||
|
return this._map.forEach(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator] () {
|
||||||
|
return this._map[Symbol.iterator]()
|
||||||
|
}
|
||||||
|
|
||||||
|
map (cb) {
|
||||||
|
const result = []
|
||||||
|
for (const [key, value] of this._map) {
|
||||||
|
result.push(cb(value, key))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
toJS () {
|
||||||
|
const result = {}
|
||||||
|
for (let [key, value] of this._map) {
|
||||||
|
if (value instanceof MutableSet || value instanceof MutableMap) {
|
||||||
|
value = value.toJS()
|
||||||
|
}
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MutableSet {
|
||||||
|
constructor (iterable) {
|
||||||
|
this._set = new Set(iterable)
|
||||||
|
Object.defineProperty(this, 'size', {
|
||||||
|
get: () => this._set.size,
|
||||||
|
set: function (value) {
|
||||||
|
this['size'] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
add (...args) {
|
||||||
|
return this._set.add(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete (...args) {
|
||||||
|
return this._set.delete(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach (...args) {
|
||||||
|
return this._set.forEach(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.iterator] () {
|
||||||
|
return this._set[Symbol.iterator]()
|
||||||
|
}
|
||||||
|
|
||||||
|
map (cb) {
|
||||||
|
const result = []
|
||||||
|
this._set.forEach(function (value, key) {
|
||||||
|
result.push(cb(value, key))
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
toJS () {
|
||||||
|
return Array.from(this._set)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Mutable = {
|
||||||
|
Map: MutableMap,
|
||||||
|
Set: MutableSet
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Mutable
|
||||||
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
|
||||||
|
}
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import _ from 'lodash'
|
|
||||||
import moment from 'moment'
|
|
||||||
import dataStore from './dataStore'
|
|
||||||
import { request, SERVER_URL } from './api'
|
|
||||||
import clientKey from './clientKey'
|
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const version = electron.remote.app.getVersion()
|
|
||||||
|
|
||||||
function isSameDate (a, b) {
|
|
||||||
a = moment(a).utcOffset(+540).format('YYYYMMDD')
|
|
||||||
b = moment(b).utcOffset(+540).format('YYYYMMDD')
|
|
||||||
|
|
||||||
return a === b
|
|
||||||
}
|
|
||||||
|
|
||||||
export function init () {
|
|
||||||
let records = getAllRecords()
|
|
||||||
if (records == null) {
|
|
||||||
saveAllRecords([])
|
|
||||||
}
|
|
||||||
emit(null)
|
|
||||||
|
|
||||||
postRecords()
|
|
||||||
if (window != null) {
|
|
||||||
window.addEventListener('online', postRecords)
|
|
||||||
window.setInterval(postRecords, 1000 * 60 * 60 * 24)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAllRecords () {
|
|
||||||
return JSON.parse(localStorage.getItem('activityRecords'))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveAllRecords (records) {
|
|
||||||
localStorage.setItem('activityRecords', JSON.stringify(records))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Post all records(except today)
|
|
||||||
and remove all posted records
|
|
||||||
*/
|
|
||||||
export function postRecords (data) {
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
console.log('post failed - NOT PRODUCTION ')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let records = getAllRecords()
|
|
||||||
records = records.filter(record => {
|
|
||||||
return !isSameDate(new Date(), record.date)
|
|
||||||
})
|
|
||||||
|
|
||||||
if (records.length === 0) {
|
|
||||||
console.log('No records to post')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('posting...', records)
|
|
||||||
let input = {
|
|
||||||
clientKey: clientKey.get(),
|
|
||||||
records
|
|
||||||
}
|
|
||||||
return request.post(SERVER_URL + 'apis/activity')
|
|
||||||
.send(input)
|
|
||||||
.then(res => {
|
|
||||||
let records = getAllRecords()
|
|
||||||
let todayRecord = _.find(records, record => {
|
|
||||||
return isSameDate(new Date(), record.date)
|
|
||||||
})
|
|
||||||
if (todayRecord != null) saveAllRecords([todayRecord])
|
|
||||||
else saveAllRecords([])
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function emit (type, data = {}) {
|
|
||||||
let records = getAllRecords()
|
|
||||||
|
|
||||||
let index = _.findIndex(records, record => {
|
|
||||||
return isSameDate(new Date(), record.date)
|
|
||||||
})
|
|
||||||
|
|
||||||
let todayRecord
|
|
||||||
if (index < 0) {
|
|
||||||
todayRecord = {date: new Date()}
|
|
||||||
records.push(todayRecord)
|
|
||||||
}
|
|
||||||
else todayRecord = records[index]
|
|
||||||
switch (type) {
|
|
||||||
case 'ARTICLE_CREATE':
|
|
||||||
case 'ARTICLE_UPDATE':
|
|
||||||
case 'ARTICLE_DESTROY':
|
|
||||||
case 'FOLDER_CREATE':
|
|
||||||
case 'FOLDER_UPDATE':
|
|
||||||
case 'FOLDER_DESTROY':
|
|
||||||
case 'FINDER_OPEN':
|
|
||||||
case 'FINDER_COPY':
|
|
||||||
case 'MAIN_DETAIL_COPY':
|
|
||||||
case 'ARTICLE_SHARE':
|
|
||||||
todayRecord[type] = todayRecord[type] == null
|
|
||||||
? 1
|
|
||||||
: todayRecord[type] + 1
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count ARTICLE_CREATE and ARTICLE_UPDATE again by syntax
|
|
||||||
if (type === 'ARTICLE_UPDATE' && data.mode != null) {
|
|
||||||
let recordKey = type + '_BY_SYNTAX'
|
|
||||||
if (todayRecord[recordKey] == null) todayRecord[recordKey] = {}
|
|
||||||
|
|
||||||
todayRecord[recordKey][data.mode] = todayRecord[recordKey][data.mode] == null
|
|
||||||
? 1
|
|
||||||
: todayRecord[recordKey][data.mode] + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
let storeData = dataStore.getData()
|
|
||||||
todayRecord.FOLDER_COUNT = storeData && _.isArray(storeData.folders) ? storeData.folders.length : 0
|
|
||||||
todayRecord.ARTICLE_COUNT = storeData && _.isArray(storeData.articles) ? storeData.articles.length : 0
|
|
||||||
todayRecord.CLIENT_VERSION = version
|
|
||||||
|
|
||||||
todayRecord.SYNTAX_COUNT = storeData && _.isArray(storeData.articles) ? storeData.articles.reduce((sum, article) => {
|
|
||||||
if (sum[article.mode] == null) sum[article.mode] = 1
|
|
||||||
else sum[article.mode]++
|
|
||||||
return sum
|
|
||||||
}, {}) : 0
|
|
||||||
|
|
||||||
saveAllRecords(records)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
init,
|
|
||||||
emit,
|
|
||||||
postRecords
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import superagent from 'superagent'
|
|
||||||
import superagentPromise from 'superagent-promise'
|
|
||||||
|
|
||||||
export const SERVER_URL = 'https://b00st.io/'
|
|
||||||
// export const SERVER_URL = 'http://localhost:3333/'
|
|
||||||
|
|
||||||
export const request = superagentPromise(superagent, Promise)
|
|
||||||
|
|
||||||
export function shareViaPublicURL (input) {
|
|
||||||
return request
|
|
||||||
.post(SERVER_URL + 'apis/share')
|
|
||||||
// .set({
|
|
||||||
// Authorization: 'Bearer ' + auth.token()
|
|
||||||
// })
|
|
||||||
.send(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
SERVER_URL,
|
|
||||||
shareViaPublicURL
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import _ from 'lodash'
|
|
||||||
import keygen from './keygen'
|
|
||||||
|
|
||||||
function getClientKey () {
|
|
||||||
let clientKey = localStorage.getItem('clientKey')
|
|
||||||
if (!_.isString(clientKey) || clientKey.length !== 40) {
|
|
||||||
clientKey = keygen(20)
|
|
||||||
setClientKey(clientKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientKey
|
|
||||||
}
|
|
||||||
|
|
||||||
function setClientKey (newKey) {
|
|
||||||
localStorage.setItem('clientKey', newKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
getClientKey()
|
|
||||||
|
|
||||||
export default {
|
|
||||||
get: getClientKey,
|
|
||||||
set: setClientKey
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,17 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const fs = require('sander')
|
||||||
|
const { remote } = require('electron')
|
||||||
|
const { app } = remote
|
||||||
|
|
||||||
|
const themePath = process.env.NODE_ENV === 'production'
|
||||||
|
? path.join(app.getAppPath(), './node_modules/codemirror/theme')
|
||||||
|
: require('path').resolve('./node_modules/codemirror/theme')
|
||||||
|
const themes = fs.readdirSync(themePath)
|
||||||
|
.map((themePath) => {
|
||||||
|
return themePath.substring(0, themePath.lastIndexOf('.'))
|
||||||
|
})
|
||||||
|
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
||||||
|
|
||||||
const consts = {
|
const consts = {
|
||||||
FOLDER_COLORS: [
|
FOLDER_COLORS: [
|
||||||
'#E10051',
|
'#E10051',
|
||||||
@@ -16,7 +30,8 @@ const consts = {
|
|||||||
'Turquoise',
|
'Turquoise',
|
||||||
'Dodger Blue',
|
'Dodger Blue',
|
||||||
'Violet Eggplant'
|
'Violet Eggplant'
|
||||||
]
|
],
|
||||||
|
THEMES: ['default'].concat(themes)
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = consts
|
module.exports = consts
|
||||||
|
|||||||
17
browser/lib/context.js
Normal file
17
browser/lib/context.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const { remote } = require('electron')
|
||||||
|
const { Menu, MenuItem } = remote
|
||||||
|
|
||||||
|
function popup (templates) {
|
||||||
|
let menu = new Menu()
|
||||||
|
templates.forEach((item) => {
|
||||||
|
menu.append(new MenuItem(item))
|
||||||
|
})
|
||||||
|
menu.popup(remote.getCurrentWindow())
|
||||||
|
}
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
popup
|
||||||
|
}
|
||||||
|
|
||||||
|
module.export = context
|
||||||
|
export default context
|
||||||
3
browser/lib/customMeta.js
Normal file
3
browser/lib/customMeta.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import CodeMirror from 'codemirror'
|
||||||
|
|
||||||
|
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
import keygen from './keygen'
|
|
||||||
import _ from 'lodash'
|
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const remote = electron.remote
|
|
||||||
const jetpack = require('fs-jetpack')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
let defaultContent = 'Boost is a brand new note App for programmers.\n\n> 下に日本語版があります。\n\n# \u25CEfeature\n\nBoost has some preponderant functions for efficient engineer\'s task.See some part of it.\n\n1. classify information by\u300CFolders\u300D\n2. deal with great variety of syntax\n3. Finder function\n\n\uFF0A\u3000\uFF0A\u3000\uFF0A\u3000\uFF0A\n\n# 1. classify information by \u300CFolders\u300D- access the information you needed easily.\n\n\u300CFolders\u300D which on the left side bar. Press plus button now. flexible way of classification.\n- Create Folder every language or flamework\n- Make Folder for your own casual memos\n\n# 2. Deal with a great variety of syntax \u2013 instead of your brain\nSave handy all information related with programming\n- Use markdown and gather api specification\n- Well using module and snippet\n\nSave them on Boost, you don\'t need to rewrite or re-search same code again.\n\n# 3. Load Finder function \u2013 now you don\'t need to spell command by hand typing.\n\n**Shift +ctrl+tab** press buttons at same time.\nThen, the window will show up for search Boost contents that instant.\n\nUsing cursor key to chose, press enter, cmd+v to paste and\u2026 please check it out by your own eye.\n\n- Such command spl or linux which programmers often use but troublesome to hand type\n\n- (Phrases commonly used for e-mail or customer support)\n\nWe support preponderant efficiency\n\n\uFF0A\u3000\uFF0A\u3000\uFF0A\u3000\uFF0A\n\n## \u25CEfor more information\nFrequently updated with this blog ( http:\/\/blog-jp.b00st.io )\n\nHave wonderful programmer life!\n\n## Hack your memory**\n\n\n\n# 日本語版\n\n**Boost**は全く新しいエンジニアライクのノートアプリです。\n\n# ◎特徴\nBoostはエンジニアの仕事を圧倒的に効率化するいくつかの機能を備えています。\nその一部をご紹介します。\n1. Folderで情報を分類\n2. 豊富なsyantaxに対応\n3. Finder機能\n\n\n* * * *\n\n# 1. Folderで情報を分類、欲しい情報にすぐアクセス。\n左側のバーに存在する「Folders」。\n今すぐプラスボタンを押しましょう。\n分類の仕方も自由自在です。\n- 言語やフレームワークごとにFolderを作成\n- 自分用のカジュアルなメモをまとめる場としてFolderを作成\n\n\n# 2. 豊富なsyntaxに対応、自分の脳の代わりに。\nプログラミングに関する情報を全て、手軽に保存しましょう。\n- mdで、apiの仕様をまとめる\n- よく使うモジュールやスニペット\n\nBoostに保存しておくことで、何度も同じコードを書いたり調べたりする必要がなくなります。\n\n# 3. Finder機能を搭載、もうコマンドを手打ちする必要はありません。\n**「shift+ctrl+tab」** を同時に押してみてください。\nここでは、一瞬でBoostの中身を検索するウィンドウを表示させることができます。\n\n矢印キーで選択、Enterを押し、cmd+vでペーストすると…続きはご自身の目でお確かめください。\n- sqlやlinux等の、よく使うが手打ちが面倒なコマンド\n- (メールやカスタマーサポート等でよく使うフレーズ)\n\n私たちは、圧倒的な効率性を支援します。\n\* * * *\n\n\n## ◎詳しくは\nこちらのブログ( http://blog-jp.b00st.io )にて随時更新しています。\n\nそれでは素晴らしいエンジニアライフを!\n\n## Hack your memory**'
|
|
||||||
|
|
||||||
let data = null
|
|
||||||
|
|
||||||
function getLocalPath () {
|
|
||||||
return path.join(remote.app.getPath('userData'), 'local.json')
|
|
||||||
}
|
|
||||||
|
|
||||||
function forgeInitialRepositories () {
|
|
||||||
let defaultRepo = {
|
|
||||||
key: keygen(),
|
|
||||||
name: 'local',
|
|
||||||
type: 'userData',
|
|
||||||
user: {
|
|
||||||
name: 'New user'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
|
||||||
defaultRepo.user.name = remote.process.env.USER
|
|
||||||
} else if (process.platform === 'win32') {
|
|
||||||
defaultRepo.user.name = remote.process.env.USERNAME
|
|
||||||
}
|
|
||||||
|
|
||||||
return [defaultRepo]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRepositories () {
|
|
||||||
let raw = localStorage.getItem('repositories')
|
|
||||||
try {
|
|
||||||
let parsed = JSON.parse(raw)
|
|
||||||
if (!_.isArray(parsed)) {
|
|
||||||
throw new Error('repositories data is corrupted. re-init data.')
|
|
||||||
}
|
|
||||||
return parsed
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
let newRepos = forgeInitialRepositories()
|
|
||||||
saveRepositories(newRepos)
|
|
||||||
return newRepos
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveRepositories (repos) {
|
|
||||||
localStorage.setItem('repositories', JSON.stringify(repos))
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getUser (repoName) {
|
|
||||||
if (repoName == null) {
|
|
||||||
return getRepositories()[0]
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
export function saveUser (repoName, user) {
|
|
||||||
let repos = getRepositories()
|
|
||||||
if (repoName == null) {
|
|
||||||
Object.assign(repos[0].user, user)
|
|
||||||
}
|
|
||||||
saveRepositories(repos)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function init () {
|
|
||||||
// set repositories info
|
|
||||||
getRepositories()
|
|
||||||
data = jetpack.read(getLocalPath(), 'json')
|
|
||||||
if (data == null) {
|
|
||||||
let defaultFolder = {
|
|
||||||
name: 'default',
|
|
||||||
key: keygen()
|
|
||||||
}
|
|
||||||
let defaultArticle = {
|
|
||||||
title: 'About Boost',
|
|
||||||
tags: ['boost', 'intro'],
|
|
||||||
content: defaultContent,
|
|
||||||
mode: 'markdown',
|
|
||||||
key: keygen(),
|
|
||||||
FolderKey: defaultFolder.key,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date()
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
articles: [defaultArticle],
|
|
||||||
folders: [defaultFolder],
|
|
||||||
version: '0.4'
|
|
||||||
}
|
|
||||||
saveData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getData (forceRead) {
|
|
||||||
if (forceRead) {
|
|
||||||
try {
|
|
||||||
data = jetpack.read(getLocalPath(), 'json')
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
let timer = null
|
|
||||||
let isSaving = false
|
|
||||||
let saveAgain = false
|
|
||||||
function saveData () {
|
|
||||||
timer = null
|
|
||||||
isSaving = true
|
|
||||||
jetpack.writeAsync(getLocalPath(), data)
|
|
||||||
.then(function () {
|
|
||||||
isSaving = false
|
|
||||||
if (saveAgain) {
|
|
||||||
saveAgain = false
|
|
||||||
queueSave()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function queueSave () {
|
|
||||||
if (!isSaving) {
|
|
||||||
if (timer) {
|
|
||||||
clearTimeout(timer)
|
|
||||||
}
|
|
||||||
timer = setTimeout(saveData, 500)
|
|
||||||
} else {
|
|
||||||
saveAgain = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setArticles (articles) {
|
|
||||||
if (!_.isArray(articles)) throw new Error('Articles must be an array')
|
|
||||||
let data = getData()
|
|
||||||
data.articles = articles
|
|
||||||
queueSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setFolders (folders) {
|
|
||||||
if (!_.isArray(folders)) throw new Error('Folders must be an array')
|
|
||||||
let data = getData()
|
|
||||||
data.folders = folders
|
|
||||||
queueSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getUser,
|
|
||||||
saveUser,
|
|
||||||
init,
|
|
||||||
getData,
|
|
||||||
setArticles,
|
|
||||||
setFolders
|
|
||||||
}
|
|
||||||
18
browser/lib/date-formatter.js
Normal file
18
browser/lib/date-formatter.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Formatting date string.
|
||||||
|
*/
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Return date string. For example, 'Sep.9, 2016 12:00'.
|
||||||
|
* @param {mixed}
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export function formatDate (date) {
|
||||||
|
const m = moment(date)
|
||||||
|
if (!m.isValid()) {
|
||||||
|
throw Error('Invalid argument.')
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.format('MMM D, gggg H:mm')
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
const hljsThemeList = [
|
|
||||||
{caption: 'Default', name: 'default'},
|
|
||||||
{caption: 'Agate', name: 'agate'},
|
|
||||||
{caption: 'Androidstudio', name: 'androidstudio'},
|
|
||||||
{caption: 'Arduino Light', name: 'arduino-light'},
|
|
||||||
{caption: 'Arta', name: 'arta'},
|
|
||||||
{caption: 'Ascetic', name: 'ascetic'},
|
|
||||||
{caption: 'Atelier Cave Dark', name: 'atelier-cave-dark'},
|
|
||||||
{caption: 'Atelier Cave Light', name: 'atelier-cave-light'},
|
|
||||||
{caption: 'Atelier Dune Dark', name: 'atelier-dune-dark'},
|
|
||||||
{caption: 'Atelier Dune Light', name: 'atelier-dune-light'},
|
|
||||||
{caption: 'Atelier Estuary Dark', name: 'atelier-estuary-dark'},
|
|
||||||
{caption: 'Atelier Estuary Light', name: 'atelier-estuary-light'},
|
|
||||||
{caption: 'Atelier Forest Dark', name: 'atelier-forest-dark'},
|
|
||||||
{caption: 'Atelier Forest Light', name: 'atelier-forest-light'},
|
|
||||||
{caption: 'Atelier Heath Dark', name: 'atelier-heath-dark'},
|
|
||||||
{caption: 'Atelier Heath Light', name: 'atelier-heath-light'},
|
|
||||||
{caption: 'Atelier Lakeside Dark', name: 'atelier-lakeside-dark'},
|
|
||||||
{caption: 'Atelier Lakeside Light', name: 'atelier-lakeside-light'},
|
|
||||||
{caption: 'Atelier Plateau Dark', name: 'atelier-plateau-dark'},
|
|
||||||
{caption: 'Atelier Plateau Light', name: 'atelier-plateau-light'},
|
|
||||||
{caption: 'Atelier Savanna Dark', name: 'atelier-savanna-dark'},
|
|
||||||
{caption: 'Atelier Savanna Light', name: 'atelier-savanna-light'},
|
|
||||||
{caption: 'Atelier Seaside Dark', name: 'atelier-seaside-dark'},
|
|
||||||
{caption: 'Atelier Seaside Light', name: 'atelier-seaside-light'},
|
|
||||||
{caption: 'Atelier Sulphurpool Dark', name: 'atelier-sulphurpool-dark'},
|
|
||||||
{caption: 'Atelier Sulphurpool Light', name: 'atelier-sulphurpool-light'},
|
|
||||||
{caption: 'Brown Paper', name: 'brown-paper'},
|
|
||||||
{caption: 'Codepen Embed', name: 'codepen-embed'},
|
|
||||||
{caption: 'Color Brewer', name: 'color-brewer'},
|
|
||||||
{caption: 'Dark', name: 'dark'},
|
|
||||||
{caption: 'Darkula', name: 'darkula'},
|
|
||||||
{caption: 'Docco', name: 'docco'},
|
|
||||||
{caption: 'Dracula', name: 'dracula'},
|
|
||||||
{caption: 'Far', name: 'far'},
|
|
||||||
{caption: 'Foundation', name: 'foundation'},
|
|
||||||
{caption: 'Github Gist', name: 'github-gist'},
|
|
||||||
{caption: 'Github', name: 'github'},
|
|
||||||
{caption: 'Googlecode', name: 'googlecode'},
|
|
||||||
{caption: 'Grayscale', name: 'grayscale'},
|
|
||||||
{caption: 'Gruvbox Dark', name: 'gruvbox.dark'},
|
|
||||||
{caption: 'Gruvbox Light', name: 'gruvbox.light'},
|
|
||||||
{caption: 'Hopscotch', name: 'hopscotch'},
|
|
||||||
{caption: 'Hybrid', name: 'hybrid'},
|
|
||||||
{caption: 'Idea', name: 'idea'},
|
|
||||||
{caption: 'Ir Black', name: 'ir-black'},
|
|
||||||
{caption: 'Kimbie Dark', name: 'kimbie.dark'},
|
|
||||||
{caption: 'Kimbie Light', name: 'kimbie.light'},
|
|
||||||
{caption: 'Magula', name: 'magula'},
|
|
||||||
{caption: 'Mono Blue', name: 'mono-blue'},
|
|
||||||
{caption: 'Monokai Sublime', name: 'monokai-sublime'},
|
|
||||||
{caption: 'Monokai', name: 'monokai'},
|
|
||||||
{caption: 'Obsidian', name: 'obsidian'},
|
|
||||||
{caption: 'Paraiso Dark', name: 'paraiso-dark'},
|
|
||||||
{caption: 'Paraiso Light', name: 'paraiso-light'},
|
|
||||||
{caption: 'Pojoaque', name: 'pojoaque'},
|
|
||||||
{caption: 'Qtcreator Dark', name: 'qtcreator_dark'},
|
|
||||||
{caption: 'Qtcreator Light', name: 'qtcreator_light'},
|
|
||||||
{caption: 'Railscasts', name: 'railscasts'},
|
|
||||||
{caption: 'Rainbow', name: 'rainbow'},
|
|
||||||
{caption: 'School Book', name: 'school-book'},
|
|
||||||
{caption: 'Solarized Dark', name: 'solarized-dark'},
|
|
||||||
{caption: 'Solarized Light', name: 'solarized-light'},
|
|
||||||
{caption: 'Sunburst', name: 'sunburst'},
|
|
||||||
{caption: 'Tomorrow Night Blue', name: 'tomorrow-night-blue'},
|
|
||||||
{caption: 'Tomorrow Night Bright', name: 'tomorrow-night-bright'},
|
|
||||||
{caption: 'Tomorrow Night Eighties', name: 'tomorrow-night-eighties'},
|
|
||||||
{caption: 'Tomorrow Night', name: 'tomorrow-night'},
|
|
||||||
{caption: 'Tomorrow', name: 'tomorrow'},
|
|
||||||
{caption: 'Vs', name: 'vs'},
|
|
||||||
{caption: 'Xcode', name: 'xcode'},
|
|
||||||
{caption: 'Xt 256', name: 'xt256'},
|
|
||||||
{caption: 'Zenburn', name: 'zenburn'}
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function hljsTheme () {
|
|
||||||
return hljsThemeList
|
|
||||||
}
|
|
||||||
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
|
||||||
|
}
|
||||||
@@ -2,6 +2,6 @@ const crypto = require('crypto')
|
|||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
|
||||||
module.exports = function (length) {
|
module.exports = function (length) {
|
||||||
if (!_.isFinite(length)) length = 6
|
if (!_.isFinite(length)) length = 10
|
||||||
return crypto.randomBytes(length).toString('hex')
|
return crypto.randomBytes(length).toString('hex')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
import markdownit from 'markdown-it'
|
import markdownit from 'markdown-it'
|
||||||
import emoji from 'markdown-it-emoji'
|
import emoji from 'markdown-it-emoji'
|
||||||
import math from '@rokt33r/markdown-it-math'
|
import math from '@rokt33r/markdown-it-math'
|
||||||
import hljs from 'highlight.js'
|
import _ from 'lodash'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
|
||||||
|
// FIXME We should not depend on global variable.
|
||||||
const katex = window.katex
|
const katex = window.katex
|
||||||
|
const config = ConfigManager.get()
|
||||||
|
|
||||||
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>' + i + '</span>')
|
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
|
||||||
}
|
}
|
||||||
return '<span class="lineNumber">' + lines.join('') + '</span>'
|
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
||||||
}
|
}
|
||||||
|
|
||||||
var md = markdownit({
|
var md = markdownit({
|
||||||
@@ -19,20 +22,18 @@ 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 && hljs.getLanguage(lang)) {
|
if (lang === 'flowchart') {
|
||||||
try {
|
return `<pre class="flowchart">${str}</pre>`
|
||||||
return '<pre class="hljs">' +
|
|
||||||
createGutter(str) +
|
|
||||||
'<code>' +
|
|
||||||
hljs.highlight(lang, str).value +
|
|
||||||
'</code></pre>'
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
}
|
||||||
return '<pre class="hljs">' +
|
if (lang === 'sequence') {
|
||||||
|
return `<pre class="sequence">${str}</pre>`
|
||||||
|
}
|
||||||
|
return '<pre class="code">' +
|
||||||
createGutter(str) +
|
createGutter(str) +
|
||||||
'<code>' +
|
'<code class="' + lang + '">' +
|
||||||
str.replace(/\&/g, '&').replace(/\</g, '<').replace(/\>/g, '>').replace(/\"/g, '"') +
|
str +
|
||||||
'</code></pre>'
|
'</code></pre>'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -40,6 +41,10 @@ md.use(emoji, {
|
|||||||
shortcuts: {}
|
shortcuts: {}
|
||||||
})
|
})
|
||||||
md.use(math, {
|
md.use(math, {
|
||||||
|
inlineOpen: config.preview.latexInlineOpen,
|
||||||
|
inlineClose: config.preview.latexInlineClose,
|
||||||
|
blockOpen: config.preview.latexBlockOpen,
|
||||||
|
blockClose: config.preview.latexBlockClose,
|
||||||
inlineRenderer: function (str) {
|
inlineRenderer: function (str) {
|
||||||
let output = ''
|
let output = ''
|
||||||
try {
|
try {
|
||||||
@@ -59,21 +64,100 @@ md.use(math, {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
md.use(require('markdown-it-checkbox'))
|
md.use(require('markdown-it-imsize'))
|
||||||
|
md.use(require('markdown-it-footnote'))
|
||||||
let originalRenderToken = md.renderer.renderToken
|
md.use(require('markdown-it-multimd-table'))
|
||||||
md.renderer.renderToken = function renderToken (tokens, idx, options) {
|
md.use(require('markdown-it-named-headers'), {
|
||||||
let token = tokens[idx]
|
slugify: (header) => {
|
||||||
|
return encodeURI(header.trim()
|
||||||
let result = originalRenderToken.call(md.renderer, tokens, idx, options)
|
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
||||||
if (token.map != null) {
|
.replace(/\s+/g, '-'))
|
||||||
return result + '<a class=\'lineAnchor\' data-key=\'' + token.map[0] + '\'></a>'
|
.replace(/\-+$/, '')
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
md.use(require('markdown-it-kbd'))
|
||||||
|
md.use(require('markdown-it-plantuml'))
|
||||||
|
|
||||||
|
// Override task item
|
||||||
|
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||||
|
let content, terminate, i, l, token
|
||||||
|
let nextLine = startLine + 1
|
||||||
|
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||||
|
const endLine = state.lineMax
|
||||||
|
|
||||||
|
// jump line-by-line until empty one or EOF
|
||||||
|
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
||||||
|
// this would be a code block normally, but after paragraph
|
||||||
|
// it's considered a lazy continuation regardless of what's there
|
||||||
|
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
|
||||||
|
|
||||||
|
// quirk for blockquotes, this line should already be checked by that rule
|
||||||
|
if (state.sCount[nextLine] < 0) { continue }
|
||||||
|
|
||||||
|
// Some tags can terminate paragraph without empty line.
|
||||||
|
terminate = false
|
||||||
|
for (i = 0, l = terminatorRules.length; i < l; i++) {
|
||||||
|
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
||||||
|
terminate = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (terminate) { break }
|
||||||
|
}
|
||||||
|
|
||||||
|
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
|
||||||
|
|
||||||
|
state.line = nextLine
|
||||||
|
|
||||||
|
token = state.push('paragraph_open', 'p', 1)
|
||||||
|
token.map = [ startLine, state.line ]
|
||||||
|
|
||||||
|
if (state.parentType === 'list') {
|
||||||
|
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
||||||
|
if (match) {
|
||||||
|
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token = state.push('inline', '', 0)
|
||||||
|
token.content = content
|
||||||
|
token.map = [ startLine, state.line ]
|
||||||
|
token.children = []
|
||||||
|
|
||||||
|
token = state.push('paragraph_close', 'p', -1)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add line number attribute for scrolling
|
||||||
|
const originalRender = md.renderer.render
|
||||||
|
md.renderer.render = function render (tokens, options, env) {
|
||||||
|
tokens.forEach((token) => {
|
||||||
|
switch (token.type) {
|
||||||
|
case 'heading_open':
|
||||||
|
case 'paragraph_open':
|
||||||
|
case 'blockquote_open':
|
||||||
|
case 'table_open':
|
||||||
|
token.attrPush(['data-line', token.map[0]])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const result = originalRender.call(md.renderer, tokens, options, env)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
// FIXME We should not depend on global variable.
|
||||||
|
window.md = md
|
||||||
|
|
||||||
export default function markdown (content) {
|
function normalizeLinkText (linkText) {
|
||||||
if (content == null) content = ''
|
return md.normalizeLinkText(linkText)
|
||||||
|
|
||||||
return md.render(content.toString())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const markdown = {
|
||||||
|
render: function markdown (content) {
|
||||||
|
if (!_.isString(content)) content = ''
|
||||||
|
const renderedContent = md.render(content)
|
||||||
|
return renderedContent
|
||||||
|
},
|
||||||
|
normalizeLinkText
|
||||||
|
}
|
||||||
|
|
||||||
|
export default markdown
|
||||||
|
|||||||
39
browser/lib/markdownTextHelper.js
Normal file
39
browser/lib/markdownTextHelper.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Text trimmer for markdown note.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export function strip (input) {
|
||||||
|
let output = input
|
||||||
|
try {
|
||||||
|
output = output
|
||||||
|
.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, '$1')
|
||||||
|
.replace(/\n={2,}/g, '\n')
|
||||||
|
.replace(/~~/g, '')
|
||||||
|
.replace(/`{3}.*\n/g, '')
|
||||||
|
.replace(/<(.*?)>/g, '$1')
|
||||||
|
.replace(/^[=\-]{2,}\s*$/g, '')
|
||||||
|
.replace(/\[\^.+?\](: .*?$)?/g, '')
|
||||||
|
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
|
||||||
|
.replace(/!\[.*?\][\[\(].*?[\]\)]/g, '')
|
||||||
|
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
||||||
|
.replace(/>/g, '')
|
||||||
|
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
||||||
|
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
|
||||||
|
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
||||||
|
.replace(/^-{3,}\s*$/g, '')
|
||||||
|
.replace(/`(.+?)`/g, '$1')
|
||||||
|
.replace(/\n{2,}/g, '\n\n')
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
strip
|
||||||
|
}
|
||||||
@@ -1,753 +0,0 @@
|
|||||||
const modes = [
|
|
||||||
{
|
|
||||||
name: 'text',
|
|
||||||
label: 'Plain text',
|
|
||||||
mode: 'text'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'abap',
|
|
||||||
label: 'ABAP',
|
|
||||||
alias: [],
|
|
||||||
mode: 'abap'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'abc',
|
|
||||||
label: 'ABC',
|
|
||||||
alias: [],
|
|
||||||
mode: 'abc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'actionscript',
|
|
||||||
label: 'ActionScript',
|
|
||||||
alias: ['as'],
|
|
||||||
mode: 'actionscript'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ada',
|
|
||||||
label: 'Ada',
|
|
||||||
alias: [],
|
|
||||||
mode: 'ada'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'apache_conf',
|
|
||||||
label: 'Apache config',
|
|
||||||
alias: ['apache', 'conf'],
|
|
||||||
mode: 'apache_conf'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'applescript',
|
|
||||||
label: 'AppleScript',
|
|
||||||
alias: ['scpt'],
|
|
||||||
mode: 'applescript'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'asciidoc',
|
|
||||||
label: 'AsciiDoc',
|
|
||||||
alias: ['ascii', 'doc', 'txt'],
|
|
||||||
mode: 'asciidoc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'assembly_x86',
|
|
||||||
label: 'Assembly x86',
|
|
||||||
alias: ['assembly', 'x86', 'asm'],
|
|
||||||
mode: 'assembly_x86'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'autohotkey',
|
|
||||||
label: 'AutoHotkey',
|
|
||||||
alias: ['ahk'],
|
|
||||||
mode: 'autohotkey'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'batchfile',
|
|
||||||
label: 'Batch file',
|
|
||||||
alias: ['dos', 'windows', 'bat', 'cmd', 'btm'],
|
|
||||||
mode: 'batchfile'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'c',
|
|
||||||
label: 'C',
|
|
||||||
alias: ['c', 'h', 'clang', 'clang'],
|
|
||||||
mode: 'c_cpp'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cirru',
|
|
||||||
label: 'Cirru',
|
|
||||||
alias: [],
|
|
||||||
mode: 'cirru'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'clojure',
|
|
||||||
label: 'Clojure',
|
|
||||||
alias: ['clj', 'cljs', 'cljc', 'edn'],
|
|
||||||
mode: 'clojure'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cobol',
|
|
||||||
label: 'COBOL',
|
|
||||||
alias: ['cbl', 'cob', 'cpy'],
|
|
||||||
mode: 'cobol'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'coffee',
|
|
||||||
label: 'CoffeeScript',
|
|
||||||
alias: ['coffee'],
|
|
||||||
mode: 'coffee'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'coldfusion',
|
|
||||||
label: 'ColdFusion',
|
|
||||||
alias: ['cfm', 'cfc'],
|
|
||||||
mode: 'coldfusion'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cpp',
|
|
||||||
label: 'C++',
|
|
||||||
alias: ['cc', 'cpp', 'cxx', 'hh', 'c++', 'cplusplus'],
|
|
||||||
mode: 'c_cpp'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'csharp',
|
|
||||||
label: 'C#',
|
|
||||||
alias: ['cs', 'c#'],
|
|
||||||
mode: 'csharp'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'css',
|
|
||||||
label: 'CSS',
|
|
||||||
alias: ['cascade', 'stylesheet'],
|
|
||||||
mode: 'css'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'curly',
|
|
||||||
label: 'Curly',
|
|
||||||
alias: [],
|
|
||||||
mode: 'curly'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'd',
|
|
||||||
label: 'D',
|
|
||||||
alias: ['dlang'],
|
|
||||||
mode: 'd'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dockerfile',
|
|
||||||
label: 'DockerFile',
|
|
||||||
alias: ['docker'],
|
|
||||||
mode: 'docker'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dart',
|
|
||||||
label: 'Dart',
|
|
||||||
alias: [],
|
|
||||||
mode: 'dart'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'diff',
|
|
||||||
label: 'Diff',
|
|
||||||
alias: [],
|
|
||||||
mode: 'diff'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'django',
|
|
||||||
label: 'Django',
|
|
||||||
alias: [],
|
|
||||||
mode: 'djt'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dot',
|
|
||||||
label: 'DOT',
|
|
||||||
alias: ['gv'],
|
|
||||||
mode: 'dot'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'eiffel',
|
|
||||||
label: 'Eiffel',
|
|
||||||
alias: [],
|
|
||||||
mode: 'eiffel'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ejs',
|
|
||||||
label: 'EJS',
|
|
||||||
alias: [],
|
|
||||||
mode: 'ejs'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'elixir',
|
|
||||||
label: 'Elixir',
|
|
||||||
alias: ['ex', 'exs'],
|
|
||||||
mode: 'elixir'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'elm',
|
|
||||||
label: 'Elm',
|
|
||||||
alias: [],
|
|
||||||
mode: 'elm'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'erlang',
|
|
||||||
label: 'Erlang',
|
|
||||||
alias: ['erl', 'hrl'],
|
|
||||||
mode: 'erlang'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'forth',
|
|
||||||
label: 'Forth',
|
|
||||||
alias: ['fs', 'fth'],
|
|
||||||
mode: 'forth'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'freemaker',
|
|
||||||
label: 'Freemaker',
|
|
||||||
alias: ['ftl'],
|
|
||||||
mode: 'ftl'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gcode',
|
|
||||||
label: 'G-code',
|
|
||||||
alias: ['mpt', 'mpf', 'nc'],
|
|
||||||
mode: 'gcode'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gherkin',
|
|
||||||
label: 'Gherkin',
|
|
||||||
alias: ['cucumber'],
|
|
||||||
mode: 'gherkin'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'gitignore',
|
|
||||||
label: 'Gitignore',
|
|
||||||
alias: ['git'],
|
|
||||||
mode: 'gitignore'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'glsl',
|
|
||||||
label: 'GLSL',
|
|
||||||
alias: ['opengl', 'shading'],
|
|
||||||
mode: 'glsl'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'golang',
|
|
||||||
label: 'Go',
|
|
||||||
alias: ['go'],
|
|
||||||
mode: 'golang'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'groovy',
|
|
||||||
label: 'Groovy',
|
|
||||||
alias: [],
|
|
||||||
mode: 'grooby'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'haml',
|
|
||||||
label: 'Haml',
|
|
||||||
alias: [],
|
|
||||||
mode: 'haml'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'handlebars',
|
|
||||||
label: 'Handlebars',
|
|
||||||
alias: ['hbs'],
|
|
||||||
mode: 'handlebars'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'haskell',
|
|
||||||
label: 'Haskell',
|
|
||||||
alias: ['hs', 'lhs'],
|
|
||||||
mode: 'haskell'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'haxe',
|
|
||||||
label: 'Haxe',
|
|
||||||
alias: ['hx', 'hxml'],
|
|
||||||
mode: 'haxe'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'html',
|
|
||||||
label: 'HTML',
|
|
||||||
alias: [],
|
|
||||||
mode: 'html'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'html_ruby',
|
|
||||||
label: 'HTML (Ruby)',
|
|
||||||
alias: ['erb', 'rhtml'],
|
|
||||||
mode: 'html_ruby'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'jsx',
|
|
||||||
label: 'JSX',
|
|
||||||
alias: ['es', 'babel', 'js', 'jsx', 'react'],
|
|
||||||
mode: 'jsx'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'typescript',
|
|
||||||
label: 'TypeScript',
|
|
||||||
alias: ['ts'],
|
|
||||||
mode: 'typescript'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ini',
|
|
||||||
label: 'INI file',
|
|
||||||
alias: [],
|
|
||||||
mode: 'ini'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'io',
|
|
||||||
label: 'Io',
|
|
||||||
alias: [],
|
|
||||||
mode: 'io'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'jack',
|
|
||||||
label: 'Jack',
|
|
||||||
alias: [],
|
|
||||||
mode: 'jack'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'jade',
|
|
||||||
label: 'Jade',
|
|
||||||
alias: [],
|
|
||||||
mode: 'jade'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'java',
|
|
||||||
label: 'Java',
|
|
||||||
alias: [],
|
|
||||||
mode: 'java'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'javascript',
|
|
||||||
label: 'JavaScript',
|
|
||||||
alias: ['js', 'jscript', 'babel', 'es'],
|
|
||||||
mode: 'javascript'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'json',
|
|
||||||
label: 'JSON',
|
|
||||||
alias: [],
|
|
||||||
mode: 'json'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'jsoniq',
|
|
||||||
label: 'JSONiq',
|
|
||||||
alias: ['query'],
|
|
||||||
mode: 'jsoniq'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'jsp',
|
|
||||||
label: 'JSP',
|
|
||||||
alias: [],
|
|
||||||
mode: 'jsp'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'julia',
|
|
||||||
label: 'Julia',
|
|
||||||
alias: [],
|
|
||||||
mode: 'julia'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'latex',
|
|
||||||
label: 'Latex',
|
|
||||||
alias: ['tex'],
|
|
||||||
mode: 'latex'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'lean',
|
|
||||||
label: 'Lean',
|
|
||||||
alias: [],
|
|
||||||
mode: 'lean'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'less',
|
|
||||||
label: 'Less',
|
|
||||||
alias: [],
|
|
||||||
mode: 'less'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'liquid',
|
|
||||||
label: 'Liquid',
|
|
||||||
alias: [],
|
|
||||||
mode: 'liquid'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'lisp',
|
|
||||||
label: 'Lisp',
|
|
||||||
alias: ['lsp'],
|
|
||||||
mode: 'lisp'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'livescript',
|
|
||||||
label: 'LiveScript',
|
|
||||||
alias: ['ls'],
|
|
||||||
mode: 'livescript'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'logiql',
|
|
||||||
label: 'LogiQL',
|
|
||||||
alias: [],
|
|
||||||
mode: 'logiql'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'lsl',
|
|
||||||
label: 'LSL',
|
|
||||||
alias: [],
|
|
||||||
mode: 'lsl'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'lua',
|
|
||||||
label: 'Lua',
|
|
||||||
alias: [],
|
|
||||||
mode: 'lua'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'luapage',
|
|
||||||
label: 'Luapage',
|
|
||||||
alias: [],
|
|
||||||
mode: 'luapage'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'lucene',
|
|
||||||
label: 'Lucene',
|
|
||||||
alias: [],
|
|
||||||
mode: 'lucene'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'makefile',
|
|
||||||
label: 'Makefile',
|
|
||||||
alias: [],
|
|
||||||
mode: 'makefile'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'markdown',
|
|
||||||
label: 'Markdown',
|
|
||||||
alias: ['md'],
|
|
||||||
mode: 'markdown'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mask',
|
|
||||||
label: 'Mask',
|
|
||||||
alias: [],
|
|
||||||
mode: 'mask'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'matlab',
|
|
||||||
label: 'MATLAB',
|
|
||||||
alias: [],
|
|
||||||
mode: 'matlab'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'maze',
|
|
||||||
label: 'Maze',
|
|
||||||
alias: [],
|
|
||||||
mode: 'maze'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mel',
|
|
||||||
label: 'MEL',
|
|
||||||
alias: [],
|
|
||||||
mode: 'mel'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mipsassembler',
|
|
||||||
label: 'MIPS assembly',
|
|
||||||
alias: [],
|
|
||||||
mode: 'mipsassembler'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mushcode',
|
|
||||||
label: 'MUSHCode',
|
|
||||||
alias: [],
|
|
||||||
mode: 'mushcode'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mysql',
|
|
||||||
label: 'MySQL',
|
|
||||||
alias: [],
|
|
||||||
mode: 'mysql'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'nix',
|
|
||||||
label: 'Nix',
|
|
||||||
alias: [],
|
|
||||||
mode: 'nix'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'objectivec',
|
|
||||||
label: 'Objective C',
|
|
||||||
alias: ['objc'],
|
|
||||||
mode: 'objectivec'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ocaml',
|
|
||||||
label: 'OCaml',
|
|
||||||
alias: [],
|
|
||||||
mode: 'ocaml'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'pascal',
|
|
||||||
label: 'Pascal',
|
|
||||||
alias: [],
|
|
||||||
mode: 'pascal'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'perl',
|
|
||||||
label: 'Perl',
|
|
||||||
alias: [],
|
|
||||||
mode: 'perl'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'pgsql',
|
|
||||||
label: 'Postgres SQL',
|
|
||||||
alias: ['postgres'],
|
|
||||||
mode: 'pgsql'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'php',
|
|
||||||
label: 'PHP',
|
|
||||||
alias: [],
|
|
||||||
mode: 'php'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'powershell',
|
|
||||||
label: 'PowerShell',
|
|
||||||
alias: ['ps1'],
|
|
||||||
mode: 'powershell'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'praat',
|
|
||||||
label: 'Praat',
|
|
||||||
alias: [],
|
|
||||||
mode: 'praat'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'prolog',
|
|
||||||
label: 'Prolog',
|
|
||||||
alias: ['pl', 'pro'],
|
|
||||||
mode: 'prolog'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'properties',
|
|
||||||
label: 'Properties',
|
|
||||||
alias: [],
|
|
||||||
mode: 'properties'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'protobuf',
|
|
||||||
label: 'Protocol Buffers',
|
|
||||||
alias: ['protocol', 'buffers'],
|
|
||||||
mode: 'protobuf'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'python',
|
|
||||||
label: 'Python',
|
|
||||||
alias: ['py'],
|
|
||||||
mode: 'python'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'r',
|
|
||||||
label: 'R',
|
|
||||||
alias: ['rlang'],
|
|
||||||
mode: 'r'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rdoc',
|
|
||||||
label: 'RDoc',
|
|
||||||
alias: [],
|
|
||||||
mode: 'rdoc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ruby',
|
|
||||||
label: 'Ruby',
|
|
||||||
alias: ['rb'],
|
|
||||||
mode: 'ruby'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rust',
|
|
||||||
label: 'Rust',
|
|
||||||
alias: [],
|
|
||||||
mode: 'rust'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sass',
|
|
||||||
label: 'Sass',
|
|
||||||
alias: [],
|
|
||||||
mode: 'sass'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'scad',
|
|
||||||
label: 'SCAD',
|
|
||||||
alias: [],
|
|
||||||
mode: 'scad'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'scala',
|
|
||||||
label: 'Scala',
|
|
||||||
alias: [],
|
|
||||||
mode: 'scala'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'scheme',
|
|
||||||
label: 'Scheme',
|
|
||||||
alias: ['scm', 'ss'],
|
|
||||||
mode: 'scheme'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'scss',
|
|
||||||
label: 'Scss',
|
|
||||||
alias: [],
|
|
||||||
mode: 'scss'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sh',
|
|
||||||
label: 'Shell',
|
|
||||||
alias: ['shell'],
|
|
||||||
mode: 'sh'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sjs',
|
|
||||||
label: 'StratifiedJS',
|
|
||||||
alias: ['stratified'],
|
|
||||||
mode: 'sjs'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'smarty',
|
|
||||||
label: 'Smarty',
|
|
||||||
alias: [],
|
|
||||||
mode: 'smarty'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'snippets',
|
|
||||||
label: 'Snippets',
|
|
||||||
alias: [],
|
|
||||||
mode: 'snippets'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'soy_template',
|
|
||||||
label: 'Soy Template',
|
|
||||||
alias: ['soy'],
|
|
||||||
mode: 'soy_template'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'space',
|
|
||||||
label: 'Space',
|
|
||||||
alias: [],
|
|
||||||
mode: 'space'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sql',
|
|
||||||
label: 'SQL',
|
|
||||||
alias: [],
|
|
||||||
mode: 'sql'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sqlserver',
|
|
||||||
label: 'SQL Server',
|
|
||||||
alias: [],
|
|
||||||
mode: 'sqlserver'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'stylus',
|
|
||||||
label: 'Stylus',
|
|
||||||
alias: [],
|
|
||||||
mode: 'stylus'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'svg',
|
|
||||||
label: 'SVG',
|
|
||||||
alias: [],
|
|
||||||
mode: 'svg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'swift',
|
|
||||||
label: 'Swift',
|
|
||||||
alias: [],
|
|
||||||
mode: 'swift'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'swig',
|
|
||||||
label: 'SWIG',
|
|
||||||
alias: [],
|
|
||||||
mode: 'swig'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tcl',
|
|
||||||
label: 'Tcl',
|
|
||||||
alias: [],
|
|
||||||
mode: 'tcl'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tex',
|
|
||||||
label: 'TeX',
|
|
||||||
alias: [],
|
|
||||||
mode: 'tex'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'textile',
|
|
||||||
label: 'Textile',
|
|
||||||
alias: [],
|
|
||||||
mode: 'textile'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'toml',
|
|
||||||
label: 'TOML',
|
|
||||||
alias: [],
|
|
||||||
mode: 'toml'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'twig',
|
|
||||||
label: 'Twig',
|
|
||||||
alias: [],
|
|
||||||
mode: 'twig'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vala',
|
|
||||||
label: 'Vala',
|
|
||||||
alias: [],
|
|
||||||
mode: 'vala'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vbscript',
|
|
||||||
label: 'VBScript',
|
|
||||||
alias: ['vbs', 'vbe'],
|
|
||||||
mode: 'vbscript'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'velocity',
|
|
||||||
label: 'Velocity',
|
|
||||||
alias: [],
|
|
||||||
mode: 'velocity'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'verilog',
|
|
||||||
label: 'Verilog',
|
|
||||||
alias: [],
|
|
||||||
mode: 'verilog'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vhdl',
|
|
||||||
label: 'VHDL',
|
|
||||||
alias: [],
|
|
||||||
mode: 'vhdl'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'xml',
|
|
||||||
label: 'XML',
|
|
||||||
alias: [],
|
|
||||||
mode: 'xml'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'xquery',
|
|
||||||
label: 'XQuery',
|
|
||||||
alias: [],
|
|
||||||
mode: 'xquery'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'yaml',
|
|
||||||
label: 'YAML',
|
|
||||||
alias: [],
|
|
||||||
mode: 'yaml'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export default modes
|
|
||||||
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,7 +11,20 @@
|
|||||||
|
|
||||||
.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"]
|
||||||
|
.root
|
||||||
|
background-color $ui-dark-backgroundColor
|
||||||
|
.empty-message
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
.empty-message
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|||||||
11
browser/main/Detail/DetailVars.styl
Normal file
11
browser/main/Detail/DetailVars.styl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Varibales for note detail space.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Margin on the left side and the right side for NoteDetail component.
|
||||||
|
$note-detail-left-margin = 100px
|
||||||
|
$note-detail-right-margin = 120px
|
||||||
|
$snippet-note-detail-left-margin = 60px
|
||||||
|
$snippet-note-detail-right-margin = 80px
|
||||||
|
|
||||||
|
$note-detail-box-shadow = 2px 0 15px -8px #b1b1b1 inset
|
||||||
@@ -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'))
|
||||||
})
|
})
|
||||||
@@ -128,8 +129,8 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nextOption () {
|
nextOption () {
|
||||||
let { storages } = this.props
|
|
||||||
let { optionIndex } = this.state
|
let { optionIndex } = this.state
|
||||||
|
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,12 +185,12 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className, storages, 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 = []
|
||||||
storages.forEach((storage, index) => {
|
data.storageMap.forEach((storage, index) => {
|
||||||
storage.folders.forEach((folder) => {
|
storage.folders.forEach((folder) => {
|
||||||
options.push({
|
options.push({
|
||||||
storage: storage,
|
storage: storage,
|
||||||
@@ -198,9 +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]
|
||||||
|
|
||||||
let optionList = options
|
if (this.state.search.trim().length > 0) {
|
||||||
|
const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
||||||
|
options = options.filter((option) => filter.test(option.folder.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionList = options
|
||||||
.map((option, index) => {
|
.map((option, index) => {
|
||||||
return (
|
return (
|
||||||
<div styleName={index === this.state.optionIndex
|
<div styleName={index === this.state.optionIndex
|
||||||
@@ -254,16 +260,13 @@ 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={{borderColor: currentOption.folder.color}}
|
<span styleName='idle-label-name'>
|
||||||
>
|
|
||||||
{currentOption.folder.name}
|
{currentOption.folder.name}
|
||||||
<span styleName='idle-label-name-surfix'>in {currentOption.storage.name}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<i styleName='idle-caret' className='fa fa-fw fa-caret-down'/>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,40 @@
|
|||||||
.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 white
|
background-color $ui-button--hover-backgroundColor
|
||||||
border-color $ui-borderColor
|
|
||||||
|
|
||||||
.root--search, .root--focus
|
.root--search, .root--focus
|
||||||
@extend .root
|
@extend .root
|
||||||
background-color white
|
background-color $ui-noteDetail-backgroundColor = #fff
|
||||||
border-color $ui-input--focus-borderColor
|
border-color $ui-input--focus-borderColor
|
||||||
|
width 154px
|
||||||
|
height 30px
|
||||||
&:hover
|
&:hover
|
||||||
background-color white
|
border-color $ui-input--focus-borderColor = #fff
|
||||||
border-color $ui-input--focus-borderColor
|
|
||||||
|
|
||||||
.idle
|
.idle
|
||||||
position relative
|
position relative
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
|
||||||
.idle-label
|
.idle-label
|
||||||
absolute left top
|
|
||||||
padding 0 0 0 5px
|
|
||||||
right 20px
|
right 20px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
|
||||||
.idle-label-name
|
.idle-label-name
|
||||||
border-left solid 4px transparent
|
font-size 13px
|
||||||
padding 2px 5px
|
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
|
||||||
@@ -40,33 +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
|
||||||
border $ui-border
|
top 30px
|
||||||
|
max-height 450px
|
||||||
|
min-width 150px
|
||||||
|
overflow auto
|
||||||
z-index 200
|
z-index 200
|
||||||
|
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
|
||||||
@@ -80,9 +87,55 @@
|
|||||||
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"]
|
||||||
|
.root
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
color white
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
|
||||||
|
.root--search, .root--focus
|
||||||
|
@extend .root
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
border-color $ui-input--focus-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
border-color $ui-input--focus-borderColor
|
||||||
|
|
||||||
|
.idle-label-name-surfix
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
.search-input
|
||||||
|
color white
|
||||||
|
background-color transparent
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
|
||||||
|
.search-optionList
|
||||||
|
color white
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
|
|
||||||
|
.search-optionList-item
|
||||||
|
&:hover
|
||||||
|
background-color lighten($ui-dark-button--hover-backgroundColor, 15%)
|
||||||
|
|
||||||
|
.search-optionList-item--active
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
color $ui-dark-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
color $ui-dark-button--active-color
|
||||||
|
.search-optionList-item-name-surfix
|
||||||
|
color $ui-dark-inactive-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)
|
||||||
10
browser/main/Detail/InfoButton.styl
Normal file
10
browser/main/Detail/InfoButton.styl
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.control-infoButton
|
||||||
|
top 10px
|
||||||
|
topBarButtonRight()
|
||||||
|
|
||||||
|
.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,84 +1,88 @@
|
|||||||
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 MarkdownSplitEditor from 'browser/components/MarkdownSplitEditor'
|
||||||
|
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/markdownTextHelper'
|
||||||
|
import StatusBar from '../StatusBar'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||||
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
import TrashButton from './TrashButton'
|
||||||
|
import 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 = remote.Menu
|
const { dialog } = remote
|
||||||
const MenuItem = remote.MenuItem
|
|
||||||
|
|
||||||
class MarkdownNoteDetail extends React.Component {
|
class MarkdownNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isMovingNote: false,
|
||||||
note: Object.assign({
|
note: Object.assign({
|
||||||
title: '',
|
title: '',
|
||||||
content: '',
|
content: ''
|
||||||
isMovingNote: false,
|
}, props.note),
|
||||||
isDeleting: false
|
isLockButtonShown: false,
|
||||||
}, props.note)
|
isLocked: false,
|
||||||
|
editorType: props.config.editor.type
|
||||||
}
|
}
|
||||||
this.dispatchTimer = null
|
this.dispatchTimer = null
|
||||||
|
|
||||||
|
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
this.refs.content.focus()
|
this.refs.content.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
||||||
|
if (this.saveQueue != null) this.saveNow()
|
||||||
this.setState({
|
this.setState({
|
||||||
note: Object.assign({}, nextProps.note),
|
note: Object.assign({}, nextProps.note)
|
||||||
isDeleting: false
|
|
||||||
}, () => {
|
}, () => {
|
||||||
this.refs.content.reload()
|
this.refs.content.reload()
|
||||||
this.refs.tags.reset()
|
if (this.refs.tags) this.refs.tags.reset()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findTitle (value) {
|
componentWillUnmount () {
|
||||||
let splitted = value.split('\n')
|
if (this.saveQueue != null) this.saveNow()
|
||||||
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) {
|
componentDidUnmount () {
|
||||||
for (let i = 0; i < splitted.length; i++) {
|
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
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.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({
|
||||||
@@ -89,41 +93,52 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save () {
|
save () {
|
||||||
let { note, dispatch } = this.props
|
clearTimeout(this.saveQueue)
|
||||||
|
this.saveQueue = setTimeout(() => {
|
||||||
|
this.saveNow()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({
|
saveNow () {
|
||||||
type: 'UPDATE_NOTE',
|
const { note, dispatch } = this.props
|
||||||
note: this.state.note
|
clearTimeout(this.saveQueue)
|
||||||
})
|
this.saveQueue = null
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.updateNote(note.storage, note.folder, note.key, this.state.note)
|
.updateNote(note.storage, note.key, this.state.note)
|
||||||
|
.then((note) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_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.folder, note.key, newStorageKey, newFolderKey)
|
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||||
.then((newNote) => {
|
.then((newNote) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
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',
|
||||||
note: note,
|
originNote: note,
|
||||||
newNote: newNote
|
note: newNote
|
||||||
})
|
})
|
||||||
hashHistory.replace({
|
hashHistory.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: newNote.uniqueKey
|
key: newNote.storage + '-' + newNote.key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -134,7 +149,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
|
||||||
|
|
||||||
@@ -149,128 +165,274 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleShareButtonClick (e) {
|
exportAsMd () {
|
||||||
let menu = new Menu()
|
ee.emit('export:save-md')
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: 'Export as a File',
|
|
||||||
click: (e) => this.handlePreferencesButtonClick(e)
|
|
||||||
}))
|
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: 'Export to Web',
|
|
||||||
disabled: true,
|
|
||||||
click: (e) => this.handlePreferencesButtonClick(e)
|
|
||||||
}))
|
|
||||||
menu.popup(remote.getCurrentWindow())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextButtonClick (e) {
|
exportAsTxt () {
|
||||||
let menu = new Menu()
|
ee.emit('export:save-text')
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: 'Delete',
|
|
||||||
click: (e) => this.handleDeleteMenuClick(e)
|
|
||||||
}))
|
|
||||||
menu.popup(remote.getCurrentWindow())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteMenuClick (e) {
|
handleTrashButtonClick (e) {
|
||||||
this.setState({
|
const { note } = this.state
|
||||||
isDeleting: true
|
const { isTrashed } = note
|
||||||
|
|
||||||
|
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
|
||||||
handleDeleteConfirmButtonClick (e) {
|
|
||||||
let { note, dispatch } = this.props
|
|
||||||
dataApi
|
dataApi
|
||||||
.removeNote(note.storage, note.folder, note.key)
|
.deleteNote(note.storage, note.key)
|
||||||
.then(() => {
|
.then((data) => {
|
||||||
let dispatchHandler = () => {
|
const dispatchHandler = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'REMOVE_NOTE',
|
type: 'DELETE_NOTE',
|
||||||
note: note
|
storageKey: data.storageKey,
|
||||||
|
noteKey: data.noteKey
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ee.once('list:moved', dispatchHandler)
|
ee.once('list:moved', dispatchHandler)
|
||||||
|
})
|
||||||
|
} 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')
|
ee.emit('list:next')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteCancelButtonClick (e) {
|
handleFullScreenButton (e) {
|
||||||
this.setState({
|
ee.emit('editor:fullscreen')
|
||||||
isDeleting: false
|
}
|
||||||
|
|
||||||
|
handleLockButtonMouseDown (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
ee.emit('editor:lock')
|
||||||
|
this.setState({ isLocked: !this.state.isLocked })
|
||||||
|
if (this.state.isLocked) this.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
getToggleLockButton () {
|
||||||
|
return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteKeyDown (e) {
|
||||||
|
if (e.keyCode === 27) this.handleDeleteCancelButtonClick(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleToggleLockButton (event, noteStatus) {
|
||||||
|
// first argument event is not used
|
||||||
|
if (this.props.config.editor.switchPreview === 'BLUR' && noteStatus === 'CODE') {
|
||||||
|
this.setState({isLockButtonShown: true})
|
||||||
|
} else {
|
||||||
|
this.setState({isLockButtonShown: false})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFocus (e) {
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSwitchMode (type) {
|
||||||
|
this.setState({ editorType: type }, () => {
|
||||||
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
|
newConfig.editor.type = type
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderEditor () {
|
||||||
|
const { config, ignorePreviewPointerEvents } = this.props
|
||||||
|
const { note } = this.state
|
||||||
|
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
||||||
|
return <MarkdownEditor
|
||||||
|
ref='content'
|
||||||
|
styleName='body-noteEditor'
|
||||||
|
config={config}
|
||||||
|
value={note.content}
|
||||||
|
storageKey={note.storage}
|
||||||
|
onChange={(e) => this.handleChange(e)}
|
||||||
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
/>
|
||||||
|
} else {
|
||||||
|
return <MarkdownSplitEditor
|
||||||
|
ref='content'
|
||||||
|
config={config}
|
||||||
|
value={note.content}
|
||||||
|
storageKey={note.storage}
|
||||||
|
onChange={(e) => this.handleChange(e)}
|
||||||
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { storages, config } = this.props
|
const { data, location } = this.props
|
||||||
let { note } = this.state
|
const { note, editorType } = 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)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div styleName='mode-tab'>
|
||||||
|
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => this.handleSwitchMode('SPLIT')}>
|
||||||
|
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-split-on.svg' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
||||||
|
</div>
|
||||||
|
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => this.handleSwitchMode('EDITOR_PREVIEW')}>
|
||||||
|
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : '../resources/icon/icon-mode-markdown-off.svg'} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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-full.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'
|
||||||
>
|
>
|
||||||
{this.state.isDeleting
|
|
||||||
? <div styleName='info'>
|
|
||||||
<div styleName='info-delete'>
|
|
||||||
|
|
||||||
<span styleName='info-delete-message'>
|
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||||
Are you sure to delete this note?
|
|
||||||
</span>
|
|
||||||
<button styleName='info-delete-cancelButton'
|
|
||||||
onClick={(e) => this.handleDeleteCancelButtonClick(e)}
|
|
||||||
>Cancel</button>
|
|
||||||
<button styleName='info-delete-confirmButton'
|
|
||||||
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
|
|
||||||
>Confirm</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
: <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'
|
|
||||||
storages={storages}
|
|
||||||
onChange={(e) => this.handleFolderChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div styleName='info-left-bottom'>
|
|
||||||
<TagSelect
|
|
||||||
styleName='info-left-bottom-tagSelect'
|
|
||||||
ref='tags'
|
|
||||||
value={this.state.note.tags}
|
|
||||||
onChange={(e) => this.handleChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div styleName='info-right'>
|
|
||||||
<StarButton styleName='info-right-button'
|
|
||||||
onClick={(e) => this.handleStarButtonClick(e)}
|
|
||||||
isActive={note.isStarred}
|
|
||||||
/>
|
|
||||||
<button styleName='info-right-button'
|
|
||||||
onClick={(e) => this.handleShareButtonClick(e)}
|
|
||||||
>
|
|
||||||
<i className='fa fa-share-alt fa-fw'/>
|
|
||||||
</button>
|
|
||||||
<button styleName='info-right-button'
|
|
||||||
onClick={(e) => this.handleContextButtonClick(e)}
|
|
||||||
>
|
|
||||||
<i className='fa fa-ellipsis-v'/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div styleName='body'>
|
<div styleName='body'>
|
||||||
<MarkdownEditor
|
{this.renderEditor()}
|
||||||
ref='content'
|
|
||||||
styleName='body-noteEditor'
|
|
||||||
config={config}
|
|
||||||
value={this.state.note.content}
|
|
||||||
onChange={(e) => this.handleChange(e)}
|
|
||||||
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<StatusBar
|
||||||
|
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||||
|
date={note.updatedAt}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +1,99 @@
|
|||||||
$info-height = 75px
|
@import('NoteDetailInfo')
|
||||||
|
@import('DetailVars')
|
||||||
|
|
||||||
.root
|
.root
|
||||||
absolute top bottom right
|
absolute top right bottom
|
||||||
border-width 0 0 1px
|
border-left 1px solid alpha(#DEDEDE, 60%)
|
||||||
border-style solid
|
background-color $ui-noteDetail-backgroundColor
|
||||||
border-color $ui-borderColor
|
box-shadow none
|
||||||
|
padding 20px 40px
|
||||||
|
|
||||||
.info
|
.lock-button
|
||||||
absolute top left right
|
padding-bottom 3px
|
||||||
height $info-height
|
|
||||||
border-bottom $ui-border
|
|
||||||
background-color $ui-backgroundColor
|
|
||||||
|
|
||||||
.info-delete
|
.control-lockButton
|
||||||
height 80px
|
top 150px
|
||||||
clearfix()
|
topBarButtonRight()
|
||||||
|
|
||||||
.info-delete-message
|
.trashed-infopanel
|
||||||
height 80px
|
top 40px
|
||||||
line-height 80px
|
position relative
|
||||||
padding 0 25px
|
|
||||||
float left
|
|
||||||
|
|
||||||
.info-delete-confirmButton
|
.control-fullScreenButton
|
||||||
float right
|
top 80px
|
||||||
margin 25px 5px 0
|
topBarButtonRight()
|
||||||
height 30px
|
|
||||||
padding 0 25px
|
|
||||||
border-radius 2px
|
|
||||||
border none
|
|
||||||
color $ui-text-color
|
|
||||||
colorDangerButton()
|
|
||||||
|
|
||||||
.info-delete-cancelButton
|
|
||||||
float right
|
|
||||||
height 30px
|
|
||||||
margin 25px 5px 0
|
|
||||||
padding 0 25px
|
|
||||||
border $ui-border
|
|
||||||
border-radius 2px
|
|
||||||
color $ui-text-color
|
|
||||||
colorDefaultButton()
|
|
||||||
|
|
||||||
.info-left
|
|
||||||
float left
|
|
||||||
padding 0 5px
|
|
||||||
|
|
||||||
.info-left-top
|
|
||||||
height 40px
|
|
||||||
line-height 40px
|
|
||||||
|
|
||||||
.info-left-top-folderSelect
|
|
||||||
display inline-block
|
|
||||||
height 34px
|
|
||||||
width 200px
|
|
||||||
vertical-align middle
|
|
||||||
|
|
||||||
.info-left-bottom
|
|
||||||
height 30px
|
|
||||||
|
|
||||||
.info-left-bottom-tagSelect
|
|
||||||
height 30px
|
|
||||||
line-height 30px
|
|
||||||
|
|
||||||
.info-right
|
|
||||||
float right
|
|
||||||
|
|
||||||
.info-right-button
|
|
||||||
width 34px
|
|
||||||
height 34px
|
|
||||||
border-radius 17px
|
|
||||||
navButtonColor()
|
|
||||||
border $ui-border
|
|
||||||
font-size 14px
|
|
||||||
margin 8px 2px
|
|
||||||
padding 0
|
|
||||||
&:active
|
|
||||||
border-color $ui-button--focus-borderColor
|
|
||||||
&:hover .left-control-newPostButton-tooltip
|
|
||||||
display block
|
|
||||||
&:focus
|
|
||||||
border-color $ui-button--focus-borderColor
|
|
||||||
|
|
||||||
.body
|
.body
|
||||||
absolute bottom left right
|
absolute left right
|
||||||
top $info-height
|
left 0
|
||||||
|
right 0
|
||||||
|
top $info-height + $info-margin-under-border
|
||||||
|
bottom $statusBar-height
|
||||||
|
margin 0 45px
|
||||||
.body-noteEditor
|
.body-noteEditor
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
|
|
||||||
|
.mode-tab
|
||||||
|
border 1px solid #eee
|
||||||
|
height 34px
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
div
|
||||||
|
width 40px
|
||||||
|
height 100%
|
||||||
|
background-color #f9f9f9
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
cursor pointer
|
||||||
|
&:first-child
|
||||||
|
border-right 1px solid #eee
|
||||||
|
.active
|
||||||
|
background-color #fff
|
||||||
|
box-shadow 2px 0px 7px #eee
|
||||||
|
z-index 1
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.root
|
||||||
|
box-shadow $note-detail-box-shadow
|
||||||
|
border none
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
box-shadow none
|
||||||
|
border-left 1px solid $ui-dark-borderColor
|
||||||
|
|
||||||
|
.control-lockButton
|
||||||
|
topBarButtonDark()
|
||||||
|
|
||||||
|
.control-lockButton-tooltip
|
||||||
|
darkTooltip()
|
||||||
|
|
||||||
|
.control-fullScreenButton
|
||||||
|
topBarButtonDark()
|
||||||
|
|
||||||
|
.mode-tab
|
||||||
|
border 1px solid #444444
|
||||||
|
div
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
&:first-child
|
||||||
|
border-right 1px solid #444444
|
||||||
|
.active
|
||||||
|
background-color #3A404C
|
||||||
|
box-shadow 2px 0px 7px #444444
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.mode-tab
|
||||||
|
border 1px solid #586E75
|
||||||
|
div
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
&:first-child
|
||||||
|
border-right 1px solid #586E75
|
||||||
|
.active
|
||||||
|
background-color #002B36
|
||||||
|
box-shadow 2px 0px 7px #222222
|
||||||
104
browser/main/Detail/NoteDetailInfo.styl
Normal file
104
browser/main/Detail/NoteDetailInfo.styl
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
@import('DetailVars')
|
||||||
|
|
||||||
|
$info-height = 50px
|
||||||
|
$info-margin-under-border = 30px
|
||||||
|
|
||||||
|
.info
|
||||||
|
absolute top left right
|
||||||
|
left 0
|
||||||
|
right 0
|
||||||
|
height $info-height
|
||||||
|
border-bottom 1px solid #eee
|
||||||
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
width 100%
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
|
||||||
|
.info-left
|
||||||
|
padding 0 10px
|
||||||
|
width 100%
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
|
||||||
|
|
||||||
|
.info-left-top-folderSelect
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
|
||||||
|
.info-left-button
|
||||||
|
width 34px
|
||||||
|
height 34px
|
||||||
|
navButtonColor()
|
||||||
|
color $ui-favorite-star-button-color
|
||||||
|
font-size 14px
|
||||||
|
line-height 0
|
||||||
|
margin 13px 2px
|
||||||
|
padding 0
|
||||||
|
border-radius 17px
|
||||||
|
&:hover .info-left-button-tooltip
|
||||||
|
opacity 1
|
||||||
|
&:focus
|
||||||
|
border-color $ui-favorite-star-button-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color $ui-favorite-star-button-color
|
||||||
|
color $ui-button--color
|
||||||
|
|
||||||
|
.info-right
|
||||||
|
position absolute
|
||||||
|
right 40px
|
||||||
|
top 60px
|
||||||
|
bottom 1px
|
||||||
|
padding-left 30px
|
||||||
|
z-index 101
|
||||||
|
|
||||||
|
.undo-button
|
||||||
|
width 34px
|
||||||
|
height 34px
|
||||||
|
border-radius 17px
|
||||||
|
font-size 14px
|
||||||
|
margin 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"]
|
||||||
|
.info
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.info-delete
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.info-delete-confirmButton
|
||||||
|
colorDarkDangerButton()
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.info-delete-cancelButton
|
||||||
|
colorDarkDefaultButton()
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.info-right
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.undo-button
|
||||||
|
topBarButtonDark()
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.info
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
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,95 +1,96 @@
|
|||||||
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'
|
||||||
|
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||||
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 modes from 'browser/lib/modes'
|
|
||||||
import { hashHistory } from 'react-router'
|
import { hashHistory } from 'react-router'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
import CodeMirror from 'codemirror'
|
||||||
|
import SnippetTab from 'browser/components/SnippetTab'
|
||||||
|
import StatusBar from '../StatusBar'
|
||||||
|
import context from 'browser/lib/context'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
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) {
|
||||||
|
switch (name) {
|
||||||
|
case 'ejs':
|
||||||
|
return 'Embedded Javascript'
|
||||||
|
case 'html_ruby':
|
||||||
|
return 'Embedded Ruby'
|
||||||
|
case 'objectivec':
|
||||||
|
return 'Objective C'
|
||||||
|
case 'text':
|
||||||
|
return 'Plain Text'
|
||||||
|
default:
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const Menu = remote.Menu
|
const { Menu, MenuItem, dialog } = remote
|
||||||
const MenuItem = remote.MenuItem
|
|
||||||
|
|
||||||
class SnippetNoteDetail extends React.Component {
|
class SnippetNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isMovingNote: false,
|
||||||
snippetIndex: 0,
|
snippetIndex: 0,
|
||||||
note: Object.assign({
|
note: Object.assign({
|
||||||
description: ''
|
description: ''
|
||||||
}, props.note, {
|
}, props.note, {
|
||||||
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
|
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
|
||||||
}),
|
})
|
||||||
isDeleting: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
|
||||||
this.refs.description.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key) {
|
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
||||||
let nextNote = Object.assign({
|
if (this.saveQueue != null) this.saveNow()
|
||||||
|
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))
|
||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
snippetIndex: 0,
|
snippetIndex: 0,
|
||||||
note: nextNote,
|
note: nextNote
|
||||||
isDeleting: false
|
|
||||||
}, () => {
|
}, () => {
|
||||||
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()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findTitle (value) {
|
componentWillUnmount () {
|
||||||
let splitted = value.split('\n')
|
if (this.saveQueue != null) this.saveNow()
|
||||||
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
|
||||||
@@ -99,41 +100,52 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
save () {
|
save () {
|
||||||
let { note, dispatch } = this.props
|
clearTimeout(this.saveQueue)
|
||||||
|
this.saveQueue = setTimeout(() => {
|
||||||
|
this.saveNow()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
dispatch({
|
saveNow () {
|
||||||
type: 'UPDATE_NOTE',
|
const { note, dispatch } = this.props
|
||||||
note: this.state.note
|
clearTimeout(this.saveQueue)
|
||||||
})
|
this.saveQueue = null
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.updateNote(note.storage, note.folder, note.key, this.state.note)
|
.updateNote(note.storage, note.key, this.state.note)
|
||||||
|
.then((note) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_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.folder, note.key, newStorageKey, newFolderKey)
|
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||||
.then((newNote) => {
|
.then((newNote) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
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',
|
||||||
note: note,
|
originNote: note,
|
||||||
newNote: newNote
|
note: newNote
|
||||||
})
|
})
|
||||||
hashHistory.replace({
|
hashHistory.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: newNote.uniqueKey
|
key: newNote.storage + '-' + newNote.key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -144,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
|
||||||
|
|
||||||
@@ -159,98 +172,134 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleShareButtonClick (e) {
|
handleTrashButtonClick (e) {
|
||||||
let menu = new Menu()
|
const { note } = this.state
|
||||||
menu.append(new MenuItem({
|
const { isTrashed } = note
|
||||||
label: 'Export as a File',
|
|
||||||
disabled: true,
|
|
||||||
click: (e) => this.handlePreferencesButtonClick(e)
|
|
||||||
}))
|
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: 'Export to Web',
|
|
||||||
disabled: true,
|
|
||||||
click: (e) => this.handlePreferencesButtonClick(e)
|
|
||||||
}))
|
|
||||||
menu.popup(remote.getCurrentWindow())
|
|
||||||
}
|
|
||||||
|
|
||||||
handleContextButtonClick (e) {
|
if (isTrashed) {
|
||||||
let menu = new Menu()
|
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
menu.append(new MenuItem({
|
type: 'warning',
|
||||||
label: 'Delete',
|
message: 'Confirm note deletion',
|
||||||
click: (e) => this.handleDeleteMenuClick(e)
|
detail: 'This will permanently remove this note.',
|
||||||
}))
|
buttons: ['Confirm', 'Cancel']
|
||||||
menu.popup(remote.getCurrentWindow())
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDeleteMenuClick (e) {
|
|
||||||
this.setState({
|
|
||||||
isDeleting: true
|
|
||||||
})
|
})
|
||||||
}
|
if (dialogueButtonIndex === 1) return
|
||||||
|
const { note, dispatch } = this.props
|
||||||
handleDeleteConfirmButtonClick (e) {
|
|
||||||
let { note, dispatch } = this.props
|
|
||||||
dataApi
|
dataApi
|
||||||
.removeNote(note.storage, note.folder, note.key)
|
.deleteNote(note.storage, note.key)
|
||||||
.then(() => {
|
.then((data) => {
|
||||||
let dispatchHandler = () => {
|
const dispatchHandler = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'REMOVE_NOTE',
|
type: 'DELETE_NOTE',
|
||||||
note: note
|
storageKey: data.storageKey,
|
||||||
|
noteKey: data.noteKey
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ee.once('list:moved', dispatchHandler)
|
ee.once('list:moved', dispatchHandler)
|
||||||
|
})
|
||||||
|
} 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')
|
ee.emit('list:next')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteCancelButtonClick (e) {
|
handleFullScreenButton (e) {
|
||||||
this.setState({
|
ee.emit('editor:fullscreen')
|
||||||
isDeleting: false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTabPlusButtonClick (e) {
|
handleTabPlusButtonClick (e) {
|
||||||
let { note } = this.state
|
this.addSnippet()
|
||||||
|
|
||||||
note.snippets = note.snippets.concat([{
|
|
||||||
name: '',
|
|
||||||
mode: 'text',
|
|
||||||
content: ''
|
|
||||||
}])
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
note
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTabButtonClick (index) {
|
handleTabButtonClick (e, index) {
|
||||||
return (e) => {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
snippetIndex: index
|
snippetIndex: index
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTabDragStart (e, index) {
|
||||||
|
e.dataTransfer.setData('text/plain', index)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTabDeleteButtonClick (index) {
|
handleTabDrop (e, index) {
|
||||||
return (e) => {
|
const oldIndex = parseInt(e.dataTransfer.getData('text'))
|
||||||
if (this.state.note.snippets.length > 1) {
|
|
||||||
let snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets.splice(index, 1)
|
const draggedSnippet = snippets[oldIndex]
|
||||||
this.state.note.snippets = snippets
|
snippets[oldIndex] = snippets[index]
|
||||||
this.setState({
|
snippets[index] = draggedSnippet
|
||||||
note: this.state.note
|
const snippetIndex = index
|
||||||
|
|
||||||
|
const note = Object.assign({}, this.state.note, {snippets})
|
||||||
|
this.setState({ note, snippetIndex }, () => {
|
||||||
|
this.save()
|
||||||
|
this.refs['code-' + index].reload()
|
||||||
|
this.refs['code-' + oldIndex].reload()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTabDeleteButtonClick (e, index) {
|
||||||
|
if (this.state.note.snippets.length > 1) {
|
||||||
|
if (this.state.note.snippets[index].content.trim().length > 0) {
|
||||||
|
const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Delete a snippet',
|
||||||
|
detail: 'This work cannot be undone.',
|
||||||
|
buttons: ['Confirm', 'Cancel']
|
||||||
|
})
|
||||||
|
if (dialogIndex === 0) {
|
||||||
|
this.deleteSnippetByIndex(index)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.deleteSnippetByIndex(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNameInputChange (index) {
|
deleteSnippetByIndex (index) {
|
||||||
return (e) => {
|
const snippets = this.state.note.snippets.slice()
|
||||||
let snippets = this.state.note.snippets.slice()
|
snippets.splice(index, 1)
|
||||||
snippets[index].name = e.target.value
|
const note = Object.assign({}, this.state.note, {snippets})
|
||||||
this.state.note.snippets = snippets
|
const snippetIndex = this.state.snippetIndex >= snippets.length
|
||||||
|
? snippets.length - 1
|
||||||
|
: this.state.snippetIndex
|
||||||
|
this.setState({ note, snippetIndex }, () => {
|
||||||
|
this.save()
|
||||||
|
this.refs['code-' + this.state.snippetIndex].reload()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renameSnippetByIndex (index, name) {
|
||||||
|
const snippets = this.state.note.snippets.slice()
|
||||||
|
snippets[index].name = name
|
||||||
|
const syntax = CodeMirror.findModeByFileName(name.trim())
|
||||||
|
const mode = syntax != null ? syntax.name : null
|
||||||
|
if (mode != null) {
|
||||||
|
snippets[index].mode = mode
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SNIPPET_LANG', {
|
||||||
|
name: mode
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
note: this.state.note
|
note: this.state.note
|
||||||
@@ -258,40 +307,30 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
handleModeButtonClick (index) {
|
|
||||||
return (e) => {
|
|
||||||
let menu = new Menu()
|
|
||||||
modes.forEach((mode) => {
|
|
||||||
menu.append(new MenuItem({
|
|
||||||
label: mode.label,
|
|
||||||
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
menu.popup(remote.getCurrentWindow())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
}, () => {
|
}, () => {
|
||||||
@@ -300,69 +339,224 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleKeyDown (e) {
|
||||||
|
switch (e.keyCode) {
|
||||||
|
case 9:
|
||||||
|
if (e.ctrlKey && !e.shiftKey) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.jumpNextTab()
|
||||||
|
} else if (e.ctrlKey && e.shiftKey) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.jumpPrevTab()
|
||||||
|
} else if (!e.ctrlKey && !e.shiftKey && e.target === this.refs.description) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.focusEditor()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 76:
|
||||||
|
{
|
||||||
|
const isSuper = global.process.platform === 'darwin'
|
||||||
|
? e.metaKey
|
||||||
|
: e.ctrlKey
|
||||||
|
if (isSuper) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 84:
|
||||||
|
{
|
||||||
|
const isSuper = global.process.platform === 'darwin'
|
||||||
|
? e.metaKey
|
||||||
|
: e.ctrlKey
|
||||||
|
if (isSuper) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.addSnippet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModeButtonClick (e, index) {
|
||||||
|
const menu = new Menu()
|
||||||
|
CodeMirror.modeInfo.forEach((mode) => {
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: mode.name,
|
||||||
|
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
menu.popup(remote.getCurrentWindow())
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIndentTypeButtonClick (e) {
|
||||||
|
context.popup([
|
||||||
|
{
|
||||||
|
label: 'tab',
|
||||||
|
click: (e) => this.handleIndentTypeItemClick(e, 'tab')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'space',
|
||||||
|
click: (e) => this.handleIndentTypeItemClick(e, 'space')
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIndentSizeButtonClick (e) {
|
||||||
|
context.popup([
|
||||||
|
{
|
||||||
|
label: '2',
|
||||||
|
click: (e) => this.handleIndentSizeItemClick(e, 2)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '4',
|
||||||
|
click: (e) => this.handleIndentSizeItemClick(e, 4)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '8',
|
||||||
|
click: (e) => this.handleIndentSizeItemClick(e, 8)
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIndentSizeItemClick (e, indentSize) {
|
||||||
|
const { config, dispatch } = this.props
|
||||||
|
const editor = Object.assign({}, config.editor, {
|
||||||
|
indentSize
|
||||||
|
})
|
||||||
|
ConfigManager.set({
|
||||||
|
editor
|
||||||
|
})
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_CONFIG',
|
||||||
|
config: {
|
||||||
|
editor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIndentTypeItemClick (e, indentType) {
|
||||||
|
const { config, dispatch } = this.props
|
||||||
|
const editor = Object.assign({}, config.editor, {
|
||||||
|
indentType
|
||||||
|
})
|
||||||
|
ConfigManager.set({
|
||||||
|
editor
|
||||||
|
})
|
||||||
|
dispatch({
|
||||||
|
type: 'SET_CONFIG',
|
||||||
|
config: {
|
||||||
|
editor
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
focus () {
|
||||||
|
this.refs.description.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
addSnippet () {
|
||||||
|
const { note } = this.state
|
||||||
|
|
||||||
|
note.snippets = note.snippets.concat([{
|
||||||
|
name: '',
|
||||||
|
mode: 'Plain Text',
|
||||||
|
content: ''
|
||||||
|
}])
|
||||||
|
const snippetIndex = note.snippets.length - 1
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
note,
|
||||||
|
snippetIndex
|
||||||
|
}, () => {
|
||||||
|
this.refs['tab-' + snippetIndex].startRenaming()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
jumpNextTab () {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length
|
||||||
|
}, () => {
|
||||||
|
this.focusEditor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
jumpPrevTab () {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length
|
||||||
|
}, () => {
|
||||||
|
this.focusEditor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
focusEditor () {
|
||||||
|
console.log('code-' + this.state.snippetIndex)
|
||||||
|
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 { storages, 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 <div styleName={isActive
|
|
||||||
? 'tabList-item--active'
|
return <SnippetTab
|
||||||
: 'tabList-item'
|
|
||||||
}
|
|
||||||
key={index}
|
key={index}
|
||||||
>
|
ref={'tab-' + index}
|
||||||
<button styleName='tabList-item-button'
|
snippet={snippet}
|
||||||
onClick={(e) => this.handleTabButtonClick(index)(e)}
|
isActive={isActive}
|
||||||
>
|
onClick={(e) => this.handleTabButtonClick(e, index)}
|
||||||
{snippet.name.trim().length > 0
|
onDelete={(e) => this.handleTabDeleteButtonClick(e, index)}
|
||||||
? snippet.name
|
onRename={(name) => this.renameSnippetByIndex(index, name)}
|
||||||
: <span styleName='tabList-item-unnamed'>
|
isDeletable={note.snippets.length > 1}
|
||||||
Unnamed
|
onDragStart={(e) => this.handleTabDragStart(e, index)}
|
||||||
</span>
|
onDrop={(e) => this.handleTabDrop(e, index)}
|
||||||
}
|
/>
|
||||||
</button>
|
|
||||||
{note.snippets.length > 1 &&
|
|
||||||
<button styleName='tabList-item-deleteButton'
|
|
||||||
onClick={(e) => this.handleTabDeleteButtonClick(index)(e)}
|
|
||||||
>
|
|
||||||
<i className='fa fa-times'/>
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
})
|
})
|
||||||
let viewList = note.snippets.map((snippet, index) => {
|
|
||||||
let isActive = this.state.snippetIndex === index
|
const viewList = note.snippets.map((snippet, index) => {
|
||||||
let mode = snippet.mode === 'text'
|
const isActive = this.state.snippetIndex === index
|
||||||
? null
|
|
||||||
: modes.filter((mode) => mode.name === snippet.mode)[0]
|
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
||||||
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
return <div styleName='tabView'
|
return <div styleName='tabView'
|
||||||
key={index}
|
key={index}
|
||||||
style={{zIndex: isActive ? 5 : 4}}
|
style={{zIndex: isActive ? 5 : 4}}
|
||||||
>
|
>
|
||||||
<div styleName='tabView-top'>
|
{snippet.mode === 'Markdown' || snippet.mode === 'GitHub Flavored Markdown'
|
||||||
<input styleName='tabView-top-name'
|
? <MarkdownEditor styleName='tabView-content'
|
||||||
placeholder='Filename including extensions...'
|
value={snippet.content}
|
||||||
value={snippet.name}
|
config={config}
|
||||||
onChange={(e) => this.handleNameInputChange(index)(e)}
|
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||||
|
ref={'code-' + index}
|
||||||
|
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
||||||
|
storageKey={storageKey}
|
||||||
/>
|
/>
|
||||||
<button styleName='tabView-top-mode'
|
: <CodeEditor styleName='tabView-content'
|
||||||
onClick={(e) => this.handleModeButtonClick(index)(e)}
|
|
||||||
>
|
|
||||||
{mode == null
|
|
||||||
? 'Select Syntax...'
|
|
||||||
: mode.label
|
|
||||||
}
|
|
||||||
<i className='fa fa-caret-down'/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<CodeEditor styleName='tabView-content'
|
|
||||||
mode={snippet.mode}
|
mode={snippet.mode}
|
||||||
value={snippet.content}
|
value={snippet.content}
|
||||||
theme={config.editor.theme}
|
theme={config.editor.theme}
|
||||||
@@ -370,73 +564,106 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
fontSize={editorFontSize}
|
fontSize={editorFontSize}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||||
ref={'code-' + index}
|
ref={'code-' + index}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
const options = []
|
||||||
<div className='NoteDetail'
|
data.storageMap.forEach((storage, index) => {
|
||||||
style={this.props.style}
|
storage.folders.forEach((folder) => {
|
||||||
styleName='root'
|
options.push({
|
||||||
>
|
storage: storage,
|
||||||
{this.state.isDeleting
|
folder: folder
|
||||||
? <div styleName='info'>
|
})
|
||||||
<div styleName='info-delete'>
|
})
|
||||||
|
})
|
||||||
|
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||||
|
|
||||||
<span styleName='info-delete-message'>
|
const trashTopBar = <div styleName='info'>
|
||||||
Are you sure to delete this note?
|
<div styleName='info-left'>
|
||||||
</span>
|
<i styleName='undo-button'
|
||||||
<button styleName='info-delete-cancelButton'
|
className='fa fa-undo fa-fw'
|
||||||
onClick={(e) => this.handleDeleteCancelButtonClick(e)}
|
onClick={(e) => this.handleUndoButtonClick(e)}
|
||||||
>Cancel</button>
|
/>
|
||||||
<button styleName='info-delete-confirmButton'
|
</div>
|
||||||
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
|
<div styleName='info-right'>
|
||||||
>Confirm</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
: <div styleName='info'>
|
|
||||||
|
const detailTopBar = <div styleName='info'>
|
||||||
<div styleName='info-left'>
|
<div styleName='info-left'>
|
||||||
<div styleName='info-left-top'>
|
<div styleName='info-left-top'>
|
||||||
<FolderSelect styleName='info-left-top-folderSelect'
|
<FolderSelect styleName='info-left-top-folderSelect'
|
||||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
ref='folder'
|
ref='folder'
|
||||||
storages={storages}
|
data={data}
|
||||||
onChange={(e) => this.handleFolderChange(e)}
|
onChange={(e) => this.handleFolderChange(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-left-bottom'>
|
|
||||||
<TagSelect
|
<TagSelect
|
||||||
styleName='info-left-bottom-tagSelect'
|
|
||||||
ref='tags'
|
ref='tags'
|
||||||
value={this.state.note.tags}
|
value={this.state.note.tags}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<StarButton styleName='info-right-button'
|
<InfoButton
|
||||||
|
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StarButton
|
||||||
onClick={(e) => this.handleStarButtonClick(e)}
|
onClick={(e) => this.handleStarButtonClick(e)}
|
||||||
isActive={note.isStarred}
|
isActive={note.isStarred}
|
||||||
/>
|
/>
|
||||||
<button styleName='info-right-button'
|
|
||||||
onClick={(e) => this.handleShareButtonClick(e)}
|
<button styleName='control-fullScreenButton'
|
||||||
>
|
onMouseDown={(e) => this.handleFullScreenButton(e)}>
|
||||||
<i className='fa fa-share-alt fa-fw'/>
|
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
|
||||||
</button>
|
|
||||||
<button styleName='info-right-button'
|
|
||||||
onClick={(e) => this.handleContextButtonClick(e)}
|
|
||||||
>
|
|
||||||
<i className='fa fa-ellipsis-v'/>
|
|
||||||
</button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
|
return (
|
||||||
|
<div className='NoteDetail'
|
||||||
|
style={this.props.style}
|
||||||
|
styleName='root'
|
||||||
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
|
>
|
||||||
|
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||||
|
|
||||||
<div styleName='body'>
|
<div styleName='body'>
|
||||||
<div styleName='body-description'>
|
<div styleName='description'>
|
||||||
<textarea styleName='body-description-textarea'
|
<textarea
|
||||||
style={{
|
style={{
|
||||||
fontFamily: config.preview.fontFamily,
|
fontFamily: config.preview.fontFamily,
|
||||||
fontSize: parseInt(config.preview.fontSize, 10)
|
fontSize: parseInt(config.preview.fontSize, 10)
|
||||||
@@ -448,8 +675,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='tabList'>
|
<div styleName='tabList'>
|
||||||
|
<div styleName='list'>
|
||||||
{tabList}
|
{tabList}
|
||||||
<button styleName='tabList-plusButton'
|
</div>
|
||||||
|
<button styleName='plusButton'
|
||||||
onClick={(e) => this.handleTabPlusButtonClick(e)}
|
onClick={(e) => this.handleTabPlusButtonClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-plus' />
|
<i className='fa fa-plus' />
|
||||||
@@ -457,6 +686,35 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
{viewList}
|
{viewList}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div styleName='override'>
|
||||||
|
<button
|
||||||
|
onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)}
|
||||||
|
>
|
||||||
|
{this.state.note.snippets[this.state.snippetIndex].mode == null
|
||||||
|
? 'Select Syntax...'
|
||||||
|
: this.state.note.snippets[this.state.snippetIndex].mode
|
||||||
|
}
|
||||||
|
<i className='fa fa-caret-down' />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={(e) => this.handleIndentTypeButtonClick(e)}
|
||||||
|
>
|
||||||
|
Indent: {config.editor.indentType}
|
||||||
|
<i className='fa fa-caret-down' />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={(e) => this.handleIndentSizeButtonClick(e)}
|
||||||
|
>
|
||||||
|
size: {config.editor.indentSize}
|
||||||
|
<i className='fa fa-caret-down' />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StatusBar
|
||||||
|
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||||
|
date={note.updatedAt}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,170 +1,128 @@
|
|||||||
$info-height = 75px
|
@import('NoteDetailInfo')
|
||||||
|
@import('DetailVars')
|
||||||
|
|
||||||
.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
|
background-color $ui-noteDetail-backgroundColor
|
||||||
border-color $ui-borderColor
|
box-shadow none
|
||||||
|
|
||||||
.info
|
|
||||||
absolute top left right
|
|
||||||
height $info-height
|
|
||||||
border-bottom $ui-border
|
|
||||||
background-color $ui-backgroundColor
|
|
||||||
|
|
||||||
.info-delete
|
|
||||||
height 80px
|
|
||||||
clearfix()
|
|
||||||
|
|
||||||
.info-delete-message
|
|
||||||
height 80px
|
|
||||||
line-height 80px
|
|
||||||
padding 0 25px
|
|
||||||
float left
|
|
||||||
|
|
||||||
.info-delete-confirmButton
|
|
||||||
float right
|
|
||||||
margin 25px 5px 0
|
|
||||||
height 30px
|
|
||||||
padding 0 25px
|
|
||||||
border-radius 2px
|
|
||||||
border none
|
|
||||||
color $ui-text-color
|
|
||||||
colorDangerButton()
|
|
||||||
|
|
||||||
.info-delete-cancelButton
|
|
||||||
float right
|
|
||||||
height 30px
|
|
||||||
margin 25px 5px 0
|
|
||||||
padding 0 25px
|
|
||||||
border $ui-border
|
|
||||||
border-radius 2px
|
|
||||||
color $ui-text-color
|
|
||||||
colorDefaultButton()
|
|
||||||
|
|
||||||
.info-left
|
|
||||||
float left
|
|
||||||
padding 0 5px
|
|
||||||
|
|
||||||
.info-left-top
|
|
||||||
height 40px
|
|
||||||
line-height 40px
|
|
||||||
|
|
||||||
.info-left-top-folderSelect
|
|
||||||
display inline-block
|
|
||||||
height 34px
|
|
||||||
width 200px
|
|
||||||
vertical-align middle
|
|
||||||
|
|
||||||
.info-left-bottom
|
|
||||||
height 30px
|
|
||||||
|
|
||||||
.info-left-bottom-tagSelect
|
|
||||||
height 30px
|
|
||||||
line-height 30px
|
|
||||||
|
|
||||||
.info-right
|
|
||||||
float right
|
|
||||||
|
|
||||||
.info-right-button
|
|
||||||
width 34px
|
|
||||||
height 34px
|
|
||||||
border-radius 17px
|
|
||||||
navButtonColor()
|
|
||||||
border $ui-border
|
|
||||||
font-size 14px
|
|
||||||
margin 8px 2px
|
|
||||||
padding 0
|
|
||||||
&:active
|
|
||||||
border-color $ui-button--focus-borderColor
|
|
||||||
&:hover .left-control-newPostButton-tooltip
|
|
||||||
display block
|
|
||||||
&:focus
|
|
||||||
border-color $ui-button--focus-borderColor
|
|
||||||
|
|
||||||
.body
|
.body
|
||||||
absolute bottom left right
|
absolute left right
|
||||||
top $info-height
|
left $snippet-note-detail-left-margin
|
||||||
|
right $snippet-note-detail-right-margin
|
||||||
|
top $info-height + $info-margin-under-border
|
||||||
|
bottom $statusBar-height
|
||||||
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|
||||||
.body-description
|
.body .description
|
||||||
absolute top left right
|
absolute top left right
|
||||||
height 80px
|
height 50px
|
||||||
border-bottom $ui-border
|
|
||||||
|
|
||||||
.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
|
||||||
|
|
||||||
.tabList
|
.tabList
|
||||||
absolute left right
|
absolute left right
|
||||||
top 80px
|
top 55px
|
||||||
height 30px
|
height 30px
|
||||||
border-bottom $ui-border
|
|
||||||
display flex
|
display flex
|
||||||
background-color $ui-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
.tabList-item
|
|
||||||
position relative
|
.tabList .list
|
||||||
flex 1
|
flex 1
|
||||||
border-right $ui-border
|
display flex
|
||||||
&:hover
|
overflow hidden
|
||||||
.tabList-item-deleteButton
|
|
||||||
color $ui-inactive-text-color
|
.tabList .plusButton
|
||||||
&:hover
|
navWhiteButtonColor()
|
||||||
background-color darken($ui-backgroundColor, 15%)
|
|
||||||
&:active
|
|
||||||
color white
|
|
||||||
background-color $ui-active-color
|
|
||||||
.tabList-item--active
|
|
||||||
@extend .tabList-item
|
|
||||||
.tabList-item-button
|
|
||||||
border-color $brand-color
|
|
||||||
.tabList-item-button
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
navButtonColor()
|
|
||||||
border-left 4px solid transparent
|
|
||||||
.tabList-item-deleteButton
|
|
||||||
position absolute
|
|
||||||
top 5px
|
|
||||||
height 20px
|
|
||||||
right 5px
|
|
||||||
width 20px
|
|
||||||
text-align center
|
|
||||||
border none
|
|
||||||
padding 0
|
|
||||||
color transparent
|
|
||||||
background-color transparent
|
|
||||||
border-radius 2px
|
|
||||||
.tabList-plusButton
|
|
||||||
navButtonColor()
|
|
||||||
width 30px
|
width 30px
|
||||||
|
|
||||||
.tabView
|
.tabView
|
||||||
absolute left right bottom
|
absolute left right bottom
|
||||||
top 110px
|
top 100px
|
||||||
.tabView-top
|
|
||||||
absolute top left right
|
|
||||||
height 30px
|
|
||||||
border-bottom $ui-border
|
|
||||||
display flex
|
|
||||||
.tabView-top-name
|
|
||||||
flex 1
|
|
||||||
border none
|
|
||||||
padding 0 10px
|
|
||||||
font-size 14px
|
|
||||||
.tabView-top-mode
|
|
||||||
width 110px
|
|
||||||
padding 0
|
|
||||||
border none
|
|
||||||
border-left $ui-border
|
|
||||||
colorDefaultButton()
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
color $ui-text-color
|
|
||||||
.tabView-content
|
.tabView-content
|
||||||
absolute left right bottom
|
absolute top left right bottom
|
||||||
top 30px
|
|
||||||
|
.override
|
||||||
|
absolute bottom left
|
||||||
|
bottom 1px
|
||||||
|
left 60px
|
||||||
|
z-index 101
|
||||||
|
button
|
||||||
|
navButtonColor()
|
||||||
|
padding 0 6px
|
||||||
|
&:hover
|
||||||
|
color $ui-active-color
|
||||||
|
&:active .update-icon
|
||||||
|
color $ui-active-color
|
||||||
|
|
||||||
|
.control-fullScreenButton
|
||||||
|
top 80px
|
||||||
|
margin-bottom 10px
|
||||||
|
topBarButtonRight()
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.root
|
||||||
|
box-shadow $note-detail-box-shadow
|
||||||
|
border none
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
box-shadow none
|
||||||
|
|
||||||
|
.body
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body .description textarea
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
color $ui-dark-text-color
|
||||||
|
border 1px solid $ui-dark-borderColor
|
||||||
|
|
||||||
|
.tabList
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.tabList .list
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
|
||||||
|
.tabList .plusButton
|
||||||
|
navDarkButtonColor()
|
||||||
|
.override
|
||||||
|
button
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.control-fullScreenButton
|
||||||
|
topBarButtonDark()
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body .description textarea
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
border 1px solid $ui-solarized-dark-borderColor
|
||||||
|
|
||||||
|
.tabList
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
color $ui-solarized-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 './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,9 +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 className={this.state.isActive || this.props.isActive
|
<img styleName='icon'
|
||||||
? 'fa fa-star'
|
src={this.state.isActive || this.props.isActive
|
||||||
: 'fa fa-star-o'
|
? '../resources/icon/icon-starred.svg'
|
||||||
|
: '../resources/icon/icon-star.svg'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
.root
|
.root
|
||||||
position relative
|
top 45px
|
||||||
padding 0
|
topBarButtonRight()
|
||||||
transition transform 0.15s
|
|
||||||
&:hover
|
&:hover
|
||||||
transform rotate(-72deg)
|
transition 0.2s
|
||||||
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
|
|
||||||
.root--active
|
.root--active
|
||||||
@extend .root
|
@extend .root
|
||||||
color $ui-active-color
|
transition 0.15s
|
||||||
transform rotate(-72deg)
|
color $ui-favorite-star-button-color
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
|
|
||||||
|
.icon
|
||||||
|
transition transform 0.15s
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
topBarButtonDark()
|
||||||
|
&:hover
|
||||||
|
transition 0.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) {
|
||||||
@@ -22,6 +24,10 @@ class TagSelect extends React.Component {
|
|||||||
|
|
||||||
handleNewTagInputKeyDown (e) {
|
handleNewTagInputKeyDown (e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
|
case 9:
|
||||||
|
e.preventDefault()
|
||||||
|
this.submitTag()
|
||||||
|
break
|
||||||
case 13:
|
case 13:
|
||||||
this.submitTag()
|
this.submitTag()
|
||||||
break
|
break
|
||||||
@@ -32,6 +38,10 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleNewTagBlur (e) {
|
||||||
|
this.submitTag()
|
||||||
|
}
|
||||||
|
|
||||||
removeLastTag () {
|
removeLastTag () {
|
||||||
let { value } = this.props
|
let { value } = this.props
|
||||||
|
|
||||||
@@ -52,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()
|
const newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
|
||||||
|
|
||||||
if (newTag.length <= 0) {
|
if (newTag.length <= 0) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -95,20 +106,20 @@ 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>
|
||||||
<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'/>
|
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
||||||
</button>
|
</button>
|
||||||
<span styleName='tag-label'>{tag}</span>
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -121,9 +132,6 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
styleName='root'
|
styleName='root'
|
||||||
>
|
>
|
||||||
<i styleName='icon'
|
|
||||||
className='fa fa-tags'
|
|
||||||
/>
|
|
||||||
{tagList}
|
{tagList}
|
||||||
<input styleName='newTag'
|
<input styleName='newTag'
|
||||||
ref='newTag'
|
ref='newTag'
|
||||||
@@ -131,8 +139,8 @@ 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,63 +1,83 @@
|
|||||||
.root
|
.root
|
||||||
position relative
|
display flex
|
||||||
|
align-items center
|
||||||
user-select none
|
user-select none
|
||||||
|
|
||||||
.icon
|
|
||||||
display inline-block
|
|
||||||
width 30px
|
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
text-align center
|
width 100%
|
||||||
color $ui-button-color
|
overflow-x scroll
|
||||||
|
white-space nowrap
|
||||||
|
margin-right 10px
|
||||||
|
|
||||||
|
.root::-webkit-scrollbar
|
||||||
|
display none
|
||||||
|
|
||||||
.tag
|
.tag
|
||||||
display inline-block
|
display flex
|
||||||
margin 0 2px
|
align-items center
|
||||||
vertical-align middle
|
margin 0px 2px
|
||||||
height 20px
|
padding 2px 4px
|
||||||
background-color white
|
background-color alpha($ui-tag-backgroundColor, 3%)
|
||||||
border-radius 3px
|
border-radius 4px
|
||||||
overflow hidden
|
position relative
|
||||||
clearfix()
|
clearfix()
|
||||||
|
|
||||||
.tag-removeButton
|
.tag-removeButton
|
||||||
float left
|
|
||||||
height 20px
|
|
||||||
width 18px
|
|
||||||
margin 0
|
margin 0
|
||||||
padding 0
|
padding 0
|
||||||
border-style solid
|
border-style solid
|
||||||
border-color $ui-borderColor
|
border-width 0
|
||||||
border-width 0 0 0 3px
|
border-radius 20px
|
||||||
line-height 18px
|
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-button-color
|
color $ui-button-color
|
||||||
&:hover
|
position absolute
|
||||||
background-color $ui-button--hover-backgroundColor
|
right 6px
|
||||||
&:active, &:active:hover
|
|
||||||
color $ui-button--active-color
|
.tag-removeButton-icon
|
||||||
background-color $ui-button--active-backgroundColor
|
width 5px
|
||||||
border-color $ui-button--focus-borderColor
|
padding-right 4px
|
||||||
&:focus
|
|
||||||
border-color $ui-button--focus-borderColor
|
|
||||||
|
|
||||||
.tag-label
|
.tag-label
|
||||||
float left
|
font-size 13px
|
||||||
height 20px
|
color: $ui-text-color
|
||||||
line-height 20px
|
padding 4px 16px 4px 8px
|
||||||
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
|
body[data-theme="dark"]
|
||||||
background-color $ui-input--disabled-backgroundColor = #DDD
|
.tag
|
||||||
|
background-color alpha($ui-dark-tag-backgroundColor, 60%)
|
||||||
|
|
||||||
|
.tag-removeButton
|
||||||
|
border-color $ui-button--focus-borderColor
|
||||||
|
background-color transparent
|
||||||
|
color $ui-button-color
|
||||||
|
|
||||||
|
.tag-label
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.newTag
|
||||||
|
border-color none
|
||||||
|
background-color transparent
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.tag
|
||||||
|
background-color $ui-solarized-dark-tag-backgroundColor
|
||||||
|
|
||||||
|
.tag-removeButton
|
||||||
|
border-color $ui-button--focus-borderColor
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.tag-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.newTag
|
||||||
|
border-color none
|
||||||
|
background-color transparent
|
||||||
|
color $ui-solarized-dark-text-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)
|
||||||
14
browser/main/Detail/TrashButton.styl
Normal file
14
browser/main/Detail/TrashButton.styl
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.control-trashButton
|
||||||
|
top 115px
|
||||||
|
topBarButtonRight()
|
||||||
|
|
||||||
|
.control-trashButton--in-trash
|
||||||
|
top 60px
|
||||||
|
topBarButtonRight()
|
||||||
|
|
||||||
|
.trashButton
|
||||||
|
padding 0px
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.control-trashButton
|
||||||
|
topBarButtonDark()
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
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'
|
||||||
import MarkdownNoteDetail from './MarkdownNoteDetail'
|
import MarkdownNoteDetail from './MarkdownNoteDetail'
|
||||||
import SnippetNoteDetail from './SnippetNoteDetail'
|
import SnippetNoteDetail from './SnippetNoteDetail'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
import StatusBar from '../StatusBar'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
@@ -16,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,19 +33,14 @@ class Detail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { location, notes, 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 folderKey = splitted.shift()
|
const noteKey = splitted.shift()
|
||||||
let noteKey = splitted.shift()
|
|
||||||
|
|
||||||
note = _.find(notes, {
|
note = data.noteMap.get(storageKey + '-' + noteKey)
|
||||||
storage: storageKey,
|
|
||||||
folder: folderKey,
|
|
||||||
key: noteKey
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
@@ -53,8 +50,11 @@ 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
|
||||||
|
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ class Detail extends React.Component {
|
|||||||
ref='root'
|
ref='root'
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'storages',
|
'data',
|
||||||
'style',
|
'style',
|
||||||
'ignorePreviewPointerEvents',
|
'ignorePreviewPointerEvents',
|
||||||
'location'
|
'location'
|
||||||
@@ -83,7 +83,7 @@ class Detail extends React.Component {
|
|||||||
ref='root'
|
ref='root'
|
||||||
{..._.pick(this.props, [
|
{..._.pick(this.props, [
|
||||||
'dispatch',
|
'dispatch',
|
||||||
'storages',
|
'data',
|
||||||
'style',
|
'style',
|
||||||
'ignorePreviewPointerEvents',
|
'ignorePreviewPointerEvents',
|
||||||
'location'
|
'location'
|
||||||
@@ -95,7 +95,6 @@ class Detail extends React.Component {
|
|||||||
|
|
||||||
Detail.propTypes = {
|
Detail.propTypes = {
|
||||||
dispatch: PropTypes.func,
|
dispatch: PropTypes.func,
|
||||||
storages: PropTypes.array,
|
|
||||||
style: PropTypes.shape({
|
style: PropTypes.shape({
|
||||||
left: PropTypes.number
|
left: PropTypes.number
|
||||||
}),
|
}),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user