mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
Compare commits
1345 Commits
v0.11.6
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6315d1c38 | ||
|
|
fae91255f9 | ||
|
|
a82a3efb14 | ||
|
|
9556417447 | ||
|
|
60fbb7db5d | ||
|
|
e504f8e63e | ||
|
|
071ce12a7e | ||
|
|
0decaf187c | ||
|
|
961644747e | ||
|
|
7f3fdedb5d | ||
|
|
3a80706938 | ||
|
|
2cf5f8e966 | ||
|
|
8ede1a4989 | ||
|
|
76da76ae76 | ||
|
|
c02ab033f4 | ||
|
|
1aaba74e24 | ||
|
|
6fe6794796 | ||
|
|
fd3e243855 | ||
|
|
938b075bf6 | ||
|
|
4fbbb4651d | ||
|
|
2ac38e9644 | ||
|
|
98d4fa0603 | ||
|
|
2ea0514bbe | ||
|
|
137aa692bc | ||
|
|
634fec39c0 | ||
|
|
d269f1e8fd | ||
|
|
4b67026bbf | ||
|
|
8b13ec4f0e | ||
|
|
7fef7660e4 | ||
|
|
01d021cc4c | ||
|
|
c355f81525 | ||
|
|
d138a54dfd | ||
|
|
514d4b9059 | ||
|
|
a7ead67c2d | ||
|
|
2f16784a20 | ||
|
|
8ca3ba21ee | ||
|
|
58ae6419f0 | ||
|
|
b56e0b98e3 | ||
|
|
4def32ab13 | ||
|
|
0de78d12ef | ||
|
|
e756534db4 | ||
|
|
2194965dc4 | ||
|
|
f9e54bcbfc | ||
|
|
667fd3a601 | ||
|
|
461e24bf39 | ||
|
|
ac2cfe5169 | ||
|
|
3f320f4337 | ||
|
|
433ee9ed45 | ||
|
|
6ee92588b1 | ||
|
|
0d797ce8a8 | ||
|
|
4915c545d9 | ||
|
|
e1c95fb1f2 | ||
|
|
5f56d3e0de | ||
|
|
d6b86b902c | ||
|
|
3abc0fec38 | ||
|
|
c0619eb746 | ||
|
|
791ababe1e | ||
|
|
d829216c8d | ||
|
|
1cf6f3b1e2 | ||
|
|
4d5939aaf4 | ||
|
|
2695f62f3e | ||
|
|
ccd0355d0b | ||
|
|
6d6e3a51c0 | ||
|
|
48c8164689 | ||
|
|
38ed5b8541 | ||
|
|
8a6df8bf95 | ||
|
|
050b1563df | ||
|
|
dbbcf385b1 | ||
|
|
d95a3af667 | ||
|
|
87a737babc | ||
|
|
a27ddd7490 | ||
|
|
5693b6d0f5 | ||
|
|
9debe8218d | ||
|
|
9549355ab7 | ||
|
|
88e8d2e009 | ||
|
|
a2ea5dd12e | ||
|
|
9f932a0911 | ||
|
|
71f05b9886 | ||
|
|
2b4e2638dc | ||
|
|
9c3f34fe04 | ||
|
|
d4123eeccd | ||
|
|
d727a6110a | ||
|
|
c42635579c | ||
|
|
fd54a7b85c | ||
|
|
76de78a72e | ||
|
|
5edce1fe6a | ||
|
|
afbe43965e | ||
|
|
d706a5375c | ||
|
|
feb2a878a9 | ||
|
|
e55f1e0308 | ||
|
|
8b2ed8585f | ||
|
|
1d570df129 | ||
|
|
d125bd07f7 | ||
|
|
997ffa620d | ||
|
|
d475146d80 | ||
|
|
1fe59caa19 | ||
|
|
a496a84cb8 | ||
|
|
eae964f7e7 | ||
|
|
e44381f295 | ||
|
|
2c0e0a6e39 | ||
|
|
216f588aa4 | ||
|
|
592aca1539 | ||
|
|
725bf2a691 | ||
|
|
051ce9e208 | ||
|
|
f367e9f08c | ||
|
|
f72fdfe33f | ||
|
|
000a54f5ed | ||
|
|
57a5de97f8 | ||
|
|
262d173c65 | ||
|
|
e558fae4b0 | ||
|
|
a90d801d08 | ||
|
|
636996356f | ||
|
|
8a87c06b97 | ||
|
|
69831571a5 | ||
|
|
24a5c839a7 | ||
|
|
93e09f11dd | ||
|
|
c570fc9873 | ||
|
|
f1d03acbad | ||
|
|
31ffbd98b6 | ||
|
|
87b9766bc0 | ||
|
|
49c9bcac9a | ||
|
|
5105babd14 | ||
|
|
0a361f5f41 | ||
|
|
8218d5eb5a | ||
|
|
7fe6925615 | ||
|
|
1dd71fc923 | ||
|
|
301f03dadd | ||
|
|
53c48d86b7 | ||
|
|
d760259ce6 | ||
|
|
1195c77f7a | ||
|
|
c373c207c0 | ||
|
|
65e83e7017 | ||
|
|
0722c2505a | ||
|
|
3c12e0d119 | ||
|
|
60d6c68e48 | ||
|
|
d8605965a8 | ||
|
|
6d455fc286 | ||
|
|
2882667e94 | ||
|
|
7fa578880e | ||
|
|
3f465df1cd | ||
|
|
c423784cc5 | ||
|
|
ce853a7e3a | ||
|
|
099ebad06b | ||
|
|
75a1347ae1 | ||
|
|
f5779558bb | ||
|
|
b83d3e5c33 | ||
|
|
1a7c719a4e | ||
|
|
b91a76b3b1 | ||
|
|
d78f6b7aba | ||
|
|
7d4d176bf4 | ||
|
|
52ea44ceaa | ||
|
|
132d04326b | ||
|
|
9996b5d686 | ||
|
|
e9d9f49ff3 | ||
|
|
95300546dc | ||
|
|
489fc6578b | ||
|
|
edebba6680 | ||
|
|
72c2a20a74 | ||
|
|
abef6c5fee | ||
|
|
662ae73637 | ||
|
|
3ef485548a | ||
|
|
a90c10ef3e | ||
|
|
d43fe8db75 | ||
|
|
1d84cac922 | ||
|
|
5280b6ed63 | ||
|
|
77833ff980 | ||
|
|
45e75cdfe9 | ||
|
|
2cb4cbe1b6 | ||
|
|
b22b09a93d | ||
|
|
e34485eb83 | ||
|
|
d010c5532d | ||
|
|
f2dc8b8020 | ||
|
|
1798353eac | ||
|
|
772a8b2383 | ||
|
|
fc08d2f8c3 | ||
|
|
5690c8361a | ||
|
|
6d09cf227c | ||
|
|
8736666e91 | ||
|
|
d1fd5cfb45 | ||
|
|
3eabf95fb3 | ||
|
|
8ea920ef91 | ||
|
|
3c0f20f364 | ||
|
|
59f8425c97 | ||
|
|
f181a7e459 | ||
|
|
6b1c595f87 | ||
|
|
0003de8f08 | ||
|
|
5357d8dc04 | ||
|
|
d069722bf9 | ||
|
|
3f4dd49a8f | ||
|
|
be06b3f7e8 | ||
|
|
5044bdda00 | ||
|
|
fbeffb0b5d | ||
|
|
ef0af39aa7 | ||
|
|
0697bc0a74 | ||
|
|
59e361cb37 | ||
|
|
1993a6588d | ||
|
|
218fba1aa1 | ||
|
|
4de6c69f5d | ||
|
|
43d8ebb3c4 | ||
|
|
68175cd71b | ||
|
|
f4d87f64ae | ||
|
|
68b3077651 | ||
|
|
1332b337f3 | ||
|
|
e9975d1ea5 | ||
|
|
2c103aca3d | ||
|
|
c0a5eb0d2b | ||
|
|
ff7c4495f0 | ||
|
|
35fe639cd2 | ||
|
|
59add8982e | ||
|
|
8d9c514097 | ||
|
|
6f880d0f02 | ||
|
|
ec47ee8110 | ||
|
|
28b8141c6b | ||
|
|
5b0b309c49 | ||
|
|
0b84a372f6 | ||
|
|
8355e1e006 | ||
|
|
c7d33fbd83 | ||
|
|
cf324d93fe | ||
|
|
9a704a2bcb | ||
|
|
1e00651541 | ||
|
|
857e75594d | ||
|
|
2f1dadfc3e | ||
|
|
7d0404657e | ||
|
|
b9dd651fc1 | ||
|
|
25ef456af2 | ||
|
|
084decaa85 | ||
|
|
330a444fc5 | ||
|
|
a47dac2854 | ||
|
|
08070f3e2d | ||
|
|
2352c78cb6 | ||
|
|
6ef9c3865f | ||
|
|
ff9789b5a7 | ||
|
|
f09297f406 | ||
|
|
3921655157 | ||
|
|
e4e10d523f | ||
|
|
404dddcb86 | ||
|
|
ffb2603485 | ||
|
|
2d3c69d178 | ||
|
|
b837653cf1 | ||
|
|
eeca031c86 | ||
|
|
918a8627e9 | ||
|
|
86370edd1e | ||
|
|
1173631255 | ||
|
|
911fd9a004 | ||
|
|
0ad3da5bbc | ||
|
|
89ae2a9516 | ||
|
|
70892cae05 | ||
|
|
de0af153bc | ||
|
|
33161e46e6 | ||
|
|
7e3c662374 | ||
|
|
a39e9c2da6 | ||
|
|
72b8d56245 | ||
|
|
0d36f59036 | ||
|
|
a3f7d2287a | ||
|
|
8edfbd28ed | ||
|
|
606be4304d | ||
|
|
329066719e | ||
|
|
93b8ef35f7 | ||
|
|
484b003b34 | ||
|
|
ed427130a9 | ||
|
|
807feae540 | ||
|
|
4b62e93257 | ||
|
|
e425417d68 | ||
|
|
bc1e837466 | ||
|
|
95321e33a0 | ||
|
|
5720b313a3 | ||
|
|
f3e2205e69 | ||
|
|
410b611b14 | ||
|
|
1c8af47bac | ||
|
|
c8a2baca3c | ||
|
|
ac5a323115 | ||
|
|
fa65e7feef | ||
|
|
71d27d0e55 | ||
|
|
972d053c83 | ||
|
|
7797661489 | ||
|
|
9f9e036c68 | ||
|
|
e940253caf | ||
|
|
2b4d20b94e | ||
|
|
f88fc23e58 | ||
|
|
caf1f92fef | ||
|
|
f2a02a25a7 | ||
|
|
e85767b4a0 | ||
|
|
c83e5cc7d8 | ||
|
|
71f565f66b | ||
|
|
769407b3df | ||
|
|
e7615ed6d7 | ||
|
|
fe508307b2 | ||
|
|
c2a26a8547 | ||
|
|
addf9b920f | ||
|
|
4e30d4b8fb | ||
|
|
850561613b | ||
|
|
782d71ddb0 | ||
|
|
a0799d19f8 | ||
|
|
ba6eb4f26f | ||
|
|
38fcee35c2 | ||
|
|
4af7106e01 | ||
|
|
bd52226ae2 | ||
|
|
cb7ac77c61 | ||
|
|
55a7ee1f91 | ||
|
|
d37210a0d0 | ||
|
|
0c7a1e8f17 | ||
|
|
3d7ab40674 | ||
|
|
b8de51be57 | ||
|
|
1ce72b91ca | ||
|
|
3f96587a70 | ||
|
|
c012bbd54a | ||
|
|
a50852306e | ||
|
|
db396ec107 | ||
|
|
6b868658aa | ||
|
|
9fe9e1a1c4 | ||
|
|
aeb77e5a40 | ||
|
|
1d59d89588 | ||
|
|
bde357f952 | ||
|
|
558c091205 | ||
|
|
f67175e628 | ||
|
|
390f6d545f | ||
|
|
44efb0178c | ||
|
|
37eee26bdf | ||
|
|
b4251a793b | ||
|
|
6736a08240 | ||
|
|
ed4a670f0a | ||
|
|
fbb9afe34f | ||
|
|
020bc11bd7 | ||
|
|
ae0837e29b | ||
|
|
f0380ef733 | ||
|
|
25bdaf9f00 | ||
|
|
ef1809305c | ||
|
|
ba34458feb | ||
|
|
a2fb50a71c | ||
|
|
b15a4007ee | ||
|
|
090b5c32f0 | ||
|
|
49c75e3599 | ||
|
|
05765642d9 | ||
|
|
f62eba9d7b | ||
|
|
bc27fd0acc | ||
|
|
244a28c7d2 | ||
|
|
edac4d3fed | ||
|
|
e402929cca | ||
|
|
639bfbe549 | ||
|
|
2e380ceb02 | ||
|
|
f26dea2420 | ||
|
|
60e841e5a2 | ||
|
|
25d055e560 | ||
|
|
052fb3df5b | ||
|
|
929f475354 | ||
|
|
1afa02bbb3 | ||
|
|
3c39dc3cec | ||
|
|
7e8f46c4f3 | ||
|
|
333b0584a4 | ||
|
|
f7a648903e | ||
|
|
6ec687ef15 | ||
|
|
b6212f4bfe | ||
|
|
9794149fae | ||
|
|
edc9d8bd4d | ||
|
|
76335f78ac | ||
|
|
56192f0758 | ||
|
|
63eb8584e7 | ||
|
|
aa4d06fb1e | ||
|
|
c70cca2776 | ||
|
|
1a38771f1a | ||
|
|
25728e51d2 | ||
|
|
02576c48b6 | ||
|
|
4263309d89 | ||
|
|
b68b367b3c | ||
|
|
2cffb50884 | ||
|
|
3b473a5f7a | ||
|
|
8b82c448af | ||
|
|
270a015514 | ||
|
|
f02125e411 | ||
|
|
b0f2694745 | ||
|
|
2497bdb124 | ||
|
|
d5d564f789 | ||
|
|
49e48f7adc | ||
|
|
c5484fbb88 | ||
|
|
0d4b6252e8 | ||
|
|
7529feb4a5 | ||
|
|
079aaec21e | ||
|
|
1601292db7 | ||
|
|
c82dbddc74 | ||
|
|
5f96e314fd | ||
|
|
9a6ee9d2ef | ||
|
|
2cbbe7aeda | ||
|
|
19fc1fd674 | ||
|
|
5b63bedc0d | ||
|
|
12229a1719 | ||
|
|
a7f802db7c | ||
|
|
0d6c952721 | ||
|
|
c42eb41fb3 | ||
|
|
2da4f1df32 | ||
|
|
69a62d1b73 | ||
|
|
61e054024b | ||
|
|
0a5c4c092a | ||
|
|
f1597f8e84 | ||
|
|
f3ca893aea | ||
|
|
ecfeedeff3 | ||
|
|
a162bab591 | ||
|
|
79a29c3461 | ||
|
|
377901606e | ||
|
|
8cd24a5734 | ||
|
|
ba913b77e7 | ||
|
|
6012fc929e | ||
|
|
33d1700548 | ||
|
|
8b3beb45c6 | ||
|
|
4ba4e68833 | ||
|
|
11bed72bed | ||
|
|
7fb22f3f07 | ||
|
|
788900e31a | ||
|
|
03b1adca12 | ||
|
|
aecf2eb08d | ||
|
|
895aee3e6c | ||
|
|
c667e1465d | ||
|
|
3c9d614e1f | ||
|
|
971e0cb61e | ||
|
|
67d78f6603 | ||
|
|
ed0b370d49 | ||
|
|
e5b8f4d372 | ||
|
|
2999e490ad | ||
|
|
156a2c6521 | ||
|
|
48bb9d3242 | ||
|
|
b2ee4f0c66 | ||
|
|
9b68f34a31 | ||
|
|
f8944eb8b3 | ||
|
|
3634194e4d | ||
|
|
038b87cf70 | ||
|
|
5ca6bbea11 | ||
|
|
7b9b4d56a7 | ||
|
|
d81e69bf00 | ||
|
|
5e134f990e | ||
|
|
1675e04f90 | ||
|
|
0a72acd899 | ||
|
|
aa6c9680da | ||
|
|
d41663143b | ||
|
|
9db363865c | ||
|
|
9b03a32eec | ||
|
|
eeec1b12b5 | ||
|
|
e655c2078b | ||
|
|
3426191e59 | ||
|
|
b1c77ae59c | ||
|
|
d17ff4afba | ||
|
|
0131bbbbed | ||
|
|
7f55fc4b56 | ||
|
|
aea9673b78 | ||
|
|
89e9a84ea6 | ||
|
|
fba9afd6f5 | ||
|
|
c474d972cb | ||
|
|
3d6f670e8d | ||
|
|
9d47f319a0 | ||
|
|
7106f042da | ||
|
|
ea6e56842f | ||
|
|
65926fea73 | ||
|
|
85cb94d99d | ||
|
|
db78f1b91e | ||
|
|
04e0523cac | ||
|
|
885b9d2c26 | ||
|
|
51aff20d65 | ||
|
|
8dc5214c9e | ||
|
|
1be208d96b | ||
|
|
22d494d3f1 | ||
|
|
5b99132f66 | ||
|
|
91d04b99d1 | ||
|
|
70e57cf738 | ||
|
|
2f00cec52b | ||
|
|
fee966996f | ||
|
|
bff081a263 | ||
|
|
b5b56f7af1 | ||
|
|
3f7f0e677d | ||
|
|
2b29d96d61 | ||
|
|
9f13645127 | ||
|
|
bbbbe9a121 | ||
|
|
46ecf0af88 | ||
|
|
93f0d3c1cf | ||
|
|
2a0906d88e | ||
|
|
8ec7d19f30 | ||
|
|
a0f5a06c73 | ||
|
|
39a98e795f | ||
|
|
57705cf41b | ||
|
|
052c70bb38 | ||
|
|
6dc88262c9 | ||
|
|
116244384e | ||
|
|
29775040d1 | ||
|
|
177888b159 | ||
|
|
bc24acd057 | ||
|
|
4d727b0af7 | ||
|
|
1cdac943ba | ||
|
|
78c00b1722 | ||
|
|
2ff655d2dc | ||
|
|
e53717cd87 | ||
|
|
d144a5884a | ||
|
|
8b8d915ab7 | ||
|
|
54de57ee7b | ||
|
|
e0d9cf7f5c | ||
|
|
10ea5d00eb | ||
|
|
de76f55fe2 | ||
|
|
cd301d514c | ||
|
|
0f232b3d86 | ||
|
|
53ff693e95 | ||
|
|
c15cc2ecfc | ||
|
|
59b441e524 | ||
|
|
215484c19a | ||
|
|
885f656d34 | ||
|
|
851d3ba159 | ||
|
|
62ab444b29 | ||
|
|
b84ebfa7b3 | ||
|
|
f1b929c13b | ||
|
|
806139091c | ||
|
|
6960c8b2d6 | ||
|
|
b1c6c0442f | ||
|
|
a85a27f225 | ||
|
|
d5629651d1 | ||
|
|
afbc8eec75 | ||
|
|
3a96164c27 | ||
|
|
e393b5a2ea | ||
|
|
950d31ada8 | ||
|
|
543c31cec6 | ||
|
|
5920b3515e | ||
|
|
5e87ec2627 | ||
|
|
7165c4550b | ||
|
|
472496d59c | ||
|
|
127da40256 | ||
|
|
a113b99de0 | ||
|
|
74535c9cba | ||
|
|
79254a562f | ||
|
|
4b1469748b | ||
|
|
1683d63f33 | ||
|
|
4cce52f9ce | ||
|
|
33fb03066e | ||
|
|
a1deb15db8 | ||
|
|
96ab8ec958 | ||
|
|
c0f68dce25 | ||
|
|
2b6c38083c | ||
|
|
667ece7d3f | ||
|
|
5d38937f34 | ||
|
|
9270e59508 | ||
|
|
b32865488e | ||
|
|
e43c7e9a6a | ||
|
|
c3a980836a | ||
|
|
304b83be89 | ||
|
|
a2e050b8c5 | ||
|
|
a7ad56be98 | ||
|
|
eea01f10ac | ||
|
|
3d9b85dc6d | ||
|
|
743a9009de | ||
|
|
a0da4f9dd0 | ||
|
|
3fe45e9cbb | ||
|
|
294bf742cd | ||
|
|
ea5970ab1c | ||
|
|
72df418953 | ||
|
|
f566b567be | ||
|
|
8d817066e8 | ||
|
|
99b53f4a55 | ||
|
|
038154c441 | ||
|
|
951a126d63 | ||
|
|
fa77cda0b4 | ||
|
|
a0bfd9e497 | ||
|
|
a8e601e5e0 | ||
|
|
47d7cef214 | ||
|
|
e298739cb9 | ||
|
|
6fb72bd44a | ||
|
|
7b8fb56440 | ||
|
|
5e9bd2fd2d | ||
|
|
aac13dcdca | ||
|
|
c6c5e33ee6 | ||
|
|
406e230ed3 | ||
|
|
5a9de1a95d | ||
|
|
0289caad67 | ||
|
|
99cb6fa9ed | ||
|
|
fe011e87d1 | ||
|
|
5a5563f00a | ||
|
|
5a8f076d85 | ||
|
|
45deb5ba7f | ||
|
|
bcea3eb7c1 | ||
|
|
9ca5fa6144 | ||
|
|
d9809318fc | ||
|
|
9ad0f58095 | ||
|
|
04ae8a81a5 | ||
|
|
082a078b51 | ||
|
|
04fdb67fc9 | ||
|
|
e6a927e5af | ||
|
|
b05ba64db8 | ||
|
|
d0f5ec8ada | ||
|
|
caa6c8d4b9 | ||
|
|
5cf4a0e09d | ||
|
|
0a0c1c45a1 | ||
|
|
fce89fe8be | ||
|
|
2bbe39120a | ||
|
|
2a5da746c7 | ||
|
|
deb2cd0156 | ||
|
|
b4e4d7055f | ||
|
|
a9feddf6f6 | ||
|
|
071f7cb035 | ||
|
|
a11b0f1665 | ||
|
|
39442bcafe | ||
|
|
48a905bf6f | ||
|
|
440b50b4e8 | ||
|
|
0cf6487cad | ||
|
|
74825dddbf | ||
|
|
699006a3e9 | ||
|
|
6367be213f | ||
|
|
4e97ac3b8c | ||
|
|
57817fd90c | ||
|
|
5b79d0439c | ||
|
|
6d4cee0041 | ||
|
|
3e645db324 | ||
|
|
05da826c24 | ||
|
|
70e16d853e | ||
|
|
9ebf949890 | ||
|
|
a06bdced8a | ||
|
|
3ab506ea94 | ||
|
|
0340402dc1 | ||
|
|
6e8fe7308c | ||
|
|
13c2f471aa | ||
|
|
604f17fbfd | ||
|
|
50669f65bb | ||
|
|
21c61121b0 | ||
|
|
a58b6f1b49 | ||
|
|
073a5d4d68 | ||
|
|
7a3cab8947 | ||
|
|
aec79c4eeb | ||
|
|
5f385e4c03 | ||
|
|
b018502079 | ||
|
|
47e0a82caf | ||
|
|
d848ee5d5f | ||
|
|
2df0f1bcb8 | ||
|
|
1ae141492a | ||
|
|
7232d07b1c | ||
|
|
64abd564b4 | ||
|
|
483ea77d14 | ||
|
|
cca5abdc8f | ||
|
|
17b3b02ac5 | ||
|
|
ce4e203c14 | ||
|
|
011defc1f7 | ||
|
|
5ccb9bde28 | ||
|
|
30e262d8ac | ||
|
|
692f6779d6 | ||
|
|
e93bf1cfe7 | ||
|
|
8d769d4c4b | ||
|
|
b539ac6335 | ||
|
|
72906b3ee7 | ||
|
|
7f6d4acf90 | ||
|
|
bb892f7e78 | ||
|
|
ead6bb09dc | ||
|
|
0b1ec3f29f | ||
|
|
e77db372bd | ||
|
|
64ca875cfd | ||
|
|
256653677e | ||
|
|
b99980fda1 | ||
|
|
13857d4313 | ||
|
|
fe1ab73818 | ||
|
|
d5a2aa6d6d | ||
|
|
4f9b37433c | ||
|
|
b5763ec89d | ||
|
|
ec506e71a4 | ||
|
|
cfcaa58b71 | ||
|
|
29cf4769f5 | ||
|
|
58fd2273ea | ||
|
|
b52616c64d | ||
|
|
ac1ce6043b | ||
|
|
6631f98c43 | ||
|
|
37340d0445 | ||
|
|
a19ff6762e | ||
|
|
743c97940e | ||
|
|
f2a0f59b08 | ||
|
|
4fb11b68e4 | ||
|
|
ed742c7e16 | ||
|
|
3b110bcd4b | ||
|
|
d4dd74e820 | ||
|
|
3f77cb2214 | ||
|
|
7c839a1df9 | ||
|
|
c5de940946 | ||
|
|
2943c5fafb | ||
|
|
fddeaa966d | ||
|
|
e706ec0ffe | ||
|
|
7034e7b620 | ||
|
|
e65c48be33 | ||
|
|
a095e8b25c | ||
|
|
d44a76bae3 | ||
|
|
4488de9add | ||
|
|
62609a2918 | ||
|
|
492294e11d | ||
|
|
f483f8fdf0 | ||
|
|
4061866042 | ||
|
|
191295b6de | ||
|
|
6d4aa27e15 | ||
|
|
d70d3b439f | ||
|
|
56851eb91f | ||
|
|
2cfe8de030 | ||
|
|
b5604ba0a9 | ||
|
|
1a0e15e04c | ||
|
|
b546b9cbe7 | ||
|
|
ce9f76fa63 | ||
|
|
dceed7d84d | ||
|
|
660a27850f | ||
|
|
0a7fd0288c | ||
|
|
33d0a9d3b3 | ||
|
|
c1deeaf5f7 | ||
|
|
7c1cd50def | ||
|
|
b224c72e98 | ||
|
|
6c57ac8f01 | ||
|
|
cf35dc5345 | ||
|
|
102d13bbae | ||
|
|
faf2aff959 | ||
|
|
5d54ad3342 | ||
|
|
0f5d753910 | ||
|
|
a9442a019f | ||
|
|
1668ef6bb4 | ||
|
|
45436f65af | ||
|
|
b8a295713c | ||
|
|
2ccb541b7f | ||
|
|
e4a6ff4c70 | ||
|
|
aa20bc769c | ||
|
|
cd53a65c14 | ||
|
|
8b54f5aa69 | ||
|
|
ceed178061 | ||
|
|
33662974bf | ||
|
|
36b97fc6a2 | ||
|
|
9bc291e618 | ||
|
|
fdb54b5cdc | ||
|
|
df20662005 | ||
|
|
900f20f164 | ||
|
|
df3b2cd8fe | ||
|
|
c2e4bae9dd | ||
|
|
0f8c627474 | ||
|
|
f38fef23a0 | ||
|
|
a39da481e0 | ||
|
|
830ade9596 | ||
|
|
2aa296ff33 | ||
|
|
9050035c74 | ||
|
|
c245855bbf | ||
|
|
ef66e71feb | ||
|
|
c33058ae2b | ||
|
|
8be0ea64a5 | ||
|
|
1419c71ef5 | ||
|
|
629d4a82ae | ||
|
|
e13742445e | ||
|
|
1d21bb1ea3 | ||
|
|
aa38b1f859 | ||
|
|
d95d282f39 | ||
|
|
64f7233bfc | ||
|
|
9d81e4be2f | ||
|
|
0a1ee86baf | ||
|
|
2908884202 | ||
|
|
aac075be06 | ||
|
|
bf288fdaeb | ||
|
|
a6eddb5798 | ||
|
|
78ae7b847c | ||
|
|
c7bae93b48 | ||
|
|
938cf238ff | ||
|
|
5188846e2e | ||
|
|
d874a3a493 | ||
|
|
f95284622e | ||
|
|
0b6c0e6b94 | ||
|
|
b021bb73ed | ||
|
|
c76b653737 | ||
|
|
3414e2daf0 | ||
|
|
92e2cd102e | ||
|
|
b703c42ee3 | ||
|
|
94f7533ee7 | ||
|
|
101e5d5035 | ||
|
|
ab78af0691 | ||
|
|
af14b682b1 | ||
|
|
a26d4fb499 | ||
|
|
f644d21c61 | ||
|
|
2af52c193c | ||
|
|
7717cda52a | ||
|
|
c13746f10e | ||
|
|
49abfac98e | ||
|
|
aa26e5ac2a | ||
|
|
336f007fa1 | ||
|
|
a3e59d43a7 | ||
|
|
ae840ea689 | ||
|
|
79ee674ad9 | ||
|
|
4d77053313 | ||
|
|
235e88f115 | ||
|
|
021bac5b68 | ||
|
|
60c4cacfbc | ||
|
|
30e6fc516b | ||
|
|
1388e7d7f4 | ||
|
|
b79b123c65 | ||
|
|
d3a6ff6b6a | ||
|
|
10d3adbe40 | ||
|
|
e100e080da | ||
|
|
e2c7a8c384 | ||
|
|
d97bbe7918 | ||
|
|
3864d73d1a | ||
|
|
0904c62a2d | ||
|
|
817b74cc7f | ||
|
|
01891d46b3 | ||
|
|
82987cd53c | ||
|
|
aa56aad46e | ||
|
|
ad7b155a99 | ||
|
|
f1ca06daf5 | ||
|
|
3df743f47b | ||
|
|
637225c2bc | ||
|
|
849104f530 | ||
|
|
3a4bc33d53 | ||
|
|
3679fbe3ea | ||
|
|
b1d2c25ce5 | ||
|
|
480c05650b | ||
|
|
fa265d769c | ||
|
|
4a197a5c90 | ||
|
|
23702056fd | ||
|
|
2a774d20f4 | ||
|
|
b1a7f0fd64 | ||
|
|
70b86907f3 | ||
|
|
d0d813552c | ||
|
|
707dace3d0 | ||
|
|
8361106660 | ||
|
|
a46c519459 | ||
|
|
b6b29e02f3 | ||
|
|
441c70b388 | ||
|
|
ab65fb7a5c | ||
|
|
2fc37d54f2 | ||
|
|
59d31c9a18 | ||
|
|
db97ab51ac | ||
|
|
c6f1f97a57 | ||
|
|
fdb1ef540d | ||
|
|
62a8ffb4ae | ||
|
|
1e3cf6f374 | ||
|
|
48c29dd7d9 | ||
|
|
e30727ab27 | ||
|
|
157eb5f87b | ||
|
|
8f290c2a6d | ||
|
|
47f6a217e8 | ||
|
|
69691bdf2a | ||
|
|
522a37b8e3 | ||
|
|
53aa142d94 | ||
|
|
61bd591ee8 | ||
|
|
fd2b438c67 | ||
|
|
5855cdfb26 | ||
|
|
a63266b0e9 | ||
|
|
fe19d96088 | ||
|
|
37933782d2 | ||
|
|
1dcc51e5a4 | ||
|
|
eb61ce2cf2 | ||
|
|
c5bcfe6ab3 | ||
|
|
6c7ed82fa9 | ||
|
|
00ed0d79ec | ||
|
|
cfc84f3e78 | ||
|
|
27e5010d8e | ||
|
|
efa00728d6 | ||
|
|
106caf2b3e | ||
|
|
3dca6c1fd6 | ||
|
|
bd56055593 | ||
|
|
dbd2c7049e | ||
|
|
07aa775de1 | ||
|
|
88ac2a98e8 | ||
|
|
d0c5592855 | ||
|
|
b03ad0cfe0 | ||
|
|
a43b0e6a57 | ||
|
|
12176473ff | ||
|
|
1614c1452f | ||
|
|
d38f16d732 | ||
|
|
d7a2296909 | ||
|
|
d6fe0df24f | ||
|
|
7fb1a06e1e | ||
|
|
4550d888bb | ||
|
|
d62d1d670b | ||
|
|
f18e8c5a38 | ||
|
|
6cb6cd3f26 | ||
|
|
c5554e8f1e | ||
|
|
5144c0ecdc | ||
|
|
642c62d8ce | ||
|
|
f92da16544 | ||
|
|
6e8b370e54 | ||
|
|
1ecf1e0413 | ||
|
|
1ea1482aad | ||
|
|
aeded9ac0e | ||
|
|
1cec872273 | ||
|
|
e550295644 | ||
|
|
70da43eeb7 | ||
|
|
7e9fb6be32 | ||
|
|
258ed4e866 | ||
|
|
b8992362c2 | ||
|
|
60fcf2734a | ||
|
|
ae9e83cf66 | ||
|
|
b8d1e37cce | ||
|
|
6ecd1e5ea5 | ||
|
|
52956503f1 | ||
|
|
3b7bedbbe8 | ||
|
|
148feac43e | ||
|
|
3272033c63 | ||
|
|
73d1bdf84b | ||
|
|
0e38f61c85 | ||
|
|
b004247478 | ||
|
|
9d84fe7719 | ||
|
|
5aaecfc0fe | ||
|
|
ff3026686f | ||
|
|
83243b61a6 | ||
|
|
80c4601fdc | ||
|
|
fa3700df7c | ||
|
|
6244e44033 | ||
|
|
43bd0b0dd5 | ||
|
|
d64dafc715 | ||
|
|
9e67880456 | ||
|
|
7484f6e6a6 | ||
|
|
d8d5810d7c | ||
|
|
4a167aa3d7 | ||
|
|
ced3460673 | ||
|
|
d75dd874ca | ||
|
|
1da477d1d1 | ||
|
|
a4ad3896f0 | ||
|
|
e536d203d2 | ||
|
|
28007a33a0 | ||
|
|
4242e0d329 | ||
|
|
ca038937e9 | ||
|
|
342d0862c6 | ||
|
|
3b11285bd5 | ||
|
|
3c404f3678 | ||
|
|
eb37be1381 | ||
|
|
c5f6ace332 | ||
|
|
0e29e8ac76 | ||
|
|
71ae42056f | ||
|
|
a9bad53209 | ||
|
|
eee340366e | ||
|
|
a2bc1a5d2d | ||
|
|
3610d5ea93 | ||
|
|
a75f8e5fdf | ||
|
|
1821a5c678 | ||
|
|
5c3a62b9c5 | ||
|
|
851f57c1f5 | ||
|
|
2cd3f8c6ee | ||
|
|
a331d82cb5 | ||
|
|
6ef2ec4ed2 | ||
|
|
29ed26a503 | ||
|
|
7b3d5ab1ae | ||
|
|
6f2f6e9567 | ||
|
|
9a0bc984d4 | ||
|
|
1922c8dbf8 | ||
|
|
a841449771 | ||
|
|
ff4ef16375 | ||
|
|
33f6926916 | ||
|
|
5d9b1abe82 | ||
|
|
7a5a821f8a | ||
|
|
0b9635c160 | ||
|
|
555ae327b6 | ||
|
|
944c79ec61 | ||
|
|
39eaed260a | ||
|
|
f308836264 | ||
|
|
623688c28e | ||
|
|
8936d8b517 | ||
|
|
14fec7473a | ||
|
|
33b40bf35f | ||
|
|
e52bcf33c5 | ||
|
|
fa6c504b34 | ||
|
|
e9dac8c8f3 | ||
|
|
52bea8f808 | ||
|
|
39ce706f3e | ||
|
|
d0171a8933 | ||
|
|
9f75d2fe4b | ||
|
|
9dfc6c2bc1 | ||
|
|
025e778252 | ||
|
|
c761f631a1 | ||
|
|
e173117a44 | ||
|
|
1ff4206bed | ||
|
|
babc5626a9 | ||
|
|
b624c9a4d2 | ||
|
|
d83feafcb2 | ||
|
|
cc1cb5fbc7 | ||
|
|
80666fed1a | ||
|
|
db2c6c99f7 | ||
|
|
3f1fa44ee7 | ||
|
|
4717e4fe3f | ||
|
|
ffc390d49d | ||
|
|
002e7ab9dd | ||
|
|
ca69bd69f2 | ||
|
|
3e2a366dc6 | ||
|
|
d365aaf270 | ||
|
|
818ee16e39 | ||
|
|
533caba717 | ||
|
|
cae6fd45b3 | ||
|
|
8c268be823 | ||
|
|
17845428bd | ||
|
|
efd1b3cd3c | ||
|
|
2ccd00a378 | ||
|
|
0123a99b5d | ||
|
|
ac744fbd90 | ||
|
|
10a1104073 | ||
|
|
a39a856f69 | ||
|
|
e6e69b4fd2 | ||
|
|
b4de1b49f2 | ||
|
|
c47428b27f | ||
|
|
d267a78416 | ||
|
|
04bb04a6a9 | ||
|
|
8eb535169f | ||
|
|
e0e0fbf739 | ||
|
|
4f8e8ae7b9 | ||
|
|
15b77482ac | ||
|
|
7420363adf | ||
|
|
1666e3a58a | ||
|
|
adbe85cc33 | ||
|
|
89d8d36ec3 | ||
|
|
a631adacb5 | ||
|
|
3384d1b7c3 | ||
|
|
f70de60672 | ||
|
|
cfd54c3f0e | ||
|
|
b29c0fe8cb | ||
|
|
5c1e5e0fcc | ||
|
|
8ec56390c4 | ||
|
|
2ad27e175c | ||
|
|
da1bd3f1fd | ||
|
|
dd913279d7 | ||
|
|
1246a677d1 | ||
|
|
43f2fc0740 | ||
|
|
b821209807 | ||
|
|
39fc5da98f | ||
|
|
504b6af3f6 | ||
|
|
b3ede3230c | ||
|
|
7035503fa7 | ||
|
|
7d147fd040 | ||
|
|
2a44e0b7eb | ||
|
|
686b9bc82c | ||
|
|
0c1497a255 | ||
|
|
496090610f | ||
|
|
16177754d5 | ||
|
|
f41f4939bc | ||
|
|
610503472a | ||
|
|
5dcd74b3b0 | ||
|
|
305825da78 | ||
|
|
205451a31d | ||
|
|
f1ae04fd07 | ||
|
|
4ba82275b9 | ||
|
|
093920173e | ||
|
|
2c3d95a4db | ||
|
|
76928e43a3 | ||
|
|
a816c5dc23 | ||
|
|
5ef84e4bfc | ||
|
|
fbdc9c9f8d | ||
|
|
6510152138 | ||
|
|
df0c6a3b94 | ||
|
|
dfe0d74845 | ||
|
|
b9edd0238d | ||
|
|
bf72237b38 | ||
|
|
a24f6e80c7 | ||
|
|
297c764fe1 | ||
|
|
b03c2a1f80 | ||
|
|
7af77384e7 | ||
|
|
bacbfc8615 | ||
|
|
c8466e9fa6 | ||
|
|
4a231d6fdb | ||
|
|
4e80e1dd03 | ||
|
|
e0b18c6868 | ||
|
|
15b9f8e13f | ||
|
|
03b8dbbc44 | ||
|
|
80af8dcf80 | ||
|
|
62b2856d29 | ||
|
|
7ab3ce91a1 | ||
|
|
e040aeef55 | ||
|
|
46df5a8fa7 | ||
|
|
f61fbbaead | ||
|
|
480f515114 | ||
|
|
e206e6babf | ||
|
|
eae4b52aa1 | ||
|
|
933f75f1ee | ||
|
|
f3c72e561a | ||
|
|
44d6374cfe | ||
|
|
eba13800ff | ||
|
|
3942492f32 | ||
|
|
fe323d5764 | ||
|
|
4740edfb1f | ||
|
|
671dff060d | ||
|
|
4b0dc08426 | ||
|
|
10ffa35b29 | ||
|
|
57fadacda0 | ||
|
|
5c186f30a8 | ||
|
|
0a205f77b0 | ||
|
|
75a0f4373c | ||
|
|
189b245b1d | ||
|
|
0eae47c8be | ||
|
|
00607cb704 | ||
|
|
d79b6e094a | ||
|
|
f9d5c86245 | ||
|
|
a6a1291d0e | ||
|
|
49db1c8244 | ||
|
|
dbe1721d50 | ||
|
|
b55420e935 | ||
|
|
2706df2b24 | ||
|
|
90f791de1b | ||
|
|
c9db3f98d1 | ||
|
|
bdfe233472 | ||
|
|
d340aeb77d | ||
|
|
2da1105ff8 | ||
|
|
e6e5036474 | ||
|
|
609a1709c5 | ||
|
|
2bb9607eea | ||
|
|
ca32d05bb2 | ||
|
|
67a016add0 | ||
|
|
ea41dbb3bc | ||
|
|
637090d259 | ||
|
|
911d65131a | ||
|
|
f4203263bb | ||
|
|
79df5249ef | ||
|
|
ac71093888 | ||
|
|
d15e0f9fe5 | ||
|
|
d3861caf28 | ||
|
|
1c8e379fdd | ||
|
|
d9783490ec | ||
|
|
bb32c3a8d3 | ||
|
|
bab7ec388c | ||
|
|
e2957192d0 | ||
|
|
3994c78365 | ||
|
|
bfd4d7ffe1 | ||
|
|
cb2f18c078 | ||
|
|
69a032c1cf | ||
|
|
a287afb3e9 | ||
|
|
da81f10e04 | ||
|
|
786675a99b | ||
|
|
6603f46678 | ||
|
|
fdfa3bb8f5 | ||
|
|
da204a27c5 | ||
|
|
e11a68afba | ||
|
|
168b0f82dd | ||
|
|
6715a54da2 | ||
|
|
6b32b3ae80 | ||
|
|
6a2242725d | ||
|
|
e9070fadab | ||
|
|
1117e1b724 | ||
|
|
d015b18c66 | ||
|
|
01641b5af4 | ||
|
|
7c0c81207b | ||
|
|
c844b60941 | ||
|
|
9f8246a26a | ||
|
|
6d5141b60f | ||
|
|
8b4a9dd325 | ||
|
|
5006aaae38 | ||
|
|
1bb841d5c5 | ||
|
|
f57c4f390d | ||
|
|
646151e020 | ||
|
|
20f573c477 | ||
|
|
094e4c5da8 | ||
|
|
7716880a6c | ||
|
|
71605fb8fe | ||
|
|
fa9d8b8881 | ||
|
|
aa0566b8ca | ||
|
|
2a838ebb0b | ||
|
|
fabc975b20 | ||
|
|
3bdc88cecb | ||
|
|
a591001761 | ||
|
|
54717ea6f2 | ||
|
|
ceca4c98a3 | ||
|
|
39b4287c5e | ||
|
|
53923c9c87 | ||
|
|
ede733888d | ||
|
|
a79db03093 | ||
|
|
5c8254a9c4 | ||
|
|
2f7b62f710 | ||
|
|
a19c13eb3c | ||
|
|
64d4cd84af | ||
|
|
734db58d85 | ||
|
|
2bbcb8ca89 | ||
|
|
07e810a231 | ||
|
|
48beb184df | ||
|
|
f195e87568 | ||
|
|
13d44ae56a | ||
|
|
00b4874d09 | ||
|
|
5bb90babbc | ||
|
|
7cde30d352 | ||
|
|
73fbf49ba4 | ||
|
|
b39ef5948b | ||
|
|
7cf9dda821 | ||
|
|
657806c8cf | ||
|
|
d61a218808 | ||
|
|
039f73711a | ||
|
|
6e885acf8c | ||
|
|
9ef07cea7a | ||
|
|
9e3b321aaf | ||
|
|
21560701ea | ||
|
|
4556375174 | ||
|
|
91b5398b5a | ||
|
|
eeb8016992 | ||
|
|
736106be3a | ||
|
|
f400568dc0 | ||
|
|
0ca96cba6e | ||
|
|
df4d837026 | ||
|
|
760f84d7fa | ||
|
|
ab35c3557f | ||
|
|
174a315e3f | ||
|
|
0834313456 | ||
|
|
ce3b29085f | ||
|
|
b93d7a204f | ||
|
|
df931e10c0 | ||
|
|
9572cb2d33 | ||
|
|
51e836f32a | ||
|
|
7fefbd88d0 | ||
|
|
cb956c5508 | ||
|
|
47b0086bf8 | ||
|
|
3c14cc219e | ||
|
|
b8d66e4a95 | ||
|
|
7804a22984 | ||
|
|
bfc1c93153 | ||
|
|
5bf3824f28 | ||
|
|
dac23e38d9 | ||
|
|
404faf8a0b | ||
|
|
4a7b0f4711 | ||
|
|
dd62fca45d | ||
|
|
79fb04126c | ||
|
|
39c9574ae3 | ||
|
|
38af257adf | ||
|
|
5aae9a4722 | ||
|
|
cfe3cae88d | ||
|
|
612de84ac6 | ||
|
|
33be597ef0 | ||
|
|
cc26fd80d7 | ||
|
|
c227a1ffec | ||
|
|
f0df787bbe | ||
|
|
09188bed48 | ||
|
|
4a3bcaba06 | ||
|
|
1d1ab65edd | ||
|
|
7330cdaf1c | ||
|
|
050a1fb6cf | ||
|
|
1e8397cf17 | ||
|
|
59b53ece2b | ||
|
|
16c62cd46f | ||
|
|
eff56c2514 | ||
|
|
ee6b9a223f | ||
|
|
acc6ea434a | ||
|
|
1e5a7356f4 | ||
|
|
4c8342c19d | ||
|
|
dad5232ecb | ||
|
|
6cad2ab4df | ||
|
|
be972781ee | ||
|
|
58fbc298b1 | ||
|
|
7de7772339 | ||
|
|
ad847a2f5d | ||
|
|
856d52891c | ||
|
|
8de3b3bd8d | ||
|
|
0414483be2 | ||
|
|
22939aa472 | ||
|
|
0cb7c44985 | ||
|
|
b18a09e5eb | ||
|
|
ef3649b1d6 | ||
|
|
ac70a0d94d | ||
|
|
3b91f9b88b | ||
|
|
c37b780ca4 | ||
|
|
20061d2c65 | ||
|
|
f18fa77c1c | ||
|
|
a4c6869d4d | ||
|
|
f9a0070c82 | ||
|
|
5cc52f91cb | ||
|
|
a46b9fb2be | ||
|
|
933e38eca9 | ||
|
|
e182390480 | ||
|
|
563fdcba94 | ||
|
|
bc640834cd | ||
|
|
0e9e7d644a | ||
|
|
1d9b3ac2b5 | ||
|
|
aebed4a644 | ||
|
|
7bfb094a40 | ||
|
|
f90a44c1d0 | ||
|
|
dfcf6d2729 | ||
|
|
806a5daa86 | ||
|
|
4a3602099a | ||
|
|
c69be54655 | ||
|
|
680eaa1d4a | ||
|
|
9cc7b8bcc6 | ||
|
|
55d86d853a | ||
|
|
7d9f309e04 | ||
|
|
c2f0147cff | ||
|
|
05488e66ae | ||
|
|
09eac89086 | ||
|
|
866a0e7534 | ||
|
|
4307db11c5 | ||
|
|
3c8337cf54 | ||
|
|
883b4c4c26 | ||
|
|
6bc42c564d | ||
|
|
4f79f52524 | ||
|
|
83f8151ca4 | ||
|
|
d2b2e76a6a | ||
|
|
9d9109e9e5 | ||
|
|
18efb89b9a | ||
|
|
cefe883025 | ||
|
|
0ffa0b96d3 | ||
|
|
0429acfa1b | ||
|
|
827e3c1829 | ||
|
|
aa756ef194 | ||
|
|
d8aad65b24 | ||
|
|
1038e86196 | ||
|
|
47845fd4e3 | ||
|
|
294c3f10ab | ||
|
|
f6afc756dc | ||
|
|
64407e5ca6 | ||
|
|
0a42b0f61f | ||
|
|
ae493cbd0e | ||
|
|
ddd339851b | ||
|
|
82db986bd7 | ||
|
|
7bacd6f8f0 | ||
|
|
f0941f47dd | ||
|
|
c9d05b1117 | ||
|
|
7ee12752ec | ||
|
|
b44772441d | ||
|
|
72e3784fa5 | ||
|
|
2c7f24cb8c | ||
|
|
8a6c86bf65 | ||
|
|
cc52cf60dc | ||
|
|
398ebae2ba | ||
|
|
0095735841 | ||
|
|
95c10a1de7 | ||
|
|
8f4c92e251 | ||
|
|
58354061d8 | ||
|
|
5de176757d | ||
|
|
7414d52dc2 | ||
|
|
c42b5c8806 | ||
|
|
5c60da0f8f | ||
|
|
70d02e9a6d | ||
|
|
6401016424 | ||
|
|
cd031a89fb | ||
|
|
aadba79002 | ||
|
|
4a017fd8ae | ||
|
|
e6072c8fe9 | ||
|
|
f7fd99ec20 | ||
|
|
836fc13449 | ||
|
|
e1f78cd682 | ||
|
|
c69f34836a | ||
|
|
48a0db28d7 | ||
|
|
58eeb90158 | ||
|
|
c36689934d | ||
|
|
342575a576 | ||
|
|
785272540e | ||
|
|
653b985018 | ||
|
|
40d90a53b2 | ||
|
|
7c3aaff635 | ||
|
|
4bb7929229 | ||
|
|
bdd5b7b3a7 | ||
|
|
a2ddb56540 | ||
|
|
7c0097951c | ||
|
|
7970016fbf | ||
|
|
129e3b283d | ||
|
|
868eefd2cf | ||
|
|
82178055af | ||
|
|
c42e1a0ab4 | ||
|
|
720475dae5 | ||
|
|
68a328a364 | ||
|
|
3bc21cdb09 | ||
|
|
29cd63d3a7 | ||
|
|
8dbf456398 | ||
|
|
0ae1263d9d | ||
|
|
31f1ebe801 | ||
|
|
c6a9c9c57d | ||
|
|
707356bffe | ||
|
|
18aae8cf7b | ||
|
|
4a9bc69ac2 | ||
|
|
d97e62f864 | ||
|
|
1516807ed5 | ||
|
|
9893fd9ae5 | ||
|
|
d6c28da3a8 | ||
|
|
52f694a714 |
2
.babelrc
2
.babelrc
@@ -7,7 +7,7 @@
|
|||||||
"test": {
|
"test": {
|
||||||
"presets": ["env" ,"react", "es2015"],
|
"presets": ["env" ,"react", "es2015"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
[ "babel-plugin-webpack-alias", { "config": "${PWD}/webpack.config.js" } ]
|
[ "babel-plugin-webpack-alias", { "config": "<rootDir>/webpack.config.js" } ]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,9 @@
|
|||||||
"fontSize": "14",
|
"fontSize": "14",
|
||||||
"lineNumber": true
|
"lineNumber": true
|
||||||
},
|
},
|
||||||
"sortBy": "UPDATED_AT",
|
"sortBy": {
|
||||||
|
"default": "UPDATED_AT"
|
||||||
|
},
|
||||||
"sortTagsBy": "ALPHABETICAL",
|
"sortTagsBy": "ALPHABETICAL",
|
||||||
"ui": {
|
"ui": {
|
||||||
"defaultNote": "ALWAYS_ASK",
|
"defaultNote": "ALWAYS_ASK",
|
||||||
|
|||||||
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Space indentation
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
# The indent size used in the `package.json` file cannot be changed
|
||||||
|
# https://github.com/npm/npm/pull/3180#issuecomment-16336516
|
||||||
|
[{*.yml,*.yaml,package.json}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
11
.eslintrc
11
.eslintrc
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": ["standard", "standard-jsx", "plugin:react/recommended"],
|
"extends": ["standard", "standard-jsx", "plugin:react/recommended", "prettier"],
|
||||||
"plugins": ["react"],
|
"plugins": ["react", "prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-useless-escape": 0,
|
"no-useless-escape": 0,
|
||||||
"prefer-const": ["warn", {
|
"prefer-const": ["warn", {
|
||||||
@@ -13,12 +13,15 @@
|
|||||||
"react/no-string-refs": 0,
|
"react/no-string-refs": 0,
|
||||||
"react/no-find-dom-node": "warn",
|
"react/no-find-dom-node": "warn",
|
||||||
"react/no-render-return-value": "warn",
|
"react/no-render-return-value": "warn",
|
||||||
"react/no-deprecated": "warn"
|
"react/no-deprecated": "warn",
|
||||||
|
"prettier/prettier": ["error"]
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
"FileReader": true,
|
"FileReader": true,
|
||||||
"localStorage": true,
|
"localStorage": true,
|
||||||
"fetch": true
|
"fetch": true,
|
||||||
|
"Image": true,
|
||||||
|
"MutationObserver": true
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"jest": true
|
"jest": true
|
||||||
|
|||||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
issuehunt: BoostIo/Boostnote
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,4 +9,6 @@ node_modules/*
|
|||||||
/secret
|
/secret
|
||||||
*.log
|
*.log
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
package-lock.json
|
||||||
|
config.json
|
||||||
|
|||||||
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": false,
|
||||||
|
"jsxSingleQuote": true
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 7
|
- 8
|
||||||
script:
|
script:
|
||||||
- npm run lint && npm run test
|
- npm run lint && npm run test
|
||||||
- yarn jest
|
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@6.4 && grunt pre-build; fi'
|
||||||
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
|
|
||||||
after_success:
|
after_success:
|
||||||
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
||||||
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
|
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
|
||||||
|
|||||||
41
.vscode/launch.json
vendored
Normal file
41
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "BoostNote Main",
|
||||||
|
"protocol": "inspector",
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--remote-debugging-port=9223",
|
||||||
|
"--hot",
|
||||||
|
"${workspaceFolder}/index.js"
|
||||||
|
],
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "attach",
|
||||||
|
"name": "BoostNote Renderer",
|
||||||
|
"port": 9223,
|
||||||
|
"webRoot": "${workspaceFolder}",
|
||||||
|
"sourceMapPathOverrides": {
|
||||||
|
"webpack:///./~/*": "${webRoot}/node_modules/*",
|
||||||
|
"webpack:///*": "${webRoot}/*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "BostNote All",
|
||||||
|
"configurations": ["BoostNote Main", "BoostNote Renderer"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
27
.vscode/tasks.json
vendored
Normal file
27
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Build Boostnote",
|
||||||
|
"group": "build",
|
||||||
|
"type": "npm",
|
||||||
|
"script": "watch",
|
||||||
|
"isBackground": true,
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
},
|
||||||
|
"problemMatcher": {
|
||||||
|
"pattern":[
|
||||||
|
{
|
||||||
|
"regexp": "^([^\\\\s].*)\\\\((\\\\d+,\\\\d+)\\\\):\\\\s*(.*)$",
|
||||||
|
"file": 1,
|
||||||
|
"location": 2,
|
||||||
|
"message": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
72
Backers.md
72
Backers.md
@@ -1,72 +0,0 @@
|
|||||||
<h1 align="center">Sponsors & Backers</h1>
|
|
||||||
|
|
||||||
Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers. If you'd like to join them, please consider:
|
|
||||||
|
|
||||||
- [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Backers via OpenCollective
|
|
||||||
|
|
||||||
### [Gold Sponsors / $1,000 per month](https://opencollective.com/boostnoteio/order/2259)
|
|
||||||
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
|
|
||||||
|
|
||||||
### [Silver Sponsors / $250 per month](https://opencollective.com/boostnoteio/order/2257)
|
|
||||||
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
|
|
||||||
|
|
||||||
### [Bronze Sponsors / $50 per month](https://opencollective.com/boostnoteio/order/2258)
|
|
||||||
- Get your name and Url (or E-mail) on Readme.md on GitHub.
|
|
||||||
|
|
||||||
### [Backers3 / $10 per month](https://opencollective.com/boostnoteio/order/2176)
|
|
||||||
- [Ralph03](https://opencollective.com/ralph03)
|
|
||||||
|
|
||||||
- [Nikolas Dan](https://opencollective.com/nikolas-dan)
|
|
||||||
|
|
||||||
### [Backers2 / $5 per month](https://opencollective.com/boostnoteio/order/2175)
|
|
||||||
- [Yeojong Kim](https://twitter.com/yeojoy)
|
|
||||||
|
|
||||||
- [Scotia Draven](https://opencollective.com/scotia-draven)
|
|
||||||
|
|
||||||
- [A. J. Vargas](https://opencollective.com/aj-vargas)
|
|
||||||
|
|
||||||
### [Backers1](https://opencollective.com/boostnoteio/order/2563) and One-time sponsors
|
|
||||||
- Ryosuke Tamura - $30
|
|
||||||
|
|
||||||
- tatoosh11 - $10
|
|
||||||
|
|
||||||
- Alexander Borovkov - $10
|
|
||||||
|
|
||||||
- spoonhoop - $5
|
|
||||||
|
|
||||||
- Drew Williams - $2
|
|
||||||
|
|
||||||
- Andy Shaw - $2
|
|
||||||
|
|
||||||
- mysafesky -$2
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Backers via Bountysource
|
|
||||||
https://salt.bountysource.com/teams/boostnote
|
|
||||||
|
|
||||||
- Kuzz - $65
|
|
||||||
|
|
||||||
- Intense Raiden - $45
|
|
||||||
|
|
||||||
- ravy22 - $25
|
|
||||||
|
|
||||||
- trentpolack - $20
|
|
||||||
|
|
||||||
- hikariru - $10
|
|
||||||
|
|
||||||
- kolchan11 - $10
|
|
||||||
|
|
||||||
- RonWalker22 - $10
|
|
||||||
|
|
||||||
- hocchuc - $5
|
|
||||||
|
|
||||||
- Adam - $5
|
|
||||||
|
|
||||||
- Steve - $5
|
|
||||||
|
|
||||||
- evmin - $5
|
|
||||||
29
FAQ.md
Normal file
29
FAQ.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Frequently Asked Questions
|
||||||
|
|
||||||
|
|
||||||
|
<details><summary>Allowing dangerous HTML tags</summary>
|
||||||
|
|
||||||
|
Sometimes it is useful to allow dangerous HTML tags to add interactivity to your notebook. One of the example is to use details/summary as a way to expand/collaps your todo-list.
|
||||||
|
|
||||||
|
* How to enable:
|
||||||
|
* Go to **Preferences** → **Interface** → **Sanitization** → **Allow dangerous html tags**
|
||||||
|
* Example note: Multiple todo-list
|
||||||
|
* Create new notes
|
||||||
|
* Paste the below code, and you'll see that you can expand/collaps the todo-list, and you can have multiple todo-list in your note.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<details><summary>What I want to do</summary>
|
||||||
|
|
||||||
|
- [x] Create an awesome feature X
|
||||||
|
- [ ] Do my homework
|
||||||
|
|
||||||
|
</details>
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Other questions
|
||||||
|
|
||||||
|
You can ask [here][ISSUES]
|
||||||
|
|
||||||
|
[ISSUES]: https://github.com/BoostIO/Boostnote/issues
|
||||||
@@ -1,25 +1,35 @@
|
|||||||
# Current behavior
|
# Current behavior
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug.
|
Let us know what is currently happening.
|
||||||
|
|
||||||
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile.
|
Please include some **screenshots** with the **developer tools** open (console tab) when you report a bug.
|
||||||
|
|
||||||
|
If your issue is regarding the new Boost Note.next, please open an issue in the new repo 👉 https://github.com/BoostIO/BoostNote.next/issues.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# Expected behavior
|
# Expected behavior
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Let us know what you think should happen.
|
||||||
|
-->
|
||||||
|
|
||||||
# Steps to reproduce
|
# Steps to reproduce
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please be thorough, issues we can reproduce are easier to fix.
|
||||||
|
-->
|
||||||
|
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
3.
|
3.
|
||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
|
|
||||||
- Version :
|
- Boostnote version: <!-- 0.x.x -->
|
||||||
- OS Version and name :
|
- OS version and name: <!-- Windows 10 / Ubuntu 18.04 / etc -->
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Love Boostnote? Please consider supporting us via OpenCollective:
|
Love Boostnote? Please consider supporting us on IssueHunt:
|
||||||
👉 https://opencollective.com/boostnoteio
|
👉 https://issuehunt.io/repos/53266139
|
||||||
-->
|
-->
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -2,7 +2,7 @@ GPL-3.0
|
|||||||
|
|
||||||
Boostnote - an open source note-taking app made for programmers just like you.
|
Boostnote - an open source note-taking app made for programmers just like you.
|
||||||
|
|
||||||
Copyright (C) 2017 - 2018 BoostIO
|
Copyright (C) 2017 - 2019 BoostIO
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
42
PULL_REQUEST_TEMPLATE.md
Normal file
42
PULL_REQUEST_TEMPLATE.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!--
|
||||||
|
Before submitting this PR, please make sure that:
|
||||||
|
- You have read and understand the contributing.md
|
||||||
|
- You have checked docs/code_style.md for information on code style
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Tell us what your PR does.
|
||||||
|
Please attach a screenshot/ video/gif image describing your PR if possible.
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Issue fixed
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please list out all issue fixed with this PR here.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please make sure you fill in these checkboxes,
|
||||||
|
your PR will be reviewed faster if we know exactly what it does.
|
||||||
|
|
||||||
|
Change :white_circle: to :radio_button: in all the options that apply
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Type of changes
|
||||||
|
|
||||||
|
- :white_circle: Bug fix (Change that fixed an issue)
|
||||||
|
- :white_circle: Breaking change (Change that can cause existing functionality to change)
|
||||||
|
- :white_circle: Improvement (Change that improves the code. Maybe performance or development improvement)
|
||||||
|
- :white_circle: Feature (Change that adds new functionality)
|
||||||
|
- :white_circle: Documentation change (Change that modifies documentation. Maybe typo fixes)
|
||||||
|
|
||||||
|
## Checklist:
|
||||||
|
|
||||||
|
- :white_circle: My code follows [the project code style](docs/code_style.md)
|
||||||
|
- :white_circle: I have written test for my code and it has been tested
|
||||||
|
- :white_circle: All existing tests have been passed
|
||||||
|
- :white_circle: I have attached a screenshot/video to visualize my change if possible
|
||||||
|
- :white_circle: This PR will modify the UI or affects the UX
|
||||||
|
- :white_circle: This PR will add/update/delete a keybinding
|
||||||
File diff suppressed because it is too large
Load Diff
5
browser/components/CodeEditor.styl
Normal file
5
browser/components/CodeEditor.styl
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.codeEditor-typo
|
||||||
|
text-decoration underline wavy red
|
||||||
|
|
||||||
|
.spellcheck-select
|
||||||
|
border: none
|
||||||
77
browser/components/ColorPicker.js
Normal file
77
browser/components/ColorPicker.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { SketchPicker } from 'react-color'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './ColorPicker.styl'
|
||||||
|
|
||||||
|
const componentHeight = 330
|
||||||
|
|
||||||
|
class ColorPicker extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
color: this.props.color || '#939395'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onColorChange = this.onColorChange.bind(this)
|
||||||
|
this.handleConfirm = this.handleConfirm.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
this.onColorChange(nextProps.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
onColorChange(color) {
|
||||||
|
this.setState({
|
||||||
|
color
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConfirm() {
|
||||||
|
this.props.onConfirm(this.state.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { onReset, onCancel, targetRect } = this.props
|
||||||
|
const { color } = this.state
|
||||||
|
|
||||||
|
const clientHeight = document.body.clientHeight
|
||||||
|
const alignX = targetRect.right + 4
|
||||||
|
let alignY = targetRect.top
|
||||||
|
if (targetRect.top + componentHeight > clientHeight) {
|
||||||
|
alignY = targetRect.bottom - componentHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
styleName='colorPicker'
|
||||||
|
style={{ top: `${alignY}px`, left: `${alignX}px` }}
|
||||||
|
>
|
||||||
|
<div styleName='cover' onClick={onCancel} />
|
||||||
|
<SketchPicker color={color} onChange={this.onColorChange} />
|
||||||
|
<div styleName='footer'>
|
||||||
|
<button styleName='btn-reset' onClick={onReset}>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
<button styleName='btn-cancel' onClick={onCancel}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button styleName='btn-confirm' onClick={this.handleConfirm}>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColorPicker.propTypes = {
|
||||||
|
color: PropTypes.string,
|
||||||
|
targetRect: PropTypes.object,
|
||||||
|
onConfirm: PropTypes.func.isRequired,
|
||||||
|
onCancel: PropTypes.func.isRequired,
|
||||||
|
onReset: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(ColorPicker, styles)
|
||||||
39
browser/components/ColorPicker.styl
Normal file
39
browser/components/ColorPicker.styl
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
.colorPicker
|
||||||
|
position fixed
|
||||||
|
z-index 2
|
||||||
|
display flex
|
||||||
|
flex-direction column
|
||||||
|
|
||||||
|
.cover
|
||||||
|
position fixed
|
||||||
|
top 0
|
||||||
|
right 0
|
||||||
|
bottom 0
|
||||||
|
left 0
|
||||||
|
|
||||||
|
.footer
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
z-index 2
|
||||||
|
align-items center
|
||||||
|
& > button + button
|
||||||
|
margin-left 10px
|
||||||
|
|
||||||
|
.btn-cancel,
|
||||||
|
.btn-confirm,
|
||||||
|
.btn-reset
|
||||||
|
vertical-align middle
|
||||||
|
height 25px
|
||||||
|
margin-top 2.5px
|
||||||
|
border-radius 2px
|
||||||
|
border none
|
||||||
|
padding 0 5px
|
||||||
|
background-color $default-button-background
|
||||||
|
&:hover
|
||||||
|
background-color $default-button-background--hover
|
||||||
|
.btn-confirm
|
||||||
|
background-color #1EC38B
|
||||||
|
&:hover
|
||||||
|
background-color darken(#1EC38B, 25%)
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable camelcase */
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
@@ -6,9 +7,11 @@ import CodeEditor from 'browser/components/CodeEditor'
|
|||||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import { findStorage } from 'browser/lib/findStorage'
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||||
|
|
||||||
class MarkdownEditor extends React.Component {
|
class MarkdownEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
// char codes for ctrl + w
|
// char codes for ctrl + w
|
||||||
@@ -18,186 +21,246 @@ class MarkdownEditor extends React.Component {
|
|||||||
this.supportMdSelectionBold = [16, 17, 186]
|
this.supportMdSelectionBold = [16, 17, 186]
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
status: 'PREVIEW',
|
status:
|
||||||
|
props.config.editor.switchPreview === 'RIGHTCLICK'
|
||||||
|
? props.config.editor.delfaultStatus
|
||||||
|
: 'CODE',
|
||||||
renderValue: props.value,
|
renderValue: props.value,
|
||||||
keyPressed: new Set(),
|
keyPressed: new Set(),
|
||||||
isLocked: false
|
isLocked: props.isLocked
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lockEditorCode = () => this.handleLockEditor()
|
this.lockEditorCode = this.handleLockEditor.bind(this)
|
||||||
|
this.focusEditor = this.focusEditor.bind(this)
|
||||||
|
|
||||||
|
this.previewRef = React.createRef()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
eventEmitter.on('editor:lock', this.lockEditorCode)
|
eventEmitter.on('editor:lock', this.lockEditorCode)
|
||||||
|
eventEmitter.on('editor:focus', this.focusEditor)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate() {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (props) {
|
UNSAFE_componentWillReceiveProps(props) {
|
||||||
if (props.value !== this.props.value) {
|
if (props.value !== this.props.value) {
|
||||||
this.queueRendering(props.value)
|
this.queueRendering(props.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
this.cancelQueue()
|
this.cancelQueue()
|
||||||
eventEmitter.off('editor:lock', this.lockEditorCode)
|
eventEmitter.off('editor:lock', this.lockEditorCode)
|
||||||
|
eventEmitter.off('editor:focus', this.focusEditor)
|
||||||
}
|
}
|
||||||
|
|
||||||
queueRendering (value) {
|
focusEditor() {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
status: 'CODE'
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if (this.refs.code == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.refs.code.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
queueRendering(value) {
|
||||||
clearTimeout(this.renderTimer)
|
clearTimeout(this.renderTimer)
|
||||||
this.renderTimer = setTimeout(() => {
|
this.renderTimer = setTimeout(() => {
|
||||||
this.renderPreview(value)
|
this.renderPreview(value)
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelQueue () {
|
cancelQueue() {
|
||||||
clearTimeout(this.renderTimer)
|
clearTimeout(this.renderTimer)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPreview (value) {
|
renderPreview(value) {
|
||||||
this.setState({
|
this.setState({
|
||||||
renderValue: value
|
renderValue: value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleChange (e) {
|
setValue(value) {
|
||||||
|
this.refs.code.setValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(e) {
|
||||||
this.value = this.refs.code.value
|
this.value = this.refs.code.value
|
||||||
this.props.onChange(e)
|
this.props.onChange(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu(e) {
|
||||||
|
if (this.state.isLocked) return
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
||||||
const newStatus = this.state.status === 'PREVIEW'
|
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
|
||||||
? 'CODE'
|
this.setState(
|
||||||
: 'PREVIEW'
|
{
|
||||||
this.setState({
|
status: newStatus
|
||||||
status: newStatus
|
},
|
||||||
}, () => {
|
() => {
|
||||||
if (newStatus === 'CODE') {
|
if (newStatus === 'CODE') {
|
||||||
this.refs.code.focus()
|
this.refs.code.focus()
|
||||||
} else {
|
} else {
|
||||||
this.refs.preview.focus()
|
this.previewRef.current.focus()
|
||||||
|
}
|
||||||
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
|
|
||||||
|
const newConfig = Object.assign({}, config)
|
||||||
|
newConfig.editor.delfaultStatus = newStatus
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
}
|
}
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlur (e) {
|
handleBlur(e) {
|
||||||
if (this.state.isLocked) return
|
if (this.state.isLocked) return
|
||||||
this.setState({ keyPressed: new Set() })
|
this.setState({ keyPressed: new Set() })
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'BLUR' ||
|
if (
|
||||||
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
|
config.editor.switchPreview === 'BLUR' ||
|
||||||
|
(config.editor.switchPreview === 'DBL_CLICK' &&
|
||||||
|
this.state.status === 'CODE')
|
||||||
) {
|
) {
|
||||||
const cursorPosition = this.refs.code.editor.getCursor()
|
const cursorPosition = this.refs.code.editor.getCursor()
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'PREVIEW'
|
{
|
||||||
}, () => {
|
status: 'PREVIEW'
|
||||||
this.refs.preview.focus()
|
},
|
||||||
this.refs.preview.scrollTo(cursorPosition.line)
|
() => {
|
||||||
})
|
this.previewRef.current.focus()
|
||||||
|
this.previewRef.current.scrollToRow(cursorPosition.line)
|
||||||
|
}
|
||||||
|
)
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDoubleClick (e) {
|
handleDoubleClick(e) {
|
||||||
if (this.state.isLocked) return
|
if (this.state.isLocked) return
|
||||||
this.setState({keyPressed: new Set()})
|
this.setState({ keyPressed: new Set() })
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'DBL_CLICK') {
|
if (config.editor.switchPreview === 'DBL_CLICK') {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'CODE'
|
{
|
||||||
}, () => {
|
status: 'CODE'
|
||||||
this.refs.code.focus()
|
},
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
() => {
|
||||||
})
|
this.refs.code.focus()
|
||||||
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePreviewMouseDown (e) {
|
handlePreviewMouseDown(e) {
|
||||||
this.previewMouseDownedAt = new Date()
|
this.previewMouseDownedAt = new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePreviewMouseUp (e) {
|
handlePreviewMouseUp(e) {
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
|
if (
|
||||||
this.setState({
|
config.editor.switchPreview === 'BLUR' &&
|
||||||
status: 'CODE'
|
new Date() - this.previewMouseDownedAt < 200
|
||||||
}, () => {
|
) {
|
||||||
this.refs.code.focus()
|
this.setState(
|
||||||
})
|
{
|
||||||
|
status: 'CODE'
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.refs.code.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCheckboxClick (e) {
|
handleCheckboxClick(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
const idMatch = /checkbox-([0-9]+)/
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
const checkedMatch = /\[x\]/i
|
const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
|
||||||
const uncheckedMatch = /\[ \]/
|
const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
|
||||||
|
const checkReplace = /\[x]/i
|
||||||
|
const uncheckReplace = /\[ ]/
|
||||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
const lineIndex =
|
||||||
const lines = this.refs.code.value
|
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
.split('\n')
|
const lines = this.refs.code.value.split('\n')
|
||||||
|
|
||||||
const targetLine = lines[lineIndex]
|
const targetLine = lines[lineIndex]
|
||||||
|
let newLine = targetLine
|
||||||
|
|
||||||
if (targetLine.match(checkedMatch)) {
|
if (targetLine.match(checkedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
newLine = targetLine.replace(checkReplace, '[ ]')
|
||||||
}
|
}
|
||||||
if (targetLine.match(uncheckedMatch)) {
|
if (targetLine.match(uncheckedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
|
newLine = targetLine.replace(uncheckReplace, '[x]')
|
||||||
}
|
}
|
||||||
this.refs.code.setValue(lines.join('\n'))
|
this.refs.code.setLineContent(lineIndex, newLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus() {
|
||||||
if (this.state.status === 'PREVIEW') {
|
if (this.state.status === 'PREVIEW') {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'CODE'
|
{
|
||||||
}, () => {
|
status: 'CODE'
|
||||||
this.refs.code.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.code.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
this.refs.code.focus()
|
this.refs.code.focus()
|
||||||
}
|
}
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
reload () {
|
reload() {
|
||||||
this.refs.code.reload()
|
this.refs.code.reload()
|
||||||
this.cancelQueue()
|
this.cancelQueue()
|
||||||
this.renderPreview(this.props.value)
|
this.renderPreview(this.props.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown(e) {
|
||||||
const { config } = this.props
|
const { config } = this.props
|
||||||
if (this.state.status !== 'CODE') return false
|
if (this.state.status !== 'CODE') return false
|
||||||
const keyPressed = this.state.keyPressed
|
const keyPressed = this.state.keyPressed
|
||||||
keyPressed.add(e.keyCode)
|
keyPressed.add(e.keyCode)
|
||||||
this.setState({ keyPressed })
|
this.setState({ keyPressed })
|
||||||
const isNoteHandlerKey = (el) => { return keyPressed.has(el) }
|
const isNoteHandlerKey = el => {
|
||||||
|
return keyPressed.has(el)
|
||||||
|
}
|
||||||
// These conditions are for ctrl-e and ctrl-w
|
// These conditions are for ctrl-e and ctrl-w
|
||||||
if (keyPressed.size === this.escapeFromEditor.length &&
|
if (
|
||||||
!this.state.isLocked && this.state.status === 'CODE' &&
|
keyPressed.size === this.escapeFromEditor.length &&
|
||||||
this.escapeFromEditor.every(isNoteHandlerKey)) {
|
!this.state.isLocked &&
|
||||||
|
this.state.status === 'CODE' &&
|
||||||
|
this.escapeFromEditor.every(isNoteHandlerKey)
|
||||||
|
) {
|
||||||
this.handleContextMenu()
|
this.handleContextMenu()
|
||||||
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
|
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
|
||||||
}
|
}
|
||||||
if (keyPressed.size === this.supportMdSelectionBold.length && this.supportMdSelectionBold.every(isNoteHandlerKey)) {
|
if (
|
||||||
|
keyPressed.size === this.supportMdSelectionBold.length &&
|
||||||
|
this.supportMdSelectionBold.every(isNoteHandlerKey)
|
||||||
|
) {
|
||||||
this.addMdAroundWord('**')
|
this.addMdAroundWord('**')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addMdAroundWord (mdElement) {
|
addMdAroundWord(mdElement) {
|
||||||
if (this.refs.code.editor.getSelection()) {
|
if (this.refs.code.editor.getSelection()) {
|
||||||
return this.addMdAroundSelection(mdElement)
|
return this.addMdAroundSelection(mdElement)
|
||||||
}
|
}
|
||||||
@@ -205,25 +268,63 @@ class MarkdownEditor extends React.Component {
|
|||||||
const word = this.refs.code.editor.findWordAt(currentCaret)
|
const word = this.refs.code.editor.findWordAt(currentCaret)
|
||||||
const cmDoc = this.refs.code.editor.getDoc()
|
const cmDoc = this.refs.code.editor.getDoc()
|
||||||
cmDoc.replaceRange(mdElement, word.anchor)
|
cmDoc.replaceRange(mdElement, word.anchor)
|
||||||
cmDoc.replaceRange(mdElement, { line: word.head.line, ch: word.head.ch + mdElement.length })
|
cmDoc.replaceRange(mdElement, {
|
||||||
|
line: word.head.line,
|
||||||
|
ch: word.head.ch + mdElement.length
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
addMdAroundSelection (mdElement) {
|
addMdAroundSelection(mdElement) {
|
||||||
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
|
this.refs.code.editor.replaceSelection(
|
||||||
|
`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyUp (e) {
|
handleDropImage(dropEvent) {
|
||||||
|
dropEvent.preventDefault()
|
||||||
|
const { storageKey, noteKey } = this.props
|
||||||
|
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
status: 'CODE'
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.refs.code.focus()
|
||||||
|
|
||||||
|
this.refs.code.editor.execCommand('goDocEnd')
|
||||||
|
this.refs.code.editor.execCommand('goLineEnd')
|
||||||
|
this.refs.code.editor.execCommand('newlineAndIndent')
|
||||||
|
|
||||||
|
attachmentManagement.handleAttachmentDrop(
|
||||||
|
this.refs.code,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
dropEvent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyUp(e) {
|
||||||
const keyPressed = this.state.keyPressed
|
const keyPressed = this.state.keyPressed
|
||||||
keyPressed.delete(e.keyCode)
|
keyPressed.delete(e.keyCode)
|
||||||
this.setState({ keyPressed })
|
this.setState({ keyPressed })
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLockEditor () {
|
handleLockEditor() {
|
||||||
this.setState({ isLocked: !this.state.isLocked })
|
this.setState({ isLocked: !this.state.isLocked })
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {className, value, config, storageKey, noteKey} = this.props
|
const {
|
||||||
|
className,
|
||||||
|
value,
|
||||||
|
config,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
linesHighlighted,
|
||||||
|
RTL
|
||||||
|
} = this.props
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
@@ -231,26 +332,27 @@ class MarkdownEditor extends React.Component {
|
|||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
|
||||||
const previewStyle = {}
|
const previewStyle = {}
|
||||||
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
if (this.props.ignorePreviewPointerEvents)
|
||||||
|
previewStyle.pointerEvents = 'none'
|
||||||
|
|
||||||
const storage = findStorage(storageKey)
|
const storage = findStorage(storageKey)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className == null
|
<div
|
||||||
? 'MarkdownEditor'
|
className={
|
||||||
: `MarkdownEditor ${className}`
|
className == null ? 'MarkdownEditor' : `MarkdownEditor ${className}`
|
||||||
}
|
}
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={e => this.handleContextMenu(e)}
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
onKeyDown={e => this.handleKeyDown(e)}
|
||||||
onKeyUp={(e) => this.handleKeyUp(e)}
|
onKeyUp={e => this.handleKeyUp(e)}
|
||||||
>
|
>
|
||||||
<CodeEditor styleName={this.state.status === 'CODE'
|
<CodeEditor
|
||||||
? 'codeEditor'
|
styleName={
|
||||||
: 'codeEditor--hide'
|
this.state.status === 'CODE' ? 'codeEditor' : 'codeEditor--hide'
|
||||||
}
|
}
|
||||||
ref='code'
|
ref='code'
|
||||||
mode='GitHub Flavored Markdown'
|
mode='Boost Flavored Markdown'
|
||||||
value={value}
|
value={value}
|
||||||
theme={config.editor.theme}
|
theme={config.editor.theme}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
@@ -261,16 +363,32 @@ class MarkdownEditor extends React.Component {
|
|||||||
enableRulers={config.editor.enableRulers}
|
enableRulers={config.editor.enableRulers}
|
||||||
rulers={config.editor.rulers}
|
rulers={config.editor.rulers}
|
||||||
displayLineNumbers={config.editor.displayLineNumbers}
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
|
lineWrapping
|
||||||
|
matchingPairs={config.editor.matchingPairs}
|
||||||
|
matchingTriples={config.editor.matchingTriples}
|
||||||
|
explodingPairs={config.editor.explodingPairs}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
onChange={(e) => this.handleChange(e)}
|
enableTableEditor={config.editor.enableTableEditor}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
linesHighlighted={linesHighlighted}
|
||||||
|
onChange={e => this.handleChange(e)}
|
||||||
|
onBlur={e => this.handleBlur(e)}
|
||||||
|
spellCheck={config.editor.spellcheck}
|
||||||
|
enableSmartPaste={config.editor.enableSmartPaste}
|
||||||
|
hotkey={config.hotkey}
|
||||||
|
switchPreview={config.editor.switchPreview}
|
||||||
|
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||||
|
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||||
|
prettierConfig={config.editor.prettierConfig}
|
||||||
|
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||||
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
<MarkdownPreview
|
||||||
? 'preview'
|
ref={this.previewRef}
|
||||||
: 'preview--hide'
|
styleName={
|
||||||
|
this.state.status === 'PREVIEW' ? 'preview' : 'preview--hide'
|
||||||
}
|
}
|
||||||
style={previewStyle}
|
style={previewStyle}
|
||||||
theme={config.ui.theme}
|
theme={config.ui.theme}
|
||||||
@@ -286,19 +404,22 @@ class MarkdownEditor extends React.Component {
|
|||||||
smartArrows={config.preview.smartArrows}
|
smartArrows={config.preview.smartArrows}
|
||||||
breaks={config.preview.breaks}
|
breaks={config.preview.breaks}
|
||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={e => this.handleContextMenu(e)}
|
||||||
onDoubleClick={(e) => this.handleDoubleClick(e)}
|
onDoubleClick={e => this.handleDoubleClick(e)}
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
value={this.state.renderValue}
|
value={this.state.renderValue}
|
||||||
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
|
onMouseUp={e => this.handlePreviewMouseUp(e)}
|
||||||
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
|
onMouseDown={e => this.handlePreviewMouseDown(e)}
|
||||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
onCheckboxClick={e => this.handleCheckboxClick(e)}
|
||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
customCSS={config.preview.customCSS}
|
customCSS={config.preview.customCSS}
|
||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
|
onDrop={e => this.handleDropImage(e)}
|
||||||
|
RTL={RTL}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
.preview
|
.preview
|
||||||
display block
|
display block
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
z-index 100
|
|
||||||
background-color white
|
background-color white
|
||||||
height 100%
|
height 100%
|
||||||
width 100%
|
width 100%
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ import styles from './MarkdownSplitEditor.styl'
|
|||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
class MarkdownSplitEditor extends React.Component {
|
class MarkdownSplitEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.value = props.value
|
this.value = props.value
|
||||||
this.focus = () => this.refs.code.focus()
|
this.focus = () => this.refs.code.focus()
|
||||||
@@ -16,17 +16,27 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
this.userScroll = true
|
this.userScroll = true
|
||||||
this.state = {
|
this.state = {
|
||||||
isSliderFocused: false,
|
isSliderFocused: false,
|
||||||
codeEditorWidthInPercent: 50
|
codeEditorWidthInPercent: 50,
|
||||||
|
codeEditorHeightInPercent: 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOnChange () {
|
setValue(value) {
|
||||||
this.value = this.refs.code.value
|
this.refs.code.setValue(value)
|
||||||
this.props.onChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll (e) {
|
handleOnChange(e) {
|
||||||
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
|
this.value = this.refs.code.value
|
||||||
|
this.props.onChange(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll(e) {
|
||||||
|
if (!this.props.config.preview.scrollSync) return
|
||||||
|
|
||||||
|
const previewDoc = _.get(
|
||||||
|
this,
|
||||||
|
'refs.preview.refs.root.contentWindow.document'
|
||||||
|
)
|
||||||
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||||
let srcTop, srcHeight, targetTop, targetHeight
|
let srcTop, srcHeight, targetTop, targetHeight
|
||||||
|
|
||||||
@@ -43,7 +53,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
targetHeight = _.get(codeDoc, 'height')
|
targetHeight = _.get(codeDoc, 'height')
|
||||||
}
|
}
|
||||||
|
|
||||||
const distance = (targetHeight * srcTop / srcHeight) - targetTop
|
const distance = (targetHeight * srcTop) / srcHeight - targetTop
|
||||||
const framerate = 1000 / 60
|
const framerate = 1000 / 60
|
||||||
const frames = 20
|
const frames = 20
|
||||||
const refractory = frames * framerate
|
const refractory = frames * framerate
|
||||||
@@ -54,121 +64,235 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
let scrollPos, time
|
let scrollPos, time
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
time = frame / frames
|
time = frame / frames
|
||||||
scrollPos = time < 0.5
|
scrollPos =
|
||||||
? 2 * time * time // ease in
|
time < 0.5
|
||||||
: -1 + (4 - 2 * time) * time // ease out
|
? 2 * time * time // ease in
|
||||||
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
: -1 + (4 - 2 * time) * time // ease out
|
||||||
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
|
if (e.doc)
|
||||||
|
_.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
||||||
|
else
|
||||||
|
_.get(this, 'refs.code.editor').scrollTo(
|
||||||
|
0,
|
||||||
|
targetTop + scrollPos * distance
|
||||||
|
)
|
||||||
if (frame >= frames) {
|
if (frame >= frames) {
|
||||||
clearInterval(timer)
|
clearInterval(timer)
|
||||||
setTimeout(() => { this.userScroll = true }, refractory)
|
setTimeout(() => {
|
||||||
|
this.userScroll = true
|
||||||
|
}, refractory)
|
||||||
}
|
}
|
||||||
frame++
|
frame++
|
||||||
}, framerate)
|
}, framerate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCheckboxClick (e) {
|
handleCheckboxClick(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
const idMatch = /checkbox-([0-9]+)/
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
const checkedMatch = /\[x\]/i
|
const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
|
||||||
const uncheckedMatch = /\[ \]/
|
const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
|
||||||
|
const checkReplace = /\[x]/i
|
||||||
|
const uncheckReplace = /\[ ]/
|
||||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
const lineIndex =
|
||||||
const lines = this.refs.code.value
|
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
.split('\n')
|
const lines = this.refs.code.value.split('\n')
|
||||||
|
|
||||||
const targetLine = lines[lineIndex]
|
const targetLine = lines[lineIndex]
|
||||||
|
let newLine = targetLine
|
||||||
|
|
||||||
if (targetLine.match(checkedMatch)) {
|
if (targetLine.match(checkedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
newLine = targetLine.replace(checkReplace, '[ ]')
|
||||||
}
|
}
|
||||||
if (targetLine.match(uncheckedMatch)) {
|
if (targetLine.match(uncheckedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
|
newLine = targetLine.replace(uncheckReplace, '[x]')
|
||||||
}
|
}
|
||||||
this.refs.code.setValue(lines.join('\n'))
|
this.refs.code.setLineContent(lineIndex, newLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove (e) {
|
handleMouseMove(e) {
|
||||||
if (this.state.isSliderFocused) {
|
if (this.state.isSliderFocused) {
|
||||||
const rootRect = this.refs.root.getBoundingClientRect()
|
const rootRect = this.refs.root.getBoundingClientRect()
|
||||||
const rootWidth = rootRect.width
|
if (this.props.isStacking) {
|
||||||
const offset = rootRect.left
|
const rootHeight = rootRect.height
|
||||||
let newCodeEditorWidthInPercent = (e.pageX - offset) / rootWidth * 100
|
const offset = rootRect.top
|
||||||
|
let newCodeEditorHeightInPercent =
|
||||||
|
((e.pageY - offset) / rootHeight) * 100
|
||||||
|
|
||||||
// limit minSize to 10%, maxSize to 90%
|
// limit minSize to 10%, maxSize to 90%
|
||||||
if (newCodeEditorWidthInPercent <= 10) {
|
if (newCodeEditorHeightInPercent <= 10) {
|
||||||
newCodeEditorWidthInPercent = 10
|
newCodeEditorHeightInPercent = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCodeEditorHeightInPercent >= 90) {
|
||||||
|
newCodeEditorHeightInPercent = 90
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
codeEditorHeightInPercent: newCodeEditorHeightInPercent
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const rootWidth = rootRect.width
|
||||||
|
const offset = rootRect.left
|
||||||
|
let newCodeEditorWidthInPercent = ((e.pageX - offset) / rootWidth) * 100
|
||||||
|
|
||||||
|
// limit minSize to 10%, maxSize to 90%
|
||||||
|
if (newCodeEditorWidthInPercent <= 10) {
|
||||||
|
newCodeEditorWidthInPercent = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCodeEditorWidthInPercent >= 90) {
|
||||||
|
newCodeEditorWidthInPercent = 90
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
codeEditorWidthInPercent: newCodeEditorWidthInPercent
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newCodeEditorWidthInPercent >= 90) {
|
|
||||||
newCodeEditorWidthInPercent = 90
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
codeEditorWidthInPercent: newCodeEditorWidthInPercent
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.setState({
|
this.setState({
|
||||||
isSliderFocused: false
|
isSliderFocused: false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
handleMouseDown(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.setState({
|
this.setState({
|
||||||
isSliderFocused: true
|
isSliderFocused: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {config, value, storageKey, noteKey} = this.props
|
const {
|
||||||
const storage = findStorage(storageKey)
|
config,
|
||||||
|
value,
|
||||||
|
storageKey,
|
||||||
|
noteKey,
|
||||||
|
linesHighlighted,
|
||||||
|
isStacking,
|
||||||
|
RTL
|
||||||
|
} = this.props
|
||||||
|
let storage
|
||||||
|
try {
|
||||||
|
storage = findStorage(storageKey)
|
||||||
|
} catch (e) {
|
||||||
|
return <div />
|
||||||
|
}
|
||||||
|
|
||||||
|
let editorStyle = {}
|
||||||
|
let previewStyle = {}
|
||||||
|
let sliderStyle = {}
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
|
editorStyle.fontSize = editorFontSize
|
||||||
|
|
||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorStyle.fontSize > 0 && editorStyle.fontSize < 132))
|
||||||
const previewStyle = {}
|
editorIndentSize = 4
|
||||||
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
|
editorStyle.indentSize = editorIndentSize
|
||||||
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
|
|
||||||
|
editorStyle = Object.assign(
|
||||||
|
editorStyle,
|
||||||
|
isStacking
|
||||||
|
? {
|
||||||
|
width: '100%',
|
||||||
|
height: `${this.state.codeEditorHeightInPercent}%`
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
width: `${this.state.codeEditorWidthInPercent}%`,
|
||||||
|
height: '100%'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
previewStyle = Object.assign(
|
||||||
|
previewStyle,
|
||||||
|
isStacking
|
||||||
|
? {
|
||||||
|
width: '100%',
|
||||||
|
height: `${100 - this.state.codeEditorHeightInPercent}%`
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
width: `${100 - this.state.codeEditorWidthInPercent}%`,
|
||||||
|
height: '100%'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
sliderStyle = Object.assign(
|
||||||
|
sliderStyle,
|
||||||
|
isStacking
|
||||||
|
? {
|
||||||
|
left: 0,
|
||||||
|
top: `${this.state.codeEditorHeightInPercent}%`
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
left: `${this.state.codeEditorWidthInPercent}%`,
|
||||||
|
top: 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused)
|
||||||
|
previewStyle.pointerEvents = 'none'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div styleName='root' ref='root'
|
<div
|
||||||
|
styleName='root'
|
||||||
|
ref='root'
|
||||||
onMouseMove={e => this.handleMouseMove(e)}
|
onMouseMove={e => this.handleMouseMove(e)}
|
||||||
onMouseUp={e => this.handleMouseUp(e)}>
|
onMouseUp={e => this.handleMouseUp(e)}
|
||||||
|
>
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
styleName='codeEditor'
|
|
||||||
ref='code'
|
ref='code'
|
||||||
width={this.state.codeEditorWidthInPercent + '%'}
|
width={editorStyle.width}
|
||||||
mode='GitHub Flavored Markdown'
|
height={editorStyle.height}
|
||||||
|
mode='Boost Flavored Markdown'
|
||||||
value={value}
|
value={value}
|
||||||
theme={config.editor.theme}
|
theme={config.editor.theme}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
fontFamily={config.editor.fontFamily}
|
fontFamily={config.editor.fontFamily}
|
||||||
fontSize={editorFontSize}
|
fontSize={editorStyle.fontSize}
|
||||||
displayLineNumbers={config.editor.displayLineNumbers}
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
|
lineWrapping
|
||||||
|
matchingPairs={config.editor.matchingPairs}
|
||||||
|
matchingTriples={config.editor.matchingTriples}
|
||||||
|
explodingPairs={config.editor.explodingPairs}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorStyle.indentSize}
|
||||||
enableRulers={config.editor.enableRulers}
|
enableRulers={config.editor.enableRulers}
|
||||||
rulers={config.editor.rulers}
|
rulers={config.editor.rulers}
|
||||||
scrollPastEnd={config.editor.scrollPastEnd}
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
fetchUrlTitle={config.editor.fetchUrlTitle}
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
|
enableTableEditor={config.editor.enableTableEditor}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
onChange={this.handleOnChange.bind(this)}
|
linesHighlighted={linesHighlighted}
|
||||||
|
onChange={e => this.handleOnChange(e)}
|
||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={this.handleScroll.bind(this)}
|
||||||
/>
|
spellCheck={config.editor.spellcheck}
|
||||||
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
enableSmartPaste={config.editor.enableSmartPaste}
|
||||||
|
hotkey={config.hotkey}
|
||||||
|
switchPreview={config.editor.switchPreview}
|
||||||
|
enableMarkdownLint={config.editor.enableMarkdownLint}
|
||||||
|
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
|
||||||
|
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
|
||||||
|
RTL={RTL}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
styleName={isStacking ? 'slider-hoz' : 'slider'}
|
||||||
|
style={{ left: sliderStyle.left, top: sliderStyle.top }}
|
||||||
|
onMouseDown={e => this.handleMouseDown(e)}
|
||||||
|
>
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
</div>
|
</div>
|
||||||
<MarkdownPreview
|
<MarkdownPreview
|
||||||
|
ref='preview'
|
||||||
style={previewStyle}
|
style={previewStyle}
|
||||||
styleName='preview'
|
|
||||||
theme={config.ui.theme}
|
theme={config.ui.theme}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
fontSize={config.preview.fontSize}
|
fontSize={config.preview.fontSize}
|
||||||
@@ -181,17 +305,19 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
smartArrows={config.preview.smartArrows}
|
smartArrows={config.preview.smartArrows}
|
||||||
breaks={config.preview.breaks}
|
breaks={config.preview.breaks}
|
||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
|
||||||
tabInde='0'
|
tabInde='0'
|
||||||
value={value}
|
value={value}
|
||||||
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
onCheckboxClick={e => this.handleCheckboxClick(e)}
|
||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={this.handleScroll.bind(this)}
|
||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
noteKey={noteKey}
|
noteKey={noteKey}
|
||||||
customCSS={config.preview.customCSS}
|
customCSS={config.preview.customCSS}
|
||||||
allowCustomCSS={config.preview.allowCustomCSS}
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
/>
|
lineThroughCheckbox={config.preview.lineThroughCheckbox}
|
||||||
|
RTL={RTL}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,36 @@
|
|||||||
height 100%
|
height 100%
|
||||||
font-size 30px
|
font-size 30px
|
||||||
display flex
|
display flex
|
||||||
|
flex-wrap wrap
|
||||||
.slider
|
.slider
|
||||||
absolute top bottom
|
absolute top bottom
|
||||||
top -2px
|
top -2px
|
||||||
width 0
|
width 0
|
||||||
z-index 0
|
z-index 0
|
||||||
|
border-left 1px solid $ui-borderColor
|
||||||
.slider-hitbox
|
.slider-hitbox
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
width 7px
|
width 7px
|
||||||
left -3px
|
left -3px
|
||||||
z-index 10
|
z-index 10
|
||||||
cursor col-resize
|
cursor col-resize
|
||||||
|
.slider-hoz
|
||||||
|
absolute left right
|
||||||
|
.slider-hitbox
|
||||||
|
absolute left right
|
||||||
|
width: 100%
|
||||||
|
height 7px
|
||||||
|
cursor row-resize
|
||||||
|
|
||||||
|
|
||||||
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
|
.root
|
||||||
|
.slider
|
||||||
|
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||||
|
|
||||||
|
for theme in 'dark' 'dracula' 'solarized-dark'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -3,12 +3,10 @@ import React from 'react'
|
|||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './ModalEscButton.styl'
|
import styles from './ModalEscButton.styl'
|
||||||
|
|
||||||
const ModalEscButton = ({
|
const ModalEscButton = ({ handleEscButtonClick }) => (
|
||||||
handleEscButtonClick
|
|
||||||
}) => (
|
|
||||||
<button styleName='escButton' onClick={handleEscButtonClick}>
|
<button styleName='escButton' onClick={handleEscButtonClick}>
|
||||||
<div styleName='esc-mark'>×</div>
|
<div styleName='esc-mark'>×</div>
|
||||||
<div styleName='esc-text'>esc</div>
|
<div>esc</div>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Micro component for toggle SideNav
|
* @fileoverview Micro component for toggle SideNav
|
||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styles from './NavToggleButton.styl'
|
import styles from './NavToggleButton.styl'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} isFolded
|
* @param {boolean} isFolded
|
||||||
* @param {Function} handleToggleButtonClick
|
* @param {Function} handleToggleButtonClick
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
|
const NavToggleButton = ({ isFolded, handleToggleButtonClick }) => (
|
||||||
<button styleName='navToggle'
|
<button styleName='navToggle' onClick={e => handleToggleButtonClick(e)}>
|
||||||
onClick={(e) => handleToggleButtonClick(e)}
|
{isFolded ? (
|
||||||
>
|
<i className='fa fa-angle-double-right fa-2x' />
|
||||||
{isFolded
|
) : (
|
||||||
? <i className='fa fa-angle-double-right' />
|
<i className='fa fa-angle-double-left fa-2x' />
|
||||||
: <i className='fa fa-angle-double-left' />
|
)}
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
border-radius 16.5px
|
border-radius 16.5px
|
||||||
height 34px
|
height 34px
|
||||||
width 34px
|
width 34px
|
||||||
line-height 32px
|
line-height 100%
|
||||||
padding 0
|
padding 0
|
||||||
&:hover
|
&:hover
|
||||||
border: 1px solid #1EC38B;
|
border: 1px solid #1EC38B;
|
||||||
@@ -17,10 +17,16 @@
|
|||||||
body[data-theme="white"]
|
body[data-theme="white"]
|
||||||
navWhiteButtonColor()
|
navWhiteButtonColor()
|
||||||
|
|
||||||
body[data-theme="dark"]
|
apply-theme(theme)
|
||||||
.navToggle
|
body[data-theme={theme}]
|
||||||
&:hover
|
.navToggle:hover
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
|
||||||
|
border 1px solid get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
|
for theme in 'dark' 'dracula' 'solarized-dark'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -3,7 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { isArray } from 'lodash'
|
import { isArray, sortBy } from 'lodash'
|
||||||
|
import invertColor from 'invert-color'
|
||||||
|
import Emoji from 'react-emoji-render'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import { getTodoStatus } from 'browser/lib/getTodoStatus'
|
import { getTodoStatus } from 'browser/lib/getTodoStatus'
|
||||||
import styles from './NoteItem.styl'
|
import styles from './NoteItem.styl'
|
||||||
@@ -13,29 +15,47 @@ import i18n from 'browser/lib/i18n'
|
|||||||
/**
|
/**
|
||||||
* @description Tag element component.
|
* @description Tag element component.
|
||||||
* @param {string} tagName
|
* @param {string} tagName
|
||||||
|
* @param {string} color
|
||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const TagElement = ({ tagName }) => (
|
const TagElement = ({ tagName, color }) => {
|
||||||
<span styleName='item-bottom-tagList-item' key={tagName}>
|
const style = {}
|
||||||
#{tagName}
|
if (color) {
|
||||||
</span>
|
style.backgroundColor = color
|
||||||
)
|
style.color = invertColor(color, {
|
||||||
|
black: '#222',
|
||||||
|
white: '#f1f1f1',
|
||||||
|
threshold: 0.3
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
|
||||||
|
#{tagName}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Tag element list component.
|
* @description Tag element list component.
|
||||||
* @param {Array|null} tags
|
* @param {Array|null} tags
|
||||||
|
* @param {boolean} showTagsAlphabetically
|
||||||
|
* @param {Object} coloredTags
|
||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const TagElementList = (tags) => {
|
const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
|
||||||
if (!isArray(tags)) {
|
if (!isArray(tags)) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const tagElements = tags.map(tag => (
|
if (showTagsAlphabetically) {
|
||||||
TagElement({tagName: tag})
|
return sortBy(tags).map(tag =>
|
||||||
))
|
TagElement({ tagName: tag, color: coloredTags[tag] })
|
||||||
|
)
|
||||||
return tagElements
|
} else {
|
||||||
|
return tags.map(tag =>
|
||||||
|
TagElement({ tagName: tag, color: coloredTags[tag] })
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,6 +65,7 @@ const TagElementList = (tags) => {
|
|||||||
* @param {Function} handleNoteClick
|
* @param {Function} handleNoteClick
|
||||||
* @param {Function} handleNoteContextMenu
|
* @param {Function} handleNoteContextMenu
|
||||||
* @param {Function} handleDragStart
|
* @param {Function} handleDragStart
|
||||||
|
* @param {Object} coloredTags
|
||||||
* @param {string} dateDisplay
|
* @param {string} dateDisplay
|
||||||
*/
|
*/
|
||||||
const NoteItem = ({
|
const NoteItem = ({
|
||||||
@@ -57,12 +78,12 @@ const NoteItem = ({
|
|||||||
pathname,
|
pathname,
|
||||||
storageName,
|
storageName,
|
||||||
folderName,
|
folderName,
|
||||||
viewType
|
viewType,
|
||||||
|
showTagsAlphabetically,
|
||||||
|
coloredTags
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName={isActive
|
<div
|
||||||
? 'item--active'
|
styleName={isActive ? 'item--active' : 'item'}
|
||||||
: 'item'
|
|
||||||
}
|
|
||||||
key={note.key}
|
key={note.key}
|
||||||
onClick={e => handleNoteClick(e, note.key)}
|
onClick={e => handleNoteClick(e, note.key)}
|
||||||
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
||||||
@@ -70,44 +91,68 @@ const NoteItem = ({
|
|||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
<div styleName='item-wrapper'>
|
<div styleName='item-wrapper'>
|
||||||
{note.type === 'SNIPPET_NOTE'
|
{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-code' />
|
||||||
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
|
) : (
|
||||||
}
|
<i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
|
||||||
|
)}
|
||||||
<div styleName='item-title'>
|
<div styleName='item-title'>
|
||||||
{note.title.trim().length > 0
|
{note.title.trim().length > 0 ? (
|
||||||
? note.title
|
<Emoji text={note.title} />
|
||||||
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
|
) : (
|
||||||
}
|
<span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{['ALL', 'STORAGE'].includes(viewType) && <div styleName='item-middle'>
|
<div styleName='item-middle'>
|
||||||
<div styleName='item-middle-time'>{dateDisplay}</div>
|
<div styleName='item-middle-time'>{dateDisplay}</div>
|
||||||
<div styleName='item-middle-app-meta'>
|
<div styleName='item-middle-app-meta'>
|
||||||
<div title={viewType === 'ALL' ? storageName : viewType === 'STORAGE' ? folderName : null} styleName='item-middle-app-meta-label'>
|
<div
|
||||||
|
title={
|
||||||
|
viewType === 'ALL'
|
||||||
|
? storageName
|
||||||
|
: viewType === 'STORAGE'
|
||||||
|
? folderName
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
styleName='item-middle-app-meta-label'
|
||||||
|
>
|
||||||
{viewType === 'ALL' && storageName}
|
{viewType === 'ALL' && storageName}
|
||||||
{viewType === 'STORAGE' && folderName}
|
{viewType === 'STORAGE' && folderName}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>
|
||||||
|
|
||||||
<div styleName='item-bottom'>
|
<div styleName='item-bottom'>
|
||||||
<div styleName='item-bottom-tagList'>
|
<div styleName='item-bottom-tagList'>
|
||||||
{note.tags.length > 0
|
{note.tags.length > 0 ? (
|
||||||
? TagElementList(note.tags)
|
TagElementList(note.tags, showTagsAlphabetically, coloredTags)
|
||||||
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>{i18n.__('No tags')}</span>
|
) : (
|
||||||
}
|
<span
|
||||||
|
style={{ fontStyle: 'italic', opacity: 0.5 }}
|
||||||
|
styleName='item-bottom-tagList-empty'
|
||||||
|
>
|
||||||
|
{i18n.__('No tags')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{note.isStarred
|
{note.isStarred ? (
|
||||||
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
|
<img
|
||||||
}
|
styleName='item-star'
|
||||||
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
src='../resources/icon/icon-starred.svg'
|
||||||
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
|
/>
|
||||||
}
|
) : (
|
||||||
{note.type === 'MARKDOWN_NOTE'
|
''
|
||||||
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
|
)}
|
||||||
: ''
|
{note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
|
||||||
}
|
<i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
|
{note.type === 'MARKDOWN_NOTE' ? (
|
||||||
|
<TodoProcess todoStatus={getTodoStatus(note.content)} />
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -117,6 +162,7 @@ const NoteItem = ({
|
|||||||
NoteItem.propTypes = {
|
NoteItem.propTypes = {
|
||||||
isActive: PropTypes.bool.isRequired,
|
isActive: PropTypes.bool.isRequired,
|
||||||
dateDisplay: PropTypes.string.isRequired,
|
dateDisplay: PropTypes.string.isRequired,
|
||||||
|
coloredTags: PropTypes.object,
|
||||||
note: PropTypes.shape({
|
note: PropTypes.shape({
|
||||||
storage: PropTypes.string.isRequired,
|
storage: PropTypes.string.isRequired,
|
||||||
key: PropTypes.string.isRequired,
|
key: PropTypes.string.isRequired,
|
||||||
@@ -125,15 +171,14 @@ NoteItem.propTypes = {
|
|||||||
tags: PropTypes.array,
|
tags: PropTypes.array,
|
||||||
isStarred: PropTypes.bool.isRequired,
|
isStarred: PropTypes.bool.isRequired,
|
||||||
isTrashed: PropTypes.bool.isRequired,
|
isTrashed: PropTypes.bool.isRequired,
|
||||||
blog: {
|
blog: PropTypes.shape({
|
||||||
blogLink: PropTypes.string,
|
blogLink: PropTypes.string,
|
||||||
blogId: PropTypes.number
|
blogId: PropTypes.number
|
||||||
}
|
})
|
||||||
}),
|
}),
|
||||||
handleNoteClick: PropTypes.func.isRequired,
|
handleNoteClick: PropTypes.func.isRequired,
|
||||||
handleNoteContextMenu: PropTypes.func.isRequired,
|
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||||
handleDragStart: PropTypes.func.isRequired,
|
handleDragStart: PropTypes.func.isRequired
|
||||||
handleDragEnd: PropTypes.func.isRequired
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(NoteItem, styles)
|
export default CSSModules(NoteItem, styles)
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha($ui-dark-tagList-backgroundColor, 20%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:active
|
&:active
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
@@ -207,7 +207,7 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha(white, 10%)
|
background-color alpha($ui-dark-tagList-backgroundColor, 10%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.item-wrapper
|
.item-wrapper
|
||||||
@@ -223,13 +223,13 @@ body[data-theme="dark"]
|
|||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha(white, 10%)
|
background-color alpha($ui-dark-tagList-backgroundColor, 10%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
color #c0392b
|
color $ui-dark-button--hover-color
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha($ui-dark-tagList-backgroundColor, 20%)
|
||||||
|
|
||||||
.item-title
|
.item-title
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
@@ -322,75 +322,82 @@ body[data-theme="solarized-dark"]
|
|||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
apply-theme(theme)
|
||||||
.root
|
body[data-theme={theme}]
|
||||||
border-color $ui-monokai-borderColor
|
.root
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
|
|
||||||
.item
|
.item
|
||||||
border-color $ui-monokai-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
&:hover
|
&:hover
|
||||||
transition 0.15s
|
|
||||||
// background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
.item-title
|
|
||||||
.item-title-icon
|
|
||||||
.item-bottom-time
|
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
color $ui-monokai-text-color
|
// background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 20%)
|
||||||
.item-bottom-tagList-item
|
color get-theme-var(theme, 'text-color')
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 20%)
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
&:active
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:active
|
.item-title
|
||||||
transition 0.15s
|
.item-title-icon
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
.item-bottom-time
|
||||||
color $ui-monokai-text-color
|
transition 0.15s
|
||||||
.item-title
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-title-icon
|
.item-bottom-tagList-item
|
||||||
.item-bottom-time
|
transition 0.15s
|
||||||
transition 0.15s
|
background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 10%)
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
.item-bottom-tagList-item
|
|
||||||
transition 0.15s
|
|
||||||
background-color alpha($ui-monokai-noteList-backgroundColor, 10%)
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.item-wrapper
|
|
||||||
border-color alpha($ui-monokai-button-backgroundColor, 60%)
|
|
||||||
|
|
||||||
.item--active
|
|
||||||
border-color $ui-monokai-borderColor
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
.item-wrapper
|
.item-wrapper
|
||||||
border-color transparent
|
border-color alpha(get-theme-var(theme, 'button-backgroundColor'), 60%)
|
||||||
.item-title
|
|
||||||
.item-title-icon
|
.item--active
|
||||||
.item-bottom-time
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
color $ui-monokai-text-color
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
.item-bottom-tagList-item
|
.item-wrapper
|
||||||
background-color alpha(white, 10%)
|
border-color transparent
|
||||||
color $ui-monokai-text-color
|
.item-title
|
||||||
&:hover
|
.item-title-icon
|
||||||
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
|
.item-bottom-time
|
||||||
color #c0392b
|
color get-theme-var(theme, 'active-color')
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
&:hover
|
||||||
|
// background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
|
||||||
|
color get-theme-var(theme, 'button--hover-color')
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
|
||||||
|
|
||||||
.item-title
|
.item-title
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.item-title-empty
|
.item-title-empty
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.item-bottom-tagList-empty
|
.item-bottom-tagList-empty
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
|
|
||||||
|
for theme in 'dracula'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -25,10 +25,8 @@ const NoteItemSimple = ({
|
|||||||
pathname,
|
pathname,
|
||||||
storage
|
storage
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName={isActive
|
<div
|
||||||
? 'item-simple--active'
|
styleName={isActive ? 'item-simple--active' : 'item-simple'}
|
||||||
: 'item-simple'
|
|
||||||
}
|
|
||||||
key={note.key}
|
key={note.key}
|
||||||
onClick={e => handleNoteClick(e, note.key)}
|
onClick={e => handleNoteClick(e, note.key)}
|
||||||
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
||||||
@@ -36,23 +34,29 @@ const NoteItemSimple = ({
|
|||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
<div styleName='item-simple-title'>
|
<div styleName='item-simple-title'>
|
||||||
{note.type === 'SNIPPET_NOTE'
|
{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-code' />
|
||||||
: <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' />
|
) : (
|
||||||
}
|
<i
|
||||||
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
styleName='item-simple-title-icon'
|
||||||
? <i styleName='item-pin' className='fa fa-thumb-tack' />
|
className='fa fa-fw fa-file-text-o'
|
||||||
: ''
|
/>
|
||||||
}
|
)}
|
||||||
{note.title.trim().length > 0
|
{note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
|
||||||
? note.title
|
<i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||||
: <span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span>
|
) : (
|
||||||
}
|
''
|
||||||
{isAllNotesView && <div styleName='item-simple-right'>
|
)}
|
||||||
<span styleName='item-simple-right-storageName'>
|
{note.title.trim().length > 0 ? (
|
||||||
{storage.name}
|
note.title
|
||||||
</span>
|
) : (
|
||||||
</div>}
|
<span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span>
|
||||||
|
)}
|
||||||
|
{isAllNotesView && (
|
||||||
|
<div styleName='item-simple-right'>
|
||||||
|
<span styleName='item-simple-right-storageName'>{storage.name}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -223,66 +223,73 @@ body[data-theme="solarized-dark"]
|
|||||||
padding-left 4px
|
padding-left 4px
|
||||||
opacity 0.4
|
opacity 0.4
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
apply-theme(theme)
|
||||||
.root
|
body[data-theme={theme}]
|
||||||
border-color $ui-monokai-borderColor
|
.root
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
|
|
||||||
.item-simple
|
.item-simple
|
||||||
border-color $ui-monokai-borderColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
&:hover
|
&:hover
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha($ui-monokai-button-backgroundColor, 60%)
|
background-color alpha(get-theme-var(theme, 'button-backgroundColor'), 60%)
|
||||||
color $ui-monokai-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
&:active
|
||||||
|
transition 0.15s
|
||||||
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
|
.item-simple--active
|
||||||
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
|
.item-simple-wrapper
|
||||||
|
border-color transparent
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
.item-simple-title-empty
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
transition 0.15s
|
color get-theme-var(theme, 'text-color')
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
transition 0.15s
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
|
||||||
background-color alpha(#fff, 20%)
|
color get-theme-var(theme, 'text-color')
|
||||||
color $ui-monokai-text-color
|
&:hover
|
||||||
&:active
|
// background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
|
||||||
transition 0.15s
|
color #c0392b
|
||||||
background-color $ui-monokai-button--active-backgroundColor
|
.item-simple-bottom-tagList-item
|
||||||
color $ui-monokai-text-color
|
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
|
||||||
.item-simple-title
|
|
||||||
.item-simple-title-empty
|
|
||||||
.item-simple-title-icon
|
|
||||||
.item-simple-bottom-time
|
|
||||||
transition 0.15s
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
.item-simple-bottom-tagList-item
|
|
||||||
transition 0.15s
|
|
||||||
background-color alpha(white, 10%)
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.item-simple--active
|
|
||||||
border-color $ui-monokai-borderColor
|
|
||||||
background-color $ui-monokai-button--active-backgroundColor
|
|
||||||
.item-simple-wrapper
|
|
||||||
border-color transparent
|
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
.item-simple-title-empty
|
color $ui-dark-text-color
|
||||||
.item-simple-title-icon
|
border-bottom $ui-dark-borderColor
|
||||||
.item-simple-bottom-time
|
.item-simple-right
|
||||||
color $ui-monokai-text-color
|
float right
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-right-storageName
|
||||||
background-color alpha(white, 10%)
|
padding-left 4px
|
||||||
color $ui-monokai-text-color
|
opacity 0.4
|
||||||
&:hover
|
|
||||||
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
for theme in 'dracula'
|
||||||
color #c0392b
|
apply-theme(theme)
|
||||||
.item-simple-bottom-tagList-item
|
|
||||||
background-color alpha(#fff, 20%)
|
for theme in $themes
|
||||||
.item-simple-title
|
apply-theme(theme)
|
||||||
color $ui-dark-text-color
|
|
||||||
border-bottom $ui-dark-borderColor
|
|
||||||
.item-simple-right
|
|
||||||
float right
|
|
||||||
.item-simple-right-storageName
|
|
||||||
padding-left 4px
|
|
||||||
opacity 0.4
|
|
||||||
@@ -6,7 +6,7 @@ const electron = require('electron')
|
|||||||
const { shell } = electron
|
const { shell } = electron
|
||||||
|
|
||||||
class RealtimeNotification extends React.Component {
|
class RealtimeNotification extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -14,38 +14,46 @@ class RealtimeNotification extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.fetchNotifications()
|
this.fetchNotifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchNotifications () {
|
fetchNotifications() {
|
||||||
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
|
const notificationsUrl =
|
||||||
|
'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
|
||||||
fetch(notificationsUrl)
|
fetch(notificationsUrl)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
.then(json => {
|
.then(json => {
|
||||||
this.setState({notifications: json.notifications})
|
this.setState({ notifications: json.notifications })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLinkClick (e) {
|
handleLinkClick(e) {
|
||||||
shell.openExternal(e.currentTarget.href)
|
shell.openExternal(e.currentTarget.href)
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { notifications } = this.state
|
const { notifications } = this.state
|
||||||
const link = notifications.length > 0
|
const link =
|
||||||
? <a styleName='notification-link' href={notifications[0].linkUrl}
|
notifications.length > 0 ? (
|
||||||
onClick={(e) => this.handleLinkClick(e)}
|
<a
|
||||||
>
|
styleName='notification-link'
|
||||||
Info: {notifications[0].text}
|
href={notifications[0].linkUrl}
|
||||||
</a>
|
onClick={e => this.handleLinkClick(e)}
|
||||||
: ''
|
>
|
||||||
|
Info: {notifications[0].text}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div styleName='notification-area' style={this.props.style}>{link}</div>
|
<div styleName='notification-area' style={this.props.style}>
|
||||||
|
{link}
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,25 +30,20 @@ body[data-theme="dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
color #5CB85C
|
color #5CB85C
|
||||||
|
|
||||||
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
|
.notification-area
|
||||||
|
background-color none
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
.notification-link
|
||||||
.notification-area
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color none
|
border none
|
||||||
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
&:hover
|
||||||
|
color get-theme-var(theme, 'button--hover-color')
|
||||||
|
|
||||||
.notification-link
|
for theme in 'solarized-dark' 'dracula'
|
||||||
color $ui-solarized-dark-text-color
|
apply-theme(theme)
|
||||||
border none
|
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
|
||||||
&:hover
|
|
||||||
color #5CB85C
|
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in $themes
|
||||||
.notification-area
|
apply-theme(theme)
|
||||||
background-color none
|
|
||||||
|
|
||||||
.notification-link
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
border none
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
&:hover
|
|
||||||
color #5CB85C
|
|
||||||
@@ -16,54 +16,70 @@ import i18n from 'browser/lib/i18n'
|
|||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const SideNavFilter = ({
|
const SideNavFilter = ({
|
||||||
isFolded, isHomeActive, handleAllNotesButtonClick,
|
isFolded,
|
||||||
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
|
isHomeActive,
|
||||||
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu
|
handleAllNotesButtonClick,
|
||||||
|
isStarredActive,
|
||||||
|
handleStarredButtonClick,
|
||||||
|
isTrashedActive,
|
||||||
|
handleTrashedButtonClick,
|
||||||
|
counterDelNote,
|
||||||
|
counterTotalNote,
|
||||||
|
counterStarredNote,
|
||||||
|
handleFilterButtonContextMenu
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
||||||
|
<button
|
||||||
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
||||||
onClick={handleAllNotesButtonClick}
|
onClick={handleAllNotesButtonClick}
|
||||||
>
|
>
|
||||||
<div styleName='iconWrap'>
|
<div styleName='iconWrap'>
|
||||||
<img src={isHomeActive
|
<img
|
||||||
? '../resources/icon/icon-all-active.svg'
|
src={
|
||||||
: '../resources/icon/icon-all.svg'
|
isHomeActive
|
||||||
}
|
? '../resources/icon/icon-all-active.svg'
|
||||||
|
: '../resources/icon/icon-all.svg'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span styleName='menu-button-label'>{i18n.__('All Notes')}</span>
|
<span styleName='menu-button-label'>{i18n.__('All Notes')}</span>
|
||||||
<span styleName='counters'>{counterTotalNote}</span>
|
<span styleName='counters'>{counterTotalNote}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
|
<button
|
||||||
|
styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
|
||||||
onClick={handleStarredButtonClick}
|
onClick={handleStarredButtonClick}
|
||||||
>
|
>
|
||||||
<div styleName='iconWrap'>
|
<div styleName='iconWrap'>
|
||||||
<img src={isStarredActive
|
<img
|
||||||
? '../resources/icon/icon-star-active.svg'
|
src={
|
||||||
: '../resources/icon/icon-star-sidenav.svg'
|
isStarredActive
|
||||||
}
|
? '../resources/icon/icon-star-active.svg'
|
||||||
|
: '../resources/icon/icon-star-sidenav.svg'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span styleName='menu-button-label'>{i18n.__('Starred')}</span>
|
<span styleName='menu-button-label'>{i18n.__('Starred')}</span>
|
||||||
<span styleName='counters'>{counterStarredNote}</span>
|
<span styleName='counters'>{counterStarredNote}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
<button
|
||||||
onClick={handleTrashedButtonClick} onContextMenu={handleFilterButtonContextMenu}
|
styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
||||||
|
onClick={handleTrashedButtonClick}
|
||||||
|
onContextMenu={handleFilterButtonContextMenu}
|
||||||
>
|
>
|
||||||
<div styleName='iconWrap'>
|
<div styleName='iconWrap'>
|
||||||
<img src={isTrashedActive
|
<img
|
||||||
? '../resources/icon/icon-trash-active.svg'
|
src={
|
||||||
: '../resources/icon/icon-trash-sidenav.svg'
|
isTrashedActive
|
||||||
}
|
? '../resources/icon/icon-trash-active.svg'
|
||||||
|
: '../resources/icon/icon-trash-sidenav.svg'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span styleName='menu-button-label'>{i18n.__('Trash')}</span>
|
<span styleName='menu-button-label'>{i18n.__('Trash')}</span>
|
||||||
<span styleName='counters'>{counterDelNote}</span>
|
<span styleName='counters'>{counterDelNote}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,7 +90,7 @@ SideNavFilter.propTypes = {
|
|||||||
isStarredActive: PropTypes.bool.isRequired,
|
isStarredActive: PropTypes.bool.isRequired,
|
||||||
isTrashedActive: PropTypes.bool.isRequired,
|
isTrashedActive: PropTypes.bool.isRequired,
|
||||||
handleStarredButtonClick: PropTypes.func.isRequired,
|
handleStarredButtonClick: PropTypes.func.isRequired,
|
||||||
handleTrashdButtonClick: PropTypes.func.isRequired
|
handleTrashedButtonClick: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(SideNavFilter, styles)
|
export default CSSModules(SideNavFilter, styles)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.menu
|
.menu
|
||||||
margin-bottom 30px
|
margin-bottom 20px
|
||||||
|
|
||||||
.menu-button
|
.menu-button
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
@@ -180,87 +180,51 @@ body[data-theme="dark"]
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
|
.menu-button
|
||||||
|
&:active
|
||||||
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
&:hover
|
||||||
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
.menu-button--active
|
||||||
.menu-button
|
color get-theme-var(theme, 'text-color')
|
||||||
&:active
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
|
|
||||||
.menu-button--active
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
&:hover
|
||||||
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
.menu-button-label
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.menu-button-star--active
|
.menu-button-star--active
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
.menu-button-label
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
&:hover
|
||||||
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
.menu-button-label
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.menu-button-trash--active
|
.menu-button-trash--active
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
.menu-button-label
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
&:hover
|
||||||
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
.menu-button-label
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.menu-button
|
apply-theme(theme)
|
||||||
&:active
|
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.menu-button--active
|
for theme in $themes
|
||||||
color $ui-monokai-text-color
|
apply-theme(theme)
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.menu-button-star--active
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.menu-button-trash--active
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
@@ -5,7 +5,7 @@ import context from 'browser/lib/context'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class SnippetTab extends React.Component {
|
class SnippetTab extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -14,7 +14,7 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate (nextProps) {
|
componentWillUpdate(nextProps) {
|
||||||
if (nextProps.snippet.name !== this.props.snippet.name) {
|
if (nextProps.snippet.name !== this.props.snippet.name) {
|
||||||
this.setState({
|
this.setState({
|
||||||
name: nextProps.snippet.name
|
name: nextProps.snippet.name
|
||||||
@@ -22,34 +22,34 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick (e) {
|
handleClick(e) {
|
||||||
this.props.onClick(e)
|
this.props.onClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu(e) {
|
||||||
context.popup([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: i18n.__('Rename'),
|
label: i18n.__('Rename'),
|
||||||
click: (e) => this.handleRenameClick(e)
|
click: e => this.handleRenameClick(e)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRenameClick (e) {
|
handleRenameClick(e) {
|
||||||
this.startRenaming()
|
this.startRenaming()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNameInputBlur (e) {
|
handleNameInputBlur(e) {
|
||||||
this.handleRename()
|
this.handleRename()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNameInputChange (e) {
|
handleNameInputChange(e) {
|
||||||
this.setState({
|
this.setState({
|
||||||
name: e.target.value
|
name: e.target.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNameInputKeyDown (e) {
|
handleNameInputKeyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 13:
|
case 13:
|
||||||
this.handleRename()
|
this.handleRename()
|
||||||
@@ -63,84 +63,87 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRename () {
|
handleRename() {
|
||||||
this.setState({
|
this.setState(
|
||||||
isRenaming: false
|
{
|
||||||
}, () => {
|
isRenaming: false
|
||||||
if (this.props.snippet.name !== this.state.name) {
|
},
|
||||||
this.props.onRename(this.state.name)
|
() => {
|
||||||
|
if (this.props.snippet.name !== this.state.name) {
|
||||||
|
this.props.onRename(this.state.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteButtonClick (e) {
|
handleDeleteButtonClick(e) {
|
||||||
this.props.onDelete(e)
|
this.props.onDelete(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
startRenaming () {
|
startRenaming() {
|
||||||
this.setState({
|
this.setState(
|
||||||
isRenaming: true
|
{
|
||||||
}, () => {
|
isRenaming: true
|
||||||
this.refs.name.focus()
|
},
|
||||||
this.refs.name.select()
|
() => {
|
||||||
})
|
this.refs.name.focus()
|
||||||
|
this.refs.name.select()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragStart (e) {
|
handleDragStart(e) {
|
||||||
e.dataTransfer.dropEffect = 'move'
|
e.dataTransfer.dropEffect = 'move'
|
||||||
this.props.onDragStart(e)
|
this.props.onDragStart(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDrop (e) {
|
handleDrop(e) {
|
||||||
this.props.onDrop(e)
|
this.props.onDrop(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { isActive, snippet, isDeletable } = this.props
|
const { isActive, snippet, isDeletable } = this.props
|
||||||
return (
|
return (
|
||||||
<div styleName={isActive
|
<div styleName={isActive ? 'root--active' : 'root'}>
|
||||||
? 'root--active'
|
{!this.state.isRenaming ? (
|
||||||
: 'root'
|
<button
|
||||||
}
|
styleName='button'
|
||||||
>
|
onClick={e => this.handleClick(e)}
|
||||||
{!this.state.isRenaming
|
onDoubleClick={e => this.handleRenameClick(e)}
|
||||||
? <button styleName='button'
|
onContextMenu={e => this.handleContextMenu(e)}
|
||||||
onClick={(e) => this.handleClick(e)}
|
onDragStart={e => this.handleDragStart(e)}
|
||||||
onDoubleClick={(e) => this.handleRenameClick(e)}
|
onDrop={e => this.handleDrop(e)}
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
|
||||||
onDragStart={(e) => this.handleDragStart(e)}
|
|
||||||
onDrop={(e) => this.handleDrop(e)}
|
|
||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
{snippet.name.trim().length > 0
|
{snippet.name.trim().length > 0 ? (
|
||||||
? snippet.name
|
snippet.name
|
||||||
: <span styleName='button-unnamed'>
|
) : (
|
||||||
{i18n.__('Unnamed')}
|
<span>{i18n.__('Unnamed')}</span>
|
||||||
</span>
|
)}
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
: <input styleName='input'
|
) : (
|
||||||
|
<input
|
||||||
|
styleName='input'
|
||||||
ref='name'
|
ref='name'
|
||||||
value={this.state.name}
|
value={this.state.name}
|
||||||
onChange={(e) => this.handleNameInputChange(e)}
|
onChange={e => this.handleNameInputChange(e)}
|
||||||
onBlur={(e) => this.handleNameInputBlur(e)}
|
onBlur={e => this.handleNameInputBlur(e)}
|
||||||
onKeyDown={(e) => this.handleNameInputKeyDown(e)}
|
onKeyDown={e => this.handleNameInputKeyDown(e)}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
{isDeletable &&
|
{isDeletable && (
|
||||||
<button styleName='deleteButton'
|
<button
|
||||||
onClick={(e) => this.handleDeleteButtonClick(e)}
|
styleName='deleteButton'
|
||||||
|
onClick={e => this.handleDeleteButtonClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-times' />
|
<i className='fa fa-times' />
|
||||||
</button>
|
</button>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SnippetTab.propTypes = {
|
SnippetTab.propTypes = {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CSSModules(SnippetTab, styles)
|
export default CSSModules(SnippetTab, styles)
|
||||||
|
|||||||
@@ -3,19 +3,30 @@
|
|||||||
flex 1
|
flex 1
|
||||||
min-width 70px
|
min-width 70px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
border-left 1px solid $ui-borderColor
|
||||||
|
border-top 1px solid $ui-borderColor
|
||||||
&:hover
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
.deleteButton
|
.deleteButton
|
||||||
color $ui-inactive-text-color
|
color: $ui-text-color
|
||||||
&:hover
|
visibility visible
|
||||||
background-color darken($ui-backgroundColor, 15%)
|
transition 0.15s
|
||||||
&:active
|
.button
|
||||||
color white
|
color: $ui-text-color
|
||||||
background-color $ui-active-color
|
transition 0.15s
|
||||||
|
|
||||||
.root--active
|
.root--active
|
||||||
@extend .root
|
@extend .root
|
||||||
min-width 100px
|
min-width 100px
|
||||||
border-bottom $ui-border
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
.deleteButton
|
||||||
|
visibility visible
|
||||||
|
color: $ui-text-color
|
||||||
|
transition 0.15s
|
||||||
|
.button
|
||||||
|
font-weight bold
|
||||||
|
color: $ui-text-color
|
||||||
|
transition 0.15s
|
||||||
|
|
||||||
.button
|
.button
|
||||||
width 100%
|
width 100%
|
||||||
@@ -27,8 +38,7 @@
|
|||||||
background-color transparent
|
background-color transparent
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
border-left 4px solid transparent
|
border-left 4px solid transparent
|
||||||
&:hover
|
color $ui-inactive-text-color
|
||||||
background-color $ui-button--hover-backgroundColor
|
|
||||||
|
|
||||||
.deleteButton
|
.deleteButton
|
||||||
position absolute
|
position absolute
|
||||||
@@ -42,6 +52,7 @@
|
|||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
background-color transparent
|
background-color transparent
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
|
visibility hidden
|
||||||
|
|
||||||
.input
|
.input
|
||||||
height 29px
|
height 29px
|
||||||
@@ -50,90 +61,82 @@
|
|||||||
width 100%
|
width 100%
|
||||||
outline none
|
outline none
|
||||||
|
|
||||||
|
body[data-theme="default"], body[data-theme="white"]
|
||||||
|
.root--active
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
color $ui-dark-text-color
|
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
|
border-top 1px solid $ui-dark-borderColor
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
transition 0.15s
|
||||||
|
.button
|
||||||
|
color $ui-dark-text-color
|
||||||
|
transition 0.15s
|
||||||
.deleteButton
|
.deleteButton
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-text-color
|
||||||
&:hover
|
transition 0.15s
|
||||||
background-color darken($ui-dark-button--hover-backgroundColor, 15%)
|
|
||||||
&:active
|
|
||||||
color $ui-dark-text-color
|
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
|
||||||
|
|
||||||
.root--active
|
.root--active
|
||||||
color $ui-dark-text-color
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
border-color $ui-dark-borderColor
|
border-left 1px solid $ui-dark-borderColor
|
||||||
&:hover
|
border-top 1px solid $ui-dark-borderColor
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
.button
|
||||||
.deleteButton
|
color $ui-dark-text-color
|
||||||
color $ui-dark-inactive-text-color
|
.deleteButton
|
||||||
&: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
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
|
||||||
|
|
||||||
.input
|
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
|
||||||
color $ui-dark-text-color
|
|
||||||
|
|
||||||
.deleteButton
|
|
||||||
color alpha($ui-dark-text-color, 30%)
|
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
|
||||||
.root
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
&:hover
|
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
|
||||||
.deleteButton
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
&:hover
|
|
||||||
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
|
|
||||||
&:active
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
|
||||||
|
|
||||||
.root--active
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
border-color $ui-solarized-dark-borderColor
|
|
||||||
&:hover
|
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
|
||||||
.deleteButton
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
&:hover
|
|
||||||
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
|
|
||||||
&:active
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
|
||||||
|
|
||||||
.button
|
.button
|
||||||
border none
|
border none
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
background-color transparent
|
background-color transparent
|
||||||
transition color background-color 0.15s
|
transition color background-color 0.15s
|
||||||
border-left 4px solid transparent
|
border-left 4px solid transparent
|
||||||
&:hover
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.input
|
.input
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
transition 0.15s
|
||||||
|
|
||||||
.deleteButton
|
apply-theme(theme)
|
||||||
color alpha($ui-solarized-dark-text-color, 30%)
|
body[data-theme={theme}]
|
||||||
|
.root
|
||||||
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
|
&:hover
|
||||||
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
|
transition 0.15s
|
||||||
|
.deleteButton
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
transition 0.15s
|
||||||
|
.button
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
transition 0.15s
|
||||||
|
|
||||||
|
.root--active
|
||||||
|
color get-theme-var(theme, 'active-color')
|
||||||
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
|
.deleteButton
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
.button
|
||||||
|
color get-theme-var(theme, 'active-color')
|
||||||
|
|
||||||
|
.button
|
||||||
|
border none
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
background-color transparent
|
||||||
|
transition color background-color 0.15s
|
||||||
|
border-left 4px solid transparent
|
||||||
|
|
||||||
|
.input
|
||||||
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
transition 0.15s
|
||||||
|
|
||||||
|
for theme in 'solarized-dark' 'dracula'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -21,7 +21,9 @@ const FolderIcon = ({ className, color, isActive }) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} isActive
|
* @param {boolean} isActive
|
||||||
|
* @param {object} tooltipRef,
|
||||||
* @param {Function} handleButtonClick
|
* @param {Function} handleButtonClick
|
||||||
|
* @param {Function} handleMouseEnter
|
||||||
* @param {Function} handleContextMenu
|
* @param {Function} handleContextMenu
|
||||||
* @param {string} folderName
|
* @param {string} folderName
|
||||||
* @param {string} folderColor
|
* @param {string} folderColor
|
||||||
@@ -35,7 +37,9 @@ const FolderIcon = ({ className, color, isActive }) => {
|
|||||||
const StorageItem = ({
|
const StorageItem = ({
|
||||||
styles,
|
styles,
|
||||||
isActive,
|
isActive,
|
||||||
|
tooltipRef,
|
||||||
handleButtonClick,
|
handleButtonClick,
|
||||||
|
handleMouseEnter,
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
folderName,
|
folderName,
|
||||||
folderColor,
|
folderColor,
|
||||||
@@ -49,6 +53,7 @@ const StorageItem = ({
|
|||||||
<button
|
<button
|
||||||
styleName={isActive ? 'folderList-item--active' : 'folderList-item'}
|
styleName={isActive ? 'folderList-item--active' : 'folderList-item'}
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
@@ -71,12 +76,13 @@ const StorageItem = ({
|
|||||||
? _.truncate(folderName, { length: 1, omission: '' })
|
? _.truncate(folderName, { length: 1, omission: '' })
|
||||||
: folderName}
|
: folderName}
|
||||||
</span>
|
</span>
|
||||||
{!isFolded &&
|
{!isFolded && _.isNumber(noteCount) && (
|
||||||
_.isNumber(noteCount) && (
|
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
||||||
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
)}
|
||||||
)}
|
|
||||||
{isFolded && (
|
{isFolded && (
|
||||||
<span styleName='folderList-item-tooltip'>{folderName}</span>
|
<span styleName='folderList-item-tooltip' ref={tooltipRef}>
|
||||||
|
{folderName}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
@@ -84,7 +90,9 @@ const StorageItem = ({
|
|||||||
|
|
||||||
StorageItem.propTypes = {
|
StorageItem.propTypes = {
|
||||||
isActive: PropTypes.bool.isRequired,
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
tooltipRef: PropTypes.object,
|
||||||
handleButtonClick: PropTypes.func,
|
handleButtonClick: PropTypes.func,
|
||||||
|
handleMouseEnter: PropTypes.func,
|
||||||
handleContextMenu: PropTypes.func,
|
handleContextMenu: PropTypes.func,
|
||||||
folderName: PropTypes.string.isRequired,
|
folderName: PropTypes.string.isRequired,
|
||||||
folderColor: PropTypes.string,
|
folderColor: PropTypes.string,
|
||||||
|
|||||||
@@ -60,6 +60,7 @@
|
|||||||
border-bottom-right-radius 2px
|
border-bottom-right-radius 2px
|
||||||
height 34px
|
height 34px
|
||||||
line-height 32px
|
line-height 32px
|
||||||
|
transition-property opacity
|
||||||
|
|
||||||
.folderList-item:hover, .folderList-item--active:hover
|
.folderList-item:hover, .folderList-item--active:hover
|
||||||
.folderList-item-tooltip
|
.folderList-item-tooltip
|
||||||
@@ -120,40 +121,28 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
apply-theme(theme)
|
||||||
.folderList-item
|
body[data-theme={theme}]
|
||||||
&:hover
|
.folderList-item
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
&:hover
|
||||||
color $ui-solarized-dark-text-color
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
&:active
|
color get-theme-var(theme, 'text-color')
|
||||||
color $ui-solarized-dark-text-color
|
&:active
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
color get-theme-var(theme, 'text-color')
|
||||||
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
|
||||||
.folderList-item--active
|
.folderList-item--active
|
||||||
@extend .folderList-item
|
@extend .folderList-item
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
&:active
|
&:active
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-solarized-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.folderList-item
|
apply-theme(theme)
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
&:active
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
|
|
||||||
.folderList-item--active
|
for theme in $themes
|
||||||
@extend .folderList-item
|
apply-theme(theme)
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
&:active
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
&:hover
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
background-color $ui-monokai-button-backgroundColor
|
|
||||||
@@ -1,24 +1,26 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Micro component for showing StorageList
|
* @fileoverview Micro component for showing StorageList
|
||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styles from './StorageList.styl'
|
import styles from './StorageList.styl'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array} storgaeList
|
* @param {Array} storageList
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const StorageList = ({storageList, isFolded}) => (
|
const StorageList = ({ storageList, isFolded }) => (
|
||||||
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
|
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
|
||||||
{storageList.length > 0 ? storageList : (
|
{storageList.length > 0 ? (
|
||||||
<div styleName='storgaeList-empty'>No storage mount.</div>
|
storageList
|
||||||
|
) : (
|
||||||
|
<div styleName='storageList-empty'>No storage mount.</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
StorageList.propTypes = {
|
StorageList.propTypes = {
|
||||||
storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired
|
storageList: PropTypes.arrayOf(PropTypes.element).isRequired
|
||||||
}
|
}
|
||||||
export default CSSModules(StorageList, styles)
|
export default CSSModules(StorageList, styles)
|
||||||
|
|||||||
@@ -1,31 +1,61 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Micro component for showing TagList.
|
* @fileoverview Micro component for showing TagList.
|
||||||
*/
|
*/
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styles from './TagListItem.styl'
|
import styles from './TagListItem.styl'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @param {Function} handleClickTagListItem
|
* @param {Function} handleClickTagListItem
|
||||||
* @param {Function} handleClickNarrowToTag
|
* @param {Function} handleClickNarrowToTag
|
||||||
* @param {bool} isActive
|
* @param {boolean} isActive
|
||||||
* @param {bool} isRelated
|
* @param {boolean} isRelated
|
||||||
*/
|
* @param {string} bgColor tab backgroundColor
|
||||||
|
*/
|
||||||
|
|
||||||
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => (
|
const TagListItem = ({
|
||||||
<div styleName='tagList-itemContainer'>
|
name,
|
||||||
{isRelated
|
handleClickTagListItem,
|
||||||
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
|
handleClickNarrowToTag,
|
||||||
|
handleContextMenu,
|
||||||
|
isActive,
|
||||||
|
isRelated,
|
||||||
|
count,
|
||||||
|
color
|
||||||
|
}) => (
|
||||||
|
<div
|
||||||
|
styleName='tagList-itemContainer'
|
||||||
|
onContextMenu={e => handleContextMenu(e, name)}
|
||||||
|
>
|
||||||
|
{isRelated ? (
|
||||||
|
<button
|
||||||
|
styleName={
|
||||||
|
isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
|
||||||
|
}
|
||||||
|
onClick={() => handleClickNarrowToTag(name)}
|
||||||
|
>
|
||||||
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
|
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
|
||||||
</button>
|
</button>
|
||||||
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
|
) : (
|
||||||
}
|
<div
|
||||||
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
styleName={
|
||||||
|
isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
styleName={isActive ? 'tagList-item-active' : 'tagList-item'}
|
||||||
|
onClick={() => handleClickTagListItem(name)}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
styleName='tagList-item-color'
|
||||||
|
style={{ backgroundColor: color || 'transparent' }}
|
||||||
|
/>
|
||||||
<span styleName='tagList-item-name'>
|
<span styleName='tagList-item-name'>
|
||||||
{`# ${name}`}
|
{`# ${name}`}
|
||||||
<span styleName='tagList-item-count'>{count}</span>
|
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +63,8 @@ const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isAc
|
|||||||
|
|
||||||
TagListItem.propTypes = {
|
TagListItem.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
handleClickTagListItem: PropTypes.func.isRequired
|
handleClickTagListItem: PropTypes.func.isRequired,
|
||||||
|
color: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(TagListItem, styles)
|
export default CSSModules(TagListItem, styles)
|
||||||
|
|||||||
@@ -71,6 +71,11 @@
|
|||||||
padding-right 15px
|
padding-right 15px
|
||||||
font-size 13px
|
font-size 13px
|
||||||
|
|
||||||
|
.tagList-item-color
|
||||||
|
height 26px
|
||||||
|
width 3px
|
||||||
|
display inline-block
|
||||||
|
|
||||||
body[data-theme="white"]
|
body[data-theme="white"]
|
||||||
.tagList-item
|
.tagList-item
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
@@ -89,23 +94,30 @@ body[data-theme="white"]
|
|||||||
.tagList-item-count
|
.tagList-item-count
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
|
|
||||||
body[data-theme="dark"]
|
apply-theme(theme)
|
||||||
.tagList-item
|
body[data-theme={theme}]
|
||||||
color $ui-dark-inactive-text-color
|
.tagList-item
|
||||||
&:hover
|
color get-theme-var(theme, 'inactive-text-color')
|
||||||
color $ui-dark-text-color
|
&:hover
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
color get-theme-var(theme, 'text-color')
|
||||||
&:active
|
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
|
||||||
color $ui-dark-text-color
|
&:active
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
color get-theme-var(theme, 'text-color')
|
||||||
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
|
|
||||||
.tagList-item-active
|
.tagList-item-active
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
&:active
|
&:active
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 50%)
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 50%)
|
||||||
.tagList-item-count
|
.tagList-item-count
|
||||||
color $ui-dark-button--active-color
|
color get-theme-var(theme, 'button--active-color')
|
||||||
|
|
||||||
|
for theme in 'dark'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -11,20 +11,27 @@ import styles from './TodoListPercentage.styl'
|
|||||||
* @param {number} percentageOfTodo
|
* @param {number} percentageOfTodo
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TodoListPercentage = ({
|
const TodoListPercentage = ({ percentageOfTodo, onClearCheckboxClick }) => (
|
||||||
percentageOfTodo
|
<div
|
||||||
}) => (
|
styleName='percentageBar'
|
||||||
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
|
style={{ display: isNaN(percentageOfTodo) ? 'none' : '' }}
|
||||||
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
|
>
|
||||||
|
<div styleName='progressBar' style={{ width: `${percentageOfTodo}%` }}>
|
||||||
<div styleName='progressBarInner'>
|
<div styleName='progressBarInner'>
|
||||||
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='todoClear'>
|
||||||
|
<p styleName='todoClearText' onClick={e => onClearCheckboxClick(e)}>
|
||||||
|
clear
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
TodoListPercentage.propTypes = {
|
TodoListPercentage.propTypes = {
|
||||||
percentageOfTodo: PropTypes.number.isRequired
|
percentageOfTodo: PropTypes.number.isRequired,
|
||||||
|
onClearCheckboxClick: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(TodoListPercentage, styles)
|
export default CSSModules(TodoListPercentage, styles)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.percentageBar
|
.percentageBar
|
||||||
|
display: flex
|
||||||
position absolute
|
position absolute
|
||||||
top 72px
|
top 72px
|
||||||
right 0px
|
right 0px
|
||||||
@@ -30,6 +31,20 @@
|
|||||||
color #f4f4f4
|
color #f4f4f4
|
||||||
font-weight 600
|
font-weight 600
|
||||||
|
|
||||||
|
.todoClear
|
||||||
|
display flex
|
||||||
|
justify-content: flex-end
|
||||||
|
position absolute
|
||||||
|
z-index 120
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
padding 2px 10px
|
||||||
|
|
||||||
|
.todoClearText
|
||||||
|
color #f4f4f4
|
||||||
|
cursor pointer
|
||||||
|
font-weight 500
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.percentageBar
|
.percentageBar
|
||||||
background-color #444444
|
background-color #444444
|
||||||
@@ -39,7 +54,10 @@ body[data-theme="dark"]
|
|||||||
|
|
||||||
.percentageText
|
.percentageText
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.todoClearText
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.percentageBar
|
.percentageBar
|
||||||
background-color #002b36
|
background-color #002b36
|
||||||
@@ -50,12 +68,22 @@ body[data-theme="solarized-dark"]
|
|||||||
.percentageText
|
.percentageText
|
||||||
color #fdf6e3
|
color #fdf6e3
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
.todoClearText
|
||||||
.percentageBar
|
color #fdf6e3
|
||||||
background-color #f92672
|
|
||||||
|
|
||||||
.progressBar
|
apply-theme(theme)
|
||||||
background-color: #373831
|
body[data-theme={theme}]
|
||||||
|
.percentageBar
|
||||||
|
background-color: get-theme-var(theme, 'borderColor')
|
||||||
|
|
||||||
.percentageText
|
.progressBar
|
||||||
color #fdf6e3
|
background-color get-theme-var(theme, 'active-color')
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
|
for theme in 'dracula'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -8,27 +8,30 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './TodoProcess.styl'
|
import styles from './TodoProcess.styl'
|
||||||
|
|
||||||
const TodoProcess = ({
|
const TodoProcess = ({
|
||||||
todoStatus: {
|
todoStatus: { total: totalTodo, completed: completedTodo }
|
||||||
total: totalTodo,
|
|
||||||
completed: completedTodo
|
|
||||||
}
|
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName='todo-process' style={{display: totalTodo > 0 ? '' : 'none'}}>
|
<div
|
||||||
|
styleName='todo-process'
|
||||||
|
style={{ display: totalTodo > 0 ? '' : 'none' }}
|
||||||
|
>
|
||||||
<div styleName='todo-process-text'>
|
<div styleName='todo-process-text'>
|
||||||
<i className='fa fa-fw fa-check-square-o' />
|
<i className='fa fa-fw fa-check-square-o' />
|
||||||
{completedTodo} of {totalTodo}
|
{completedTodo} of {totalTodo}
|
||||||
</div>
|
</div>
|
||||||
<div styleName='todo-process-bar'>
|
<div styleName='todo-process-bar'>
|
||||||
<div styleName='todo-process-bar--inner' style={{width: parseInt(completedTodo / totalTodo * 100) + '%'}} />
|
<div
|
||||||
|
styleName='todo-process-bar--inner'
|
||||||
|
style={{ width: parseInt((completedTodo / totalTodo) * 100) + '%' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
TodoProcess.propTypes = {
|
TodoProcess.propTypes = {
|
||||||
todoStatus: {
|
todoStatus: PropTypes.exact({
|
||||||
total: PropTypes.number.isRequired,
|
total: PropTypes.number.isRequired,
|
||||||
completed: PropTypes.number.isRequired
|
completed: PropTypes.number.isRequired
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(TodoProcess, styles)
|
export default CSSModules(TodoProcess, styles)
|
||||||
|
|||||||
@@ -55,11 +55,14 @@ body
|
|||||||
line-height 1.6
|
line-height 1.6
|
||||||
overflow-x hidden
|
overflow-x hidden
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
// do not allow display line breaks
|
||||||
|
.katex-display > .katex
|
||||||
|
white-space nowrap
|
||||||
|
// allow inline line breaks
|
||||||
.katex
|
.katex
|
||||||
font 400 1.2em 'KaTeX_Main'
|
|
||||||
line-height 1.2em
|
|
||||||
white-space initial
|
white-space initial
|
||||||
text-indent 0
|
.katex .katex-html
|
||||||
|
display inline-flex
|
||||||
.katex .mfrac>.vlist>span:nth-child(2)
|
.katex .mfrac>.vlist>span:nth-child(2)
|
||||||
top 0 !important
|
top 0 !important
|
||||||
.katex-error
|
.katex-error
|
||||||
@@ -68,7 +71,7 @@ body
|
|||||||
padding 5px
|
padding 5px
|
||||||
margin -5px
|
margin -5px
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
.flowchart-error, .sequence-error
|
.flowchart-error, .sequence-error .chart-error
|
||||||
background-color errorBackgroundColor
|
background-color errorBackgroundColor
|
||||||
color errorTextColor
|
color errorTextColor
|
||||||
padding 5px
|
padding 5px
|
||||||
@@ -80,6 +83,9 @@ li
|
|||||||
&.checked
|
&.checked
|
||||||
text-decoration line-through
|
text-decoration line-through
|
||||||
opacity 0.5
|
opacity 0.5
|
||||||
|
&.taskListItem.checked
|
||||||
|
text-decoration line-through
|
||||||
|
opacity 0.5
|
||||||
div.math-rendered
|
div.math-rendered
|
||||||
text-align center
|
text-align center
|
||||||
.math-failed
|
.math-failed
|
||||||
@@ -118,40 +124,34 @@ hr
|
|||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
margin 15px 0
|
margin 15px 0
|
||||||
h1, h2, h3, h4, h5, h6
|
h1, h2, h3, h4, h5, h6
|
||||||
|
margin 1em 0 1.5em
|
||||||
|
line-height 1.4
|
||||||
font-weight bold
|
font-weight bold
|
||||||
word-wrap break-word
|
word-wrap break-word
|
||||||
|
padding .2em 0 .2em
|
||||||
h1
|
h1
|
||||||
font-size 2.55em
|
font-size 2.55em
|
||||||
padding-bottom 0.3em
|
line-height 1.2
|
||||||
line-height 1.2em
|
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
margin 1em 0 0.44em
|
|
||||||
&:first-child
|
&:first-child
|
||||||
margin-top 0
|
margin-top 0
|
||||||
h2
|
h2
|
||||||
font-size 1.75em
|
font-size 1.75em
|
||||||
padding-bottom 0.3em
|
line-height 1.225
|
||||||
line-height 1.225em
|
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
margin 1em 0 0.57em
|
|
||||||
&:first-child
|
&:first-child
|
||||||
margin-top 0
|
margin-top 0
|
||||||
h3
|
h3
|
||||||
font-size 1.5em
|
font-size 1.5em
|
||||||
line-height 1.43em
|
line-height 1.43
|
||||||
margin 1em 0 0.66em
|
|
||||||
h4
|
h4
|
||||||
font-size 1.25em
|
font-size 1.25em
|
||||||
line-height 1.4em
|
line-height 1.4
|
||||||
margin 1em 0 0.8em
|
|
||||||
h5
|
h5
|
||||||
font-size 1em
|
font-size 1em
|
||||||
line-height 1.4em
|
line-height 1.1
|
||||||
margin 1em 0 1em
|
|
||||||
h6
|
h6
|
||||||
font-size 1em
|
font-size 1em
|
||||||
line-height 1.4em
|
|
||||||
margin 1em 0 1em
|
|
||||||
color #777
|
color #777
|
||||||
p
|
p
|
||||||
line-height 1.6em
|
line-height 1.6em
|
||||||
@@ -159,6 +159,7 @@ p
|
|||||||
white-space pre-line
|
white-space pre-line
|
||||||
word-wrap break-word
|
word-wrap break-word
|
||||||
img
|
img
|
||||||
|
cursor zoom-in
|
||||||
max-width 100%
|
max-width 100%
|
||||||
strong, b
|
strong, b
|
||||||
font-weight bold
|
font-weight bold
|
||||||
@@ -180,6 +181,10 @@ ul
|
|||||||
display list-item
|
display list-item
|
||||||
&.taskListItem
|
&.taskListItem
|
||||||
list-style none
|
list-style none
|
||||||
|
&>input
|
||||||
|
margin-left -1.6em
|
||||||
|
&>p
|
||||||
|
margin-left -1.8em
|
||||||
p
|
p
|
||||||
margin 0
|
margin 0
|
||||||
&>li>ul, &>li>ol
|
&>li>ul, &>li>ol
|
||||||
@@ -206,41 +211,39 @@ code
|
|||||||
text-decoration none
|
text-decoration none
|
||||||
margin-right 2px
|
margin-right 2px
|
||||||
pre
|
pre
|
||||||
padding 0.5em !important
|
padding 0.5rem !important
|
||||||
border solid 1px #D1D1D1
|
border solid 1px #D1D1D1
|
||||||
border-radius 5px
|
border-radius 5px
|
||||||
overflow-x auto
|
overflow-x auto
|
||||||
margin 0 0 1em
|
margin 0 0 1rem
|
||||||
display flex
|
display flex
|
||||||
line-height 1.4em
|
line-height 1.4em
|
||||||
&.flowchart, &.sequence
|
|
||||||
display flex
|
|
||||||
justify-content center
|
|
||||||
background-color white
|
|
||||||
&.CodeMirror
|
|
||||||
height initial
|
|
||||||
flex-wrap wrap
|
|
||||||
&>code
|
|
||||||
flex 1
|
|
||||||
overflow-x auto
|
|
||||||
code
|
code
|
||||||
background-color inherit
|
background-color inherit
|
||||||
margin 0
|
margin 0
|
||||||
padding 0
|
padding 0
|
||||||
border none
|
border none
|
||||||
border-radius 0
|
border-radius 0
|
||||||
|
&.CodeMirror
|
||||||
|
height initial
|
||||||
|
flex-wrap wrap
|
||||||
|
&>code
|
||||||
|
flex 1
|
||||||
|
overflow-x auto
|
||||||
|
&.mermaid svg
|
||||||
|
max-width 100% !important
|
||||||
&>span.filename
|
&>span.filename
|
||||||
width 100%
|
margin -0.5rem 100% 0.5rem -0.5rem
|
||||||
border-radius: 5px 0px 0px 0px
|
padding 0.125rem 0.375rem
|
||||||
margin -8px 100% 8px -8px
|
|
||||||
padding 0px 6px
|
|
||||||
background-color #777;
|
background-color #777;
|
||||||
color white
|
color white
|
||||||
|
&:empty
|
||||||
|
display none
|
||||||
&>span.lineNumber
|
&>span.lineNumber
|
||||||
display none
|
display none
|
||||||
font-size 1em
|
font-size 1em
|
||||||
padding 0.5em 0
|
padding 0.5rem 0
|
||||||
margin -0.5em 0.5em -0.5em -0.5em
|
margin -0.5rem 0.5rem -0.5rem -0.5rem
|
||||||
border-right 1px solid
|
border-right 1px solid
|
||||||
text-align right
|
text-align right
|
||||||
border-top-left-radius 4px
|
border-top-left-radius 4px
|
||||||
@@ -257,6 +260,7 @@ table
|
|||||||
display block
|
display block
|
||||||
width 100%
|
width 100%
|
||||||
margin 0 0 1em
|
margin 0 0 1em
|
||||||
|
overflow-x auto
|
||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
background-color tableHeadBgColor
|
background-color tableHeadBgColor
|
||||||
@@ -315,6 +319,8 @@ $admonition-icon
|
|||||||
position absolute
|
position absolute
|
||||||
left 1.2rem
|
left 1.2rem
|
||||||
font-family: "Material Icons"
|
font-family: "Material Icons"
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
font-size: 24px
|
font-size: 24px
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@@ -346,29 +352,136 @@ $admonition-title
|
|||||||
margin-bottom 0
|
margin-bottom 0
|
||||||
|
|
||||||
admonition_types = {
|
admonition_types = {
|
||||||
note: {border-color: #448aff, title-color: rgba(68,138,255,.1), icon: "note"},
|
note: {color: #0288D1, icon: "note"},
|
||||||
hint: {border-color: #00bfa5, title-color: rgba(0,191,165,.1), icon: "info"},
|
hint: {color: #009688, icon: "info_outline"},
|
||||||
danger: {border-color: #ff1744, title-color: rgba(255,23,68,.1), icon: "block"},
|
danger: {color: #c2185b, icon: "block"},
|
||||||
caution: {border-color: #ff9100, title-color: rgba(255,145,0,.1), icon: "warning"},
|
caution: {color: #ffa726, icon: "warning"},
|
||||||
error: {border-color: #ff1744, title-color: rgba(255,23,68,.1), icon: "error"},
|
error: {color: #d32f2f, icon: "error_outline"},
|
||||||
attention: {border-color: #64dd17, title-color: rgba(100,221,23,.1), icon: "priority_high"}
|
question: {color: #64dd17, icon: "help_outline"},
|
||||||
|
quote: {color: #9e9e9e, icon: "format_quote"},
|
||||||
|
abstract: {color: #00b0ff, icon: "subject"},
|
||||||
|
attention: {color: #455a64, icon: "priority_high"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, val in admonition_types
|
for name, val in admonition_types
|
||||||
.admonition.{name}
|
.admonition.{name}
|
||||||
@extend $admonition
|
@extend $admonition
|
||||||
border-left-color: val[border-color]
|
border-left-color: val[color]
|
||||||
|
|
||||||
.admonition.{name}>.admonition-title
|
.admonition.{name}>.admonition-title
|
||||||
@extend $admonition-title
|
@extend $admonition-title
|
||||||
border-bottom-color: .1rem solid val[title-color]
|
border-bottom-color: .1rem solid rgba(val[color], 0.2)
|
||||||
background-color: val[title-color]
|
background-color: rgba(val[color], 0.2)
|
||||||
|
|
||||||
.admonition.{name}>.admonition-title:before
|
.admonition.{name}>.admonition-title:before
|
||||||
@extend $admonition-icon
|
@extend $admonition-icon
|
||||||
color: val[border-color]
|
color: val[color]
|
||||||
content: val[icon]
|
content: val[icon]
|
||||||
|
|
||||||
|
dl
|
||||||
|
margin 2rem 0
|
||||||
|
padding 0
|
||||||
|
display flex
|
||||||
|
width 100%
|
||||||
|
flex-wrap wrap
|
||||||
|
align-items flex-start
|
||||||
|
border-bottom 1px solid borderColor
|
||||||
|
background-color tableHeadBgColor
|
||||||
|
|
||||||
|
dt
|
||||||
|
border-top 1px solid borderColor
|
||||||
|
font-weight bold
|
||||||
|
text-align right
|
||||||
|
overflow hidden
|
||||||
|
flex-basis 20%
|
||||||
|
padding 0.4rem 0.9rem
|
||||||
|
box-sizing border-box
|
||||||
|
|
||||||
|
dd
|
||||||
|
border-top 1px solid borderColor
|
||||||
|
flex-basis 80%
|
||||||
|
padding 0.4rem 0.9rem
|
||||||
|
min-height 2.5rem
|
||||||
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
box-sizing border-box
|
||||||
|
|
||||||
|
dd + dd
|
||||||
|
margin-left 20%
|
||||||
|
|
||||||
|
pre.fence
|
||||||
|
flex-wrap wrap
|
||||||
|
|
||||||
|
.chart, .flowchart, .mermaid, .sequence
|
||||||
|
display flex
|
||||||
|
justify-content center
|
||||||
|
background-color white
|
||||||
|
max-width 100%
|
||||||
|
flex-grow 1
|
||||||
|
|
||||||
|
canvas, svg
|
||||||
|
max-width 100% !important
|
||||||
|
|
||||||
|
svg[ratio]
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
.gallery
|
||||||
|
width 100%
|
||||||
|
height 50vh
|
||||||
|
|
||||||
|
.carousel
|
||||||
|
.carousel-main img
|
||||||
|
min-width auto
|
||||||
|
max-width 100%
|
||||||
|
min-height auto
|
||||||
|
max-height 100%
|
||||||
|
|
||||||
|
.carousel-footer::-webkit-scrollbar-corner
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.carousel-main, .carousel-footer
|
||||||
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
.prev, .next
|
||||||
|
color $ui-text-color
|
||||||
|
background-color $ui-tag-backgroundColor
|
||||||
|
|
||||||
|
.markdownIt-TOC-wrapper
|
||||||
|
list-style none
|
||||||
|
position fixed
|
||||||
|
right 0
|
||||||
|
top 0
|
||||||
|
margin-left 15px
|
||||||
|
z-index 1000
|
||||||
|
transition transform .2s ease-in-out
|
||||||
|
transform translateX(100%)
|
||||||
|
|
||||||
|
.markdownIt-TOC
|
||||||
|
display block
|
||||||
|
max-height 90vh
|
||||||
|
overflow-y auto
|
||||||
|
padding 25px
|
||||||
|
padding-left 38px
|
||||||
|
|
||||||
|
&,
|
||||||
|
&:before
|
||||||
|
background-color $ui-dark-backgroundColor
|
||||||
|
color: $ui-dark-text-color
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
transform translateX(-15px)
|
||||||
|
|
||||||
|
&:before
|
||||||
|
content 'TOC'
|
||||||
|
position absolute
|
||||||
|
width 60px
|
||||||
|
height 30px
|
||||||
|
top 60px
|
||||||
|
left -29px
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
transform-origin top left
|
||||||
|
transform rotate(-90deg)
|
||||||
|
|
||||||
themeDarkBackground = darken(#21252B, 10%)
|
themeDarkBackground = darken(#21252B, 10%)
|
||||||
themeDarkText = #f9f9f9
|
themeDarkText = #f9f9f9
|
||||||
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
themeDarkBorder = lighten(themeDarkBackground, 20%)
|
||||||
@@ -419,59 +532,80 @@ body[data-theme="dark"]
|
|||||||
kbd
|
kbd
|
||||||
background-color themeDarkBorder
|
background-color themeDarkBorder
|
||||||
color themeDarkText
|
color themeDarkText
|
||||||
|
dl
|
||||||
|
border-color themeDarkBorder
|
||||||
|
background-color themeDarkTableHead
|
||||||
|
dt
|
||||||
|
border-color themeDarkBorder
|
||||||
|
dd
|
||||||
|
border-color themeDarkBorder
|
||||||
|
background-color themeDarkPreview
|
||||||
|
|
||||||
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
|
pre.fence
|
||||||
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
|
.gallery
|
||||||
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
|
.carousel-main, .carousel-footer
|
||||||
themeSolarizedDarkTableBorder = themeDarkBorder
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
|
.prev, .next
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-tag-backgroundColor
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
.markdownIt-TOC-wrapper
|
||||||
color $ui-solarized-dark-text-color
|
&,
|
||||||
border-color themeDarkBorder
|
&:before
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color darken(themeDarkBackground, 5%)
|
||||||
table
|
color themeDarkText
|
||||||
thead
|
|
||||||
tr
|
|
||||||
background-color themeSolarizedDarkTableHead
|
|
||||||
th
|
|
||||||
border-color themeSolarizedDarkTableBorder
|
|
||||||
&:last-child
|
|
||||||
border-right solid 1px themeSolarizedDarkTableBorder
|
|
||||||
tbody
|
|
||||||
tr:nth-child(2n + 1)
|
|
||||||
background-color themeSolarizedDarkTableOdd
|
|
||||||
tr:nth-child(2n)
|
|
||||||
background-color themeSolarizedDarkTableEven
|
|
||||||
td
|
|
||||||
border-color themeSolarizedDarkTableBorder
|
|
||||||
&:last-child
|
|
||||||
border-right solid 1px themeSolarizedDarkTableBorder
|
|
||||||
|
|
||||||
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
|
apply-theme(theme)
|
||||||
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
|
body[data-theme={theme}]
|
||||||
themeMonokaiTableHead = themeMonokaiTableEven
|
color get-theme-var(theme, 'text-color')
|
||||||
themeMonokaiTableBorder = themeDarkBorder
|
border-color themeDarkBorder
|
||||||
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
|
table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
background-color get-theme-var(theme, 'table-head-backgroundColor')
|
||||||
|
th
|
||||||
|
border-color get-theme-var(theme, 'table-borderColor')
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px get-theme-var(theme, 'table-borderColor')
|
||||||
|
tbody
|
||||||
|
tr:nth-child(2n + 1)
|
||||||
|
background-color get-theme-var(theme, 'table-odd-backgroundColor')
|
||||||
|
tr:nth-child(2n)
|
||||||
|
background-color get-theme-var(theme, 'table-even-backgroundColor')
|
||||||
|
td
|
||||||
|
border-color get-theme-var(theme, 'table-borderColor')
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px get-theme-var(theme, 'table-borderColor')
|
||||||
|
kbd
|
||||||
|
background-color get-theme-var(theme, 'kbd-backgroundColor')
|
||||||
|
color get-theme-var(theme, 'kbd-color')
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
dl
|
||||||
color $ui-monokai-text-color
|
border-color themeDarkBorder
|
||||||
border-color themeDarkBorder
|
background-color get-theme-var(theme, 'table-head-backgroundColor')
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
dt
|
||||||
table
|
border-color themeDarkBorder
|
||||||
thead
|
dd
|
||||||
tr
|
border-color themeDarkBorder
|
||||||
background-color themeMonokaiTableHead
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
th
|
|
||||||
border-color themeMonokaiTableBorder
|
pre.fence
|
||||||
&:last-child
|
.gallery
|
||||||
border-right solid 1px themeMonokaiTableBorder
|
.carousel-main, .carousel-footer
|
||||||
tbody
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
tr:nth-child(2n + 1)
|
.prev, .next
|
||||||
background-color themeMonokaiTableOdd
|
color get-theme-var(theme, 'button--active-color')
|
||||||
tr:nth-child(2n)
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
background-color themeMonokaiTableEven
|
|
||||||
td
|
.markdownIt-TOC-wrapper
|
||||||
border-color themeMonokaiTableBorder
|
&,
|
||||||
&:last-child
|
&:before
|
||||||
border-right solid 1px themeMonokaiTableBorder
|
background-color darken(get-theme-var(theme, 'noteDetail-backgroundColor'), 15%)
|
||||||
kbd
|
color themeDarkText
|
||||||
background-color themeDarkBackground
|
|
||||||
|
for theme in 'solarized-dark' 'dracula'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
|
|||||||
72
browser/components/render/MermaidRender.js
Normal file
72
browser/components/render/MermaidRender.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import mermaidAPI from 'mermaid/dist/mermaid.min.js'
|
||||||
|
import uiThemes from 'browser/lib/ui-themes'
|
||||||
|
|
||||||
|
// fixes bad styling in the mermaid dark theme
|
||||||
|
const darkThemeStyling = `
|
||||||
|
.loopText tspan {
|
||||||
|
fill: white;
|
||||||
|
}`
|
||||||
|
|
||||||
|
function getRandomInt(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min)) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
function getId() {
|
||||||
|
const pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
|
let id = 'm-'
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
id += pool[getRandomInt(0, 16)]
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(element, content, theme, enableHTMLLabel) {
|
||||||
|
try {
|
||||||
|
const height = element.attributes.getNamedItem('data-height')
|
||||||
|
const isPredefined = height && height.value !== 'undefined'
|
||||||
|
|
||||||
|
if (isPredefined) {
|
||||||
|
element.style.height = height.value + 'vh'
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDarkTheme = uiThemes.some(
|
||||||
|
item => item.name === theme && item.isDark
|
||||||
|
)
|
||||||
|
|
||||||
|
mermaidAPI.initialize({
|
||||||
|
theme: isDarkTheme ? 'dark' : 'default',
|
||||||
|
themeCSS: isDarkTheme ? darkThemeStyling : '',
|
||||||
|
flowchart: {
|
||||||
|
htmlLabels: enableHTMLLabel
|
||||||
|
},
|
||||||
|
gantt: {
|
||||||
|
useWidth: element.clientWidth
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mermaidAPI.render(getId(), content, svgGraph => {
|
||||||
|
element.innerHTML = svgGraph
|
||||||
|
|
||||||
|
if (!isPredefined) {
|
||||||
|
const el = element.firstChild
|
||||||
|
const viewBox = el.getAttribute('viewBox').split(' ')
|
||||||
|
|
||||||
|
let ratio = viewBox[2] / viewBox[3]
|
||||||
|
|
||||||
|
if (el.style.maxWidth) {
|
||||||
|
const maxWidth = parseFloat(el.style.maxWidth)
|
||||||
|
|
||||||
|
ratio *= el.parentNode.clientWidth / maxWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
el.setAttribute('ratio', ratio)
|
||||||
|
el.setAttribute('height', el.parentNode.clientWidth / ratio)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
element.className = 'mermaid-error'
|
||||||
|
element.innerHTML = 'mermaid diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default render
|
||||||
78
browser/lib/CMLanguageList.js
Normal file
78
browser/lib/CMLanguageList.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
export const languageMaps = {
|
||||||
|
brainfuck: 'Brainfuck',
|
||||||
|
cpp: 'C++',
|
||||||
|
cs: 'C#',
|
||||||
|
clojure: 'Clojure',
|
||||||
|
'clojure-repl': 'ClojureScript',
|
||||||
|
cmake: 'CMake',
|
||||||
|
coffeescript: 'CoffeeScript',
|
||||||
|
crystal: 'Crystal',
|
||||||
|
css: 'CSS',
|
||||||
|
d: 'D',
|
||||||
|
dart: 'Dart',
|
||||||
|
delphi: 'Pascal',
|
||||||
|
diff: 'Diff',
|
||||||
|
django: 'Django',
|
||||||
|
dockerfile: 'Dockerfile',
|
||||||
|
ebnf: 'EBNF',
|
||||||
|
elm: 'Elm',
|
||||||
|
erlang: 'Erlang',
|
||||||
|
'erlang-repl': 'Erlang',
|
||||||
|
fortran: 'Fortran',
|
||||||
|
fsharp: 'F#',
|
||||||
|
gherkin: 'Gherkin',
|
||||||
|
go: 'Go',
|
||||||
|
groovy: 'Groovy',
|
||||||
|
haml: 'HAML',
|
||||||
|
haskell: 'Haskell',
|
||||||
|
haxe: 'Haxe',
|
||||||
|
http: 'HTTP',
|
||||||
|
ini: 'toml',
|
||||||
|
java: 'Java',
|
||||||
|
javascript: 'JavaScript',
|
||||||
|
json: 'JSON',
|
||||||
|
julia: 'Julia',
|
||||||
|
kotlin: 'Kotlin',
|
||||||
|
less: 'LESS',
|
||||||
|
livescript: 'LiveScript',
|
||||||
|
lua: 'Lua',
|
||||||
|
markdown: 'Markdown',
|
||||||
|
mathematica: 'Mathematica',
|
||||||
|
nginx: 'Nginx',
|
||||||
|
nsis: 'NSIS',
|
||||||
|
objectivec: 'Objective-C',
|
||||||
|
ocaml: 'Ocaml',
|
||||||
|
perl: 'Perl',
|
||||||
|
php: 'PHP',
|
||||||
|
powershell: 'PowerShell',
|
||||||
|
properties: 'Properties files',
|
||||||
|
protobuf: 'ProtoBuf',
|
||||||
|
python: 'Python',
|
||||||
|
puppet: 'Puppet',
|
||||||
|
q: 'Q',
|
||||||
|
r: 'R',
|
||||||
|
ruby: 'Ruby',
|
||||||
|
rust: 'Rust',
|
||||||
|
sas: 'SAS',
|
||||||
|
scala: 'Scala',
|
||||||
|
scheme: 'Scheme',
|
||||||
|
scss: 'SCSS',
|
||||||
|
shell: 'Shell',
|
||||||
|
smalltalk: 'Smalltalk',
|
||||||
|
sml: 'SML',
|
||||||
|
sql: 'SQL',
|
||||||
|
stylus: 'Stylus',
|
||||||
|
swift: 'Swift',
|
||||||
|
tcl: 'Tcl',
|
||||||
|
tex: 'LaTex',
|
||||||
|
typescript: 'TypeScript',
|
||||||
|
twig: 'Twig',
|
||||||
|
vbnet: 'VB.NET',
|
||||||
|
vbscript: 'VBScript',
|
||||||
|
verilog: 'Verilog',
|
||||||
|
vhdl: 'VHDL',
|
||||||
|
xml: 'HTML',
|
||||||
|
xquery: 'XQuery',
|
||||||
|
yaml: 'YAML',
|
||||||
|
elixir: 'Elixir'
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import CSSModules from 'react-css-modules'
|
import CSSModules from 'react-css-modules'
|
||||||
|
|
||||||
export default function (component, styles) {
|
export default function(component, styles) {
|
||||||
return CSSModules(component, styles, {errorWhenNotFound: false})
|
return CSSModules(component, styles, { handleNotFoundStyleName: 'log' })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ const languages = [
|
|||||||
name: 'Chinese (zh-TW)',
|
name: 'Chinese (zh-TW)',
|
||||||
locale: 'zh-TW'
|
locale: 'zh-TW'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Czech',
|
||||||
|
locale: 'cs'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Danish',
|
name: 'Danish',
|
||||||
locale: 'da'
|
locale: 'da'
|
||||||
@@ -48,8 +52,12 @@ const languages = [
|
|||||||
locale: 'pl'
|
locale: 'pl'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Portuguese',
|
name: 'Portuguese (PT-BR)',
|
||||||
locale: 'pt'
|
locale: 'pt-BR'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Portuguese (PT-PT)',
|
||||||
|
locale: 'pt-PT'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Russian',
|
name: 'Russian',
|
||||||
@@ -58,21 +66,25 @@ const languages = [
|
|||||||
{
|
{
|
||||||
name: 'Spanish',
|
name: 'Spanish',
|
||||||
locale: 'es-ES'
|
locale: 'es-ES'
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
name: 'Turkish',
|
name: 'Turkish',
|
||||||
locale: 'tr'
|
locale: 'tr'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Thai',
|
||||||
|
locale: 'th'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getLocales () {
|
getLocales() {
|
||||||
return languages.reduce(function (localeList, locale) {
|
return languages.reduce(function(localeList, locale) {
|
||||||
localeList.push(locale.locale)
|
localeList.push(locale.locale)
|
||||||
return localeList
|
return localeList
|
||||||
}, [])
|
}, [])
|
||||||
},
|
},
|
||||||
getLanguages () {
|
getLanguages() {
|
||||||
return languages
|
return languages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
class MutableMap {
|
class MutableMap {
|
||||||
constructor (iterable) {
|
constructor(iterable) {
|
||||||
this._map = new Map(iterable)
|
this._map = new Map(iterable)
|
||||||
Object.defineProperty(this, 'size', {
|
Object.defineProperty(this, 'size', {
|
||||||
get: () => this._map.size,
|
get: () => this._map.size,
|
||||||
set: function (value) {
|
set: function(value) {
|
||||||
this['size'] = value
|
this['size'] = value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get (...args) {
|
get(...args) {
|
||||||
return this._map.get(...args)
|
return this._map.get(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
set (...args) {
|
set(...args) {
|
||||||
return this._map.set(...args)
|
return this._map.set(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete (...args) {
|
delete(...args) {
|
||||||
return this._map.delete(...args)
|
return this._map.delete(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
has (...args) {
|
has(...args) {
|
||||||
return this._map.has(...args)
|
return this._map.has(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
clear (...args) {
|
clear(...args) {
|
||||||
return this._map.clear(...args)
|
return this._map.clear(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach (...args) {
|
forEach(...args) {
|
||||||
return this._map.forEach(...args)
|
return this._map.forEach(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.iterator] () {
|
[Symbol.iterator]() {
|
||||||
return this._map[Symbol.iterator]()
|
return this._map[Symbol.iterator]()
|
||||||
}
|
}
|
||||||
|
|
||||||
map (cb) {
|
map(cb) {
|
||||||
const result = []
|
const result = []
|
||||||
for (const [key, value] of this._map) {
|
for (const [key, value] of this._map) {
|
||||||
result.push(cb(value, key))
|
result.push(cb(value, key))
|
||||||
@@ -45,7 +45,7 @@ class MutableMap {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
toJS () {
|
toJS() {
|
||||||
const result = {}
|
const result = {}
|
||||||
for (let [key, value] of this._map) {
|
for (let [key, value] of this._map) {
|
||||||
if (value instanceof MutableSet || value instanceof MutableMap) {
|
if (value instanceof MutableSet || value instanceof MutableMap) {
|
||||||
@@ -58,42 +58,42 @@ class MutableMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MutableSet {
|
class MutableSet {
|
||||||
constructor (iterable) {
|
constructor(iterable) {
|
||||||
this._set = new Set(iterable)
|
this._set = new Set(iterable)
|
||||||
Object.defineProperty(this, 'size', {
|
Object.defineProperty(this, 'size', {
|
||||||
get: () => this._set.size,
|
get: () => this._set.size,
|
||||||
set: function (value) {
|
set: function(value) {
|
||||||
this['size'] = value
|
this['size'] = value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
add (...args) {
|
add(...args) {
|
||||||
return this._set.add(...args)
|
return this._set.add(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete (...args) {
|
delete(...args) {
|
||||||
return this._set.delete(...args)
|
return this._set.delete(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach (...args) {
|
forEach(...args) {
|
||||||
return this._set.forEach(...args)
|
return this._set.forEach(...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
[Symbol.iterator] () {
|
[Symbol.iterator]() {
|
||||||
return this._set[Symbol.iterator]()
|
return this._set[Symbol.iterator]()
|
||||||
}
|
}
|
||||||
|
|
||||||
map (cb) {
|
map(cb) {
|
||||||
const result = []
|
const result = []
|
||||||
this._set.forEach(function (value, key) {
|
this._set.forEach(function(value, key) {
|
||||||
result.push(cb(value, key))
|
result.push(cb(value, key))
|
||||||
})
|
})
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
toJS () {
|
toJS() {
|
||||||
return Array.from(this._set)
|
return Array.from(this._set)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ const BOOSTNOTERC = '.boostnoterc'
|
|||||||
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
|
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
|
||||||
const _boostnotercPath = path.join(homePath, BOOSTNOTERC)
|
const _boostnotercPath = path.join(homePath, BOOSTNOTERC)
|
||||||
|
|
||||||
export function parse (boostnotercPath = _boostnotercPath) {
|
export function parse(boostnotercPath = _boostnotercPath) {
|
||||||
if (!sander.existsSync(boostnotercPath)) return {}
|
if (!sander.existsSync(boostnotercPath)) return {}
|
||||||
try {
|
try {
|
||||||
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
|
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
console.warn('Your .boostnoterc is broken so it\'s not used.')
|
console.warn("Your .boostnoterc is broken so it's not used.")
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
92
browser/lib/SnippetManager.js
Normal file
92
browser/lib/SnippetManager.js
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import crypto from 'crypto'
|
||||||
|
import fs from 'fs'
|
||||||
|
import consts from './consts'
|
||||||
|
|
||||||
|
class SnippetManager {
|
||||||
|
constructor() {
|
||||||
|
this.defaultSnippet = [
|
||||||
|
{
|
||||||
|
id: crypto.randomBytes(16).toString('hex'),
|
||||||
|
name: 'Dummy text',
|
||||||
|
prefix: ['lorem', 'ipsum'],
|
||||||
|
content:
|
||||||
|
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
this.snippets = []
|
||||||
|
this.expandSnippet = this.expandSnippet.bind(this)
|
||||||
|
this.init = this.init.bind(this)
|
||||||
|
this.assignSnippets = this.assignSnippets.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
if (fs.existsSync(consts.SNIPPET_FILE)) {
|
||||||
|
try {
|
||||||
|
this.snippets = JSON.parse(
|
||||||
|
fs.readFileSync(consts.SNIPPET_FILE, { encoding: 'UTF-8' })
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Error while parsing snippet file')
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fs.writeFileSync(
|
||||||
|
consts.SNIPPET_FILE,
|
||||||
|
JSON.stringify(this.defaultSnippet, null, 4),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
this.snippets = this.defaultSnippet
|
||||||
|
}
|
||||||
|
|
||||||
|
assignSnippets(snippets) {
|
||||||
|
this.snippets = snippets
|
||||||
|
}
|
||||||
|
|
||||||
|
expandSnippet(wordBeforeCursor, cursor, cm) {
|
||||||
|
const templateCursorString = ':{}'
|
||||||
|
for (let i = 0; i < this.snippets.length; i++) {
|
||||||
|
if (this.snippets[i].prefix.indexOf(wordBeforeCursor.text) === -1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (this.snippets[i].content.indexOf(templateCursorString) !== -1) {
|
||||||
|
const snippetLines = this.snippets[i].content.split('\n')
|
||||||
|
let cursorLineNumber = 0
|
||||||
|
let cursorLinePosition = 0
|
||||||
|
|
||||||
|
let cursorIndex
|
||||||
|
for (let j = 0; j < snippetLines.length; j++) {
|
||||||
|
cursorIndex = snippetLines[j].indexOf(templateCursorString)
|
||||||
|
|
||||||
|
if (cursorIndex !== -1) {
|
||||||
|
cursorLineNumber = j
|
||||||
|
cursorLinePosition = cursorIndex
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cm.replaceRange(
|
||||||
|
this.snippets[i].content.replace(templateCursorString, ''),
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
cm.setCursor({
|
||||||
|
line: cursor.line + cursorLineNumber,
|
||||||
|
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cm.replaceRange(
|
||||||
|
this.snippets[i].content,
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const manager = new SnippetManager()
|
||||||
|
export default manager
|
||||||
109
browser/lib/TextEditorInterface.js
Normal file
109
browser/lib/TextEditorInterface.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { Point } from '@susisu/mte-kernel'
|
||||||
|
|
||||||
|
export default class TextEditorInterface {
|
||||||
|
constructor(editor) {
|
||||||
|
this.editor = editor
|
||||||
|
this.doc = editor.getDoc()
|
||||||
|
this.transaction = false
|
||||||
|
}
|
||||||
|
|
||||||
|
getCursorPosition() {
|
||||||
|
const { line, ch } = this.doc.getCursor()
|
||||||
|
return new Point(line, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
setCursorPosition(pos) {
|
||||||
|
this.doc.setCursor({
|
||||||
|
line: pos.row,
|
||||||
|
ch: pos.column
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectionRange(range) {
|
||||||
|
this.doc.setSelection(
|
||||||
|
{ line: range.start.row, ch: range.start.column },
|
||||||
|
{ line: range.end.row, ch: range.end.column }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastRow() {
|
||||||
|
return this.doc.lineCount() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptsTableEdit() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
getLine(row) {
|
||||||
|
return this.doc.getLine(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
insertLine(row, line) {
|
||||||
|
const lastRow = this.getLastRow()
|
||||||
|
if (row > lastRow) {
|
||||||
|
const lastLine = this.getLine(lastRow)
|
||||||
|
this.doc.replaceRange(
|
||||||
|
'\n' + line,
|
||||||
|
{ line: lastRow, ch: lastLine.length },
|
||||||
|
{ line: lastRow, ch: lastLine.length }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.doc.replaceRange(
|
||||||
|
line + '\n',
|
||||||
|
{ line: row, ch: 0 },
|
||||||
|
{ line: row, ch: 0 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteLine(row) {
|
||||||
|
const lastRow = this.getLastRow()
|
||||||
|
if (row >= lastRow) {
|
||||||
|
if (lastRow > 0) {
|
||||||
|
const preLastLine = this.getLine(lastRow - 1)
|
||||||
|
const lastLine = this.getLine(lastRow)
|
||||||
|
this.doc.replaceRange(
|
||||||
|
'',
|
||||||
|
{ line: lastRow - 1, ch: preLastLine.length },
|
||||||
|
{ line: lastRow, ch: lastLine.length }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const lastLine = this.getLine(lastRow)
|
||||||
|
this.doc.replaceRange(
|
||||||
|
'',
|
||||||
|
{ line: lastRow, ch: 0 },
|
||||||
|
{ line: lastRow, ch: lastLine.length }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.doc.replaceRange('', { line: row, ch: 0 }, { line: row + 1, ch: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceLines(startRow, endRow, lines) {
|
||||||
|
const lastRow = this.getLastRow()
|
||||||
|
if (endRow > lastRow) {
|
||||||
|
const lastLine = this.getLine(lastRow)
|
||||||
|
this.doc.replaceRange(
|
||||||
|
lines.join('\n'),
|
||||||
|
{ line: startRow, ch: 0 },
|
||||||
|
{ line: lastRow, ch: lastLine.length }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.doc.replaceRange(
|
||||||
|
lines.join('\n') + '\n',
|
||||||
|
{ line: startRow, ch: 0 },
|
||||||
|
{ line: endRow, ch: 0 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transact(func) {
|
||||||
|
this.transaction = true
|
||||||
|
func()
|
||||||
|
this.transaction = false
|
||||||
|
if (this.onDidFinishTransaction) {
|
||||||
|
this.onDidFinishTransaction.call(undefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,17 +3,18 @@ import i18n from 'browser/lib/i18n'
|
|||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const { dialog } = remote
|
const { dialog } = remote
|
||||||
|
|
||||||
export function confirmDeleteNote (confirmDeletion, permanent) {
|
export function confirmDeleteNote(confirmDeletion, permanent) {
|
||||||
if (confirmDeletion || permanent) {
|
if (confirmDeletion || permanent) {
|
||||||
const alertConfig = {
|
const alertConfig = {
|
||||||
ype: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('Confirm note deletion'),
|
message: i18n.__('Confirm note deletion'),
|
||||||
detail: i18n.__('This will permanently remove this note.'),
|
detail: i18n.__('This will permanently remove this note.'),
|
||||||
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
}
|
}
|
||||||
|
|
||||||
const dialogButtonIndex = dialog.showMessageBox(
|
const dialogButtonIndex = dialog.showMessageBox(
|
||||||
remote.getCurrentWindow(), alertConfig
|
remote.getCurrentWindow(),
|
||||||
|
alertConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
return dialogButtonIndex === 0
|
return dialogButtonIndex === 0
|
||||||
|
|||||||
@@ -3,18 +3,59 @@ const fs = require('sander')
|
|||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { app } = remote
|
const { app } = remote
|
||||||
|
|
||||||
const themePath = process.env.NODE_ENV === 'production'
|
const CODEMIRROR_THEME_PATH = 'node_modules/codemirror/theme'
|
||||||
? path.join(app.getAppPath(), './node_modules/codemirror/theme')
|
const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
|
||||||
: require('path').resolve('./node_modules/codemirror/theme')
|
|
||||||
const themes = fs.readdirSync(themePath)
|
|
||||||
.map((themePath) => {
|
|
||||||
return themePath.substring(0, themePath.lastIndexOf('.'))
|
|
||||||
})
|
|
||||||
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
|
||||||
|
|
||||||
const snippetFile = process.env.NODE_ENV !== 'test'
|
const isProduction = process.env.NODE_ENV === 'production'
|
||||||
? path.join(app.getPath('userData'), 'snippets.json')
|
|
||||||
: '' // return nothing as we specified different path to snippets.json in test
|
const paths = [
|
||||||
|
isProduction
|
||||||
|
? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH)
|
||||||
|
: path.resolve(CODEMIRROR_THEME_PATH),
|
||||||
|
isProduction
|
||||||
|
? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH)
|
||||||
|
: path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
|
||||||
|
]
|
||||||
|
|
||||||
|
const themes = paths
|
||||||
|
.map(directory =>
|
||||||
|
fs.readdirSync(directory).map(file => {
|
||||||
|
const name = file.substring(0, file.lastIndexOf('.'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
path: path.join(directory, file),
|
||||||
|
className: `cm-s-${name}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.reduce((accumulator, value) => accumulator.concat(value), [])
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
|
||||||
|
themes.splice(
|
||||||
|
themes.findIndex(({ name }) => name === 'solarized'),
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
name: 'solarized dark',
|
||||||
|
path: path.join(paths[0], 'solarized.css'),
|
||||||
|
className: `cm-s-solarized cm-s-dark`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'solarized light',
|
||||||
|
path: path.join(paths[0], 'solarized.css'),
|
||||||
|
className: `cm-s-solarized cm-s-light`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
themes.splice(0, 0, {
|
||||||
|
name: 'default',
|
||||||
|
path: path.join(paths[0], 'elegant.css'),
|
||||||
|
className: `cm-s-default`
|
||||||
|
})
|
||||||
|
|
||||||
|
const snippetFile =
|
||||||
|
process.env.NODE_ENV !== 'test'
|
||||||
|
? path.join(app.getPath('userData'), 'snippets.json')
|
||||||
|
: '' // return nothing as we specified different path to snippets.json in test
|
||||||
|
|
||||||
const consts = {
|
const consts = {
|
||||||
FOLDER_COLORS: [
|
FOLDER_COLORS: [
|
||||||
@@ -35,8 +76,16 @@ const consts = {
|
|||||||
'Dodger Blue',
|
'Dodger Blue',
|
||||||
'Violet Eggplant'
|
'Violet Eggplant'
|
||||||
],
|
],
|
||||||
THEMES: ['default'].concat(themes),
|
THEMES: themes,
|
||||||
SNIPPET_FILE: snippetFile
|
SNIPPET_FILE: snippetFile,
|
||||||
|
DEFAULT_EDITOR_FONT_FAMILY: [
|
||||||
|
'Monaco',
|
||||||
|
'Menlo',
|
||||||
|
'Ubuntu Mono',
|
||||||
|
'Consolas',
|
||||||
|
'source-code-pro',
|
||||||
|
'monospace'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = consts
|
module.exports = consts
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem } = remote
|
const { Menu, MenuItem } = remote
|
||||||
|
|
||||||
function popup (templates) {
|
function popup(templates) {
|
||||||
const menu = new Menu()
|
const menu = new Menu()
|
||||||
templates.forEach((item) => {
|
templates.forEach(item => {
|
||||||
menu.append(new MenuItem(item))
|
menu.append(new MenuItem(item))
|
||||||
})
|
})
|
||||||
menu.popup(remote.getCurrentWindow())
|
menu.popup(remote.getCurrentWindow())
|
||||||
|
|||||||
152
browser/lib/contextMenuBuilder.js
Normal file
152
browser/lib/contextMenuBuilder.js
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
const { remote } = require('electron')
|
||||||
|
const { Menu } = remote.require('electron')
|
||||||
|
const { clipboard } = remote.require('electron')
|
||||||
|
const { shell } = remote.require('electron')
|
||||||
|
const spellcheck = require('./spellcheck')
|
||||||
|
const uri2path = require('file-uri-to-path')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the context menu that is shown when there is a right click in the editor of a (not-snippet) note.
|
||||||
|
* If the word is does not contains a spelling error (determined by the 'error style'), no suggestions for corrections are requested
|
||||||
|
* => they are not visible in the context menu
|
||||||
|
* @param editor CodeMirror editor
|
||||||
|
* @param {MouseEvent} event that has triggered the creation of the context menu
|
||||||
|
* @returns {Electron.Menu} The created electron context menu
|
||||||
|
*/
|
||||||
|
const buildEditorContextMenu = function(editor, event) {
|
||||||
|
if (
|
||||||
|
editor == null ||
|
||||||
|
event == null ||
|
||||||
|
event.pageX == null ||
|
||||||
|
event.pageY == null
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const cursor = editor.coordsChar({ left: event.pageX, top: event.pageY })
|
||||||
|
const wordRange = editor.findWordAt(cursor)
|
||||||
|
const word = editor.getRange(wordRange.anchor, wordRange.head)
|
||||||
|
const existingMarks = editor.findMarks(wordRange.anchor, wordRange.head) || []
|
||||||
|
let isMisspelled = false
|
||||||
|
for (const mark of existingMarks) {
|
||||||
|
if (mark.className === spellcheck.getCSSClassName()) {
|
||||||
|
isMisspelled = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let suggestion = []
|
||||||
|
if (isMisspelled) {
|
||||||
|
suggestion = spellcheck.getSpellingSuggestion(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = {
|
||||||
|
isMisspelled: isMisspelled,
|
||||||
|
spellingSuggestions: suggestion
|
||||||
|
}
|
||||||
|
const template = [
|
||||||
|
{
|
||||||
|
role: 'cut'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'copy'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'paste'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'selectall'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (selection.isMisspelled) {
|
||||||
|
const suggestions = selection.spellingSuggestions
|
||||||
|
template.unshift.apply(
|
||||||
|
template,
|
||||||
|
suggestions
|
||||||
|
.map(function(suggestion) {
|
||||||
|
return {
|
||||||
|
label: suggestion,
|
||||||
|
click: function(suggestion) {
|
||||||
|
if (editor != null) {
|
||||||
|
editor.replaceRange(
|
||||||
|
suggestion.label,
|
||||||
|
wordRange.anchor,
|
||||||
|
wordRange.head
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.concat({
|
||||||
|
type: 'separator'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return Menu.buildFromTemplate(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the context menu that is shown when there is a right click Markdown preview of a (not-snippet) note.
|
||||||
|
* @param {MarkdownPreview} markdownPreview
|
||||||
|
* @param {MouseEvent} event that has triggered the creation of the context menu
|
||||||
|
* @returns {Electron.Menu} The created electron context menu
|
||||||
|
*/
|
||||||
|
const buildMarkdownPreviewContextMenu = function(markdownPreview, event) {
|
||||||
|
if (
|
||||||
|
markdownPreview == null ||
|
||||||
|
event == null ||
|
||||||
|
event.pageX == null ||
|
||||||
|
event.pageY == null
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default context menu inclusions
|
||||||
|
const template = [
|
||||||
|
{
|
||||||
|
role: 'copy'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'selectall'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
if (
|
||||||
|
event.target.tagName.toLowerCase() === 'a' &&
|
||||||
|
event.target.getAttribute('href')
|
||||||
|
) {
|
||||||
|
// Link opener for files on the local system pointed to by href
|
||||||
|
const href = event.target.href
|
||||||
|
const isLocalFile = href.startsWith('file:')
|
||||||
|
if (isLocalFile) {
|
||||||
|
const absPath = uri2path(href)
|
||||||
|
try {
|
||||||
|
if (fs.lstatSync(absPath).isFile()) {
|
||||||
|
template.push({
|
||||||
|
label: i18n.__('Show in explorer'),
|
||||||
|
click: e => shell.showItemInFolder(absPath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
'Error while evaluating if the file is locally available',
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add option to context menu to copy url
|
||||||
|
template.push({
|
||||||
|
label: i18n.__('Copy Url'),
|
||||||
|
click: e => clipboard.writeText(href)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Menu.buildFromTemplate(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
buildEditorContextMenu: buildEditorContextMenu,
|
||||||
|
buildMarkdownPreviewContextMenu: buildMarkdownPreviewContextMenu
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export default function convertModeName (name) {
|
export default function convertModeName(name) {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case 'ejs':
|
case 'ejs':
|
||||||
return 'Embedded Javascript'
|
return 'Embedded Javascript'
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
import 'codemirror-mode-elixir'
|
import 'codemirror-mode-elixir'
|
||||||
|
|
||||||
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
|
const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus')
|
||||||
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})
|
if (stylusCodeInfo == null) {
|
||||||
|
CodeMirror.modeInfo.push({
|
||||||
|
name: 'Stylus',
|
||||||
|
mime: 'text/x-styl',
|
||||||
|
mode: 'stylus',
|
||||||
|
ext: ['styl'],
|
||||||
|
alias: ['styl']
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
stylusCodeInfo.alias = ['styl']
|
||||||
|
}
|
||||||
|
CodeMirror.modeInfo.push({
|
||||||
|
name: 'Elixir',
|
||||||
|
mime: 'text/x-elixir',
|
||||||
|
mode: 'elixir',
|
||||||
|
ext: ['ex']
|
||||||
|
})
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import moment from 'moment'
|
|||||||
* @param {mixed}
|
* @param {mixed}
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
export function formatDate (date) {
|
export function formatDate(date) {
|
||||||
const m = moment(date)
|
const m = moment(date)
|
||||||
if (!m.isValid()) {
|
if (!m.isValid()) {
|
||||||
throw Error('Invalid argument.')
|
throw Error('Invalid argument.')
|
||||||
|
|||||||
@@ -1,23 +1,54 @@
|
|||||||
export function findNoteTitle (value) {
|
export function findNoteTitle(
|
||||||
|
value,
|
||||||
|
enableFrontMatterTitle,
|
||||||
|
frontMatterTitleField = 'title'
|
||||||
|
) {
|
||||||
const splitted = value.split('\n')
|
const splitted = value.split('\n')
|
||||||
let title = null
|
let title = null
|
||||||
let isInsideCodeBlock = false
|
let isInsideCodeBlock = false
|
||||||
|
|
||||||
splitted.some((line, index) => {
|
if (splitted[0] === '---') {
|
||||||
const trimmedLine = line.trim()
|
let line = 0
|
||||||
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
while (++line < splitted.length) {
|
||||||
if (trimmedLine.match('```')) {
|
if (
|
||||||
isInsideCodeBlock = !isInsideCodeBlock
|
enableFrontMatterTitle &&
|
||||||
|
splitted[line].startsWith(frontMatterTitleField + ':')
|
||||||
|
) {
|
||||||
|
title = splitted[line]
|
||||||
|
.substring(frontMatterTitleField.length + 1)
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (splitted[line] === '---') {
|
||||||
|
splitted.splice(0, line + 1)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
|
}
|
||||||
title = trimmedLine
|
|
||||||
return true
|
if (title === null) {
|
||||||
}
|
splitted.some((line, index) => {
|
||||||
})
|
const trimmedLine = line.trim()
|
||||||
|
const trimmedNextLine =
|
||||||
|
splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
||||||
|
if (trimmedLine.match('```')) {
|
||||||
|
isInsideCodeBlock = !isInsideCodeBlock
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
isInsideCodeBlock === false &&
|
||||||
|
(trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))
|
||||||
|
) {
|
||||||
|
title = trimmedLine
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (title === null) {
|
if (title === null) {
|
||||||
title = ''
|
title = ''
|
||||||
splitted.some((line) => {
|
splitted.some(line => {
|
||||||
if (line.trim().length > 0) {
|
if (line.trim().length > 0) {
|
||||||
title = line.trim()
|
title = line.trim()
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
|
||||||
export function findStorage (storageKey) {
|
export function findStorage(storageKey) {
|
||||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
if (!_.isArray(cachedStorageList))
|
||||||
const storage = _.find(cachedStorageList, {key: storageKey})
|
throw new Error("Target storage doesn't exist.")
|
||||||
if (storage === undefined) 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
|
return storage
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
export function getTodoStatus (content) {
|
export function getTodoStatus(content) {
|
||||||
const splitted = content.split('\n')
|
const splitted = content.split('\n')
|
||||||
let numberOfTodo = 0
|
let numberOfTodo = 0
|
||||||
let numberOfCompletedTodo = 0
|
let numberOfCompletedTodo = 0
|
||||||
|
|
||||||
splitted.forEach((line) => {
|
splitted.forEach(line => {
|
||||||
const trimmedLine = line.trim()
|
const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
|
||||||
if (trimmedLine.match(/^[\+\-\*] \[(\s|x)\] ./)) {
|
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
|
||||||
numberOfTodo++
|
numberOfTodo++
|
||||||
}
|
}
|
||||||
if (trimmedLine.match(/^[\+\-\*] \[x\] ./)) {
|
if (trimmedLine.match(/^[+\-*] \[x] ./i)) {
|
||||||
numberOfCompletedTodo++
|
numberOfCompletedTodo++
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -19,7 +19,7 @@ export function getTodoStatus (content) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTodoPercentageOfCompleted (content) {
|
export function getTodoPercentageOfCompleted(content) {
|
||||||
const state = getTodoStatus(content)
|
const state = getTodoStatus(content)
|
||||||
return Math.floor(state.completed / state.total * 100)
|
return Math.floor((state.completed / state.total) * 100)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function decodeEntities (text) {
|
export function decodeEntities(text) {
|
||||||
var entities = [
|
var entities = [
|
||||||
['apos', '\''],
|
['apos', "'"],
|
||||||
['amp', '&'],
|
['amp', '&'],
|
||||||
['lt', '<'],
|
['lt', '<'],
|
||||||
['gt', '>'],
|
['gt', '>'],
|
||||||
@@ -24,16 +24,16 @@ export function decodeEntities (text) {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
export function encodeEntities (text) {
|
export function encodeEntities(text) {
|
||||||
const entities = [
|
const entities = [
|
||||||
['\'', 'apos'],
|
["'", 'apos'],
|
||||||
['<', 'lt'],
|
['<', 'lt'],
|
||||||
['>', 'gt'],
|
['>', 'gt'],
|
||||||
['\\?', '#63'],
|
['\\?', '#63'],
|
||||||
['\\$', '#36']
|
['\\$', '#36']
|
||||||
]
|
]
|
||||||
|
|
||||||
entities.forEach((entity) => {
|
entities.forEach(entity => {
|
||||||
text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`)
|
text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`)
|
||||||
})
|
})
|
||||||
return text
|
return text
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ const i18n = new (require('i18n-2'))({
|
|||||||
// setup some locales - other locales default to the first locale
|
// setup some locales - other locales default to the first locale
|
||||||
locales: getLocales(),
|
locales: getLocales(),
|
||||||
extension: '.json',
|
extension: '.json',
|
||||||
directory: process.env.NODE_ENV === 'production'
|
directory:
|
||||||
? path.join(app.getAppPath(), './locales')
|
process.env.NODE_ENV === 'production'
|
||||||
: path.resolve('./locales'),
|
? path.join(app.getAppPath(), './locales')
|
||||||
|
: path.resolve('./locales'),
|
||||||
devMode: false
|
devMode: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const _ = require('lodash')
|
|
||||||
const uuidv4 = require('uuid/v4')
|
const uuidv4 = require('uuid/v4')
|
||||||
|
|
||||||
module.exports = function (uuid) {
|
module.exports = function(uuid) {
|
||||||
if (typeof uuid === typeof true && uuid) {
|
if (typeof uuid === typeof true && uuid) {
|
||||||
return uuidv4()
|
return uuidv4()
|
||||||
}
|
}
|
||||||
|
|||||||
282
browser/lib/markdown-it-deflist.js
Normal file
282
browser/lib/markdown-it-deflist.js
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
module.exports = function definitionListPlugin(md) {
|
||||||
|
var isSpace = md.utils.isSpace
|
||||||
|
|
||||||
|
// Search `[:~][\n ]`, returns next pos after marker on success
|
||||||
|
// or -1 on fail.
|
||||||
|
function skipMarker(state, line) {
|
||||||
|
let start = state.bMarks[line] + state.tShift[line]
|
||||||
|
const max = state.eMarks[line]
|
||||||
|
|
||||||
|
if (start >= max) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bullet
|
||||||
|
const marker = state.src.charCodeAt(start++)
|
||||||
|
if (marker !== 0x7e /* ~ */ && marker !== 0x3a /* : */) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
const pos = state.skipSpaces(start)
|
||||||
|
|
||||||
|
// require space after ":"
|
||||||
|
if (start === pos) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
|
||||||
|
function markTightParagraphs(state, idx) {
|
||||||
|
const level = state.level + 2
|
||||||
|
|
||||||
|
let i
|
||||||
|
let l
|
||||||
|
for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
|
||||||
|
if (
|
||||||
|
state.tokens[i].level === level &&
|
||||||
|
state.tokens[i].type === 'paragraph_open'
|
||||||
|
) {
|
||||||
|
state.tokens[i + 2].hidden = true
|
||||||
|
state.tokens[i].hidden = true
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deflist(state, startLine, endLine, silent) {
|
||||||
|
var ch,
|
||||||
|
contentStart,
|
||||||
|
ddLine,
|
||||||
|
dtLine,
|
||||||
|
itemLines,
|
||||||
|
listLines,
|
||||||
|
listTokIdx,
|
||||||
|
max,
|
||||||
|
newEndLine,
|
||||||
|
nextLine,
|
||||||
|
offset,
|
||||||
|
oldDDIndent,
|
||||||
|
oldIndent,
|
||||||
|
oldLineMax,
|
||||||
|
oldParentType,
|
||||||
|
oldSCount,
|
||||||
|
oldTShift,
|
||||||
|
oldTight,
|
||||||
|
pos,
|
||||||
|
prevEmptyEnd,
|
||||||
|
tight,
|
||||||
|
token
|
||||||
|
|
||||||
|
if (silent) {
|
||||||
|
// quirk: validation mode validates a dd block only, not a whole deflist
|
||||||
|
if (state.ddIndent < 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return skipMarker(state, startLine) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
nextLine = startLine + 1
|
||||||
|
if (nextLine >= endLine) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.isEmpty(nextLine)) {
|
||||||
|
nextLine++
|
||||||
|
if (nextLine >= endLine) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.sCount[nextLine] < state.blkIndent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
contentStart = skipMarker(state, nextLine)
|
||||||
|
if (contentStart < 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start list
|
||||||
|
listTokIdx = state.tokens.length
|
||||||
|
tight = true
|
||||||
|
|
||||||
|
token = state.push('dl_open', 'dl', 1)
|
||||||
|
token.map = listLines = [startLine, 0]
|
||||||
|
|
||||||
|
//
|
||||||
|
// Iterate list items
|
||||||
|
//
|
||||||
|
|
||||||
|
dtLine = startLine
|
||||||
|
ddLine = nextLine
|
||||||
|
|
||||||
|
// One definition list can contain multiple DTs,
|
||||||
|
// and one DT can be followed by multiple DDs.
|
||||||
|
//
|
||||||
|
// Thus, there is two loops here, and label is
|
||||||
|
// needed to break out of the second one
|
||||||
|
//
|
||||||
|
/* eslint no-labels:0,block-scoped-var:0 */
|
||||||
|
OUTER: for (;;) {
|
||||||
|
prevEmptyEnd = false
|
||||||
|
|
||||||
|
token = state.push('dt_open', 'dt', 1)
|
||||||
|
token.map = [dtLine, dtLine]
|
||||||
|
|
||||||
|
token = state.push('inline', '', 0)
|
||||||
|
token.map = [dtLine, dtLine]
|
||||||
|
token.content = state
|
||||||
|
.getLines(dtLine, dtLine + 1, state.blkIndent, false)
|
||||||
|
.trim()
|
||||||
|
token.children = []
|
||||||
|
|
||||||
|
token = state.push('dt_close', 'dt', -1)
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
token = state.push('dd_open', 'dd', 1)
|
||||||
|
token.map = itemLines = [ddLine, 0]
|
||||||
|
|
||||||
|
pos = contentStart
|
||||||
|
max = state.eMarks[ddLine]
|
||||||
|
offset =
|
||||||
|
state.sCount[ddLine] +
|
||||||
|
contentStart -
|
||||||
|
(state.bMarks[ddLine] + state.tShift[ddLine])
|
||||||
|
|
||||||
|
while (pos < max) {
|
||||||
|
ch = state.src.charCodeAt(pos)
|
||||||
|
|
||||||
|
if (isSpace(ch)) {
|
||||||
|
if (ch === 0x09) {
|
||||||
|
offset += 4 - (offset % 4)
|
||||||
|
} else {
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
contentStart = pos
|
||||||
|
|
||||||
|
oldTight = state.tight
|
||||||
|
oldDDIndent = state.ddIndent
|
||||||
|
oldIndent = state.blkIndent
|
||||||
|
oldTShift = state.tShift[ddLine]
|
||||||
|
oldSCount = state.sCount[ddLine]
|
||||||
|
oldParentType = state.parentType
|
||||||
|
state.blkIndent = state.ddIndent = state.sCount[ddLine] + 2
|
||||||
|
state.tShift[ddLine] = contentStart - state.bMarks[ddLine]
|
||||||
|
state.sCount[ddLine] = offset
|
||||||
|
state.tight = true
|
||||||
|
state.parentType = 'deflist'
|
||||||
|
|
||||||
|
newEndLine = ddLine
|
||||||
|
while (
|
||||||
|
++newEndLine < endLine &&
|
||||||
|
(state.sCount[newEndLine] >= state.sCount[ddLine] ||
|
||||||
|
state.isEmpty(newEndLine))
|
||||||
|
) {}
|
||||||
|
|
||||||
|
oldLineMax = state.lineMax
|
||||||
|
state.lineMax = newEndLine
|
||||||
|
|
||||||
|
state.md.block.tokenize(state, ddLine, newEndLine, true)
|
||||||
|
|
||||||
|
state.lineMax = oldLineMax
|
||||||
|
|
||||||
|
// If any of list item is tight, mark list as tight
|
||||||
|
if (!state.tight || prevEmptyEnd) {
|
||||||
|
tight = false
|
||||||
|
}
|
||||||
|
// Item become loose if finish with empty line,
|
||||||
|
// but we should filter last element, because it means list finish
|
||||||
|
prevEmptyEnd = state.line - ddLine > 1 && state.isEmpty(state.line - 1)
|
||||||
|
|
||||||
|
state.tShift[ddLine] = oldTShift
|
||||||
|
state.sCount[ddLine] = oldSCount
|
||||||
|
state.tight = oldTight
|
||||||
|
state.parentType = oldParentType
|
||||||
|
state.blkIndent = oldIndent
|
||||||
|
state.ddIndent = oldDDIndent
|
||||||
|
|
||||||
|
token = state.push('dd_close', 'dd', -1)
|
||||||
|
|
||||||
|
itemLines[1] = nextLine = state.line
|
||||||
|
|
||||||
|
if (nextLine >= endLine) {
|
||||||
|
break OUTER
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.sCount[nextLine] < state.blkIndent) {
|
||||||
|
break OUTER
|
||||||
|
}
|
||||||
|
contentStart = skipMarker(state, nextLine)
|
||||||
|
if (contentStart < 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ddLine = nextLine
|
||||||
|
|
||||||
|
// go to the next loop iteration:
|
||||||
|
// insert DD tag and repeat checking
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextLine >= endLine) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
dtLine = nextLine
|
||||||
|
|
||||||
|
if (state.isEmpty(dtLine)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (state.sCount[dtLine] < state.blkIndent) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
ddLine = dtLine + 1
|
||||||
|
if (ddLine >= endLine) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (state.isEmpty(ddLine)) {
|
||||||
|
ddLine++
|
||||||
|
}
|
||||||
|
if (ddLine >= endLine) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.sCount[ddLine] < state.blkIndent) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
contentStart = skipMarker(state, ddLine)
|
||||||
|
if (contentStart < 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to the next loop iteration:
|
||||||
|
// insert DT and DD tags and repeat checking
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finilize list
|
||||||
|
token = state.push('dl_close', 'dl', -1)
|
||||||
|
|
||||||
|
listLines[1] = nextLine
|
||||||
|
|
||||||
|
state.line = nextLine
|
||||||
|
|
||||||
|
// mark paragraphs tight if needed
|
||||||
|
if (tight) {
|
||||||
|
markTightParagraphs(state, listTokIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
md.block.ruler.before('paragraph', 'deflist', deflist, {
|
||||||
|
alt: ['paragraph', 'reference']
|
||||||
|
})
|
||||||
|
}
|
||||||
141
browser/lib/markdown-it-fence.js
Normal file
141
browser/lib/markdown-it-fence.js
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
module.exports = function(md, renderers, defaultRenderer) {
|
||||||
|
const paramsRE = /^[ \t]*([\w+#-]+)?(?:\(((?:\s*\w[-\w]*(?:=(?:'(?:.*?[^\\])?'|"(?:.*?[^\\])?"|(?:[^'"][^\s]*)))?)*)\))?(?::([^:]*)(?::(\d+))?)?\s*$/
|
||||||
|
|
||||||
|
function fence(state, startLine, endLine, silent) {
|
||||||
|
let pos = state.bMarks[startLine] + state.tShift[startLine]
|
||||||
|
let max = state.eMarks[startLine]
|
||||||
|
|
||||||
|
if (state.sCount[startLine] - state.blkIndent >= 4 || pos + 3 > max) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const marker = state.src.charCodeAt(pos)
|
||||||
|
if (marker !== 0x7e /* ~ */ && marker !== 0x60 /* ` */) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let mem = pos
|
||||||
|
pos = state.skipChars(pos, marker)
|
||||||
|
|
||||||
|
let len = pos - mem
|
||||||
|
if (len < 3) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const markup = state.src.slice(mem, pos)
|
||||||
|
const params = state.src.slice(pos, max)
|
||||||
|
|
||||||
|
if (silent) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
let nextLine = startLine
|
||||||
|
let haveEndMarker = false
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
nextLine++
|
||||||
|
if (nextLine >= endLine) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine]
|
||||||
|
max = state.eMarks[nextLine]
|
||||||
|
|
||||||
|
if (pos < max && state.sCount[nextLine] < state.blkIndent) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
state.src.charCodeAt(pos) !== marker ||
|
||||||
|
state.sCount[nextLine] - state.blkIndent >= 4
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = state.skipChars(pos, marker)
|
||||||
|
|
||||||
|
if (pos - mem < len) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = state.skipSpaces(pos)
|
||||||
|
|
||||||
|
if (pos >= max) {
|
||||||
|
haveEndMarker = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
len = state.sCount[startLine]
|
||||||
|
state.line = nextLine + (haveEndMarker ? 1 : 0)
|
||||||
|
|
||||||
|
const parameters = {}
|
||||||
|
let langType = ''
|
||||||
|
let fileName = ''
|
||||||
|
let firstLineNumber = 1
|
||||||
|
|
||||||
|
let match = paramsRE.exec(params)
|
||||||
|
if (match) {
|
||||||
|
if (match[1]) {
|
||||||
|
langType = match[1]
|
||||||
|
}
|
||||||
|
if (match[3]) {
|
||||||
|
fileName = match[3]
|
||||||
|
}
|
||||||
|
if (match[4]) {
|
||||||
|
firstLineNumber = parseInt(match[4], 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match[2]) {
|
||||||
|
const params = match[2]
|
||||||
|
const regex = /(\w[-\w]*)(?:=(?:'(.*?[^\\])?'|"(.*?[^\\])?"|([^'"][^\s]*)))?/g
|
||||||
|
|
||||||
|
let name, value
|
||||||
|
while ((match = regex.exec(params))) {
|
||||||
|
name = match[1]
|
||||||
|
value = match[2] || match[3] || match[4] || null
|
||||||
|
|
||||||
|
const height = /^(\d+)h$/.exec(name)
|
||||||
|
if (height && !value) {
|
||||||
|
parameters.height = height[1]
|
||||||
|
} else {
|
||||||
|
parameters[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let token
|
||||||
|
if (renderers[langType]) {
|
||||||
|
token = state.push(`${langType}_fence`, 'div', 0)
|
||||||
|
} else {
|
||||||
|
token = state.push('_fence', 'code', 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
token.langType = langType
|
||||||
|
token.fileName = fileName
|
||||||
|
token.firstLineNumber = firstLineNumber
|
||||||
|
token.parameters = parameters
|
||||||
|
|
||||||
|
token.content = state.getLines(startLine + 1, nextLine, len, true)
|
||||||
|
token.markup = markup
|
||||||
|
token.map = [startLine, state.line]
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
md.block.ruler.before('fence', '_fence', fence, {
|
||||||
|
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const name in renderers) {
|
||||||
|
md.renderer.rules[`${name}_fence`] = (tokens, index) =>
|
||||||
|
renderers[name](tokens[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultRenderer) {
|
||||||
|
md.renderer.rules['_fence'] = (tokens, index) =>
|
||||||
|
defaultRenderer(tokens[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
29
browser/lib/markdown-it-frontmatter.js
Normal file
29
browser/lib/markdown-it-frontmatter.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
module.exports = function frontMatterPlugin(md) {
|
||||||
|
function frontmatter(state, startLine, endLine, silent) {
|
||||||
|
if (
|
||||||
|
startLine !== 0 ||
|
||||||
|
state.src.substr(startLine, state.eMarks[0]) !== '---'
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = 0
|
||||||
|
while (++line < state.lineMax) {
|
||||||
|
if (
|
||||||
|
state.src.substring(state.bMarks[line], state.eMarks[line]) === '---'
|
||||||
|
) {
|
||||||
|
state.line = line + 1
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
md.block.ruler.before('table', 'frontmatter', frontmatter, {
|
||||||
|
alt: ['paragraph', 'reference', 'blockquote', 'list']
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,23 +1,141 @@
|
|||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
import sanitizeHtml from 'sanitize-html'
|
import sanitizeHtml from 'sanitize-html'
|
||||||
|
import { escapeHtmlCharacters } from './utils'
|
||||||
|
import url from 'url'
|
||||||
|
|
||||||
module.exports = function sanitizePlugin (md, options) {
|
module.exports = function sanitizePlugin(md, options) {
|
||||||
options = options || {}
|
options = options || {}
|
||||||
|
|
||||||
md.core.ruler.after('linkify', 'sanitize_inline', state => {
|
md.core.ruler.after('linkify', 'sanitize_inline', state => {
|
||||||
for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) {
|
for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) {
|
||||||
if (state.tokens[tokenIdx].type === 'html_block') {
|
if (state.tokens[tokenIdx].type === 'html_block') {
|
||||||
state.tokens[tokenIdx].content = sanitizeHtml(state.tokens[tokenIdx].content, options)
|
state.tokens[tokenIdx].content = sanitizeHtml(
|
||||||
|
state.tokens[tokenIdx].content,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (state.tokens[tokenIdx].type.match(/.*_fence$/)) {
|
||||||
|
// escapeHtmlCharacters has better performance
|
||||||
|
state.tokens[tokenIdx].content = escapeHtmlCharacters(
|
||||||
|
state.tokens[tokenIdx].content,
|
||||||
|
{ skipSingleQuote: true }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (state.tokens[tokenIdx].type === 'inline') {
|
if (state.tokens[tokenIdx].type === 'inline') {
|
||||||
const inlineTokens = state.tokens[tokenIdx].children
|
const inlineTokens = state.tokens[tokenIdx].children
|
||||||
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
|
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
|
||||||
if (inlineTokens[childIdx].type === 'html_inline') {
|
if (inlineTokens[childIdx].type === 'html_inline') {
|
||||||
inlineTokens[childIdx].content = sanitizeHtml(inlineTokens[childIdx].content, options)
|
inlineTokens[childIdx].content = sanitizeInline(
|
||||||
|
inlineTokens[childIdx].content,
|
||||||
|
options
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
|
||||||
|
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/gi
|
||||||
|
|
||||||
|
function sanitizeInline(html, options) {
|
||||||
|
let match = tagRegex.exec(html)
|
||||||
|
if (!match) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
allowedTags,
|
||||||
|
allowedAttributes,
|
||||||
|
selfClosing,
|
||||||
|
allowedSchemesAppliedToAttributes
|
||||||
|
} = options
|
||||||
|
|
||||||
|
if (match[1] !== undefined) {
|
||||||
|
// opening tag
|
||||||
|
const tag = match[1].toLowerCase()
|
||||||
|
if (allowedTags.indexOf(tag) === -1) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributes = match[2]
|
||||||
|
|
||||||
|
let attrs = ''
|
||||||
|
let name
|
||||||
|
let value
|
||||||
|
|
||||||
|
while ((match = attributesRegex.exec(attributes))) {
|
||||||
|
name = match[1].toLowerCase()
|
||||||
|
value = match[3]
|
||||||
|
|
||||||
|
if (
|
||||||
|
allowedAttributes['*'].indexOf(name) !== -1 ||
|
||||||
|
(allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)
|
||||||
|
) {
|
||||||
|
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
|
||||||
|
if (
|
||||||
|
naughtyHRef(value, options) ||
|
||||||
|
(tag === 'iframe' &&
|
||||||
|
name === 'src' &&
|
||||||
|
naughtyIFrame(value, options))
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs += ` ${name}`
|
||||||
|
if (match[2]) {
|
||||||
|
attrs += `="${value}"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selfClosing.indexOf(tag) === -1) {
|
||||||
|
return '<' + tag + attrs + '>'
|
||||||
|
} else {
|
||||||
|
return '<' + tag + attrs + ' />'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// closing tag
|
||||||
|
if (allowedTags.indexOf(match[4].toLowerCase()) !== -1) {
|
||||||
|
return html
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function naughtyHRef(href, options) {
|
||||||
|
// href = href.replace(/[\x00-\x20]+/g, '')
|
||||||
|
if (!href) {
|
||||||
|
// No href
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
href = href.replace(/<\!\-\-.*?\-\-\>/g, '')
|
||||||
|
|
||||||
|
const matches = href.match(/^([a-zA-Z]+)\:/)
|
||||||
|
if (!matches) {
|
||||||
|
if (href.match(/^[\/\\]{2}/)) {
|
||||||
|
return !options.allowProtocolRelative
|
||||||
|
}
|
||||||
|
|
||||||
|
// No scheme
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheme = matches[1].toLowerCase()
|
||||||
|
|
||||||
|
return options.allowedSchemes.indexOf(scheme) === -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function naughtyIFrame(src, options) {
|
||||||
|
try {
|
||||||
|
const parsed = url.parse(src, false, true)
|
||||||
|
|
||||||
|
return options.allowedIframeHostnames.index(parsed.hostname) === -1
|
||||||
|
} catch (e) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
104
browser/lib/markdown-toc-generator.js
Normal file
104
browser/lib/markdown-toc-generator.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Markdown table of contents generator
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EOL } from 'os'
|
||||||
|
import toc from 'markdown-toc'
|
||||||
|
import mdlink from 'markdown-link'
|
||||||
|
import slugify from './slugify'
|
||||||
|
|
||||||
|
const hasProp = Object.prototype.hasOwnProperty
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From @enyaxu/markdown-it-anchor
|
||||||
|
*/
|
||||||
|
function uniqueSlug(slug, slugs, opts) {
|
||||||
|
let uniq = slug
|
||||||
|
let i = opts.uniqueSlugStartIndex
|
||||||
|
while (hasProp.call(slugs, uniq)) uniq = `${slug}-${i++}`
|
||||||
|
slugs[uniq] = true
|
||||||
|
return uniq
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkify(token) {
|
||||||
|
token.content = mdlink(token.content, `#${decodeURI(token.slug)}`)
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOC_MARKER_START = '<!-- toc -->'
|
||||||
|
const TOC_MARKER_END = '<!-- tocstop -->'
|
||||||
|
|
||||||
|
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes care of proper updating given editor with TOC.
|
||||||
|
* If TOC doesn't exit in the editor, it's inserted at current caret position.
|
||||||
|
* Otherwise,TOC is updated in place.
|
||||||
|
* @param editor CodeMirror editor to be updated with TOC
|
||||||
|
*/
|
||||||
|
export function generateInEditor(editor) {
|
||||||
|
function updateExistingToc() {
|
||||||
|
const toc = generate(editor.getValue())
|
||||||
|
const search = editor.getSearchCursor(tocRegex)
|
||||||
|
while (search.findNext()) {
|
||||||
|
search.replace(toc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTocAtCursorPosition() {
|
||||||
|
const toc = generate(
|
||||||
|
editor.getRange(editor.getCursor(), { line: Infinity })
|
||||||
|
)
|
||||||
|
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tocExistsInEditor(editor)) {
|
||||||
|
updateExistingToc()
|
||||||
|
} else {
|
||||||
|
addTocAtCursorPosition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tocExistsInEditor(editor) {
|
||||||
|
return tocRegex.test(editor.getValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates MD TOC based on MD document passed as string.
|
||||||
|
* @param markdownText MD document
|
||||||
|
* @returns generatedTOC String containing generated TOC
|
||||||
|
*/
|
||||||
|
export function generate(markdownText) {
|
||||||
|
const slugs = {}
|
||||||
|
const opts = {
|
||||||
|
uniqueSlugStartIndex: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = toc(markdownText, {
|
||||||
|
slugify: title => {
|
||||||
|
return uniqueSlug(slugify(title), slugs, opts)
|
||||||
|
},
|
||||||
|
linkify: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const md = toc.bullets(result.json.map(linkify), {
|
||||||
|
highest: result.highest
|
||||||
|
})
|
||||||
|
|
||||||
|
return TOC_MARKER_START + EOL + EOL + md + EOL + EOL + TOC_MARKER_END
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapTocWithEol(toc, editor) {
|
||||||
|
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
|
||||||
|
const rightWrap =
|
||||||
|
editor.getLine(editor.getCursor().line).length === editor.getCursor().ch
|
||||||
|
? ''
|
||||||
|
: EOL
|
||||||
|
return leftWrap + toc + rightWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
generate,
|
||||||
|
generateInEditor,
|
||||||
|
tocExistsInEditor
|
||||||
|
}
|
||||||
@@ -2,24 +2,28 @@ import markdownit from 'markdown-it'
|
|||||||
import sanitize from './markdown-it-sanitize-html'
|
import sanitize from './markdown-it-sanitize-html'
|
||||||
import emoji from 'markdown-it-emoji'
|
import emoji from 'markdown-it-emoji'
|
||||||
import math from '@rokt33r/markdown-it-math'
|
import math from '@rokt33r/markdown-it-math'
|
||||||
|
import mdurl from 'mdurl'
|
||||||
import smartArrows from 'markdown-it-smartarrows'
|
import smartArrows from 'markdown-it-smartarrows'
|
||||||
|
import markdownItTocAndAnchor from '@hikerpig/markdown-it-toc-and-anchor'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import katex from 'katex'
|
import katex from 'katex'
|
||||||
import { lastFindInArray } from './utils'
|
import { lastFindInArray } from './utils'
|
||||||
|
|
||||||
function createGutter (str, firstLineNumber) {
|
function createGutter(str, firstLineNumber) {
|
||||||
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||||
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
|
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
|
||||||
const lines = []
|
const lines = []
|
||||||
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
|
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
|
||||||
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
|
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
|
||||||
}
|
}
|
||||||
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
return (
|
||||||
|
'<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Markdown {
|
class Markdown {
|
||||||
constructor (options = {}) {
|
constructor(options = {}) {
|
||||||
const config = ConfigManager.get()
|
const config = ConfigManager.get()
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
typographer: config.preview.smartQuotes,
|
typographer: config.preview.smartQuotes,
|
||||||
@@ -27,56 +31,137 @@ class Markdown {
|
|||||||
html: true,
|
html: true,
|
||||||
xhtmlOut: true,
|
xhtmlOut: true,
|
||||||
breaks: config.preview.breaks,
|
breaks: config.preview.breaks,
|
||||||
highlight: function (str, lang) {
|
|
||||||
const delimiter = ':'
|
|
||||||
const langInfo = lang.split(delimiter)
|
|
||||||
const langType = langInfo[0]
|
|
||||||
const fileName = langInfo[1] || ''
|
|
||||||
const firstLineNumber = parseInt(langInfo[2], 10)
|
|
||||||
|
|
||||||
if (langType === 'flowchart') {
|
|
||||||
return `<pre class="flowchart">${str}</pre>`
|
|
||||||
}
|
|
||||||
if (langType === 'sequence') {
|
|
||||||
return `<pre class="sequence">${str}</pre>`
|
|
||||||
}
|
|
||||||
return '<pre class="code CodeMirror">' +
|
|
||||||
'<span class="filename">' + fileName + '</span>' +
|
|
||||||
createGutter(str, firstLineNumber) +
|
|
||||||
'<code class="' + langType + '">' +
|
|
||||||
str +
|
|
||||||
'</code></pre>'
|
|
||||||
},
|
|
||||||
sanitize: 'STRICT'
|
sanitize: 'STRICT'
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedOptions = Object.assign(defaultOptions, options)
|
const updatedOptions = Object.assign(defaultOptions, options)
|
||||||
this.md = markdownit(updatedOptions)
|
this.md = markdownit(updatedOptions)
|
||||||
|
this.md.linkify.set({ fuzzyLink: false })
|
||||||
|
|
||||||
if (updatedOptions.sanitize !== 'NONE') {
|
if (updatedOptions.sanitize !== 'NONE') {
|
||||||
const allowedTags = ['iframe', 'input', 'b',
|
const allowedTags = [
|
||||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
|
'iframe',
|
||||||
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote',
|
'input',
|
||||||
'dl', 'dt', 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details'
|
'b',
|
||||||
|
'h1',
|
||||||
|
'h2',
|
||||||
|
'h3',
|
||||||
|
'h4',
|
||||||
|
'h5',
|
||||||
|
'h6',
|
||||||
|
'h7',
|
||||||
|
'h8',
|
||||||
|
'br',
|
||||||
|
'b',
|
||||||
|
'i',
|
||||||
|
'strong',
|
||||||
|
'em',
|
||||||
|
'a',
|
||||||
|
'pre',
|
||||||
|
'code',
|
||||||
|
'img',
|
||||||
|
'tt',
|
||||||
|
'div',
|
||||||
|
'ins',
|
||||||
|
'del',
|
||||||
|
'sup',
|
||||||
|
'sub',
|
||||||
|
'p',
|
||||||
|
'ol',
|
||||||
|
'ul',
|
||||||
|
'table',
|
||||||
|
'thead',
|
||||||
|
'tbody',
|
||||||
|
'tfoot',
|
||||||
|
'blockquote',
|
||||||
|
'dl',
|
||||||
|
'dt',
|
||||||
|
'dd',
|
||||||
|
'kbd',
|
||||||
|
'q',
|
||||||
|
'samp',
|
||||||
|
'var',
|
||||||
|
'hr',
|
||||||
|
'ruby',
|
||||||
|
'rt',
|
||||||
|
'rp',
|
||||||
|
'li',
|
||||||
|
'tr',
|
||||||
|
'td',
|
||||||
|
'th',
|
||||||
|
's',
|
||||||
|
'strike',
|
||||||
|
'summary',
|
||||||
|
'details'
|
||||||
]
|
]
|
||||||
const allowedAttributes = [
|
const allowedAttributes = [
|
||||||
'abbr', 'accept', 'accept-charset',
|
'abbr',
|
||||||
'accesskey', 'action', 'align', 'alt', 'axis',
|
'accept',
|
||||||
'border', 'cellpadding', 'cellspacing', 'char',
|
'accept-charset',
|
||||||
'charoff', 'charset', 'checked',
|
'accesskey',
|
||||||
'clear', 'cols', 'colspan', 'color',
|
'action',
|
||||||
'compact', 'coords', 'datetime', 'dir',
|
'align',
|
||||||
'disabled', 'enctype', 'for', 'frame',
|
'alt',
|
||||||
'headers', 'height', 'hreflang',
|
'axis',
|
||||||
'hspace', 'ismap', 'label', 'lang',
|
'border',
|
||||||
'maxlength', 'media', 'method',
|
'cellpadding',
|
||||||
'multiple', 'name', 'nohref', 'noshade',
|
'cellspacing',
|
||||||
'nowrap', 'open', 'prompt', 'readonly', 'rel', 'rev',
|
'char',
|
||||||
'rows', 'rowspan', 'rules', 'scope',
|
'charoff',
|
||||||
'selected', 'shape', 'size', 'span',
|
'charset',
|
||||||
'start', 'summary', 'tabindex', 'target',
|
'checked',
|
||||||
'title', 'type', 'usemap', 'valign', 'value',
|
'clear',
|
||||||
'vspace', 'width', 'itemprop'
|
'cols',
|
||||||
|
'colspan',
|
||||||
|
'color',
|
||||||
|
'compact',
|
||||||
|
'coords',
|
||||||
|
'datetime',
|
||||||
|
'dir',
|
||||||
|
'disabled',
|
||||||
|
'enctype',
|
||||||
|
'for',
|
||||||
|
'frame',
|
||||||
|
'headers',
|
||||||
|
'height',
|
||||||
|
'hreflang',
|
||||||
|
'hspace',
|
||||||
|
'ismap',
|
||||||
|
'label',
|
||||||
|
'lang',
|
||||||
|
'maxlength',
|
||||||
|
'media',
|
||||||
|
'method',
|
||||||
|
'multiple',
|
||||||
|
'name',
|
||||||
|
'nohref',
|
||||||
|
'noshade',
|
||||||
|
'nowrap',
|
||||||
|
'open',
|
||||||
|
'prompt',
|
||||||
|
'readonly',
|
||||||
|
'rel',
|
||||||
|
'rev',
|
||||||
|
'rows',
|
||||||
|
'rowspan',
|
||||||
|
'rules',
|
||||||
|
'scope',
|
||||||
|
'selected',
|
||||||
|
'shape',
|
||||||
|
'size',
|
||||||
|
'span',
|
||||||
|
'start',
|
||||||
|
'summary',
|
||||||
|
'tabindex',
|
||||||
|
'target',
|
||||||
|
'title',
|
||||||
|
'type',
|
||||||
|
'usemap',
|
||||||
|
'valign',
|
||||||
|
'value',
|
||||||
|
'vspace',
|
||||||
|
'width',
|
||||||
|
'itemprop'
|
||||||
]
|
]
|
||||||
|
|
||||||
if (updatedOptions.sanitize === 'ALLOW_STYLES') {
|
if (updatedOptions.sanitize === 'ALLOW_STYLES') {
|
||||||
@@ -89,17 +174,21 @@ class Markdown {
|
|||||||
allowedTags,
|
allowedTags,
|
||||||
allowedAttributes: {
|
allowedAttributes: {
|
||||||
'*': allowedAttributes,
|
'*': allowedAttributes,
|
||||||
'a': ['href'],
|
a: ['href'],
|
||||||
'div': ['itemscope', 'itemtype'],
|
div: ['itemscope', 'itemtype'],
|
||||||
'blockquote': ['cite'],
|
blockquote: ['cite'],
|
||||||
'del': ['cite'],
|
del: ['cite'],
|
||||||
'ins': ['cite'],
|
ins: ['cite'],
|
||||||
'q': ['cite'],
|
q: ['cite'],
|
||||||
'img': ['src', 'width', 'height'],
|
img: ['src', 'width', 'height'],
|
||||||
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||||
'input': ['type', 'id', 'checked']
|
input: ['type', 'id', 'checked']
|
||||||
},
|
},
|
||||||
allowedIframeHostnames: ['www.youtube.com']
|
allowedIframeHostnames: ['www.youtube.com'],
|
||||||
|
selfClosing: ['img', 'br', 'hr', 'input'],
|
||||||
|
allowedSchemes: ['http', 'https', 'ftp', 'mailto'],
|
||||||
|
allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],
|
||||||
|
allowProtocolRelative: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +200,7 @@ class Markdown {
|
|||||||
inlineClose: config.preview.latexInlineClose,
|
inlineClose: config.preview.latexInlineClose,
|
||||||
blockOpen: config.preview.latexBlockOpen,
|
blockOpen: config.preview.latexBlockOpen,
|
||||||
blockClose: config.preview.latexBlockClose,
|
blockClose: config.preview.latexBlockClose,
|
||||||
inlineRenderer: function (str) {
|
inlineRenderer: function(str) {
|
||||||
let output = ''
|
let output = ''
|
||||||
try {
|
try {
|
||||||
output = katex.renderToString(str.trim())
|
output = katex.renderToString(str.trim())
|
||||||
@@ -120,7 +209,7 @@ class Markdown {
|
|||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
},
|
},
|
||||||
blockRenderer: function (str) {
|
blockRenderer: function(str) {
|
||||||
let output = ''
|
let output = ''
|
||||||
try {
|
try {
|
||||||
output = katex.renderToString(str.trim(), { displayMode: true })
|
output = katex.renderToString(str.trim(), { displayMode: true })
|
||||||
@@ -133,32 +222,171 @@ class Markdown {
|
|||||||
this.md.use(require('markdown-it-imsize'))
|
this.md.use(require('markdown-it-imsize'))
|
||||||
this.md.use(require('markdown-it-footnote'))
|
this.md.use(require('markdown-it-footnote'))
|
||||||
this.md.use(require('markdown-it-multimd-table'))
|
this.md.use(require('markdown-it-multimd-table'))
|
||||||
this.md.use(require('markdown-it-named-headers'), {
|
this.md.use(require('@enyaxu/markdown-it-anchor'), {
|
||||||
slugify: (header) => {
|
slugify: require('./slugify')
|
||||||
return encodeURI(header.trim()
|
|
||||||
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
|
||||||
.replace(/\s+/g, '-'))
|
|
||||||
.replace(/\-+$/, '')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
this.md.use(require('markdown-it-kbd'))
|
this.md.use(require('markdown-it-kbd'))
|
||||||
this.md.use(require('markdown-it-admonition'))
|
this.md.use(require('markdown-it-admonition'), {
|
||||||
|
types: [
|
||||||
|
'note',
|
||||||
|
'hint',
|
||||||
|
'attention',
|
||||||
|
'caution',
|
||||||
|
'danger',
|
||||||
|
'error',
|
||||||
|
'quote',
|
||||||
|
'abstract',
|
||||||
|
'question'
|
||||||
|
]
|
||||||
|
})
|
||||||
|
this.md.use(require('markdown-it-abbr'))
|
||||||
|
this.md.use(require('markdown-it-sub'))
|
||||||
|
this.md.use(require('markdown-it-sup'))
|
||||||
|
|
||||||
|
this.md.use(md => {
|
||||||
|
markdownItTocAndAnchor(md, {
|
||||||
|
toc: true,
|
||||||
|
tocPattern: /\[TOC\]/i,
|
||||||
|
anchorLink: false,
|
||||||
|
appendIdToHeading: false
|
||||||
|
})
|
||||||
|
|
||||||
|
md.renderer.rules.toc_open = () => '<div class="markdownIt-TOC-wrapper">'
|
||||||
|
md.renderer.rules.toc_close = () => '</div>'
|
||||||
|
})
|
||||||
|
|
||||||
|
this.md.use(require('./markdown-it-deflist'))
|
||||||
|
this.md.use(require('./markdown-it-frontmatter'))
|
||||||
|
|
||||||
|
this.md.use(
|
||||||
|
require('./markdown-it-fence'),
|
||||||
|
{
|
||||||
|
chart: token => {
|
||||||
|
if (token.parameters.hasOwnProperty('yaml')) {
|
||||||
|
token.parameters.format = 'yaml'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
|
<span class="filename">${token.fileName}</span>
|
||||||
|
<div class="chart" data-height="${
|
||||||
|
token.parameters.height
|
||||||
|
}" data-format="${token.parameters.format || 'json'}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
|
</pre>`
|
||||||
|
},
|
||||||
|
flowchart: token => {
|
||||||
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
|
<span class="filename">${token.fileName}</span>
|
||||||
|
<div class="flowchart" data-height="${token.parameters.height}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
|
</pre>`
|
||||||
|
},
|
||||||
|
gallery: token => {
|
||||||
|
const content = token.content
|
||||||
|
.split('\n')
|
||||||
|
.slice(0, -1)
|
||||||
|
.map(line => {
|
||||||
|
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
|
||||||
|
if (match) {
|
||||||
|
return mdurl.encode(match[1])
|
||||||
|
} else {
|
||||||
|
return mdurl.encode(line)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
|
<span class="filename">${token.fileName}</span>
|
||||||
|
<div class="gallery" data-autoplay="${
|
||||||
|
token.parameters.autoplay
|
||||||
|
}" data-height="${token.parameters.height}">${content}</div>
|
||||||
|
</pre>`
|
||||||
|
},
|
||||||
|
mermaid: token => {
|
||||||
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
|
<span class="filename">${token.fileName}</span>
|
||||||
|
<div class="mermaid" data-height="${token.parameters.height}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
|
</pre>`
|
||||||
|
},
|
||||||
|
sequence: token => {
|
||||||
|
return `<pre class="fence" data-line="${token.map[0]}">
|
||||||
|
<span class="filename">${token.fileName}</span>
|
||||||
|
<div class="sequence" data-height="${token.parameters.height}">${
|
||||||
|
token.content
|
||||||
|
}</div>
|
||||||
|
</pre>`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
token => {
|
||||||
|
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
|
||||||
|
<span class="filename">${token.fileName}</span>
|
||||||
|
${createGutter(token.content, token.firstLineNumber)}
|
||||||
|
<code class="${token.langType}">${token.content}</code>
|
||||||
|
</pre>`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const deflate = require('markdown-it-plantuml/lib/deflate')
|
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||||
this.md.use(require('markdown-it-plantuml'), '', {
|
const plantuml = require('markdown-it-plantuml')
|
||||||
generateSource: function (umlCode) {
|
const plantUmlStripTrailingSlash = url =>
|
||||||
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
|
url.endsWith('/') ? url.slice(0, -1) : url
|
||||||
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
|
const plantUmlServerAddress = plantUmlStripTrailingSlash(
|
||||||
const s = unescape(encodeURIComponent(umlCode))
|
config.preview.plantUMLServerAddress
|
||||||
const zippedCode = deflate.encode64(
|
)
|
||||||
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
|
const parsePlantUml = function(umlCode, openMarker, closeMarker, type) {
|
||||||
)
|
const s = unescape(encodeURIComponent(umlCode))
|
||||||
return `${serverAddress}/${zippedCode}`
|
const zippedCode = deflate.encode64(
|
||||||
}
|
deflate.zip_deflate(`${openMarker}\n${s}\n${closeMarker}`, 9)
|
||||||
|
)
|
||||||
|
return `${plantUmlServerAddress}/${type}/${zippedCode}`
|
||||||
|
}
|
||||||
|
|
||||||
|
this.md.use(plantuml, {
|
||||||
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startuml', '@enduml', 'svg')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Ditaa support. PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
|
||||||
|
this.md.use(plantuml, {
|
||||||
|
openMarker: '@startditaa',
|
||||||
|
closeMarker: '@endditaa',
|
||||||
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mindmap support
|
||||||
|
this.md.use(plantuml, {
|
||||||
|
openMarker: '@startmindmap',
|
||||||
|
closeMarker: '@endmindmap',
|
||||||
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
|
||||||
|
})
|
||||||
|
|
||||||
|
// WBS support
|
||||||
|
this.md.use(plantuml, {
|
||||||
|
openMarker: '@startwbs',
|
||||||
|
closeMarker: '@endwbs',
|
||||||
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startwbs', '@endwbs', 'svg')
|
||||||
|
})
|
||||||
|
|
||||||
|
// Gantt support
|
||||||
|
this.md.use(plantuml, {
|
||||||
|
openMarker: '@startgantt',
|
||||||
|
closeMarker: '@endgantt',
|
||||||
|
generateSource: umlCode =>
|
||||||
|
parsePlantUml(umlCode, '@startgantt', '@endgantt', 'svg')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Override task item
|
// Override task item
|
||||||
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
this.md.block.ruler.at('paragraph', function(
|
||||||
|
state,
|
||||||
|
startLine /*, endLine */
|
||||||
|
) {
|
||||||
let content, terminate, i, l, token
|
let content, terminate, i, l, token
|
||||||
let nextLine = startLine + 1
|
let nextLine = startLine + 1
|
||||||
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||||
@@ -168,10 +396,14 @@ class Markdown {
|
|||||||
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
||||||
// this would be a code block normally, but after paragraph
|
// this would be a code block normally, but after paragraph
|
||||||
// it's considered a lazy continuation regardless of what's there
|
// it's considered a lazy continuation regardless of what's there
|
||||||
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
|
if (state.sCount[nextLine] - state.blkIndent > 3) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// quirk for blockquotes, this line should already be checked by that rule
|
// quirk for blockquotes, this line should already be checked by that rule
|
||||||
if (state.sCount[nextLine] < 0) { continue }
|
if (state.sCount[nextLine] < 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Some tags can terminate paragraph without empty line.
|
// Some tags can terminate paragraph without empty line.
|
||||||
terminate = false
|
terminate = false
|
||||||
@@ -181,10 +413,14 @@ class Markdown {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (terminate) { break }
|
if (terminate) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
|
content = state
|
||||||
|
.getLines(startLine, nextLine, state.blkIndent, false)
|
||||||
|
.trim()
|
||||||
|
|
||||||
state.line = nextLine
|
state.line = nextLine
|
||||||
|
|
||||||
@@ -194,14 +430,31 @@ class Markdown {
|
|||||||
if (state.parentType === 'list') {
|
if (state.parentType === 'list') {
|
||||||
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
||||||
if (match) {
|
if (match) {
|
||||||
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
|
const liToken = lastFindInArray(
|
||||||
|
state.tokens,
|
||||||
|
token => token.type === 'list_item_open'
|
||||||
|
)
|
||||||
if (liToken) {
|
if (liToken) {
|
||||||
if (!liToken.attrs) {
|
if (!liToken.attrs) {
|
||||||
liToken.attrs = []
|
liToken.attrs = []
|
||||||
}
|
}
|
||||||
liToken.attrs.push(['class', 'taskListItem'])
|
if (config.preview.lineThroughCheckbox) {
|
||||||
|
liToken.attrs.push([
|
||||||
|
'class',
|
||||||
|
`taskListItem${match[1] !== ' ' ? ' checked' : ''}`
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
liToken.attrs.push(['class', 'taskListItem'])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
|
content = `<label class='taskListItem${
|
||||||
|
match[1] !== ' ' ? ' checked' : ''
|
||||||
|
}' for='checkbox-${startLine + 1}'><input type='checkbox'${
|
||||||
|
match[1] !== ' ' ? ' checked' : ''
|
||||||
|
} id='checkbox-${startLine + 1}'/> ${content.substring(
|
||||||
|
4,
|
||||||
|
content.length
|
||||||
|
)}</label>`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,13 +475,18 @@ class Markdown {
|
|||||||
// Add line number attribute for scrolling
|
// Add line number attribute for scrolling
|
||||||
const originalRender = this.md.renderer.render
|
const originalRender = this.md.renderer.render
|
||||||
this.md.renderer.render = (tokens, options, env) => {
|
this.md.renderer.render = (tokens, options, env) => {
|
||||||
tokens.forEach((token) => {
|
tokens.forEach(token => {
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
case 'heading_open':
|
|
||||||
case 'paragraph_open':
|
|
||||||
case 'blockquote_open':
|
case 'blockquote_open':
|
||||||
|
case 'dd_open':
|
||||||
|
case 'dt_open':
|
||||||
|
case 'heading_open':
|
||||||
|
case 'list_item_open':
|
||||||
|
case 'paragraph_open':
|
||||||
case 'table_open':
|
case 'table_open':
|
||||||
token.attrPush(['data-line', token.map[0]])
|
if (token.map) {
|
||||||
|
token.attrPush(['data-line', token.map[0]])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const result = originalRender.call(this.md.renderer, tokens, options, env)
|
const result = originalRender.call(this.md.renderer, tokens, options, env)
|
||||||
@@ -238,11 +496,10 @@ class Markdown {
|
|||||||
window.md = this.md
|
window.md = this.md
|
||||||
}
|
}
|
||||||
|
|
||||||
render (content) {
|
render(content) {
|
||||||
if (!_.isString(content)) content = ''
|
if (!_.isString(content)) content = ''
|
||||||
return this.md.render(content)
|
return this.md.render(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Markdown
|
export default Markdown
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
* @param {string} input
|
* @param {string} input
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
export function strip (input) {
|
export function strip(input) {
|
||||||
let output = input
|
let output = input
|
||||||
try {
|
try {
|
||||||
output = output
|
output = output
|
||||||
@@ -22,7 +22,7 @@ export function strip (input) {
|
|||||||
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
||||||
.replace(/>/g, '')
|
.replace(/>/g, '')
|
||||||
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
||||||
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
|
.replace(/^#{1,6}\s*/gm, '')
|
||||||
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
||||||
.replace(/^-{3,}\s*$/g, '')
|
.replace(/^-{3,}\s*$/g, '')
|
||||||
.replace(/`(.+?)`/g, '$1')
|
.replace(/`(.+?)`/g, '$1')
|
||||||
|
|||||||
108
browser/lib/newNote.js
Normal file
108
browser/lib/newNote.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
import queryString from 'query-string'
|
||||||
|
import { push } from 'connected-react-router'
|
||||||
|
|
||||||
|
export function createMarkdownNote(
|
||||||
|
storage,
|
||||||
|
folder,
|
||||||
|
dispatch,
|
||||||
|
location,
|
||||||
|
params,
|
||||||
|
config
|
||||||
|
) {
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||||
|
|
||||||
|
let tags = []
|
||||||
|
if (
|
||||||
|
config.ui.tagNewNoteWithFilteringTags &&
|
||||||
|
location.pathname.match(/\/tags/)
|
||||||
|
) {
|
||||||
|
tags = params.tagname.split(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataApi
|
||||||
|
.createNote(storage, {
|
||||||
|
type: 'MARKDOWN_NOTE',
|
||||||
|
folder: folder,
|
||||||
|
title: '',
|
||||||
|
tags,
|
||||||
|
content: '',
|
||||||
|
linesHighlighted: []
|
||||||
|
})
|
||||||
|
.then(note => {
|
||||||
|
const noteHash = note.key
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
push({
|
||||||
|
pathname: location.pathname,
|
||||||
|
search: queryString.stringify({ key: noteHash })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
ee.emit('list:jump', noteHash)
|
||||||
|
ee.emit('detail:focus')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSnippetNote(
|
||||||
|
storage,
|
||||||
|
folder,
|
||||||
|
dispatch,
|
||||||
|
location,
|
||||||
|
params,
|
||||||
|
config
|
||||||
|
) {
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
|
||||||
|
|
||||||
|
let tags = []
|
||||||
|
if (
|
||||||
|
config.ui.tagNewNoteWithFilteringTags &&
|
||||||
|
location.pathname.match(/\/tags/)
|
||||||
|
) {
|
||||||
|
tags = params.tagname.split(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultLanguage =
|
||||||
|
config.editor.snippetDefaultLanguage === 'Auto Detect'
|
||||||
|
? null
|
||||||
|
: config.editor.snippetDefaultLanguage
|
||||||
|
|
||||||
|
return dataApi
|
||||||
|
.createNote(storage, {
|
||||||
|
type: 'SNIPPET_NOTE',
|
||||||
|
folder: folder,
|
||||||
|
title: '',
|
||||||
|
tags,
|
||||||
|
description: '',
|
||||||
|
snippets: [
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
mode: defaultLanguage,
|
||||||
|
content: '',
|
||||||
|
linesHighlighted: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.then(note => {
|
||||||
|
const noteHash = note.key
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
dispatch(
|
||||||
|
push({
|
||||||
|
pathname: location.pathname,
|
||||||
|
search: queryString.stringify({ key: noteHash })
|
||||||
|
})
|
||||||
|
)
|
||||||
|
ee.emit('list:jump', noteHash)
|
||||||
|
ee.emit('detail:focus')
|
||||||
|
})
|
||||||
|
}
|
||||||
9
browser/lib/normalizeEditorFontFamily.js
Normal file
9
browser/lib/normalizeEditorFontFamily.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import isString from 'lodash/isString'
|
||||||
|
|
||||||
|
export default function normalizeEditorFontFamily(fontFamily) {
|
||||||
|
const defaultEditorFontFamily = consts.DEFAULT_EDITOR_FONT_FAMILY
|
||||||
|
return isString(fontFamily) && fontFamily.length > 0
|
||||||
|
? [fontFamily].concat(defaultEditorFontFamily).join(', ')
|
||||||
|
: defaultEditorFontFamily.join(', ')
|
||||||
|
}
|
||||||
@@ -1,29 +1,38 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
export default function searchFromNotes (notes, search) {
|
export default function searchFromNotes(notes, search) {
|
||||||
if (search.trim().length === 0) return []
|
if (search.trim().length === 0) return []
|
||||||
const searchBlocks = search.split(' ').filter(block => { return block !== '' })
|
const searchBlocks = search.split(' ').filter(block => {
|
||||||
|
return block !== ''
|
||||||
|
})
|
||||||
|
|
||||||
let foundNotes = notes
|
let foundNotes = notes
|
||||||
searchBlocks.forEach((block) => {
|
searchBlocks.forEach(block => {
|
||||||
foundNotes = findByWordOrTag(foundNotes, block)
|
foundNotes = findByWordOrTag(foundNotes, block)
|
||||||
})
|
})
|
||||||
return foundNotes
|
return foundNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
function findByWordOrTag (notes, block) {
|
function findByWordOrTag(notes, block) {
|
||||||
let tag = block
|
let tag = block
|
||||||
if (tag.match(/^#.+/)) {
|
if (tag.match(/^#.+/)) {
|
||||||
tag = tag.match(/#(.+)/)[1]
|
tag = tag.match(/#(.+)/)[1]
|
||||||
}
|
}
|
||||||
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
|
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
|
||||||
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
|
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
|
||||||
return notes.filter((note) => {
|
return notes.filter(note => {
|
||||||
if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) {
|
if (_.isArray(note.tags) && note.tags.some(_tag => _tag.match(tagRegExp))) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (note.type === 'SNIPPET_NOTE') {
|
if (note.type === 'SNIPPET_NOTE') {
|
||||||
return note.description.match(wordRegExp)
|
return (
|
||||||
|
note.description.match(wordRegExp) ||
|
||||||
|
note.snippets.some(snippet => {
|
||||||
|
return (
|
||||||
|
snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
)
|
||||||
} else if (note.type === 'MARKDOWN_NOTE') {
|
} else if (note.type === 'MARKDOWN_NOTE') {
|
||||||
return note.content.match(wordRegExp)
|
return note.content.match(wordRegExp)
|
||||||
}
|
}
|
||||||
|
|||||||
15
browser/lib/slugify.js
Normal file
15
browser/lib/slugify.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
module.exports = function slugify(title) {
|
||||||
|
const slug = encodeURI(
|
||||||
|
title
|
||||||
|
.trim()
|
||||||
|
.replace(/^\s+/, '')
|
||||||
|
.replace(/\s+$/, '')
|
||||||
|
.replace(/\s+/g, '-')
|
||||||
|
.replace(
|
||||||
|
/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g,
|
||||||
|
''
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return slug
|
||||||
|
}
|
||||||
255
browser/lib/spellcheck.js
Normal file
255
browser/lib/spellcheck.js
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
import styles from '../components/CodeEditor.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const Typo = require('typo-js')
|
||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
|
const CSS_ERROR_CLASS = 'codeEditor-typo'
|
||||||
|
const SPELLCHECK_DISABLED = 'NONE'
|
||||||
|
const DICTIONARY_PATH = '../dictionaries'
|
||||||
|
const MILLISECONDS_TILL_LIVECHECK = 500
|
||||||
|
|
||||||
|
let dictionary = null
|
||||||
|
let self
|
||||||
|
|
||||||
|
function getAvailableDictionaries() {
|
||||||
|
return [
|
||||||
|
{ label: i18n.__('Spellcheck disabled'), value: SPELLCHECK_DISABLED },
|
||||||
|
{ label: i18n.__('English'), value: 'en_GB' },
|
||||||
|
{ label: i18n.__('German'), value: 'de_DE' },
|
||||||
|
{ label: i18n.__('French'), value: 'fr_FR' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only to be used in the tests :)
|
||||||
|
*/
|
||||||
|
function setDictionaryForTestsOnly(newDictionary) {
|
||||||
|
dictionary = newDictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Initializes the spellcheck. It removes all existing marks of the current editor.
|
||||||
|
* If a language was given (i.e. lang !== this.SPELLCHECK_DISABLED) it will load the stated dictionary and use it to check the whole document.
|
||||||
|
* @param {Codemirror} editor CodeMirror-Editor
|
||||||
|
* @param {String} lang on of the values from getAvailableDictionaries()-Method
|
||||||
|
*/
|
||||||
|
function setLanguage(editor, lang) {
|
||||||
|
self = this
|
||||||
|
dictionary = null
|
||||||
|
|
||||||
|
if (editor == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingMarks = editor.getAllMarks() || []
|
||||||
|
for (const mark of existingMarks) {
|
||||||
|
mark.clear()
|
||||||
|
}
|
||||||
|
if (lang !== SPELLCHECK_DISABLED) {
|
||||||
|
dictionary = new Typo(lang, false, false, {
|
||||||
|
dictionaryPath: DICTIONARY_PATH,
|
||||||
|
asyncLoad: true,
|
||||||
|
loadedCallback: () => checkWholeDocument(editor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the whole content of the editor for typos
|
||||||
|
* @param {Codemirror} editor CodeMirror-Editor
|
||||||
|
*/
|
||||||
|
function checkWholeDocument(editor) {
|
||||||
|
const lastLine = editor.lineCount() - 1
|
||||||
|
const textOfLastLine = editor.getLine(lastLine) || ''
|
||||||
|
const lastChar = textOfLastLine.length
|
||||||
|
const from = { line: 0, ch: 0 }
|
||||||
|
const to = { line: lastLine, ch: lastChar }
|
||||||
|
checkMultiLineRange(editor, from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the given range for typos
|
||||||
|
* @param {Codemirror} editor CodeMirror-Editor
|
||||||
|
* @param {line, ch} from starting position of the spellcheck
|
||||||
|
* @param {line, ch} to end position of the spellcheck
|
||||||
|
*/
|
||||||
|
function checkMultiLineRange(editor, from, to) {
|
||||||
|
function sortRange(pos1, pos2) {
|
||||||
|
if (
|
||||||
|
pos1.line > pos2.line ||
|
||||||
|
(pos1.line === pos2.line && pos1.ch > pos2.ch)
|
||||||
|
) {
|
||||||
|
return { from: pos2, to: pos1 }
|
||||||
|
}
|
||||||
|
return { from: pos1, to: pos2 }
|
||||||
|
}
|
||||||
|
|
||||||
|
const { from: smallerPos, to: higherPos } = sortRange(from, to)
|
||||||
|
for (let l = smallerPos.line; l <= higherPos.line; l++) {
|
||||||
|
const line = editor.getLine(l) || ''
|
||||||
|
let w = 0
|
||||||
|
if (l === smallerPos.line) {
|
||||||
|
w = smallerPos.ch
|
||||||
|
}
|
||||||
|
let wEnd = line.length
|
||||||
|
if (l === higherPos.line) {
|
||||||
|
wEnd = higherPos.ch
|
||||||
|
}
|
||||||
|
while (w <= wEnd) {
|
||||||
|
const wordRange = editor.findWordAt({ line: l, ch: w })
|
||||||
|
self.checkWord(editor, wordRange)
|
||||||
|
w += wordRange.head.ch - wordRange.anchor.ch + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Checks whether a certain range of characters in the editor (i.e. a word) contains a typo.
|
||||||
|
* If so the ranged will be marked with the class CSS_ERROR_CLASS.
|
||||||
|
* Note: Due to performance considerations, only words with more then 3 signs are checked.
|
||||||
|
* @param {Codemirror} editor CodeMirror-Editor
|
||||||
|
* @param wordRange Object specifying the range that should be checked.
|
||||||
|
* Having the following structure: <code>{anchor: {line: integer, ch: integer}, head: {line: integer, ch: integer}}</code>
|
||||||
|
*/
|
||||||
|
function checkWord(editor, wordRange) {
|
||||||
|
const word = editor.getRange(wordRange.anchor, wordRange.head)
|
||||||
|
if (word == null || word.length <= 3) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!dictionary.check(word)) {
|
||||||
|
editor.markText(wordRange.anchor, wordRange.head, {
|
||||||
|
className: styles[CSS_ERROR_CLASS]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the changes recently made (aka live check)
|
||||||
|
* @param {Codemirror} editor CodeMirror-Editor
|
||||||
|
* @param fromChangeObject codeMirror changeObject describing the start of the editing
|
||||||
|
* @param toChangeObject codeMirror changeObject describing the end of the editing
|
||||||
|
*/
|
||||||
|
function checkChangeRange(editor, fromChangeObject, toChangeObject) {
|
||||||
|
/**
|
||||||
|
* Calculate the smallest respectively largest position as a start, resp. end, position and return it
|
||||||
|
* @param start CodeMirror change object
|
||||||
|
* @param end CodeMirror change object
|
||||||
|
* @returns {{start: {line: *, ch: *}, end: {line: *, ch: *}}}
|
||||||
|
*/
|
||||||
|
function getStartAndEnd(start, end) {
|
||||||
|
const possiblePositions = [start.from, start.to, end.from, end.to]
|
||||||
|
let smallest = start.from
|
||||||
|
let biggest = end.to
|
||||||
|
for (const currentPos of possiblePositions) {
|
||||||
|
if (
|
||||||
|
currentPos.line < smallest.line ||
|
||||||
|
(currentPos.line === smallest.line && currentPos.ch < smallest.ch)
|
||||||
|
) {
|
||||||
|
smallest = currentPos
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
currentPos.line > biggest.line ||
|
||||||
|
(currentPos.line === biggest.line && currentPos.ch > biggest.ch)
|
||||||
|
) {
|
||||||
|
biggest = currentPos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { start: smallest, end: biggest }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dictionary === null || editor == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { start, end } = getStartAndEnd(fromChangeObject, toChangeObject)
|
||||||
|
|
||||||
|
// Expand the range to include words after/before whitespaces
|
||||||
|
start.ch = Math.max(start.ch - 1, 0)
|
||||||
|
end.ch = end.ch + 1
|
||||||
|
|
||||||
|
// clean existing marks
|
||||||
|
const existingMarks = editor.findMarks(start, end) || []
|
||||||
|
for (const mark of existingMarks) {
|
||||||
|
mark.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.checkMultiLineRange(editor, start, end)
|
||||||
|
} catch (e) {
|
||||||
|
console.info(
|
||||||
|
'Error during the spell check. It might be due to problems figuring out the range of the new text..',
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveLiveSpellCheckFrom(changeObject) {
|
||||||
|
liveSpellCheckFrom = changeObject
|
||||||
|
}
|
||||||
|
let liveSpellCheckFrom
|
||||||
|
const debouncedSpellCheckLeading = _.debounce(
|
||||||
|
saveLiveSpellCheckFrom,
|
||||||
|
MILLISECONDS_TILL_LIVECHECK,
|
||||||
|
{
|
||||||
|
leading: true,
|
||||||
|
trailing: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const debouncedSpellCheck = _.debounce(
|
||||||
|
checkChangeRange,
|
||||||
|
MILLISECONDS_TILL_LIVECHECK,
|
||||||
|
{
|
||||||
|
leading: false,
|
||||||
|
trailing: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a keystroke. Buffers the input and performs a live spell check after a certain time. Uses _debounce from lodash to buffer the input
|
||||||
|
* @param {Codemirror} editor CodeMirror-Editor
|
||||||
|
* @param changeObject codeMirror changeObject
|
||||||
|
*/
|
||||||
|
function handleChange(editor, changeObject) {
|
||||||
|
if (dictionary === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
debouncedSpellCheckLeading(changeObject)
|
||||||
|
debouncedSpellCheck(editor, liveSpellCheckFrom, changeObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of spelling suggestions for the given (wrong written) word.
|
||||||
|
* Returns an empty array if the dictionary is null (=> spellcheck is disabled) or the given word was null
|
||||||
|
* @param word word to be checked
|
||||||
|
* @returns {String[]} Array of suggestions
|
||||||
|
*/
|
||||||
|
function getSpellingSuggestion(word) {
|
||||||
|
if (dictionary == null || word == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return dictionary.suggest(word)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the CSS class used for errors
|
||||||
|
*/
|
||||||
|
function getCSSClassName() {
|
||||||
|
return styles[CSS_ERROR_CLASS]
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
DICTIONARY_PATH,
|
||||||
|
CSS_ERROR_CLASS,
|
||||||
|
SPELLCHECK_DISABLED,
|
||||||
|
getAvailableDictionaries,
|
||||||
|
setLanguage,
|
||||||
|
checkChangeRange,
|
||||||
|
handleChange,
|
||||||
|
getSpellingSuggestion,
|
||||||
|
checkWord,
|
||||||
|
checkMultiLineRange,
|
||||||
|
checkWholeDocument,
|
||||||
|
setDictionaryForTestsOnly,
|
||||||
|
getCSSClassName
|
||||||
|
}
|
||||||
9
browser/lib/turndown.js
Normal file
9
browser/lib/turndown.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const TurndownService = require('turndown')
|
||||||
|
const { gfm } = require('turndown-plugin-gfm')
|
||||||
|
|
||||||
|
export const createTurndownService = function() {
|
||||||
|
const turndown = new TurndownService()
|
||||||
|
turndown.use(gfm)
|
||||||
|
turndown.remove('script')
|
||||||
|
return turndown
|
||||||
|
}
|
||||||
44
browser/lib/ui-themes.js
Normal file
44
browser/lib/ui-themes.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
name: 'dark',
|
||||||
|
label: i18n.__('Dark'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'default',
|
||||||
|
label: i18n.__('Default'),
|
||||||
|
isDark: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dracula',
|
||||||
|
label: i18n.__('Dracula'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'monokai',
|
||||||
|
label: i18n.__('Monokai'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'nord',
|
||||||
|
label: i18n.__('Nord'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'solarized-dark',
|
||||||
|
label: i18n.__('Solarized Dark'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'vulcan',
|
||||||
|
label: i18n.__('Vulcan'),
|
||||||
|
isDark: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'white',
|
||||||
|
label: i18n.__('White'),
|
||||||
|
isDark: false
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export function lastFindInArray (array, callback) {
|
export function lastFindInArray(array, callback) {
|
||||||
for (let i = array.length - 1; i >= 0; --i) {
|
for (let i = array.length - 1; i >= 0; --i) {
|
||||||
if (callback(array[i], i, array)) {
|
if (callback(array[i], i, array)) {
|
||||||
return array[i]
|
return array[i]
|
||||||
@@ -6,55 +6,116 @@ export function lastFindInArray (array, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function escapeHtmlCharacters (text) {
|
export function escapeHtmlCharacters(
|
||||||
const matchHtmlRegExp = /["'&<>]/
|
html,
|
||||||
const str = '' + text
|
opt = { detectCodeBlock: false, skipSingleQuote: false }
|
||||||
const match = matchHtmlRegExp.exec(str)
|
) {
|
||||||
|
const matchHtmlRegExp = /["'&<>]/g
|
||||||
|
const matchCodeBlockRegExp = /```/g
|
||||||
|
const escapes = ['"', '&', ''', '<', '>']
|
||||||
|
let match = null
|
||||||
|
const replaceAt = (str, index, replace) =>
|
||||||
|
str.substr(0, index) +
|
||||||
|
replace +
|
||||||
|
str.substr(index + replace.length - (replace.length - 1))
|
||||||
|
|
||||||
if (!match) {
|
while ((match = matchHtmlRegExp.exec(html)) !== null) {
|
||||||
return str
|
const current = { char: match[0], index: match.index }
|
||||||
}
|
const codeBlockIndexs = []
|
||||||
|
let openCodeBlock = null
|
||||||
let escape
|
// if the detectCodeBlock option is activated then this function should skip
|
||||||
let html = ''
|
// characters that needed to be escape but located in code block
|
||||||
let index = 0
|
if (opt.detectCodeBlock) {
|
||||||
let lastIndex = 0
|
// The first type of code block is lines that start with 4 spaces
|
||||||
|
// Here we check for the \n character located before the character that
|
||||||
for (index = match.index; index < str.length; index++) {
|
// needed to be escape. It means we check for the begining of the line that
|
||||||
switch (str.charCodeAt(index)) {
|
// contain that character, then we check if there are 4 spaces next to the
|
||||||
case 34: // "
|
// \n character (the line start with 4 spaces)
|
||||||
escape = '"'
|
let previousLineEnd = current.index - 1
|
||||||
break
|
while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) {
|
||||||
case 38: // &
|
previousLineEnd--
|
||||||
escape = '&'
|
}
|
||||||
break
|
// 4 spaces means this character is in a code block
|
||||||
case 39: // '
|
if (
|
||||||
escape = '''
|
html[previousLineEnd + 1] === ' ' &&
|
||||||
break
|
html[previousLineEnd + 2] === ' ' &&
|
||||||
case 60: // <
|
html[previousLineEnd + 3] === ' ' &&
|
||||||
escape = '<'
|
html[previousLineEnd + 4] === ' '
|
||||||
break
|
) {
|
||||||
case 62: // >
|
// skip the current character
|
||||||
escape = '>'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
|
// The second type of code block is lines that wrapped in ```
|
||||||
|
// We will get the position of each ```
|
||||||
|
// then push it into an array
|
||||||
|
// then the array returned will be like this:
|
||||||
|
// [startCodeblock, endCodeBlock, startCodeBlock, endCodeBlock]
|
||||||
|
while ((openCodeBlock = matchCodeBlockRegExp.exec(html)) !== null) {
|
||||||
|
codeBlockIndexs.push(openCodeBlock.index)
|
||||||
|
}
|
||||||
|
let shouldSkipChar = false
|
||||||
|
// we loop through the array of positions
|
||||||
|
// we skip 2 element as the i index position is the position of ``` that
|
||||||
|
// open the codeblock and the i + 1 is the position of the ``` that close
|
||||||
|
// the code block
|
||||||
|
for (let i = 0; i < codeBlockIndexs.length; i += 2) {
|
||||||
|
// the i index position is the position of the ``` that open code block
|
||||||
|
// so we have to + 2 as that position is the position of the first ` in the ````
|
||||||
|
// but we need to make sure that the position current character is larger
|
||||||
|
// that the last ` in the ``` that open the code block so we have to take
|
||||||
|
// the position of the first ` and + 2
|
||||||
|
// the i + 1 index position is the closing ``` so the char must less than it
|
||||||
|
if (
|
||||||
|
current.index > codeBlockIndexs[i] + 2 &&
|
||||||
|
current.index < codeBlockIndexs[i + 1]
|
||||||
|
) {
|
||||||
|
// skip it
|
||||||
|
shouldSkipChar = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldSkipChar) {
|
||||||
|
// skip the current character
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// otherwise, escape it !!!
|
||||||
if (lastIndex !== index) {
|
if (current.char === '&') {
|
||||||
html += str.substring(lastIndex, index)
|
// when escaping character & we have to be becareful as the & could be a part
|
||||||
|
// of an escaped character like " will be came &quot;
|
||||||
|
let nextStr = ''
|
||||||
|
let nextIndex = current.index
|
||||||
|
let escapedStr = false
|
||||||
|
// maximum length of an escaped string is 5. For example ('"')
|
||||||
|
// we take the next 5 character of the next string if it is one of the string:
|
||||||
|
// ['"', '&', ''', '<', '>'] then we will not escape the & character
|
||||||
|
// as it is a part of the escaped string and should not be escaped
|
||||||
|
while (nextStr.length <= 5) {
|
||||||
|
nextStr += html[nextIndex]
|
||||||
|
nextIndex++
|
||||||
|
if (escapes.indexOf(nextStr) !== -1) {
|
||||||
|
escapedStr = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!escapedStr) {
|
||||||
|
// this & char is not a part of an escaped string
|
||||||
|
html = replaceAt(html, current.index, '&')
|
||||||
|
}
|
||||||
|
} else if (current.char === '"') {
|
||||||
|
html = replaceAt(html, current.index, '"')
|
||||||
|
} else if (current.char === "'" && !opt.skipSingleQuote) {
|
||||||
|
html = replaceAt(html, current.index, ''')
|
||||||
|
} else if (current.char === '<') {
|
||||||
|
html = replaceAt(html, current.index, '<')
|
||||||
|
} else if (current.char === '>') {
|
||||||
|
html = replaceAt(html, current.index, '>')
|
||||||
}
|
}
|
||||||
|
|
||||||
lastIndex = index + 1
|
|
||||||
html += escape
|
|
||||||
}
|
}
|
||||||
|
return html
|
||||||
return lastIndex !== index
|
|
||||||
? html + str.substring(lastIndex, index)
|
|
||||||
: html
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isObjectEqual (a, b) {
|
export function isObjectEqual(a, b) {
|
||||||
const aProps = Object.getOwnPropertyNames(a)
|
const aProps = Object.getOwnPropertyNames(a)
|
||||||
const bProps = Object.getOwnPropertyNames(b)
|
const bProps = Object.getOwnPropertyNames(b)
|
||||||
|
|
||||||
@@ -71,8 +132,30 @@ export function isObjectEqual (a, b) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isMarkdownTitleURL(str) {
|
||||||
|
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(
|
||||||
|
str
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function humanFileSize(bytes) {
|
||||||
|
const threshold = 1000
|
||||||
|
if (Math.abs(bytes) < threshold) {
|
||||||
|
return bytes + ' B'
|
||||||
|
}
|
||||||
|
var units = ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
|
var u = -1
|
||||||
|
do {
|
||||||
|
bytes /= threshold
|
||||||
|
++u
|
||||||
|
} while (Math.abs(bytes) >= threshold && u < units.length - 1)
|
||||||
|
return bytes.toFixed(1) + ' ' + units[u]
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
lastFindInArray,
|
lastFindInArray,
|
||||||
escapeHtmlCharacters,
|
escapeHtmlCharacters,
|
||||||
isObjectEqual
|
isObjectEqual,
|
||||||
|
isMarkdownTitleURL,
|
||||||
|
humanFileSize
|
||||||
}
|
}
|
||||||
|
|||||||
49
browser/lib/wakatime-plugin.js
Normal file
49
browser/lib/wakatime-plugin.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import config from 'browser/main/lib/ConfigManager'
|
||||||
|
const exec = require('child_process').exec
|
||||||
|
const path = require('path')
|
||||||
|
let lastHeartbeat = 0
|
||||||
|
|
||||||
|
function sendWakatimeHeartBeat(
|
||||||
|
storagePath,
|
||||||
|
noteKey,
|
||||||
|
storageName,
|
||||||
|
{ isWrite, hasFileChanges, isFileChange }
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
config.get().wakatime.isActive &&
|
||||||
|
!!config.get().wakatime.key &&
|
||||||
|
(new Date().getTime() - lastHeartbeat > 120000 || isFileChange)
|
||||||
|
) {
|
||||||
|
const notePath = path.join(storagePath, 'notes', noteKey + '.cson')
|
||||||
|
|
||||||
|
if (!isWrite && !hasFileChanges && !isFileChange) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastHeartbeat = new Date()
|
||||||
|
const wakatimeKey = config.get().wakatime.key
|
||||||
|
if (wakatimeKey) {
|
||||||
|
exec(
|
||||||
|
`wakatime --file ${notePath} --project '${storageName}' --key ${wakatimeKey} --plugin Boostnote-wakatime`,
|
||||||
|
(error, stdOut, stdErr) => {
|
||||||
|
if (error) {
|
||||||
|
console.log(error)
|
||||||
|
lastHeartbeat = 0
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
'wakatime',
|
||||||
|
'isWrite',
|
||||||
|
isWrite,
|
||||||
|
'hasChanges',
|
||||||
|
hasFileChanges,
|
||||||
|
'isFileChange',
|
||||||
|
isFileChange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { sendWakatimeHeartBeat }
|
||||||
@@ -23,17 +23,17 @@ body[data-theme="dark"]
|
|||||||
border-left 1px solid $ui-dark-borderColor
|
border-left 1px solid $ui-dark-borderColor
|
||||||
.empty-message
|
.empty-message
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
|
||||||
.root
|
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
|
||||||
border-left 1px solid $ui-solarized-dark-borderColor
|
|
||||||
.empty-message
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
apply-theme(theme)
|
||||||
.root
|
body[data-theme={theme}]
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
.root
|
||||||
border-left 1px solid $ui-monokai-borderColor
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
.empty-message
|
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||||
color $ui-monokai-text-color
|
.empty-message
|
||||||
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
|
for theme in 'solarized-dark' 'dracula'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
@@ -6,7 +6,7 @@ import _ from 'lodash'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class FolderSelect extends React.Component {
|
class FolderSelect extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -16,24 +16,27 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate() {
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClick (e) {
|
handleClick(e) {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'SEARCH',
|
{
|
||||||
optionIndex: -1
|
status: 'SEARCH',
|
||||||
}, () => {
|
optionIndex: -1
|
||||||
this.refs.search.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFocus (e) {
|
handleFocus(e) {
|
||||||
if (this.state.status === 'IDLE') {
|
if (this.state.status === 'IDLE') {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'FOCUS'
|
status: 'FOCUS'
|
||||||
@@ -41,7 +44,7 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBlur (e) {
|
handleBlur(e) {
|
||||||
if (this.state.status === 'FOCUS') {
|
if (this.state.status === 'FOCUS') {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'IDLE'
|
status: 'IDLE'
|
||||||
@@ -49,40 +52,49 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 13:
|
case 13:
|
||||||
if (this.state.status === 'FOCUS') {
|
if (this.state.status === 'FOCUS') {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'SEARCH',
|
{
|
||||||
optionIndex: -1
|
status: 'SEARCH',
|
||||||
}, () => {
|
optionIndex: -1
|
||||||
this.refs.search.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 40:
|
case 40:
|
||||||
case 38:
|
case 38:
|
||||||
if (this.state.status === 'FOCUS') {
|
if (this.state.status === 'FOCUS') {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'SEARCH',
|
{
|
||||||
optionIndex: 0
|
status: 'SEARCH',
|
||||||
}, () => {
|
optionIndex: 0
|
||||||
this.refs.search.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.search.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 9:
|
case 9:
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
const tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
|
const tabbable = document.querySelectorAll(
|
||||||
const previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'
|
||||||
|
)
|
||||||
|
const previousEl =
|
||||||
|
tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
||||||
if (previousEl != null) previousEl.focus()
|
if (previousEl != null) previousEl.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchInputBlur (e) {
|
handleSearchInputBlur(e) {
|
||||||
if (e.relatedTarget !== this.refs.root) {
|
if (e.relatedTarget !== this.refs.root) {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'IDLE'
|
status: 'IDLE'
|
||||||
@@ -90,14 +102,17 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchInputChange (e) {
|
handleSearchInputChange(e) {
|
||||||
const { folders } = this.props
|
const { folders } = this.props
|
||||||
const search = this.refs.search.value
|
const search = this.refs.search.value
|
||||||
const optionIndex = search.length > 0
|
const optionIndex =
|
||||||
? _.findIndex(folders, (folder) => {
|
search.length > 0
|
||||||
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
|
? _.findIndex(folders, folder => {
|
||||||
})
|
return folder.name.match(
|
||||||
: -1
|
new RegExp('^' + _.escapeRegExp(search), 'i')
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: -1
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
search: this.refs.search.value,
|
search: this.refs.search.value,
|
||||||
@@ -105,7 +120,7 @@ class FolderSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchInputKeyDown (e) {
|
handleSearchInputKeyDown(e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 40:
|
case 40:
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
@@ -121,15 +136,18 @@ class FolderSelect extends React.Component {
|
|||||||
break
|
break
|
||||||
case 27:
|
case 27:
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'FOCUS'
|
{
|
||||||
}, () => {
|
status: 'FOCUS'
|
||||||
this.refs.root.focus()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.refs.root.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextOption () {
|
nextOption() {
|
||||||
let { optionIndex } = this.state
|
let { optionIndex } = this.state
|
||||||
const { folders } = this.props
|
const { folders } = this.props
|
||||||
|
|
||||||
@@ -141,7 +159,7 @@ class FolderSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
previousOption () {
|
previousOption() {
|
||||||
const { folders } = this.props
|
const { folders } = this.props
|
||||||
let { optionIndex } = this.state
|
let { optionIndex } = this.state
|
||||||
|
|
||||||
@@ -153,46 +171,52 @@ class FolderSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
selectOption () {
|
selectOption() {
|
||||||
const { folders } = this.props
|
const { folders } = this.props
|
||||||
const optionIndex = this.state.optionIndex
|
const optionIndex = this.state.optionIndex
|
||||||
|
|
||||||
const folder = folders[optionIndex]
|
const folder = folders[optionIndex]
|
||||||
if (folder != null) {
|
if (folder != null) {
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'FOCUS'
|
{
|
||||||
}, () => {
|
status: 'FOCUS'
|
||||||
this.setValue(folder.key)
|
},
|
||||||
this.refs.root.focus()
|
() => {
|
||||||
})
|
this.setValue(folder.key)
|
||||||
|
this.refs.root.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOptionClick (storageKey, folderKey) {
|
handleOptionClick(storageKey, folderKey) {
|
||||||
return (e) => {
|
return e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
this.setState({
|
this.setState(
|
||||||
status: 'FOCUS'
|
{
|
||||||
}, () => {
|
status: 'FOCUS'
|
||||||
this.setValue(storageKey + '-' + folderKey)
|
},
|
||||||
this.refs.root.focus()
|
() => {
|
||||||
})
|
this.setValue(storageKey + '-' + folderKey)
|
||||||
|
this.refs.root.focus()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue (value) {
|
setValue(value) {
|
||||||
this.value = value
|
this.value = value
|
||||||
this.props.onChange()
|
this.props.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { className, data, value } = this.props
|
const { className, data, value } = this.props
|
||||||
const splitted = value.split('-')
|
const splitted = value.split('-')
|
||||||
const storageKey = splitted.shift()
|
const storageKey = splitted.shift()
|
||||||
const folderKey = splitted.shift()
|
const folderKey = splitted.shift()
|
||||||
let options = []
|
let options = []
|
||||||
data.storageMap.forEach((storage, index) => {
|
data.storageMap.forEach((storage, index) => {
|
||||||
storage.folders.forEach((folder) => {
|
storage.folders.forEach(folder => {
|
||||||
options.push({
|
options.push({
|
||||||
storage: storage,
|
storage: storage,
|
||||||
folder: folder
|
folder: folder
|
||||||
@@ -200,68 +224,78 @@ class FolderSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
const currentOption = options.filter(
|
||||||
|
option =>
|
||||||
|
option.storage.key === storageKey && option.folder.key === folderKey
|
||||||
|
)[0]
|
||||||
|
|
||||||
if (this.state.search.trim().length > 0) {
|
if (this.state.search.trim().length > 0) {
|
||||||
const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
||||||
options = options.filter((option) => filter.test(option.folder.name))
|
options = options.filter(option => filter.test(option.folder.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
const optionList = options
|
const optionList = options.map((option, index) => {
|
||||||
.map((option, index) => {
|
return (
|
||||||
return (
|
<div
|
||||||
<div styleName={index === this.state.optionIndex
|
styleName={
|
||||||
|
index === this.state.optionIndex
|
||||||
? 'search-optionList-item--active'
|
? 'search-optionList-item--active'
|
||||||
: 'search-optionList-item'
|
: 'search-optionList-item'
|
||||||
}
|
}
|
||||||
key={option.storage.key + '-' + option.folder.key}
|
key={option.storage.key + '-' + option.folder.key}
|
||||||
onClick={(e) => this.handleOptionClick(option.storage.key, option.folder.key)(e)}
|
onClick={e =>
|
||||||
|
this.handleOptionClick(option.storage.key, option.folder.key)(e)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
styleName='search-optionList-item-name'
|
||||||
|
style={{ borderColor: option.folder.color }}
|
||||||
>
|
>
|
||||||
<span styleName='search-optionList-item-name'
|
{option.folder.name}
|
||||||
style={{borderColor: option.folder.color}}
|
<span styleName='search-optionList-item-name-surfix'>
|
||||||
>
|
in {option.storage.name}
|
||||||
{option.folder.name}
|
|
||||||
<span styleName='search-optionList-item-name-surfix'>in {option.storage.name}</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</span>
|
||||||
)
|
</div>
|
||||||
})
|
)
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={_.isString(className)
|
<div
|
||||||
? 'FolderSelect ' + className
|
className={
|
||||||
: 'FolderSelect'
|
_.isString(className) ? 'FolderSelect ' + className : 'FolderSelect'
|
||||||
}
|
}
|
||||||
styleName={this.state.status === 'SEARCH'
|
styleName={
|
||||||
? 'root--search'
|
this.state.status === 'SEARCH'
|
||||||
: this.state.status === 'FOCUS'
|
? 'root--search'
|
||||||
? 'root--focus'
|
: this.state.status === 'FOCUS'
|
||||||
: 'root'
|
? 'root--focus'
|
||||||
|
: 'root'
|
||||||
}
|
}
|
||||||
ref='root'
|
ref='root'
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
onClick={(e) => this.handleClick(e)}
|
onClick={e => this.handleClick(e)}
|
||||||
onFocus={(e) => this.handleFocus(e)}
|
onFocus={e => this.handleFocus(e)}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
onBlur={e => this.handleBlur(e)}
|
||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
onKeyDown={e => this.handleKeyDown(e)}
|
||||||
>
|
>
|
||||||
{this.state.status === 'SEARCH'
|
{this.state.status === 'SEARCH' ? (
|
||||||
? <div styleName='search'>
|
<div styleName='search'>
|
||||||
<input styleName='search-input'
|
<input
|
||||||
|
styleName='search-input'
|
||||||
ref='search'
|
ref='search'
|
||||||
value={this.state.search}
|
value={this.state.search}
|
||||||
placeholder={i18n.__('Folder...')}
|
placeholder={i18n.__('Folder...')}
|
||||||
onChange={(e) => this.handleSearchInputChange(e)}
|
onChange={e => this.handleSearchInputChange(e)}
|
||||||
onBlur={(e) => this.handleSearchInputBlur(e)}
|
onBlur={e => this.handleSearchInputBlur(e)}
|
||||||
onKeyDown={(e) => this.handleSearchInputKeyDown(e)}
|
onKeyDown={e => this.handleSearchInputKeyDown(e)}
|
||||||
/>
|
/>
|
||||||
<div styleName='search-optionList'
|
<div styleName='search-optionList' ref='optionList'>
|
||||||
ref='optionList'
|
|
||||||
>
|
|
||||||
{optionList}
|
{optionList}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: <div styleName='idle' style={{color: currentOption.folder.color}}>
|
) : currentOption ? (
|
||||||
|
<div styleName='idle' style={{ color: currentOption.folder.color }}>
|
||||||
<div styleName='idle-label'>
|
<div styleName='idle-label'>
|
||||||
<i className='fa fa-folder' />
|
<i className='fa fa-folder' />
|
||||||
<span styleName='idle-label-name'>
|
<span styleName='idle-label-name'>
|
||||||
@@ -269,8 +303,7 @@ class FolderSelect extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
) : null}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -280,11 +313,13 @@ FolderSelect.propTypes = {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
onChange: PropTypes.func,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
folders: PropTypes.arrayOf(PropTypes.shape({
|
folders: PropTypes.arrayOf(
|
||||||
key: PropTypes.string,
|
PropTypes.shape({
|
||||||
name: PropTypes.string,
|
key: PropTypes.string,
|
||||||
color: PropTypes.string
|
name: PropTypes.string,
|
||||||
}))
|
color: PropTypes.string
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(FolderSelect, styles)
|
export default CSSModules(FolderSelect, styles)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
height 34px
|
height 34px
|
||||||
width 20px
|
width 20px
|
||||||
line-height 34px
|
line-height 34px
|
||||||
|
|
||||||
.search-input
|
.search-input
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
position relative
|
position relative
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
cursor pointer
|
cursor pointer
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-button--hover-backgroundColor
|
background-color $ui-button--hover-backgroundColor
|
||||||
|
|
||||||
.search-optionList-item--active
|
.search-optionList-item--active
|
||||||
@extend .search-optionList-item
|
@extend .search-optionList-item
|
||||||
@@ -134,28 +134,39 @@ body[data-theme="dark"]
|
|||||||
.search-optionList-item-name-surfix
|
.search-optionList-item-name-surfix
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
apply-theme(theme)
|
||||||
.root
|
body[data-theme={theme}]
|
||||||
color $ui-dark-text-color
|
.root
|
||||||
&:hover
|
&:hover
|
||||||
color white
|
background-color get-theme-var(theme, 'button--hover-backgroundColor')
|
||||||
background-color $ui-monokai-button--hover-backgroundColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
border-color $ui-monokai-borderColor
|
|
||||||
|
|
||||||
.search-optionList
|
.search-input
|
||||||
color white
|
color get-theme-var(theme, 'text-color')
|
||||||
border-color $ui-monokai-borderColor
|
background-color transparent
|
||||||
background-color $ui-monokai-button-backgroundColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
|
|
||||||
.search-optionList-item
|
.search-optionList
|
||||||
&:hover
|
color get-theme-var(theme, 'text-color')
|
||||||
background-color lighten($ui-monokai-button--hover-backgroundColor, 15%)
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
|
background-color get-theme-var(theme, 'button-backgroundColor')
|
||||||
|
|
||||||
.search-optionList-item--active
|
.search-optionList-item
|
||||||
background-color $ui-monokai-button--active-backgroundColor
|
&:hover
|
||||||
color $ui-monokai-button--active-color
|
background-color lighten(get-theme-var(theme, 'button--hover-backgroundColor'), 15%)
|
||||||
&:hover
|
|
||||||
background-color $ui-monokai-button--active-backgroundColor
|
.search-optionList-item--active
|
||||||
color $ui-monokai-button--active-color
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
.search-optionList-item-name-surfix
|
color get-theme-var(theme, 'button--active-color')
|
||||||
color $ui-monokai-inactive-text-color
|
&:hover
|
||||||
|
background-color get-theme-var(theme, 'button--active-backgroundColor')
|
||||||
|
color get-theme-var(theme, 'button--active-color')
|
||||||
|
|
||||||
|
.search-optionList-item-name-surfix
|
||||||
|
color get-theme-var(theme, 'inactive-text-color')
|
||||||
|
|
||||||
|
for theme in 'solarized-dark' 'dracula'
|
||||||
|
apply-theme(theme)
|
||||||
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
71
browser/main/Detail/FromUrlButton.js
Normal file
71
browser/main/Detail/FromUrlButton.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './FromUrlButton.styl'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
class FromUrlButton extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isActive: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseDown(e) {
|
||||||
|
this.setState({
|
||||||
|
isActive: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseUp(e) {
|
||||||
|
this.setState({
|
||||||
|
isActive: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave(e) {
|
||||||
|
this.setState({
|
||||||
|
isActive: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { className } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={
|
||||||
|
_.isString(className) ? 'FromUrlButton ' + className : 'FromUrlButton'
|
||||||
|
}
|
||||||
|
styleName={
|
||||||
|
this.state.isActive || this.props.isActive ? 'root--active' : 'root'
|
||||||
|
}
|
||||||
|
onMouseDown={e => this.handleMouseDown(e)}
|
||||||
|
onMouseUp={e => this.handleMouseUp(e)}
|
||||||
|
onMouseLeave={e => this.handleMouseLeave(e)}
|
||||||
|
onClick={this.props.onClick}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
styleName='icon'
|
||||||
|
src={
|
||||||
|
this.state.isActive || this.props.isActive
|
||||||
|
? '../resources/icon/icon-external.svg'
|
||||||
|
: '../resources/icon/icon-external.svg'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span styleName='tooltip'>{i18n.__('Convert URL to Markdown')}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FromUrlButton.propTypes = {
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
className: PropTypes.string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(FromUrlButton, styles)
|
||||||
41
browser/main/Detail/FromUrlButton.styl
Normal file
41
browser/main/Detail/FromUrlButton.styl
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
.root
|
||||||
|
top 45px
|
||||||
|
topBarButtonRight()
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 50px
|
||||||
|
right 125px
|
||||||
|
width 90px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
|
.root--active
|
||||||
|
@extend .root
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-favorite-star-button-color
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
|
|
||||||
|
.icon
|
||||||
|
transition transform 0.15s
|
||||||
|
height 13px
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.root
|
||||||
|
topBarButtonDark()
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
@@ -4,14 +4,22 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './FullscreenButton.styl'
|
import styles from './FullscreenButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const FullscreenButton = ({
|
const OSX = global.process.platform === 'darwin'
|
||||||
onClick
|
const FullscreenButton = ({ onClick }) => {
|
||||||
}) => (
|
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||||
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
return (
|
||||||
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
<button
|
||||||
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
|
styleName='control-fullScreenButton'
|
||||||
</button>
|
title={i18n.__('Fullscreen')}
|
||||||
)
|
onMouseDown={e => onClick(e)}
|
||||||
|
>
|
||||||
|
<img src='../resources/icon/icon-full.svg' />
|
||||||
|
<span lang={i18n.locale} styleName='tooltip'>
|
||||||
|
{i18n.__('Fullscreen')}({hotkey})
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
FullscreenButton.propTypes = {
|
FullscreenButton.propTypes = {
|
||||||
onClick: PropTypes.func.isRequired
|
onClick: PropTypes.func.isRequired
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
top 80px
|
top 80px
|
||||||
topBarButtonRight()
|
topBarButtonRight()
|
||||||
&:hover .tooltip
|
&:hover .tooltip
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
.tooltip
|
.tooltip
|
||||||
tooltip()
|
tooltip()
|
||||||
position absolute
|
position absolute
|
||||||
pointer-events none
|
pointer-events none
|
||||||
top 50px
|
top 50px
|
||||||
right 70px
|
right 70px
|
||||||
z-index 200
|
z-index 200
|
||||||
padding 5px
|
padding 5px
|
||||||
line-height normal
|
line-height normal
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
opacity 0
|
opacity 0
|
||||||
transition 0.1s
|
transition 0.1s
|
||||||
|
|
||||||
body[data-theme="dark"]
|
.tooltip:lang(ja)
|
||||||
.control-fullScreenButton
|
@extend .tooltip
|
||||||
|
right 35px
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './InfoButton.styl'
|
import styles from './InfoButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const InfoButton = ({
|
const InfoButton = ({ onClick }) => (
|
||||||
onClick
|
<button styleName='control-infoButton' onClick={e => onClick(e)}>
|
||||||
}) => (
|
|
||||||
<button styleName='control-infoButton'
|
|
||||||
onClick={(e) => onClick(e)}
|
|
||||||
>
|
|
||||||
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||||
<span styleName='tooltip'>{i18n.__('Info')}</span>
|
<span styleName='tooltip'>{i18n.__('Info')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,28 +6,47 @@ import copy from 'copy-to-clipboard'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class InfoPanel extends React.Component {
|
class InfoPanel extends React.Component {
|
||||||
copyNoteLink () {
|
copyNoteLink() {
|
||||||
const {noteLink} = this.props
|
const { noteLink } = this.props
|
||||||
this.refs.noteLink.select()
|
this.refs.noteLink.select()
|
||||||
copy(noteLink)
|
copy(noteLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {
|
const {
|
||||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
|
storageName,
|
||||||
|
folderName,
|
||||||
|
noteLink,
|
||||||
|
updatedAt,
|
||||||
|
createdAt,
|
||||||
|
exportAsMd,
|
||||||
|
exportAsTxt,
|
||||||
|
exportAsHtml,
|
||||||
|
exportAsPdf,
|
||||||
|
wordCount,
|
||||||
|
letterCount,
|
||||||
|
type,
|
||||||
|
print
|
||||||
} = this.props
|
} = this.props
|
||||||
return (
|
return (
|
||||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
<div
|
||||||
|
className='infoPanel'
|
||||||
|
styleName='control-infoButton-panel'
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<p styleName='modification-date'>{updatedAt}</p>
|
<p styleName='modification-date'>{updatedAt}</p>
|
||||||
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
<p styleName='modification-date-desc'>
|
||||||
|
{i18n.__('MODIFICATION DATE')}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{type === 'SNIPPET_NOTE'
|
{type === 'SNIPPET_NOTE' ? (
|
||||||
? ''
|
''
|
||||||
: <div styleName='count-wrap'>
|
) : (
|
||||||
|
<div styleName='count-wrap'>
|
||||||
<div styleName='count-number'>
|
<div styleName='count-number'>
|
||||||
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
||||||
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p>
|
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p>
|
||||||
@@ -37,12 +56,9 @@ class InfoPanel extends React.Component {
|
|||||||
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p>
|
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
|
|
||||||
{type === 'SNIPPET_NOTE'
|
{type === 'SNIPPET_NOTE' ? '' : <hr />}
|
||||||
? ''
|
|
||||||
: <hr />
|
|
||||||
}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p styleName='infoPanel-default'>{storageName}</p>
|
<p styleName='infoPanel-default'>{storageName}</p>
|
||||||
@@ -60,8 +76,18 @@ class InfoPanel extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input styleName='infoPanel-noteLink' ref='noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
|
<input
|
||||||
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
|
styleName='infoPanel-noteLink'
|
||||||
|
ref='noteLink'
|
||||||
|
defaultValue={noteLink}
|
||||||
|
onClick={e => {
|
||||||
|
e.target.select()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => this.copyNoteLink()}
|
||||||
|
styleName='infoPanel-copyButton'
|
||||||
|
>
|
||||||
<i className='fa fa-clipboard' />
|
<i className='fa fa-clipboard' />
|
||||||
</button>
|
</button>
|
||||||
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
|
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
|
||||||
@@ -70,22 +96,39 @@ class InfoPanel extends React.Component {
|
|||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div id='export-wrap'>
|
<div id='export-wrap'>
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsMd(e, 'export-md')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-code-o' />
|
<i className='fa fa-file-code-o' />
|
||||||
<p>{i18n.__('.md')}</p>
|
<p>{i18n.__('.md')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsTxt(e, 'export-txt')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-text-o' />
|
<i className='fa fa-file-text-o' />
|
||||||
<p>{i18n.__('.txt')}</p>
|
<p>{i18n.__('.txt')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsHtml(e, 'export-html')}
|
||||||
|
>
|
||||||
<i className='fa fa-html5' />
|
<i className='fa fa-html5' />
|
||||||
<p>{i18n.__('.html')}</p>
|
<p>{i18n.__('.html')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => print(e)}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||||
|
>
|
||||||
|
<i className='fa fa-file-pdf-o' />
|
||||||
|
<p>{i18n.__('.pdf')}</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--enable' onClick={e => print(e, 'print')}>
|
||||||
<i className='fa fa-print' />
|
<i className='fa fa-print' />
|
||||||
<p>{i18n.__('Print')}</p>
|
<p>{i18n.__('Print')}</p>
|
||||||
</button>
|
</button>
|
||||||
@@ -104,6 +147,7 @@ InfoPanel.propTypes = {
|
|||||||
exportAsMd: PropTypes.func.isRequired,
|
exportAsMd: PropTypes.func.isRequired,
|
||||||
exportAsTxt: PropTypes.func.isRequired,
|
exportAsTxt: PropTypes.func.isRequired,
|
||||||
exportAsHtml: PropTypes.func.isRequired,
|
exportAsHtml: PropTypes.func.isRequired,
|
||||||
|
exportAsPdf: PropTypes.func.isRequired,
|
||||||
wordCount: PropTypes.number,
|
wordCount: PropTypes.number,
|
||||||
letterCount: PropTypes.number,
|
letterCount: PropTypes.number,
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -11,10 +11,11 @@
|
|||||||
.control-infoButton-panel
|
.control-infoButton-panel
|
||||||
z-index 200
|
z-index 200
|
||||||
margin-top 0px
|
margin-top 0px
|
||||||
|
top: 50px
|
||||||
right 25px
|
right 25px
|
||||||
position absolute
|
position absolute
|
||||||
padding 20px 25px 0 25px
|
padding 20px 25px 0 25px
|
||||||
width 300px
|
// width 300px
|
||||||
overflow auto
|
overflow auto
|
||||||
background-color $ui-noteList-backgroundColor
|
background-color $ui-noteList-backgroundColor
|
||||||
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
.control-infoButton-panel-trash
|
.control-infoButton-panel-trash
|
||||||
z-index 200
|
z-index 200
|
||||||
margin-top 0px
|
margin-top 0px
|
||||||
|
top 50px
|
||||||
right 0px
|
right 0px
|
||||||
position absolute
|
position absolute
|
||||||
padding 20px 25px 0 25px
|
padding 20px 25px 0 25px
|
||||||
@@ -136,122 +138,49 @@
|
|||||||
.export--unable
|
.export--unable
|
||||||
cursor not-allowed
|
cursor not-allowed
|
||||||
|
|
||||||
body[data-theme="dark"]
|
apply-theme(theme)
|
||||||
.control-infoButton-panel
|
body[data-theme={theme}]
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
.control-infoButton-panel
|
||||||
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
|
|
||||||
.control-infoButton-panel-trash
|
.control-infoButton-panel-trash
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color get-theme-var(theme, 'noteList-backgroundColor')
|
||||||
|
|
||||||
.modification-date
|
.modification-date
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.modification-date-desc
|
.modification-date-desc
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.infoPanel-defaul-count
|
.infoPanel-defaul-count
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.infoPanel-sub-count
|
.infoPanel-sub-count
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.infoPanel-default
|
.infoPanel-default
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
.infoPanel-sub
|
.infoPanel-sub
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.infoPanel-noteLink
|
.infoPanel-noteLink
|
||||||
background-color alpha($ui-dark-borderColor, 60%)
|
background-color alpha(get-theme-var(theme, 'borderColor'), 20%)
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
[id=export-wrap]
|
[id=export-wrap]
|
||||||
button
|
button
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-dark-borderColor, 20%)
|
background-color alpha(get-theme-var(theme, 'borderColor'), 20%)
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
p
|
p
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
color get-theme-var(theme, 'text-color')
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
for theme in 'dark' 'solarized-dark' 'dracula'
|
||||||
.control-infoButton-panel
|
apply-theme(theme)
|
||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
|
||||||
|
|
||||||
.control-infoButton-panel-trash
|
for theme in $themes
|
||||||
background-color $ui-solarized-ark-noteList-backgroundColor
|
apply-theme(theme)
|
||||||
|
|
||||||
.modification-date
|
|
||||||
color $ui-solarized-ark-text-color
|
|
||||||
|
|
||||||
.modification-date-desc
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-defaul-count
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
|
|
||||||
.infoPanel-sub-count
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-default
|
|
||||||
color $ui-solarized-ark-text-color
|
|
||||||
|
|
||||||
.infoPanel-sub
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-noteLink
|
|
||||||
background-color alpha($ui-solarized-dark-borderColor, 20%)
|
|
||||||
color $ui-solarized-dark-text-color
|
|
||||||
|
|
||||||
[id=export-wrap]
|
|
||||||
button
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
background-color alpha($ui-solarized-dark-borderColor, 20%)
|
|
||||||
color $ui-solarized-ark-text-color
|
|
||||||
p
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
color $ui-solarized-ark-text-color
|
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
|
||||||
.control-infoButton-panel
|
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
|
||||||
|
|
||||||
.control-infoButton-panel-trash
|
|
||||||
background-color $ui-monokai-noteList-backgroundColor
|
|
||||||
|
|
||||||
.modification-date
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.modification-date-desc
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-defaul-count
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.infoPanel-sub-count
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-default
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
.infoPanel-sub
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.infoPanel-noteLink
|
|
||||||
background-color alpha($ui-monokai-borderColor, 20%)
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
|
|
||||||
[id=export-wrap]
|
|
||||||
button
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
background-color alpha($ui-monokai-borderColor, 20%)
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
p
|
|
||||||
color $ui-dark-inactive-text-color
|
|
||||||
&:hover
|
|
||||||
color $ui-monokai-text-color
|
|
||||||
@@ -5,9 +5,20 @@ import styles from './InfoPanel.styl'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const InfoPanelTrashed = ({
|
const InfoPanelTrashed = ({
|
||||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
|
storageName,
|
||||||
|
folderName,
|
||||||
|
updatedAt,
|
||||||
|
createdAt,
|
||||||
|
exportAsMd,
|
||||||
|
exportAsTxt,
|
||||||
|
exportAsHtml,
|
||||||
|
exportAsPdf
|
||||||
}) => (
|
}) => (
|
||||||
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
<div
|
||||||
|
className='infoPanel'
|
||||||
|
styleName='control-infoButton-panel-trash'
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<p styleName='modification-date'>{updatedAt}</p>
|
<p styleName='modification-date'>{updatedAt}</p>
|
||||||
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
||||||
@@ -21,7 +32,10 @@ const InfoPanelTrashed = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
|
<p styleName='infoPanel-default'>
|
||||||
|
<text styleName='infoPanel-trash'>Trash</text>
|
||||||
|
{folderName}
|
||||||
|
</p>
|
||||||
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
|
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -31,22 +45,34 @@ const InfoPanelTrashed = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id='export-wrap'>
|
<div id='export-wrap'>
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsMd(e, 'export-md')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-code-o' />
|
<i className='fa fa-file-code-o' />
|
||||||
<p>.md</p>
|
<p>.md</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsTxt(e, 'export-txt')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-text-o' />
|
<i className='fa fa-file-text-o' />
|
||||||
<p>.txt</p>
|
<p>.txt</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsHtml(e, 'export-html')}
|
||||||
|
>
|
||||||
<i className='fa fa-html5' />
|
<i className='fa fa-html5' />
|
||||||
<p>.html</p>
|
<p>.html</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--unable'>
|
<button
|
||||||
|
styleName='export--enable'
|
||||||
|
onClick={e => exportAsPdf(e, 'export-pdf')}
|
||||||
|
>
|
||||||
<i className='fa fa-file-pdf-o' />
|
<i className='fa fa-file-pdf-o' />
|
||||||
<p>.pdf</p>
|
<p>.pdf</p>
|
||||||
</button>
|
</button>
|
||||||
@@ -61,7 +87,8 @@ InfoPanelTrashed.propTypes = {
|
|||||||
createdAt: PropTypes.string.isRequired,
|
createdAt: PropTypes.string.isRequired,
|
||||||
exportAsMd: PropTypes.func.isRequired,
|
exportAsMd: PropTypes.func.isRequired,
|
||||||
exportAsTxt: PropTypes.func.isRequired,
|
exportAsTxt: PropTypes.func.isRequired,
|
||||||
exportAsHtml: PropTypes.func.isRequired
|
exportAsHtml: PropTypes.func.isRequired,
|
||||||
|
exportAsPdf: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(InfoPanelTrashed, styles)
|
export default CSSModules(InfoPanelTrashed, styles)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable camelcase */
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
@@ -9,7 +10,6 @@ import StarButton from './StarButton'
|
|||||||
import TagSelect from './TagSelect'
|
import TagSelect from './TagSelect'
|
||||||
import FolderSelect from './FolderSelect'
|
import FolderSelect from './FolderSelect'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import { hashHistory } from 'react-router'
|
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import markdown from 'browser/lib/markdownTextHelper'
|
import markdown from 'browser/lib/markdownTextHelper'
|
||||||
import StatusBar from '../StatusBar'
|
import StatusBar from '../StatusBar'
|
||||||
@@ -29,99 +29,147 @@ import { formatDate } from 'browser/lib/date-formatter'
|
|||||||
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||||
import striptags from 'striptags'
|
import striptags from 'striptags'
|
||||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
|
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||||
|
import queryString from 'query-string'
|
||||||
|
import { replace } from 'connected-react-router'
|
||||||
|
import ToggleDirectionButton from 'browser/main/Detail/ToggleDirectionButton'
|
||||||
|
|
||||||
class MarkdownNoteDetail extends React.Component {
|
class MarkdownNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isMovingNote: false,
|
isMovingNote: false,
|
||||||
note: Object.assign({
|
note: Object.assign(
|
||||||
title: '',
|
{
|
||||||
content: ''
|
title: '',
|
||||||
}, props.note),
|
content: '',
|
||||||
isLockButtonShown: false,
|
linesHighlighted: []
|
||||||
|
},
|
||||||
|
props.note
|
||||||
|
),
|
||||||
|
isLockButtonShown: props.config.editor.type !== 'SPLIT',
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
editorType: props.config.editor.type
|
editorType: props.config.editor.type,
|
||||||
|
switchPreview: props.config.editor.switchPreview,
|
||||||
|
RTL: false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dispatchTimer = null
|
this.dispatchTimer = null
|
||||||
|
|
||||||
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
this.toggleLockButton = this.handleToggleLockButton.bind(this)
|
||||||
|
this.generateToc = this.handleGenerateToc.bind(this)
|
||||||
|
this.handleUpdateContent = this.handleUpdateContent.bind(this)
|
||||||
|
this.handleSwitchStackDirection = this.handleSwitchStackDirection.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus() {
|
||||||
this.refs.content.focus()
|
this.refs.content.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
|
ee.on('editor:orientation', this.handleSwitchStackDirection)
|
||||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
|
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
|
||||||
ee.on('topbar:togglemodebutton', () => {
|
ee.on('topbar:togglemodebutton', () => {
|
||||||
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
const reversedType =
|
||||||
|
this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||||
this.handleSwitchMode(reversedType)
|
this.handleSwitchMode(reversedType)
|
||||||
})
|
})
|
||||||
|
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
|
||||||
|
ee.on('code:generate-toc', this.generateToc)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
|
const isNewNote = nextProps.note.key !== this.props.note.key
|
||||||
|
const hasDeletedTags =
|
||||||
|
nextProps.note.tags.length < this.props.note.tags.length
|
||||||
|
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
note: Object.assign({ linesHighlighted: [] }, nextProps.note)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.refs.content.reload()
|
||||||
|
if (this.refs.tags) this.refs.tags.reset()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus content if using blur or double click
|
||||||
|
// --> Moved here from componentDidMount so a re-render during search won't set focus to the editor
|
||||||
|
const { switchPreview } = nextProps.config.editor
|
||||||
|
|
||||||
|
if (this.state.switchPreview !== switchPreview) {
|
||||||
this.setState({
|
this.setState({
|
||||||
note: Object.assign({}, nextProps.note)
|
switchPreview
|
||||||
}, () => {
|
|
||||||
this.refs.content.reload()
|
|
||||||
if (this.refs.tags) this.refs.tags.reset()
|
|
||||||
})
|
})
|
||||||
|
if (switchPreview === 'BLUR' || switchPreview === 'DBL_CLICK') {
|
||||||
|
console.log('setting focus', switchPreview)
|
||||||
|
this.focus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount() {
|
||||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
|
ee.on('topbar:toggledirectionbutton', this.handleSwitchDirection)
|
||||||
|
ee.off('code:generate-toc', this.generateToc)
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateTag () {
|
handleUpdateTag() {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||||
this.updateNote(note)
|
this.updateNote(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateContent () {
|
handleUpdateContent() {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
note.content = this.refs.content.value
|
note.content = this.refs.content.value
|
||||||
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
|
|
||||||
|
let title = findNoteTitle(
|
||||||
|
note.content,
|
||||||
|
this.props.config.editor.enableFrontMatterTitle,
|
||||||
|
this.props.config.editor.frontMatterTitleField
|
||||||
|
)
|
||||||
|
title = striptags(title)
|
||||||
|
title = markdown.strip(title)
|
||||||
|
note.title = title
|
||||||
|
|
||||||
this.updateNote(note)
|
this.updateNote(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNote (note) {
|
updateNote(note) {
|
||||||
note.updatedAt = new Date()
|
note.updatedAt = new Date()
|
||||||
this.setState({note}, () => {
|
this.setState({ note }, () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
save () {
|
save() {
|
||||||
clearTimeout(this.saveQueue)
|
clearTimeout(this.saveQueue)
|
||||||
this.saveQueue = setTimeout(() => {
|
this.saveQueue = setTimeout(() => {
|
||||||
this.saveNow()
|
this.saveNow()
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
saveNow () {
|
saveNow() {
|
||||||
const { note, dispatch } = this.props
|
const { note, dispatch } = this.props
|
||||||
clearTimeout(this.saveQueue)
|
clearTimeout(this.saveQueue)
|
||||||
this.saveQueue = null
|
this.saveQueue = null
|
||||||
|
|
||||||
dataApi
|
dataApi.updateNote(note.storage, note.key, this.state.note).then(note => {
|
||||||
.updateNote(note.storage, note.key, this.state.note)
|
dispatch({
|
||||||
.then((note) => {
|
type: 'UPDATE_NOTE',
|
||||||
dispatch({
|
note: note
|
||||||
type: 'UPDATE_NOTE',
|
|
||||||
note: note
|
|
||||||
})
|
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
|
||||||
})
|
})
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFolderChange (e) {
|
handleFolderChange(e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
const value = this.refs.folder.value
|
const value = this.refs.folder.value
|
||||||
const splitted = value.split('-')
|
const splitted = value.split('-')
|
||||||
@@ -130,70 +178,114 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||||
.then((newNote) => {
|
.then(newNote => {
|
||||||
this.setState({
|
this.setState(
|
||||||
isMovingNote: true,
|
{
|
||||||
note: Object.assign({}, newNote)
|
isMovingNote: true,
|
||||||
}, () => {
|
note: Object.assign({}, newNote)
|
||||||
const { dispatch, location } = this.props
|
},
|
||||||
dispatch({
|
() => {
|
||||||
type: 'MOVE_NOTE',
|
const { dispatch, location } = this.props
|
||||||
originNote: note,
|
dispatch({
|
||||||
note: newNote
|
type: 'MOVE_NOTE',
|
||||||
})
|
originNote: note,
|
||||||
hashHistory.replace({
|
note: newNote
|
||||||
pathname: location.pathname,
|
})
|
||||||
query: {
|
dispatch(
|
||||||
key: newNote.key
|
replace({
|
||||||
}
|
pathname: location.pathname,
|
||||||
})
|
search: queryString.stringify({
|
||||||
this.setState({
|
key: newNote.key
|
||||||
isMovingNote: false
|
})
|
||||||
})
|
})
|
||||||
})
|
)
|
||||||
|
this.setState({
|
||||||
|
isMovingNote: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleStarButtonClick (e) {
|
handleStarButtonClick(e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
if (!note.isStarred)
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
|
||||||
|
|
||||||
note.isStarred = !note.isStarred
|
note.isStarred = !note.isStarred
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
note
|
{
|
||||||
}, () => {
|
note
|
||||||
this.save()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsFile () {
|
exportAsFile() {}
|
||||||
|
|
||||||
}
|
exportAsMd() {
|
||||||
|
|
||||||
exportAsMd () {
|
|
||||||
ee.emit('export:save-md')
|
ee.emit('export:save-md')
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsTxt () {
|
exportAsTxt() {
|
||||||
ee.emit('export:save-text')
|
ee.emit('export:save-text')
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsHtml () {
|
exportAsHtml() {
|
||||||
ee.emit('export:save-html')
|
ee.emit('export:save-html')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTrashButtonClick (e) {
|
exportAsPdf() {
|
||||||
|
ee.emit('export:save-pdf')
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown(e) {
|
||||||
|
switch (e.keyCode) {
|
||||||
|
// tab key
|
||||||
|
case 9:
|
||||||
|
if (e.ctrlKey && !e.shiftKey) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.jumpNextTab()
|
||||||
|
} else if (e.ctrlKey && e.shiftKey) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.jumpPrevTab()
|
||||||
|
} else if (
|
||||||
|
!e.ctrlKey &&
|
||||||
|
!e.shiftKey &&
|
||||||
|
e.target === this.refs.description
|
||||||
|
) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.focusEditor()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
// I key
|
||||||
|
case 73:
|
||||||
|
{
|
||||||
|
const isSuper =
|
||||||
|
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey
|
||||||
|
if (isSuper) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.handleInfoButtonClick(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTrashButtonClick(e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
const { isTrashed } = note
|
const { isTrashed } = note
|
||||||
const { confirmDeletion } = this.props.config.ui
|
const { confirmDeletion } = this.props.config.ui
|
||||||
|
|
||||||
if (isTrashed) {
|
if (isTrashed) {
|
||||||
if (confirmDeleteNote(confirmDeletion, true)) {
|
if (confirmDeleteNote(confirmDeletion, true)) {
|
||||||
const {note, dispatch} = this.props
|
const { note, dispatch } = this.props
|
||||||
dataApi
|
dataApi
|
||||||
.deleteNote(note.storage, note.key)
|
.deleteNote(note.storage, note.key)
|
||||||
.then((data) => {
|
.then(data => {
|
||||||
const dispatchHandler = () => {
|
const dispatchHandler = () => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'DELETE_NOTE',
|
type: 'DELETE_NOTE',
|
||||||
@@ -209,222 +301,335 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
if (confirmDeleteNote(confirmDeletion, false)) {
|
if (confirmDeleteNote(confirmDeletion, false)) {
|
||||||
note.isTrashed = true
|
note.isTrashed = true
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
note
|
{
|
||||||
}, () => {
|
note
|
||||||
this.save()
|
},
|
||||||
})
|
() => {
|
||||||
|
this.save()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
ee.emit('list:next')
|
ee.emit('list:next')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUndoButtonClick (e) {
|
handleUndoButtonClick(e) {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
note.isTrashed = false
|
note.isTrashed = false
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
note
|
{
|
||||||
}, () => {
|
note
|
||||||
this.save()
|
},
|
||||||
this.refs.content.reload()
|
() => {
|
||||||
ee.emit('list:next')
|
this.save()
|
||||||
})
|
this.refs.content.reload()
|
||||||
|
ee.emit('list:next')
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFullScreenButton (e) {
|
handleFullScreenButton(e) {
|
||||||
ee.emit('editor:fullscreen')
|
ee.emit('editor:fullscreen')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLockButtonMouseDown (e) {
|
handleLockButtonMouseDown(e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
ee.emit('editor:lock')
|
ee.emit('editor:lock')
|
||||||
this.setState({ isLocked: !this.state.isLocked })
|
this.setState({ isLocked: !this.state.isLocked })
|
||||||
if (this.state.isLocked) this.focus()
|
if (this.state.isLocked) this.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
getToggleLockButton () {
|
getToggleLockButton() {
|
||||||
return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
|
return this.state.isLocked
|
||||||
|
? '../resources/icon/icon-lock.svg'
|
||||||
|
: '../resources/icon/icon-unlock.svg'
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteKeyDown (e) {
|
handleDeleteKeyDown(e) {
|
||||||
if (e.keyCode === 27) this.handleDeleteCancelButtonClick(e)
|
if (e.keyCode === 27) this.handleDeleteCancelButtonClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleToggleLockButton (event, noteStatus) {
|
handleToggleLockButton(event, noteStatus) {
|
||||||
// first argument event is not used
|
// first argument event is not used
|
||||||
if (this.props.config.editor.switchPreview === 'BLUR' && noteStatus === 'CODE') {
|
if (noteStatus === 'CODE') {
|
||||||
this.setState({isLockButtonShown: true})
|
this.setState({ isLockButtonShown: true })
|
||||||
} else {
|
} else {
|
||||||
this.setState({isLockButtonShown: false})
|
this.setState({ isLockButtonShown: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleFocus (e) {
|
handleGenerateToc() {
|
||||||
|
const editor = this.refs.content.refs.code.editor
|
||||||
|
markdownToc.generateInEditor(editor)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFocus(e) {
|
||||||
this.focus()
|
this.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleInfoButtonClick (e) {
|
handleInfoButtonClick(e) {
|
||||||
const infoPanel = document.querySelector('.infoPanel')
|
const infoPanel = document.querySelector('.infoPanel')
|
||||||
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
if (infoPanel.style)
|
||||||
|
infoPanel.style.display =
|
||||||
|
infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
print (e) {
|
print(e) {
|
||||||
ee.emit('print')
|
ee.emit('print')
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSwitchMode (type) {
|
handleSwitchMode(type) {
|
||||||
this.setState({ editorType: type }, () => {
|
// If in split mode, hide the lock button
|
||||||
const newConfig = Object.assign({}, this.props.config)
|
this.setState(
|
||||||
newConfig.editor.type = type
|
{ editorType: type, isLockButtonShown: type !== 'SPLIT' },
|
||||||
ConfigManager.set(newConfig)
|
() => {
|
||||||
})
|
this.focus()
|
||||||
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
|
newConfig.editor.type = type
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderEditor () {
|
handleSwitchStackDirection() {
|
||||||
const { config, ignorePreviewPointerEvents } = this.props
|
this.setState(
|
||||||
|
prevState => ({ isStacking: !prevState.isStacking }),
|
||||||
|
() => {
|
||||||
|
this.focus()
|
||||||
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
|
newConfig.ui.isStacking = this.state.isStacking
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSwitchDirection() {
|
||||||
|
if (!this.props.config.editor.rtlEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If in split mode, hide the lock button
|
||||||
|
const direction = this.state.RTL
|
||||||
|
this.setState({ RTL: !direction })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeleteNote() {
|
||||||
|
this.handleTrashButtonClick()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClearTodo() {
|
||||||
const { note } = this.state
|
const { note } = this.state
|
||||||
|
const splitted = note.content.split('\n')
|
||||||
|
|
||||||
|
const clearTodoContent = splitted
|
||||||
|
.map(line => {
|
||||||
|
const trimmedLine = line.trim()
|
||||||
|
if (trimmedLine.match(/\[x\]/i)) {
|
||||||
|
return line.replace(/\[x\]/i, '[ ]')
|
||||||
|
} else {
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
|
||||||
|
note.content = clearTodoContent
|
||||||
|
this.refs.content.setValue(note.content)
|
||||||
|
|
||||||
|
this.updateNote(note)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEditor() {
|
||||||
|
const { config, ignorePreviewPointerEvents } = this.props
|
||||||
|
const { note, isStacking } = this.state
|
||||||
|
|
||||||
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
||||||
return <MarkdownEditor
|
return (
|
||||||
ref='content'
|
<MarkdownEditor
|
||||||
styleName='body-noteEditor'
|
ref='content'
|
||||||
config={config}
|
styleName='body-noteEditor'
|
||||||
value={note.content}
|
config={config}
|
||||||
storageKey={note.storage}
|
value={note.content}
|
||||||
noteKey={note.key}
|
storageKey={note.storage}
|
||||||
onChange={this.handleUpdateContent.bind(this)}
|
noteKey={note.key}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
linesHighlighted={note.linesHighlighted}
|
||||||
/>
|
onChange={this.handleUpdateContent}
|
||||||
|
isLocked={this.state.isLocked}
|
||||||
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||||
|
/>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return <MarkdownSplitEditor
|
return (
|
||||||
ref='content'
|
<MarkdownSplitEditor
|
||||||
config={config}
|
ref='content'
|
||||||
value={note.content}
|
config={config}
|
||||||
storageKey={note.storage}
|
value={note.content}
|
||||||
noteKey={note.key}
|
storageKey={note.storage}
|
||||||
onChange={this.handleUpdateContent.bind(this)}
|
noteKey={note.key}
|
||||||
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
isStacking={isStacking}
|
||||||
/>
|
linesHighlighted={note.linesHighlighted}
|
||||||
|
onChange={this.handleUpdateContent}
|
||||||
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
RTL={config.editor.rtlEnabled && this.state.RTL}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { data, location } = this.props
|
const { data, dispatch, location, config } = this.props
|
||||||
const { note, editorType } = this.state
|
const { note, editorType } = this.state
|
||||||
const storageKey = note.storage
|
const storageKey = note.storage
|
||||||
const folderKey = note.folder
|
const folderKey = note.folder
|
||||||
|
|
||||||
const options = []
|
const options = []
|
||||||
data.storageMap.forEach((storage, index) => {
|
data.storageMap.forEach((storage, index) => {
|
||||||
storage.folders.forEach((folder) => {
|
storage.folders.forEach(folder => {
|
||||||
options.push({
|
options.push({
|
||||||
storage: storage,
|
storage: storage,
|
||||||
folder: folder
|
folder: folder
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
|
||||||
|
|
||||||
const trashTopBar = <div styleName='info'>
|
const currentOption = _.find(
|
||||||
<div styleName='info-left'>
|
options,
|
||||||
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
option =>
|
||||||
</div>
|
option.storage.key === storageKey && option.folder.key === folderKey
|
||||||
<div styleName='info-right'>
|
)
|
||||||
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
|
||||||
<InfoButton
|
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
|
||||||
/>
|
|
||||||
<InfoPanelTrashed
|
|
||||||
storageName={currentOption.storage.name}
|
|
||||||
folderName={currentOption.folder.name}
|
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
|
||||||
createdAt={formatDate(note.createdAt)}
|
|
||||||
exportAsHtml={this.exportAsHtml}
|
|
||||||
exportAsMd={this.exportAsMd}
|
|
||||||
exportAsTxt={this.exportAsTxt}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
const detailTopBar = <div styleName='info'>
|
// currentOption may be undefined
|
||||||
<div styleName='info-left'>
|
const storageName = _.get(currentOption, 'storage.name') || ''
|
||||||
<div styleName='info-left-top'>
|
const folderName = _.get(currentOption, 'folder.name') || ''
|
||||||
<FolderSelect styleName='info-left-top-folderSelect'
|
|
||||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
const trashTopBar = (
|
||||||
ref='folder'
|
<div styleName='info'>
|
||||||
data={data}
|
<div styleName='info-left'>
|
||||||
onChange={(e) => this.handleFolderChange(e)}
|
<RestoreButton onClick={e => this.handleUndoButtonClick(e)} />
|
||||||
|
</div>
|
||||||
|
<div styleName='info-right'>
|
||||||
|
<PermanentDeleteButton
|
||||||
|
onClick={e => this.handleTrashButtonClick(e)}
|
||||||
|
/>
|
||||||
|
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||||
|
<InfoPanelTrashed
|
||||||
|
storageName={storageName}
|
||||||
|
folderName={folderName}
|
||||||
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
|
createdAt={formatDate(note.createdAt)}
|
||||||
|
exportAsHtml={this.exportAsHtml}
|
||||||
|
exportAsMd={this.exportAsMd}
|
||||||
|
exportAsTxt={this.exportAsTxt}
|
||||||
|
exportAsPdf={this.exportAsPdf}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TagSelect
|
|
||||||
ref='tags'
|
|
||||||
value={this.state.note.tags}
|
|
||||||
onChange={this.handleUpdateTag.bind(this)}
|
|
||||||
/>
|
|
||||||
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
)
|
||||||
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
|
||||||
<StarButton
|
|
||||||
onClick={(e) => this.handleStarButtonClick(e)}
|
|
||||||
isActive={note.isStarred}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{(() => {
|
const detailTopBar = (
|
||||||
const imgSrc = `${this.getToggleLockButton()}`
|
<div styleName='info'>
|
||||||
const lockButtonComponent =
|
<div styleName='info-left'>
|
||||||
<button styleName='control-lockButton'
|
<div>
|
||||||
onFocus={(e) => this.handleFocus(e)}
|
<FolderSelect
|
||||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
styleName='info-left-top-folderSelect'
|
||||||
>
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
<img styleName='iconInfo' src={imgSrc} />
|
ref='folder'
|
||||||
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
|
data={data}
|
||||||
</button>
|
onChange={e => this.handleFolderChange(e)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
return (
|
<TagSelect
|
||||||
this.state.isLockButtonShown ? lockButtonComponent : ''
|
ref='tags'
|
||||||
)
|
value={this.state.note.tags}
|
||||||
})()}
|
saveTagsAlphabetically={config.ui.saveTagsAlphabetically}
|
||||||
|
showTagsAlphabetically={config.ui.showTagsAlphabetically}
|
||||||
|
data={data}
|
||||||
|
dispatch={dispatch}
|
||||||
|
onChange={this.handleUpdateTag.bind(this)}
|
||||||
|
coloredTags={config.coloredTags}
|
||||||
|
/>
|
||||||
|
<TodoListPercentage
|
||||||
|
onClearCheckboxClick={e => this.handleClearTodo(e)}
|
||||||
|
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div styleName='info-right'>
|
||||||
|
<ToggleModeButton
|
||||||
|
onClick={e => this.handleSwitchMode(e)}
|
||||||
|
editorType={editorType}
|
||||||
|
/>
|
||||||
|
{this.props.config.editor.rtlEnabled && (
|
||||||
|
<ToggleDirectionButton
|
||||||
|
onClick={e => this.handleSwitchDirection(e)}
|
||||||
|
isRTL={this.state.RTL}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<StarButton
|
||||||
|
onClick={e => this.handleStarButtonClick(e)}
|
||||||
|
isActive={note.isStarred}
|
||||||
|
/>
|
||||||
|
|
||||||
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
|
{(() => {
|
||||||
|
const imgSrc = `${this.getToggleLockButton()}`
|
||||||
|
const lockButtonComponent = (
|
||||||
|
<button
|
||||||
|
styleName='control-lockButton'
|
||||||
|
onFocus={e => this.handleFocus(e)}
|
||||||
|
onMouseDown={e => this.handleLockButtonMouseDown(e)}
|
||||||
|
>
|
||||||
|
<img src={imgSrc} />
|
||||||
|
{this.state.isLocked ? (
|
||||||
|
<span styleName='tooltip'>Unlock</span>
|
||||||
|
) : (
|
||||||
|
<span styleName='tooltip'>Lock</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
return this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||||
|
})()}
|
||||||
|
|
||||||
<InfoButton
|
<FullscreenButton onClick={e => this.handleFullScreenButton(e)} />
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<InfoPanel
|
<TrashButton onClick={e => this.handleTrashButtonClick(e)} />
|
||||||
storageName={currentOption.storage.name}
|
|
||||||
folderName={currentOption.folder.name}
|
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
|
||||||
noteLink={`[${note.title}](:note:${location.query.key})`}
|
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
<InfoPanel
|
||||||
createdAt={formatDate(note.createdAt)}
|
storageName={storageName}
|
||||||
exportAsMd={this.exportAsMd}
|
folderName={folderName}
|
||||||
exportAsTxt={this.exportAsTxt}
|
noteLink={`[${note.title}](:note:${
|
||||||
exportAsHtml={this.exportAsHtml}
|
queryString.parse(location.search).key
|
||||||
wordCount={note.content.split(' ').length}
|
})`}
|
||||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
type={note.type}
|
createdAt={formatDate(note.createdAt)}
|
||||||
print={this.print}
|
exportAsMd={this.exportAsMd}
|
||||||
/>
|
exportAsTxt={this.exportAsTxt}
|
||||||
|
exportAsHtml={this.exportAsHtml}
|
||||||
|
exportAsPdf={this.exportAsPdf}
|
||||||
|
wordCount={note.content.trim().split(/\s+/g).length}
|
||||||
|
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||||
|
type={note.type}
|
||||||
|
print={this.print}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='NoteDetail'
|
<div
|
||||||
|
className='NoteDetail'
|
||||||
style={this.props.style}
|
style={this.props.style}
|
||||||
styleName='root'
|
styleName='root'
|
||||||
|
onKeyDown={e => this.handleKeyDown(e)}
|
||||||
>
|
>
|
||||||
|
|
||||||
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||||
|
|
||||||
<div styleName='body'>
|
<div styleName='body'>{this.renderEditor()}</div>
|
||||||
{this.renderEditor()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<StatusBar
|
<StatusBar
|
||||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||||
@@ -438,9 +643,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
MarkdownNoteDetail.propTypes = {
|
MarkdownNoteDetail.propTypes = {
|
||||||
dispatch: PropTypes.func,
|
dispatch: PropTypes.func,
|
||||||
repositories: PropTypes.array,
|
repositories: PropTypes.array,
|
||||||
note: PropTypes.shape({
|
note: PropTypes.shape({}),
|
||||||
|
|
||||||
}),
|
|
||||||
style: PropTypes.shape({
|
style: PropTypes.shape({
|
||||||
left: PropTypes.number
|
left: PropTypes.number
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
.control-lockButton
|
.control-lockButton
|
||||||
topBarButtonRight()
|
topBarButtonRight()
|
||||||
position absolute
|
position absolute
|
||||||
right 225px
|
right 265px
|
||||||
&:hover .tooltip
|
&:hover .tooltip
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
@@ -66,13 +66,14 @@ body[data-theme="dark"]
|
|||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|
||||||
|
apply-theme(theme)
|
||||||
|
body[data-theme={theme}]
|
||||||
|
.root
|
||||||
|
border-left 1px solid get-theme-var(theme, 'borderColor')
|
||||||
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
for theme in 'solarized-dark' 'dracula'
|
||||||
.root
|
apply-theme(theme)
|
||||||
border-left 1px solid $ui-solarized-dark-borderColor
|
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
body[data-theme="monokai"]
|
for theme in $themes
|
||||||
.root
|
apply-theme(theme)
|
||||||
border-left 1px solid $ui-monokai-borderColor
|
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ $info-margin-under-border = 30px
|
|||||||
display flex
|
display flex
|
||||||
align-items center
|
align-items center
|
||||||
padding 0 20px
|
padding 0 20px
|
||||||
|
z-index 99
|
||||||
|
|
||||||
|
.info > div
|
||||||
|
> button
|
||||||
|
-webkit-user-drag none
|
||||||
|
user-select none
|
||||||
|
> img, span
|
||||||
|
-webkit-user-drag none
|
||||||
|
user-select none
|
||||||
|
|
||||||
.info-left
|
.info-left
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
@@ -93,12 +102,14 @@ body[data-theme="dark"]
|
|||||||
.undo-button
|
.undo-button
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|
||||||
body[data-theme="solarized-dark"]
|
apply-theme(theme)
|
||||||
.info
|
body[data-theme={theme}]
|
||||||
border-color $ui-solarized-dark-borderColor
|
.info
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
border-color get-theme-var(theme, 'borderColor')
|
||||||
|
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
|
||||||
body[data-theme="monokai"]
|
|
||||||
.info
|
for theme in 'solarized-dark' 'dracula'
|
||||||
border-color $ui-monokai-borderColor
|
apply-theme(theme)
|
||||||
background-color $ui-monokai-noteDetail-backgroundColor
|
|
||||||
|
for theme in $themes
|
||||||
|
apply-theme(theme)
|
||||||
|
|||||||
@@ -4,13 +4,9 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './TrashButton.styl'
|
import styles from './TrashButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const PermanentDeleteButton = ({
|
const PermanentDeleteButton = ({ onClick }) => (
|
||||||
onClick
|
<button styleName='control-trashButton--in-trash' onClick={e => onClick(e)}>
|
||||||
}) => (
|
<img src='../resources/icon/icon-trash.svg' />
|
||||||
<button styleName='control-trashButton--in-trash'
|
|
||||||
onClick={(e) => onClick(e)}
|
|
||||||
>
|
|
||||||
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
|
||||||
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
|
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
|
|||||||
import styles from './RestoreButton.styl'
|
import styles from './RestoreButton.styl'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const RestoreButton = ({
|
const RestoreButton = ({ onClick }) => (
|
||||||
onClick
|
<button styleName='control-restoreButton' onClick={onClick}>
|
||||||
}) => (
|
|
||||||
<button styleName='control-restoreButton'
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
|
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
|
||||||
<span styleName='tooltip'>{i18n.__('Restore')}</span>
|
<span styleName='tooltip'>{i18n.__('Restore')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user