mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 18:26:26 +00:00
Compare commits
1735 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
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 |
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/
|
||||||
10
.eslintrc
Normal file
10
.eslintrc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": ["standard", "standard-jsx"],
|
||||||
|
"rules": {
|
||||||
|
"no-useless-escape": 0,
|
||||||
|
"prefer-const": "warn",
|
||||||
|
"no-unused-vars": "warn",
|
||||||
|
"no-undef": "warn",
|
||||||
|
"no-lone-blocks": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
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
|
||||||
|
|||||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,6 +0,0 @@
|
|||||||
[submodule "browser/ace"]
|
|
||||||
path = browser/ace
|
|
||||||
url = https://github.com/ajaxorg/ace-builds.git
|
|
||||||
[submodule "browser/electron-stylus"]
|
|
||||||
path = browser/electron-stylus
|
|
||||||
url = https://github.com/Rokt33r/electron-stylus.git
|
|
||||||
6
.travis.yml
Normal file
6
.travis.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- 'stable'
|
||||||
|
- 'lts/*'
|
||||||
|
|
||||||
|
script: npm run lint && npm run test
|
||||||
5
Backers.md
Normal file
5
Backers.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Become a [backer](https://salt.bountysource.com/teams/boostnote) and support Boostnote!
|
||||||
|
You can support Boostnote from $ 5 a month!
|
||||||
|
|
||||||
|
# Backers
|
||||||
|
[Kazu Yokomizo](https://twitter.com/kazup_bot)
|
||||||
1
ISSUE_TEMPLATE.md
Normal file
1
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Please paste some **screenshots** with the **developer tool** open if you report a bug.
|
||||||
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
247
browser/components/CodeEditor.js
Normal file
247
browser/components/CodeEditor.js
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import CodeMirror from 'codemirror'
|
||||||
|
import path from 'path'
|
||||||
|
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
||||||
|
|
||||||
|
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.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() === '- ' || line.trimLeft() === '* ' || line.trimLeft() === '+ ') {
|
||||||
|
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)
|
||||||
|
|
||||||
|
let editorTheme = document.getElementById('editorTheme')
|
||||||
|
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
this.editor.off('blur', this.blurHandler)
|
||||||
|
this.editor.off('change', this.changeHandler)
|
||||||
|
let 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) {
|
||||||
|
let 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) {
|
||||||
|
const textarea = this.editor.getInputField()
|
||||||
|
const cm = this.editor
|
||||||
|
textarea.value = `${textarea.value.substr(0, textarea.selectionStart)}${imageMd}${textarea.value.substr(textarea.selectionEnd)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
let { className, fontFamily, fontSize } = this.props
|
||||||
|
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'
|
||||||
|
}
|
||||||
283
browser/components/MarkdownEditor.js
Normal file
283
browser/components/MarkdownEditor.js
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
import React, { PropTypes } 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'
|
||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
|
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) {
|
||||||
|
let { config } = this.props
|
||||||
|
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
||||||
|
let 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() })
|
||||||
|
let { config } = this.props
|
||||||
|
if (config.editor.switchPreview === 'BLUR') {
|
||||||
|
let 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) {
|
||||||
|
let { 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()
|
||||||
|
let idMatch = /checkbox-([0-9]+)/
|
||||||
|
let checkedMatch = /\[x\]/i
|
||||||
|
let uncheckedMatch = /\[ \]/
|
||||||
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
|
let lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
|
let lines = this.refs.code.value
|
||||||
|
.split('\n')
|
||||||
|
|
||||||
|
let 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) {
|
||||||
|
let { config } = this.props
|
||||||
|
if (this.state.status !== 'CODE') return false
|
||||||
|
const keyPressed = this.state.keyPressed
|
||||||
|
keyPressed.add(e.keyCode)
|
||||||
|
this.setState({ keyPressed })
|
||||||
|
let 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 () {
|
||||||
|
let { 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
|
||||||
|
|
||||||
|
let 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)}
|
||||||
|
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
|
||||||
|
|
||||||
429
browser/components/MarkdownPreview.js
Normal file
429
browser/components/MarkdownPreview.js
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
import React, { PropTypes } 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'
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
${markdownStyle}
|
||||||
|
body {
|
||||||
|
font-family: ${fontFamily.join(', ')};
|
||||||
|
font-size: ${fontSize}px;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
font-family: ${codeBlockFontFamily.join(', ')};
|
||||||
|
background-color: rgba(0,0,0,0.04);
|
||||||
|
color: #CC305F;
|
||||||
|
}
|
||||||
|
.lineNumber {
|
||||||
|
${lineNumber && 'display: block !important;'}
|
||||||
|
font-family: ${codeBlockFontFamily.join(', ')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.clipboardButton {
|
||||||
|
color: rgba(147,147,149,0.8);;
|
||||||
|
fill: rgba(147,147,149,1);;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin: 7px;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
outline: none;
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
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()
|
||||||
|
|
||||||
|
let anchor = e.target.closest('a')
|
||||||
|
let href = anchor.getAttribute('href')
|
||||||
|
if (_.isString(href) && href.match(/^#/)) {
|
||||||
|
let 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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.theme !== this.props.theme) {
|
||||||
|
this.applyStyle()
|
||||||
|
this.rewriteIframe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyStyle () {
|
||||||
|
let { fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme } = 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 = `${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)
|
||||||
|
})
|
||||||
|
|
||||||
|
let { value, theme, indentSize, codeBlockTheme, storagePath } = 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) => {
|
||||||
|
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) => {
|
||||||
|
if (!/\/:storage/.test(el.src)) return
|
||||||
|
el.src = `file:///${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, () => {
|
||||||
|
let 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)
|
||||||
|
this.notify('Saved to Clipboard!', {
|
||||||
|
body: 'Paste it wherever you want!',
|
||||||
|
silent: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
el.parentNode.appendChild(copyIcon)
|
||||||
|
el.innerHTML = ''
|
||||||
|
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
|
||||||
|
CodeMirror.runMode(content, syntax.mime, el, {
|
||||||
|
tabSize: indentSize
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
let 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 {
|
||||||
|
let 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 {
|
||||||
|
let 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) {
|
||||||
|
let blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
|
||||||
|
|
||||||
|
for (let index = 0; index < blocks.length; index++) {
|
||||||
|
let block = blocks[index]
|
||||||
|
let 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 () {
|
||||||
|
let { 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,
|
||||||
|
storagePath: PropTypes.string
|
||||||
|
}
|
||||||
18
browser/components/ModalEscButton.js
Normal file
18
browser/components/ModalEscButton.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import React, {PropTypes} 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'>x</div>
|
||||||
|
<div styleName='esc-text'>esc</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
ModalEscButton.propTypes = {
|
||||||
|
handleEscButtonClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(ModalEscButton, styles)
|
||||||
14
browser/components/ModalEscButton.styl
Normal file
14
browser/components/ModalEscButton.styl
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
.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 15px
|
||||||
106
browser/components/NoteItem.js
Normal file
106
browser/components/NoteItem.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Note item component.
|
||||||
|
*/
|
||||||
|
import React, { PropTypes } 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} handleDragStart
|
||||||
|
* @param {string} dateDisplay
|
||||||
|
*/
|
||||||
|
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStart }) => (
|
||||||
|
<div styleName={isActive
|
||||||
|
? 'item--active'
|
||||||
|
: 'item'
|
||||||
|
}
|
||||||
|
key={`${note.storage}-${note.key}`}
|
||||||
|
onClick={e => handleNoteClick(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
|
||||||
|
? <i styleName='item-star' className='fa fa-star' /> : ''
|
||||||
|
}
|
||||||
|
{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,
|
||||||
|
handleDragStart: PropTypes.func.isRequired,
|
||||||
|
handleDragEnd: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(NoteItem, styles)
|
||||||
193
browser/components/NoteItem.styl
Normal file
193
browser/components/NoteItem.styl
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
$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 background-color 0.2s
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
.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 $ui-button--active-backgroundColor
|
||||||
|
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 $ui-button--active-backgroundColor
|
||||||
|
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 $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.item-title-icon
|
||||||
|
position relative
|
||||||
|
font-size 12px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title
|
||||||
|
font-size 14px
|
||||||
|
position relative
|
||||||
|
top -12px
|
||||||
|
left 20px
|
||||||
|
padding-right 15px
|
||||||
|
padding-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 2px
|
||||||
|
font-size 12px
|
||||||
|
line-height 20px
|
||||||
|
overflow ellipsis
|
||||||
|
display flex
|
||||||
|
|
||||||
|
.item-bottom-tagList
|
||||||
|
flex 1
|
||||||
|
overflow ellipsis
|
||||||
|
line-height 20px
|
||||||
|
padding-top 7px
|
||||||
|
padding-left 2px
|
||||||
|
margin-right 27px
|
||||||
|
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
font-size 11px
|
||||||
|
margin-right 8px
|
||||||
|
padding 0
|
||||||
|
height 20px
|
||||||
|
box-sizing border-box
|
||||||
|
border-radius 2px
|
||||||
|
padding 1px 2px
|
||||||
|
vertical-align middle
|
||||||
|
background-color white
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-time
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
font-size 11px
|
||||||
|
padding-left 2px
|
||||||
|
padding-bottom 2px
|
||||||
|
|
||||||
|
.item-star
|
||||||
|
position absolute
|
||||||
|
right -20px
|
||||||
|
bottom 2px
|
||||||
|
width 34px
|
||||||
|
height 34px
|
||||||
|
color alpha($ui-favorite-star-button-color, 60%)
|
||||||
|
font-size 12px
|
||||||
|
padding 0
|
||||||
|
border-radius 17px
|
||||||
|
|
||||||
|
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%)
|
||||||
|
.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($ui-dark-button--active-backgroundColor, 40%)
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:active
|
||||||
|
transition 0.15s
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
.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
|
||||||
|
|
||||||
|
.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
|
||||||
50
browser/components/NoteItemSimple.js
Normal file
50
browser/components/NoteItemSimple.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Note item component with simple display mode.
|
||||||
|
*/
|
||||||
|
import React, { PropTypes } 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} handleDragStart
|
||||||
|
*/
|
||||||
|
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleDragStart }) => (
|
||||||
|
<div styleName={isActive
|
||||||
|
? 'item-simple--active'
|
||||||
|
: 'item-simple'
|
||||||
|
}
|
||||||
|
key={`${note.storage}-${note.key}`}
|
||||||
|
onClick={e => handleNoteClick(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,
|
||||||
|
handleDragStart: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(NoteItemSimple, styles)
|
||||||
106
browser/components/NoteItemSimple.styl
Normal file
106
browser/components/NoteItemSimple.styl
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
$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 background-color 0.15s
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
color $ui-text-color
|
||||||
|
&:active
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
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 $ui-button--active-backgroundColor
|
||||||
|
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 $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.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="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
|
||||||
|
&:active
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.item-simple--active
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color transparent
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.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
|
||||||
52
browser/components/SideNavFilter.js
Normal file
52
browser/components/SideNavFilter.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Filter for all notes.
|
||||||
|
*/
|
||||||
|
import React, { PropTypes } 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
|
||||||
|
}) => (
|
||||||
|
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
||||||
|
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
||||||
|
onClick={handleAllNotesButtonClick}
|
||||||
|
>
|
||||||
|
<i className='fa fa-archive fa-fw' />
|
||||||
|
<span styleName='menu-button-label'>All Notes</span>
|
||||||
|
</button>
|
||||||
|
<button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
|
||||||
|
onClick={handleStarredButtonClick}
|
||||||
|
>
|
||||||
|
<i className='fa fa-star fa-fw' />
|
||||||
|
<span styleName='menu-button-label'>Starred</span>
|
||||||
|
</button>
|
||||||
|
<button styleName={isTrashedActive ? 'menu-button--active' : 'menu-button'}
|
||||||
|
onClick={handleTrashedButtonClick}
|
||||||
|
>
|
||||||
|
<i className='fa fa-trash fa-fw' />
|
||||||
|
<span styleName='menu-button-label'>Trash</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)
|
||||||
105
browser/components/SideNavFilter.styl
Normal file
105
browser/components/SideNavFilter.styl
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
.menu
|
||||||
|
margin-bottom 30px
|
||||||
|
|
||||||
|
.menu-button
|
||||||
|
navButtonColor()
|
||||||
|
height 32px
|
||||||
|
padding 0 15px
|
||||||
|
font-size 12px
|
||||||
|
width 100%
|
||||||
|
text-align left
|
||||||
|
overflow ellipsis
|
||||||
|
|
||||||
|
.menu-button--active
|
||||||
|
@extend .menu-button
|
||||||
|
color #e74c3c
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.menu-button-star--active
|
||||||
|
@extend .menu-button
|
||||||
|
color #F9BF3B
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color #F9BF3B
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color #F9BF3B
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.menu-button-label
|
||||||
|
margin-left 5px
|
||||||
|
|
||||||
|
.menu--folded
|
||||||
|
@extend .menu
|
||||||
|
.menu-button, .menu-button--active
|
||||||
|
text-align center
|
||||||
|
&:hover .menu-button-label
|
||||||
|
transition opacity 0.15s
|
||||||
|
opacity 1
|
||||||
|
.menu-button-label
|
||||||
|
position fixed
|
||||||
|
display inline-block
|
||||||
|
height 32px
|
||||||
|
left 44px
|
||||||
|
padding 0 10px
|
||||||
|
margin-top -8px
|
||||||
|
margin-left 0
|
||||||
|
overflow ellipsis
|
||||||
|
background-color $ui-tooltip-backgroundColor
|
||||||
|
z-index 10
|
||||||
|
color white
|
||||||
|
line-height 32px
|
||||||
|
border-top-right-radius 2px
|
||||||
|
border-bottom-right-radius 2px
|
||||||
|
pointer-events none
|
||||||
|
opacity 0
|
||||||
|
font-size 12px
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.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 $ui-dark-button--active-backgroundColor
|
||||||
|
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 $ui-dark-button--active-backgroundColor
|
||||||
|
color $ui-favorite-star-button-color
|
||||||
|
.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 () {
|
||||||
|
let { 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
|
||||||
66
browser/components/StorageItem.js
Normal file
66
browser/components/StorageItem.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for showing storage.
|
||||||
|
*/
|
||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import styles from './StorageItem.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import { isNumber } 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'
|
||||||
|
}
|
||||||
|
style={{borderColor: folderColor}}
|
||||||
|
>
|
||||||
|
{isFolded ? folderName.substring(0, 1) : 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)
|
||||||
92
browser/components/StorageItem.styl
Normal file
92
browser/components/StorageItem.styl
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
.root
|
||||||
|
width 100%
|
||||||
|
user-select none
|
||||||
|
|
||||||
|
.folderList-item
|
||||||
|
display flex
|
||||||
|
width 100%
|
||||||
|
height 26px
|
||||||
|
background-color transparent
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding 0
|
||||||
|
margin-bottom 5px
|
||||||
|
text-align left
|
||||||
|
border none
|
||||||
|
overflow ellipsis
|
||||||
|
font-size 12px
|
||||||
|
&:first-child
|
||||||
|
margin-top 0
|
||||||
|
&:hover
|
||||||
|
color $ui-text-color
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
|
transition background-color 0.15s
|
||||||
|
&:active
|
||||||
|
color $ui-text-color
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.folderList-item--active
|
||||||
|
@extend .folderList-item
|
||||||
|
color $ui-text-color
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color $ui-text-color
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.folderList-item-name
|
||||||
|
display block
|
||||||
|
flex 1
|
||||||
|
padding 0 25px
|
||||||
|
height 26px
|
||||||
|
line-height 26px
|
||||||
|
border-width 0 0 0 2px
|
||||||
|
border-style solid
|
||||||
|
border-color transparent
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
|
||||||
|
.folderList-item-noteCount
|
||||||
|
float right
|
||||||
|
line-height 26px
|
||||||
|
padding-right 15px
|
||||||
|
font-size 12px
|
||||||
|
|
||||||
|
.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 12px
|
||||||
|
|
||||||
|
|
||||||
|
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 $ui-dark-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
27
browser/components/TodoListPercentage.js
Normal file
27
browser/components/TodoListPercentage.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Percentage of todo achievement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { PropTypes } 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)
|
||||||
31
browser/components/TodoListPercentage.styl
Normal file
31
browser/components/TodoListPercentage.styl
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
.percentageBar
|
||||||
|
position absolute
|
||||||
|
top 58px
|
||||||
|
right: 0px
|
||||||
|
left 0px
|
||||||
|
background-color #DADFE1
|
||||||
|
width 100%
|
||||||
|
height: 15px
|
||||||
|
font-size: 12px
|
||||||
|
z-index 100
|
||||||
|
border-radius 2px
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: #6C7A89
|
||||||
|
height 15px
|
||||||
|
border-radius 2px
|
||||||
|
transition 0.3s
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color #f4f4f4
|
||||||
|
padding: 2px 43%
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.percentageBar
|
||||||
|
background-color #363A3D
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: alpha(#939395, 50%)
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color $ui-dark-text-color
|
||||||
33
browser/components/TodoProcess.js
Normal file
33
browser/components/TodoProcess.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Percentage of todo achievement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { PropTypes } 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%)
|
||||||
318
browser/components/markdown.styl
Normal file
318
browser/components/markdown.styl
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
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.25em
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
Submodule browser/electron-stylus deleted from 6d86de7c66
@@ -1,42 +0,0 @@
|
|||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var CodeViewer = require('../../main/Components/CodeViewer')
|
|
||||||
|
|
||||||
var MarkdownPreview = require('../../main/Components/MarkdownPreview')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
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'>
|
|
||||||
<MarkdownPreview className='marked' content={article.content}/>
|
|
||||||
</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
|
||||||
|
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
|
||||||
208
browser/finder/NoteDetail.js
Normal file
208
browser/finder/NoteDetail.js
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
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 () {
|
||||||
|
let { 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 () {
|
||||||
|
let { note } = this.props
|
||||||
|
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
||||||
|
this.setState({
|
||||||
|
snippetIndex: (this.state.snippetIndex + 1) % note.snippets.length
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveToClipboard () {
|
||||||
|
let { 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 () {
|
||||||
|
let { 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') {
|
||||||
|
let tabList = note.snippets.map((snippet, index) => {
|
||||||
|
let 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>
|
||||||
|
})
|
||||||
|
|
||||||
|
let viewList = note.snippets.map((snippet, index) => {
|
||||||
|
let 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}
|
||||||
|
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
|
||||||
|
left $note-detail-left-margin
|
||||||
|
right $note-detail-right-margin
|
||||||
|
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 () {
|
||||||
|
let { index } = this.props
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
let list = this.refs.root
|
||||||
|
let item = list.childNodes[index]
|
||||||
|
if (item == null) return null
|
||||||
|
|
||||||
|
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
||||||
|
if (overflowBelow) {
|
||||||
|
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
|
||||||
|
}
|
||||||
|
let overflowAbove = list.scrollTop > item.offsetTop
|
||||||
|
if (overflowAbove) {
|
||||||
|
list.scrollTop = item.offsetTop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetScroll () {
|
||||||
|
this.refs.root.scrollTop = 0
|
||||||
|
this.setState({
|
||||||
|
range: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll (e) {
|
||||||
|
let { 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 () {
|
||||||
|
let { notes, index } = this.props
|
||||||
|
|
||||||
|
let 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) {
|
||||||
|
let { storage } = this.props
|
||||||
|
this.props.handleStorageButtonClick(e, storage.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFolderClick (e, folder) {
|
||||||
|
let { storage } = this.props
|
||||||
|
this.props.handleFolderButtonClick(e, storage.key, folder.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
let { storage, filter } = this.props
|
||||||
|
let 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', 'finderCss')
|
|
||||||
require('node-jsx').install({ harmony: true, extension: '.jsx' })
|
|
||||||
require('./index.jsx')
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
356
browser/finder/index.js
Normal file
356
browser/finder/index.js
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
import React, { PropTypes } from 'react'
|
||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import { connect, Provider } from 'react-redux'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import ipc from './ipcClient'
|
||||||
|
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')
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const { remote } = electron
|
||||||
|
const { Menu } = remote
|
||||||
|
|
||||||
|
function hideFinder () {
|
||||||
|
let 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) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.includeSnippet = e.target.checked
|
||||||
|
this.setState({
|
||||||
|
filter: filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnlyMarkdownCheckboxChange (e) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.includeMarkdown = e.target.checked
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter: filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAllNotesButtonClick (e) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.type = 'ALL'
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStarredButtonClick (e) {
|
||||||
|
let { filter } = this.state
|
||||||
|
filter.type = 'STARRED'
|
||||||
|
this.refs.list.resetScroll()
|
||||||
|
this.setState({
|
||||||
|
filter,
|
||||||
|
index: 0
|
||||||
|
}, () => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStorageButtonClick (e, storage) {
|
||||||
|
let { 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) {
|
||||||
|
let { 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 () {
|
||||||
|
let { data, config } = this.props
|
||||||
|
let { filter, search } = this.state
|
||||||
|
let storageList = []
|
||||||
|
for (let key in data.storageMap) {
|
||||||
|
let storage = data.storageMap[key]
|
||||||
|
let 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 (let 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) {
|
||||||
|
let 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))
|
||||||
|
|
||||||
|
let 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'))
|
|
||||||
122
browser/finder/ipcClient.js
Normal file
122
browser/finder/ipcClient.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
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 () {
|
||||||
|
let 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 () {
|
||||||
|
let 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 {
|
||||||
|
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'
|
||||||
|
|
||||||
|
let 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
|
||||||
|
}
|
||||||
|
|
||||||
|
let reducer = combineReducers({
|
||||||
|
data,
|
||||||
|
config,
|
||||||
|
routing: routerReducer
|
||||||
|
})
|
||||||
|
|
||||||
|
let 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) {
|
||||||
|
let result = []
|
||||||
|
for (let [key, value] of this._map) {
|
||||||
|
result.push(cb(value, key))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
toJS () {
|
||||||
|
let 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) {
|
||||||
|
let 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
|
||||||
|
}
|
||||||
36
browser/lib/consts.js
Normal file
36
browser/lib/consts.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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('.'))
|
||||||
|
})
|
||||||
|
|
||||||
|
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) {
|
||||||
|
let splitted = value.split('\n')
|
||||||
|
let title = null
|
||||||
|
let isInsideCodeBlock = false
|
||||||
|
|
||||||
|
splitted.some((line, index) => {
|
||||||
|
let trimmedLine = line.trim()
|
||||||
|
let 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) {
|
||||||
|
let splitted = content.split('\n')
|
||||||
|
let numberOfTodo = 0
|
||||||
|
let numberOfCompletedTodo = 0
|
||||||
|
|
||||||
|
splitted.forEach((line) => {
|
||||||
|
let 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')
|
||||||
|
}
|
||||||
167
browser/lib/markdown.js
Normal file
167
browser/lib/markdown.js
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import markdownit from 'markdown-it'
|
||||||
|
import emoji from 'markdown-it-emoji'
|
||||||
|
import math from '@rokt33r/markdown-it-math'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
const katex = window.katex
|
||||||
|
|
||||||
|
function createGutter (str) {
|
||||||
|
let lc = (str.match(/\n/g) || []).length
|
||||||
|
let 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'))
|
||||||
|
// Override task item
|
||||||
|
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||||
|
let content, terminate, i, l, token
|
||||||
|
let nextLine = startLine + 1
|
||||||
|
let terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||||
|
let 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') {
|
||||||
|
let 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
|
||||||
|
let 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]])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let result = originalRender.call(md.renderer, tokens, options, env)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
window.md = md
|
||||||
|
|
||||||
|
function strip (input) {
|
||||||
|
var output = input
|
||||||
|
try {
|
||||||
|
output = output
|
||||||
|
.replace(/^([\s\t]*)([\*\-\+]|\d\.)\s+/gm, '$1')
|
||||||
|
.replace(/\n={2,}/g, '\n')
|
||||||
|
.replace(/~~/g, '')
|
||||||
|
.replace(/`{3}.*\n/g, '')
|
||||||
|
.replace(/<(.*?)>/g, '$1')
|
||||||
|
.replace(/^[=\-]{2,}\s*$/g, '')
|
||||||
|
.replace(/\[\^.+?\](: .*?$)?/g, '')
|
||||||
|
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
|
||||||
|
.replace(/!\[.*?\][\[\(].*?[\]\)]/g, '')
|
||||||
|
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
||||||
|
.replace(/>/g, '')
|
||||||
|
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
||||||
|
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
|
||||||
|
.replace(/([\*_]{1,3})(\S.*?\S)\1/g, '$2')
|
||||||
|
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
||||||
|
.replace(/^-{3,}\s*$/g, '')
|
||||||
|
.replace(/`(.+?)`/g, '$1')
|
||||||
|
.replace(/\n{2,}/g, '\n\n')
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
const markdown = {
|
||||||
|
render: function markdown (content) {
|
||||||
|
if (!_.isString(content)) content = ''
|
||||||
|
const renderedContent = md.render(content)
|
||||||
|
return md.normalizeLinkText(renderedContent)
|
||||||
|
},
|
||||||
|
strip
|
||||||
|
}
|
||||||
|
export default markdown
|
||||||
42
browser/lib/search.js
Normal file
42
browser/lib/search.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
export default function searchFromNotes (notes, search) {
|
||||||
|
if (search.trim().length === 0) return []
|
||||||
|
let searchBlocks = search.split(' ')
|
||||||
|
searchBlocks.forEach((block) => {
|
||||||
|
if (block.match(/^#.+/)) {
|
||||||
|
notes = findByTag(notes, block)
|
||||||
|
} else {
|
||||||
|
notes = findByWord(notes, block)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return notes
|
||||||
|
}
|
||||||
|
|
||||||
|
function findByTag (notes, block) {
|
||||||
|
const tag = block.match(/#(.+)/)[1]
|
||||||
|
let 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) {
|
||||||
|
let 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,38 +0,0 @@
|
|||||||
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 () {
|
|
||||||
var version = global.version
|
|
||||||
return (
|
|
||||||
<div className='PreferencesModal sideNavModal 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,170 +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 () {
|
|
||||||
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 sideNavModal modal'>
|
|
||||||
<div className='leftPane'>
|
|
||||||
<div className='modalLabel'>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 tab'>
|
|
||||||
<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 tab'>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>Current password</label>
|
|
||||||
<input type='password' valueLink={this.linkState('password.currentPassword')}/>
|
|
||||||
</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>New password</label>
|
|
||||||
<input type='password' valueLink={this.linkState('password.newPassword')}/>
|
|
||||||
</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>Confirmation</label>
|
|
||||||
<input type='password' 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 PreferencesModal = require('./PreferencesModal')
|
|
||||||
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})
|
|
||||||
},
|
|
||||||
openPreferencesModal: function () {
|
|
||||||
this.openModal(PreferencesModal)
|
|
||||||
},
|
|
||||||
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.openPreferencesModal}><i className='fa fa-gears fa-fw'/> Preferences</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-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>
|
|
||||||
|
|
||||||
<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: 'Unexpected error occured! 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,188 +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({profileFormStatus: 'sending', profileFormError: null}, function () {
|
|
||||||
Hq.updatePlanet(planet.userName, planet.name, this.state.planet)
|
|
||||||
.then(function (res) {
|
|
||||||
var planet = res.body
|
|
||||||
console.log(planet)
|
|
||||||
this.setState({profileFormStatus: 'done'})
|
|
||||||
|
|
||||||
PlanetStore.Actions.update(planet)
|
|
||||||
this.props.close()
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
var newState = {
|
|
||||||
profileFormStatus: 'error'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.status == null) {
|
|
||||||
newState.profileFormError = {message: 'Check your network connection'}
|
|
||||||
return this.setState(newState)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (err.status) {
|
|
||||||
case 403:
|
|
||||||
newState.profileFormError = err.response.body
|
|
||||||
this.setState(newState)
|
|
||||||
break
|
|
||||||
case 422:
|
|
||||||
newState.profileFormError = {message: 'Planet name should be Alphanumeric with _, -'}
|
|
||||||
this.setState(newState)
|
|
||||||
break
|
|
||||||
case 409:
|
|
||||||
newState.profileFormError = {message: 'The entered name already in use'}
|
|
||||||
this.setState(newState)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
newState.profileFormError = {message: 'Undefined error please try again'}
|
|
||||||
this.setState(newState)
|
|
||||||
}
|
|
||||||
}.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 sideNavModal modal'>
|
|
||||||
<div className='leftPane'>
|
|
||||||
<h1 className='modalLabel'>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 tab'>
|
|
||||||
<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.profileFormStatus === 'sending' ? '' : ' hide')}>on Sending...</div>
|
|
||||||
|
|
||||||
<div className={'alertError' + (this.state.profileFormStatus === 'error' ? '' : ' hide')}>{this.state.profileFormError != null ? this.state.profileFormError.message : 'Unexpected error occured! please try again'}</div>
|
|
||||||
|
|
||||||
<div className={'alertSuccess' + (this.state.profileFormStatus === '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 tab'>
|
|
||||||
<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,112 +0,0 @@
|
|||||||
var ipc = require('ipc')
|
|
||||||
var remote = require('remote')
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var ExternalLink = require('../Mixins/ExternalLink')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, ExternalLink, KeyCaster('aboutModal')],
|
|
||||||
propTypes: {
|
|
||||||
close: React.PropTypes.func
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
var keymap = remote.getGlobal('keymap')
|
|
||||||
console.log(keymap)
|
|
||||||
return {
|
|
||||||
currentTab: 'settings',
|
|
||||||
keymap: keymap
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'closeModal':
|
|
||||||
this.props.close()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
activeSettings: function () {
|
|
||||||
this.setState({currentTab: 'settings'})
|
|
||||||
},
|
|
||||||
activeAbout: function () {
|
|
||||||
this.setState({currentTab: 'about'})
|
|
||||||
},
|
|
||||||
saveKeymap: function () {
|
|
||||||
ipc.send('hotkeyUpdated', JSON.stringify(this.state.keymap))
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var content = this.state.currentTab === 'settings' ? this.renderSettingsTab() : this.renderAboutTab()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='PreferencesModal sideNavModal modal'>
|
|
||||||
<div className='leftPane'>
|
|
||||||
<h1 className='modalLabel'>Preferences</h1>
|
|
||||||
<nav className='tabList'>
|
|
||||||
<button onClick={this.activeSettings} className={this.state.currentTab === 'settings' ? 'active' : ''}><i className='fa fa-gear fa-fw'/> Settings</button>
|
|
||||||
<button onClick={this.activeAbout} className={this.state.currentTab === 'about' ? 'active' : ''}><i className='fa fa-info-circle fa-fw'/> About this app</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
<div className='rightPane'>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
renderSettingsTab: function () {
|
|
||||||
return (
|
|
||||||
<div className='settingsTab tab'>
|
|
||||||
<div className='categoryLabel'>Hotkey</div>
|
|
||||||
<div className='formField'>
|
|
||||||
<label>Toggle finder</label>
|
|
||||||
<input valueLink={this.linkState('keymap.toggleFinder')}/>
|
|
||||||
</div>
|
|
||||||
<div className='formConfirm'>
|
|
||||||
<button onClick={this.saveKeymap}>Save</button>
|
|
||||||
</div>
|
|
||||||
<div className='example'>
|
|
||||||
<h3>Example</h3>
|
|
||||||
<ul>
|
|
||||||
<li><code>0</code> to <code>9</code></li>
|
|
||||||
<li><code>A</code> to <code>Z</code></li>
|
|
||||||
<li><code>F1</code> to <code>F24</code></li>
|
|
||||||
<li>Punctuations like <code>~</code>, <code>!</code>, <code>@</code>, <code>#</code>, <code>$</code>, etc.</li>
|
|
||||||
<li><code>Plus</code></li>
|
|
||||||
<li><code>Space</code></li>
|
|
||||||
<li><code>Backspace</code></li>
|
|
||||||
<li><code>Delete</code></li>
|
|
||||||
<li><code>Insert</code></li>
|
|
||||||
<li><code>Return</code> (or <code>Enter</code> as alias)</li>
|
|
||||||
<li><code>Up</code>, <code>Down</code>, <code>Left</code> and <code>Right</code></li>
|
|
||||||
<li><code>Home</code> and <code>End</code></li>
|
|
||||||
<li><code>PageUp</code> and <code>PageDown</code></li>
|
|
||||||
<li><code>Escape</code> (or <code>Esc</code> for short)</li>
|
|
||||||
<li><code>VolumeUp</code>, <code>VolumeDown</code> and <code>VolumeMute</code></li>
|
|
||||||
<li><code>MediaNextTrack</code>, <code>MediaPreviousTrack</code>, <code>MediaStop</code> and <code>MediaPlayPause</code></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
renderAboutTab: function () {
|
|
||||||
var version = global.version
|
|
||||||
return (
|
|
||||||
<div className='aboutTab tab'>
|
|
||||||
<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 onClick={this.openExternal} href='http://boostio.github.io/regulations.html'>Regulation <i className='fa fa-external-link'/></a></li>
|
|
||||||
<li><a onClick={this.openExternal} href='http://boostio.github.io/privacypolicies.html'>Private policy <i className='fa fa-external-link'/></a></li>
|
|
||||||
</ul>
|
|
||||||
</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 sideNavModal modal'>
|
|
||||||
<div className='leftPane'>
|
|
||||||
<div className='modalLabel'>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 tab'>
|
|
||||||
<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 tab'>
|
|
||||||
<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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
var ReactRouter = require('react-router')
|
|
||||||
var RouteHandler = ReactRouter.RouteHandler
|
|
||||||
var State = ReactRouter.State
|
|
||||||
var Navigation = ReactRouter.Navigation
|
|
||||||
|
|
||||||
var AuthFilter = require('../Mixins/AuthFilter')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var HomeNavigator = require('../Components/HomeNavigator')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [AuthFilter.OnlyUser, State, Navigation, KeyCaster('homeContainer')],
|
|
||||||
componentDidMount: function () {
|
|
||||||
if (this.isActive('homeEmpty')) {
|
|
||||||
var user = JSON.parse(localStorage.getItem('currentUser'))
|
|
||||||
if (user.Planets != null && user.Planets.length > 0) {
|
|
||||||
this.transitionTo('planet', {userName: user.name, planetName: user.Planets[0].name})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.transitionTo('userHome', {userName: user.name})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'switchPlanet':
|
|
||||||
this.refs.navigator.switchPlanetByIndex(e.data)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='HomeContainer'>
|
|
||||||
<HomeNavigator ref='navigator'/>
|
|
||||||
<RouteHandler/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
var React = require('react/addons')
|
|
||||||
var ReactRouter = require('react-router')
|
|
||||||
var Link = ReactRouter.Link
|
|
||||||
|
|
||||||
var AuthFilter = require('../Mixins/AuthFilter')
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, ReactRouter.Navigation, AuthFilter.OnlyGuest],
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
user: {},
|
|
||||||
authenticationFailed: false,
|
|
||||||
connectionFailed: false,
|
|
||||||
isSending: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onListen: function (res) {
|
|
||||||
if (res.status === 'failedToLogIn') {
|
|
||||||
if (res.data.status === 401) {
|
|
||||||
// Wrong E-mail or Password
|
|
||||||
this.setState({
|
|
||||||
authenticationFailed: true,
|
|
||||||
connectionFailed: false,
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Connection Failed or Whatever
|
|
||||||
this.setState({
|
|
||||||
authenticationFailed: false,
|
|
||||||
connectionFailed: true,
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSubmit: function (e) {
|
|
||||||
this.setState({
|
|
||||||
authenticationFailed: false,
|
|
||||||
connectionFailed: false,
|
|
||||||
isSending: true
|
|
||||||
}, function () {
|
|
||||||
Hq.login(this.state.user)
|
|
||||||
.then(function (res) {
|
|
||||||
localStorage.setItem('token', res.body.token)
|
|
||||||
localStorage.setItem('currentUser', JSON.stringify(res.body.user))
|
|
||||||
|
|
||||||
this.transitionTo('userHome', {userName: res.body.user.name})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
if (err.status === 401) {
|
|
||||||
this.setState({
|
|
||||||
authenticationFailed: true,
|
|
||||||
connectionFailed: false,
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.setState({
|
|
||||||
authenticationFailed: false,
|
|
||||||
connectionFailed: true,
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='LoginContainer'>
|
|
||||||
<img className='logo' src='resources/favicon-230x230.png'/>
|
|
||||||
|
|
||||||
<nav className='authNavigator text-center'><Link to='login'>Log In</Link> / <Link to='signup'>Sign Up</Link></nav>
|
|
||||||
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
<div className='form-group'>
|
|
||||||
<input className='stripInput' valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
|
||||||
</div>
|
|
||||||
<div className='form-group'>
|
|
||||||
<input className='stripInput' valueLink={this.linkState('user.password')} onChange={this.handleChange} type='password' placeholder='Password'/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.state.isSending ? (
|
|
||||||
<p className='alertInfo'>Logging in...</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{this.state.connectionFailed ? (
|
|
||||||
<p className='alertError'>Please try again.</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{this.state.authenticationFailed ? (
|
|
||||||
<p className='alertError'>Wrong E-mail or Password.</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<button className='logInButton' type='submit'>Log In</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
|
|
||||||
var ipc = require('ipc')
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
var ReactRouter = require('react-router')
|
|
||||||
var RouteHandler = ReactRouter.RouteHandler
|
|
||||||
var Navigation = ReactRouter.Navigation
|
|
||||||
var State = ReactRouter.State
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var Modal = require('../Mixins/Modal')
|
|
||||||
|
|
||||||
var UserStore = require('../Stores/UserStore')
|
|
||||||
|
|
||||||
var ContactModal = require('../Components/ContactModal')
|
|
||||||
|
|
||||||
function fetchPlanet (userName, planetName) {
|
|
||||||
Hq.fetchPlanet(userName, planetName)
|
|
||||||
.then(function (res) {
|
|
||||||
var planet = res.body
|
|
||||||
|
|
||||||
planet.Codes.forEach(function (code) {
|
|
||||||
code.type = 'code'
|
|
||||||
})
|
|
||||||
|
|
||||||
planet.Notes.forEach(function (note) {
|
|
||||||
note.type = 'note'
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('planet-' + planet.id + ' fetched!')
|
|
||||||
localStorage.setItem('planet-' + planet.id, JSON.stringify(planet))
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [State, Navigation, Modal],
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
updateAvailable: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
ipc.on('update-available', function (message) {
|
|
||||||
this.setState({updateAvailable: true})
|
|
||||||
}.bind(this))
|
|
||||||
|
|
||||||
if (this.isActive('root')) {
|
|
||||||
if (localStorage.getItem('currentUser') == null) {
|
|
||||||
this.transitionTo('login')
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
this.transitionTo('home')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Hq.getUser()
|
|
||||||
.then(function (res) {
|
|
||||||
var user = res.body
|
|
||||||
localStorage.setItem('currentUser', JSON.stringify(user))
|
|
||||||
UserStore.Actions.update(user)
|
|
||||||
|
|
||||||
user.Planets.forEach(function (planet) {
|
|
||||||
fetchPlanet(planet.userName, planet.name)
|
|
||||||
})
|
|
||||||
user.Teams.forEach(function (team) {
|
|
||||||
team.Planets.forEach(function (planet) {
|
|
||||||
fetchPlanet(planet.userName, planet.name)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
if (err.status === 401) {
|
|
||||||
console.log('Not logged in yet')
|
|
||||||
localStorage.removeItem('currentUser')
|
|
||||||
this.transitionTo('login')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.error(err)
|
|
||||||
}.bind(this))
|
|
||||||
},
|
|
||||||
updateApp: function () {
|
|
||||||
ipc.send('update-app', 'Deal with it.')
|
|
||||||
},
|
|
||||||
openContactModal: function () {
|
|
||||||
this.openModal(ContactModal)
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='Main'>
|
|
||||||
{this.state.updateAvailable ? (
|
|
||||||
<button onClick={this.updateApp} className='appUpdateButton'><i className='fa fa-cloud-download'/> Update available!</button>
|
|
||||||
) : null}
|
|
||||||
<button onClick={this.openContactModal} className='contactButton'>
|
|
||||||
<i className='fa fa-paper-plane-o'/>
|
|
||||||
<div className='tooltip'>Contact us</div>
|
|
||||||
</button>
|
|
||||||
<RouteHandler/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,415 +0,0 @@
|
|||||||
/* global localStorage*/
|
|
||||||
'strict'
|
|
||||||
var React = require('react/addons')
|
|
||||||
var ReactRouter = require('react-router')
|
|
||||||
var Reflux = require('reflux')
|
|
||||||
|
|
||||||
var PlanetHeader = require('../Components/PlanetHeader')
|
|
||||||
var PlanetNavigator = require('../Components/PlanetNavigator')
|
|
||||||
var PlanetArticleList = require('../Components/PlanetArticleList')
|
|
||||||
var PlanetArticleDetail = require('../Components/PlanetArticleDetail')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var Modal = require('../Mixins/Modal')
|
|
||||||
var ArticleFilter = require('../Mixins/ArticleFilter')
|
|
||||||
var Helper = require('../Mixins/Helper')
|
|
||||||
var KeyCaster = require('../Mixins/KeyCaster')
|
|
||||||
|
|
||||||
var UserStore = require('../Stores/UserStore')
|
|
||||||
var PlanetStore = require('../Stores/PlanetStore')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [ReactRouter.Navigation, ReactRouter.State, Modal, Reflux.listenTo(UserStore, 'onUserChange'), Reflux.listenTo(PlanetStore, 'onPlanetChange'), ArticleFilter, Helper, KeyCaster('planetContainer')],
|
|
||||||
propTypes: {
|
|
||||||
params: React.PropTypes.object,
|
|
||||||
planetName: React.PropTypes.string
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
currentUser: JSON.parse(localStorage.getItem('currentUser')),
|
|
||||||
planet: null,
|
|
||||||
search: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
this.fetchPlanet(this.props.params.userName, this.props.params.planetName)
|
|
||||||
},
|
|
||||||
componentDidUpdate: function () {
|
|
||||||
if (this.isActive('planetHome') && this.refs.list != null && this.refs.list.props.articles.length > 0) {
|
|
||||||
var article = this.refs.list.props.articles[0]
|
|
||||||
var planet = this.state.planet
|
|
||||||
switch (article.type) {
|
|
||||||
case 'code':
|
|
||||||
this.transitionTo('codes', {userName: planet.userName, planetName: planet.name, localId: article.localId})
|
|
||||||
break
|
|
||||||
case 'note':
|
|
||||||
this.transitionTo('notes', {userName: planet.userName, planetName: planet.name, localId: article.localId})
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentWillReceiveProps: function (nextProps) {
|
|
||||||
if (this.state.planet == null) {
|
|
||||||
this.fetchPlanet(nextProps.params.userName, nextProps.params.planetName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextProps.params.userName !== this.state.planet.userName || nextProps.params.planetName !== this.state.planet.name) {
|
|
||||||
this.setState({
|
|
||||||
planet: null
|
|
||||||
}, function () {
|
|
||||||
this.fetchPlanet(nextProps.params.userName, nextProps.params.planetName)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyCast: function (e) {
|
|
||||||
switch (e.status) {
|
|
||||||
case 'openLaunchModal':
|
|
||||||
this.refs.navigator.openLaunchModal()
|
|
||||||
break
|
|
||||||
case 'selectNextArticle':
|
|
||||||
this.selectNextArticle()
|
|
||||||
break
|
|
||||||
case 'selectPriorArticle':
|
|
||||||
this.selectPriorArticle()
|
|
||||||
break
|
|
||||||
case 'toggleFocusSearchInput':
|
|
||||||
this.toggleFocusSearchInput()
|
|
||||||
break
|
|
||||||
case 'openEditModal':
|
|
||||||
this.refs.detail.openEditModal()
|
|
||||||
break
|
|
||||||
case 'openDeleteModal':
|
|
||||||
this.refs.detail.openDeleteModal()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPlanetChange: function (res) {
|
|
||||||
if (this.state.planet == null) return
|
|
||||||
|
|
||||||
var planet, code, note, articleIndex, articlesCount
|
|
||||||
switch (res.status) {
|
|
||||||
case 'updated':
|
|
||||||
planet = res.data
|
|
||||||
if (this.state.planet.id === planet.id) {
|
|
||||||
if (this.state.planet.name === planet.name) {
|
|
||||||
this.setState({planet: planet})
|
|
||||||
} else {
|
|
||||||
this.transitionTo('planetHome', {userName: planet.userName, planetName: planet.name})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'destroyed':
|
|
||||||
planet = res.data
|
|
||||||
if (this.state.planet.id === planet.id) {
|
|
||||||
this.transitionTo('userHome', {userName: this.state.planet.userName})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'codeUpdated':
|
|
||||||
code = res.data
|
|
||||||
if (code.PlanetId === this.state.planet.id) {
|
|
||||||
this.state.planet.Codes = this.updateItemToTargetArray(code, this.state.planet.Codes)
|
|
||||||
|
|
||||||
this.setState({planet: this.state.planet})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'noteUpdated':
|
|
||||||
note = res.data
|
|
||||||
if (note.PlanetId === this.state.planet.id) {
|
|
||||||
this.state.planet.Notes = this.updateItemToTargetArray(note, this.state.planet.Notes)
|
|
||||||
|
|
||||||
this.setState({planet: this.state.planet})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'codeDestroyed':
|
|
||||||
code = res.data
|
|
||||||
if (code.PlanetId === this.state.planet.id) {
|
|
||||||
this.state.planet.Codes = this.deleteItemFromTargetArray(code, this.state.planet.Codes)
|
|
||||||
|
|
||||||
if (this.refs.detail.props.article != null && this.refs.detail.props.article.type === code.type && this.refs.detail.props.article.localId === code.localId) {
|
|
||||||
articleIndex = this.getFilteredIndexOfCurrentArticle()
|
|
||||||
articlesCount = this.refs.list.props.articles.length
|
|
||||||
|
|
||||||
this.setState({planet: this.state.planet}, function () {
|
|
||||||
if (articlesCount > 1) {
|
|
||||||
if (articleIndex > 0) {
|
|
||||||
this.selectArticleByListIndex(articleIndex - 1)
|
|
||||||
} else {
|
|
||||||
this.selectArticleByListIndex(articleIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({planet: this.state.planet})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'noteDestroyed':
|
|
||||||
note = res.data
|
|
||||||
if (note.PlanetId === this.state.planet.id) {
|
|
||||||
this.state.planet.Notes = this.deleteItemFromTargetArray(note, this.state.planet.Notes)
|
|
||||||
|
|
||||||
if (this.refs.detail.props.article != null && this.refs.detail.props.article.type === note.type && this.refs.detail.props.article.localId === note.localId) {
|
|
||||||
articleIndex = this.getFilteredIndexOfCurrentArticle()
|
|
||||||
articlesCount = this.refs.list.props.articles.length
|
|
||||||
|
|
||||||
this.setState({planet: this.state.planet}, function () {
|
|
||||||
if (articlesCount > 1) {
|
|
||||||
if (articleIndex > 0) {
|
|
||||||
this.selectArticleByListIndex(articleIndex - 1)
|
|
||||||
} else {
|
|
||||||
this.selectArticleByListIndex(articleIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({planet: this.state.planet})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onUserChange: function () {
|
|
||||||
|
|
||||||
},
|
|
||||||
fetchPlanet: function (userName, planetName) {
|
|
||||||
if (userName == null) userName = this.props.params.userName
|
|
||||||
if (planetName == null) planetName = this.props.params.planetName
|
|
||||||
|
|
||||||
Hq.fetchPlanet(userName, planetName)
|
|
||||||
.then(function (res) {
|
|
||||||
var planet = res.body
|
|
||||||
|
|
||||||
planet.Codes.forEach(function (code) {
|
|
||||||
code.type = 'code'
|
|
||||||
})
|
|
||||||
|
|
||||||
planet.Notes.forEach(function (note) {
|
|
||||||
note.type = 'note'
|
|
||||||
})
|
|
||||||
|
|
||||||
localStorage.setItem('planet-' + planet.id, JSON.stringify(planet))
|
|
||||||
|
|
||||||
this.setState({planet: planet})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getFilteredIndexOfCurrentArticle: function () {
|
|
||||||
var params = this.props.params
|
|
||||||
var index = 0
|
|
||||||
|
|
||||||
if (this.isActive('codes')) {
|
|
||||||
this.refs.list.props.articles.some(function (_article, _index) {
|
|
||||||
if (_article.type === 'code' && _article.localId === parseInt(params.localId, 10)) {
|
|
||||||
index = _index
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else if (this.isActive('notes')) {
|
|
||||||
this.refs.list.props.articles.some(function (_article, _index) {
|
|
||||||
if (_article.type === 'note' && _article.localId === parseInt(params.localId, 10)) {
|
|
||||||
index = _index
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return index
|
|
||||||
},
|
|
||||||
selectArticleByListIndex: function (index) {
|
|
||||||
var article = this.refs.list.props.articles[index]
|
|
||||||
var params = this.props.params
|
|
||||||
|
|
||||||
if (article == null) {
|
|
||||||
this.transitionTo('planetHome', params)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var listElement = this.refs.list.refs.articles.getDOMNode()
|
|
||||||
var articleElement = listElement.querySelectorAll('li')[index]
|
|
||||||
|
|
||||||
var overflowBelow = listElement.clientHeight + listElement.scrollTop < articleElement.offsetTop + articleElement.clientHeight
|
|
||||||
if (overflowBelow) {
|
|
||||||
listElement.scrollTop = articleElement.offsetTop + articleElement.clientHeight - listElement.clientHeight
|
|
||||||
}
|
|
||||||
var overflowAbove = listElement.scrollTop > articleElement.offsetTop
|
|
||||||
if (overflowAbove) {
|
|
||||||
listElement.scrollTop = articleElement.offsetTop
|
|
||||||
}
|
|
||||||
|
|
||||||
if (article.type === 'code') {
|
|
||||||
params.localId = article.localId
|
|
||||||
this.transitionTo('codes', params)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (article.type === 'note') {
|
|
||||||
params.localId = article.localId
|
|
||||||
this.transitionTo('notes', params)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectNextArticle: function () {
|
|
||||||
if (this.state.planet == null) return
|
|
||||||
|
|
||||||
var index = this.getFilteredIndexOfCurrentArticle()
|
|
||||||
|
|
||||||
if (index < this.refs.list.props.articles.length - 1) {
|
|
||||||
this.selectArticleByListIndex(index + 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectPriorArticle: function () {
|
|
||||||
if (this.state.planet == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var index = this.getFilteredIndexOfCurrentArticle()
|
|
||||||
|
|
||||||
if (index > 0) {
|
|
||||||
this.selectArticleByListIndex(index - 1)
|
|
||||||
} else {
|
|
||||||
React.findDOMNode(this.refs.header.refs.search).focus()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toggleFocusSearchInput: function () {
|
|
||||||
var search = React.findDOMNode(this.refs.header.refs.search)
|
|
||||||
if (document.activeElement === search) {
|
|
||||||
React.findDOMNode(this.refs.header.refs.search).blur()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
React.findDOMNode(this.refs.header.refs.search).focus()
|
|
||||||
},
|
|
||||||
handleSearchChange: function (e) {
|
|
||||||
this.setState({search: e.target.value}, function () {
|
|
||||||
this.selectArticleByListIndex(0)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
showAll: function () {
|
|
||||||
this.setState({search: ''})
|
|
||||||
},
|
|
||||||
toggleCodeFilter: function () {
|
|
||||||
var keywords = typeof this.state.search === 'string' ? this.state.search.split(' ') : []
|
|
||||||
|
|
||||||
var usingCodeFilter = false
|
|
||||||
var usingNoteFilter = false
|
|
||||||
keywords = keywords.filter(function (keyword) {
|
|
||||||
if (keyword === '$n') {
|
|
||||||
usingNoteFilter = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (keyword === '$c') usingCodeFilter = true
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (usingCodeFilter && !usingNoteFilter) {
|
|
||||||
keywords = keywords.filter(function (keyword) {
|
|
||||||
return keyword !== '$c'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!usingCodeFilter) {
|
|
||||||
keywords.unshift('$c')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({search: keywords.join(' ')}, function () {
|
|
||||||
this.selectArticleByListIndex(0)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
toggleNoteFilter: function () {
|
|
||||||
var keywords = typeof this.state.search === 'string' ? this.state.search.split(' ') : []
|
|
||||||
|
|
||||||
var usingCodeFilter = false
|
|
||||||
var usingNoteFilter = false
|
|
||||||
keywords = keywords.filter(function (keyword) {
|
|
||||||
if (keyword === '$c') {
|
|
||||||
usingCodeFilter = true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (keyword === '$n') usingNoteFilter = true
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if (usingNoteFilter && !usingCodeFilter) {
|
|
||||||
keywords = keywords.filter(function (keyword) {
|
|
||||||
return keyword !== '$n'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!usingNoteFilter) {
|
|
||||||
keywords.unshift('$n')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({search: keywords.join(' ')}, function () {
|
|
||||||
this.selectArticleByListIndex(0)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
applyTagFilter: function (tag) {
|
|
||||||
return function () {
|
|
||||||
this.setState({search: '#' + tag})
|
|
||||||
}.bind(this)
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
if (this.state.planet == null) return (<div/>)
|
|
||||||
|
|
||||||
var localId = parseInt(this.props.params.localId, 10)
|
|
||||||
|
|
||||||
var codes = this.state.planet.Codes
|
|
||||||
var notes = this.state.planet.Notes
|
|
||||||
|
|
||||||
var article
|
|
||||||
if (this.isActive('codes')) {
|
|
||||||
codes.some(function (_article) {
|
|
||||||
if (localId === _article.localId) {
|
|
||||||
article = _article
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
} else if (this.isActive('notes')) {
|
|
||||||
notes.some(function (_article) {
|
|
||||||
if (localId === _article.localId) {
|
|
||||||
article = _article
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var articles = codes.concat(notes)
|
|
||||||
|
|
||||||
var filteredArticles = this.searchArticle(this.state.search, articles)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='PlanetContainer'>
|
|
||||||
<PlanetHeader
|
|
||||||
ref='header'
|
|
||||||
search={this.state.search}
|
|
||||||
fetchPlanet={this.fetchPlanet}
|
|
||||||
onSearchChange={this.handleSearchChange}
|
|
||||||
currentPlanet={this.state.planet}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PlanetNavigator
|
|
||||||
ref='navigator'
|
|
||||||
search={this.state.search}
|
|
||||||
showAll={this.showAll}
|
|
||||||
toggleCodeFilter={this.toggleCodeFilter}
|
|
||||||
toggleNoteFilter={this.toggleNoteFilter}
|
|
||||||
planet={this.state.planet}
|
|
||||||
currentUser={this.state.currentUser}/>
|
|
||||||
|
|
||||||
<PlanetArticleList showOnlyWithTag={this.applyTagFilter} ref='list' articles={filteredArticles}/>
|
|
||||||
|
|
||||||
<PlanetArticleDetail
|
|
||||||
ref='detail'
|
|
||||||
article={article}
|
|
||||||
planet={this.state.planet}
|
|
||||||
showOnlyWithTag={this.applyTagFilter}/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
var ReactRouter = require('react-router')
|
|
||||||
var Link = ReactRouter.Link
|
|
||||||
|
|
||||||
var AuthFilter = require('../Mixins/AuthFilter')
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var ExternalLink = require('../Mixins/ExternalLink')
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, ReactRouter.Navigation, AuthFilter.OnlyGuest, ExternalLink],
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
user: {},
|
|
||||||
connectionFailed: false,
|
|
||||||
emailConflicted: false,
|
|
||||||
nameConflicted: false,
|
|
||||||
validationFailed: false,
|
|
||||||
isSending: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleSubmit: function (e) {
|
|
||||||
this.setState({
|
|
||||||
connectionFailed: false,
|
|
||||||
emailConflicted: false,
|
|
||||||
nameConflicted: false,
|
|
||||||
validationFailed: false,
|
|
||||||
isSending: true
|
|
||||||
}, function () {
|
|
||||||
Hq.signup(this.state.user)
|
|
||||||
.then(function (res) {
|
|
||||||
localStorage.setItem('token', res.body.token)
|
|
||||||
localStorage.setItem('currentUser', JSON.stringify(res.body.user))
|
|
||||||
|
|
||||||
this.transitionTo('userHome', {userName: res.body.user.name})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
var res = err.response
|
|
||||||
if (err.status === 409) {
|
|
||||||
// Confliction
|
|
||||||
var emailConflicted = res.body.errors[0].path === 'email'
|
|
||||||
var nameConflicted = res.body.errors[0].path === 'name'
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
connectionFailed: false,
|
|
||||||
emailConflicted: emailConflicted,
|
|
||||||
nameConflicted: nameConflicted,
|
|
||||||
validationFailed: false,
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err.status === 422) {
|
|
||||||
// Validation Failed
|
|
||||||
this.setState({
|
|
||||||
connectionFailed: false,
|
|
||||||
emailConflicted: false,
|
|
||||||
nameConflicted: false,
|
|
||||||
validationFailed: {
|
|
||||||
errors: res.body.errors.map(function (error) {
|
|
||||||
return error.path
|
|
||||||
})
|
|
||||||
},
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connection Failed or Whatever
|
|
||||||
this.setState({
|
|
||||||
connectionFailed: true,
|
|
||||||
emailConflicted: false,
|
|
||||||
nameConflicted: false,
|
|
||||||
validationFailed: false,
|
|
||||||
isSending: false
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}.bind(this))
|
|
||||||
})
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
return (
|
|
||||||
<div className='SignupContainer'>
|
|
||||||
<img className='logo' src='resources/favicon-230x230.png'/>
|
|
||||||
|
|
||||||
<nav className='authNavigator text-center'><Link to='login'>Log In</Link> / <Link to='signup'>Sign Up</Link></nav>
|
|
||||||
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
<div className='form-group'>
|
|
||||||
<input className='stripInput' valueLink={this.linkState('user.email')} type='text' placeholder='E-mail'/>
|
|
||||||
</div>
|
|
||||||
<div className='form-group'>
|
|
||||||
<input className='stripInput' valueLink={this.linkState('user.password')} type='password' placeholder='Password'/>
|
|
||||||
</div>
|
|
||||||
<div className='form-group'>
|
|
||||||
<input className='stripInput' valueLink={this.linkState('user.name')} type='text' placeholder='name'/>
|
|
||||||
</div>
|
|
||||||
<div className='form-group'>
|
|
||||||
<input className='stripInput' valueLink={this.linkState('user.profileName')} type='text' placeholder='Profile name'/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{this.state.isSending ? (
|
|
||||||
<p className='alertInfo'>Signing up...</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{this.state.connectionFailed ? (
|
|
||||||
<p className='alertError'>Please try again.</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{this.state.emailConflicted ? (
|
|
||||||
<p className='alertError'>E-mail already exists.</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{this.state.nameConflicted ? (
|
|
||||||
<p className='alertError'>Username already exists.</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{this.state.validationFailed ? (
|
|
||||||
<p className='alertError'>Please fill every field correctly: {this.state.validationFailed.errors.join(', ')}</p>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className='form-group'>
|
|
||||||
<button className='logInButton' type='submit'>Sign Up</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p className='alert'>会員登録することで、<a onClick={this.openExternal} href='http://boostio.github.io/regulations.html'>当サイトの利用規約</a>及び<a onClick={this.openExternal} href='http://boostio.github.io/privacypolicies.html'>Cookieの使用を含むデータに関するポリシー</a>に同意するものとします。</p>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -1,367 +0,0 @@
|
|||||||
/* global localStorage */
|
|
||||||
|
|
||||||
var React = require('react/addons')
|
|
||||||
var ReactRouter = require('react-router')
|
|
||||||
var Navigation = ReactRouter.Navigation
|
|
||||||
var State = ReactRouter.State
|
|
||||||
var RouteHandler = ReactRouter.RouteHandler
|
|
||||||
var Link = ReactRouter.Link
|
|
||||||
var Reflux = require('reflux')
|
|
||||||
|
|
||||||
var LinkedState = require('../Mixins/LinkedState')
|
|
||||||
var Modal = require('../Mixins/Modal')
|
|
||||||
var Helper = require('../Mixins/Helper')
|
|
||||||
|
|
||||||
var Hq = require('../Services/Hq')
|
|
||||||
|
|
||||||
var ProfileImage = require('../Components/ProfileImage')
|
|
||||||
var EditProfileModal = require('../Components/EditProfileModal')
|
|
||||||
var TeamSettingsModal = require('../Components/TeamSettingsModal')
|
|
||||||
var PlanetCreateModal = require('../Components/PlanetCreateModal')
|
|
||||||
var AddMemberModal = require('../Components/AddMemberModal')
|
|
||||||
var TeamCreateModal = require('../Components/TeamCreateModal')
|
|
||||||
|
|
||||||
var UserStore = require('../Stores/UserStore')
|
|
||||||
var PlanetStore = require('../Stores/PlanetStore')
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
|
||||||
mixins: [LinkedState, State, Navigation, Modal, Reflux.listenTo(UserStore, 'onUserChange'), Reflux.listenTo(PlanetStore, 'onPlanetChange'), Helper],
|
|
||||||
propTypes: {
|
|
||||||
params: React.PropTypes.shape({
|
|
||||||
userName: React.PropTypes.string,
|
|
||||||
planetName: React.PropTypes.string
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
return {
|
|
||||||
user: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
componentDidMount: function () {
|
|
||||||
this.fetchUser()
|
|
||||||
},
|
|
||||||
componentWillReceiveProps: function (nextProps) {
|
|
||||||
if (this.state.user == null) {
|
|
||||||
this.fetchUser(nextProps.params.userName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextProps.params.userName !== this.state.user.name) {
|
|
||||||
this.setState({
|
|
||||||
user: null
|
|
||||||
}, function () {
|
|
||||||
this.fetchUser(nextProps.params.userName)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onUserChange: function (res) {
|
|
||||||
if (this.state.user == null) return
|
|
||||||
|
|
||||||
var member
|
|
||||||
switch (res.status) {
|
|
||||||
case 'userUpdated':
|
|
||||||
if (this.state.user.id === res.data.id) {
|
|
||||||
this.setState({user: res.data})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'memberAdded':
|
|
||||||
member = res.data
|
|
||||||
if (this.state.user.userType === 'team' && member.TeamMember.TeamId === this.state.user.id) {
|
|
||||||
this.state.user.Members = this.updateItemToTargetArray(member, this.state.user.Members)
|
|
||||||
|
|
||||||
this.setState({user: this.state.user})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'memberRemoved':
|
|
||||||
member = res.data
|
|
||||||
if (this.state.user.userType === 'team' && member.TeamMember.TeamId === this.state.user.id) {
|
|
||||||
this.state.user.Members = this.deleteItemFromTargetArray(member, this.state.user.Members)
|
|
||||||
|
|
||||||
this.setState({user: this.state.user})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPlanetChange: function (res) {
|
|
||||||
if (this.state.user == null) return
|
|
||||||
|
|
||||||
var currentUser, planet, isOwner, team
|
|
||||||
switch (res.status) {
|
|
||||||
case 'updated':
|
|
||||||
// if state.user is currentUser, planet will be fetched by UserStore
|
|
||||||
currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
|
||||||
if (currentUser.id === this.state.user.id) return
|
|
||||||
|
|
||||||
planet = res.data
|
|
||||||
isOwner = planet.Owner.id === this.state.user.id
|
|
||||||
if (isOwner) {
|
|
||||||
this.state.user.Planets = this.updateItemToTargetArray(planet, this.state.user.Planets)
|
|
||||||
this.setState({user: this.state.user})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// check if team of user has this planet
|
|
||||||
team = null
|
|
||||||
this.state.user.userType !== 'team' && this.state.user.Teams.some(function (_team) {
|
|
||||||
if (planet.Owner.id === _team.id) {
|
|
||||||
team = _team
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
if (team != null) {
|
|
||||||
team.Planets = this.updateItemToTargetArray(planet, team.Planets)
|
|
||||||
this.setState({user: this.state.user})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
break
|
|
||||||
case 'destroyed':
|
|
||||||
// if state.user is currentUser, planet will be fetched by UserStore
|
|
||||||
currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
|
||||||
if (currentUser.id === this.state.user.id) return
|
|
||||||
|
|
||||||
planet = res.data
|
|
||||||
isOwner = planet.Owner.id === this.state.user.id
|
|
||||||
if (isOwner) {
|
|
||||||
this.state.user.Planets = this.deleteItemFromTargetArray(planet, this.state.user.Planets)
|
|
||||||
this.setState({user: this.state.user})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// check if team of user has this planet
|
|
||||||
team = null
|
|
||||||
this.state.user.userType !== 'team' && this.state.user.Teams.some(function (_team) {
|
|
||||||
if (planet.Owner.id === _team.id) {
|
|
||||||
team = _team
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
if (team != null) {
|
|
||||||
team.Planets = this.deleteItemFromTargetArray(planet, team.Planets)
|
|
||||||
this.setState({user: this.state.user})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchUser: function (userName) {
|
|
||||||
if (userName == null) userName = this.props.params.userName
|
|
||||||
|
|
||||||
Hq.fetchUser(userName)
|
|
||||||
.then(function (res) {
|
|
||||||
this.setState({user: res.body})
|
|
||||||
}.bind(this))
|
|
||||||
.catch(function (err) {
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
openEditProfileModal: function () {
|
|
||||||
this.openModal(EditProfileModal, {user: this.state.user})
|
|
||||||
},
|
|
||||||
openTeamSettingsModal: function () {
|
|
||||||
this.openModal(TeamSettingsModal, {team: this.state.user})
|
|
||||||
},
|
|
||||||
openAddUserModal: function () {
|
|
||||||
this.openModal(AddMemberModal, {team: this.state.user})
|
|
||||||
},
|
|
||||||
openTeamCreateModal: function () {
|
|
||||||
this.openModal(TeamCreateModal, {user: this.state.user})
|
|
||||||
},
|
|
||||||
openPlanetCreateModalWithOwnerName: function (name) {
|
|
||||||
return function () {
|
|
||||||
this.openModal(PlanetCreateModal, {ownerName: name})
|
|
||||||
}.bind(this)
|
|
||||||
},
|
|
||||||
render: function () {
|
|
||||||
var user = this.state.user
|
|
||||||
|
|
||||||
var currentUser = JSON.parse(localStorage.getItem('currentUser'))
|
|
||||||
|
|
||||||
if (this.isActive('userHome')) {
|
|
||||||
if (user == null) {
|
|
||||||
return (
|
|
||||||
<div className='UserContainer'>
|
|
||||||
User Loading...
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
} else if (user.userType === 'team') {
|
|
||||||
return this.renderTeamHome(currentUser)
|
|
||||||
} else {
|
|
||||||
return this.renderUserHome(currentUser)
|
|
||||||
}
|
|
||||||
} else if (this.isActive('planet') && user != null && user.userType === 'team') {
|
|
||||||
var members = user.Members.map(function (member) {
|
|
||||||
return (
|
|
||||||
<li key={'user-' + member.id}><Link to='userHome' params={{userName: member.name}}>
|
|
||||||
<ProfileImage className='memberImage' size='22' email={member.email}/>
|
|
||||||
<div className='memberInfo'>
|
|
||||||
<div className='memberProfileName'>{member.profileName}</div>
|
|
||||||
<div className='memberName'>@{member.name}</div>
|
|
||||||
</div>
|
|
||||||
</Link></li>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<div className='UserContainer'>
|
|
||||||
<RouteHandler/>
|
|
||||||
<div className='memberPopup'>
|
|
||||||
<div className='label'>Members</div>
|
|
||||||
<ul className='members'>
|
|
||||||
{members}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<div className='UserContainer'>
|
|
||||||
<RouteHandler/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
renderTeamHome: function (currentUser) {
|
|
||||||
var user = this.state.user
|
|
||||||
|
|
||||||
var isOwner = user.Members == null ? false : user.Members.some(function (member) {
|
|
||||||
return member.id === currentUser.id && member.TeamMember.role === 'owner'
|
|
||||||
})
|
|
||||||
|
|
||||||
var userPlanets = user.Planets.map(function (planet) {
|
|
||||||
return (
|
|
||||||
<li key={'planet-' + planet.id}>
|
|
||||||
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>{planet.userName}/{planet.name}</Link>
|
|
||||||
{!planet.public ? (<i className='fa fa-lock'/>) : null}
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
var members = user.Members == null ? [] : user.Members.map(function (member) {
|
|
||||||
return (
|
|
||||||
<li key={'user-' + member.id}>
|
|
||||||
<Link to='userHome' params={{userName: member.name}}>
|
|
||||||
<ProfileImage size='22' className='memberImage' email={member.email}/>
|
|
||||||
<div className='memberInfo'>
|
|
||||||
<div className='memberProfileName'>{member.profileName} <span className='memberRole'>({member.TeamMember.role})</span></div>
|
|
||||||
<div className='memberName'>@{member.name}</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
<div className='role'></div>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<div className='UserContainer'>
|
|
||||||
<div className='userProfile'>
|
|
||||||
<ProfileImage className='userPhoto' size='75' email={user.email}/>
|
|
||||||
<div className='userInfo'>
|
|
||||||
<div className='userProfileName'>{user.profileName}</div>
|
|
||||||
<div className='userName'>{user.name}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isOwner ? (<button onClick={this.openTeamSettingsModal} className='editProfileButton'>Team settings</button>) : null}
|
|
||||||
</div>
|
|
||||||
<div className='memberList'>
|
|
||||||
<div className='memberLabel'>{members.length} {members.length > 1 ? 'Members' : 'Member'}</div>
|
|
||||||
<ul className='members'>
|
|
||||||
{members}
|
|
||||||
{isOwner ? (<li><button onClick={this.openAddUserModal} className='addMemberButton'><i className='fa fa-plus-square-o'/> add Member</button></li>) : null}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className='planetList'>
|
|
||||||
<div className='planetLabel'>{userPlanets.length} {userPlanets.length > 0 ? 'Planets' : 'Planet'}</div>
|
|
||||||
<div className='planetGroup'>
|
|
||||||
<ul className='planets'>
|
|
||||||
{userPlanets}
|
|
||||||
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(user.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
renderUserHome: function (currentUser) {
|
|
||||||
var user = this.state.user
|
|
||||||
|
|
||||||
var isOwner = currentUser.id === user.id
|
|
||||||
|
|
||||||
var userPlanets = user.Planets.map(function (planet) {
|
|
||||||
return (
|
|
||||||
<li key={'planet-' + planet.id}>
|
|
||||||
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>{planet.userName}/{planet.name}</Link>
|
|
||||||
{!planet.public ? (<i className='fa fa-lock'/>) : null}
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
var teams = user.Teams == null ? [] : user.Teams.map(function (team) {
|
|
||||||
return (
|
|
||||||
<li key={'user-' + team.id}>
|
|
||||||
<Link to='userHome' params={{userName: team.name}}>
|
|
||||||
<div className='teamInfo'>
|
|
||||||
<div className='teamProfileName'>{team.profileName}</div>
|
|
||||||
<div className='teamName'>@{team.name}</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
var teamPlanets = user.Teams == null ? [] : user.Teams.map(function (team) {
|
|
||||||
var planets = (team.Planets == null ? [] : team.Planets).map(function (planet) {
|
|
||||||
return (
|
|
||||||
<li key={'planet-' + planet.id}>
|
|
||||||
<Link to='planet' params={{userName: planet.userName, planetName: planet.name}}>{planet.userName}/{planet.name}</Link>
|
|
||||||
{!planet.public ? (<i className='fa fa-lock'/>) : null}
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<div key={'user-' + team.id} className='planetGroup'>
|
|
||||||
<div className='planetGroupLabel'>{team.profileName} <small>@{team.name}</small></div>
|
|
||||||
<ul className='planets'>
|
|
||||||
{planets}
|
|
||||||
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(team.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}.bind(this))
|
|
||||||
|
|
||||||
var planetCount = userPlanets.length + user.Teams.reduce(function (sum, team) {
|
|
||||||
return sum + (team.Planets != null ? team.Planets.length : 0)
|
|
||||||
}, 0)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='UserContainer'>
|
|
||||||
<div className='userProfile'>
|
|
||||||
<ProfileImage className='userPhoto' size='75' email={user.email}/>
|
|
||||||
<div className='userInfo'>
|
|
||||||
<div className='userProfileName'>{user.profileName}</div>
|
|
||||||
<div className='userName'>{user.name}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isOwner ? (
|
|
||||||
<button onClick={this.openEditProfileModal} className='editProfileButton'>Edit profile</button>) : null}
|
|
||||||
</div>
|
|
||||||
<div className='teamList'>
|
|
||||||
<div className='teamLabel'>{teams.length} {teams.length > 1 ? 'Teams' : 'Team'}</div>
|
|
||||||
<ul className='teams'>
|
|
||||||
{teams}
|
|
||||||
{isOwner ? (<li><button onClick={this.openTeamCreateModal} className='createTeamButton'><i className='fa fa-plus-square-o'/> Create new team</button></li>) : null}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div className='planetList'>
|
|
||||||
<div className='planetLabel'>{planetCount} {planetCount > 1 ? 'Planets' : 'Planet'}</div>
|
|
||||||
<div className='planetGroup'>
|
|
||||||
<div className='planetGroupLabel'>{user.profileName} <small>@{user.name}</small></div>
|
|
||||||
<ul className='planets'>
|
|
||||||
{userPlanets}
|
|
||||||
{isOwner ? (<li><button onClick={this.openPlanetCreateModalWithOwnerName(user.name)} className='createPlanetButton'><i className='fa fa-plus-square-o'/> Create new planet</button></li>) : null}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{teamPlanets}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
20
browser/main/Detail/Detail.styl
Normal file
20
browser/main/Detail/Detail.styl
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.root
|
||||||
|
absolute top bottom right
|
||||||
|
|
||||||
|
.empty
|
||||||
|
height 320px
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
|
||||||
|
.empty-message
|
||||||
|
width 100%
|
||||||
|
font-size 42px
|
||||||
|
line-height 72px
|
||||||
|
text-align center
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
background-color $ui-dark-backgroundColor
|
||||||
|
.empty-message
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user