mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-16 11:15:12 +00:00
Compare commits
2308 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7a5b789fa | ||
|
|
10b7d58dc6 | ||
|
|
16f0e95e32 | ||
|
|
55395d3a2d | ||
|
|
4e0fa63fad | ||
|
|
b9ea7696d8 | ||
|
|
7de3308f52 | ||
|
|
b2b2373c7b | ||
|
|
1c54f40a28 | ||
|
|
4735992835 | ||
|
|
cba3519458 | ||
|
|
c64a5e1cca | ||
|
|
47ee8b8ce7 | ||
|
|
455b424429 | ||
|
|
7d67ac3f12 | ||
|
|
34f377eb5c | ||
|
|
b7f4af8c78 | ||
|
|
a6c7dde194 | ||
|
|
43ebe4ecfd | ||
|
|
1d38f1abb4 | ||
|
|
061a0cd219 | ||
|
|
f0ed20ee2c | ||
|
|
edfc8d95c8 | ||
|
|
c33f9d8307 | ||
|
|
eee212f5b8 | ||
|
|
b690147b0b | ||
|
|
10879d0f67 | ||
|
|
b48b8f39fc | ||
|
|
3d0b3e759b | ||
|
|
3e7f4a41e2 | ||
|
|
b89b888129 | ||
|
|
cba9afc9ba | ||
|
|
a66a11b81e | ||
|
|
22cc2791b6 | ||
|
|
369cf16b68 | ||
|
|
0d38baf194 | ||
|
|
5b63c95f40 | ||
|
|
52497999a0 | ||
|
|
6bab108a35 | ||
|
|
eec22e6b7d | ||
|
|
edaa0713e8 | ||
|
|
6b6a415dd5 | ||
|
|
414e0dbc05 | ||
|
|
081a3da9e1 | ||
|
|
3fbc749395 | ||
|
|
626175b2b8 | ||
|
|
54db0c718a | ||
|
|
ae9175d1b1 | ||
|
|
16e1f0f882 | ||
|
|
179b830d14 | ||
|
|
aeb27f7bff | ||
|
|
0b2b89da0f | ||
|
|
804991b300 | ||
|
|
529f271e07 | ||
|
|
402d577ce0 | ||
|
|
4bb28a358e | ||
|
|
200a9275d2 | ||
|
|
9cb086cbd9 | ||
|
|
3a836aaf34 | ||
|
|
e14a6f2374 | ||
|
|
28794bff79 | ||
|
|
ecc9443c9a | ||
|
|
a1c499c026 | ||
|
|
4e8321268c | ||
|
|
8510325732 | ||
|
|
49102be894 | ||
|
|
1a97488dd7 | ||
|
|
b90c7e5318 | ||
|
|
ece1e37c16 | ||
|
|
3a6e3f5cae | ||
|
|
8e502eec80 | ||
|
|
042ebe8316 | ||
|
|
eee47b1f76 | ||
|
|
7978704f1b | ||
|
|
9392ac438c | ||
|
|
cbcfb57e35 | ||
|
|
460437397f | ||
|
|
29fa512a90 | ||
|
|
059d1fb4f2 | ||
|
|
4d3a538213 | ||
|
|
108a0db799 | ||
|
|
04f632570d | ||
|
|
92b919da6c | ||
|
|
4f0f611e52 | ||
|
|
90f7cf0996 | ||
|
|
ce43e80bdf | ||
|
|
c397752b7a | ||
|
|
2470364571 | ||
|
|
cab122ba8b | ||
|
|
56b01b5a85 | ||
|
|
cbcc9fb411 | ||
|
|
5bbc60d48a | ||
|
|
8fb8aab7b8 | ||
|
|
5b76216a64 | ||
|
|
7f5e372bdc | ||
|
|
01913a9a40 | ||
|
|
62f9fcf171 | ||
|
|
34c667f6b2 | ||
|
|
7a6de387e3 | ||
|
|
b6efbcedef | ||
|
|
3c70b2e5a0 | ||
|
|
62e108d460 | ||
|
|
b7f426b03f | ||
|
|
5ca13b70aa | ||
|
|
4644d107c6 | ||
|
|
06013b2a6a | ||
|
|
7380fc8500 | ||
|
|
81b550e66c | ||
|
|
f5972c273b | ||
|
|
c259ec2bed | ||
|
|
a36e779980 | ||
|
|
86a04b0dcb | ||
|
|
8b7c0c957c | ||
|
|
0cfe971a48 | ||
|
|
8884db858c | ||
|
|
0fe9dd4fbb | ||
|
|
ae57e99173 | ||
|
|
c238e1b5cb | ||
|
|
55ef998c55 | ||
|
|
166815ccbf | ||
|
|
34a16298d4 | ||
|
|
1613e72d47 | ||
|
|
e933ad89dc | ||
|
|
148fb25a15 | ||
|
|
fe0544dbc9 | ||
|
|
0aaea28e74 | ||
|
|
f4764afbf4 | ||
|
|
2a2b662a6f | ||
|
|
deade1f9f8 | ||
|
|
2b73c17cca | ||
|
|
369f8b6093 | ||
|
|
5391ca161d | ||
|
|
5b224d3b54 | ||
|
|
dad6a93944 | ||
|
|
10ec5c1342 | ||
|
|
8edfc15f45 | ||
|
|
792e41f161 | ||
|
|
9fbdf895af | ||
|
|
dbc0fedf43 | ||
|
|
cb298b8cad | ||
|
|
2459a80e15 | ||
|
|
0a973c4db3 | ||
|
|
a84fddd5fa | ||
|
|
1cd26d2e71 | ||
|
|
f88ac891ff | ||
|
|
17aa9ae85f | ||
|
|
bb46a9ba4c | ||
|
|
4a6c16df8d | ||
|
|
95787fafc6 | ||
|
|
c51d79e4d7 | ||
|
|
d5232a1b0e | ||
|
|
e093c7f982 | ||
|
|
3f53bddf7e | ||
|
|
e03d8175e3 | ||
|
|
7ab2e9f6ca | ||
|
|
6316ddc6a6 | ||
|
|
11c46ede5d | ||
|
|
2ec5238d97 | ||
|
|
b7f359f6cf | ||
|
|
8ef485221c | ||
|
|
afddb6fc33 | ||
|
|
f28ee1bc4b | ||
|
|
b997f0a5a7 | ||
|
|
48dc47ce57 | ||
|
|
84f18ced47 | ||
|
|
037ff2e749 | ||
|
|
f0f23ede3d | ||
|
|
c8763063c0 | ||
|
|
e57fef2413 | ||
|
|
f1a90f4a11 | ||
|
|
990d7edba4 | ||
|
|
13a1da91be | ||
|
|
930b58d2a8 | ||
|
|
b88b1065ee | ||
|
|
5fbb802b32 | ||
|
|
a40f8d25ef | ||
|
|
0a28798d54 | ||
|
|
6047987c25 | ||
|
|
2ce96186f2 | ||
|
|
26357bd4bc | ||
|
|
e94b45a7e4 | ||
|
|
4be1eb7a28 | ||
|
|
f4bbbb640d | ||
|
|
94b4e70b0f | ||
|
|
b31e3f7783 | ||
|
|
9095fe934d | ||
|
|
9139495f02 | ||
|
|
6375ac857a | ||
|
|
5eac08430f | ||
|
|
ef0b109ad4 | ||
|
|
7fbe6c0955 | ||
|
|
c86e43597c | ||
|
|
123a73a5e6 | ||
|
|
706b5d253f | ||
|
|
8fb8c7a40b | ||
|
|
21220f93b1 | ||
|
|
705e64377b | ||
|
|
5c3f0cd060 | ||
|
|
bcb1fb4331 | ||
|
|
a238be5b7c | ||
|
|
2351bb78da | ||
|
|
6bbc5a91fe | ||
|
|
70b69a3bc9 | ||
|
|
2f52233bd0 | ||
|
|
a7e458b784 | ||
|
|
a504a45d99 | ||
|
|
2d0e14c1cc | ||
|
|
92eccb635a | ||
|
|
1f8acc3afc | ||
|
|
f8eaa9e796 | ||
|
|
7c9fecd943 | ||
|
|
3df8cbb357 | ||
|
|
993cb9cb0b | ||
|
|
5bd4a3f761 | ||
|
|
fcad84ced3 | ||
|
|
0bd48981ec | ||
|
|
0bf7e8b705 | ||
|
|
875c451221 | ||
|
|
c8256bea3a | ||
|
|
7d10b951b7 | ||
|
|
34e9238cc4 | ||
|
|
7d97784a58 | ||
|
|
1265e7c4a1 | ||
|
|
44ece2bf34 | ||
|
|
fde2a8055d | ||
|
|
f9643c2503 | ||
|
|
e6a97e5cb3 | ||
|
|
73b5546ae9 | ||
|
|
f98719ee73 | ||
|
|
7666182ae3 | ||
|
|
05af30f336 | ||
|
|
20de08b625 | ||
|
|
e60f4f4a64 | ||
|
|
6af25d932c | ||
|
|
3f49a8a15a | ||
|
|
bfa8db7b55 | ||
|
|
d6f2e7588c | ||
|
|
a594332ffb | ||
|
|
ceb18ebf1c | ||
|
|
b9038e254e | ||
|
|
b351e42538 | ||
|
|
8910c26ee2 | ||
|
|
7549a7bbbe | ||
|
|
17fbe6e232 | ||
|
|
ccdac8f604 | ||
|
|
88a828c9ef | ||
|
|
ae3291b90e | ||
|
|
2c6f0452b8 | ||
|
|
4651acd6f4 | ||
|
|
bba7babce3 | ||
|
|
73dc6a4a92 | ||
|
|
992f5a525a | ||
|
|
b38d5789f3 | ||
|
|
47b5945e17 | ||
|
|
73544b0f06 | ||
|
|
e7d9311e23 | ||
|
|
c97c65b707 | ||
|
|
1c02b4e62a | ||
|
|
d23156d11a | ||
|
|
bd013adb4d | ||
|
|
c0368ce713 | ||
|
|
80283b5f55 | ||
|
|
4078645958 | ||
|
|
955ade0b8a | ||
|
|
4b158af9f6 | ||
|
|
642fae3ac7 | ||
|
|
d249967aee | ||
|
|
7b6b7f05e0 | ||
|
|
35b9bf5d34 | ||
|
|
59f0cc4f98 | ||
|
|
a29ca73fb4 | ||
|
|
59b658f059 | ||
|
|
3397b3108f | ||
|
|
cae7baa5e1 | ||
|
|
4af71fd1dd | ||
|
|
4194b61373 | ||
|
|
c91fd6783d | ||
|
|
89bbed1dfd | ||
|
|
2aeb53920c | ||
|
|
fe51c232b6 | ||
|
|
57b054794c | ||
|
|
8318c56046 | ||
|
|
0d52417ee7 | ||
|
|
6f3b1b8d6f | ||
|
|
a460d7722e | ||
|
|
d770208d4c | ||
|
|
0434109908 | ||
|
|
289d3a4e6b | ||
|
|
ffb9be63c7 | ||
|
|
bf2b53cbce | ||
|
|
1d9bf65c31 | ||
|
|
4744b918d3 | ||
|
|
588b1809a9 | ||
|
|
dc1c19293d | ||
|
|
1f548959e3 | ||
|
|
8cae5670fc | ||
|
|
07c0982d4f | ||
|
|
2f9e4b3198 | ||
|
|
89dba149a3 | ||
|
|
aa71b4c1b8 | ||
|
|
43110f8f2a | ||
|
|
e48540713d | ||
|
|
cfd13139e0 | ||
|
|
ac5cdf384f | ||
|
|
e9d858d902 | ||
|
|
1beae4403a | ||
|
|
dedf36f704 | ||
|
|
1477de3899 | ||
|
|
0d947c7dd8 | ||
|
|
ebfd8f40e3 | ||
|
|
3159cc0ded | ||
|
|
10dcbfb891 | ||
|
|
19dc16e14a | ||
|
|
95586b3156 | ||
|
|
0637daf645 | ||
|
|
fdcd62617d | ||
|
|
0f3e5ee4ed | ||
|
|
7b171ecc67 | ||
|
|
7a4052ede3 | ||
|
|
3f53a1f629 | ||
|
|
31daec5fe2 | ||
|
|
0d7155bda6 | ||
|
|
35beec3e39 | ||
|
|
ff4b96b622 | ||
|
|
9b60814292 | ||
|
|
3c4fa83161 | ||
|
|
e8564f6540 | ||
|
|
a22e97d4bd | ||
|
|
046e6af489 | ||
|
|
f805e8a688 | ||
|
|
2fddc32eb7 | ||
|
|
6018cd5d81 | ||
|
|
3533903be3 | ||
|
|
d867292f66 | ||
|
|
7691b662d6 | ||
|
|
86270dd856 | ||
|
|
012e2dde4f | ||
|
|
ad7a3c49f9 | ||
|
|
e8abd43c8a | ||
|
|
3192ce9d39 | ||
|
|
d09de09fef | ||
|
|
4689ddeb98 | ||
|
|
e300b33a4f | ||
|
|
0ca87ea407 | ||
|
|
2886da4f63 | ||
|
|
bf9ecb02e5 | ||
|
|
852617726c | ||
|
|
c2aa35104c | ||
|
|
95e237d4a3 | ||
|
|
59e5c547e9 | ||
|
|
06bd2b2b79 | ||
|
|
faede48217 | ||
|
|
ad0ac19d3d | ||
|
|
3154110de1 | ||
|
|
5248c05e61 | ||
|
|
8311030bec | ||
|
|
c429fc6b2c | ||
|
|
590aa9ab17 | ||
|
|
f9a7c2d457 | ||
|
|
b4506168fb | ||
|
|
f203ab3aaf | ||
|
|
c197dd0a4b | ||
|
|
457e596851 | ||
|
|
d274563b2c | ||
|
|
2003bea3cf | ||
|
|
f9b3284852 | ||
|
|
9bca133d88 | ||
|
|
03fc453608 | ||
|
|
3027cc81b3 | ||
|
|
2415fbf676 | ||
|
|
725c6a7ba9 | ||
|
|
c33da0cf8e | ||
|
|
d915d19425 | ||
|
|
f3370242bf | ||
|
|
0e312ba929 | ||
|
|
6440395197 | ||
|
|
5433abddaf | ||
|
|
0ccb465288 | ||
|
|
8fd4deb3eb | ||
|
|
fe8045c51d | ||
|
|
b890c59134 | ||
|
|
f39caeb967 | ||
|
|
7ab482184b | ||
|
|
78b12ae686 | ||
|
|
caa5deac4e | ||
|
|
af3083825e | ||
|
|
5255708ff2 | ||
|
|
9331f2034b | ||
|
|
fc6a5c22bf | ||
|
|
51c397d177 | ||
|
|
7c9596308e | ||
|
|
15dc424ade | ||
|
|
49243a8010 | ||
|
|
93e188d118 | ||
|
|
df3195fc1e | ||
|
|
da6b8c30a0 | ||
|
|
70468b6b7d | ||
|
|
2511512d94 | ||
|
|
4418617d3b | ||
|
|
6e480ba146 | ||
|
|
b4f5913a80 | ||
|
|
6a3062709c | ||
|
|
d66bc1faef | ||
|
|
bef7d45c3e | ||
|
|
bb9489a8d3 | ||
|
|
700eeb8f5a | ||
|
|
7e2f0049b6 | ||
|
|
b2388544d8 | ||
|
|
d772551c60 | ||
|
|
31dca6f06b | ||
|
|
83f68fe153 | ||
|
|
08a2ae0fd3 | ||
|
|
53d3f51c74 | ||
|
|
f7cdafb087 | ||
|
|
5b17808569 | ||
|
|
a7328e21f1 | ||
|
|
e64370e9a2 | ||
|
|
d5b37b2418 | ||
|
|
4da08d93fd | ||
|
|
c39e5c67f5 | ||
|
|
00d5cf13c9 | ||
|
|
1120bcfc0c | ||
|
|
8e506cb7c2 | ||
|
|
145b66d375 | ||
|
|
82e4a8bbc3 | ||
|
|
bca9bfb960 | ||
|
|
d8e19d9c17 | ||
|
|
95d74d1ca2 | ||
|
|
ebdd6d77f7 | ||
|
|
d56bcc4fdf | ||
|
|
4bb18cfc9a | ||
|
|
6f30692534 | ||
|
|
e249c1ec65 | ||
|
|
c2b4c77003 | ||
|
|
e64733827a | ||
|
|
ea81b0d414 | ||
|
|
000cf2a864 | ||
|
|
dc13b919b3 | ||
|
|
a0c8ec3171 | ||
|
|
80c13f7c4f | ||
|
|
1a6f3d808b | ||
|
|
ec8fac1199 | ||
|
|
98c93d3248 | ||
|
|
a053706c24 | ||
|
|
e1a75a13e9 | ||
|
|
ee6f4de183 | ||
|
|
475885b3ef | ||
|
|
2d2b2d4c6c | ||
|
|
4d00454539 | ||
|
|
bf590b5614 | ||
|
|
3ef33c065c | ||
|
|
8e89fb8b92 | ||
|
|
8e81cfcf89 | ||
|
|
515736262d | ||
|
|
bd266dc602 | ||
|
|
9b3306157c | ||
|
|
f8e6a939ca | ||
|
|
8c48ee6fc1 | ||
|
|
5e476054d7 | ||
|
|
2fc8547384 | ||
|
|
2af2d71540 | ||
|
|
6aaf9d9eb2 | ||
|
|
42a9caf5a3 | ||
|
|
e6c1d7a383 | ||
|
|
02100bbc0a | ||
|
|
ce7c5f5d40 | ||
|
|
69f1ad6eb3 | ||
|
|
8320fb5024 | ||
|
|
4b79bca6bf | ||
|
|
4a5fd41249 | ||
|
|
4e90a93b30 | ||
|
|
9861fbf7c8 | ||
|
|
c762b9ae00 | ||
|
|
cc667a6edf | ||
|
|
419c57ed3f | ||
|
|
601f0b0de8 | ||
|
|
7b1c6c10b7 | ||
|
|
5a85c257cf | ||
|
|
beceb851c2 | ||
|
|
31485d3387 | ||
|
|
fc552e030a | ||
|
|
bf4c9f920a | ||
|
|
4ebd503664 | ||
|
|
0907bc80ef | ||
|
|
2cf46a3332 | ||
|
|
41868f28e6 | ||
|
|
964b7b62de | ||
|
|
0d34a03fe0 | ||
|
|
a62faa471c | ||
|
|
66f3ce2cb2 | ||
|
|
43c49f54d2 | ||
|
|
a15dfffa44 | ||
|
|
59985dee72 | ||
|
|
6b7132f134 | ||
|
|
e313b5e59d | ||
|
|
bb26d9a0a8 | ||
|
|
5c2c99282d | ||
|
|
94e6f89d07 | ||
|
|
3804a746df | ||
|
|
5c2d7e2d2a | ||
|
|
c34dd462b6 | ||
|
|
9141b1a641 | ||
|
|
0fea85e2f2 | ||
|
|
0ca41fbdb4 | ||
|
|
a58c191ded | ||
|
|
77089a1178 | ||
|
|
2cfb883bad | ||
|
|
ec8c8bb669 | ||
|
|
0b54f01107 | ||
|
|
67be198bee | ||
|
|
32a4a1aae1 | ||
|
|
dac7372839 | ||
|
|
521c261a37 | ||
|
|
27367488c2 | ||
|
|
0d5c3b1be6 | ||
|
|
a67d5ffacb | ||
|
|
687440a7c7 | ||
|
|
bafdc24a6d | ||
|
|
047f9c93c5 | ||
|
|
116fafc117 | ||
|
|
060c92091c | ||
|
|
5802525b73 | ||
|
|
c3580caabc | ||
|
|
08c027acc5 | ||
|
|
4468792346 | ||
|
|
1b16c68cf9 | ||
|
|
b99c1e3b32 | ||
|
|
eb2994e3c2 | ||
|
|
d88dd26186 | ||
|
|
59fcc58e9c | ||
|
|
acba61f36a | ||
|
|
a3a55a8bb4 | ||
|
|
22929d84fc | ||
|
|
9ea9d30947 | ||
|
|
7f08428fe2 | ||
|
|
0d80a7d961 | ||
|
|
2899264b54 | ||
|
|
923de0aa0d | ||
|
|
2b729dad15 | ||
|
|
b9b5bae78a | ||
|
|
a9acde07d1 | ||
|
|
a46b8d3079 | ||
|
|
8b92e2cbb7 | ||
|
|
881f5a5110 | ||
|
|
4e986a6384 | ||
|
|
ce5e1babb7 | ||
|
|
b85790d2fa | ||
|
|
6bc3e7fcf1 | ||
|
|
1ca968201d | ||
|
|
f2a03e4cc7 | ||
|
|
a752730718 | ||
|
|
9d20fd91ec | ||
|
|
70a6a3acb8 | ||
|
|
7f52eed4d5 | ||
|
|
105119e1a4 | ||
|
|
169e30e029 | ||
|
|
a5fa3e9e7a | ||
|
|
8985062d34 | ||
|
|
56eb9c76ae | ||
|
|
e5b6762bf3 | ||
|
|
e8bccaef88 | ||
|
|
afdb038244 | ||
|
|
56942d55eb | ||
|
|
9d742c8435 | ||
|
|
6ee4e48de2 | ||
|
|
184f3dc04b | ||
|
|
2027f60014 | ||
|
|
076edd375f | ||
|
|
ab1aa56059 | ||
|
|
46f7dfdfeb | ||
|
|
fcaa5e21cf | ||
|
|
1e202db50f | ||
|
|
9405b95825 | ||
|
|
f05e256afc | ||
|
|
731ffd4a22 | ||
|
|
8f4566b7e1 | ||
|
|
95aec54f60 | ||
|
|
f14ce0d68e | ||
|
|
cc1c7f3820 | ||
|
|
2df901288a | ||
|
|
821a7c780e | ||
|
|
6e2e48fa64 | ||
|
|
2864ac88f5 | ||
|
|
4a292d6518 | ||
|
|
e934182e86 | ||
|
|
d8fa73287b | ||
|
|
35938c09e8 | ||
|
|
9eaa90c691 | ||
|
|
049835d426 | ||
|
|
af91c40406 | ||
|
|
4940ad6825 | ||
|
|
d02b740300 | ||
|
|
9cb443dc2f | ||
|
|
1c7cba2951 | ||
|
|
473b80710d | ||
|
|
2247c0835d | ||
|
|
b7b715ba3d | ||
|
|
6c43fb2325 | ||
|
|
a6fe3c27d4 | ||
|
|
d47ff96b13 | ||
|
|
a0def654bd | ||
|
|
4873b40e49 | ||
|
|
0a758f20a7 | ||
|
|
5e58d457a3 | ||
|
|
0f745361ad | ||
|
|
bf6cae9a0e | ||
|
|
ab640a7676 | ||
|
|
820171e19e | ||
|
|
f1e9d0ab81 | ||
|
|
0646484c83 | ||
|
|
a27b79c213 | ||
|
|
773a9b4b7f | ||
|
|
07b838ef7b | ||
|
|
85217a7171 | ||
|
|
886d7b7227 | ||
|
|
6987b762dd | ||
|
|
f32ac81f84 | ||
|
|
87ea66bb92 | ||
|
|
ff6fd62932 | ||
|
|
76728448ff | ||
|
|
3b7225e0fa | ||
|
|
d6280f4397 | ||
|
|
8df867046f | ||
|
|
331c822816 | ||
|
|
6219173945 | ||
|
|
6207e02e7f | ||
|
|
537ba537dc | ||
|
|
3e919241e6 | ||
|
|
2324327e7e | ||
|
|
b8374494ea | ||
|
|
a480ca7b55 | ||
|
|
bfd67fb7f1 | ||
|
|
e6bd6a5077 | ||
|
|
350af844ca | ||
|
|
a49d53179a | ||
|
|
e68069fd2f | ||
|
|
a193ba3e6c | ||
|
|
9034b3ab54 | ||
|
|
f39b7594ab | ||
|
|
f79734391e | ||
|
|
e54f516418 | ||
|
|
e86464535d | ||
|
|
e8553caa65 | ||
|
|
044e6b7180 | ||
|
|
0266770657 | ||
|
|
2d6f7c08e8 | ||
|
|
0a27819a7f | ||
|
|
3185c25ee1 | ||
|
|
820802cdc2 | ||
|
|
2c169e6f15 | ||
|
|
49be1320f9 | ||
|
|
6bc4ecce48 | ||
|
|
0290d23832 | ||
|
|
e001c97e01 | ||
|
|
976d1bb4f3 | ||
|
|
dee6495b08 | ||
|
|
4a77f250f1 | ||
|
|
c697f19642 | ||
|
|
f7d1f9e949 | ||
|
|
27b9952f8e | ||
|
|
961dab4a97 | ||
|
|
930b6bc927 | ||
|
|
ed7d8258cf | ||
|
|
762425125f | ||
|
|
03bdf0c653 | ||
|
|
7002026190 | ||
|
|
b35395e6bd | ||
|
|
b74fb76d03 | ||
|
|
72d2df465b | ||
|
|
2592c943f7 | ||
|
|
7b3a3ba200 | ||
|
|
8369832585 | ||
|
|
d94a674343 | ||
|
|
260611ffd6 | ||
|
|
690549b57f | ||
|
|
5d9aeb4c04 | ||
|
|
eddfdea2ca | ||
|
|
a40385f87f | ||
|
|
0123526c98 | ||
|
|
275e3317a3 | ||
|
|
d0b835a825 | ||
|
|
f450260ff8 | ||
|
|
5f7b119e5c | ||
|
|
bb9eb494e9 | ||
|
|
6ea2b5e1d9 | ||
|
|
b21baf1ce5 | ||
|
|
4c3fd461e4 | ||
|
|
8365a60d5d | ||
|
|
804ac1aa96 | ||
|
|
49fd2cfc4d | ||
|
|
2bb2a52f27 | ||
|
|
8d71b28afa | ||
|
|
86eac7054d | ||
|
|
dccb92d72b | ||
|
|
2e628de9c6 | ||
|
|
2d243abc12 | ||
|
|
ae08bf4d7a | ||
|
|
cbff5fb585 | ||
|
|
2650cc2f1c | ||
|
|
ec560ceab1 | ||
|
|
ad17cb8837 | ||
|
|
4f98d9641a | ||
|
|
17535ccd4c | ||
|
|
110c1ea337 | ||
|
|
7e087bfbab | ||
|
|
85e0d4b922 | ||
|
|
265262ccbf | ||
|
|
345008e9b6 | ||
|
|
849aa05d76 | ||
|
|
5ff5ec6a71 | ||
|
|
684c3f64aa | ||
|
|
4ff73ede59 | ||
|
|
7302d83f60 | ||
|
|
f9836fd66e | ||
|
|
c0f05695fe | ||
|
|
e50a1172d6 | ||
|
|
7a4234e73c | ||
|
|
4b9640e5a3 | ||
|
|
4b58054100 | ||
|
|
8951dbf4ed | ||
|
|
5047bc94eb | ||
|
|
75fb29c594 | ||
|
|
bd75f7fd2c | ||
|
|
8b8cb3c9b4 | ||
|
|
b479b21c37 | ||
|
|
525c490704 | ||
|
|
2e3599d005 | ||
|
|
f005bb1e46 | ||
|
|
21f96febdb | ||
|
|
805829be78 | ||
|
|
a7bd3f253f | ||
|
|
81e8a290f0 | ||
|
|
f46a967b6e | ||
|
|
b78d9534aa | ||
|
|
bdefaf7427 | ||
|
|
43550c7dec | ||
|
|
1b07d393f2 | ||
|
|
44fc356775 | ||
|
|
5623c68170 | ||
|
|
d5f82943cf | ||
|
|
96a020341d | ||
|
|
99b5f75763 | ||
|
|
a128ff7cd8 | ||
|
|
59329cc5da | ||
|
|
ecd5daaf55 | ||
|
|
514232d720 | ||
|
|
93abfe3202 | ||
|
|
4d9e0e3bd7 | ||
|
|
75d9bf8e38 | ||
|
|
ccb9752254 | ||
|
|
0f2ca19c6c | ||
|
|
d5a77aade4 | ||
|
|
3c89e6a128 | ||
|
|
a36be79977 | ||
|
|
617f0e0f9c | ||
|
|
0db1a94105 | ||
|
|
f29f97a00f | ||
|
|
032b5a59f2 | ||
|
|
1e5bc395ac | ||
|
|
fef4afe660 | ||
|
|
679ed1eacf | ||
|
|
78a9320017 | ||
|
|
927981bf30 | ||
|
|
70ea2f6c14 | ||
|
|
a6779414ff | ||
|
|
b62fcf523a | ||
|
|
d0bbea5a97 | ||
|
|
212d7027fc | ||
|
|
be1a5b09da | ||
|
|
3de2db4459 | ||
|
|
f2405a5b34 | ||
|
|
94abb8f959 | ||
|
|
2e30db05bc | ||
|
|
c93f3cc5dd | ||
|
|
068f8f2ba9 | ||
|
|
0980c3b012 | ||
|
|
5288d6768f | ||
|
|
34491f4ea4 | ||
|
|
8c73ca8854 | ||
|
|
0786d8eab6 | ||
|
|
209518c815 | ||
|
|
b2753b6457 | ||
|
|
4775a920c0 | ||
|
|
0e94dc8740 | ||
|
|
cf68d202d5 | ||
|
|
d29ba2bf16 | ||
|
|
720686cef5 | ||
|
|
0625c65cf0 | ||
|
|
acaefe22d1 | ||
|
|
df1f083ebf | ||
|
|
5a1dfc2ca9 | ||
|
|
d84894f1bf | ||
|
|
0fdc444c4c | ||
|
|
4d216c6f13 | ||
|
|
1c0af8eede | ||
|
|
f443f9264a | ||
|
|
d9b2981327 | ||
|
|
68e36d2a6d | ||
|
|
68b91bf98c | ||
|
|
b91ddfad05 | ||
|
|
a110cbfb5d | ||
|
|
d69f45b0c9 | ||
|
|
07df26d5c4 | ||
|
|
d24bcb7f86 | ||
|
|
9f383ba491 | ||
|
|
c38a76d587 | ||
|
|
994ca5dd02 | ||
|
|
9281f8f6cb | ||
|
|
324f579474 | ||
|
|
10bd2d4547 | ||
|
|
d1942868e4 | ||
|
|
a409b3e48d | ||
|
|
21004aab6a | ||
|
|
eaf88c6491 | ||
|
|
17dcd1b6f1 | ||
|
|
244a06eea6 | ||
|
|
afb13af7a1 | ||
|
|
ff9b935e98 | ||
|
|
4250d6fe52 | ||
|
|
5bc2094f10 | ||
|
|
552653c0ed | ||
|
|
c9bbc61c95 | ||
|
|
41a04aa3f1 | ||
|
|
cd421c4662 | ||
|
|
063e2e02bd | ||
|
|
0c3019b52e | ||
|
|
2ee2494dc4 | ||
|
|
df6ff1fffe | ||
|
|
0d2e6a6a12 | ||
|
|
79ed55a76f | ||
|
|
cbe58b9437 | ||
|
|
afc729b1c3 | ||
|
|
5d0cb0302e | ||
|
|
29e0d121cd | ||
|
|
48ca13f82c | ||
|
|
278061e4f1 | ||
|
|
186a815821 | ||
|
|
2fea9eb874 | ||
|
|
f8b6453be9 | ||
|
|
be625f8884 | ||
|
|
2271def5d3 | ||
|
|
275a8ea7cc | ||
|
|
dc236f33b1 | ||
|
|
678d739e75 | ||
|
|
8fe05a4c24 | ||
|
|
77adfdb9f0 | ||
|
|
30c94028fb | ||
|
|
b5bf0780fa | ||
|
|
ad838d82ee | ||
|
|
d5ec0c0cdd | ||
|
|
ff29f1e0c6 | ||
|
|
1f58698a04 | ||
|
|
a275f331d0 | ||
|
|
0862c6e059 | ||
|
|
46fddfd26c | ||
|
|
c547ccb4cb | ||
|
|
86e82fb149 | ||
|
|
00a284bfbd | ||
|
|
f6fbec0a2e | ||
|
|
b24c06bee6 | ||
|
|
95e7f4f645 | ||
|
|
4b75d501f5 | ||
|
|
b28d5c8b25 | ||
|
|
86e61661e6 | ||
|
|
cdd5717e63 | ||
|
|
42bd7fb4fb | ||
|
|
66e478a001 | ||
|
|
c8259abcac | ||
|
|
d6d16a63a4 | ||
|
|
df8d1f0714 | ||
|
|
fbbc6411c3 | ||
|
|
bc1e94fcab | ||
|
|
418439e3d5 | ||
|
|
46cbd04ba7 | ||
|
|
0ade6d9ece | ||
|
|
823599192f | ||
|
|
9496ab88f7 | ||
|
|
290e6ab170 | ||
|
|
a9e3572f4f | ||
|
|
92b2b7dde3 | ||
|
|
dad1e40234 | ||
|
|
b75c8b0373 | ||
|
|
fc4c471a87 | ||
|
|
24de71f240 | ||
|
|
805c39e60c | ||
|
|
10e75041e8 | ||
|
|
2364348df4 | ||
|
|
e643443660 | ||
|
|
d72e876b23 | ||
|
|
f3612774ba | ||
|
|
67d8b02c49 | ||
|
|
7abf53009a | ||
|
|
90bb4632b6 | ||
|
|
630da00235 | ||
|
|
245e0dd7ee | ||
|
|
824e288a80 | ||
|
|
99228f2e60 | ||
|
|
0f71139eba | ||
|
|
2bbe7056d1 | ||
|
|
005d8f84fd | ||
|
|
908cd7a890 | ||
|
|
d4865adf6a | ||
|
|
20411a2fd5 | ||
|
|
19f8930f5a | ||
|
|
ffc4ca1dd4 | ||
|
|
0bc9c6fb5a | ||
|
|
983f453afd | ||
|
|
60a0293495 | ||
|
|
4807d22763 | ||
|
|
e8d451bcbb | ||
|
|
4809bb6f06 | ||
|
|
d6d694cf66 | ||
|
|
132e3c3088 | ||
|
|
3c7ff78549 | ||
|
|
adbdc2cb1c | ||
|
|
e52b74bbc9 | ||
|
|
575f1b4b33 | ||
|
|
a528c99900 | ||
|
|
e6047ed383 | ||
|
|
381b7d960a | ||
|
|
045a8bde22 | ||
|
|
e528182c2a | ||
|
|
b86cdb461a | ||
|
|
7265f76770 | ||
|
|
2ed092279d | ||
|
|
7d71819be0 | ||
|
|
e848c74511 | ||
|
|
968ed146b2 | ||
|
|
5e03f12875 | ||
|
|
09f8e5f25a | ||
|
|
89f4ed3006 | ||
|
|
8d14a9557d | ||
|
|
513042d769 | ||
|
|
c28980c2a9 | ||
|
|
1c31ff4e98 | ||
|
|
7c9d3904b3 | ||
|
|
e23707f0a0 | ||
|
|
a952b18b96 | ||
|
|
35cc07bf63 | ||
|
|
118bf18434 | ||
|
|
34da15208b | ||
|
|
e0dc62c00b | ||
|
|
b58df2f172 | ||
|
|
90846fab81 | ||
|
|
efd80d5c0c | ||
|
|
e37e28a22e | ||
|
|
a1e71b318c | ||
|
|
aee6541d45 | ||
|
|
50ad5e3791 | ||
|
|
b34d72f21a | ||
|
|
e5ae420ef6 | ||
|
|
7269750264 | ||
|
|
33fe3ff733 | ||
|
|
aa77180957 | ||
|
|
3d80b6a4cd | ||
|
|
d76f9243a3 | ||
|
|
88f1a0d5cd | ||
|
|
26c435d6da | ||
|
|
e66abdea2d | ||
|
|
c7d2eeb71a | ||
|
|
0e8d681954 | ||
|
|
433d110cf0 | ||
|
|
4a514cd7bd | ||
|
|
417fee16bd | ||
|
|
49a821d9ee | ||
|
|
6a5ce098e0 | ||
|
|
1af73eebea | ||
|
|
b7ba29ac92 | ||
|
|
133c2ec308 | ||
|
|
a70fe1bba8 | ||
|
|
6dcd653eac | ||
|
|
db2f90b1ce | ||
|
|
60e5665133 | ||
|
|
7aa982849f | ||
|
|
d94f7626c2 | ||
|
|
549c289f81 | ||
|
|
b35953d1f9 | ||
|
|
941c4aeb19 | ||
|
|
11f7fcbaef | ||
|
|
6b8488ae0f | ||
|
|
ee5268a07e | ||
|
|
28bc331318 | ||
|
|
098153b6ba | ||
|
|
878f31e91e | ||
|
|
b40d09fa86 | ||
|
|
eb48f48f6b | ||
|
|
7961008500 | ||
|
|
ff87c6b226 | ||
|
|
0c2807a08b | ||
|
|
f0cf369317 | ||
|
|
5e9954c060 | ||
|
|
0c7bdf20af | ||
|
|
9c1179a6f9 | ||
|
|
43cb290c80 | ||
|
|
f8b7b7df9f | ||
|
|
b73f0a8012 | ||
|
|
75b2c7bd2e | ||
|
|
5fd9866eef | ||
|
|
4e7204bdbc | ||
|
|
ff7024e38f | ||
|
|
8c11a0b42d | ||
|
|
2df295dc1d | ||
|
|
b695d27817 | ||
|
|
8e2fd300f6 | ||
|
|
1722e103fc | ||
|
|
71464112ce | ||
|
|
003d8a1b21 | ||
|
|
adf81175f3 | ||
|
|
39274ce483 | ||
|
|
1daf07edeb | ||
|
|
cd2dc471c7 | ||
|
|
6229ca7ac9 | ||
|
|
10043cc755 | ||
|
|
e60a5430b4 | ||
|
|
f7e0cb655f | ||
|
|
04097ecfcd | ||
|
|
2222192bcd | ||
|
|
ae93c38d46 | ||
|
|
9ff9dcbe6d | ||
|
|
94d442af7d | ||
|
|
6a711d6a71 | ||
|
|
9d8e71aeb3 | ||
|
|
cfeeba209e | ||
|
|
455029851a | ||
|
|
d0f7baaad0 | ||
|
|
e3fb236139 | ||
|
|
42296e421a | ||
|
|
c6f4ed7c8f | ||
|
|
e1fb36d64d | ||
|
|
48cf695e11 | ||
|
|
5b0e0c71a0 | ||
|
|
c1a76b6fb4 | ||
|
|
945a6306ec | ||
|
|
313bacf9dc | ||
|
|
9027f48dda | ||
|
|
eea8f7cdf4 | ||
|
|
ffef239aa7 | ||
|
|
50fc15feea | ||
|
|
2d7a37c872 | ||
|
|
db468fc095 | ||
|
|
9a9f0035c2 | ||
|
|
b9270cd040 | ||
|
|
65b1bd18c4 | ||
|
|
c87ecc3d40 | ||
|
|
e39e1648f9 | ||
|
|
091d2618a2 | ||
|
|
d039b17715 | ||
|
|
e350dca72c | ||
|
|
c07e334f03 | ||
|
|
1ccfd8e392 | ||
|
|
d22c40f0a5 | ||
|
|
3d37db6e54 | ||
|
|
22bd92916b | ||
|
|
de303cf072 | ||
|
|
b22aad0678 | ||
|
|
bf02f9b256 | ||
|
|
6440ab423e | ||
|
|
fceab73226 | ||
|
|
fded0ad3e8 | ||
|
|
294eb64686 | ||
|
|
851b28c482 | ||
|
|
63fa2f1149 | ||
|
|
816d8decbd | ||
|
|
85b4eedcd6 | ||
|
|
901f40a669 | ||
|
|
bae5f202a5 | ||
|
|
131f6780c2 | ||
|
|
a0e067cacf | ||
|
|
8cdcf537be | ||
|
|
575f5f160c | ||
|
|
d8fbac584c | ||
|
|
b73e1b04dc | ||
|
|
3dd49c287d | ||
|
|
6ce6a7036b | ||
|
|
c120633f14 | ||
|
|
6e84b24309 | ||
|
|
343e35bb54 | ||
|
|
09cf94d807 | ||
|
|
a5531e20f2 | ||
|
|
0f311120af | ||
|
|
614506cada | ||
|
|
7891d14a0a | ||
|
|
2df12b6891 | ||
|
|
97b42d6be1 | ||
|
|
39baadeb04 | ||
|
|
a6f5452a85 | ||
|
|
29dc3bd550 | ||
|
|
e103605956 | ||
|
|
c510c2e540 | ||
|
|
41d65e4132 | ||
|
|
b6cb532568 | ||
|
|
a034ea3a05 | ||
|
|
adeb45a9ce | ||
|
|
4ada755793 | ||
|
|
c4be052a49 | ||
|
|
54c2d7bac9 | ||
|
|
233ab17992 | ||
|
|
e251ec64dc | ||
|
|
32bd6d76ee | ||
|
|
3fc17634aa | ||
|
|
555d725e7b | ||
|
|
35416796b5 | ||
|
|
6c737fe25f | ||
|
|
1b58e320aa | ||
|
|
658a90bf15 | ||
|
|
d092e75f3c | ||
|
|
162fae19cc | ||
|
|
58f5035ec6 | ||
|
|
c5076e4e95 | ||
|
|
28ef1e625c | ||
|
|
97441ccacb | ||
|
|
6b29aed6c4 | ||
|
|
31eb9caee4 | ||
|
|
64cf34e673 | ||
|
|
8996ebb819 | ||
|
|
a36c62044b | ||
|
|
13418109ea | ||
|
|
fe7c05aaa5 | ||
|
|
116e27e0db | ||
|
|
6b135afe1a | ||
|
|
7a9c4951a2 | ||
|
|
041a51a70f | ||
|
|
7b4ff9906e | ||
|
|
11ab5c7598 | ||
|
|
64d53c611b | ||
|
|
657d69a0fb | ||
|
|
62d39e6715 | ||
|
|
a27d8192ee | ||
|
|
41b69afe03 | ||
|
|
54b5af0741 | ||
|
|
d4060f8a5a | ||
|
|
2935d41aba | ||
|
|
af6cd10e28 | ||
|
|
435c80d870 | ||
|
|
775cec32da | ||
|
|
e8cc7abadc | ||
|
|
c1051afdc0 | ||
|
|
573d3ce11e | ||
|
|
0a89dcc6d8 | ||
|
|
d45033ae8e | ||
|
|
313e8b8c98 | ||
|
|
25685dc8b0 | ||
|
|
d6a78dfe28 | ||
|
|
de45852790 | ||
|
|
c6a505cb44 | ||
|
|
44427a40b7 | ||
|
|
8a2ac08c0a | ||
|
|
7babf66d5f | ||
|
|
50cd0b794b | ||
|
|
720f07f62c | ||
|
|
6daffbcafa | ||
|
|
b76729e836 | ||
|
|
b2294e0fc9 | ||
|
|
3559737e8e | ||
|
|
40b7ce607b | ||
|
|
1de67a00cb | ||
|
|
6b4b44dba8 | ||
|
|
c424cc5d33 | ||
|
|
5b71d010b4 | ||
|
|
b9457d3e33 | ||
|
|
60e267409c | ||
|
|
e6a2521143 | ||
|
|
ae36ed2b46 | ||
|
|
fb2ed81fd3 | ||
|
|
a74651b515 | ||
|
|
6904c192e4 | ||
|
|
6540d2670c | ||
|
|
966ba06bc4 | ||
|
|
3b4921b848 | ||
|
|
b11e10ac07 | ||
|
|
2ec7ba04f5 | ||
|
|
095910d156 | ||
|
|
c39463aea8 | ||
|
|
87e47c7ffb | ||
|
|
359f6734c5 | ||
|
|
1d3e71cf49 | ||
|
|
1ab449cecf | ||
|
|
44dd609134 | ||
|
|
1e8e161a33 | ||
|
|
c56d232e58 | ||
|
|
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 | ||
|
|
2544b5b821 | ||
|
|
90613b7cc8 | ||
|
|
173640d0b4 | ||
|
|
e70d09ebbf | ||
|
|
1ff33378e1 | ||
|
|
286739f770 | ||
|
|
45b1cd3942 | ||
|
|
b2d34ab95d | ||
|
|
f628ed55cf | ||
|
|
70ec9f50ab | ||
|
|
d6d130b8f9 | ||
|
|
40410eb10f | ||
|
|
a6e3dbd825 | ||
|
|
3882df41f1 | ||
|
|
6c67b96e30 | ||
|
|
b6d34472fe | ||
|
|
69d11b5cd0 | ||
|
|
f36f2029e5 | ||
|
|
af7476f79a | ||
|
|
890207daee | ||
|
|
9a5653f1e3 | ||
|
|
4f45ba1680 | ||
|
|
0ccfd36a83 | ||
|
|
8452a95114 | ||
|
|
9b98035ee7 | ||
|
|
6dc75afdc8 | ||
|
|
922ae7a274 | ||
|
|
2d46d12628 | ||
|
|
44f270f408 | ||
|
|
9ff70c4aef | ||
|
|
8c6a54397f | ||
|
|
ce915df2b2 | ||
|
|
84dd772b7c | ||
|
|
959f6c2c58 | ||
|
|
793b2b20e9 | ||
|
|
1c224fedc5 | ||
|
|
4299baec5d | ||
|
|
e931d3efbc | ||
|
|
36d81a6ec9 | ||
|
|
22c2d09d88 | ||
|
|
558659b54d | ||
|
|
eb1a0ba49f | ||
|
|
45212e7e14 | ||
|
|
c691af9712 | ||
|
|
0c2226b3fc | ||
|
|
eeda6b0208 | ||
|
|
d9096424c5 | ||
|
|
d171078ca4 | ||
|
|
cb18b8f347 | ||
|
|
4fdb72f93c | ||
|
|
549930b48c | ||
|
|
409a3de901 | ||
|
|
431ac5a403 | ||
|
|
999d4633ab | ||
|
|
5a26fc812d | ||
|
|
eb210e9072 | ||
|
|
dbfd25bb8e | ||
|
|
4292583d73 | ||
|
|
7c346d62eb | ||
|
|
14abbee3fb | ||
|
|
903979b3f2 | ||
|
|
913e649e5d | ||
|
|
7f8733796e | ||
|
|
1a98afee92 | ||
|
|
60daafe136 | ||
|
|
c7ad06a1f7 | ||
|
|
d07c795511 | ||
|
|
89d1071dc1 | ||
|
|
dfe34a9f22 | ||
|
|
93d3ea70fc | ||
|
|
89a76d9ead | ||
|
|
dc6bd1aae8 | ||
|
|
3412d70737 | ||
|
|
ce52d5cf42 | ||
|
|
da19066fb8 | ||
|
|
be20c2c800 | ||
|
|
c59638aaae | ||
|
|
00691f1225 | ||
|
|
735b79a4e0 | ||
|
|
443b529667 | ||
|
|
c3e62f58ab | ||
|
|
9869545777 | ||
|
|
04e5034f5f | ||
|
|
e99e3eb6c4 | ||
|
|
508615b600 | ||
|
|
4d3d416ecb | ||
|
|
be2897806a | ||
|
|
6ebde04d22 | ||
|
|
9deaa8694e | ||
|
|
5dff50ff92 | ||
|
|
ba4d90765a | ||
|
|
187ccaa95e | ||
|
|
18b6d8289f | ||
|
|
c851f8f006 | ||
|
|
9d60704c9b | ||
|
|
365d7a1afd | ||
|
|
a3d5f23861 | ||
|
|
330ea986d4 | ||
|
|
652c1aae73 | ||
|
|
c91adcd165 | ||
|
|
293c286d22 | ||
|
|
70244f79ba | ||
|
|
6ba49ad294 | ||
|
|
acf8cbfe0e | ||
|
|
f904fd00e5 | ||
|
|
831bec5baf | ||
|
|
f0257b0f87 | ||
|
|
66856d1229 | ||
|
|
27c22a4b09 | ||
|
|
1e7cce9056 | ||
|
|
219af76679 | ||
|
|
dcf773fd88 | ||
|
|
df51e6d429 | ||
|
|
6ea66b85e0 | ||
|
|
10aae90ae2 | ||
|
|
ea8e31561e | ||
|
|
45c627b0a5 | ||
|
|
1d2ca469fc | ||
|
|
e6a0c86f4e | ||
|
|
039429d452 | ||
|
|
46df9398e8 | ||
|
|
744907ac38 | ||
|
|
084677cdca | ||
|
|
891cd3124f | ||
|
|
b9fd3b8b7d | ||
|
|
f6208c1324 | ||
|
|
3a117c0f09 | ||
|
|
a0c83f33ca | ||
|
|
99b9fadd74 | ||
|
|
cf023847ad | ||
|
|
59357b274d | ||
|
|
1582184223 | ||
|
|
db9bcafb82 | ||
|
|
50b9838eec | ||
|
|
ff958b7cd6 | ||
|
|
33bc2aa220 | ||
|
|
03b331e5d5 | ||
|
|
86cc9fb7d8 | ||
|
|
f98efe0b97 | ||
|
|
6c5345df64 | ||
|
|
2d97661d28 | ||
|
|
3efe0c1ce2 | ||
|
|
6099c91216 | ||
|
|
934e4d9607 | ||
|
|
1198f6112e | ||
|
|
ac7e08ae2c | ||
|
|
9c5cbd348b | ||
|
|
8600710d23 | ||
|
|
d06d52deda | ||
|
|
948377ba93 | ||
|
|
d878028dcc | ||
|
|
65c969a321 | ||
|
|
777f7f9305 | ||
|
|
43fb37ab1d | ||
|
|
06734ec886 | ||
|
|
0b8ae93727 | ||
|
|
24cea97e08 | ||
|
|
18536e5db0 | ||
|
|
8c236cb5cb | ||
|
|
797c8ad7fa | ||
|
|
ba8d2dcb8b | ||
|
|
78a095d958 | ||
|
|
627172f6df | ||
|
|
a39f25961c | ||
|
|
e738ae5c8c | ||
|
|
749e85e8e6 | ||
|
|
cfa251b158 | ||
|
|
1ca38b8741 | ||
|
|
d7bc5a7088 | ||
|
|
3b9a2c3ee1 | ||
|
|
260ad77d39 | ||
|
|
5c508a0cd9 | ||
|
|
fd3d1607a4 | ||
|
|
abbc0fbcf1 | ||
|
|
00be41608d | ||
|
|
499c3f2e13 | ||
|
|
627845f6e4 | ||
|
|
07eea76057 | ||
|
|
fecc4e9b79 | ||
|
|
58e9302f15 | ||
|
|
86f649fab1 | ||
|
|
9ac0becfb2 | ||
|
|
eda547b868 | ||
|
|
f75e872415 | ||
|
|
aef0712165 | ||
|
|
bed4b7fd27 | ||
|
|
b53ff5daf3 | ||
|
|
bb0872b4fc | ||
|
|
b65101f4be | ||
|
|
593d242a4c | ||
|
|
db7f339c34 | ||
|
|
9f3575a874 | ||
|
|
1c9c59c512 | ||
|
|
127202b831 | ||
|
|
4f8a04ed21 | ||
|
|
63b2e0560b | ||
|
|
07291d71f2 | ||
|
|
d1ca1ec4d9 | ||
|
|
6907cf9972 | ||
|
|
d4f8d1498d | ||
|
|
0952e4a664 | ||
|
|
6a4e8c95ea | ||
|
|
983bfb7adf | ||
|
|
d7aaf5e210 | ||
|
|
50281132ad | ||
|
|
6a2b22015e | ||
|
|
2f90890f50 | ||
|
|
0fe83a0583 | ||
|
|
ce74e69480 | ||
|
|
ddea2aeb22 | ||
|
|
7bbe69cce9 | ||
|
|
e921e30d64 | ||
|
|
cd4f9d8bb4 | ||
|
|
a0553788b6 | ||
|
|
1a183d78af | ||
|
|
cabcaa892c | ||
|
|
01c9d62a2b | ||
|
|
ba76df863c | ||
|
|
81441a0895 | ||
|
|
da0222f213 | ||
|
|
fb8a2eb2e0 | ||
|
|
cde2e27e04 | ||
|
|
3758ea2cf4 | ||
|
|
e62fc11328 | ||
|
|
3cbfae83c1 | ||
|
|
57667654ef | ||
|
|
eadd66fa91 | ||
|
|
75cd94a39a | ||
|
|
7872bfe19d | ||
|
|
af008e69c2 | ||
|
|
a549abc20f | ||
|
|
116344737a | ||
|
|
93c03f4e88 | ||
|
|
445332c27c | ||
|
|
c42e1892d0 | ||
|
|
b6b526dd57 | ||
|
|
3ef7f19ffc | ||
|
|
9d0d851c2e | ||
|
|
adb35b5bef | ||
|
|
acead09377 | ||
|
|
714cf43f6a | ||
|
|
5df0755252 | ||
|
|
c14827b234 | ||
|
|
ff9ef2af41 | ||
|
|
91ef5edcc3 | ||
|
|
27302c6fcc | ||
|
|
4d975da176 | ||
|
|
5b58d8a1e8 | ||
|
|
3105958afb | ||
|
|
a505227d01 | ||
|
|
673503b76f | ||
|
|
384682421d | ||
|
|
2ddd6e6321 | ||
|
|
86739aa1ac | ||
|
|
45a46cbc7a | ||
|
|
567f453232 | ||
|
|
890f654971 | ||
|
|
572a0ac266 | ||
|
|
d26ffdbe1a | ||
|
|
0bfc9236ed | ||
|
|
32e6394b3f | ||
|
|
09735b7f47 | ||
|
|
ee280d5c7b | ||
|
|
c1b56e4cb6 | ||
|
|
6698d15f20 | ||
|
|
ef35fd02e5 | ||
|
|
8e70e20f9e | ||
|
|
9632bf5b93 | ||
|
|
dde0cab04b | ||
|
|
c8337c7287 | ||
|
|
15560a3bce | ||
|
|
2e3a60cf6e | ||
|
|
08b0c43382 | ||
|
|
4e0e11a611 | ||
|
|
ef41dfca4c | ||
|
|
cfbca4b0fd | ||
|
|
fdea9a68a1 | ||
|
|
47169e19aa | ||
|
|
0b03c8360b | ||
|
|
62f8af1455 | ||
|
|
0934d452bb | ||
|
|
f2f31790b4 | ||
|
|
cf6ecc17cc | ||
|
|
931f9bdce0 | ||
|
|
bec0528a3a | ||
|
|
670f2b1fc3 | ||
|
|
f2f6de717b | ||
|
|
f8ad2eddf3 | ||
|
|
c36a46cad6 | ||
|
|
00360c77d2 | ||
|
|
8a62cd386e | ||
|
|
450327f093 | ||
|
|
e87ec04058 | ||
|
|
f9d41de8f1 | ||
|
|
f80a1a5f6b | ||
|
|
f81caf962d | ||
|
|
d18fcf0a18 | ||
|
|
0187217c86 | ||
|
|
b820bdec09 | ||
|
|
adace2954e | ||
|
|
6eeb8eeba6 | ||
|
|
dd2a8202ef | ||
|
|
d1cfd627bc | ||
|
|
fb97b7443d | ||
|
|
48fcd45d7d | ||
|
|
5cfc418d77 | ||
|
|
f3fbe38247 | ||
|
|
a0a1c84db1 | ||
|
|
54d563f49e | ||
|
|
e8ee8b8a16 | ||
|
|
c6ac44ba14 | ||
|
|
e4d8438801 | ||
|
|
f9539ab50a | ||
|
|
59f83c2432 | ||
|
|
cd789136c0 | ||
|
|
54b5bc441e | ||
|
|
2537b6ba09 | ||
|
|
013a1b4f51 | ||
|
|
d2377bd7c3 | ||
|
|
c17314125e | ||
|
|
09a59480f3 | ||
|
|
63cc2ce70a | ||
|
|
4642e050ba | ||
|
|
27a442ed2e | ||
|
|
325ae00eeb | ||
|
|
152e4129b2 | ||
|
|
2ddcf84625 | ||
|
|
13314700cd | ||
|
|
a7a499a2b1 | ||
|
|
b646313b58 | ||
|
|
f3ce4ca803 | ||
|
|
93d99c0c47 | ||
|
|
ae1fc7572a | ||
|
|
1a527cca10 | ||
|
|
c625513924 | ||
|
|
3f58302a14 | ||
|
|
63b199c9c2 | ||
|
|
fc64c565db | ||
|
|
91e60fa82b | ||
|
|
0cc52c2206 | ||
|
|
2ffe4ba70b | ||
|
|
2afd7e3687 | ||
|
|
a0f8d13c4f | ||
|
|
2571ea021a | ||
|
|
6950e05b6a | ||
|
|
7eb767a268 | ||
|
|
8e64abc4bc | ||
|
|
52df793a74 | ||
|
|
8e44a421a2 | ||
|
|
7f4ccdcac8 | ||
|
|
03e8de2f62 | ||
|
|
8b04eecc90 | ||
|
|
16bcd86792 | ||
|
|
be3c519a57 | ||
|
|
8776cb1cea | ||
|
|
4c94503f9a | ||
|
|
48f57376d3 | ||
|
|
958469f526 | ||
|
|
2a774a7bb6 | ||
|
|
a872ad9d8b | ||
|
|
2499a05473 | ||
|
|
6b66893ea4 | ||
|
|
529c27aed5 | ||
|
|
70fc0afbc4 | ||
|
|
09f81fd0d6 | ||
|
|
af7f2d4d5e | ||
|
|
3bd5d6b9f6 | ||
|
|
57912b5a5a | ||
|
|
a05f5b9737 | ||
|
|
1963b586ac | ||
|
|
3b9ad59849 | ||
|
|
79e0e5668d | ||
|
|
0e8edf0c72 | ||
|
|
24e2544544 | ||
|
|
f3732c76ea | ||
|
|
a4c72a9a86 | ||
|
|
455610e586 | ||
|
|
634d58b3ca | ||
|
|
27bbd77e8c | ||
|
|
d8ae77ded7 | ||
|
|
0648c04728 | ||
|
|
57c26e3b4a | ||
|
|
b03afff994 | ||
|
|
77f9e60177 | ||
|
|
35bb792496 | ||
|
|
8a87304800 | ||
|
|
64bbe053f8 | ||
|
|
d3f420bf6d | ||
|
|
7fcaaa297a | ||
|
|
7c2d2044a9 | ||
|
|
aa32f59dc6 | ||
|
|
182af99e7c | ||
|
|
5b520a7a81 | ||
|
|
364917c910 | ||
|
|
ca7b9c786a | ||
|
|
15c2363098 | ||
|
|
1a11095121 | ||
|
|
2b384b1d15 | ||
|
|
a1d61edb9c | ||
|
|
96a8687896 | ||
|
|
0448773682 | ||
|
|
57998ba727 | ||
|
|
de83447cb3 | ||
|
|
eba19468d5 | ||
|
|
65c78df671 | ||
|
|
a7096aa89f | ||
|
|
15a50ef452 | ||
|
|
04036e5c87 | ||
|
|
2bbb5ef74e | ||
|
|
91eb7feb3c | ||
|
|
978d77142c | ||
|
|
e36478b9ac | ||
|
|
e1fe4dd693 | ||
|
|
b1ee949b1c | ||
|
|
a0e5f8e97e | ||
|
|
e9cfb2c4ee | ||
|
|
190b6edfb1 | ||
|
|
80a0c59f87 | ||
|
|
823fdec705 | ||
|
|
fe87dcced7 | ||
|
|
137eb44516 | ||
|
|
f60d957102 | ||
|
|
8f0b04504f | ||
|
|
2c39d8b1c8 | ||
|
|
d4d1c32288 | ||
|
|
e4f39d2b6a | ||
|
|
e5a2bfbcbd | ||
|
|
de3b76b31d | ||
|
|
53455496bf | ||
|
|
cc2a2f6dfb | ||
|
|
ee4ac7371c | ||
|
|
d5265407b9 | ||
|
|
954b3e9fc5 | ||
|
|
7d9894bef7 | ||
|
|
3b34698e8b | ||
|
|
263cb581c4 | ||
|
|
1c9cb4516c | ||
|
|
ac4ceccb4f | ||
|
|
e731b7882d | ||
|
|
84e0728ff3 | ||
|
|
666bc18e91 | ||
|
|
8f83124a0d | ||
|
|
ee91daad7e | ||
|
|
ee78c0d33b | ||
|
|
1318abd37e | ||
|
|
76a031a8c9 | ||
|
|
09482ebcf3 | ||
|
|
67424f2d3a | ||
|
|
51f530ffbe | ||
|
|
013f96a754 | ||
|
|
df6a018fb6 | ||
|
|
409eaf54c1 | ||
|
|
7e04fd342c | ||
|
|
1fe15bc6a5 | ||
|
|
ff1bffbb55 | ||
|
|
b28b18a19a | ||
|
|
bbc3c85212 | ||
|
|
26a08fac06 | ||
|
|
da9d7a4336 | ||
|
|
46c6555f94 | ||
|
|
3e980fd2d4 | ||
|
|
fb1462f669 | ||
|
|
41e1630aac | ||
|
|
ef84c4e3da | ||
|
|
61cc44cc83 | ||
|
|
c20cbe7d66 | ||
|
|
2f4af3223b | ||
|
|
e4b2c42897 | ||
|
|
746df9277c | ||
|
|
8428588a4c | ||
|
|
83a8f4b911 | ||
|
|
2736024cb7 | ||
|
|
9a32ca893e | ||
|
|
59d3c6c94f | ||
|
|
388027b731 | ||
|
|
8abdedc11d | ||
|
|
9758f5baa8 | ||
|
|
248262a597 | ||
|
|
cc0f2c7c7f | ||
|
|
72f6468d12 | ||
|
|
522c0edd90 | ||
|
|
d338f217fe | ||
|
|
ca79857386 | ||
|
|
60e551e273 | ||
|
|
954e148be3 | ||
|
|
3d0b79f674 | ||
|
|
d9442aa23c | ||
|
|
ba0daf4452 | ||
|
|
8d9cd5bbd1 | ||
|
|
186b877c09 | ||
|
|
5ed2dfccd1 | ||
|
|
911cfd8642 | ||
|
|
3539bd1e79 | ||
|
|
f56df7c16d | ||
|
|
c507dfa6c4 | ||
|
|
f6d2e898dc | ||
|
|
326c7a93fb | ||
|
|
58381b8062 | ||
|
|
0899cea4b4 | ||
|
|
7459e937b5 | ||
|
|
55db0bebbb | ||
|
|
0bdb8142c6 | ||
|
|
88ee94d4b6 | ||
|
|
1df4ed0fe9 | ||
|
|
2a339a2935 | ||
|
|
a1810e6023 | ||
|
|
832ca3347c | ||
|
|
9d2b64e82b | ||
|
|
9a5e4b3f54 | ||
|
|
e5e8032ba1 | ||
|
|
5356e68b51 | ||
|
|
cd94c625a7 | ||
|
|
972a3746a1 | ||
|
|
a9e12e4384 | ||
|
|
1690e6420f | ||
|
|
acdf61f7ab | ||
|
|
2e4fc557ea | ||
|
|
979dcead49 | ||
|
|
116ddf345d | ||
|
|
366805a64f | ||
|
|
1fee2a846a | ||
|
|
3f54eb52b2 | ||
|
|
a3847ce1c9 | ||
|
|
1e7415b692 | ||
|
|
ff950ef28a | ||
|
|
51bd12c6cf | ||
|
|
5fa37dbffb | ||
|
|
c6307e4ad3 | ||
|
|
06a54d451c | ||
|
|
e317075815 | ||
|
|
45541a255b | ||
|
|
3ab423d695 |
14
.babelrc
Normal file
14
.babelrc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"presets": ["react", "es2015"],
|
||||||
|
"env": {
|
||||||
|
"development": {
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
}
|
||||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -1,6 +1,10 @@
|
|||||||
build/
|
.DS_Store
|
||||||
node_modules/
|
|
||||||
electron_build/
|
|
||||||
.env
|
.env
|
||||||
dist/
|
Desktop.ini
|
||||||
vendor/
|
Thumbs.db
|
||||||
|
node_modules/*
|
||||||
|
!node_modules/boost
|
||||||
|
/dist
|
||||||
|
/compiled
|
||||||
|
/secret
|
||||||
|
*.log
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "browser/ace"]
|
|
||||||
path = browser/ace
|
|
||||||
url = https://github.com/ajaxorg/ace-builds.git
|
|
||||||
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
|
||||||
|
-->
|
||||||
18
LICENSE
Normal file
18
LICENSE
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
GPL-3.0
|
||||||
|
|
||||||
|
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
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
11
appdmg.json
Normal file
11
appdmg.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"title": "Boostnote",
|
||||||
|
"icon": "resources/dmg.icns",
|
||||||
|
"background": "resources/boostnote-install.png",
|
||||||
|
"icon-size": 80,
|
||||||
|
"contents": [
|
||||||
|
{ "x": 448, "y": 344, "type": "link", "path": "/Applications" },
|
||||||
|
{ "x": 192, "y": 344, "type": "file", "path": "dist/Boostnote-darwin-x64/Boostnote.app" }
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
11
bower.json
11
bower.json
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "codexen-app",
|
|
||||||
"dependencies": {
|
|
||||||
"react": "~0.13.3",
|
|
||||||
"fontawesome": "~4.3.0",
|
|
||||||
"react-router": "~0.13.3",
|
|
||||||
"reflux": "~0.2.8",
|
|
||||||
"moment": "~2.10.3",
|
|
||||||
"markdown-it": "~4.3.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Submodule browser/ace deleted from 0982db4853
286
browser/components/CodeEditor.js
Normal file
286
browser/components/CodeEditor.js
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import CodeMirror from 'codemirror'
|
||||||
|
import path from 'path'
|
||||||
|
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
|
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 {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.changeHandler = (e) => this.handleChange(e)
|
||||||
|
this.blurHandler = (editor, e) => {
|
||||||
|
if (e == null) return null
|
||||||
|
let el = e.relatedTarget
|
||||||
|
while (el != null) {
|
||||||
|
if (el === this.refs.root) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
el = el.parentNode
|
||||||
|
}
|
||||||
|
this.props.onBlur != null && this.props.onBlur(e)
|
||||||
|
}
|
||||||
|
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||||
|
this.loadStyleHandler = (e) => {
|
||||||
|
this.editor.refresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.value = this.props.value
|
||||||
|
this.editor = CodeMirror(this.refs.root, {
|
||||||
|
value: this.props.value,
|
||||||
|
lineNumbers: true,
|
||||||
|
lineWrapping: true,
|
||||||
|
theme: this.props.theme,
|
||||||
|
indentUnit: this.props.indentSize,
|
||||||
|
tabSize: this.props.indentSize,
|
||||||
|
indentWithTabs: this.props.indentType !== 'space',
|
||||||
|
keyMap: this.props.keyMap,
|
||||||
|
inputStyle: 'textarea',
|
||||||
|
dragDrop: false,
|
||||||
|
extraKeys: {
|
||||||
|
Tab: function (cm) {
|
||||||
|
const cursor = cm.getCursor()
|
||||||
|
const line = cm.getLine(cursor.line)
|
||||||
|
if (cm.somethingSelected()) cm.indentSelection('add')
|
||||||
|
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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Cmd-T': function (cm) {
|
||||||
|
// Do nothing
|
||||||
|
},
|
||||||
|
Enter: 'newlineAndIndentContinueMarkdownList',
|
||||||
|
'Ctrl-C': (cm) => {
|
||||||
|
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
||||||
|
document.execCommand('copy')
|
||||||
|
}
|
||||||
|
return CodeMirror.Pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setMode(this.props.mode)
|
||||||
|
|
||||||
|
this.editor.on('blur', this.blurHandler)
|
||||||
|
this.editor.on('change', this.changeHandler)
|
||||||
|
this.editor.on('paste', this.pasteHandler)
|
||||||
|
|
||||||
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
|
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||||
|
|
||||||
|
CodeMirror.Vim.defineEx('quit', 'q', this.quitEditor)
|
||||||
|
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
|
||||||
|
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
||||||
|
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
||||||
|
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
||||||
|
}
|
||||||
|
|
||||||
|
quitEditor () {
|
||||||
|
document.querySelector('textarea').blur()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.editor.off('blur', this.blurHandler)
|
||||||
|
this.editor.off('change', this.changeHandler)
|
||||||
|
this.editor.off('paste', this.pasteHandler)
|
||||||
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
|
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps, prevState) {
|
||||||
|
let needRefresh = false
|
||||||
|
if (prevProps.mode !== this.props.mode) {
|
||||||
|
this.setMode(this.props.mode)
|
||||||
|
}
|
||||||
|
if (prevProps.theme !== this.props.theme) {
|
||||||
|
this.editor.setOption('theme', this.props.theme)
|
||||||
|
// editor should be refreshed after css loaded
|
||||||
|
}
|
||||||
|
if (prevProps.fontSize !== 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) {
|
||||||
|
this.editor.setOption('indentUnit', this.props.indentSize)
|
||||||
|
this.editor.setOption('tabSize', this.props.indentSize)
|
||||||
|
}
|
||||||
|
if (prevProps.indentType !== this.props.indentType) {
|
||||||
|
this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
this.value = this.editor.getValue()
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveCursorTo (row, col) {
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToLine (num) {
|
||||||
|
}
|
||||||
|
|
||||||
|
focus () {
|
||||||
|
this.editor.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
blur () {
|
||||||
|
this.editor.blur()
|
||||||
|
}
|
||||||
|
|
||||||
|
reload () {
|
||||||
|
// Change event shouldn't be fired when switch note
|
||||||
|
this.editor.off('change', this.changeHandler)
|
||||||
|
this.value = this.props.value
|
||||||
|
this.editor.setValue(this.props.value)
|
||||||
|
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 () {
|
||||||
|
const { className, fontSize } = this.props
|
||||||
|
let fontFamily = this.props.className
|
||||||
|
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
||||||
|
? [fontFamily].concat(defaultEditorFontFamily)
|
||||||
|
: defaultEditorFontFamily
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={className == null
|
||||||
|
? 'CodeEditor'
|
||||||
|
: `CodeEditor ${className}`
|
||||||
|
}
|
||||||
|
ref='root'
|
||||||
|
tabIndex='-1'
|
||||||
|
style={{
|
||||||
|
fontFamily: fontFamily.join(', '),
|
||||||
|
fontSize: fontSize
|
||||||
|
}}
|
||||||
|
onDrop={(e) => this.handleDropImage(e)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeEditor.propTypes = {
|
||||||
|
value: PropTypes.string,
|
||||||
|
mode: PropTypes.string,
|
||||||
|
className: PropTypes.string,
|
||||||
|
onBlur: PropTypes.func,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
readOnly: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeEditor.defaultProps = {
|
||||||
|
readOnly: false,
|
||||||
|
theme: 'xcode',
|
||||||
|
keyMap: 'sublime',
|
||||||
|
fontSize: 14,
|
||||||
|
fontFamily: 'Monaco, Consolas',
|
||||||
|
indentSize: 4,
|
||||||
|
indentType: 'space'
|
||||||
|
}
|
||||||
284
browser/components/MarkdownEditor.js
Normal file
284
browser/components/MarkdownEditor.js
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './MarkdownEditor.styl'
|
||||||
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
|
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||||
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
|
||||||
|
class MarkdownEditor extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
// char codes for ctrl + w
|
||||||
|
this.escapeFromEditor = [17, 87]
|
||||||
|
|
||||||
|
// ctrl + shift + ;
|
||||||
|
this.supportMdSelectionBold = [16, 17, 186]
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
status: 'PREVIEW',
|
||||||
|
renderValue: props.value,
|
||||||
|
keyPressed: new Set(),
|
||||||
|
isLocked: false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lockEditorCode = () => this.handleLockEditor()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.value = this.refs.code.value
|
||||||
|
eventEmitter.on('editor:lock', this.lockEditorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate () {
|
||||||
|
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) {
|
||||||
|
this.value = this.refs.code.value
|
||||||
|
this.props.onChange(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleContextMenu (e) {
|
||||||
|
const { config } = this.props
|
||||||
|
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
||||||
|
const newStatus = this.state.status === 'PREVIEW'
|
||||||
|
? 'CODE'
|
||||||
|
: 'PREVIEW'
|
||||||
|
this.setState({
|
||||||
|
status: newStatus
|
||||||
|
}, () => {
|
||||||
|
if (newStatus === 'CODE') {
|
||||||
|
this.refs.code.focus()
|
||||||
|
} else {
|
||||||
|
this.refs.preview.focus()
|
||||||
|
}
|
||||||
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlur (e) {
|
||||||
|
if (this.state.isLocked) return
|
||||||
|
this.setState({ keyPressed: new Set() })
|
||||||
|
const { config } = this.props
|
||||||
|
if (config.editor.switchPreview === 'BLUR') {
|
||||||
|
const cursorPosition = this.refs.code.editor.getCursor()
|
||||||
|
this.setState({
|
||||||
|
status: 'PREVIEW'
|
||||||
|
}, () => {
|
||||||
|
this.refs.preview.focus()
|
||||||
|
this.refs.preview.scrollTo(cursorPosition.line)
|
||||||
|
})
|
||||||
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePreviewMouseDown (e) {
|
||||||
|
this.previewMouseDownedAt = new Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePreviewMouseUp (e) {
|
||||||
|
const { config } = this.props
|
||||||
|
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
|
||||||
|
this.setState({
|
||||||
|
status: 'CODE'
|
||||||
|
}, () => {
|
||||||
|
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'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focus () {
|
||||||
|
if (this.state.status === 'PREVIEW') {
|
||||||
|
this.setState({
|
||||||
|
status: 'CODE'
|
||||||
|
}, () => {
|
||||||
|
this.refs.code.focus()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.refs.code.focus()
|
||||||
|
}
|
||||||
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 () {
|
||||||
|
const { className, value, config, storageKey } = this.props
|
||||||
|
|
||||||
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
|
||||||
|
const previewStyle = {}
|
||||||
|
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
||||||
|
|
||||||
|
const storage = findStorage(storageKey)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className == null
|
||||||
|
? 'MarkdownEditor'
|
||||||
|
: `MarkdownEditor ${className}`
|
||||||
|
}
|
||||||
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
|
tabIndex='-1'
|
||||||
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
|
onKeyUp={(e) => this.handleKeyUp(e)}
|
||||||
|
>
|
||||||
|
<CodeEditor styleName={this.state.status === 'CODE'
|
||||||
|
? 'codeEditor'
|
||||||
|
: 'codeEditor--hide'
|
||||||
|
}
|
||||||
|
ref='code'
|
||||||
|
mode='GitHub Flavored Markdown'
|
||||||
|
value={value}
|
||||||
|
theme={config.editor.theme}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
|
fontFamily={config.editor.fontFamily}
|
||||||
|
fontSize={editorFontSize}
|
||||||
|
indentType={config.editor.indentType}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
|
storageKey={storageKey}
|
||||||
|
onChange={(e) => this.handleChange(e)}
|
||||||
|
onBlur={(e) => this.handleBlur(e)}
|
||||||
|
/>
|
||||||
|
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
||||||
|
? 'preview'
|
||||||
|
: 'preview--hide'
|
||||||
|
}
|
||||||
|
style={previewStyle}
|
||||||
|
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}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
|
ref='preview'
|
||||||
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
|
tabIndex='0'
|
||||||
|
value={this.state.renderValue}
|
||||||
|
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
||||||
|
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
||||||
|
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||||
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
|
storagePath={storage.path}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkdownEditor.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
ignorePreviewPointerEvents: PropTypes.bool
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(MarkdownEditor, styles)
|
||||||
27
browser/components/MarkdownEditor.styl
Normal file
27
browser/components/MarkdownEditor.styl
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
.root
|
||||||
|
position relative
|
||||||
|
|
||||||
|
.codeEditor
|
||||||
|
absolute top bottom left right
|
||||||
|
|
||||||
|
.hide
|
||||||
|
z-index 0
|
||||||
|
opacity 0
|
||||||
|
pointer-events none
|
||||||
|
|
||||||
|
.codeEditor--hide
|
||||||
|
@extend .codeEditor
|
||||||
|
@extend .hide
|
||||||
|
|
||||||
|
.preview
|
||||||
|
display block
|
||||||
|
absolute top bottom left right
|
||||||
|
z-index 100
|
||||||
|
background-color white
|
||||||
|
height 100%
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
.preview--hide
|
||||||
|
@extend .preview
|
||||||
|
@extend .hide
|
||||||
|
|
||||||
464
browser/components/MarkdownPreview.js
Normal file
464
browser/components/MarkdownPreview.js
Normal file
@@ -0,0 +1,464 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import markdown from 'browser/lib/markdown'
|
||||||
|
import _ from 'lodash'
|
||||||
|
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 appPath = 'file://' + (process.env.NODE_ENV === 'production'
|
||||||
|
? app.getAppPath()
|
||||||
|
: path.resolve())
|
||||||
|
|
||||||
|
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 defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
||||||
|
if (!OSX) {
|
||||||
|
defaultFontFamily.unshift('Microsoft YaHei')
|
||||||
|
defaultFontFamily.unshift('meiryo')
|
||||||
|
}
|
||||||
|
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||||
|
|
||||||
|
export default class MarkdownPreview extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
||||||
|
this.mouseDownHandler = (e) => this.handleMouseDown(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) {
|
||||||
|
this.props.onContextMenu(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseDown (e) {
|
||||||
|
if (e.target != null) {
|
||||||
|
switch (e.target.tagName) {
|
||||||
|
case 'A':
|
||||||
|
case 'INPUT':
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.props.onMouseDown != null) this.props.onMouseDown(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseUp (e) {
|
||||||
|
if (e.target != null && e.target.tagName === 'A') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
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 () {
|
||||||
|
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
||||||
|
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.applyStyle()
|
||||||
|
|
||||||
|
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
|
||||||
|
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 () {
|
||||||
|
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('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) {
|
||||||
|
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||||
|
if (prevProps.fontFamily !== this.props.fontFamily ||
|
||||||
|
prevProps.fontSize !== this.props.fontSize ||
|
||||||
|
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
||||||
|
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
||||||
|
prevProps.lineNumber !== this.props.lineNumber ||
|
||||||
|
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
||||||
|
prevProps.theme !== this.props.theme) {
|
||||||
|
this.applyStyle()
|
||||||
|
this.rewriteIframe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyStyle () {
|
||||||
|
const { fontSize, lineNumber, codeBlockTheme } = this.props
|
||||||
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
|
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
|
? [fontFamily].concat(defaultFontFamily)
|
||||||
|
: defaultFontFamily
|
||||||
|
codeBlockFontFamily = _.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
||||||
|
? [codeBlockFontFamily].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 () {
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
|
el.removeEventListener('click', this.anchorClickHandler)
|
||||||
|
})
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
|
el.removeEventListener('click', this.checkboxClickHandler)
|
||||||
|
})
|
||||||
|
|
||||||
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
|
el.removeEventListener('click', this.linkClickHandler)
|
||||||
|
})
|
||||||
|
|
||||||
|
const { theme, indentSize, showCopyNotification, storagePath } = this.props
|
||||||
|
let { value, codeBlockTheme } = this.props
|
||||||
|
|
||||||
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
focus () {
|
||||||
|
this.refs.root.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
getWindow () {
|
||||||
|
return this.refs.root.contentWindow
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollTo (targetRow) {
|
||||||
|
const blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
|
||||||
|
|
||||||
|
for (let index = 0; index < blocks.length; index++) {
|
||||||
|
let block = blocks[index]
|
||||||
|
const row = parseInt(block.getAttribute('data-line'))
|
||||||
|
if (row > targetRow || index === blocks.length - 1) {
|
||||||
|
block = blocks[index - 1]
|
||||||
|
block != null && this.getWindow().scrollTo(0, block.offsetTop)
|
||||||
|
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 () {
|
||||||
|
const { className, style, tabIndex } = this.props
|
||||||
|
return (
|
||||||
|
<iframe className={className != null
|
||||||
|
? 'MarkdownPreview ' + className
|
||||||
|
: 'MarkdownPreview'
|
||||||
|
}
|
||||||
|
style={style}
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
ref='root'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkdownPreview.propTypes = {
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
onDoubleClick: PropTypes.func,
|
||||||
|
onMouseUp: PropTypes.func,
|
||||||
|
onMouseDown: PropTypes.func,
|
||||||
|
className: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
showCopyNotification: PropTypes.bool,
|
||||||
|
storagePath: PropTypes.string
|
||||||
|
}
|
||||||
19
browser/components/ModalEscButton.js
Normal file
19
browser/components/ModalEscButton.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './ModalEscButton.styl'
|
||||||
|
|
||||||
|
const ModalEscButton = ({
|
||||||
|
handleEscButtonClick
|
||||||
|
}) => (
|
||||||
|
<button styleName='escButton' onClick={handleEscButtonClick}>
|
||||||
|
<div styleName='esc-mark'>×</div>
|
||||||
|
<div styleName='esc-text'>esc</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
ModalEscButton.propTypes = {
|
||||||
|
handleEscButtonClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(ModalEscButton, styles)
|
||||||
16
browser/components/ModalEscButton.styl
Normal file
16
browser/components/ModalEscButton.styl
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.escButton
|
||||||
|
height 50px
|
||||||
|
position absolute
|
||||||
|
background-color transparent
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
border none
|
||||||
|
top 1px
|
||||||
|
right 10px
|
||||||
|
text-align center
|
||||||
|
width top-bar--height
|
||||||
|
height top-bar-height
|
||||||
|
|
||||||
|
.esc-mark
|
||||||
|
font-size 28px
|
||||||
|
margin-top -5px
|
||||||
|
margin-bottom -7px
|
||||||
30
browser/components/NavToggleButton.js
Normal file
30
browser/components/NavToggleButton.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for toggle SideNav
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import styles from './NavToggleButton.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} isFolded
|
||||||
|
* @param {Function} handleToggleButtonClick
|
||||||
|
*/
|
||||||
|
|
||||||
|
const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
|
||||||
|
<button styleName='navToggle'
|
||||||
|
onClick={(e) => handleToggleButtonClick(e)}
|
||||||
|
>
|
||||||
|
{isFolded
|
||||||
|
? <i className='fa fa-angle-double-right' />
|
||||||
|
: <i className='fa fa-angle-double-left' />
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
NavToggleButton.propTypes = {
|
||||||
|
isFolded: PropTypes.bool.isRequired,
|
||||||
|
handleToggleButtonClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(NavToggleButton, styles)
|
||||||
22
browser/components/NavToggleButton.styl
Normal file
22
browser/components/NavToggleButton.styl
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
.navToggle
|
||||||
|
navButtonColor()
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
left 5px
|
||||||
|
bottom 5px
|
||||||
|
border-radius 16.5px
|
||||||
|
height 34px
|
||||||
|
width 34px
|
||||||
|
line-height 32px
|
||||||
|
padding 0
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
navWhiteButtonColor()
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.navToggle
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
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)
|
||||||
233
browser/components/NoteItem.styl
Normal file
233
browser/components/NoteItem.styl
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
$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
|
||||||
54
browser/components/NoteItemSimple.js
Normal file
54
browser/components/NoteItemSimple.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* @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 }) => (
|
||||||
|
<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.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)
|
||||||
145
browser/components/NoteItemSimple.styl
Normal file
145
browser/components/NoteItemSimple.styl
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
$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
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
55
browser/components/RealtimeNotification.js
Normal file
55
browser/components/RealtimeNotification.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './RealtimeNotification.styl'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const { shell } = electron
|
||||||
|
|
||||||
|
class RealtimeNotification extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
notifications: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.fetchNotifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchNotifications () {
|
||||||
|
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
|
||||||
|
fetch(notificationsUrl)
|
||||||
|
.then(response => {
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then(json => {
|
||||||
|
this.setState({notifications: json.notifications})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLinkClick (e) {
|
||||||
|
shell.openExternal(e.currentTarget.href)
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { notifications } = this.state
|
||||||
|
const link = notifications.length > 0
|
||||||
|
? <a styleName='notification-link' href={notifications[0].linkUrl}
|
||||||
|
onClick={(e) => this.handleLinkClick(e)}
|
||||||
|
>
|
||||||
|
Info: {notifications[0].text}
|
||||||
|
</a>
|
||||||
|
: ''
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div styleName='notification-area' style={this.props.style}>{link}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealtimeNotification.propTypes = {}
|
||||||
|
|
||||||
|
export default CSSModules(RealtimeNotification, styles)
|
||||||
31
browser/components/RealtimeNotification.styl
Normal file
31
browser/components/RealtimeNotification.styl
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
.notification-area
|
||||||
|
z-index 1000
|
||||||
|
font-size 12px
|
||||||
|
position: relative
|
||||||
|
top: 12px
|
||||||
|
background-color none
|
||||||
|
|
||||||
|
.notification-link
|
||||||
|
position absolute
|
||||||
|
text-decoration none
|
||||||
|
color #282A36
|
||||||
|
font-size 14px
|
||||||
|
border 1px solid #6FA8E6
|
||||||
|
background-color alpha(#6FA8E6, 0.2)
|
||||||
|
padding 5px 12px
|
||||||
|
border-radius 2px
|
||||||
|
transition 0.2s
|
||||||
|
&:hover
|
||||||
|
color #1378BD
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.notification-area
|
||||||
|
background-color none
|
||||||
|
|
||||||
|
.notification-link
|
||||||
|
color #fff
|
||||||
|
border 1px solid alpha(#5CB85C, 0.6)
|
||||||
|
background-color alpha(#5CB85C, 0.2)
|
||||||
|
transition 0.2s
|
||||||
|
&:hover
|
||||||
|
color #5CB85C
|
||||||
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.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.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)
|
||||||
185
browser/components/SideNavFilter.styl
Normal file
185
browser/components/SideNavFilter.styl
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
.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
|
||||||
|
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
|
||||||
132
browser/components/SnippetTab.js
Normal file
132
browser/components/SnippetTab.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
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()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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)}
|
||||||
|
>
|
||||||
|
{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)
|
||||||
91
browser/components/SnippetTab.styl
Normal file
91
browser/components/SnippetTab.styl
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
.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 transparent
|
||||||
|
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
|
||||||
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)
|
||||||
111
browser/components/StorageItem.styl
Normal file
111
browser/components/StorageItem.styl
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
.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%)
|
||||||
24
browser/components/StorageList.js
Normal file
24
browser/components/StorageList.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for showing StorageList
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import styles from './StorageList.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array} storgaeList
|
||||||
|
*/
|
||||||
|
|
||||||
|
const StorageList = ({storageList}) => (
|
||||||
|
<div styleName='storageList'>
|
||||||
|
{storageList.length > 0 ? storageList : (
|
||||||
|
<div styleName='storgaeList-empty'>No storage mount.</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
StorageList.propTypes = {
|
||||||
|
storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired
|
||||||
|
}
|
||||||
|
export default CSSModules(StorageList, styles)
|
||||||
20
browser/components/StorageList.styl
Normal file
20
browser/components/StorageList.styl
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.storageList
|
||||||
|
absolute left right
|
||||||
|
bottom 37px
|
||||||
|
top 180px
|
||||||
|
overflow-y auto
|
||||||
|
|
||||||
|
.storageList-empty
|
||||||
|
padding 0 10px
|
||||||
|
margin-top 15px
|
||||||
|
line-height 24px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.storageList-empty
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
.root-folded
|
||||||
|
.storageList-empty
|
||||||
|
white-space nowrap
|
||||||
|
transform rotate(90deg)
|
||||||
28
browser/components/TagListItem.js
Normal file
28
browser/components/TagListItem.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for showing TagList.
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import styles from './TagListItem.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Function} handleClickTagListItem
|
||||||
|
* @param {bool} isActive
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TagListItem = ({name, handleClickTagListItem, isActive}) => (
|
||||||
|
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
||||||
|
<span styleName='tagList-item-name'>
|
||||||
|
{`# ${name}`}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
TagListItem.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
handleClickTagListItem: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(TagListItem, styles)
|
||||||
84
browser/components/TagListItem.styl
Normal file
84
browser/components/TagListItem.styl
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
.tagList-item
|
||||||
|
display flex
|
||||||
|
width 100%
|
||||||
|
height 26px
|
||||||
|
background-color transparent
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding 0
|
||||||
|
margin-bottom 5px
|
||||||
|
text-align left
|
||||||
|
border none
|
||||||
|
overflow ellipsis
|
||||||
|
font-size 13px
|
||||||
|
&:first-child
|
||||||
|
margin-top 0
|
||||||
|
&:hover
|
||||||
|
color $ui-button-default-color
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
transition background-color 0.15s
|
||||||
|
&:active, &:active:hover
|
||||||
|
color $ui-button-default-color
|
||||||
|
background-color $ui-button-default--active-backgroundColor
|
||||||
|
|
||||||
|
.tagList-item-active
|
||||||
|
background-color $ui-button-default--active-backgroundColor
|
||||||
|
display flex
|
||||||
|
width 100%
|
||||||
|
height 26px
|
||||||
|
padding 0
|
||||||
|
margin-bottom 5px
|
||||||
|
text-align left
|
||||||
|
border none
|
||||||
|
overflow ellipsis
|
||||||
|
font-size 13px
|
||||||
|
color $ui-button-default-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 60%)
|
||||||
|
transition 0.2s
|
||||||
|
|
||||||
|
.tagList-item-name
|
||||||
|
display block
|
||||||
|
flex 1
|
||||||
|
padding 0 15px
|
||||||
|
height 26px
|
||||||
|
line-height 26px
|
||||||
|
border-width 0 0 0 2px
|
||||||
|
border-style solid
|
||||||
|
border-color transparent
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.tagList-item
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-text-color
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
|
&:active
|
||||||
|
color $ui-text-color
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.tagList-item-active
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.tagList-item
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
&:active
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.tagList-item-active
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:active
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
|
&:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
28
browser/components/TodoListPercentage.js
Normal file
28
browser/components/TodoListPercentage.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Percentage of todo achievement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './TodoListPercentage.styl'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} percentageOfTodo
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TodoListPercentage = ({
|
||||||
|
percentageOfTodo
|
||||||
|
}) => (
|
||||||
|
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
|
||||||
|
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
|
||||||
|
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
TodoListPercentage.propTypes = {
|
||||||
|
percentageOfTodo: PropTypes.number.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(TodoListPercentage, styles)
|
||||||
32
browser/components/TodoListPercentage.styl
Normal file
32
browser/components/TodoListPercentage.styl
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
.percentageBar
|
||||||
|
position absolute
|
||||||
|
top 50px
|
||||||
|
right 0px
|
||||||
|
left 0px
|
||||||
|
background-color #DADFE1
|
||||||
|
width 100%
|
||||||
|
height: 17px
|
||||||
|
font-size: 12px
|
||||||
|
z-index 100
|
||||||
|
border-radius 2px
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: #1EC38B
|
||||||
|
height 17px
|
||||||
|
border-radius 2px
|
||||||
|
transition 0.4s cubic-bezier(0.4, 0.4, 0, 1)
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color #f4f4f4
|
||||||
|
padding: 2px 43%
|
||||||
|
font-weight 600
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.percentageBar
|
||||||
|
background-color #363A3D
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: alpha(#939395, 50%)
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color $ui-dark-text-color
|
||||||
34
browser/components/TodoProcess.js
Normal file
34
browser/components/TodoProcess.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Percentage of todo achievement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './TodoProcess.styl'
|
||||||
|
|
||||||
|
const TodoProcess = ({
|
||||||
|
todoStatus: {
|
||||||
|
total: totalTodo,
|
||||||
|
completed: completedTodo
|
||||||
|
}
|
||||||
|
}) => (
|
||||||
|
<div styleName='todo-process' style={{display: totalTodo > 0 ? '' : 'none'}}>
|
||||||
|
<div styleName='todo-process-text'>
|
||||||
|
<i className='fa fa-fw fa-check-square-o' />
|
||||||
|
{completedTodo} of {totalTodo}
|
||||||
|
</div>
|
||||||
|
<div styleName='todo-process-bar'>
|
||||||
|
<div styleName='todo-process-bar--inner' style={{width: parseInt(completedTodo / totalTodo * 100) + '%'}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
TodoProcess.propTypes = {
|
||||||
|
todoStatus: {
|
||||||
|
total: PropTypes.number.isRequired,
|
||||||
|
completed: PropTypes.number.isRequired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(TodoProcess, styles)
|
||||||
45
browser/components/TodoProcess.styl
Normal file
45
browser/components/TodoProcess.styl
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
.todo-process
|
||||||
|
font-size 12px
|
||||||
|
display flex
|
||||||
|
padding-top 15px
|
||||||
|
width 85%
|
||||||
|
|
||||||
|
.todo-process-text
|
||||||
|
display inline-block
|
||||||
|
padding-right 10px
|
||||||
|
white-space nowrap
|
||||||
|
text-overflow ellipsis
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
i
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding-right 5px
|
||||||
|
|
||||||
|
.todo-process-bar
|
||||||
|
display inline-block
|
||||||
|
margin auto
|
||||||
|
height 4px
|
||||||
|
border-radius 10px
|
||||||
|
background-color #DADFE1
|
||||||
|
border-radius 2px
|
||||||
|
flex-grow 1
|
||||||
|
border 1px solid alpha(#6C7A89, 10%)
|
||||||
|
|
||||||
|
.todo-process-bar--inner
|
||||||
|
height 100%
|
||||||
|
border-radius 5px
|
||||||
|
background-color #6C7A89
|
||||||
|
transition 0.3s
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.todo-process
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.todo-process-bar
|
||||||
|
background-color #363A3D
|
||||||
|
|
||||||
|
.todo-process-text
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.todo-process-bar--inner
|
||||||
|
background-color: alpha(#939395, 50%)
|
||||||
332
browser/components/markdown.styl
Normal file
332
browser/components/markdown.styl
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
global-reset()
|
||||||
|
|
||||||
|
borderColor = #D0D0D0 // using
|
||||||
|
highlightenBorderColor = darken(borderColor, 20%)
|
||||||
|
invBorderColor = #404849
|
||||||
|
brandBorderColor = #3FB399
|
||||||
|
|
||||||
|
focusBorderColor = #369DCD
|
||||||
|
|
||||||
|
buttonBorderColor = #4C4C4C
|
||||||
|
|
||||||
|
lightButtonColor = #898989
|
||||||
|
|
||||||
|
hoverBackgroundColor= transparentify(#444, 4%) // using
|
||||||
|
|
||||||
|
inactiveTextColor = #888 // using
|
||||||
|
textColor = #4D4D4D // using
|
||||||
|
backgroundColor= white
|
||||||
|
fontSize= 14px // using
|
||||||
|
|
||||||
|
shadowColor= #C5C5C5
|
||||||
|
|
||||||
|
invBackgroundColor = #4C4C4C
|
||||||
|
invTextColor = white
|
||||||
|
|
||||||
|
btnColor = #888
|
||||||
|
btnHighlightenColor = #000
|
||||||
|
|
||||||
|
brandColor = #2BAC8F
|
||||||
|
|
||||||
|
popupShadow = 0 0 5px 0 #888
|
||||||
|
|
||||||
|
|
||||||
|
tableHeadBgColor = white
|
||||||
|
tableOddBgColor = #F9F9F9
|
||||||
|
tableEvenBgColor = white
|
||||||
|
|
||||||
|
facebookColor= #3b5998
|
||||||
|
githubBtn= #201F1F
|
||||||
|
|
||||||
|
// using
|
||||||
|
successBackgroundColor= #E0F0D9
|
||||||
|
successTextColor= #3E753F
|
||||||
|
errorBackgroundColor= #F2DEDE
|
||||||
|
errorTextColor= #A64444
|
||||||
|
infoBackgroundColor= #D9EDF7
|
||||||
|
infoTextColor= #34708E
|
||||||
|
|
||||||
|
popupZIndex= 500
|
||||||
|
|
||||||
|
body
|
||||||
|
font-size 16px
|
||||||
|
padding 15px
|
||||||
|
font-family helvetica, arial, sans-serif
|
||||||
|
line-height 1.6
|
||||||
|
overflow-x hidden
|
||||||
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
.katex
|
||||||
|
font 400 1.2em 'KaTeX_Main'
|
||||||
|
line-height 1.2em
|
||||||
|
white-space nowrap
|
||||||
|
text-indent 0
|
||||||
|
.katex .mfrac>.vlist>span:nth-child(2)
|
||||||
|
top 0 !important
|
||||||
|
.katex-error
|
||||||
|
background-color errorBackgroundColor
|
||||||
|
color errorTextColor
|
||||||
|
padding 5px
|
||||||
|
margin -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
|
||||||
|
div.math-rendered
|
||||||
|
text-align center
|
||||||
|
.math-failed
|
||||||
|
background-color alpha(red, 0.1)
|
||||||
|
color darken(red, 15%)
|
||||||
|
padding 5px
|
||||||
|
margin 5px 0
|
||||||
|
border-radius 5px
|
||||||
|
sup
|
||||||
|
position relative
|
||||||
|
top -.4em
|
||||||
|
font-size 0.8em
|
||||||
|
vertical-align top
|
||||||
|
sub
|
||||||
|
position relative
|
||||||
|
bottom -.4em
|
||||||
|
font-size 0.8em
|
||||||
|
vertical-align top
|
||||||
|
a
|
||||||
|
color brandColor
|
||||||
|
text-decoration none
|
||||||
|
padding 5px
|
||||||
|
border-radius 5px
|
||||||
|
margin -5px
|
||||||
|
transition .1s
|
||||||
|
display inline-block
|
||||||
|
img
|
||||||
|
vertical-align sub
|
||||||
|
&:hover
|
||||||
|
color lighten(brandColor, 5%)
|
||||||
|
text-decoration underline
|
||||||
|
background-color alpha(#FFC95C, 0.3)
|
||||||
|
&:visited
|
||||||
|
color brandColor
|
||||||
|
hr
|
||||||
|
border-top none
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
margin 15px 0
|
||||||
|
h1, h2, h3, h4, h5, h6
|
||||||
|
font-weight bold
|
||||||
|
h1
|
||||||
|
font-size 2.55em
|
||||||
|
padding-bottom 0.3em
|
||||||
|
line-height 1.2em
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
margin 1em 0 0.44em
|
||||||
|
&:first-child
|
||||||
|
margin-top 0
|
||||||
|
h2
|
||||||
|
font-size 1.75em
|
||||||
|
padding-bottom 0.3em
|
||||||
|
line-height 1.225em
|
||||||
|
border-bottom solid 1px borderColor
|
||||||
|
margin 1em 0 0.57em
|
||||||
|
&:first-child
|
||||||
|
margin-top 0
|
||||||
|
h3
|
||||||
|
font-size 1.5em
|
||||||
|
line-height 1.43em
|
||||||
|
margin 1em 0 0.66em
|
||||||
|
h4
|
||||||
|
font-size 1.25em
|
||||||
|
line-height 1.4em
|
||||||
|
margin 1em 0 0.8em
|
||||||
|
h5
|
||||||
|
font-size 1em
|
||||||
|
line-height 1.4em
|
||||||
|
margin 1em 0 1em
|
||||||
|
h6
|
||||||
|
font-size 1em
|
||||||
|
line-height 1.4em
|
||||||
|
margin 1em 0 1em
|
||||||
|
color #777
|
||||||
|
p
|
||||||
|
line-height 1.6em
|
||||||
|
margin 0 0 1em
|
||||||
|
white-space pre-line
|
||||||
|
img
|
||||||
|
max-width 100%
|
||||||
|
strong, b
|
||||||
|
font-weight bold
|
||||||
|
em, i
|
||||||
|
font-style italic
|
||||||
|
s, del, strike
|
||||||
|
text-decoration line-through
|
||||||
|
u
|
||||||
|
text-decoration underline
|
||||||
|
blockquote
|
||||||
|
border-left solid 4px brandBorderColor
|
||||||
|
margin 0 0 1em
|
||||||
|
padding 0 25px
|
||||||
|
ul
|
||||||
|
list-style-type disc
|
||||||
|
padding-left 2em
|
||||||
|
margin-bottom 1em
|
||||||
|
li
|
||||||
|
display list-item
|
||||||
|
p
|
||||||
|
margin 0
|
||||||
|
&>li>ul, &>li>ol
|
||||||
|
margin 0
|
||||||
|
&>li>ul
|
||||||
|
list-style-type circle
|
||||||
|
&>li>ul
|
||||||
|
list-style-type square
|
||||||
|
ol
|
||||||
|
list-style-type decimal
|
||||||
|
padding-left 2em
|
||||||
|
margin-bottom 1em
|
||||||
|
li
|
||||||
|
display list-item
|
||||||
|
p
|
||||||
|
margin 0
|
||||||
|
&>li>ul, &>li>ol
|
||||||
|
margin 0
|
||||||
|
code
|
||||||
|
color #CC305F
|
||||||
|
padding 0.2em 0.4em
|
||||||
|
background-color #f7f7f7
|
||||||
|
border-radius 3px
|
||||||
|
font-size 1em
|
||||||
|
text-decoration none
|
||||||
|
margin-right 2px
|
||||||
|
pre
|
||||||
|
padding 0.5em !important
|
||||||
|
border solid 1px #D1D1D1
|
||||||
|
border-radius 5px
|
||||||
|
overflow-x auto
|
||||||
|
margin 0 0 1em
|
||||||
|
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
|
||||||
|
background-color inherit
|
||||||
|
margin 0
|
||||||
|
padding 0
|
||||||
|
border none
|
||||||
|
border-radius 0
|
||||||
|
&>span.lineNumber
|
||||||
|
display none
|
||||||
|
font-size 1em
|
||||||
|
padding 0.5em 0
|
||||||
|
margin -0.5em 0.5em -0.5em -0.5em
|
||||||
|
border-right 1px solid
|
||||||
|
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
|
||||||
|
display block
|
||||||
|
padding 0 .5em 0
|
||||||
|
table
|
||||||
|
display block
|
||||||
|
width 100%
|
||||||
|
margin 0 0 1em
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
background-color tableHeadBgColor
|
||||||
|
th
|
||||||
|
border-style solid
|
||||||
|
padding 6px 13px
|
||||||
|
line-height 1.6
|
||||||
|
border-width 1px 0 2px 1px
|
||||||
|
border-color borderColor
|
||||||
|
font-weight bold
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px borderColor
|
||||||
|
tbody
|
||||||
|
tr:nth-child(2n + 1)
|
||||||
|
background-color tableOddBgColor
|
||||||
|
tr:nth-child(2n)
|
||||||
|
background-color tableEvenBgColor
|
||||||
|
td
|
||||||
|
border-style solid
|
||||||
|
padding 6px 13px
|
||||||
|
line-height 1.6
|
||||||
|
border-width 0 0 1px 1px
|
||||||
|
border-color borderColor
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px borderColor
|
||||||
|
kbd
|
||||||
|
background-color #fafbfc
|
||||||
|
border solid 1px borderColor
|
||||||
|
border-bottom-color btnColor
|
||||||
|
border-radius 3px
|
||||||
|
box-shadow inset 0 -1px 0 #959da5
|
||||||
|
display inline-block
|
||||||
|
font-size .8em
|
||||||
|
line-height 1
|
||||||
|
padding 3px 5px
|
||||||
|
|
||||||
|
themeDarkBackground = darken(#21252B, 10%)
|
||||||
|
themeDarkText = #f9f9f9
|
||||||
|
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
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var CodeViewer = require('../../main/Components/CodeViewer')
|
|
||||||
|
|
||||||
var Markdown = require('../../main/Mixins/Markdown')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [Markdown],
|
|
||||||
propTypes: {
|
|
||||||
currentArticle: React.PropTypes.object
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var article = this.props.currentArticle
|
|
||||||
|
|
||||||
if (article != null) {
|
|
||||||
if (article.type === 'code') {
|
|
||||||
return (
|
|
||||||
<div className='FinderDetail'>
|
|
||||||
<div className='header'><i className='fa fa-code fa-fw'/> {article.description}</div>
|
|
||||||
<div className='content'>
|
|
||||||
<CodeViewer code={article.content} mode={article.mode}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
} else if (article.type === 'note') {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='FinderDetail'>
|
|
||||||
<div className='header'><i className='fa fa-file-text-o fa-fw'/> {article.title}</div>
|
|
||||||
<div className='content'>
|
|
||||||
<div className='marked' dangerouslySetInnerHTML={{__html: ' ' + this.markdown(article.content)}}></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className='FinderDetail'>
|
|
||||||
<div className='nothing'>Nothing selected</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
onChange: React.PropTypes.func,
|
|
||||||
search: React.PropTypes.string
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='FinderInput'>
|
|
||||||
<input value={this.props.search} onChange={this.props.onChange} type='text'/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
articles: React.PropTypes.arrayOf,
|
|
||||||
currentArticle: React.PropTypes.shape({
|
|
||||||
id: React.PropTypes.number,
|
|
||||||
type: React.PropTypes.string
|
|
||||||
}),
|
|
||||||
selectArticle: React.PropTypes.func
|
|
||||||
},
|
|
||||||
componentDidUpdate: function () {
|
|
||||||
var index = this.props.articles.indexOf(this.props.currentArticle)
|
|
||||||
var el = React.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: function (article) {
|
|
||||||
return function () {
|
|
||||||
this.props.selectArticle(article)
|
|
||||||
}.bind(this)
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var list = 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.currentArticle != null && (article.type === this.props.currentArticle.type && article.id === this.props.currentArticle.id)
|
|
||||||
if (article.type === 'code') {
|
|
||||||
return (
|
|
||||||
<li onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
|
|
||||||
<div className='articleItem'><i className='fa fa-code fa-fw'/> {article.description}</div>
|
|
||||||
<div className='divider'/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (article.type === 'note') {
|
|
||||||
return (
|
|
||||||
<li onClick={this.handleArticleClick(article)} className={isActive ? 'active' : ''}>
|
|
||||||
<div className='articleItem'><i className='fa fa-file-text-o fa-fw'/> {article.title}</div>
|
|
||||||
<div className='divider'/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<li className={isActive ? 'active' : ''}>
|
|
||||||
<div className='articleItem'>Undefined</div>
|
|
||||||
<div className='divider'/>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}.bind(this))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='FinderList'>
|
|
||||||
<ul>
|
|
||||||
{list}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
118
browser/finder/FinderMain.styl
Normal file
118
browser/finder/FinderMain.styl
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
$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
|
||||||
209
browser/finder/NoteDetail.js
Normal file
209
browser/finder/NoteDetail.js
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
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}
|
||||||
|
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)
|
||||||
97
browser/finder/NoteDetail.styl
Normal file
97
browser/finder/NoteDetail.styl
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
@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
|
||||||
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
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,72 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
|
|
||||||
<title>CodeXen Popup</title>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="../../node_modules/font-awesome/css/font-awesome.min.css" media="screen" title="no title" charset="utf-8">
|
|
||||||
<link rel="shortcut icon" href="favicon.ico">
|
|
||||||
<style>
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Lato';
|
|
||||||
src: url('../../Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
|
||||||
url('../../Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
|
||||||
url('../../Lato-Regular.ttf') format('truetype');
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
document.addEventListener('mousewheel', function(e) {
|
|
||||||
if(e.deltaY % 1 !== 0) {
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!Object.assign) {
|
|
||||||
Object.defineProperty(Object, 'assign', {
|
|
||||||
enumerable: false,
|
|
||||||
configurable: true,
|
|
||||||
writable: true,
|
|
||||||
value: function(target) {
|
|
||||||
'use strict';
|
|
||||||
if (target === undefined || target === null) {
|
|
||||||
throw new TypeError('Cannot convert first argument to object');
|
|
||||||
}
|
|
||||||
|
|
||||||
var to = Object(target);
|
|
||||||
for (var i = 1; i < arguments.length; i++) {
|
|
||||||
var nextSource = arguments[i];
|
|
||||||
if (nextSource === undefined || nextSource === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
nextSource = Object(nextSource);
|
|
||||||
|
|
||||||
var keysArray = Object.keys(Object(nextSource));
|
|
||||||
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
|
|
||||||
var nextKey = keysArray[nextIndex];
|
|
||||||
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
|
|
||||||
if (desc !== undefined && desc.enumerable) {
|
|
||||||
to[nextKey] = nextSource[nextKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return to;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="content"></div>
|
|
||||||
<script src="../ace/src-min/ace.js"></script>
|
|
||||||
<script>
|
|
||||||
require('electron-stylus')(__dirname + '/../styles/finder/index.styl')
|
|
||||||
require('node-jsx').install({ harmony: true, extension: '.jsx' })
|
|
||||||
require('./index.jsx')
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
357
browser/finder/index.js
Normal file
357
browser/finder/index.js
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import { connect, Provider } from 'react-redux'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import store from './store'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './FinderMain.styl'
|
||||||
|
import StorageSection from './StorageSection'
|
||||||
|
import NoteList from './NoteList'
|
||||||
|
import NoteDetail from './NoteDetail'
|
||||||
|
import SideNavFilter from 'browser/components/SideNavFilter'
|
||||||
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
require('!!style!css!stylus?sourceMap!../main/global.styl')
|
||||||
|
require('../lib/customMeta')
|
||||||
|
require('./ipcClient.js')
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const { remote } = electron
|
||||||
|
const { Menu } = remote
|
||||||
|
|
||||||
|
function hideFinder () {
|
||||||
|
const finderWindow = remote.getCurrentWindow()
|
||||||
|
if (global.process.platform === 'win32') {
|
||||||
|
finderWindow.blur()
|
||||||
|
finderWindow.hide()
|
||||||
|
}
|
||||||
|
if (global.process.platform === 'darwin') {
|
||||||
|
Menu.sendActionToFirstResponder('hide:')
|
||||||
|
}
|
||||||
|
remote.getCurrentWindow().hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
require('!!style!css!stylus?sourceMap!../styles/finder/index.styl')
|
||||||
|
|
||||||
|
class FinderMain extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
search: '',
|
||||||
|
index: 0,
|
||||||
|
filter: {
|
||||||
|
includeSnippet: true,
|
||||||
|
includeMarkdown: false,
|
||||||
|
type: 'ALL',
|
||||||
|
storage: null,
|
||||||
|
folder: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.focusHandler = (e) => this.handleWindowFocus(e)
|
||||||
|
this.blurHandler = (e) => this.handleWindowBlur(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.refs.search.focus()
|
||||||
|
window.addEventListener('focus', this.focusHandler)
|
||||||
|
window.addEventListener('blur', this.blurHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
window.removeEventListener('focus', this.focusHandler)
|
||||||
|
window.removeEventListener('blur', this.blurHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWindowFocus (e) {
|
||||||
|
this.refs.search.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWindowBlur (e) {
|
||||||
|
this.setState({
|
||||||
|
search: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown (e) {
|
||||||
|
this.refs.search.focus()
|
||||||
|
if (e.keyCode === 9) {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
this.refs.detail.selectPriorSnippet()
|
||||||
|
} else {
|
||||||
|
this.refs.detail.selectNextSnippet()
|
||||||
|
}
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
if (e.keyCode === 38) {
|
||||||
|
this.selectPrevious()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 40) {
|
||||||
|
this.selectNext()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
this.refs.detail.saveToClipboard()
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('COPY_FINDER')
|
||||||
|
hideFinder()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
hideFinder()
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
if (e.keyCode === 91 || e.metaKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearchChange (e) {
|
||||||
|
this.setState({
|
||||||
|
search: e.target.value,
|
||||||
|
index: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
selectArticle (article) {
|
||||||
|
this.setState({currentArticle: article})
|
||||||
|
}
|
||||||
|
|
||||||
|
selectPrevious () {
|
||||||
|
if (this.state.index > 0) {
|
||||||
|
this.setState({
|
||||||
|
index: this.state.index - 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectNext () {
|
||||||
|
if (this.state.index < this.noteCount - 1) {
|
||||||
|
this.setState({
|
||||||
|
index: this.state.index + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnlySnippetCheckboxChange (e) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.includeSnippet = e.target.checked
|
||||||
|
this.setState({
|
||||||
|
filter: filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnlyMarkdownCheckboxChange (e) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.includeMarkdown = e.target.checked
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter: filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAllNotesButtonClick (e) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.type = 'ALL'
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStarredButtonClick (e) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.type = 'STARRED'
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStorageButtonClick (e, storage) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.type = 'STORAGE'
|
||||||
|
filter.storage = storage
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFolderButtonClick (e, storage, folder) {
|
||||||
|
const { filter } = this.state
|
||||||
|
filter.type = 'FOLDER'
|
||||||
|
filter.storage = storage
|
||||||
|
filter.folder = folder
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNoteClick (e, index) {
|
||||||
|
this.setState({
|
||||||
|
index
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { data, config } = this.props
|
||||||
|
const { filter, search } = this.state
|
||||||
|
const storageList = []
|
||||||
|
for (const key in data.storageMap) {
|
||||||
|
const storage = data.storageMap[key]
|
||||||
|
const item = (
|
||||||
|
<StorageSection
|
||||||
|
filter={filter}
|
||||||
|
storage={storage}
|
||||||
|
key={storage.key}
|
||||||
|
handleStorageButtonClick={(e, storage) => this.handleStorageButtonClick(e, storage)}
|
||||||
|
handleFolderButtonClick={(e, storage, folder) => this.handleFolderButtonClick(e, storage, folder)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
storageList.push(item)
|
||||||
|
}
|
||||||
|
let notes = []
|
||||||
|
let noteIds
|
||||||
|
|
||||||
|
switch (filter.type) {
|
||||||
|
case 'STORAGE':
|
||||||
|
noteIds = data.storageNoteMap[filter.storage]
|
||||||
|
break
|
||||||
|
case 'FOLDER':
|
||||||
|
noteIds = data.folderNoteMap[filter.storage + '-' + filter.folder]
|
||||||
|
break
|
||||||
|
case 'STARRED':
|
||||||
|
noteIds = data.starredSet
|
||||||
|
}
|
||||||
|
if (noteIds != null) {
|
||||||
|
noteIds.forEach((id) => {
|
||||||
|
notes.push(data.noteMap[id])
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
for (const key in data.noteMap) {
|
||||||
|
notes.push(data.noteMap[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filter.includeSnippet && filter.includeMarkdown) {
|
||||||
|
notes = notes.filter((note) => note.type === 'MARKDOWN_NOTE')
|
||||||
|
} else if (filter.includeSnippet && !filter.includeMarkdown) {
|
||||||
|
notes = notes.filter((note) => note.type === 'SNIPPET_NOTE')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search.trim().length > 0) {
|
||||||
|
const needle = new RegExp(_.escapeRegExp(search.trim()), 'i')
|
||||||
|
notes = notes.filter((note) => note.title.match(needle))
|
||||||
|
}
|
||||||
|
notes = notes
|
||||||
|
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
||||||
|
|
||||||
|
const activeNote = notes[this.state.index]
|
||||||
|
this.noteCount = notes.length
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='Finder'
|
||||||
|
styleName='root'
|
||||||
|
ref='-1'
|
||||||
|
onKeyDown={(e) => this.handleKeyDown(e)}
|
||||||
|
>
|
||||||
|
<div styleName='search'>
|
||||||
|
<input
|
||||||
|
styleName='search-input'
|
||||||
|
ref='search'
|
||||||
|
value={search}
|
||||||
|
placeholder='Search...'
|
||||||
|
onChange={(e) => this.handleSearchChange(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div styleName='result'>
|
||||||
|
<div styleName='result-nav'>
|
||||||
|
<div styleName='result-nav-filter'>
|
||||||
|
<div styleName='result-nav-filter-option'>
|
||||||
|
<label>
|
||||||
|
<input type='checkbox'
|
||||||
|
checked={filter.includeSnippet}
|
||||||
|
onChange={(e) => this.handleOnlySnippetCheckboxChange(e)}
|
||||||
|
/> Only Snippets</label>
|
||||||
|
</div>
|
||||||
|
<div styleName='result-nav-filter-option'>
|
||||||
|
<label>
|
||||||
|
<input type='checkbox'
|
||||||
|
checked={filter.includeMarkdown}
|
||||||
|
onChange={(e) => this.handleOnlyMarkdownCheckboxChange(e)}
|
||||||
|
/> Only Markdown</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SideNavFilter
|
||||||
|
isHomeActive={filter.type === 'ALL'}
|
||||||
|
handleAllNotesButtonClick={(e) => this.handleAllNotesButtonClick(e)}
|
||||||
|
isStarredActive={filter.type === 'STARRED'}
|
||||||
|
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
|
||||||
|
/>
|
||||||
|
<div styleName='result-nav-storageList'>
|
||||||
|
{storageList}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NoteList styleName='result-list'
|
||||||
|
storageMap={data.storageMap}
|
||||||
|
notes={notes}
|
||||||
|
ref='list'
|
||||||
|
search={search}
|
||||||
|
index={this.state.index}
|
||||||
|
handleNoteClick={(e, _index) => this.handleNoteClick(e, _index)}
|
||||||
|
/>
|
||||||
|
<div styleName='result-detail'>
|
||||||
|
<NoteDetail
|
||||||
|
note={activeNote}
|
||||||
|
config={config}
|
||||||
|
ref='detail'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FinderMain.propTypes = {
|
||||||
|
dispatch: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
var Finder = connect((x) => x)(CSSModules(FinderMain, styles))
|
||||||
|
|
||||||
|
function refreshData () {
|
||||||
|
// let data = dataStore.getData(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactDOM.render((
|
||||||
|
<Provider store={store}>
|
||||||
|
<Finder />
|
||||||
|
</Provider>
|
||||||
|
), document.getElementById('content'), function () {
|
||||||
|
refreshData()
|
||||||
|
})
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
var remote = require('remote')
|
|
||||||
var hideFinder = remote.getGlobal('hideFinder')
|
|
||||||
var clipboard = require('clipboard')
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var ArticleFilter = require('../main/Mixins/ArticleFilter')
|
|
||||||
|
|
||||||
var FinderInput = require('./Components/FinderInput')
|
|
||||||
var FinderList = require('./Components/FinderList')
|
|
||||||
var FinderDetail = require('./Components/FinderDetail')
|
|
||||||
|
|
||||||
// Filter end
|
|
||||||
|
|
||||||
function fetchArticles () {
|
|
||||||
var user = JSON.parse(localStorage.getItem('currentUser'))
|
|
||||||
if (user == null) {
|
|
||||||
console.log('need to login')
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
var articles = []
|
|
||||||
user.Planets.forEach(function (planet) {
|
|
||||||
var _planet = JSON.parse(localStorage.getItem('planet-' + planet.id))
|
|
||||||
articles = articles.concat(_planet.Codes, _planet.Notes)
|
|
||||||
})
|
|
||||||
user.Teams.forEach(function (team) {
|
|
||||||
team.Planets.forEach(function (planet) {
|
|
||||||
var _planet = JSON.parse(localStorage.getItem('planet-' + planet.id))
|
|
||||||
articles = articles.concat(_planet.Codes, _planet.Notes)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return articles
|
|
||||||
}
|
|
||||||
|
|
||||||
var Finder = React.createClass({
|
|
||||||
mixins: [ArticleFilter],
|
|
||||||
getInitialState: function () {
|
|
||||||
var articles = fetchArticles()
|
|
||||||
return {
|
|
||||||
articles: articles,
|
|
||||||
currentArticle: articles[0],
|
|
||||||
search: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
document.addEventListener('keydown', this.handleKeyDown)
|
|
||||||
document.addEventListener('click', this.handleClick)
|
|
||||||
window.addEventListener('focus', this.handleFinderFocus)
|
|
||||||
this.handleFinderFocus()
|
|
||||||
},
|
|
||||||
componentWillUnmount: function () {
|
|
||||||
document.removeEventListener('keydown', this.handleKeyDown)
|
|
||||||
document.removeEventListener('click', this.handleClick)
|
|
||||||
window.removeEventListener('focus', this.handleFinderFocus)
|
|
||||||
},
|
|
||||||
handleFinderFocus: function () {
|
|
||||||
console.log('focusseeddddd')
|
|
||||||
this.focusInput()
|
|
||||||
var articles = fetchArticles()
|
|
||||||
this.setState({
|
|
||||||
articles: articles,
|
|
||||||
search: ''
|
|
||||||
}, function () {
|
|
||||||
var firstArticle = this.refs.finderList.props.articles[0]
|
|
||||||
if (firstArticle) {
|
|
||||||
this.setState({
|
|
||||||
currentArticle: firstArticle
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleKeyDown: function (e) {
|
|
||||||
if (e.keyCode === 38) {
|
|
||||||
this.selectPrevious()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 40) {
|
|
||||||
this.selectNext()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
var article = this.state.currentArticle
|
|
||||||
clipboard.writeText(article.content)
|
|
||||||
hideFinder()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
if (e.keyCode === 27) {
|
|
||||||
hideFinder()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
focusInput: function () {
|
|
||||||
React.findDOMNode(this.refs.finderInput).querySelector('input').focus()
|
|
||||||
},
|
|
||||||
handleClick: function () {
|
|
||||||
this.focusInput()
|
|
||||||
},
|
|
||||||
selectPrevious: function () {
|
|
||||||
var index = this.refs.finderList.props.articles.indexOf(this.state.currentArticle)
|
|
||||||
if (index > 0) {
|
|
||||||
this.setState({currentArticle: this.refs.finderList.props.articles[index - 1]})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectNext: function () {
|
|
||||||
var index = this.refs.finderList.props.articles.indexOf(this.state.currentArticle)
|
|
||||||
if (index > -1 && index < this.refs.finderList.props.articles.length - 1) {
|
|
||||||
this.setState({currentArticle: this.refs.finderList.props.articles[index + 1]})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectArticle: function (article) {
|
|
||||||
this.setState({currentArticle: article})
|
|
||||||
},
|
|
||||||
handleChange: function (e) {
|
|
||||||
this.setState({search: e.target.value}, function () {
|
|
||||||
this.setState({currentArticle: this.refs.finderList.props.articles[0]})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var articles = this.searchArticle(this.state.search, this.state.articles)
|
|
||||||
return (
|
|
||||||
<div className='Finder'>
|
|
||||||
<FinderInput ref='finderInput' onChange={this.handleChange} search={this.state.search}/>
|
|
||||||
<FinderList ref='finderList' currentArticle={this.state.currentArticle} articles={articles} selectArticle={this.selectArticle}/>
|
|
||||||
<FinderDetail currentArticle={this.state.currentArticle}/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
React.render(<Finder/>, document.getElementById('content'))
|
|
||||||
124
browser/finder/ipcClient.js
Normal file
124
browser/finder/ipcClient.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
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 {
|
||||||
|
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
|
||||||
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
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<a href="/main">Go Main</a>
|
|
||||||
<a href="/main">Go Popup</a>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
5
browser/lib/CSSModules.js
Normal file
5
browser/lib/CSSModules.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import CSSModules from 'react-css-modules'
|
||||||
|
|
||||||
|
export default function (component, styles) {
|
||||||
|
return CSSModules(component, styles, {errorWhenNotFound: false})
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
37
browser/lib/consts.js
Normal file
37
browser/lib/consts.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
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 = {
|
||||||
|
FOLDER_COLORS: [
|
||||||
|
'#E10051',
|
||||||
|
'#FF8E00',
|
||||||
|
'#E8D252',
|
||||||
|
'#3FD941',
|
||||||
|
'#30D5C8',
|
||||||
|
'#2BA5F7',
|
||||||
|
'#B013A4'
|
||||||
|
],
|
||||||
|
FOLDER_COLOR_NAMES: [
|
||||||
|
'Razzmatazz (Red)',
|
||||||
|
'Pizazz (Orange)',
|
||||||
|
'Confetti (Yellow)',
|
||||||
|
'Emerald (Green)',
|
||||||
|
'Turquoise',
|
||||||
|
'Dodger Blue',
|
||||||
|
'Violet Eggplant'
|
||||||
|
],
|
||||||
|
THEMES: ['default'].concat(themes)
|
||||||
|
}
|
||||||
|
|
||||||
|
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']})
|
||||||
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)
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
7
browser/lib/keygen.js
Normal file
7
browser/lib/keygen.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const crypto = require('crypto')
|
||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
|
module.exports = function (length) {
|
||||||
|
if (!_.isFinite(length)) length = 10
|
||||||
|
return crypto.randomBytes(length).toString('hex')
|
||||||
|
}
|
||||||
157
browser/lib/markdown.js
Normal file
157
browser/lib/markdown.js
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import markdownit from 'markdown-it'
|
||||||
|
import emoji from 'markdown-it-emoji'
|
||||||
|
import math from '@rokt33r/markdown-it-math'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
// FIXME We should not depend on global variable.
|
||||||
|
const katex = window.katex
|
||||||
|
|
||||||
|
function createGutter (str) {
|
||||||
|
const lc = (str.match(/\n/g) || []).length
|
||||||
|
const lines = []
|
||||||
|
for (let i = 1; i <= lc; i++) {
|
||||||
|
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
|
||||||
|
}
|
||||||
|
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
||||||
|
}
|
||||||
|
|
||||||
|
var md = markdownit({
|
||||||
|
typographer: true,
|
||||||
|
linkify: true,
|
||||||
|
html: true,
|
||||||
|
xhtmlOut: true,
|
||||||
|
breaks: true,
|
||||||
|
highlight: function (str, lang) {
|
||||||
|
if (lang === 'flowchart') {
|
||||||
|
return `<pre class="flowchart">${str}</pre>`
|
||||||
|
}
|
||||||
|
if (lang === 'sequence') {
|
||||||
|
return `<pre class="sequence">${str}</pre>`
|
||||||
|
}
|
||||||
|
return '<pre class="code">' +
|
||||||
|
createGutter(str) +
|
||||||
|
'<code class="' + lang + '">' +
|
||||||
|
str +
|
||||||
|
'</code></pre>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
md.use(emoji, {
|
||||||
|
shortcuts: {}
|
||||||
|
})
|
||||||
|
md.use(math, {
|
||||||
|
inlineRenderer: function (str) {
|
||||||
|
let output = ''
|
||||||
|
try {
|
||||||
|
output = katex.renderToString(str.trim())
|
||||||
|
} catch (err) {
|
||||||
|
output = `<span class="katex-error">${err.message}</span>`
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
},
|
||||||
|
blockRenderer: function (str) {
|
||||||
|
let output = ''
|
||||||
|
try {
|
||||||
|
output = katex.renderToString(str.trim(), {displayMode: true})
|
||||||
|
} catch (err) {
|
||||||
|
output = `<div class="katex-error">${err.message}</div>`
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
})
|
||||||
|
md.use(require('markdown-it-imsize'))
|
||||||
|
md.use(require('markdown-it-footnote'))
|
||||||
|
md.use(require('markdown-it-multimd-table'))
|
||||||
|
md.use(require('markdown-it-named-headers'), {
|
||||||
|
slugify: (header) => {
|
||||||
|
return encodeURI(header.trim()
|
||||||
|
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
||||||
|
.replace(/\s+/g, '-'))
|
||||||
|
.replace(/\-+$/, '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
md.use(require('markdown-it-kbd'))
|
||||||
|
md.use(require('markdown-it-plantuml'))
|
||||||
|
|
||||||
|
// Override task item
|
||||||
|
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||||
|
let content, terminate, i, l, token
|
||||||
|
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' 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
|
||||||
|
}
|
||||||
|
// FIXME We should not depend on global variable.
|
||||||
|
window.md = md
|
||||||
|
|
||||||
|
function normalizeLinkText (linkText) {
|
||||||
|
return md.normalizeLinkText(linkText)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
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,40 +0,0 @@
|
|||||||
var remote = require('remote')
|
|
||||||
var version = remote.getGlobal('version')
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var ExternalLink = require('../Mixins/ExternalLink')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [ExternalLink, KeyCaster('aboutModal')],
|
|
||||||
propTypes: {
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='AboutModal modal'>
|
|
||||||
<div className='about1'>
|
|
||||||
<img className='logo' src='resources/favicon-230x230.png'/>
|
|
||||||
<div className='appInfo'>Boost {version == null || version.length === 0 ? 'DEV version' : 'v' + version}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='about2'>
|
|
||||||
<div className='externalLabel'>External links</div>
|
|
||||||
<ul className='externalList'>
|
|
||||||
<li><a onClick={this.openExternal} href='http://b00st.io'>Boost Homepage <i className='fa fa-external-link'/></a></li>
|
|
||||||
<li><a>Regulation <i className='fa fa-external-link'/></a></li>
|
|
||||||
<li><a>Private policy <i className='fa fa-external-link'/></a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
var Select = require('react-select')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var UserStore = require('../Stores/UserStore')
|
|
||||||
|
|
||||||
var getOptions = function (input, callback) {
|
|
||||||
Hq.searchUser(input)
|
|
||||||
.then(function (res) {
|
|
||||||
callback(null, {
|
|
||||||
options: res.body.map(function (user) {
|
|
||||||
return {
|
|
||||||
label: user.name,
|
|
||||||
value: user.name
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
complete: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, KeyCaster('addMemberModal')],
|
|
||||||
propTypes: {
|
|
||||||
team: React.PropTypes.object,
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
userName: '',
|
|
||||||
role: 'member'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
case 'submitAddMemberModal':
|
|
||||||
this.handleSubmit()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSubmit: function () {
|
|
||||||
this.setState({errorMessage: null}, function () {
|
|
||||||
Hq
|
|
||||||
.addMember(this.props.team.name, {
|
|
||||||
userName: this.state.userName,
|
|
||||||
role: this.state.role
|
|
||||||
})
|
|
||||||
.then(function (res) {
|
|
||||||
console.log(res.body)
|
|
||||||
UserStore.Actions.addMember(res.body)
|
|
||||||
this.props.close()
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
if (err.status === 403) {
|
|
||||||
this.setState({errorMessage: err.response.body.message})
|
|
||||||
}
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleChange: function (value) {
|
|
||||||
this.setState({userName: value})
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='AddMemberModal modal'>
|
|
||||||
<Select
|
|
||||||
name='userName'
|
|
||||||
value={this.state.userName}
|
|
||||||
placeholder='Username to add'
|
|
||||||
asyncOptions={getOptions}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
className='userNameSelect'
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className='formField'>
|
|
||||||
Add member as
|
|
||||||
<select valueLink={this.linkState('role')}>
|
|
||||||
<option value={'member'}>Member</option>
|
|
||||||
<option value={'owner'}>Owner</option>
|
|
||||||
</select>
|
|
||||||
role
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.state.errorMessage != null ? (<p className='errorAlert'>{this.state.errorMessage}</p>) : null}
|
|
||||||
|
|
||||||
<button onClick={this.handleSubmit} className='submitButton'><i className='fa fa-check'/></button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
var React = require('react')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var PlanetStore = require('../Stores/PlanetStore')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [KeyCaster('codeDeleteModal')],
|
|
||||||
propTypes: {
|
|
||||||
planet: React.PropTypes.object,
|
|
||||||
code: React.PropTypes.object,
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'submitCodeDeleteModal':
|
|
||||||
this.submit()
|
|
||||||
break
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submit: function () {
|
|
||||||
var planet = this.props.planet
|
|
||||||
Hq.destroyCode(planet.userName, planet.name, this.props.code.localId)
|
|
||||||
.then(function (res) {
|
|
||||||
PlanetStore.Actions.destroyCode(res.body)
|
|
||||||
this.props.close()
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='CodeDeleteModal modal'>
|
|
||||||
<div className='modal-header'>
|
|
||||||
<h1>Delete Code</h1>
|
|
||||||
</div>
|
|
||||||
<div className='modal-body'>
|
|
||||||
<p>Are you sure to delete it?</p>
|
|
||||||
</div>
|
|
||||||
<div className='modal-footer'>
|
|
||||||
<div className='modal-control'>
|
|
||||||
<button onClick={this.props.close} className='btn-default'>Cancel</button>
|
|
||||||
<button ref='submit' onClick={this.submit} className='btn-primary'>Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
var React = require('react')
|
|
||||||
var CodeForm = require('./CodeForm')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
close: React.PropTypes.func,
|
|
||||||
code: React.PropTypes.object,
|
|
||||||
planet: React.PropTypes.object
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
// TODO: Hacked!! should fix later
|
|
||||||
setTimeout(function () {
|
|
||||||
React.findDOMNode(this.refs.form.refs.description).focus()
|
|
||||||
}.bind(this), 1)
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='CodeEditModal modal'>
|
|
||||||
<div className='modal-header'>
|
|
||||||
<h1>Edit Code</h1>
|
|
||||||
</div>
|
|
||||||
<CodeForm ref='form' code={this.props.code} planet={this.props.planet} close={this.props.close}/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var ace = window.ace
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
code: React.PropTypes.string,
|
|
||||||
mode: React.PropTypes.string,
|
|
||||||
className: React.PropTypes.string,
|
|
||||||
onChange: React.PropTypes.func
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
var el = React.findDOMNode(this.refs.target)
|
|
||||||
var editor = ace.edit(el)
|
|
||||||
editor.$blockScrolling = Infinity
|
|
||||||
editor.setValue(this.props.code)
|
|
||||||
editor.renderer.setShowGutter(true)
|
|
||||||
editor.setTheme('ace/theme/xcode')
|
|
||||||
editor.clearSelection()
|
|
||||||
|
|
||||||
var session = editor.getSession()
|
|
||||||
if (this.props.mode != null && this.props.mode.length > 0) {
|
|
||||||
session.setMode('ace/mode/' + this.props.mode)
|
|
||||||
} else {
|
|
||||||
session.setMode('ace/mode/text')
|
|
||||||
}
|
|
||||||
session.setUseSoftTabs(true)
|
|
||||||
session.setOption('useWorker', false)
|
|
||||||
session.setUseWrapMode(true)
|
|
||||||
|
|
||||||
session.on('change', function (e) {
|
|
||||||
if (this.props.onChange != null) {
|
|
||||||
var value = editor.getValue()
|
|
||||||
this.props.onChange(e, value)
|
|
||||||
}
|
|
||||||
}.bind(this))
|
|
||||||
|
|
||||||
this.setState({editor: editor})
|
|
||||||
},
|
|
||||||
componentDidUpdate: function (prevProps) {
|
|
||||||
if (this.state.editor.getValue() !== this.props.code) {
|
|
||||||
this.state.editor.setValue(this.props.code)
|
|
||||||
this.state.editor.clearSelection()
|
|
||||||
}
|
|
||||||
if (prevProps.mode !== this.props.mode) {
|
|
||||||
var session = this.state.editor.getSession()
|
|
||||||
if (this.props.mode != null && this.props.mode.length > 0) {
|
|
||||||
session.setMode('ace/mode/' + this.props.mode)
|
|
||||||
} else {
|
|
||||||
session.setMode('ace/mode/text')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div ref='target' className={this.props.className}></div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
var CodeEditor = require('./CodeEditor')
|
|
||||||
var Select = require('react-select')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var PlanetStore = require('../Stores/PlanetStore')
|
|
||||||
|
|
||||||
var aceModes = require('../../../modules/ace-modes')
|
|
||||||
|
|
||||||
var getOptions = function (input, callback) {
|
|
||||||
Hq.searchTag(input)
|
|
||||||
.then(function (res) {
|
|
||||||
callback(null, {
|
|
||||||
options: res.body.map(function (tag) {
|
|
||||||
return {
|
|
||||||
label: tag.name,
|
|
||||||
value: tag.name
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
complete: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, KeyCaster('codeForm')],
|
|
||||||
propTypes: {
|
|
||||||
planet: React.PropTypes.object,
|
|
||||||
close: React.PropTypes.func,
|
|
||||||
transitionTo: React.PropTypes.func,
|
|
||||||
code: React.PropTypes.object
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
var code = Object.assign({
|
|
||||||
description: '',
|
|
||||||
mode: '',
|
|
||||||
content: '',
|
|
||||||
Tags: []
|
|
||||||
}, this.props.code)
|
|
||||||
|
|
||||||
code.Tags = code.Tags.map(function (tag) {
|
|
||||||
return {
|
|
||||||
label: tag.name,
|
|
||||||
value: tag.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
code: code
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'submitCodeForm':
|
|
||||||
this.submit()
|
|
||||||
break
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleModeChange: function (selected) {
|
|
||||||
var code = this.state.code
|
|
||||||
code.mode = selected
|
|
||||||
this.setState({code: code})
|
|
||||||
},
|
|
||||||
handleTagsChange: function (selected, all) {
|
|
||||||
var code = this.state.code
|
|
||||||
code.Tags = all
|
|
||||||
this.setState({code: code})
|
|
||||||
},
|
|
||||||
handleContentChange: function (e, value) {
|
|
||||||
var code = this.state.code
|
|
||||||
code.content = value
|
|
||||||
this.setState({code: code})
|
|
||||||
},
|
|
||||||
submit: function () {
|
|
||||||
var planet = this.props.planet
|
|
||||||
var code = this.state.code
|
|
||||||
code.Tags = code.Tags.map(function (tag) {
|
|
||||||
return tag.value
|
|
||||||
})
|
|
||||||
if (this.props.code == null) {
|
|
||||||
Hq.createCode(planet.userName, planet.name, this.state.code)
|
|
||||||
.then(function (res) {
|
|
||||||
var code = res.body
|
|
||||||
PlanetStore.Actions.updateCode(code)
|
|
||||||
this.props.close()
|
|
||||||
this.props.transitionTo('codes', {userName: planet.userName, planetName: planet.name, localId: code.localId})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Hq.updateCode(planet.userName, planet.name, this.props.code.localId, this.state.code)
|
|
||||||
.then(function (res) {
|
|
||||||
var code = res.body
|
|
||||||
PlanetStore.Actions.updateCode(code)
|
|
||||||
this.props.close()
|
|
||||||
}.bind(this))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleKeyDown: function (e) {
|
|
||||||
if (e.keyCode === 13 && e.metaKey) {
|
|
||||||
this.submit()
|
|
||||||
e.stopPropagation()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var modeOptions = aceModes.map(function (mode) {
|
|
||||||
return {
|
|
||||||
label: mode,
|
|
||||||
value: mode
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<div className='CodeForm'>
|
|
||||||
<div className='modal-body'>
|
|
||||||
<div className='form-group'>
|
|
||||||
<textarea ref='description' className='codeDescription block-input' valueLink={this.linkState('code.description')} placeholder='Description'/>
|
|
||||||
</div>
|
|
||||||
<div className='form-group'>
|
|
||||||
<Select
|
|
||||||
name='mode'
|
|
||||||
className='modeSelect'
|
|
||||||
value={this.state.code.mode}
|
|
||||||
placeholder='Select Language'
|
|
||||||
options={modeOptions}
|
|
||||||
onChange={this.handleModeChange}/>
|
|
||||||
</div>
|
|
||||||
<div className='form-group'>
|
|
||||||
<CodeEditor onChange={this.handleContentChange} code={this.state.code.content} mode={this.state.code.mode}/>
|
|
||||||
</div>
|
|
||||||
<div className='form-group'>
|
|
||||||
<Select
|
|
||||||
name='Tags'
|
|
||||||
multi={true}
|
|
||||||
allowCreate={true}
|
|
||||||
value={this.state.code.Tags}
|
|
||||||
placeholder='Tags...'
|
|
||||||
asyncOptions={getOptions}
|
|
||||||
onChange={this.handleTagsChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='modal-footer'>
|
|
||||||
<div className='modal-control'>
|
|
||||||
<button onClick={this.props.close} className='btn-default'>Cancel</button>
|
|
||||||
<button onClick={this.submit} className='btn-primary'>{this.props.code == null ? 'Launch' : 'Relaunch'}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var ace = window.ace
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
code: React.PropTypes.string,
|
|
||||||
mode: React.PropTypes.string,
|
|
||||||
className: React.PropTypes.string
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
var el = React.findDOMNode(this.refs.target)
|
|
||||||
var editor = ace.edit(el)
|
|
||||||
editor.$blockScrolling = Infinity
|
|
||||||
editor.setValue(this.props.code)
|
|
||||||
editor.renderer.setShowGutter(false)
|
|
||||||
editor.setReadOnly(true)
|
|
||||||
editor.setTheme('ace/theme/xcode')
|
|
||||||
editor.setHighlightActiveLine(false)
|
|
||||||
editor.clearSelection()
|
|
||||||
|
|
||||||
var session = editor.getSession()
|
|
||||||
if (this.props.mode != null && this.props.mode.length > 0) {
|
|
||||||
session.setMode('ace/mode/' + this.props.mode)
|
|
||||||
} else {
|
|
||||||
session.setMode('ace/mode/text')
|
|
||||||
}
|
|
||||||
session.setUseSoftTabs(true)
|
|
||||||
session.setOption('useWorker', false)
|
|
||||||
session.setUseWrapMode(true)
|
|
||||||
|
|
||||||
this.setState({editor: editor})
|
|
||||||
},
|
|
||||||
componentDidUpdate: function (prevProps) {
|
|
||||||
if (this.state.editor.getValue() !== this.props.code) {
|
|
||||||
this.state.editor.setValue(this.props.code)
|
|
||||||
this.state.editor.clearSelection()
|
|
||||||
}
|
|
||||||
if (prevProps.mode !== this.props.mode) {
|
|
||||||
var session = this.state.editor.getSession()
|
|
||||||
if (this.props.mode != null && this.props.mode.length > 0) {
|
|
||||||
session.setMode('ace/mode/' + this.props.mode)
|
|
||||||
} else {
|
|
||||||
session.setMode('ace/mode/text')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div ref='target' className={this.props.className}></div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
var React = require('react')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, KeyCaster('contactModal')],
|
|
||||||
propTypes: {
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
isSent: false,
|
|
||||||
mail: {
|
|
||||||
title: '',
|
|
||||||
content: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
case 'submitContactModal':
|
|
||||||
if (this.state.isSent) {
|
|
||||||
this.props.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.sendEmail()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
React.findDOMNode(this.refs.title).focus()
|
|
||||||
},
|
|
||||||
sendEmail: function () {
|
|
||||||
Hq.sendEmail(this.state.mail)
|
|
||||||
.then(function (res) {
|
|
||||||
this.setState({isSent: !this.state.isSent})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='ContactModal modal'>
|
|
||||||
<div className='modal-header'><h1>Contact form</h1></div>
|
|
||||||
|
|
||||||
{!this.state.isSent ? (
|
|
||||||
<div className='contactForm'>
|
|
||||||
<div className='modal-body'>
|
|
||||||
<div className='formField'>
|
|
||||||
<input ref='title' valueLink={this.linkState('mail.title')} placeholder='Title'/>
|
|
||||||
</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<textarea valueLink={this.linkState('mail.content')} placeholder='Content'/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='modal-footer'>
|
|
||||||
<div className='formControl'>
|
|
||||||
<button onClick={this.sendEmail} className='sendButton'>Send</button>
|
|
||||||
<button onClick={this.props.close}>Cancel</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className='confirmation'>
|
|
||||||
<div className='confirmationMessage'>Thanks for sharing your opinion!</div>
|
|
||||||
<button className='doneButton' onClick={this.props.close}>Done</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var UserStore = require('../Stores/UserStore')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, KeyCaster('editProfileModal')],
|
|
||||||
propTypes: {
|
|
||||||
user: React.PropTypes.shape({
|
|
||||||
name: React.PropTypes.string,
|
|
||||||
profileName: React.PropTypes.string,
|
|
||||||
email: React.PropTypes.string
|
|
||||||
}),
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
var user = this.props.user
|
|
||||||
return {
|
|
||||||
currentTab: 'userInfo',
|
|
||||||
user: {
|
|
||||||
profileName: user.profileName,
|
|
||||||
email: user.email
|
|
||||||
},
|
|
||||||
userSubmitStatus: null,
|
|
||||||
password: {
|
|
||||||
currentPassword: '',
|
|
||||||
newPassword: '',
|
|
||||||
passwordConfirmation: ''
|
|
||||||
},
|
|
||||||
passwordSubmitStatus: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectTab: function (tabName) {
|
|
||||||
return function () {
|
|
||||||
this.setState({currentTab: tabName})
|
|
||||||
}.bind(this)
|
|
||||||
},
|
|
||||||
saveUserInfo: function () {
|
|
||||||
this.setState({
|
|
||||||
userSubmitStatus: 'sending'
|
|
||||||
}, function () {
|
|
||||||
Hq.updateUser(this.props.user.name, this.state.user)
|
|
||||||
.then(function (res) {
|
|
||||||
this.setState({userSubmitStatus: 'done'}, function () {
|
|
||||||
localStorage.setItem('currentUser', JSON.stringify(res.body))
|
|
||||||
UserStore.Actions.update(res.body)
|
|
||||||
})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
this.setState({userSubmitStatus: 'error'})
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
savePassword: function () {
|
|
||||||
this.setState({
|
|
||||||
passwordSubmitStatus: 'sending'
|
|
||||||
}, function () {
|
|
||||||
console.log(this.state.password)
|
|
||||||
Hq.changePassword(this.state.password)
|
|
||||||
.then(function (res) {
|
|
||||||
this.setState({
|
|
||||||
passwordSubmitStatus: 'done',
|
|
||||||
currentPassword: '',
|
|
||||||
newPassword: '',
|
|
||||||
passwordConfirmation: ''
|
|
||||||
})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
this.setState({
|
|
||||||
passwordSubmitStatus: 'error',
|
|
||||||
currentPassword: '',
|
|
||||||
newPassword: '',
|
|
||||||
passwordConfirmation: ''
|
|
||||||
})
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var content
|
|
||||||
|
|
||||||
switch (this.state.currentTab) {
|
|
||||||
case 'userInfo':
|
|
||||||
content = this.renderUserInfoTab()
|
|
||||||
break
|
|
||||||
case 'password':
|
|
||||||
content = this.renderPasswordTab()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='EditProfileModal modal tabModal'>
|
|
||||||
<div className='leftPane'>
|
|
||||||
<div className='tabLabel'>Edit profile</div>
|
|
||||||
<div className='tabList'>
|
|
||||||
<button className={this.state.currentTab === 'userInfo' ? 'active' : ''} onClick={this.selectTab('userInfo')}><i className='fa fa-user fa-fw'/> User Info</button>
|
|
||||||
<button className={this.state.currentTab === 'password' ? 'active' : ''} onClick={this.selectTab('password')}><i className='fa fa-lock fa-fw'/> Password</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='rightPane'>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
renderUserInfoTab: function () {
|
|
||||||
return (
|
|
||||||
<div className='userInfoTab'>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>Profile Name</label>
|
|
||||||
<input valueLink={this.linkState('user.profileName')}/>
|
|
||||||
</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>E-mail</label>
|
|
||||||
<input valueLink={this.linkState('user.email')}/>
|
|
||||||
</div>
|
|
||||||
<div className='formConfirm'>
|
|
||||||
<button disabled={this.state.userSubmitStatus === 'sending'} onClick={this.saveUserInfo}>Save</button>
|
|
||||||
|
|
||||||
<div className={'alertInfo' + (this.state.userSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
|
||||||
|
|
||||||
<div className={'alertError' + (this.state.userSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
|
||||||
|
|
||||||
<div className={'alertSuccess' + (this.state.userSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
renderPasswordTab: function () {
|
|
||||||
return (
|
|
||||||
<div className='passwordTab'>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>Current password</label>
|
|
||||||
<input valueLink={this.linkState('password.currentPassword')}/>
|
|
||||||
</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>New password</label>
|
|
||||||
<input valueLink={this.linkState('password.newPassword')}/>
|
|
||||||
</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>Confirmation</label>
|
|
||||||
<input valueLink={this.linkState('password.passwordConfirmation')}/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='formConfirm'>
|
|
||||||
<button disabled={this.state.password.newPassword.length === 0 || this.state.password.newPassword !== this.state.password.passwordConfirmation || this.state.passwordSubmitStatus === 'sending'} onClick={this.savePassword}>Save</button>
|
|
||||||
|
|
||||||
<div className={'alertInfo' + (this.state.passwordSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
|
||||||
|
|
||||||
<div className={'alertError' + (this.state.passwordSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
|
||||||
|
|
||||||
<div className={'alertSuccess' + (this.state.passwordSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
var ReactRouter = require('react-router')
|
|
||||||
var Navigation = ReactRouter.Navigation
|
|
||||||
var State = ReactRouter.State
|
|
||||||
var Link = ReactRouter.Link
|
|
||||||
var Reflux = require('reflux')
|
|
||||||
|
|
||||||
var Modal = require('../Mixins/Modal')
|
|
||||||
|
|
||||||
var UserStore = require('../Stores/UserStore')
|
|
||||||
|
|
||||||
var AboutModal = require('./AboutModal')
|
|
||||||
var PlanetCreateModal = require('./PlanetCreateModal')
|
|
||||||
var TeamCreateModal = require('./TeamCreateModal')
|
|
||||||
var LogoutModal = require('./LogoutModal')
|
|
||||||
var ProfileImage = require('./ProfileImage')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [Navigation, State, Reflux.listenTo(UserStore, 'onUserChange'), Modal],
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
isPlanetCreateModalOpen: false,
|
|
||||||
currentUser: JSON.parse(localStorage.getItem('currentUser'))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onUserChange: function (res) {
|
|
||||||
switch (res.status) {
|
|
||||||
case 'userUpdated':
|
|
||||||
var user = res.data
|
|
||||||
var currentUser = this.state.currentUser
|
|
||||||
if (currentUser.id === user.id) {
|
|
||||||
this.setState({currentUser: user})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.userType === 'team') {
|
|
||||||
var isMyTeam = user.Members.some(function (member) {
|
|
||||||
if (currentUser.id === member.id) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isMyTeam) {
|
|
||||||
var isNew = !currentUser.Teams.some(function (team, index) {
|
|
||||||
if (user.id === team.id) {
|
|
||||||
currentUser.Teams.splice(index, 1, user)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isNew) {
|
|
||||||
currentUser.Teams.push(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({currentUser: currentUser})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openTeamCreateModal: function () {
|
|
||||||
this.openModal(TeamCreateModal, {user: this.state.currentUser, transitionTo: this.transitionTo})
|
|
||||||
},
|
|
||||||
openAboutModal: function () {
|
|
||||||
this.openModal(AboutModal)
|
|
||||||
},
|
|
||||||
openPlanetCreateModal: function () {
|
|
||||||
this.openModal(PlanetCreateModal, {transitionTo: this.transitionTo})
|
|
||||||
},
|
|
||||||
toggleProfilePopup: function () {
|
|
||||||
this.openProfilePopup()
|
|
||||||
},
|
|
||||||
openProfilePopup: function () {
|
|
||||||
this.setState({isProfilePopupOpen: true}, function () {
|
|
||||||
document.addEventListener('click', this.closeProfilePopup)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
closeProfilePopup: function () {
|
|
||||||
document.removeEventListener('click', this.closeProfilePopup)
|
|
||||||
this.setState({isProfilePopupOpen: false})
|
|
||||||
},
|
|
||||||
handleLogoutClick: function () {
|
|
||||||
this.openModal(LogoutModal, {transitionTo: this.transitionTo})
|
|
||||||
},
|
|
||||||
switchPlanetByIndex: function (index) {
|
|
||||||
var planetProps = this.refs.planets.props.children[index - 1].props
|
|
||||||
this.transitionTo('planet', {userName: planetProps.userName, planetName: planetProps.planetName})
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var params = this.getParams()
|
|
||||||
|
|
||||||
if (this.state.currentUser == null) {
|
|
||||||
return (
|
|
||||||
<div className='HomeNavigator'>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var planets = (this.state.currentUser.Planets.concat(this.state.currentUser.Teams.reduce(function (planets, team) {
|
|
||||||
return team.Planets == null ? planets : planets.concat(team.Planets)
|
|
||||||
}, []))).map(function (planet, index) {
|
|
||||||
return (
|
|
||||||
<li userName={planet.userName} planetName={planet.name} key={planet.id} className={params.userName === planet.userName && params.planetName === planet.name ? 'active' : ''}>
|
|
||||||
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>
|
|
||||||
{planet.name[0]}
|
|
||||||
<div className='planetTooltip'>{planet.userName}/{planet.name}</div>
|
|
||||||
</Link>
|
|
||||||
{index < 9 ? (<div className='shortCut'>⌘{index + 1}</div>) : null}
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
var popup = this.renderPopup()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='HomeNavigator'>
|
|
||||||
<button onClick={this.toggleProfilePopup} className='profileButton'>
|
|
||||||
<ProfileImage size='55' email={this.state.currentUser.email}/>
|
|
||||||
</button>
|
|
||||||
{popup}
|
|
||||||
<ul ref='planets' className='planetList'>
|
|
||||||
{planets}
|
|
||||||
</ul>
|
|
||||||
<button onClick={this.openPlanetCreateModal} className='newPlanet'>
|
|
||||||
<i className='fa fa-plus'/>
|
|
||||||
<div className='tooltip'>Create new planet</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
renderPopup: function () {
|
|
||||||
var teams = this.state.currentUser.Teams == null ? [] : this.state.currentUser.Teams.map(function (team) {
|
|
||||||
return (
|
|
||||||
<li key={'user-' + team.id}>
|
|
||||||
<Link to='userHome' params={{userName: team.name}} className='userName'>{team.profileName} ({team.name})</Link>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref='profilePopup' className={'profilePopup' + (this.state.isProfilePopupOpen ? '' : ' close')}>
|
|
||||||
<div className='profileGroup'>
|
|
||||||
<div className='profileGroupLabel'>
|
|
||||||
<span>You</span>
|
|
||||||
</div>
|
|
||||||
<ul className='profileGroupList'>
|
|
||||||
<li>
|
|
||||||
<Link to='userHome' params={{userName: this.state.currentUser.name}} className='userName'>Profile ({this.state.currentUser.name})</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='profileGroup'>
|
|
||||||
<div className='profileGroupLabel'>
|
|
||||||
<span>Team</span>
|
|
||||||
</div>
|
|
||||||
<ul className='profileGroupList'>
|
|
||||||
{teams}
|
|
||||||
<li>
|
|
||||||
<button onClick={this.openTeamCreateModal} className='createNewTeam'><i className='fa fa-plus-square-o'/> create new team</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul className='controlGroup'>
|
|
||||||
<li>
|
|
||||||
<button onClick={this.openAboutModal}><i className='fa fa-info-circle fa-fw'/> About this app</button>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<button onClick={this.handleLogoutClick}><i className='fa fa-sign-out fa-fw'/> Log out</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var CodeForm = require('./CodeForm')
|
|
||||||
var NoteForm = require('./NoteForm')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
planet: React.PropTypes.object,
|
|
||||||
transitionTo: React.PropTypes.func,
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
currentTab: 'code'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
var codeButton = React.findDOMNode(this.refs.codeButton)
|
|
||||||
codeButton.addEventListener('keydown', this.handleKeyDown)
|
|
||||||
React.findDOMNode(this.refs.noteButton).addEventListener('keydown', this.handleKeyDown)
|
|
||||||
codeButton.focus()
|
|
||||||
},
|
|
||||||
componentWillUnmount: function () {
|
|
||||||
React.findDOMNode(this.refs.codeButton).removeEventListener('keydown', this.handleKeyDown)
|
|
||||||
React.findDOMNode(this.refs.noteButton).removeEventListener('keydown', this.handleKeyDown)
|
|
||||||
},
|
|
||||||
handleKeyDown: function (e) {
|
|
||||||
if (e.keyCode === 37 && e.metaKey) {
|
|
||||||
this.selectCodeTab()
|
|
||||||
e.stopPropagation()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (e.keyCode === 39 && e.metaKey) {
|
|
||||||
this.selectNoteTab()
|
|
||||||
e.stopPropagation()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (e.keyCode === 9) {
|
|
||||||
if (this.state.currentTab === 'code') React.findDOMNode(this.refs.form.refs.description).focus()
|
|
||||||
else React.findDOMNode(this.refs.form.refs.title).focus()
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectCodeTab: function () {
|
|
||||||
this.setState({currentTab: 'code'}, function () {
|
|
||||||
React.findDOMNode(this.refs.codeButton).focus()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
selectNoteTab: function () {
|
|
||||||
this.setState({currentTab: 'note'}, function () {
|
|
||||||
React.findDOMNode(this.refs.noteButton).focus()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var modalBody
|
|
||||||
if (this.state.currentTab === 'code') {
|
|
||||||
modalBody = (
|
|
||||||
<CodeForm ref='form' planet={this.props.planet} transitionTo={this.props.transitionTo} close={this.props.close}/>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
modalBody = (
|
|
||||||
<NoteForm ref='form' planet={this.props.planet} transitionTo={this.props.transitionTo} close={this.props.close}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='LaunchModal modal'>
|
|
||||||
<div className='modal-header'>
|
|
||||||
<div className='modal-tab'>
|
|
||||||
<button ref='codeButton' className={this.state.currentTab === 'code' ? 'btn-primary active' : 'btn-default'} onClick={this.selectCodeTab}>Code</button>
|
|
||||||
<button ref='noteButton' className={this.state.currentTab === 'note' ? 'btn-primary active' : 'btn-default'} onClick={this.selectNoteTab}>Note</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{modalBody}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
|
|
||||||
var React = require('react')
|
|
||||||
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [KeyCaster('logoutModal')],
|
|
||||||
propTypes: {
|
|
||||||
transitionTo: React.PropTypes.func,
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
case 'submitLogoutModal':
|
|
||||||
this.logout()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
logout: function () {
|
|
||||||
localStorage.removeItem('currentUser')
|
|
||||||
localStorage.removeItem('token')
|
|
||||||
this.props.transitionTo('login')
|
|
||||||
this.props.close()
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='LogoutModal modal'>
|
|
||||||
<div className='messageLabel'>Are you sure to log out?</div>
|
|
||||||
<div className='formControl'>
|
|
||||||
<button onClick={this.props.close}>Cancel</button>
|
|
||||||
<button className='logoutButton' onClick={this.logout}>Log out</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
var React = require('react')
|
|
||||||
|
|
||||||
var Markdown = require('../Mixins/Markdown')
|
|
||||||
var ExternalLink = require('../Mixins/ExternalLink')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [Markdown, ExternalLink],
|
|
||||||
propTypes: {
|
|
||||||
className: React.PropTypes.string,
|
|
||||||
content: React.PropTypes.string
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
this.addListener()
|
|
||||||
},
|
|
||||||
componentDidUpdate: function () {
|
|
||||||
this.addListener()
|
|
||||||
},
|
|
||||||
componentWillUnmount: function () {
|
|
||||||
this.removeListener()
|
|
||||||
},
|
|
||||||
componentWillUpdate: function () {
|
|
||||||
this.removeListener()
|
|
||||||
},
|
|
||||||
addListener: function () {
|
|
||||||
var anchors = React.findDOMNode(this).querySelectorAll('a')
|
|
||||||
|
|
||||||
for (var i = 0; i < anchors.length; i++) {
|
|
||||||
anchors[i].addEventListener('click', this.openExternal)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
removeListener: function () {
|
|
||||||
var anchors = React.findDOMNode(this).querySelectorAll('a')
|
|
||||||
|
|
||||||
for (var i = 0; i < anchors.length; i++) {
|
|
||||||
anchors[i].removeEventListener('click', this.openExternal)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className={'MarkdownPreview' + (this.props.className != null ? ' ' + this.props.className : '')} dangerouslySetInnerHTML={{__html: ' ' + this.markdown(this.props.content)}}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
var React = require('react')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var PlanetStore = require('../Stores/PlanetStore')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [KeyCaster('noteDeleteModal')],
|
|
||||||
propTypes: {
|
|
||||||
planet: React.PropTypes.object,
|
|
||||||
note: React.PropTypes.object,
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'submitNoteDeleteModal':
|
|
||||||
this.submit()
|
|
||||||
break
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submit: function () {
|
|
||||||
var planet = this.props.planet
|
|
||||||
Hq.destroyNote(planet.userName, planet.name, this.props.note.localId)
|
|
||||||
.then(function (res) {
|
|
||||||
PlanetStore.Actions.destroyNote(res.body)
|
|
||||||
this.props.close()
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='NoteDeleteModal modal'>
|
|
||||||
<div className='modal-header'>
|
|
||||||
<h1>Delete Note</h1>
|
|
||||||
</div>
|
|
||||||
<div className='modal-body'>
|
|
||||||
<p>Are you sure to delete it?</p>
|
|
||||||
</div>
|
|
||||||
<div className='modal-footer'>
|
|
||||||
<div className='modal-control'>
|
|
||||||
<button onClick={this.props.close} className='btn-default'>Cancel</button>
|
|
||||||
<button ref='submit' onClick={this.submit} className='btn-primary'>Delete</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
var React = require('react')
|
|
||||||
|
|
||||||
var NoteForm = require('./NoteForm')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
close: React.PropTypes.func,
|
|
||||||
note: React.PropTypes.object,
|
|
||||||
planet: React.PropTypes.object
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
// TODO: Hacked!! should fix later
|
|
||||||
setTimeout(function () {
|
|
||||||
React.findDOMNode(this.refs.form.refs.title).focus()
|
|
||||||
}.bind(this), 1)
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='NoteEditModal modal'>
|
|
||||||
<div className='modal-header'>
|
|
||||||
<h1>Edit Note</h1>
|
|
||||||
</div>
|
|
||||||
<NoteForm ref='form' note={this.props.note} planet={this.props.planet} close={this.props.close}/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
var Select = require('react-select')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var Markdown = require('../Mixins/Markdown')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var PlanetStore = require('../Stores/PlanetStore')
|
|
||||||
|
|
||||||
var CodeEditor = require('./CodeEditor')
|
|
||||||
var MarkdownPreview = require('./MarkdownPreview')
|
|
||||||
|
|
||||||
var getOptions = function (input, callback) {
|
|
||||||
Hq.searchTag(input)
|
|
||||||
.then(function (res) {
|
|
||||||
callback(null, {
|
|
||||||
options: res.body.map(function (tag) {
|
|
||||||
return {
|
|
||||||
label: tag.name,
|
|
||||||
value: tag.name
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
complete: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var EDIT_MODE = 0
|
|
||||||
var PREVIEW_MODE = 1
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, Markdown, KeyCaster('noteForm')],
|
|
||||||
propTypes: {
|
|
||||||
planet: React.PropTypes.object,
|
|
||||||
close: React.PropTypes.func,
|
|
||||||
transitionTo: React.PropTypes.func,
|
|
||||||
note: React.PropTypes.object
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
var note = Object.assign({
|
|
||||||
title: '',
|
|
||||||
content: '',
|
|
||||||
Tags: []
|
|
||||||
}, this.props.note)
|
|
||||||
note.Tags = note.Tags.map(function (tag) {
|
|
||||||
return {
|
|
||||||
label: tag.name,
|
|
||||||
value: tag.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
note: note,
|
|
||||||
mode: EDIT_MODE
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'submitNoteForm':
|
|
||||||
this.submit()
|
|
||||||
break
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleTagsChange: function (selected, all) {
|
|
||||||
var note = this.state.note
|
|
||||||
note.Tags = all
|
|
||||||
this.setState({note: note})
|
|
||||||
},
|
|
||||||
handleContentChange: function (e, value) {
|
|
||||||
var note = this.state.note
|
|
||||||
note.content = value
|
|
||||||
this.setState({note: note})
|
|
||||||
},
|
|
||||||
togglePreview: function () {
|
|
||||||
this.setState({mode: this.state.mode === EDIT_MODE ? PREVIEW_MODE : EDIT_MODE})
|
|
||||||
},
|
|
||||||
submit: function () {
|
|
||||||
var planet = this.props.planet
|
|
||||||
var note = this.state.note
|
|
||||||
note.Tags = note.Tags.map(function (tag) {
|
|
||||||
return tag.value
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.props.note == null) {
|
|
||||||
Hq.createNote(planet.userName, planet.name, this.state.note)
|
|
||||||
.then(function (res) {
|
|
||||||
var note = res.body
|
|
||||||
PlanetStore.Actions.updateNote(note)
|
|
||||||
this.props.close()
|
|
||||||
this.props.transitionTo('notes', {userName: planet.userName, planetName: planet.name, localId: note.localId})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Hq.updateNote(planet.userName, planet.name, this.props.note.localId, this.state.note)
|
|
||||||
.then(function (res) {
|
|
||||||
var note = res.body
|
|
||||||
PlanetStore.Actions.updateNote(note)
|
|
||||||
this.props.close()
|
|
||||||
}.bind(this))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var content = this.state.mode === EDIT_MODE ? (
|
|
||||||
<div className='form-group'>
|
|
||||||
<CodeEditor onChange={this.handleContentChange} code={this.state.note.content} mode={'markdown'}/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className='form-group relative'>
|
|
||||||
<div className='previewMode'>Preview mode</div>
|
|
||||||
<MarkdownPreview className='marked' content={this.state.note.content}/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='NoteForm'>
|
|
||||||
<div className='modal-body'>
|
|
||||||
<div className='form-group'>
|
|
||||||
<input ref='title' className='block-input' valueLink={this.linkState('note.title')} placeholder='Title'/>
|
|
||||||
</div>
|
|
||||||
{content}
|
|
||||||
<div className='form-group'>
|
|
||||||
<Select
|
|
||||||
name='Tags'
|
|
||||||
multi={true}
|
|
||||||
allowCreate={true}
|
|
||||||
value={this.state.note.Tags}
|
|
||||||
placeholder='Tags...'
|
|
||||||
asyncOptions={getOptions}
|
|
||||||
onChange={this.handleTagsChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='modal-footer'>
|
|
||||||
<button onClick={this.togglePreview} className={'btn-default' + (this.state.mode === PREVIEW_MODE ? ' active' : '')}>Preview mode</button>
|
|
||||||
<div className='modal-control'>
|
|
||||||
<button onClick={this.props.close} className='btn-default'>Cancel</button>
|
|
||||||
<button onClick={this.submit} className='btn-primary'>Launch</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
var moment = require('moment')
|
|
||||||
|
|
||||||
var CodeViewer = require('./CodeViewer')
|
|
||||||
var CodeEditModal = require('./CodeEditModal')
|
|
||||||
var CodeDeleteModal = require('./CodeDeleteModal')
|
|
||||||
var NoteEditModal = require('./NoteEditModal')
|
|
||||||
var NoteDeleteModal = require('./NoteDeleteModal')
|
|
||||||
var MarkdownPreview = require('./MarkdownPreview')
|
|
||||||
var ProfileImage = require('./ProfileImage')
|
|
||||||
|
|
||||||
var Modal = require('../Mixins/Modal')
|
|
||||||
var ForceUpdate = require('../Mixins/ForceUpdate')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [ForceUpdate(60000), Modal],
|
|
||||||
propTypes: {
|
|
||||||
article: React.PropTypes.object,
|
|
||||||
showOnlyWithTag: React.PropTypes.func,
|
|
||||||
planet: React.PropTypes.object
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
isEditModalOpen: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openEditModal: function () {
|
|
||||||
if (this.props.article == null) return
|
|
||||||
switch (this.props.article.type) {
|
|
||||||
case 'code' :
|
|
||||||
this.openModal(CodeEditModal, {code: this.props.article, planet: this.props.planet})
|
|
||||||
break
|
|
||||||
case 'note' :
|
|
||||||
this.openModal(NoteEditModal, {note: this.props.article, planet: this.props.planet})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openDeleteModal: function () {
|
|
||||||
if (this.props.article == null) return
|
|
||||||
switch (this.props.article.type) {
|
|
||||||
case 'code' :
|
|
||||||
this.openModal(CodeDeleteModal, {code: this.props.article, planet: this.props.planet})
|
|
||||||
break
|
|
||||||
case 'note' :
|
|
||||||
this.openModal(NoteDeleteModal, {note: this.props.article, planet: this.props.planet})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var article = this.props.article
|
|
||||||
if (article == null) {
|
|
||||||
return (
|
|
||||||
<div className='PlanetArticleDetail'>
|
|
||||||
Nothing selected
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
var tags = article.Tags.length > 0 ? article.Tags.map(function (tag) {
|
|
||||||
return (
|
|
||||||
<a onClick={this.props.showOnlyWithTag(tag.name)} key={tag.id}>#{tag.name}</a>
|
|
||||||
)
|
|
||||||
}.bind(this)) : (
|
|
||||||
<a className='noTag'>Not tagged yet</a>
|
|
||||||
)
|
|
||||||
if (article.type === 'code') {
|
|
||||||
return (
|
|
||||||
<div className='PlanetArticleDetail codeDetail'>
|
|
||||||
<div className='detailHeader'>
|
|
||||||
<div className='itemLeft'>
|
|
||||||
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
|
|
||||||
<i className='fa fa-file-text-o fa-fw'></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='itemRight'>
|
|
||||||
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
|
|
||||||
<div className='description'>{article.description}</div>
|
|
||||||
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span className='itemControl'>
|
|
||||||
<button id='articleEditButton' onClick={this.openEditModal} className='editButton'>
|
|
||||||
<i className='fa fa-edit fa-fw'></i>
|
|
||||||
<div className='tooltip'>Edit</div>
|
|
||||||
</button>
|
|
||||||
<button onClick={this.openDeleteModal} className='deleteButton'>
|
|
||||||
<i className='fa fa-trash fa-fw'></i>
|
|
||||||
<div className='tooltip'>Delete</div>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className='detailBody'>
|
|
||||||
<CodeViewer className='content' code={article.content} mode={article.mode}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className='PlanetArticleDetail noteDetail'>
|
|
||||||
<div className='detailHeader'>
|
|
||||||
<div className='itemLeft'>
|
|
||||||
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
|
|
||||||
<i className='fa fa-file-text-o fa-fw'></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='itemRight'>
|
|
||||||
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
|
|
||||||
<div className='description'>{article.title}</div>
|
|
||||||
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span className='itemControl'>
|
|
||||||
<button id='articleEditButton' onClick={this.openEditModal} className='editButton'>
|
|
||||||
<i className='fa fa-edit fa-fw'></i>
|
|
||||||
<div className='tooltip'>Edit</div>
|
|
||||||
</button>
|
|
||||||
<button onClick={this.openDeleteModal} className='deleteButton'>
|
|
||||||
<i className='fa fa-trash fa-fw'></i>
|
|
||||||
<div className='tooltip'>Delete</div>
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className='detailBody'>
|
|
||||||
<MarkdownPreview className='content' content={article.content}/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
var ReactRouter = require('react-router')
|
|
||||||
var moment = require('moment')
|
|
||||||
|
|
||||||
var ForceUpdate = require('../Mixins/ForceUpdate')
|
|
||||||
var Markdown = require('../Mixins/Markdown')
|
|
||||||
|
|
||||||
var ProfileImage = require('../Components/ProfileImage')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [ReactRouter.Navigation, ReactRouter.State, ForceUpdate(60000), Markdown],
|
|
||||||
propTypes: {
|
|
||||||
articles: React.PropTypes.array,
|
|
||||||
showOnlyWithTag: React.PropTypes.func
|
|
||||||
},
|
|
||||||
handleArticleClikck: function (article) {
|
|
||||||
if (article.type === 'code') {
|
|
||||||
return function (e) {
|
|
||||||
var params = this.getParams()
|
|
||||||
|
|
||||||
document.getElementById('articleEditButton').focus()
|
|
||||||
this.transitionTo('codes', {
|
|
||||||
userName: params.userName,
|
|
||||||
planetName: params.planetName,
|
|
||||||
localId: article.localId
|
|
||||||
})
|
|
||||||
}.bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (article.type === 'note') {
|
|
||||||
return function (e) {
|
|
||||||
var params = this.getParams()
|
|
||||||
|
|
||||||
document.getElementById('articleEditButton').focus()
|
|
||||||
this.transitionTo('notes', {
|
|
||||||
userName: params.userName,
|
|
||||||
planetName: params.planetName,
|
|
||||||
localId: article.localId
|
|
||||||
})
|
|
||||||
}.bind(this)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var articles = this.props.articles.map(function (article) {
|
|
||||||
var tags = article.Tags.length > 0 ? article.Tags.map(function (tag) {
|
|
||||||
return (
|
|
||||||
<a onClick={this.props.showOnlyWithTag(tag.name)} key={tag.id}>#{tag.name}</a>
|
|
||||||
)
|
|
||||||
}.bind(this)) : (
|
|
||||||
<a className='noTag'>Not tagged yet</a>
|
|
||||||
)
|
|
||||||
var params = this.getParams()
|
|
||||||
var isActive = article.type === 'code' ? this.isActive('codes') && parseInt(params.localId, 10) === article.localId : this.isActive('notes') && parseInt(params.localId, 10) === article.localId
|
|
||||||
|
|
||||||
if (article.type === 'code') {
|
|
||||||
return (
|
|
||||||
<li onClick={this.handleArticleClikck(article)} key={'code-' + article.id}>
|
|
||||||
<div className={'articleItem' + (isActive ? ' active' : '')}>
|
|
||||||
<div className='itemLeft'>
|
|
||||||
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
|
|
||||||
<i className='fa fa-code fa-fw'></i>
|
|
||||||
</div>
|
|
||||||
<div className='itemRight'>
|
|
||||||
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
|
|
||||||
<div className='description'>{article.description}</div>
|
|
||||||
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='divider'></div>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<li onClick={this.handleArticleClikck(article)} key={'note-' + article.id}>
|
|
||||||
<div className={'articleItem blueprintItem' + (isActive ? ' active' : '')}>
|
|
||||||
<div className='itemLeft'>
|
|
||||||
<ProfileImage className='profileImage' size='25' email={article.User.email}/>
|
|
||||||
<i className='fa fa-file-text-o fa-fw'></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='itemRight'>
|
|
||||||
<div className='itemInfo'>{moment(article.updatedAt).fromNow()} by <span className='userProfileName'>{article.User.profileName}</span></div>
|
|
||||||
<div className='description'>{article.title}</div>
|
|
||||||
<div className='tags'><i className='fa fa-tags'/>{tags}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='divider'></div>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
|
|
||||||
}.bind(this))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='PlanetArticleList'>
|
|
||||||
<ul ref='articles'>
|
|
||||||
{articles}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var PlanetStore = require('../Stores/PlanetStore')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, KeyCaster('planetCreateModal')],
|
|
||||||
propTypes: {
|
|
||||||
ownerName: React.PropTypes.string,
|
|
||||||
transitionTo: React.PropTypes.func,
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
|
||||||
var ownerName = this.props.ownerName != null ? this.props.ownerName : currentUser.name
|
|
||||||
return {
|
|
||||||
user: currentUser,
|
|
||||||
planet: {
|
|
||||||
name: '',
|
|
||||||
public: true
|
|
||||||
},
|
|
||||||
ownerName: ownerName,
|
|
||||||
error: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
React.findDOMNode(this.refs.name).focus()
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
case 'submitPlanetCreateModal':
|
|
||||||
this.handleSubmit()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSubmit: function () {
|
|
||||||
this.setState({error: null}, function () {
|
|
||||||
Hq.createPlanet(this.state.ownerName, this.state.planet)
|
|
||||||
.then(function (res) {
|
|
||||||
var planet = res.body
|
|
||||||
|
|
||||||
PlanetStore.Actions.update(planet)
|
|
||||||
|
|
||||||
if (this.props.transitionTo != null) {
|
|
||||||
this.props.transitionTo('planetHome', {userName: planet.userName, planetName: planet.name})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.close()
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
|
|
||||||
if (err.status == null) return this.setState({error: {message: 'Check your network connection'}})
|
|
||||||
|
|
||||||
switch (err.status) {
|
|
||||||
case 403:
|
|
||||||
this.setState({error: err.response.body})
|
|
||||||
break
|
|
||||||
case 422:
|
|
||||||
this.setState({error: {message: 'Planet name should be Alphanumeric with _, -'}})
|
|
||||||
break
|
|
||||||
case 409:
|
|
||||||
this.setState({error: {message: 'The entered name already in use'}})
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
this.setState({error: {message: 'Undefined error please try again'}})
|
|
||||||
}
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var teamOptions = this.state.user.Teams.map(function (team) {
|
|
||||||
return (
|
|
||||||
<option key={'user-' + team.id} value={team.name}>{team.profileName} ({team.name})</option>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<div className='PlanetCreateModal modal'>
|
|
||||||
<input ref='name' valueLink={this.linkState('planet.name')} className='nameInput stripInput' placeholder='Crate new Planet'/>
|
|
||||||
|
|
||||||
<div className='formField'>
|
|
||||||
of
|
|
||||||
<select valueLink={this.linkState('ownerName')}>
|
|
||||||
<option value={this.state.user.name}>Me({this.state.user.name})</option>
|
|
||||||
{teamOptions}
|
|
||||||
</select>
|
|
||||||
as
|
|
||||||
<select valueLink={this.linkState('planet.public')}>
|
|
||||||
<option value={true}>Public</option>
|
|
||||||
<option value={false}>Private</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.state.error != null ? (<p className='errorAlert'>{this.state.error.message != null ? this.state.error.message : 'Error message undefined'}</p>) : null}
|
|
||||||
|
|
||||||
<button onClick={this.handleSubmit} className='submitButton'>
|
|
||||||
<i className='fa fa-check'/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
var ReactRouter = require('react-router')
|
|
||||||
var Link = ReactRouter.Link
|
|
||||||
|
|
||||||
var Modal = require('../Mixins/Modal')
|
|
||||||
var ExternalLink = require('../Mixins/ExternalLink')
|
|
||||||
|
|
||||||
var PlanetSettingModal = require('./PlanetSettingModal')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [ReactRouter.State, Modal, ExternalLink],
|
|
||||||
propTypes: {
|
|
||||||
search: React.PropTypes.string,
|
|
||||||
fetchPlanet: React.PropTypes.func,
|
|
||||||
onSearchChange: React.PropTypes.func,
|
|
||||||
currentPlanet: React.PropTypes.object
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
search: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
var search = React.findDOMNode(this.refs.search)
|
|
||||||
search.addEventListener('keydown', this.handleSearchKeyDown)
|
|
||||||
},
|
|
||||||
componentWillUnmount: function () {
|
|
||||||
var search = React.findDOMNode(this.refs.search)
|
|
||||||
search.removeEventListener('keydown', this.handleSearchKeyDown)
|
|
||||||
},
|
|
||||||
handleSearchKeyDown: function (e) {
|
|
||||||
if (e.keyCode === 38 || e.keyCode === 40) {
|
|
||||||
var search = React.findDOMNode(this.refs.search)
|
|
||||||
search.blur()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
if (e.keyCode !== 27 && (e.keyCode !== 13 || !e.metaKey)) {
|
|
||||||
e.stopPropagation()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openPlanetSettingModal: function () {
|
|
||||||
this.openModal(PlanetSettingModal, {planet: this.props.currentPlanet})
|
|
||||||
},
|
|
||||||
refresh: function () {
|
|
||||||
this.props.fetchPlanet()
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var currentPlanetName = this.props.currentPlanet.name
|
|
||||||
var currentUserName = this.props.currentPlanet.userName
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='PlanetHeader'>
|
|
||||||
<div className='headerLabel'>
|
|
||||||
<Link to='userHome' params={{userName: currentUserName}} className='userName'>{currentUserName}</Link>
|
|
||||||
<span className='planetName'>{currentPlanetName}</span>
|
|
||||||
|
|
||||||
{this.props.currentPlanet.public ? null : (
|
|
||||||
<div className='private'>
|
|
||||||
<i className='fa fa-lock'/>
|
|
||||||
<div className='tooltip'>Private planet</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button onClick={this.openPlanetSettingModal} className='planetSettingButton'>
|
|
||||||
<i className='fa fa-chevron-down'></i>
|
|
||||||
<div className='tooltip'>Planet setting</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className='headerControl'>
|
|
||||||
<div className='searchInput'>
|
|
||||||
<i className='fa fa-search'/>
|
|
||||||
<input onChange={this.props.onSearchChange} value={this.props.search} ref='search' type='text' className='inline-input circleInput' placeholder='Search...'/>
|
|
||||||
</div>
|
|
||||||
<button onClick={this.refresh} className='refreshButton'>
|
|
||||||
<i className='fa fa-refresh'/>
|
|
||||||
<div className='tooltip'>Refresh planet</div>
|
|
||||||
</button>
|
|
||||||
<a onClick={this.openExternal} href='http://b00st.io' className='logo'>
|
|
||||||
<img width='44' height='44' src='resources/favicon-230x230.png'/>
|
|
||||||
<div className='tooltip'>Boost official page</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
var ReactRouter = require('react-router')
|
|
||||||
var Navigation = ReactRouter.Navigation
|
|
||||||
|
|
||||||
var Modal = require('../Mixins/Modal')
|
|
||||||
|
|
||||||
var LaunchModal = require('../Components/LaunchModal')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [Modal, Navigation],
|
|
||||||
propTypes: {
|
|
||||||
planet: React.PropTypes.shape({
|
|
||||||
name: React.PropTypes.string,
|
|
||||||
Owner: React.PropTypes.shape({
|
|
||||||
id: React.PropTypes.number,
|
|
||||||
userType: React.PropTypes.string
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
search: React.PropTypes.string,
|
|
||||||
toggleCodeFilter: React.PropTypes.func,
|
|
||||||
toggleNoteFilter: React.PropTypes.func,
|
|
||||||
currentUser: React.PropTypes.shape({
|
|
||||||
id: React.PropTypes.number,
|
|
||||||
userType: React.PropTypes.string,
|
|
||||||
Teams: React.PropTypes.array
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
isLaunchModalOpen: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openLaunchModal: function () {
|
|
||||||
this.openModal(LaunchModal, {planet: this.props.planet, transitionTo: this.transitionTo})
|
|
||||||
},
|
|
||||||
isMyPlanet: function () {
|
|
||||||
if (this.props.currentUser == null) return false
|
|
||||||
if (this.props.planet.Owner.userType === 'person' && this.props.planet.Owner.id !== this.props.currentUser.id) return false
|
|
||||||
if (this.props.planet.Owner.userType === 'team' && !this.props.currentUser.Teams.some(function (team) {
|
|
||||||
if (team.id === this.props.planet.Owner.id) return true
|
|
||||||
return false
|
|
||||||
}.bind(this))) return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var keywords = this.props.search.split(' ')
|
|
||||||
var usingCodeFilter = keywords.some(function (keyword) {
|
|
||||||
if (keyword === '$c') return true
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
var usingNoteFilter = keywords.some(function (keyword) {
|
|
||||||
if (keyword === '$n') return true
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='PlanetNavigator'>
|
|
||||||
{this.isMyPlanet() ? (
|
|
||||||
<button onClick={this.openLaunchModal} className='launchButton btn-primary btn-block'>
|
|
||||||
<i className='fa fa-rocket fa-fw'/> Launch
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
<nav className='articleFilters'>
|
|
||||||
<a className={usingCodeFilter && !usingNoteFilter ? 'active' : ''} onClick={this.props.toggleCodeFilter}>
|
|
||||||
<i className='fa fa-code fa-fw'/> Codes
|
|
||||||
</a>
|
|
||||||
<a className={!usingCodeFilter && usingNoteFilter ? 'active' : ''} onClick={this.props.toggleNoteFilter}>
|
|
||||||
<i className='fa fa-file-text-o fa-fw'/> Notes
|
|
||||||
</a>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var PlanetStore = require('../Stores/PlanetStore')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, KeyCaster('planetSettingModal')],
|
|
||||||
propTypes: {
|
|
||||||
close: React.PropTypes.func,
|
|
||||||
planet: React.PropTypes.shape({
|
|
||||||
name: React.PropTypes.string,
|
|
||||||
public: React.PropTypes.bool,
|
|
||||||
userName: React.PropTypes.string
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
var deleteTextCandidates = [
|
|
||||||
'Confirm',
|
|
||||||
'Exterminatus',
|
|
||||||
'Avada Kedavra'
|
|
||||||
]
|
|
||||||
var random = Math.round(Math.random() * 10) % 10
|
|
||||||
var randomDeleteText = random > 1 ? deleteTextCandidates[0] : random === 1 ? deleteTextCandidates[1] : deleteTextCandidates[2]
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentTab: 'profile',
|
|
||||||
planet: {
|
|
||||||
name: this.props.planet.name,
|
|
||||||
public: this.props.planet.public
|
|
||||||
},
|
|
||||||
randomDeleteText: randomDeleteText,
|
|
||||||
deleteConfirmation: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
activePlanetProfile: function () {
|
|
||||||
this.setState({currentTab: 'profile'})
|
|
||||||
},
|
|
||||||
activePlanetDelete: function () {
|
|
||||||
this.setState({currentTab: 'delete'})
|
|
||||||
},
|
|
||||||
handlePublicChange: function (value) {
|
|
||||||
return function () {
|
|
||||||
this.state.planet.public = value
|
|
||||||
this.setState({planet: this.state.planet})
|
|
||||||
}.bind(this)
|
|
||||||
},
|
|
||||||
handleSavePlanetProfile: function (e) {
|
|
||||||
var planet = this.props.planet
|
|
||||||
|
|
||||||
this.setState({profileSubmitStatus: 'sending'}, function () {
|
|
||||||
Hq.updatePlanet(planet.userName, planet.name, this.state.planet)
|
|
||||||
.then(function (res) {
|
|
||||||
var planet = res.body
|
|
||||||
|
|
||||||
this.setState({profileSubmitStatus: 'done'})
|
|
||||||
|
|
||||||
PlanetStore.Actions.update(planet)
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
this.setState({profileSubmitStatus: 'error'})
|
|
||||||
console.error(err)
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleDeletePlanetClick: function () {
|
|
||||||
var planet = this.props.planet
|
|
||||||
|
|
||||||
this.setState({deleteSubmitStatus: 'sending'}, function () {
|
|
||||||
Hq.destroyPlanet(planet.userName, planet.name)
|
|
||||||
.then(function (res) {
|
|
||||||
var planet = res.body
|
|
||||||
|
|
||||||
PlanetStore.Actions.destroy(planet)
|
|
||||||
this.setState({deleteSubmitStatus: 'done'}, function () {
|
|
||||||
this.props.close()
|
|
||||||
})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
this.setState({deleteSubmitStatus: 'error'})
|
|
||||||
console.error(err)
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var content
|
|
||||||
|
|
||||||
content = this.state.currentTab === 'profile' ? this.renderPlanetProfileTab() : this.renderPlanetDeleteTab()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='PlanetSettingModal modal tabModal'>
|
|
||||||
<div className='leftPane'>
|
|
||||||
<h1 className='tabLabel'>Planet setting</h1>
|
|
||||||
<nav className='tabList'>
|
|
||||||
<button onClick={this.activePlanetProfile} className={this.state.currentTab === 'profile' ? 'active' : ''}><i className='fa fa-globe fa-fw'/> Planet profile</button>
|
|
||||||
<button onClick={this.activePlanetDelete} className={this.state.currentTab === 'delete' ? 'active' : ''}><i className='fa fa-trash fa-fw'/> Delete Planet</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='rightPane'>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
renderPlanetProfileTab: function () {
|
|
||||||
return (
|
|
||||||
<div className='planetProfileTab'>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>Planet name </label>
|
|
||||||
<input valueLink={this.linkState('planet.name')}/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='formRadioField'>
|
|
||||||
<input id='publicOption' checked={this.state.planet.public} onChange={this.handlePublicChange(true)} name='public' type='radio'/> <label htmlFor='publicOption'>Public</label>
|
|
||||||
|
|
||||||
<input id='privateOption' checked={!this.state.planet.public} onChange={this.handlePublicChange(false)} name='public' type='radio'/> <label htmlFor='privateOption'>Private</label>
|
|
||||||
</div>
|
|
||||||
<div className='formConfirm'>
|
|
||||||
<button onClick={this.handleSavePlanetProfile} className='saveButton btn-primary'>Save</button>
|
|
||||||
|
|
||||||
<div className={'alertInfo' + (this.state.profileSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
|
||||||
|
|
||||||
<div className={'alertError' + (this.state.profileSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
|
||||||
|
|
||||||
<div className={'alertSuccess' + (this.state.profileSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
renderPlanetDeleteTab: function () {
|
|
||||||
var disabled = !this.state.deleteConfirmation.match(new RegExp('^' + this.props.planet.userName + '/' + this.props.planet.name + '$'))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='planetDeleteTab'>
|
|
||||||
<p>Are you sure to destroy <strong>'{this.props.planet.userName + '/' + this.props.planet.name}'</strong>?</p>
|
|
||||||
<p>If you are sure, write <strong>'{this.props.planet.userName + '/' + this.props.planet.name}'</strong> to input below and click <strong>'{this.state.randomDeleteText}'</strong> button.</p>
|
|
||||||
<input valueLink={this.linkState('deleteConfirmation')} placeholder='userName/planetName'/>
|
|
||||||
<div className='formConfirm'>
|
|
||||||
<button disabled={disabled} onClick={this.handleDeletePlanetClick}>{this.state.randomDeleteText}</button>
|
|
||||||
|
|
||||||
<div className={'alertInfo' + (this.state.deleteSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
|
||||||
|
|
||||||
<div className={'alertError' + (this.state.deleteSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
|
||||||
|
|
||||||
<div className={'alertSuccess' + (this.state.deleteSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
var md5 = require('md5')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
propTypes: {
|
|
||||||
email: React.PropTypes.string,
|
|
||||||
size: React.PropTypes.string,
|
|
||||||
className: React.PropTypes.string
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<img className={this.props.className} width={this.props.size} height={this.props.size} src={'http://www.gravatar.com/avatar/' + md5(this.props.email.trim().toLowerCase()) + '?s=' + this.props.size}/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var UserStore = require('../Stores/UserStore')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, KeyCaster('teamCreateModal')],
|
|
||||||
propTypes: {
|
|
||||||
user: React.PropTypes.shape({
|
|
||||||
name: React.PropTypes.string
|
|
||||||
}),
|
|
||||||
transitionTo: React.PropTypes.func,
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
team: {
|
|
||||||
name: ''
|
|
||||||
},
|
|
||||||
error: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
React.findDOMNode(this.refs.teamName).focus()
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
case 'submitTeamCreateModal':
|
|
||||||
this.handleSubmit()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSubmit: function () {
|
|
||||||
this.setState({error: null}, function () {
|
|
||||||
Hq.createTeam(this.props.user.name, this.state.team)
|
|
||||||
.then(function (res) {
|
|
||||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
|
||||||
var team = res.body
|
|
||||||
|
|
||||||
currentUser.Teams.push(team)
|
|
||||||
localStorage.setItem('currentUser', JSON.stringify(currentUser))
|
|
||||||
UserStore.Actions.update(currentUser)
|
|
||||||
|
|
||||||
if (this.props.transitionTo != null) {
|
|
||||||
this.props.transitionTo('userHome', {userName: team.name})
|
|
||||||
}
|
|
||||||
this.props.close()
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
|
|
||||||
if (err.status == null) return this.setState({error: {message: 'Check your network connection'}})
|
|
||||||
|
|
||||||
switch (err.status) {
|
|
||||||
case 422:
|
|
||||||
this.setState({error: {message: 'Team name should be Alphanumeric with _, -'}})
|
|
||||||
break
|
|
||||||
case 409:
|
|
||||||
this.setState({error: {message: 'The entered name already in use'}})
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
this.setState({error: {message: 'Error message undefined'}})
|
|
||||||
}
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='TeamCreateModal modal'>
|
|
||||||
<input ref='teamName' valueLink={this.linkState('team.name')} className='nameInput stripInput' placeholder='Create new team'/>
|
|
||||||
{this.state.error != null ? (<p className='errorAlert'>{this.state.error.message != null ? this.state.error.message : 'Unintended error occured'}</p>) : null}
|
|
||||||
<button onClick={this.handleSubmit} className='submitButton'>
|
|
||||||
<i className='fa fa-check'/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
var Reflux = require('reflux')
|
|
||||||
var Select = require('react-select')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var Helper = require('../Mixins/Helper')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var UserStore = require('../Stores/UserStore')
|
|
||||||
|
|
||||||
var getOptions = function (input, callback) {
|
|
||||||
Hq.searchUser(input)
|
|
||||||
.then(function (res) {
|
|
||||||
callback(null, {
|
|
||||||
options: res.body.map(function (user) {
|
|
||||||
return {
|
|
||||||
label: user.name,
|
|
||||||
value: user.name
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
complete: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, Reflux.listenTo(UserStore, 'onUserChange'), Helper, KeyCaster('teamSettingsModal')],
|
|
||||||
propTypes: {
|
|
||||||
team: React.PropTypes.shape({
|
|
||||||
id: React.PropTypes.number,
|
|
||||||
name: React.PropTypes.string,
|
|
||||||
profileName: React.PropTypes.string,
|
|
||||||
email: React.PropTypes.string,
|
|
||||||
Members: React.PropTypes.array
|
|
||||||
}),
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
var team = this.props.team
|
|
||||||
return {
|
|
||||||
currentTab: 'teamInfo',
|
|
||||||
team: {
|
|
||||||
profileName: team.profileName
|
|
||||||
},
|
|
||||||
userSubmitStatus: null,
|
|
||||||
member: {
|
|
||||||
name: '',
|
|
||||||
role: 'member'
|
|
||||||
},
|
|
||||||
updatingMember: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onUserChange: function (res) {
|
|
||||||
var member
|
|
||||||
switch (res.status) {
|
|
||||||
case 'memberAdded':
|
|
||||||
member = res.data
|
|
||||||
if (member.TeamMember.TeamId === this.props.team.id) {
|
|
||||||
this.forceUpdate()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'memberRemoved':
|
|
||||||
member = res.data
|
|
||||||
if (member.TeamMember.TeamId === this.props.team.id) {
|
|
||||||
this.forceUpdate()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectTab: function (tabName) {
|
|
||||||
return function () {
|
|
||||||
this.setState({currentTab: tabName})
|
|
||||||
}.bind(this)
|
|
||||||
},
|
|
||||||
saveUserInfo: function () {
|
|
||||||
this.setState({
|
|
||||||
userSubmitStatus: 'sending'
|
|
||||||
}, function () {
|
|
||||||
Hq.updateUser(this.props.team.name, this.state.team)
|
|
||||||
.then(function (res) {
|
|
||||||
this.setState({userSubmitStatus: 'done'}, function () {
|
|
||||||
UserStore.Actions.update(res.body)
|
|
||||||
this.forceUpdate()
|
|
||||||
})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
this.setState({userSubmitStatus: 'error'})
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
handleMemberNameChange: function (value) {
|
|
||||||
var member = this.state.member
|
|
||||||
member.name = value
|
|
||||||
this.setState({member: member})
|
|
||||||
},
|
|
||||||
addMember: function () {
|
|
||||||
this.setState({updatingMember: true}, function () {
|
|
||||||
Hq
|
|
||||||
.addMember(this.props.team.name, {
|
|
||||||
userName: this.state.member.name,
|
|
||||||
role: this.state.member.role
|
|
||||||
})
|
|
||||||
.then(function (res) {
|
|
||||||
UserStore.Actions.addMember(res.body)
|
|
||||||
this.setState({updatingMember: false})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
this.setState({updatingMember: false})
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
roleChange: function (memberName) {
|
|
||||||
return function (e) {
|
|
||||||
var role = e.target.value
|
|
||||||
this.setState({updatingMember: true}, function () {
|
|
||||||
Hq
|
|
||||||
.addMember(this.props.team.name, {
|
|
||||||
userName: memberName,
|
|
||||||
role: role
|
|
||||||
})
|
|
||||||
.then(function (res) {
|
|
||||||
UserStore.Actions.addMember(res.body)
|
|
||||||
this.setState({updatingMember: false})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
this.setState({updatingMember: false})
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
}.bind(this)
|
|
||||||
},
|
|
||||||
removeMember: function (memberName) {
|
|
||||||
return function () {
|
|
||||||
this.setState({updatingMember: true}, function () {
|
|
||||||
Hq
|
|
||||||
.removeMember(this.props.team.name, {
|
|
||||||
userName: memberName
|
|
||||||
})
|
|
||||||
.then(function (res) {
|
|
||||||
UserStore.Actions.removeMember(res.body)
|
|
||||||
this.setState({updatingMember: false})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
this.setState({updatingMember: false})
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
}.bind(this)
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var content
|
|
||||||
|
|
||||||
switch (this.state.currentTab) {
|
|
||||||
case 'teamInfo':
|
|
||||||
content = this.renderTeamInfoTab()
|
|
||||||
break
|
|
||||||
case 'members':
|
|
||||||
content = this.renderMembersTab()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='TeamSettingsModal modal tabModal'>
|
|
||||||
<div className='leftPane'>
|
|
||||||
<div className='tabLabel'>Team settings</div>
|
|
||||||
<div className='tabList'>
|
|
||||||
<button className={this.state.currentTab === 'teamInfo' ? 'active' : ''} onClick={this.selectTab('teamInfo')}><i className='fa fa-info-circle fa-fw'/> Team Info</button>
|
|
||||||
<button className={this.state.currentTab === 'members' ? 'active' : ''} onClick={this.selectTab('members')}><i className='fa fa-users fa-fw'/> Members</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className='rightPane'>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
renderTeamInfoTab: function () {
|
|
||||||
return (
|
|
||||||
<div className='userInfoTab'>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>Profile Name</label>
|
|
||||||
<input valueLink={this.linkState('team.profileName')}/>
|
|
||||||
</div>
|
|
||||||
<div className='formConfirm'>
|
|
||||||
<button disabled={this.state.userSubmitStatus === 'sending'} onClick={this.saveUserInfo}>Save</button>
|
|
||||||
|
|
||||||
<div className={'alertInfo' + (this.state.userSubmitStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
|
||||||
|
|
||||||
<div className={'alertError' + (this.state.userSubmitStatus === 'error' ? '' : ' hide')}>Connection failed.. Try again.</div>
|
|
||||||
|
|
||||||
<div className={'alertSuccess' + (this.state.userSubmitStatus === 'done' ? '' : ' hide')}>Successfully done!!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
renderMembersTab: function () {
|
|
||||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
|
||||||
|
|
||||||
var members = this.props.team.Members.map(function (member) {
|
|
||||||
var isCurrentUser = currentUser.id === member.id
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<td>{member.profileName}({member.name})</td>
|
|
||||||
<td>
|
|
||||||
{isCurrentUser ? (
|
|
||||||
'Owner'
|
|
||||||
) : (
|
|
||||||
<select disabled={this.state.updatingMember} onChange={this.roleChange(member.name)} className='roleSelect' value={member.TeamMember.role}>
|
|
||||||
<option value='owner'>Owner</option>
|
|
||||||
<option value='member'>Member</option>
|
|
||||||
</select>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{isCurrentUser ? '-' : (
|
|
||||||
<button disabled={this.state.updatingMember} onClick={this.removeMember(member.name)}><i className='fa fa-close fa-fw'/></button>
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
)
|
|
||||||
}.bind(this))
|
|
||||||
|
|
||||||
var belowLimit = members.length < 5
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='membersTab'>
|
|
||||||
<table className='memberTable'>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Username</th>
|
|
||||||
<th>Role</th>
|
|
||||||
<th>Control</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{members}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{belowLimit ? (
|
|
||||||
<div className='addMemberForm'>
|
|
||||||
<div className='formLabel'>Add Member</div>
|
|
||||||
<div className='formGroup'>
|
|
||||||
<Select
|
|
||||||
name='userName'
|
|
||||||
value={this.state.member.name}
|
|
||||||
placeholder='Username to add'
|
|
||||||
asyncOptions={getOptions}
|
|
||||||
onChange={this.handleMemberNameChange}
|
|
||||||
className='userNameSelect'
|
|
||||||
/>
|
|
||||||
<select valueLink={this.linkState('member.role')} className='roleSelect'>
|
|
||||||
<option value={'member'}>Member</option>
|
|
||||||
<option value={'owner'}>Owner</option>
|
|
||||||
</select>
|
|
||||||
<button disabled={this.state.updatingMember} onClick={this.addMember} className='confirmButton'>Add Member</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
Maximum number of members is 5 on Beta version. Please contact us if you want futher use.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user