mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
Compare commits
1608 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cad2ab4df | ||
|
|
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 | ||
|
|
3c8337cf54 | ||
|
|
883b4c4c26 | ||
|
|
6bc42c564d | ||
|
|
d2b2e76a6a | ||
|
|
9d9109e9e5 | ||
|
|
18efb89b9a | ||
|
|
cefe883025 | ||
|
|
0ffa0b96d3 | ||
|
|
0429acfa1b | ||
|
|
827e3c1829 | ||
|
|
aa756ef194 | ||
|
|
d8aad65b24 | ||
|
|
1038e86196 | ||
|
|
47845fd4e3 | ||
|
|
294c3f10ab | ||
|
|
f6afc756dc | ||
|
|
64407e5ca6 | ||
|
|
0a42b0f61f | ||
|
|
ae493cbd0e | ||
|
|
ddd339851b | ||
|
|
82db986bd7 | ||
|
|
7bacd6f8f0 | ||
|
|
f0941f47dd | ||
|
|
c9d05b1117 | ||
|
|
7ee12752ec | ||
|
|
b44772441d | ||
|
|
72e3784fa5 | ||
|
|
8a6c86bf65 | ||
|
|
cc52cf60dc | ||
|
|
0095735841 | ||
|
|
95c10a1de7 | ||
|
|
8f4c92e251 | ||
|
|
58354061d8 | ||
|
|
7414d52dc2 | ||
|
|
c42b5c8806 | ||
|
|
5c60da0f8f | ||
|
|
70d02e9a6d | ||
|
|
6401016424 | ||
|
|
cd031a89fb | ||
|
|
aadba79002 | ||
|
|
4a017fd8ae | ||
|
|
e6072c8fe9 | ||
|
|
f7fd99ec20 | ||
|
|
836fc13449 | ||
|
|
e1f78cd682 | ||
|
|
c69f34836a | ||
|
|
48a0db28d7 | ||
|
|
58eeb90158 | ||
|
|
c36689934d | ||
|
|
653b985018 | ||
|
|
40d90a53b2 | ||
|
|
7c3aaff635 | ||
|
|
4bb7929229 | ||
|
|
bdd5b7b3a7 | ||
|
|
a2ddb56540 | ||
|
|
7c0097951c | ||
|
|
7970016fbf | ||
|
|
129e3b283d | ||
|
|
868eefd2cf | ||
|
|
c42e1a0ab4 | ||
|
|
720475dae5 | ||
|
|
259df880ba | ||
|
|
68a328a364 | ||
|
|
3bc21cdb09 | ||
|
|
9e8ef70510 | ||
|
|
5fd822a24d | ||
|
|
8ee4dbbb5c | ||
|
|
9522a4d5d9 | ||
|
|
eefecdefbe | ||
|
|
84e670e71c | ||
|
|
c22b69234f | ||
|
|
29cd63d3a7 | ||
|
|
214ed388f2 | ||
|
|
0bd3445370 | ||
|
|
002dc9b017 | ||
|
|
ebf5a03f56 | ||
|
|
2797faafe5 | ||
|
|
84ac739993 | ||
|
|
13c37f046f | ||
|
|
2631cc3747 | ||
|
|
5873e8e896 | ||
|
|
6dc633c2a1 | ||
|
|
8b07126285 | ||
|
|
93a120543a | ||
|
|
af0aa4a567 | ||
|
|
58fd1f0c46 | ||
|
|
030041932e | ||
|
|
8dbf456398 | ||
|
|
3a90a078ce | ||
|
|
c30957fc9f | ||
|
|
f6db946c9a | ||
|
|
c6c0d4c62a | ||
|
|
57befc4ccb | ||
|
|
e716af75ed | ||
|
|
efe2bea64b | ||
|
|
9b926326ef | ||
|
|
de71033fe2 | ||
|
|
2c10bf251d | ||
|
|
8af50aa5bd | ||
|
|
05bedfe3d4 | ||
|
|
a1929dac8a | ||
|
|
834ecc643a | ||
|
|
60baabf7e7 | ||
|
|
185a149d74 | ||
|
|
5d62dd2002 | ||
|
|
9eaa6b5cec | ||
|
|
6ff03bbb95 | ||
|
|
9fac6bca64 | ||
|
|
88856b788a | ||
|
|
eda4e46d9f | ||
|
|
0ae1263d9d | ||
|
|
31f1ebe801 | ||
|
|
c6a9c9c57d | ||
|
|
35bcbbbae4 | ||
|
|
22e2c3da1f | ||
|
|
b526d48946 | ||
|
|
91a95b7c20 | ||
|
|
d634e1124a | ||
|
|
309e159df1 | ||
|
|
ffae53326a | ||
|
|
ddd1522e19 | ||
|
|
4bc0cccb24 | ||
|
|
72fbefa300 | ||
|
|
30378eeb50 | ||
|
|
03293c0d25 | ||
|
|
2e3f6e39f6 | ||
|
|
e4d4041c6b | ||
|
|
166a5c10a7 | ||
|
|
1fec81cc3e | ||
|
|
9c247bcb22 | ||
|
|
707356bffe | ||
|
|
fc88a49acc | ||
|
|
8ccf490e9b | ||
|
|
ea768f982e | ||
|
|
172ea82954 | ||
|
|
10500c3c1c | ||
|
|
225916fbba | ||
|
|
2fce78422b | ||
|
|
8132dd6847 | ||
|
|
4caee1e103 | ||
|
|
ca0b03e97c | ||
|
|
f03178bb8d | ||
|
|
d083a86138 | ||
|
|
8216b992ea | ||
|
|
8e74ee7dde | ||
|
|
b207fe14df | ||
|
|
92cfa21be6 | ||
|
|
5fd482428a | ||
|
|
98b09f7edc | ||
|
|
7b83a34777 | ||
|
|
aeb63ec901 | ||
|
|
436093e0b6 | ||
|
|
d7ee06ce6d | ||
|
|
e72003009d | ||
|
|
cfe8235a36 | ||
|
|
8c1ac9c5b3 | ||
|
|
77e3a7d43a | ||
|
|
53728a0f4a | ||
|
|
06f33d9a63 | ||
|
|
ed2698ecc3 | ||
|
|
0cb5554ae5 | ||
|
|
89850c0b22 | ||
|
|
d78b94f4e8 | ||
|
|
c2c50817f1 | ||
|
|
680c2a2904 | ||
|
|
a1085e3863 | ||
|
|
37f6a05170 | ||
|
|
0d296c3b25 | ||
|
|
73caa2508e | ||
|
|
69e012a6f0 | ||
|
|
21251a1915 | ||
|
|
67143ba2d5 | ||
|
|
d399cba4c0 | ||
|
|
9cad7cd025 | ||
|
|
a593842265 | ||
|
|
2f4eb595f6 | ||
|
|
bfcf349ffe | ||
|
|
2bd78cd47f | ||
|
|
713615e28b | ||
|
|
2b2f17525e | ||
|
|
cd6233a3d7 | ||
|
|
f76224bd17 | ||
|
|
1516807ed5 | ||
|
|
8afa373726 | ||
|
|
199f2202e0 | ||
|
|
86a6311f75 | ||
|
|
9893fd9ae5 | ||
|
|
d6c28da3a8 | ||
|
|
52f694a714 | ||
|
|
0ca4e6ca4f | ||
|
|
50bce4892f | ||
|
|
ca345cf008 | ||
|
|
e0e1290fae | ||
|
|
a84b2611e4 | ||
|
|
ec31fab344 | ||
|
|
1b96eee4de | ||
|
|
a6af5de3e1 | ||
|
|
52b3068330 | ||
|
|
0934c08dfe | ||
|
|
f0428fde66 | ||
|
|
b3f57a67c4 | ||
|
|
8bc2b1262b | ||
|
|
fb24efd3de | ||
|
|
ce594b0b5a | ||
|
|
7b39ab4ec4 | ||
|
|
f717ed9f66 | ||
|
|
d3091a5384 | ||
|
|
2a6d950a4b | ||
|
|
b03b9d5334 | ||
|
|
c9cb31bd02 | ||
|
|
905d6860fc | ||
|
|
6f52744b0f | ||
|
|
007d3e52c5 | ||
|
|
f10fa632ca | ||
|
|
266323b90b | ||
|
|
60707a8f45 | ||
|
|
b89896a4e7 | ||
|
|
d69fd12fb9 | ||
|
|
0bdcfa6028 | ||
|
|
55b8488901 | ||
|
|
8ddbf2067b | ||
|
|
fa5cebda6d | ||
|
|
4fd01b4234 | ||
|
|
0f354f4f06 | ||
|
|
f94a197828 | ||
|
|
a26ff660b0 | ||
|
|
50cc648799 | ||
|
|
a7946805ae | ||
|
|
f3b2969b42 | ||
|
|
d6c3490165 | ||
|
|
6ee594d4d1 | ||
|
|
7e1596de30 | ||
|
|
67bba043ed | ||
|
|
26d7f4923d | ||
|
|
e9218d1088 | ||
|
|
03fd1e29e3 | ||
|
|
ff59af6b51 | ||
|
|
73ba8b8b13 | ||
|
|
ffc3fb770c | ||
|
|
d58ea70a95 | ||
|
|
90e8dd038d | ||
|
|
56d1e3edaa | ||
|
|
83a9e54896 | ||
|
|
ab038b1f31 | ||
|
|
f5a9d3928c | ||
|
|
2bc0bce1b5 | ||
|
|
372933fd99 | ||
|
|
9112347e95 | ||
|
|
30548a68e4 | ||
|
|
ce052d1691 | ||
|
|
765ba8c867 | ||
|
|
a20c0cd49e | ||
|
|
e06ca9a056 | ||
|
|
d04048c749 | ||
|
|
2d0f7589ea | ||
|
|
dc60be404a | ||
|
|
9ff5cc51f9 | ||
|
|
e9de8f42e5 | ||
|
|
5bd0499ae4 | ||
|
|
99e706bcd2 | ||
|
|
239edb0605 | ||
|
|
bf3f5a5971 | ||
|
|
92be3f32d6 | ||
|
|
106f5a53ff | ||
|
|
8c43f3d567 | ||
|
|
2e09501c8a | ||
|
|
a2592e48c8 | ||
|
|
291d76674b | ||
|
|
78957cf128 | ||
|
|
e88694b049 | ||
|
|
5e7bdf7354 | ||
|
|
2e3e0bc1d8 | ||
|
|
b33e6b232c | ||
|
|
df6b083670 | ||
|
|
05009d40c4 | ||
|
|
ea27a3b449 | ||
|
|
2831b0bd2a | ||
|
|
32e22dd507 | ||
|
|
2ee9951853 | ||
|
|
b1912135ed | ||
|
|
bed3d42923 | ||
|
|
c4ec69a43f | ||
|
|
24b004bb2d | ||
|
|
84925b24b5 | ||
|
|
c02b91dfd4 | ||
|
|
066d97220f | ||
|
|
61ed47dda0 | ||
|
|
68c0f210cc | ||
|
|
6c542750f4 | ||
|
|
25440a26ee | ||
|
|
ab393b1f6d | ||
|
|
e643147b69 | ||
|
|
6c4aa71cbc | ||
|
|
9930ba8748 | ||
|
|
fbb8b4687b | ||
|
|
01b1c49738 | ||
|
|
c9c28eda1b | ||
|
|
744bcba599 | ||
|
|
d76db726c4 | ||
|
|
71ec528a87 | ||
|
|
fbbc93900e | ||
|
|
33b45737c9 | ||
|
|
4a55f78a48 | ||
|
|
a82a79e25c | ||
|
|
6ec2124a9c | ||
|
|
1f1ef1440e | ||
|
|
a7d0a4bdac | ||
|
|
d2129ffac6 | ||
|
|
a4782f0663 | ||
|
|
16794b9d78 | ||
|
|
a76aed2d4e | ||
|
|
d2163dacf9 | ||
|
|
158305346f | ||
|
|
e692432242 | ||
|
|
a7b85b123e | ||
|
|
ddcd722598 | ||
|
|
358458a937 | ||
|
|
8925f7c381 | ||
|
|
ff2e39901a | ||
|
|
90ff0f43ea | ||
|
|
442c352c8d | ||
|
|
e91b7fb082 | ||
|
|
88de66a31f | ||
|
|
d3b3e45800 | ||
|
|
50d2f90621 | ||
|
|
8ccf6cb8a3 | ||
|
|
2e9b478824 | ||
|
|
89b2d54725 | ||
|
|
3d0af2d8ca | ||
|
|
813b433f4d | ||
|
|
0bce96b0c6 | ||
|
|
1d4f1764fc | ||
|
|
2994420160 | ||
|
|
65d8d7282f | ||
|
|
47af3f09fc | ||
|
|
f4024f4683 | ||
|
|
ee0ed6df7a | ||
|
|
d3fbba3572 | ||
|
|
4a6b22f5b7 | ||
|
|
d070305002 | ||
|
|
a8500150b0 | ||
|
|
02fb1d01ad | ||
|
|
497dee038f | ||
|
|
a4af77f91e | ||
|
|
f2a4e1d230 | ||
|
|
8560901f80 | ||
|
|
daea604c60 | ||
|
|
7aedb59f26 | ||
|
|
0be1c2f464 | ||
|
|
0dbfaf0e79 | ||
|
|
4147399cda | ||
|
|
cc667ac738 | ||
|
|
022915ffc9 | ||
|
|
4b9cf775ff | ||
|
|
56879924bd | ||
|
|
6142f2d641 | ||
|
|
0d53f799b7 | ||
|
|
65e77e9669 | ||
|
|
2c8f3b56ae | ||
|
|
8ab190affc | ||
|
|
eafccc4fc4 | ||
|
|
ce440351a5 | ||
|
|
be94edde0f | ||
|
|
4fcc9af933 | ||
|
|
e2b4ac6ee8 | ||
|
|
c151049cc2 | ||
|
|
ac778d3f65 | ||
|
|
1aed0cb4b9 | ||
|
|
5836b65aad | ||
|
|
46f750efba | ||
|
|
b33c9e23ce | ||
|
|
14694f1cb0 | ||
|
|
75575348cd | ||
|
|
f6ad0a235c | ||
|
|
bbf6c60888 | ||
|
|
f5915f3e10 | ||
|
|
a32cfc8aff | ||
|
|
c90a878c9e | ||
|
|
b46b958105 | ||
|
|
6943b06a88 | ||
|
|
27a9def88c | ||
|
|
11f8cfe0e6 | ||
|
|
e1e3cc7999 | ||
|
|
254c8816f1 | ||
|
|
9a445e34fd | ||
|
|
ee78e113de | ||
|
|
0dfb14962a | ||
|
|
d493df4295 | ||
|
|
f0144233f9 | ||
|
|
90f21f4ed1 | ||
|
|
646ebe592e | ||
|
|
b098a15e9c | ||
|
|
4f98995fe4 | ||
|
|
56231edc3a | ||
|
|
871ab428c2 | ||
|
|
a9b75f752e | ||
|
|
9590559b81 | ||
|
|
24bd2eddf1 | ||
|
|
f4ba06c401 | ||
|
|
cd405d1df9 | ||
|
|
9d6dbc1a6f | ||
|
|
6d57712fca | ||
|
|
191f2cacbf | ||
|
|
080448af3a | ||
|
|
6e2272d043 | ||
|
|
71078dea4f | ||
|
|
333f0be879 | ||
|
|
3b0f664a3b | ||
|
|
2a23d19321 | ||
|
|
5ee4237510 | ||
|
|
bdb906c26d | ||
|
|
02095ac155 | ||
|
|
80d1ca81ac | ||
|
|
3bbabbc80b | ||
|
|
f8ff3d4bf5 | ||
|
|
3a40f9ebd6 | ||
|
|
cf776088e6 | ||
|
|
7ab81608e8 | ||
|
|
017e40fcb9 | ||
|
|
29309fbaa3 | ||
|
|
b766b08bf5 | ||
|
|
e796e00963 | ||
|
|
10136df977 | ||
|
|
44ec107ce8 | ||
|
|
c7fb5b0475 | ||
|
|
785735ccf5 | ||
|
|
f0736ccf8d | ||
|
|
a24a7e03be | ||
|
|
be235e5204 | ||
|
|
57ec598672 | ||
|
|
2627c09cda | ||
|
|
36e63bb8a9 | ||
|
|
5ea24f650b | ||
|
|
8c792ce7a1 | ||
|
|
055969f5c6 | ||
|
|
145ae10a79 | ||
|
|
bdb9349b52 | ||
|
|
8b11b57ec5 | ||
|
|
29888c89ad | ||
|
|
281fb2afd3 | ||
|
|
fbb7839f83 | ||
|
|
d3f9c170ac | ||
|
|
4f9a0b0040 | ||
|
|
aae584106a | ||
|
|
2a784deb4b | ||
|
|
5f5a7880a6 | ||
|
|
ab8b6d806d | ||
|
|
6c4206a2dd | ||
|
|
0d61d9cee4 | ||
|
|
48bdb9e818 | ||
|
|
cfe447a403 | ||
|
|
2c30f0e487 | ||
|
|
147211478c | ||
|
|
35b7674e69 | ||
|
|
55075b2a96 | ||
|
|
6577599959 | ||
|
|
0c4e72e507 | ||
|
|
9bf96e943d | ||
|
|
2811843e70 | ||
|
|
842ece2a8a | ||
|
|
222de03ea3 | ||
|
|
d31ae9f3fc | ||
|
|
1dd7644e12 | ||
|
|
2cee54f70a | ||
|
|
a8431fae96 | ||
|
|
03d11b7b58 | ||
|
|
316e2eeefb | ||
|
|
c21e19337a | ||
|
|
b9cab0dae8 | ||
|
|
33b3299ca2 | ||
|
|
00ba38beba | ||
|
|
433fce286e | ||
|
|
b36322bba4 | ||
|
|
c147e0a789 | ||
|
|
8bf5d02624 | ||
|
|
35616c1ccd | ||
|
|
7c8939ecb8 | ||
|
|
ccb0302d3f | ||
|
|
0cfd048013 | ||
|
|
f72b4f0249 | ||
|
|
b5cb209f14 | ||
|
|
1af374439d | ||
|
|
bbcd674516 | ||
|
|
3cba71b7a8 | ||
|
|
6a1e9c5818 | ||
|
|
ede41d01b4 | ||
|
|
826a67b550 | ||
|
|
f3d59a9b61 | ||
|
|
65434453b8 | ||
|
|
b8658abbad | ||
|
|
0387b33f6c | ||
|
|
41042fe64e | ||
|
|
1efc3bb15d | ||
|
|
0816a9410b | ||
|
|
cb823eaaa6 | ||
|
|
44f027bf18 | ||
|
|
571d159a1f | ||
|
|
a30d977727 | ||
|
|
847ad2d781 | ||
|
|
da9067a672 | ||
|
|
5ed5950ed5 | ||
|
|
f53c182cfb | ||
|
|
bb2978b370 | ||
|
|
66ae4a5163 | ||
|
|
f435595ad8 | ||
|
|
4bec4596fe | ||
|
|
10b86aec49 | ||
|
|
ecabd37cbb | ||
|
|
f3cabfcb4d | ||
|
|
46fc010cf9 | ||
|
|
e370c76521 | ||
|
|
1c283f88d0 | ||
|
|
cfffa1b10a | ||
|
|
8aaae4de49 | ||
|
|
4f6c35713e | ||
|
|
8fb0b5b572 | ||
|
|
12262671ee | ||
|
|
3165c62985 | ||
|
|
1ca1166f74 | ||
|
|
7b4d2f3b97 | ||
|
|
ccce75a047 | ||
|
|
410e82e375 | ||
|
|
bd385ec062 | ||
|
|
638227ef25 | ||
|
|
e70b35126b | ||
|
|
4d41c7f37f | ||
|
|
d01a7b16a1 | ||
|
|
326d873279 | ||
|
|
74c32bed29 | ||
|
|
a222fd0786 | ||
|
|
c633ba9497 | ||
|
|
40b5472866 | ||
|
|
a413e273ca | ||
|
|
0f82085cae | ||
|
|
fa1ab04409 | ||
|
|
f370508c93 | ||
|
|
7472019422 | ||
|
|
aa3597881a | ||
|
|
a36841e501 | ||
|
|
fe9afc8952 | ||
|
|
63d39a81aa | ||
|
|
b11dc2ca20 | ||
|
|
10f5ad6127 | ||
|
|
4800a88cf6 | ||
|
|
9d8bc40594 | ||
|
|
7d3d96a6e0 | ||
|
|
4c99429d76 | ||
|
|
96fbd7572c | ||
|
|
b012bba6d6 | ||
|
|
45d90b2530 | ||
|
|
a8b946a07a | ||
|
|
18392c4f23 | ||
|
|
88e7869284 | ||
|
|
3c8332c448 | ||
|
|
207f95693a | ||
|
|
de15120bf8 | ||
|
|
1dcf11e1c4 | ||
|
|
b74ba22c44 | ||
|
|
fa2d34dcfc | ||
|
|
0280a5f09e | ||
|
|
a35f876f4f | ||
|
|
c22b7f81c1 | ||
|
|
9344fd78d8 | ||
|
|
e89c0a3e61 | ||
|
|
5aa7ef5738 | ||
|
|
88ac6c6fc6 | ||
|
|
a537f9762b | ||
|
|
aab192223a | ||
|
|
6439810d03 | ||
|
|
8ae7d96cc7 | ||
|
|
c5c78763d1 | ||
|
|
012908e32d | ||
|
|
1f1e545538 | ||
|
|
5b8519b2b8 | ||
|
|
6502158716 | ||
|
|
7c525ab7a6 | ||
|
|
778e3d9799 | ||
|
|
e5f986a959 | ||
|
|
d76ecd5423 | ||
|
|
6bcb6398f8 | ||
|
|
6c341d8fa5 | ||
|
|
0d5e57eeab | ||
|
|
aef603ed8c | ||
|
|
97600e526b | ||
|
|
f3f6095d81 | ||
|
|
fb7280127c | ||
|
|
60df509fc6 | ||
|
|
ced237fbda | ||
|
|
9473a26892 | ||
|
|
e5825e898d | ||
|
|
8d59bd9f71 | ||
|
|
8f90bb83e7 | ||
|
|
ba888a7165 | ||
|
|
9591a7cac2 | ||
|
|
e8fd53d8b7 | ||
|
|
3b7c36b1c9 | ||
|
|
2750a3ef30 | ||
|
|
7d59f51244 | ||
|
|
a3ec6f470a | ||
|
|
8e85a33dac | ||
|
|
77542597f5 | ||
|
|
2a26e8a010 | ||
|
|
8b4c1a6c38 | ||
|
|
7d7e277cc6 | ||
|
|
cb1da609a4 | ||
|
|
e4fbdb35a3 | ||
|
|
c3586372e0 | ||
|
|
808d193543 | ||
|
|
0b65b70af2 | ||
|
|
deab34abd6 | ||
|
|
646cded650 | ||
|
|
9047320301 | ||
|
|
f16b054f01 | ||
|
|
fea856202d | ||
|
|
4f15cc3f08 | ||
|
|
1769dd959e | ||
|
|
1253b81a01 | ||
|
|
3b524f6aba | ||
|
|
051ccad29a | ||
|
|
c82eba05d1 | ||
|
|
74af199afc | ||
|
|
c2afdba659 | ||
|
|
e6ae45f133 | ||
|
|
129ef6766b | ||
|
|
3194a7b1a2 | ||
|
|
46a733ba5b | ||
|
|
466844fc55 | ||
|
|
0cc793e3fe | ||
|
|
0fdfb385a4 | ||
|
|
419a4c6b2d | ||
|
|
55e9441547 | ||
|
|
7abff6ded4 | ||
|
|
a648d310a5 | ||
|
|
4b56fa56b5 | ||
|
|
18bf700936 | ||
|
|
67ddff736c | ||
|
|
1bba125a2a | ||
|
|
187acad592 | ||
|
|
5e07c7b3e1 | ||
|
|
cd942cf8b6 | ||
|
|
c43589fe6a | ||
|
|
d4594eff3b | ||
|
|
809a0e846b | ||
|
|
7f00ce2598 | ||
|
|
d7d77dbfe9 | ||
|
|
7a124c74cc | ||
|
|
f8549f4643 | ||
|
|
0476ff70da | ||
|
|
5ec541c3c1 | ||
|
|
7b920348f3 | ||
|
|
51b1ef41a1 | ||
|
|
12447effc9 | ||
|
|
dc1b059a9d | ||
|
|
a676ebf46c | ||
|
|
338f9fb5a9 | ||
|
|
98c9132d4a | ||
|
|
a054f12492 | ||
|
|
c42ac1df1b | ||
|
|
d9d46cda1c | ||
|
|
f678a17505 | ||
|
|
3da4bb69ce | ||
|
|
3e2b876c59 | ||
|
|
10daf2599d | ||
|
|
56d6eb7077 | ||
|
|
9b54272f8e | ||
|
|
685e003db8 | ||
|
|
701fc0bc80 | ||
|
|
e5518ac0fa | ||
|
|
8e1bf48cd1 | ||
|
|
8dd82e1a3b | ||
|
|
4418bfe965 | ||
|
|
39c4d710bc | ||
|
|
51a8c47afd | ||
|
|
922570bb5c | ||
|
|
ca282d5635 | ||
|
|
ff67043210 | ||
|
|
31da231c1c | ||
|
|
eb698a7430 | ||
|
|
03be809ba9 | ||
|
|
69601bf15a | ||
|
|
7cad3d403b | ||
|
|
cc84af3346 | ||
|
|
33ef54a162 | ||
|
|
ac43ff886a | ||
|
|
a64f73ca0c | ||
|
|
314477d2fc | ||
|
|
c63fc93daa | ||
|
|
f04ad1e702 | ||
|
|
2183c4bda6 | ||
|
|
6ba91c1515 | ||
|
|
dc03bb76e6 | ||
|
|
d0559c16b5 | ||
|
|
eb693f7b48 | ||
|
|
ef5639ff4b | ||
|
|
34a335797c | ||
|
|
edda3a4d23 | ||
|
|
184839423f | ||
|
|
c0f3600a52 | ||
|
|
2b1302aa07 | ||
|
|
d894bb4aab | ||
|
|
470c071344 | ||
|
|
4bd639c6c4 | ||
|
|
4cb7e63421 | ||
|
|
9f14a503d8 | ||
|
|
d5da6de86c | ||
|
|
4c2b233722 | ||
|
|
ca5b1eea13 | ||
|
|
614e9b6d55 | ||
|
|
27b2530b8d | ||
|
|
2259167200 | ||
|
|
2e05214828 | ||
|
|
cfb996039b | ||
|
|
44c4d56214 | ||
|
|
8e6be91f7c | ||
|
|
00816fb2c8 | ||
|
|
535356b77f | ||
|
|
5e558746ce | ||
|
|
f7bd52ac0c | ||
|
|
9165f518a9 | ||
|
|
01605aa221 | ||
|
|
8b0b29c424 | ||
|
|
7a116966fa | ||
|
|
e7e8f11a74 | ||
|
|
f235d832d5 | ||
|
|
7730b5e20b | ||
|
|
8c3ba4ce48 | ||
|
|
e9a126f586 | ||
|
|
097e7d2ff2 | ||
|
|
81265f1238 | ||
|
|
2b507e6e20 | ||
|
|
747d3a8f13 | ||
|
|
30f6f07434 | ||
|
|
6de5488a15 | ||
|
|
5413647166 | ||
|
|
e83fe73b18 | ||
|
|
87a289ec65 | ||
|
|
8a0a118dba | ||
|
|
687126ce87 | ||
|
|
8a05d577da | ||
|
|
4c3ebfc0f8 | ||
|
|
6093f25f9a | ||
|
|
ecab68d676 | ||
|
|
1cb4f37c95 | ||
|
|
14318528b9 | ||
|
|
9c0e1f8f1a | ||
|
|
2034ce9e4d | ||
|
|
657489caf6 | ||
|
|
94be3d1fe5 | ||
|
|
f6eae41cee | ||
|
|
69c64434e3 | ||
|
|
256cabfce1 | ||
|
|
e8b8272cf9 | ||
|
|
bd5ab4881c | ||
|
|
9630744bdb | ||
|
|
ab3ad0eb97 | ||
|
|
2393889028 | ||
|
|
c36ecb1ed1 | ||
|
|
3e9b28ff0c | ||
|
|
d4eec461a9 | ||
|
|
b584f37087 | ||
|
|
5c75644db2 | ||
|
|
72d9e3e00b | ||
|
|
b3d3362f34 | ||
|
|
1cbaf55cee | ||
|
|
7771875d57 | ||
|
|
85937d8e23 | ||
|
|
1480986a3a | ||
|
|
bc968736df | ||
|
|
ad80b8e8b6 | ||
|
|
d44d220c55 | ||
|
|
f6bcef0789 | ||
|
|
a28ec752e8 | ||
|
|
48ea5746d9 | ||
|
|
f473d31970 | ||
|
|
9cd1d4b466 | ||
|
|
4f02065eaf | ||
|
|
4b85e3e8fb | ||
|
|
e4e8c2205e | ||
|
|
18649dd074 | ||
|
|
9f9463f0e8 | ||
|
|
6cf9bc5de2 | ||
|
|
297b4346c5 | ||
|
|
767a203439 | ||
|
|
c564c253b1 | ||
|
|
b4e138e21b | ||
|
|
8ca01921c4 | ||
|
|
c8b97ffde3 | ||
|
|
abc84e5710 | ||
|
|
d732d195f3 | ||
|
|
789975abb0 | ||
|
|
ed1eab7fcc | ||
|
|
29b31c114a | ||
|
|
c8cf353c21 | ||
|
|
e82e2c71f1 | ||
|
|
dd729c406f | ||
|
|
3127e85900 | ||
|
|
304eeb3158 | ||
|
|
bf781c6b50 | ||
|
|
da1098e441 | ||
|
|
85065357e2 | ||
|
|
1f5f6c3b0e | ||
|
|
4f7026969f | ||
|
|
16d264ebfa | ||
|
|
04ffbe945b | ||
|
|
974a1a1e7d | ||
|
|
ca2c07244f | ||
|
|
f90786d1c0 | ||
|
|
bdf55568c7 | ||
|
|
e262d2f19b | ||
|
|
aabfe820ac | ||
|
|
3bba5442bd | ||
|
|
6ce1922fb3 | ||
|
|
9367a404ef | ||
|
|
7c8e19c681 | ||
|
|
7ccc5eb9b8 | ||
|
|
b4b6d3d07c | ||
|
|
9007bac7b2 | ||
|
|
df13ca3c92 | ||
|
|
d86935acaa | ||
|
|
72b450d526 | ||
|
|
4b7afeeb4f | ||
|
|
0e0e779cbe | ||
|
|
89b2f48a06 | ||
|
|
c80bdb8d0c | ||
|
|
50d89a8ec9 | ||
|
|
f5ccaa7b48 | ||
|
|
e682ee8541 | ||
|
|
caaa7a9e74 | ||
|
|
6ef0e325e2 | ||
|
|
ea064deeb8 | ||
|
|
8e81609a39 | ||
|
|
83da07a941 | ||
|
|
977e80c829 | ||
|
|
8ba0d10f40 | ||
|
|
2581091652 | ||
|
|
e72a7ceaea | ||
|
|
a17ddf6d54 | ||
|
|
b5e2d21f33 | ||
|
|
d09f8dff18 | ||
|
|
bdb3406dcb | ||
|
|
f54b49db1a | ||
|
|
0cc9f006c5 | ||
|
|
db1398b65f | ||
|
|
e8192e6c3f | ||
|
|
775200bdd6 | ||
|
|
820a2a093c | ||
|
|
d3995b9b10 | ||
|
|
f6867f9338 | ||
|
|
3e2548fcd5 | ||
|
|
745d250787 | ||
|
|
b1063eb38f | ||
|
|
9032a1debb | ||
|
|
795fe8ae1d | ||
|
|
e5a908af68 | ||
|
|
6ce16c1cc0 | ||
|
|
6ff2a5ac94 | ||
|
|
fcea16e43a | ||
|
|
7b8e42382e | ||
|
|
a372b5ea39 | ||
|
|
1aafee2a7c | ||
|
|
7afe3d5181 | ||
|
|
6fba62d062 | ||
|
|
6906c0ab0d | ||
|
|
5d46adf8fd | ||
|
|
eda1f11d42 | ||
|
|
6431a8255d | ||
|
|
48fd1d11e2 | ||
|
|
4c3e62efad | ||
|
|
52a15a5d92 | ||
|
|
57f4aa5995 | ||
|
|
ab9ab004b7 | ||
|
|
ac624eb98f | ||
|
|
1a4c37820d | ||
|
|
685206950b | ||
|
|
eececf8a93 | ||
|
|
9bc3d65554 | ||
|
|
f9b854ce39 | ||
|
|
1416968fe5 | ||
|
|
efc183c709 | ||
|
|
07a2442718 | ||
|
|
f549c50a58 | ||
|
|
8d6ce1a2f7 | ||
|
|
c5b33e025e | ||
|
|
4cdfc738c0 | ||
|
|
46d46f21e4 | ||
|
|
4983605b23 | ||
|
|
9e8d04d806 | ||
|
|
042f935133 | ||
|
|
ed9d3639e2 | ||
|
|
728f105830 | ||
|
|
6f359fa6a8 | ||
|
|
57a88743bc | ||
|
|
667f022086 | ||
|
|
b742a3a4cd | ||
|
|
8d5c9742f8 | ||
|
|
c2a49efe73 | ||
|
|
8c8a0ab46d | ||
|
|
959b75bddd | ||
|
|
d29d5105f1 | ||
|
|
38e82872a5 | ||
|
|
15d9ce1d36 | ||
|
|
67f7cdb36c | ||
|
|
6a9d4ae0fd | ||
|
|
6a761c3fb5 | ||
|
|
3baf97e69f | ||
|
|
694dc60481 | ||
|
|
e3c6f0452c | ||
|
|
ed2401a87b | ||
|
|
cb59458c79 | ||
|
|
125a493400 | ||
|
|
83910b55d2 | ||
|
|
f4fd131100 | ||
|
|
cfdc880d8c | ||
|
|
7303e8bdd2 | ||
|
|
ecde465d9f | ||
|
|
5c5e70a805 | ||
|
|
4e41d9be55 | ||
|
|
d06d94449c | ||
|
|
1af2c83c63 | ||
|
|
6c75136777 | ||
|
|
31351e34e1 | ||
|
|
a058a774e9 | ||
|
|
e6db28485c | ||
|
|
391bb096d6 | ||
|
|
a7a5b789fa | ||
|
|
10b7d58dc6 | ||
|
|
2b496dc2e5 | ||
|
|
a6e0b30576 | ||
|
|
16f0e95e32 | ||
|
|
55395d3a2d | ||
|
|
4e0fa63fad | ||
|
|
b9ea7696d8 | ||
|
|
7de3308f52 | ||
|
|
b2b2373c7b | ||
|
|
1c54f40a28 | ||
|
|
1ac31264b7 | ||
|
|
ca4b8224fd | ||
|
|
d1e5781c24 | ||
|
|
c86e451198 | ||
|
|
4735992835 | ||
|
|
cba3519458 | ||
|
|
c64a5e1cca | ||
|
|
47ee8b8ce7 | ||
|
|
b4a7b547f0 | ||
|
|
455b424429 | ||
|
|
7d67ac3f12 | ||
|
|
34f377eb5c | ||
|
|
b7f4af8c78 | ||
|
|
07b395c24a | ||
|
|
a6c7dde194 | ||
|
|
43ebe4ecfd | ||
|
|
53b9630fa5 | ||
|
|
1d38f1abb4 | ||
|
|
061a0cd219 | ||
|
|
f0ed20ee2c | ||
|
|
edfc8d95c8 | ||
|
|
f820c3089e | ||
|
|
c33f9d8307 | ||
|
|
eee212f5b8 | ||
|
|
b690147b0b | ||
|
|
10879d0f67 | ||
|
|
b48b8f39fc | ||
|
|
3d0b3e759b | ||
|
|
3e7f4a41e2 | ||
|
|
b89b888129 | ||
|
|
cba9afc9ba | ||
|
|
a66a11b81e | ||
|
|
22cc2791b6 | ||
|
|
369cf16b68 | ||
|
|
0d38baf194 | ||
|
|
5b63c95f40 | ||
|
|
52497999a0 | ||
|
|
6bab108a35 | ||
|
|
eec22e6b7d | ||
|
|
edaa0713e8 | ||
|
|
6b6a415dd5 | ||
|
|
414e0dbc05 | ||
|
|
081a3da9e1 | ||
|
|
3fbc749395 | ||
|
|
626175b2b8 | ||
|
|
54db0c718a | ||
|
|
ae9175d1b1 | ||
|
|
16e1f0f882 | ||
|
|
179b830d14 | ||
|
|
aeb27f7bff | ||
|
|
0b2b89da0f | ||
|
|
804991b300 | ||
|
|
529f271e07 | ||
|
|
402d577ce0 | ||
|
|
4bb28a358e | ||
|
|
200a9275d2 | ||
|
|
9cb086cbd9 | ||
|
|
3a836aaf34 | ||
|
|
e14a6f2374 | ||
|
|
28794bff79 | ||
|
|
ecc9443c9a | ||
|
|
a1c499c026 | ||
|
|
4e8321268c | ||
|
|
8510325732 | ||
|
|
49102be894 | ||
|
|
1a97488dd7 | ||
|
|
b90c7e5318 | ||
|
|
ece1e37c16 | ||
|
|
3a6e3f5cae | ||
|
|
8e502eec80 | ||
|
|
042ebe8316 | ||
|
|
eee47b1f76 | ||
|
|
7978704f1b | ||
|
|
9392ac438c | ||
|
|
cbcfb57e35 | ||
|
|
460437397f | ||
|
|
29fa512a90 | ||
|
|
059d1fb4f2 | ||
|
|
4d3a538213 | ||
|
|
108a0db799 | ||
|
|
04f632570d | ||
|
|
92b919da6c | ||
|
|
4f0f611e52 | ||
|
|
90f7cf0996 | ||
|
|
ce43e80bdf | ||
|
|
c397752b7a | ||
|
|
2470364571 | ||
|
|
cab122ba8b | ||
|
|
56b01b5a85 | ||
|
|
cbcc9fb411 | ||
|
|
5bbc60d48a | ||
|
|
8fb8aab7b8 | ||
|
|
5b76216a64 | ||
|
|
7f5e372bdc | ||
|
|
01913a9a40 | ||
|
|
62f9fcf171 | ||
|
|
34c667f6b2 | ||
|
|
7a6de387e3 | ||
|
|
b6efbcedef | ||
|
|
3c70b2e5a0 | ||
|
|
62e108d460 | ||
|
|
b7f426b03f | ||
|
|
5ca13b70aa | ||
|
|
4644d107c6 | ||
|
|
06013b2a6a | ||
|
|
7380fc8500 | ||
|
|
81b550e66c | ||
|
|
f5972c273b | ||
|
|
c259ec2bed | ||
|
|
a36e779980 | ||
|
|
86a04b0dcb | ||
|
|
8b7c0c957c | ||
|
|
0cfe971a48 | ||
|
|
8884db858c | ||
|
|
0fe9dd4fbb | ||
|
|
ae57e99173 | ||
|
|
c238e1b5cb | ||
|
|
55ef998c55 | ||
|
|
166815ccbf | ||
|
|
34a16298d4 | ||
|
|
1613e72d47 | ||
|
|
e933ad89dc | ||
|
|
148fb25a15 | ||
|
|
fe0544dbc9 | ||
|
|
0aaea28e74 | ||
|
|
f4764afbf4 | ||
|
|
2a2b662a6f | ||
|
|
deade1f9f8 | ||
|
|
2b73c17cca | ||
|
|
369f8b6093 | ||
|
|
5391ca161d | ||
|
|
5b224d3b54 | ||
|
|
dad6a93944 | ||
|
|
10ec5c1342 | ||
|
|
8edfc15f45 | ||
|
|
792e41f161 | ||
|
|
9fbdf895af | ||
|
|
dbc0fedf43 | ||
|
|
cb298b8cad | ||
|
|
2459a80e15 | ||
|
|
0a973c4db3 | ||
|
|
a84fddd5fa | ||
|
|
1cd26d2e71 | ||
|
|
f88ac891ff | ||
|
|
17aa9ae85f | ||
|
|
bb46a9ba4c | ||
|
|
4a6c16df8d | ||
|
|
95787fafc6 | ||
|
|
c51d79e4d7 | ||
|
|
d5232a1b0e | ||
|
|
e093c7f982 | ||
|
|
3f53bddf7e | ||
|
|
e03d8175e3 | ||
|
|
7ab2e9f6ca | ||
|
|
6316ddc6a6 | ||
|
|
11c46ede5d | ||
|
|
2ec5238d97 | ||
|
|
b7f359f6cf | ||
|
|
8ef485221c | ||
|
|
afddb6fc33 | ||
|
|
f28ee1bc4b | ||
|
|
b997f0a5a7 | ||
|
|
48dc47ce57 | ||
|
|
84f18ced47 | ||
|
|
037ff2e749 | ||
|
|
f0f23ede3d | ||
|
|
c8763063c0 | ||
|
|
e57fef2413 | ||
|
|
f1a90f4a11 | ||
|
|
990d7edba4 | ||
|
|
13a1da91be | ||
|
|
930b58d2a8 | ||
|
|
b88b1065ee | ||
|
|
5fbb802b32 | ||
|
|
a40f8d25ef | ||
|
|
0a28798d54 | ||
|
|
6047987c25 | ||
|
|
2ce96186f2 | ||
|
|
26357bd4bc | ||
|
|
e94b45a7e4 | ||
|
|
4be1eb7a28 | ||
|
|
f4bbbb640d | ||
|
|
94b4e70b0f | ||
|
|
b31e3f7783 | ||
|
|
9095fe934d | ||
|
|
9139495f02 | ||
|
|
6375ac857a | ||
|
|
5eac08430f | ||
|
|
ef0b109ad4 | ||
|
|
7fbe6c0955 | ||
|
|
c86e43597c | ||
|
|
123a73a5e6 | ||
|
|
706b5d253f | ||
|
|
8fb8c7a40b | ||
|
|
21220f93b1 | ||
|
|
705e64377b | ||
|
|
5c3f0cd060 | ||
|
|
bcb1fb4331 | ||
|
|
a238be5b7c | ||
|
|
2351bb78da | ||
|
|
6bbc5a91fe | ||
|
|
70b69a3bc9 | ||
|
|
2f52233bd0 | ||
|
|
a7e458b784 | ||
|
|
a504a45d99 | ||
|
|
2d0e14c1cc | ||
|
|
92eccb635a | ||
|
|
1f8acc3afc | ||
|
|
f8eaa9e796 | ||
|
|
7c9fecd943 | ||
|
|
3df8cbb357 | ||
|
|
993cb9cb0b | ||
|
|
5bd4a3f761 | ||
|
|
fcad84ced3 | ||
|
|
0bd48981ec | ||
|
|
0bf7e8b705 | ||
|
|
875c451221 | ||
|
|
c8256bea3a | ||
|
|
7d10b951b7 | ||
|
|
34e9238cc4 | ||
|
|
7d97784a58 | ||
|
|
1265e7c4a1 | ||
|
|
44ece2bf34 | ||
|
|
fde2a8055d | ||
|
|
f9643c2503 | ||
|
|
e6a97e5cb3 | ||
|
|
73b5546ae9 | ||
|
|
f98719ee73 | ||
|
|
7666182ae3 | ||
|
|
05af30f336 | ||
|
|
20de08b625 | ||
|
|
e60f4f4a64 | ||
|
|
6af25d932c | ||
|
|
3f49a8a15a | ||
|
|
bfa8db7b55 | ||
|
|
d6f2e7588c | ||
|
|
a594332ffb | ||
|
|
ceb18ebf1c | ||
|
|
b9038e254e | ||
|
|
b351e42538 | ||
|
|
8910c26ee2 | ||
|
|
7549a7bbbe | ||
|
|
17fbe6e232 | ||
|
|
ccdac8f604 | ||
|
|
88a828c9ef | ||
|
|
ae3291b90e | ||
|
|
2c6f0452b8 | ||
|
|
4651acd6f4 | ||
|
|
bba7babce3 | ||
|
|
73dc6a4a92 | ||
|
|
992f5a525a | ||
|
|
b38d5789f3 | ||
|
|
47b5945e17 | ||
|
|
73544b0f06 | ||
|
|
e7d9311e23 | ||
|
|
c97c65b707 | ||
|
|
1c02b4e62a | ||
|
|
d23156d11a | ||
|
|
bd013adb4d | ||
|
|
c0368ce713 | ||
|
|
80283b5f55 | ||
|
|
4078645958 | ||
|
|
955ade0b8a | ||
|
|
4b158af9f6 | ||
|
|
642fae3ac7 | ||
|
|
d249967aee | ||
|
|
7b6b7f05e0 | ||
|
|
35b9bf5d34 | ||
|
|
59f0cc4f98 | ||
|
|
a29ca73fb4 | ||
|
|
59b658f059 | ||
|
|
3397b3108f | ||
|
|
cae7baa5e1 | ||
|
|
4af71fd1dd | ||
|
|
4194b61373 | ||
|
|
c91fd6783d | ||
|
|
89bbed1dfd | ||
|
|
2aeb53920c | ||
|
|
fe51c232b6 | ||
|
|
57b054794c | ||
|
|
8318c56046 | ||
|
|
0d52417ee7 | ||
|
|
6f3b1b8d6f | ||
|
|
a460d7722e | ||
|
|
d770208d4c | ||
|
|
0434109908 | ||
|
|
289d3a4e6b | ||
|
|
ffb9be63c7 | ||
|
|
bf2b53cbce | ||
|
|
1d9bf65c31 | ||
|
|
4744b918d3 | ||
|
|
588b1809a9 | ||
|
|
dc1c19293d | ||
|
|
1f548959e3 | ||
|
|
8cae5670fc | ||
|
|
07c0982d4f | ||
|
|
2f9e4b3198 | ||
|
|
89dba149a3 | ||
|
|
aa71b4c1b8 | ||
|
|
43110f8f2a | ||
|
|
e48540713d | ||
|
|
cfd13139e0 | ||
|
|
ac5cdf384f | ||
|
|
e9d858d902 | ||
|
|
1beae4403a | ||
|
|
dedf36f704 | ||
|
|
1477de3899 | ||
|
|
0d947c7dd8 | ||
|
|
ebfd8f40e3 | ||
|
|
3159cc0ded | ||
|
|
10dcbfb891 | ||
|
|
19dc16e14a | ||
|
|
95586b3156 | ||
|
|
0637daf645 | ||
|
|
fdcd62617d | ||
|
|
0f3e5ee4ed | ||
|
|
7b171ecc67 | ||
|
|
7a4052ede3 | ||
|
|
3f53a1f629 | ||
|
|
31daec5fe2 | ||
|
|
0d7155bda6 | ||
|
|
35beec3e39 | ||
|
|
ff4b96b622 | ||
|
|
9b60814292 | ||
|
|
3c4fa83161 | ||
|
|
e8564f6540 | ||
|
|
a22e97d4bd | ||
|
|
046e6af489 | ||
|
|
f805e8a688 | ||
|
|
2fddc32eb7 | ||
|
|
6018cd5d81 | ||
|
|
3533903be3 | ||
|
|
d867292f66 | ||
|
|
7691b662d6 | ||
|
|
86270dd856 | ||
|
|
012e2dde4f | ||
|
|
ad7a3c49f9 | ||
|
|
e8abd43c8a | ||
|
|
3192ce9d39 | ||
|
|
d09de09fef | ||
|
|
4689ddeb98 | ||
|
|
e300b33a4f | ||
|
|
0ca87ea407 | ||
|
|
2886da4f63 | ||
|
|
bf9ecb02e5 | ||
|
|
852617726c | ||
|
|
c2aa35104c | ||
|
|
95e237d4a3 | ||
|
|
59e5c547e9 | ||
|
|
06bd2b2b79 | ||
|
|
faede48217 | ||
|
|
ad0ac19d3d | ||
|
|
3154110de1 | ||
|
|
5248c05e61 | ||
|
|
8311030bec | ||
|
|
c429fc6b2c | ||
|
|
590aa9ab17 | ||
|
|
f9a7c2d457 | ||
|
|
b4506168fb | ||
|
|
f203ab3aaf | ||
|
|
c197dd0a4b | ||
|
|
457e596851 | ||
|
|
d274563b2c | ||
|
|
2003bea3cf | ||
|
|
f9b3284852 | ||
|
|
9bca133d88 | ||
|
|
03fc453608 | ||
|
|
3027cc81b3 | ||
|
|
2415fbf676 | ||
|
|
725c6a7ba9 | ||
|
|
c33da0cf8e | ||
|
|
d915d19425 | ||
|
|
f3370242bf | ||
|
|
0e312ba929 | ||
|
|
6440395197 | ||
|
|
5433abddaf | ||
|
|
0ccb465288 | ||
|
|
8fd4deb3eb | ||
|
|
fe8045c51d | ||
|
|
b890c59134 | ||
|
|
f39caeb967 | ||
|
|
7ab482184b | ||
|
|
78b12ae686 | ||
|
|
caa5deac4e | ||
|
|
af3083825e | ||
|
|
5255708ff2 | ||
|
|
9331f2034b | ||
|
|
fc6a5c22bf | ||
|
|
51c397d177 | ||
|
|
7c9596308e | ||
|
|
15dc424ade | ||
|
|
49243a8010 | ||
|
|
93e188d118 | ||
|
|
df3195fc1e | ||
|
|
da6b8c30a0 | ||
|
|
70468b6b7d | ||
|
|
2511512d94 | ||
|
|
4418617d3b | ||
|
|
6e480ba146 | ||
|
|
b4f5913a80 | ||
|
|
6a3062709c | ||
|
|
d66bc1faef | ||
|
|
bef7d45c3e | ||
|
|
bb9489a8d3 | ||
|
|
700eeb8f5a | ||
|
|
7e2f0049b6 | ||
|
|
b2388544d8 | ||
|
|
d772551c60 | ||
|
|
31dca6f06b | ||
|
|
83f68fe153 | ||
|
|
08a2ae0fd3 | ||
|
|
53d3f51c74 | ||
|
|
f7cdafb087 | ||
|
|
5b17808569 | ||
|
|
a7328e21f1 | ||
|
|
e64370e9a2 | ||
|
|
d5b37b2418 | ||
|
|
4da08d93fd | ||
|
|
c39e5c67f5 | ||
|
|
00d5cf13c9 | ||
|
|
1120bcfc0c | ||
|
|
8e506cb7c2 | ||
|
|
145b66d375 | ||
|
|
82e4a8bbc3 | ||
|
|
bca9bfb960 | ||
|
|
d8e19d9c17 | ||
|
|
95d74d1ca2 | ||
|
|
ebdd6d77f7 | ||
|
|
d56bcc4fdf | ||
|
|
4bb18cfc9a | ||
|
|
6f30692534 | ||
|
|
e249c1ec65 | ||
|
|
c2b4c77003 | ||
|
|
e64733827a | ||
|
|
ea81b0d414 | ||
|
|
000cf2a864 | ||
|
|
dc13b919b3 | ||
|
|
a0c8ec3171 | ||
|
|
80c13f7c4f | ||
|
|
1a6f3d808b | ||
|
|
ec8fac1199 | ||
|
|
98c93d3248 | ||
|
|
a053706c24 | ||
|
|
e1a75a13e9 | ||
|
|
ee6f4de183 | ||
|
|
475885b3ef | ||
|
|
2d2b2d4c6c | ||
|
|
4d00454539 | ||
|
|
bf590b5614 | ||
|
|
3ef33c065c | ||
|
|
8e89fb8b92 | ||
|
|
8e81cfcf89 | ||
|
|
515736262d | ||
|
|
bd266dc602 | ||
|
|
9b3306157c | ||
|
|
f8e6a939ca | ||
|
|
8c48ee6fc1 | ||
|
|
5e476054d7 | ||
|
|
2fc8547384 | ||
|
|
2af2d71540 | ||
|
|
6aaf9d9eb2 | ||
|
|
42a9caf5a3 | ||
|
|
e6c1d7a383 | ||
|
|
02100bbc0a | ||
|
|
ce7c5f5d40 | ||
|
|
69f1ad6eb3 | ||
|
|
8320fb5024 | ||
|
|
4b79bca6bf | ||
|
|
4a5fd41249 | ||
|
|
4e90a93b30 | ||
|
|
9861fbf7c8 | ||
|
|
c762b9ae00 | ||
|
|
cc667a6edf | ||
|
|
419c57ed3f | ||
|
|
601f0b0de8 | ||
|
|
7b1c6c10b7 | ||
|
|
5a85c257cf | ||
|
|
beceb851c2 | ||
|
|
31485d3387 | ||
|
|
fc552e030a | ||
|
|
bf4c9f920a | ||
|
|
4ebd503664 | ||
|
|
0907bc80ef | ||
|
|
7a4258bb20 | ||
|
|
2cf46a3332 | ||
|
|
41868f28e6 | ||
|
|
964b7b62de | ||
|
|
0d34a03fe0 | ||
|
|
a62faa471c | ||
|
|
66f3ce2cb2 | ||
|
|
43c49f54d2 | ||
|
|
a15dfffa44 | ||
|
|
59985dee72 | ||
|
|
6b7132f134 | ||
|
|
e313b5e59d | ||
|
|
bb26d9a0a8 | ||
|
|
5c2c99282d | ||
|
|
94e6f89d07 | ||
|
|
3804a746df | ||
|
|
5c2d7e2d2a | ||
|
|
c34dd462b6 | ||
|
|
9141b1a641 | ||
|
|
0fea85e2f2 | ||
|
|
0ca41fbdb4 | ||
|
|
a58c191ded | ||
|
|
77089a1178 | ||
|
|
2cfb883bad | ||
|
|
ec8c8bb669 | ||
|
|
0b54f01107 | ||
|
|
67be198bee | ||
|
|
32a4a1aae1 | ||
|
|
dac7372839 | ||
|
|
521c261a37 | ||
|
|
27367488c2 | ||
|
|
0d5c3b1be6 | ||
|
|
a67d5ffacb | ||
|
|
687440a7c7 | ||
|
|
bafdc24a6d | ||
|
|
047f9c93c5 | ||
|
|
116fafc117 | ||
|
|
060c92091c | ||
|
|
5802525b73 | ||
|
|
c3580caabc | ||
|
|
08c027acc5 | ||
|
|
4468792346 | ||
|
|
1b16c68cf9 | ||
|
|
b99c1e3b32 | ||
|
|
eb2994e3c2 | ||
|
|
d88dd26186 | ||
|
|
59fcc58e9c | ||
|
|
acba61f36a | ||
|
|
a3a55a8bb4 | ||
|
|
22929d84fc | ||
|
|
9ea9d30947 | ||
|
|
7f08428fe2 | ||
|
|
0d80a7d961 | ||
|
|
2899264b54 | ||
|
|
923de0aa0d | ||
|
|
2b729dad15 | ||
|
|
b9b5bae78a | ||
|
|
a9acde07d1 | ||
|
|
a46b8d3079 | ||
|
|
8b92e2cbb7 | ||
|
|
881f5a5110 | ||
|
|
4e986a6384 | ||
|
|
ce5e1babb7 | ||
|
|
b85790d2fa | ||
|
|
6bc3e7fcf1 | ||
|
|
1ca968201d | ||
|
|
f2a03e4cc7 | ||
|
|
a752730718 | ||
|
|
9d20fd91ec | ||
|
|
70a6a3acb8 | ||
|
|
7f52eed4d5 | ||
|
|
105119e1a4 | ||
|
|
169e30e029 | ||
|
|
a5fa3e9e7a | ||
|
|
8985062d34 | ||
|
|
56eb9c76ae | ||
|
|
e5b6762bf3 | ||
|
|
e8bccaef88 | ||
|
|
afdb038244 | ||
|
|
56942d55eb | ||
|
|
9d742c8435 | ||
|
|
6ee4e48de2 | ||
|
|
184f3dc04b | ||
|
|
2027f60014 | ||
|
|
076edd375f | ||
|
|
ab1aa56059 | ||
|
|
46f7dfdfeb | ||
|
|
fcaa5e21cf | ||
|
|
1e202db50f | ||
|
|
9405b95825 | ||
|
|
f05e256afc | ||
|
|
731ffd4a22 | ||
|
|
8f4566b7e1 | ||
|
|
95aec54f60 | ||
|
|
f14ce0d68e | ||
|
|
cc1c7f3820 | ||
|
|
2df901288a | ||
|
|
821a7c780e | ||
|
|
6e2e48fa64 | ||
|
|
2864ac88f5 | ||
|
|
4a292d6518 | ||
|
|
e934182e86 | ||
|
|
d8fa73287b | ||
|
|
35938c09e8 | ||
|
|
9eaa90c691 | ||
|
|
049835d426 | ||
|
|
af91c40406 | ||
|
|
4940ad6825 | ||
|
|
d02b740300 | ||
|
|
9cb443dc2f | ||
|
|
1c7cba2951 | ||
|
|
473b80710d | ||
|
|
2247c0835d | ||
|
|
b7b715ba3d | ||
|
|
6c43fb2325 | ||
|
|
a6fe3c27d4 | ||
|
|
d47ff96b13 | ||
|
|
a0def654bd | ||
|
|
4873b40e49 | ||
|
|
0a758f20a7 | ||
|
|
5e58d457a3 | ||
|
|
0f745361ad | ||
|
|
bf6cae9a0e | ||
|
|
ab640a7676 | ||
|
|
820171e19e | ||
|
|
f1e9d0ab81 | ||
|
|
0646484c83 | ||
|
|
a27b79c213 | ||
|
|
773a9b4b7f | ||
|
|
07b838ef7b | ||
|
|
85217a7171 | ||
|
|
886d7b7227 | ||
|
|
6987b762dd | ||
|
|
f32ac81f84 | ||
|
|
87ea66bb92 | ||
|
|
ff6fd62932 | ||
|
|
76728448ff | ||
|
|
3b7225e0fa | ||
|
|
d6280f4397 | ||
|
|
8df867046f | ||
|
|
331c822816 | ||
|
|
6219173945 | ||
|
|
6207e02e7f | ||
|
|
537ba537dc | ||
|
|
3e919241e6 | ||
|
|
2324327e7e | ||
|
|
b8374494ea | ||
|
|
a480ca7b55 | ||
|
|
f39b7594ab | ||
|
|
f79734391e | ||
|
|
e54f516418 | ||
|
|
b6304a04e6 |
2
.babelrc
2
.babelrc
@@ -5,7 +5,7 @@
|
|||||||
"presets": ["react-hmre"]
|
"presets": ["react-hmre"]
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"presets": ["react", "es2015"],
|
"presets": ["env" ,"react", "es2015"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
[ "babel-plugin-webpack-alias", { "config": "${PWD}/webpack.config.js" } ]
|
[ "babel-plugin-webpack-alias", { "config": "${PWD}/webpack.config.js" } ]
|
||||||
]
|
]
|
||||||
|
|||||||
33
.boostnoterc.sample
Normal file
33
.boostnoterc.sample
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"amaEnabled": true,
|
||||||
|
"editor": {
|
||||||
|
"fontFamily": "Monaco, Consolas",
|
||||||
|
"fontSize": "14",
|
||||||
|
"indentSize": "2",
|
||||||
|
"indentType": "space",
|
||||||
|
"keyMap": "vim",
|
||||||
|
"switchPreview": "BLUR",
|
||||||
|
"theme": "monokai"
|
||||||
|
},
|
||||||
|
"hotkey": {
|
||||||
|
"toggleMain": "Cmd + Alt + L"
|
||||||
|
},
|
||||||
|
"isSideNavFolded": false,
|
||||||
|
"listStyle": "DEFAULT",
|
||||||
|
"listWidth": 174,
|
||||||
|
"navWidth": 200,
|
||||||
|
"preview": {
|
||||||
|
"codeBlockTheme": "dracula",
|
||||||
|
"fontFamily": "Lato",
|
||||||
|
"fontSize": "14",
|
||||||
|
"lineNumber": true
|
||||||
|
},
|
||||||
|
"sortBy": "UPDATED_AT",
|
||||||
|
"sortTagsBy": "ALPHABETICAL",
|
||||||
|
"ui": {
|
||||||
|
"defaultNote": "ALWAYS_ASK",
|
||||||
|
"disableDirectWrite": false,
|
||||||
|
"theme": "default"
|
||||||
|
},
|
||||||
|
"zoom": 1
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
compiled/
|
compiled/
|
||||||
dist/
|
dist/
|
||||||
|
extra_scripts/
|
||||||
22
.eslintrc
22
.eslintrc
@@ -1,10 +1,26 @@
|
|||||||
{
|
{
|
||||||
"extends": ["standard", "standard-jsx"],
|
"extends": ["standard", "standard-jsx", "plugin:react/recommended"],
|
||||||
|
"plugins": ["react"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-useless-escape": 0,
|
"no-useless-escape": 0,
|
||||||
"prefer-const": "warn",
|
"prefer-const": ["warn", {
|
||||||
|
"destructuring": "all"
|
||||||
|
}],
|
||||||
"no-unused-vars": "warn",
|
"no-unused-vars": "warn",
|
||||||
"no-undef": "warn",
|
"no-undef": "warn",
|
||||||
"no-lone-blocks": "warn"
|
"no-lone-blocks": "warn",
|
||||||
|
"react/prop-types": 0,
|
||||||
|
"react/no-string-refs": 0,
|
||||||
|
"react/no-find-dom-node": "warn",
|
||||||
|
"react/no-render-return-value": "warn",
|
||||||
|
"react/no-deprecated": "warn"
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"FileReader": true,
|
||||||
|
"localStorage": true,
|
||||||
|
"fetch": true
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,3 +8,5 @@ node_modules/*
|
|||||||
/compiled
|
/compiled
|
||||||
/secret
|
/secret
|
||||||
*.log
|
*.log
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
BIN
.snapcraft/travis_snapcraft.cfg
Normal file
BIN
.snapcraft/travis_snapcraft.cfg
Normal file
Binary file not shown.
22
.travis.yml
22
.travis.yml
@@ -1,6 +1,20 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 'stable'
|
- 7
|
||||||
- 'lts/*'
|
script:
|
||||||
|
- npm run lint && npm run test
|
||||||
script: npm run lint && npm run test
|
- yarn jest
|
||||||
|
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
|
||||||
|
after_success:
|
||||||
|
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
||||||
|
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
|
||||||
|
sudo: required
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
deploy:
|
||||||
|
'on':
|
||||||
|
branch: master
|
||||||
|
provider: script
|
||||||
|
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
|
||||||
|
&& cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
|
||||||
|
skip_cleanup: true
|
||||||
|
|||||||
75
Backers.md
75
Backers.md
@@ -1,5 +1,72 @@
|
|||||||
Become a [backer](https://salt.bountysource.com/teams/boostnote) and support Boostnote!
|
<h1 align="center">Sponsors & Backers</h1>
|
||||||
You can support Boostnote from $ 5 a month!
|
|
||||||
|
|
||||||
# Backers
|
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:
|
||||||
[Kazu Yokomizo](https://twitter.com/kazup_bot)
|
|
||||||
|
- [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
|
||||||
|
|||||||
@@ -1 +1,25 @@
|
|||||||
Please paste some **screenshots** with opening the developer tool if you report a bug.
|
# Current behavior
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug.
|
||||||
|
|
||||||
|
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile.
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Expected behavior
|
||||||
|
|
||||||
|
# Steps to reproduce
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
|
||||||
|
- Version :
|
||||||
|
- OS Version and name :
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Love Boostnote? Please consider supporting us on IssueHunt:
|
||||||
|
👉 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 Maisin&Co., Inc.
|
Copyright (C) 2017 - 2018 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
|
||||||
|
|||||||
7
__mocks__/electron.js
Normal file
7
__mocks__/electron.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module.exports = {
|
||||||
|
require: jest.genMockFunction(),
|
||||||
|
match: jest.genMockFunction(),
|
||||||
|
app: jest.genMockFunction(),
|
||||||
|
remote: jest.genMockFunction(),
|
||||||
|
dialog: jest.genMockFunction()
|
||||||
|
}
|
||||||
@@ -1,34 +1,36 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
import path from 'path'
|
import 'codemirror-mode-elixir'
|
||||||
import copyImage from 'browser/main/lib/dataApi/copyImage'
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||||
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
|
import { options, TableEditor } from '@susisu/mte-kernel'
|
||||||
|
import TextEditorInterface from 'browser/lib/TextEditorInterface'
|
||||||
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
|
import iconv from 'iconv-lite'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import consts from 'browser/lib/consts'
|
||||||
|
import fs from 'fs'
|
||||||
|
const { ipcRenderer } = require('electron')
|
||||||
|
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
|
||||||
|
|
||||||
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
|
||||||
|
|
||||||
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
const buildCMRulers = (rulers, enableRulers) =>
|
||||||
|
enableRulers ? rulers.map(ruler => ({column: ruler})) : []
|
||||||
function pass (name) {
|
|
||||||
switch (name) {
|
|
||||||
case 'ejs':
|
|
||||||
return 'Embedded Javascript'
|
|
||||||
case 'html_ruby':
|
|
||||||
return 'Embedded Ruby'
|
|
||||||
case 'objectivec':
|
|
||||||
return 'Objective C'
|
|
||||||
case 'text':
|
|
||||||
return 'Plain Text'
|
|
||||||
default:
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class CodeEditor extends React.Component {
|
export default class CodeEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
||||||
this.changeHandler = (e) => this.handleChange(e)
|
this.changeHandler = (e) => this.handleChange(e)
|
||||||
|
this.focusHandler = () => {
|
||||||
|
ipcRenderer.send('editor:focused', true)
|
||||||
|
}
|
||||||
this.blurHandler = (editor, e) => {
|
this.blurHandler = (editor, e) => {
|
||||||
|
ipcRenderer.send('editor:focused', false)
|
||||||
if (e == null) return null
|
if (e == null) return null
|
||||||
let el = e.relatedTarget
|
let el = e.relatedTarget
|
||||||
while (el != null) {
|
while (el != null) {
|
||||||
@@ -38,33 +40,103 @@ export default class CodeEditor extends React.Component {
|
|||||||
el = el.parentNode
|
el = el.parentNode
|
||||||
}
|
}
|
||||||
this.props.onBlur != null && this.props.onBlur(e)
|
this.props.onBlur != null && this.props.onBlur(e)
|
||||||
|
|
||||||
|
const {storageKey, noteKey} = this.props
|
||||||
|
attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
|
||||||
}
|
}
|
||||||
|
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||||
this.loadStyleHandler = (e) => {
|
this.loadStyleHandler = (e) => {
|
||||||
this.editor.refresh()
|
this.editor.refresh()
|
||||||
}
|
}
|
||||||
|
this.searchHandler = (e, msg) => this.handleSearch(msg)
|
||||||
|
this.searchState = null
|
||||||
|
|
||||||
|
this.formatTable = () => this.handleFormatTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSearch (msg) {
|
||||||
|
const cm = this.editor
|
||||||
|
const component = this
|
||||||
|
|
||||||
|
if (component.searchState) cm.removeOverlay(component.searchState)
|
||||||
|
if (msg.length < 3) return
|
||||||
|
|
||||||
|
cm.operation(function () {
|
||||||
|
component.searchState = makeOverlay(msg, 'searching')
|
||||||
|
cm.addOverlay(component.searchState)
|
||||||
|
|
||||||
|
function makeOverlay (query, style) {
|
||||||
|
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi')
|
||||||
|
return {
|
||||||
|
token: function (stream) {
|
||||||
|
query.lastIndex = stream.pos
|
||||||
|
var match = query.exec(stream.string)
|
||||||
|
if (match && match.index === stream.pos) {
|
||||||
|
stream.pos += match[0].length || 1
|
||||||
|
return style
|
||||||
|
} else if (match) {
|
||||||
|
stream.pos = match.index
|
||||||
|
} else {
|
||||||
|
stream.skipToEnd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFormatTable () {
|
||||||
|
this.tableEditor.formatAll(options({textWidthOptions: {}}))
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
|
const { rulers, enableRulers } = this.props
|
||||||
|
const expandSnippet = this.expandSnippet.bind(this)
|
||||||
|
|
||||||
|
const 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.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
if (!fs.existsSync(consts.SNIPPET_FILE)) {
|
||||||
|
fs.writeFileSync(consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
this.value = this.props.value
|
this.value = this.props.value
|
||||||
this.editor = CodeMirror(this.refs.root, {
|
this.editor = CodeMirror(this.refs.root, {
|
||||||
|
rulers: buildCMRulers(rulers, enableRulers),
|
||||||
value: this.props.value,
|
value: this.props.value,
|
||||||
lineNumbers: true,
|
lineNumbers: this.props.displayLineNumbers,
|
||||||
lineWrapping: true,
|
lineWrapping: true,
|
||||||
theme: this.props.theme,
|
theme: this.props.theme,
|
||||||
indentUnit: this.props.indentSize,
|
indentUnit: this.props.indentSize,
|
||||||
tabSize: this.props.indentSize,
|
tabSize: this.props.indentSize,
|
||||||
indentWithTabs: this.props.indentType !== 'space',
|
indentWithTabs: this.props.indentType !== 'space',
|
||||||
keyMap: this.props.keyMap,
|
keyMap: this.props.keyMap,
|
||||||
|
scrollPastEnd: this.props.scrollPastEnd,
|
||||||
inputStyle: 'textarea',
|
inputStyle: 'textarea',
|
||||||
dragDrop: false,
|
dragDrop: false,
|
||||||
|
foldGutter: true,
|
||||||
|
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||||
|
autoCloseBrackets: {
|
||||||
|
pairs: '()[]{}\'\'""$$**``',
|
||||||
|
triples: '```"""\'\'\'',
|
||||||
|
explode: '[]{}``$$',
|
||||||
|
override: true
|
||||||
|
},
|
||||||
extraKeys: {
|
extraKeys: {
|
||||||
Tab: function (cm) {
|
Tab: function (cm) {
|
||||||
const cursor = cm.getCursor()
|
const cursor = cm.getCursor()
|
||||||
const line = cm.getLine(cursor.line)
|
const line = cm.getLine(cursor.line)
|
||||||
|
const cursorPosition = cursor.ch
|
||||||
|
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
|
||||||
if (cm.somethingSelected()) cm.indentSelection('add')
|
if (cm.somethingSelected()) cm.indentSelection('add')
|
||||||
else {
|
else {
|
||||||
const tabs = cm.getOption('indentWithTabs')
|
const tabs = cm.getOption('indentWithTabs')
|
||||||
if (line.trimLeft() === '- ' || line.trimLeft() === '* ' || line.trimLeft() === '+ ') {
|
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
|
||||||
cm.execCommand('goLineStart')
|
cm.execCommand('goLineStart')
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
cm.execCommand('insertTab')
|
cm.execCommand('insertTab')
|
||||||
@@ -72,6 +144,16 @@ export default class CodeEditor extends React.Component {
|
|||||||
cm.execCommand('insertSoftTab')
|
cm.execCommand('insertSoftTab')
|
||||||
}
|
}
|
||||||
cm.execCommand('goLineEnd')
|
cm.execCommand('goLineEnd')
|
||||||
|
} else if (!charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) {
|
||||||
|
// text expansion on tab key if the char before is alphabet
|
||||||
|
const snippets = JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8'))
|
||||||
|
if (expandSnippet(line, cursor, cm, snippets) === false) {
|
||||||
|
if (tabs) {
|
||||||
|
cm.execCommand('insertTab')
|
||||||
|
} else {
|
||||||
|
cm.execCommand('insertSoftTab')
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (tabs) {
|
if (tabs) {
|
||||||
cm.execCommand('insertTab')
|
cm.execCommand('insertTab')
|
||||||
@@ -84,7 +166,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
'Cmd-T': function (cm) {
|
'Cmd-T': function (cm) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
},
|
},
|
||||||
Enter: 'newlineAndIndentContinueMarkdownList',
|
Enter: 'boostNewLineAndIndentContinueMarkdownList',
|
||||||
'Ctrl-C': (cm) => {
|
'Ctrl-C': (cm) => {
|
||||||
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
|
||||||
document.execCommand('copy')
|
document.execCommand('copy')
|
||||||
@@ -96,22 +178,115 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
this.setMode(this.props.mode)
|
this.setMode(this.props.mode)
|
||||||
|
|
||||||
|
this.editor.on('focus', this.focusHandler)
|
||||||
this.editor.on('blur', this.blurHandler)
|
this.editor.on('blur', this.blurHandler)
|
||||||
this.editor.on('change', this.changeHandler)
|
this.editor.on('change', this.changeHandler)
|
||||||
|
this.editor.on('paste', this.pasteHandler)
|
||||||
|
eventEmitter.on('top:search', this.searchHandler)
|
||||||
|
|
||||||
let editorTheme = document.getElementById('editorTheme')
|
eventEmitter.emit('code:init')
|
||||||
|
this.editor.on('scroll', this.scrollHandler)
|
||||||
|
|
||||||
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
editorTheme.addEventListener('load', this.loadStyleHandler)
|
editorTheme.addEventListener('load', this.loadStyleHandler)
|
||||||
|
|
||||||
|
CodeMirror.Vim.defineEx('quit', 'q', this.quitEditor)
|
||||||
|
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
|
||||||
|
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
|
||||||
|
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
|
||||||
|
CodeMirror.Vim.map('ZZ', ':q', 'normal')
|
||||||
|
|
||||||
|
this.tableEditor = new TableEditor(new TextEditorInterface(this.editor))
|
||||||
|
eventEmitter.on('code:format-table', this.formatTable)
|
||||||
|
}
|
||||||
|
|
||||||
|
expandSnippet (line, cursor, cm, snippets) {
|
||||||
|
const wordBeforeCursor = this.getWordBeforeCursor(line, cursor.line, cursor.ch)
|
||||||
|
const templateCursorString = ':{}'
|
||||||
|
for (let i = 0; i < snippets.length; i++) {
|
||||||
|
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
|
||||||
|
if (snippets[i].content.indexOf(templateCursorString) !== -1) {
|
||||||
|
const snippetLines = snippets[i].content.split('\n')
|
||||||
|
let cursorLineNumber = 0
|
||||||
|
let cursorLinePosition = 0
|
||||||
|
for (let j = 0; j < snippetLines.length; j++) {
|
||||||
|
const cursorIndex = snippetLines[j].indexOf(templateCursorString)
|
||||||
|
if (cursorIndex !== -1) {
|
||||||
|
cursorLineNumber = j
|
||||||
|
cursorLinePosition = cursorIndex
|
||||||
|
cm.replaceRange(
|
||||||
|
snippets[i].content.replace(templateCursorString, ''),
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cm.replaceRange(
|
||||||
|
snippets[i].content,
|
||||||
|
wordBeforeCursor.range.from,
|
||||||
|
wordBeforeCursor.range.to
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
getWordBeforeCursor (line, lineNumber, cursorPosition) {
|
||||||
|
let wordBeforeCursor = ''
|
||||||
|
const originCursorPosition = cursorPosition
|
||||||
|
const emptyChars = /\t|\s|\r|\n/
|
||||||
|
|
||||||
|
// to prevent the word to expand is long that will crash the whole app
|
||||||
|
// the safeStop is there to stop user to expand words that longer than 20 chars
|
||||||
|
const safeStop = 20
|
||||||
|
|
||||||
|
while (cursorPosition > 0) {
|
||||||
|
const currentChar = line.substr(cursorPosition - 1, 1)
|
||||||
|
// if char is not an empty char
|
||||||
|
if (!emptyChars.test(currentChar)) {
|
||||||
|
wordBeforeCursor = currentChar + wordBeforeCursor
|
||||||
|
} else if (wordBeforeCursor.length >= safeStop) {
|
||||||
|
throw new Error('Your snippet trigger is too long !')
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cursorPosition--
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: wordBeforeCursor,
|
||||||
|
range: {
|
||||||
|
from: {line: lineNumber, ch: originCursorPosition},
|
||||||
|
to: {line: lineNumber, ch: cursorPosition}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quitEditor () {
|
||||||
|
document.querySelector('textarea').blur()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
this.editor.off('focus', this.focusHandler)
|
||||||
this.editor.off('blur', this.blurHandler)
|
this.editor.off('blur', this.blurHandler)
|
||||||
this.editor.off('change', this.changeHandler)
|
this.editor.off('change', this.changeHandler)
|
||||||
let editorTheme = document.getElementById('editorTheme')
|
this.editor.off('paste', this.pasteHandler)
|
||||||
|
eventEmitter.off('top:search', this.searchHandler)
|
||||||
|
this.editor.off('scroll', this.scrollHandler)
|
||||||
|
const editorTheme = document.getElementById('editorTheme')
|
||||||
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
editorTheme.removeEventListener('load', this.loadStyleHandler)
|
||||||
|
|
||||||
|
eventEmitter.off('code:format-table', this.formatTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps, prevState) {
|
componentDidUpdate (prevProps, prevState) {
|
||||||
let needRefresh = false
|
let needRefresh = false
|
||||||
|
const {rulers, enableRulers} = this.props
|
||||||
if (prevProps.mode !== this.props.mode) {
|
if (prevProps.mode !== this.props.mode) {
|
||||||
this.setMode(this.props.mode)
|
this.setMode(this.props.mode)
|
||||||
}
|
}
|
||||||
@@ -129,6 +304,10 @@ export default class CodeEditor extends React.Component {
|
|||||||
needRefresh = true
|
needRefresh = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevProps.enableRulers !== enableRulers || prevProps.rulers !== rulers) {
|
||||||
|
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
|
||||||
|
}
|
||||||
|
|
||||||
if (prevProps.indentSize !== this.props.indentSize) {
|
if (prevProps.indentSize !== this.props.indentSize) {
|
||||||
this.editor.setOption('indentUnit', this.props.indentSize)
|
this.editor.setOption('indentUnit', this.props.indentSize)
|
||||||
this.editor.setOption('tabSize', this.props.indentSize)
|
this.editor.setOption('tabSize', this.props.indentSize)
|
||||||
@@ -137,13 +316,21 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
|
this.editor.setOption('indentWithTabs', this.props.indentType !== 'space')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevProps.displayLineNumbers !== this.props.displayLineNumbers) {
|
||||||
|
this.editor.setOption('lineNumbers', this.props.displayLineNumbers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
|
||||||
|
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
|
||||||
|
}
|
||||||
|
|
||||||
if (needRefresh) {
|
if (needRefresh) {
|
||||||
this.editor.refresh()
|
this.editor.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setMode (mode) {
|
setMode (mode) {
|
||||||
let syntax = CodeMirror.findModeByName(pass(mode))
|
let syntax = CodeMirror.findModeByName(convertModeName(mode))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
this.editor.setOption('mode', syntax.mime)
|
this.editor.setOption('mode', syntax.mime)
|
||||||
@@ -182,33 +369,152 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setValue (value) {
|
setValue (value) {
|
||||||
let cursor = this.editor.getCursor()
|
const cursor = this.editor.getCursor()
|
||||||
this.editor.setValue(value)
|
this.editor.setValue(value)
|
||||||
this.editor.setCursor(cursor)
|
this.editor.setCursor(cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDropImage (e) {
|
handleDropImage (dropEvent) {
|
||||||
e.preventDefault()
|
dropEvent.preventDefault()
|
||||||
const imagePath = e.dataTransfer.files[0].path
|
const {storageKey, noteKey} = this.props
|
||||||
const filename = path.basename(imagePath)
|
attachmentManagement.handleAttachmentDrop(this, storageKey, noteKey, dropEvent)
|
||||||
|
}
|
||||||
|
|
||||||
copyImage(imagePath, this.props.storageKey).then((imagePath) => {
|
insertAttachmentMd (imageMd) {
|
||||||
const imageMd = `})`
|
this.editor.replaceSelection(imageMd)
|
||||||
this.insertImageMd(imageMd)
|
}
|
||||||
|
|
||||||
|
handlePaste (editor, e) {
|
||||||
|
const clipboardData = e.clipboardData
|
||||||
|
const {storageKey, noteKey} = this.props
|
||||||
|
const dataTransferItem = clipboardData.items[0]
|
||||||
|
const pastedTxt = clipboardData.getData('text')
|
||||||
|
const isURL = (str) => {
|
||||||
|
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
|
||||||
|
return matcher.test(str)
|
||||||
|
}
|
||||||
|
const isInLinkTag = (editor) => {
|
||||||
|
const startCursor = editor.getCursor('start')
|
||||||
|
const prevChar = editor.getRange(
|
||||||
|
{line: startCursor.line, ch: startCursor.ch - 2},
|
||||||
|
{line: startCursor.line, ch: startCursor.ch}
|
||||||
|
)
|
||||||
|
const endCursor = editor.getCursor('end')
|
||||||
|
const nextChar = editor.getRange(
|
||||||
|
{line: endCursor.line, ch: endCursor.ch},
|
||||||
|
{line: endCursor.line, ch: endCursor.ch + 1}
|
||||||
|
)
|
||||||
|
return prevChar === '](' && nextChar === ')'
|
||||||
|
}
|
||||||
|
if (dataTransferItem.type.match('image')) {
|
||||||
|
attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem)
|
||||||
|
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
|
||||||
|
this.handlePasteUrl(e, editor, pastedTxt)
|
||||||
|
}
|
||||||
|
if (attachmentManagement.isAttachmentLink(pastedTxt)) {
|
||||||
|
attachmentManagement.handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt)
|
||||||
|
.then((modifiedText) => {
|
||||||
|
this.editor.replaceSelection(modifiedText)
|
||||||
|
})
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll (e) {
|
||||||
|
if (this.props.onScroll) {
|
||||||
|
this.props.onScroll(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePasteUrl (e, editor, pastedTxt) {
|
||||||
|
e.preventDefault()
|
||||||
|
const taggedUrl = `<${pastedTxt}>`
|
||||||
|
editor.replaceSelection(taggedUrl)
|
||||||
|
|
||||||
|
const isImageReponse = (response) => {
|
||||||
|
return response.headers.has('content-type') &&
|
||||||
|
response.headers.get('content-type').match(/^image\/.+$/)
|
||||||
|
}
|
||||||
|
const replaceTaggedUrl = (replacement) => {
|
||||||
|
const value = editor.getValue()
|
||||||
|
const cursor = editor.getCursor()
|
||||||
|
const newValue = value.replace(taggedUrl, replacement)
|
||||||
|
const newCursor = Object.assign({}, cursor, { ch: cursor.ch + newValue.length - value.length })
|
||||||
|
editor.setValue(newValue)
|
||||||
|
editor.setCursor(newCursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(pastedTxt, {
|
||||||
|
method: 'get'
|
||||||
|
}).then((response) => {
|
||||||
|
if (isImageReponse(response)) {
|
||||||
|
return this.mapImageResponse(response, pastedTxt)
|
||||||
|
} else {
|
||||||
|
return this.mapNormalResponse(response, pastedTxt)
|
||||||
|
}
|
||||||
|
}).then((replacement) => {
|
||||||
|
replaceTaggedUrl(replacement)
|
||||||
|
}).catch((e) => {
|
||||||
|
replaceTaggedUrl(pastedTxt)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
insertImageMd (imageMd) {
|
mapNormalResponse (response, pastedTxt) {
|
||||||
const textarea = this.editor.getInputField()
|
return this.decodeResponse(response).then((body) => {
|
||||||
const cm = this.editor
|
return new Promise((resolve, reject) => {
|
||||||
textarea.value = `${textarea.value.substr(0, textarea.selectionStart)}${imageMd}${textarea.value.substr(textarea.selectionEnd)}`
|
try {
|
||||||
|
const parsedBody = (new window.DOMParser()).parseFromString(body, 'text/html')
|
||||||
|
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
|
||||||
|
resolve(linkWithTitle)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mapImageResponse (response, pastedTxt) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const url = response.url
|
||||||
|
const name = url.substring(url.lastIndexOf('/') + 1)
|
||||||
|
const imageLinkWithName = ``
|
||||||
|
resolve(imageLinkWithName)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeResponse (response) {
|
||||||
|
const headers = response.headers
|
||||||
|
const _charset = headers.has('content-type')
|
||||||
|
? this.extractContentTypeCharset(headers.get('content-type'))
|
||||||
|
: undefined
|
||||||
|
return response.arrayBuffer().then((buff) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const charset = _charset !== undefined && iconv.encodingExists(_charset) ? _charset : 'utf-8'
|
||||||
|
resolve(iconv.decode(new Buffer(buff), charset).toString())
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
extractContentTypeCharset (contentType) {
|
||||||
|
return contentType.split(';').filter((str) => {
|
||||||
|
return str.trim().toLowerCase().startsWith('charset')
|
||||||
|
}).map((str) => {
|
||||||
|
return str.replace(/['"]/g, '').split('=')[1]
|
||||||
|
})[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className, fontFamily, fontSize } = this.props
|
const {className, fontSize} = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.length > 0
|
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
|
||||||
? [fontFamily].concat(defaultEditorFontFamily)
|
const width = this.props.width
|
||||||
: defaultEditorFontFamily
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className == null
|
className={className == null
|
||||||
@@ -218,8 +524,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
ref='root'
|
ref='root'
|
||||||
tabIndex='-1'
|
tabIndex='-1'
|
||||||
style={{
|
style={{
|
||||||
fontFamily: fontFamily.join(', '),
|
fontFamily,
|
||||||
fontSize: fontSize
|
fontSize: fontSize,
|
||||||
|
width: width
|
||||||
}}
|
}}
|
||||||
onDrop={(e) => this.handleDropImage(e)}
|
onDrop={(e) => this.handleDropImage(e)}
|
||||||
/>
|
/>
|
||||||
@@ -229,6 +536,8 @@ export default class CodeEditor extends React.Component {
|
|||||||
|
|
||||||
CodeEditor.propTypes = {
|
CodeEditor.propTypes = {
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
|
enableRulers: PropTypes.bool,
|
||||||
|
rulers: PropTypes.arrayOf(Number),
|
||||||
mode: PropTypes.string,
|
mode: PropTypes.string,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
onBlur: PropTypes.func,
|
onBlur: PropTypes.func,
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './MarkdownEditor.styl'
|
import styles from './MarkdownEditor.styl'
|
||||||
import CodeEditor from 'browser/components/CodeEditor'
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
const _ = require('lodash')
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
|
||||||
class MarkdownEditor extends React.Component {
|
class MarkdownEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -69,9 +70,9 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu (e) {
|
||||||
let { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
if (config.editor.switchPreview === 'RIGHTCLICK') {
|
||||||
let newStatus = this.state.status === 'PREVIEW'
|
const newStatus = this.state.status === 'PREVIEW'
|
||||||
? 'CODE'
|
? 'CODE'
|
||||||
: 'PREVIEW'
|
: 'PREVIEW'
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -80,7 +81,6 @@ class MarkdownEditor extends React.Component {
|
|||||||
if (newStatus === 'CODE') {
|
if (newStatus === 'CODE') {
|
||||||
this.refs.code.focus()
|
this.refs.code.focus()
|
||||||
} else {
|
} else {
|
||||||
this.refs.code.blur()
|
|
||||||
this.refs.preview.focus()
|
this.refs.preview.focus()
|
||||||
}
|
}
|
||||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||||
@@ -91,9 +91,11 @@ class MarkdownEditor extends React.Component {
|
|||||||
handleBlur (e) {
|
handleBlur (e) {
|
||||||
if (this.state.isLocked) return
|
if (this.state.isLocked) return
|
||||||
this.setState({ keyPressed: new Set() })
|
this.setState({ keyPressed: new Set() })
|
||||||
let { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'BLUR') {
|
if (config.editor.switchPreview === 'BLUR' ||
|
||||||
let cursorPosition = this.refs.code.editor.getCursor()
|
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
|
||||||
|
) {
|
||||||
|
const cursorPosition = this.refs.code.editor.getCursor()
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'PREVIEW'
|
status: 'PREVIEW'
|
||||||
}, () => {
|
}, () => {
|
||||||
@@ -104,12 +106,26 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDoubleClick (e) {
|
||||||
|
if (this.state.isLocked) return
|
||||||
|
this.setState({keyPressed: new Set()})
|
||||||
|
const { config } = this.props
|
||||||
|
if (config.editor.switchPreview === 'DBL_CLICK') {
|
||||||
|
this.setState({
|
||||||
|
status: 'CODE'
|
||||||
|
}, () => {
|
||||||
|
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) {
|
||||||
let { config } = this.props
|
const { config } = this.props
|
||||||
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
|
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'CODE'
|
status: 'CODE'
|
||||||
@@ -123,15 +139,15 @@ class MarkdownEditor extends React.Component {
|
|||||||
handleCheckboxClick (e) {
|
handleCheckboxClick (e) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
let idMatch = /checkbox-([0-9]+)/
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
let checkedMatch = /\[x\]/i
|
const checkedMatch = /\[x\]/i
|
||||||
let uncheckedMatch = /\[ \]/
|
const uncheckedMatch = /\[ \]/
|
||||||
if (idMatch.test(e.target.getAttribute('id'))) {
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
let lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
let lines = this.refs.code.value
|
const lines = this.refs.code.value
|
||||||
.split('\n')
|
.split('\n')
|
||||||
|
|
||||||
let targetLine = lines[lineIndex]
|
const targetLine = lines[lineIndex]
|
||||||
|
|
||||||
if (targetLine.match(checkedMatch)) {
|
if (targetLine.match(checkedMatch)) {
|
||||||
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
||||||
@@ -163,15 +179,18 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown (e) {
|
||||||
|
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 })
|
||||||
let isNoteHandlerKey = (el) => { return keyPressed.has(el) }
|
const isNoteHandlerKey = (el) => { return keyPressed.has(el) }
|
||||||
|
// These conditions are for ctrl-e and ctrl-w
|
||||||
if (keyPressed.size === this.escapeFromEditor.length &&
|
if (keyPressed.size === this.escapeFromEditor.length &&
|
||||||
!this.state.isLocked && this.state.status === 'CODE' &&
|
!this.state.isLocked && this.state.status === 'CODE' &&
|
||||||
this.escapeFromEditor.every(isNoteHandlerKey)) {
|
this.escapeFromEditor.every(isNoteHandlerKey)) {
|
||||||
document.activeElement.blur()
|
this.handleContextMenu()
|
||||||
|
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('**')
|
||||||
@@ -204,20 +223,17 @@ class MarkdownEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className, value, config, storageKey } = this.props
|
const {className, value, config, storageKey, noteKey} = this.props
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
|
||||||
let previewStyle = {}
|
const previewStyle = {}
|
||||||
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
||||||
|
|
||||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
const storage = findStorage(storageKey)
|
||||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
|
||||||
const storage = _.find(cachedStorageList, {key: storageKey})
|
|
||||||
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className == null
|
<div className={className == null
|
||||||
@@ -242,7 +258,13 @@ class MarkdownEditor extends React.Component {
|
|||||||
fontSize={editorFontSize}
|
fontSize={editorFontSize}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
|
enableRulers={config.editor.enableRulers}
|
||||||
|
rulers={config.editor.rulers}
|
||||||
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
storageKey={storageKey}
|
storageKey={storageKey}
|
||||||
|
noteKey={noteKey}
|
||||||
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
onBlur={(e) => this.handleBlur(e)}
|
||||||
/>
|
/>
|
||||||
@@ -259,14 +281,24 @@ class MarkdownEditor extends React.Component {
|
|||||||
codeBlockFontFamily={config.editor.fontFamily}
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
|
smartQuotes={config.preview.smartQuotes}
|
||||||
|
smartArrows={config.preview.smartArrows}
|
||||||
|
breaks={config.preview.breaks}
|
||||||
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={(e) => this.handleContextMenu(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}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
|
noteKey={noteKey}
|
||||||
|
customCSS={config.preview.customCSS}
|
||||||
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
423
browser/components/MarkdownPreview.js
Normal file → Executable file
423
browser/components/MarkdownPreview.js
Normal file → Executable file
@@ -1,27 +1,42 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
import markdown from 'browser/lib/markdown'
|
import React from 'react'
|
||||||
|
import Markdown from 'browser/lib/markdown'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
import 'codemirror-mode-elixir'
|
||||||
import consts from 'browser/lib/consts'
|
import consts from 'browser/lib/consts'
|
||||||
import Raphael from 'raphael'
|
import Raphael from 'raphael'
|
||||||
import flowchart from 'flowchart'
|
import flowchart from 'flowchart'
|
||||||
|
import mermaidRender from './render/MermaidRender'
|
||||||
import SequenceDiagram from 'js-sequence-diagrams'
|
import SequenceDiagram from 'js-sequence-diagrams'
|
||||||
|
import Chart from 'chart.js'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import fs from 'fs'
|
|
||||||
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||||
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
|
import mdurl from 'mdurl'
|
||||||
|
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
||||||
|
import { escapeHtmlCharacters } from 'browser/lib/utils'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
|
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
|
||||||
|
|
||||||
const { app } = remote
|
const { app } = remote
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
const fileUrl = require('file-url')
|
||||||
|
|
||||||
const dialog = remote.dialog
|
const dialog = remote.dialog
|
||||||
|
|
||||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||||
const appPath = 'file://' + (process.env.NODE_ENV === 'production'
|
const appPath = fileUrl(process.env.NODE_ENV === 'production'
|
||||||
? app.getAppPath()
|
? app.getAppPath()
|
||||||
: path.resolve())
|
: path.resolve())
|
||||||
|
const CSS_FILES = [
|
||||||
|
`${appPath}/node_modules/katex/dist/katex.min.css`,
|
||||||
|
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
||||||
|
]
|
||||||
|
|
||||||
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber) {
|
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS) {
|
||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
@@ -32,31 +47,52 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber) {
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src: url('${appPath}/resources/fonts/Lato-Black.woff2') format('woff2'), /* Modern Browsers */
|
||||||
|
url('${appPath}/resources/fonts/Lato-Black.woff') format('woff'), /* Modern Browsers */
|
||||||
|
url('${appPath}/resources/fonts/Lato-Black.ttf') format('truetype');
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local('Material Icons'),
|
||||||
|
local('MaterialIcons-Regular'),
|
||||||
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff2') format('woff2'),
|
||||||
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
|
||||||
|
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
|
||||||
|
}
|
||||||
|
${allowCustomCSS ? customCSS : ''}
|
||||||
${markdownStyle}
|
${markdownStyle}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: ${fontFamily.join(', ')};
|
font-family: '${fontFamily.join("','")}';
|
||||||
font-size: ${fontSize}px;
|
font-size: ${fontSize}px;
|
||||||
|
${scrollPastEnd && 'padding-bottom: 90vh;'}
|
||||||
}
|
}
|
||||||
code {
|
code {
|
||||||
font-family: ${codeBlockFontFamily.join(', ')};
|
font-family: '${codeBlockFontFamily.join("','")}';
|
||||||
background-color: rgba(0,0,0,0.04);
|
background-color: rgba(0,0,0,0.04);
|
||||||
color: #CC305F;
|
|
||||||
}
|
}
|
||||||
.lineNumber {
|
.lineNumber {
|
||||||
${lineNumber && 'display: block !important;'}
|
${lineNumber && 'display: block !important;'}
|
||||||
font-family: ${codeBlockFontFamily.join(', ')};
|
font-family: '${codeBlockFontFamily.join("','")}';
|
||||||
}
|
}
|
||||||
|
|
||||||
.clipboardButton {
|
.clipboardButton {
|
||||||
color: rgba(147,147,149,0.8);;
|
color: rgba(147,147,149,0.8);;
|
||||||
fill: rgba(147,147,149,1);;
|
fill: rgba(147,147,149,1);;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
margin: 7px;
|
margin: 0px 10px;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
outline: none;
|
outline: none;
|
||||||
height: 32px;
|
height: 15px;
|
||||||
width: 32px;
|
width: 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,19 +120,47 @@ h2 {
|
|||||||
body p {
|
body p {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body[data-theme="${theme}"] {
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.clipboardButton {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scrollBarStyle = `
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
const scrollBarDarkStyle = `
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const { shell } = require('electron')
|
const { shell } = require('electron')
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
|
||||||
if (!OSX) {
|
if (!OSX) {
|
||||||
defaultFontFamily.unshift('\'Microsoft YaHei\'')
|
defaultFontFamily.unshift('Microsoft YaHei')
|
||||||
defaultFontFamily.unshift('meiryo')
|
defaultFontFamily.unshift('meiryo')
|
||||||
}
|
}
|
||||||
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||||
|
|
||||||
export default class MarkdownPreview extends React.Component {
|
export default class MarkdownPreview extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -104,38 +168,46 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
||||||
this.mouseDownHandler = (e) => this.handleMouseDown(e)
|
this.mouseDownHandler = (e) => this.handleMouseDown(e)
|
||||||
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
this.mouseUpHandler = (e) => this.handleMouseUp(e)
|
||||||
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
|
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
|
||||||
|
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
|
||||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||||
|
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
|
||||||
|
this.printHandler = () => this.handlePrint()
|
||||||
|
|
||||||
this.linkClickHandler = this.handlelinkClick.bind(this)
|
this.linkClickHandler = this.handlelinkClick.bind(this)
|
||||||
|
this.initMarkdown = this.initMarkdown.bind(this)
|
||||||
|
this.initMarkdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePreviewAnchorClick (e) {
|
initMarkdown () {
|
||||||
e.preventDefault()
|
const { smartQuotes, sanitize, breaks } = this.props
|
||||||
e.stopPropagation()
|
this.markdown = new Markdown({
|
||||||
|
typographer: smartQuotes,
|
||||||
let anchor = e.target.closest('a')
|
sanitize,
|
||||||
let href = anchor.getAttribute('href')
|
breaks
|
||||||
if (_.isString(href) && href.match(/^#/)) {
|
})
|
||||||
let targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
|
|
||||||
if (targetElement != null) {
|
|
||||||
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
shell.openExternal(href)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCheckboxClick (e) {
|
handleCheckboxClick (e) {
|
||||||
this.props.onCheckboxClick(e)
|
this.props.onCheckboxClick(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleScroll (e) {
|
||||||
|
if (this.props.onScroll) {
|
||||||
|
this.props.onScroll(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleContextMenu (e) {
|
handleContextMenu (e) {
|
||||||
this.props.onContextMenu(e)
|
this.props.onContextMenu(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDoubleClick (e) {
|
||||||
|
if (this.props.onDoubleClick != null) this.props.onDoubleClick(e)
|
||||||
|
}
|
||||||
|
|
||||||
handleMouseDown (e) {
|
handleMouseDown (e) {
|
||||||
if (e.target != null) {
|
if (e.target != null) {
|
||||||
switch (e.target.tagName) {
|
switch (e.target.tagName) {
|
||||||
@@ -148,6 +220,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMouseUp (e) {
|
handleMouseUp (e) {
|
||||||
|
if (!this.props.onMouseUp) return
|
||||||
if (e.target != null && e.target.tagName === 'A') {
|
if (e.target != null && e.target.tagName === 'A') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@@ -162,91 +235,203 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
this.exportAsDocument('md')
|
this.exportAsDocument('md')
|
||||||
}
|
}
|
||||||
|
|
||||||
exportAsDocument (fileType) {
|
handleSaveAsHtml () {
|
||||||
|
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
||||||
|
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
|
||||||
|
|
||||||
|
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
|
||||||
|
let body = this.markdown.render(escapeHtmlCharacters(noteContent, { detectCodeBlock: true }))
|
||||||
|
|
||||||
|
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
|
||||||
|
|
||||||
|
files.forEach((file) => {
|
||||||
|
file = file.replace('file://', '')
|
||||||
|
exportTasks.push({
|
||||||
|
src: file,
|
||||||
|
dst: 'css'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
attachmentsAbsolutePaths.forEach((attachment) => {
|
||||||
|
exportTasks.push({
|
||||||
|
src: attachment,
|
||||||
|
dst: attachmentManagement.DESTINATION_FOLDER
|
||||||
|
})
|
||||||
|
})
|
||||||
|
body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey)
|
||||||
|
|
||||||
|
let styles = ''
|
||||||
|
files.forEach((file) => {
|
||||||
|
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
|
||||||
|
})
|
||||||
|
|
||||||
|
return `<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
|
||||||
|
<style id="style">${inlineStyles}</style>
|
||||||
|
${styles}
|
||||||
|
</head>
|
||||||
|
<body>${body}</body>
|
||||||
|
</html>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePrint () {
|
||||||
|
this.refs.root.contentWindow.print()
|
||||||
|
}
|
||||||
|
|
||||||
|
exportAsDocument (fileType, contentFormatter) {
|
||||||
const options = {
|
const options = {
|
||||||
filters: [
|
filters: [
|
||||||
{ name: 'Documents', extensions: [fileType] }
|
{name: 'Documents', extensions: [fileType]}
|
||||||
],
|
],
|
||||||
properties: ['openFile', 'createDirectory']
|
properties: ['openFile', 'createDirectory']
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog.showSaveDialog(remote.getCurrentWindow(), options,
|
dialog.showSaveDialog(remote.getCurrentWindow(), options,
|
||||||
(filename) => {
|
(filename) => {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
fs.writeFile(filename, this.props.value, (err) => {
|
const content = this.props.value
|
||||||
if (err) throw err
|
const storage = this.props.storagePath
|
||||||
|
|
||||||
|
exportNote(storage, content, filename, contentFormatter)
|
||||||
|
.then((res) => {
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), {type: 'info', message: `Exported to ${filename}`})
|
||||||
|
}).catch((err) => {
|
||||||
|
dialog.showErrorBox('Export error', err ? err.message || err : 'Unexpected error during export')
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
fixDecodedURI (node) {
|
||||||
|
if (node && node.children.length === 1 && typeof node.children[0] === 'string') {
|
||||||
|
const { innerText, href } = node
|
||||||
|
|
||||||
|
node.innerText = mdurl.decode(href) === innerText
|
||||||
|
? href
|
||||||
|
: innerText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getScrollBarStyle () {
|
||||||
|
const {
|
||||||
|
theme
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
switch (theme) {
|
||||||
|
case 'dark':
|
||||||
|
case 'solarized-dark':
|
||||||
|
case 'monokai':
|
||||||
|
return scrollBarDarkStyle
|
||||||
|
default:
|
||||||
|
return scrollBarStyle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
this.refs.root.setAttribute('sandbox', 'allow-scripts')
|
||||||
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
|
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.head.innerHTML = `
|
let styles = `
|
||||||
<style id='style'></style>
|
<style id='style'></style>
|
||||||
<link rel="stylesheet" href="${appPath}/node_modules/katex/dist/katex.min.css">
|
|
||||||
<link rel="stylesheet" href="${appPath}/node_modules/codemirror/lib/codemirror.css">
|
|
||||||
<link rel="stylesheet" id="codeTheme">
|
<link rel="stylesheet" id="codeTheme">
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<style>
|
||||||
|
${this.getScrollBarStyle()}
|
||||||
|
</style>
|
||||||
`
|
`
|
||||||
|
|
||||||
|
CSS_FILES.forEach((file) => {
|
||||||
|
styles += `<link rel="stylesheet" href="${file}">`
|
||||||
|
})
|
||||||
|
|
||||||
|
this.refs.root.contentWindow.document.head.innerHTML = styles
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
|
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
|
||||||
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
|
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener('dblclick', this.DoubleClickHandler)
|
||||||
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
|
||||||
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
|
||||||
|
this.refs.root.contentWindow.document.addEventListener('scroll', this.scrollHandler)
|
||||||
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
||||||
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||||
|
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
|
||||||
|
eventEmitter.on('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
|
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
|
||||||
this.refs.root.contentWindow.document.removeEventListener('mousedown', this.mouseDownHandler)
|
this.refs.root.contentWindow.document.removeEventListener('mousedown', this.mouseDownHandler)
|
||||||
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
|
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener('dblclick', this.DoubleClickHandler)
|
||||||
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
|
||||||
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
|
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
|
||||||
|
this.refs.root.contentWindow.document.removeEventListener('scroll', this.scrollHandler)
|
||||||
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
||||||
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
||||||
|
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
|
||||||
|
eventEmitter.off('print', this.printHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||||
|
if (prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||||
|
prevProps.sanitize !== this.props.sanitize ||
|
||||||
|
prevProps.smartArrows !== this.props.smartArrows ||
|
||||||
|
prevProps.breaks !== this.props.breaks) {
|
||||||
|
this.initMarkdown()
|
||||||
|
this.rewriteIframe()
|
||||||
|
}
|
||||||
if (prevProps.fontFamily !== this.props.fontFamily ||
|
if (prevProps.fontFamily !== this.props.fontFamily ||
|
||||||
prevProps.fontSize !== this.props.fontSize ||
|
prevProps.fontSize !== this.props.fontSize ||
|
||||||
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
|
||||||
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
|
||||||
prevProps.lineNumber !== this.props.lineNumber ||
|
prevProps.lineNumber !== this.props.lineNumber ||
|
||||||
prevProps.theme !== this.props.theme) {
|
prevProps.showCopyNotification !== this.props.showCopyNotification ||
|
||||||
|
prevProps.theme !== this.props.theme ||
|
||||||
|
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
|
||||||
|
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
|
||||||
|
prevProps.customCSS !== this.props.customCSS) {
|
||||||
this.applyStyle()
|
this.applyStyle()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyle () {
|
getStyleParams () {
|
||||||
let { fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme } = this.props
|
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS } = this.props
|
||||||
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
? [fontFamily].concat(defaultFontFamily)
|
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
||||||
: defaultFontFamily
|
: defaultFontFamily
|
||||||
codeBlockFontFamily = _.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
codeBlockFontFamily = _.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
|
||||||
? [codeBlockFontFamily].concat(defaultCodeBlockFontFamily)
|
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
|
||||||
: defaultCodeBlockFontFamily
|
: defaultCodeBlockFontFamily
|
||||||
|
|
||||||
this.setCodeTheme(codeBlockTheme)
|
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS}
|
||||||
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCodeTheme (theme) {
|
applyStyle () {
|
||||||
|
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme, allowCustomCSS, customCSS} = this.getStyleParams()
|
||||||
|
|
||||||
|
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
|
||||||
|
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme, allowCustomCSS, customCSS)
|
||||||
|
}
|
||||||
|
|
||||||
|
GetCodeThemeLink (theme) {
|
||||||
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
|
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
|
||||||
? theme
|
? theme
|
||||||
: 'elegant'
|
: 'elegant'
|
||||||
this.getWindow().document.getElementById('codeTheme').href = `${appPath}/node_modules/codemirror/theme/${theme}.css`
|
return theme.startsWith('solarized')
|
||||||
|
? `${appPath}/node_modules/codemirror/theme/solarized.css`
|
||||||
|
: `${appPath}/node_modules/codemirror/theme/${theme}.css`
|
||||||
}
|
}
|
||||||
|
|
||||||
rewriteIframe () {
|
rewriteIframe () {
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
|
||||||
el.removeEventListener('click', this.anchorClickHandler)
|
|
||||||
})
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
el.removeEventListener('click', this.checkboxClickHandler)
|
el.removeEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
@@ -255,7 +440,8 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.removeEventListener('click', this.linkClickHandler)
|
el.removeEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
let { value, theme, indentSize, codeBlockTheme, storagePath } = this.props
|
const { theme, indentSize, showCopyNotification, storagePath, noteKey } = this.props
|
||||||
|
let { value, codeBlockTheme } = this.props
|
||||||
|
|
||||||
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
|
||||||
|
|
||||||
@@ -265,56 +451,53 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
|
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.refs.root.contentWindow.document.body.innerHTML = markdown.render(value)
|
const renderedHTML = this.markdown.render(value)
|
||||||
|
attachmentManagement.migrateAttachments(value, storagePath, noteKey)
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.taskListItem'), (el) => {
|
this.refs.root.contentWindow.document.body.innerHTML = attachmentManagement.fixLocalURLS(renderedHTML, storagePath)
|
||||||
el.parentNode.parentNode.style.listStyleType = 'none'
|
|
||||||
})
|
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
|
||||||
})
|
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('input[type="checkbox"]'), (el) => {
|
||||||
el.addEventListener('click', this.checkboxClickHandler)
|
el.addEventListener('click', this.checkboxClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||||
|
this.fixDecodedURI(el)
|
||||||
el.addEventListener('click', this.linkClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
|
|
||||||
if (!/\/:storage/.test(el.src)) return
|
|
||||||
el.src = `file:///${path.join(storagePath, 'images', path.basename(el.src))}`
|
|
||||||
})
|
|
||||||
|
|
||||||
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
|
||||||
? codeBlockTheme
|
? codeBlockTheme
|
||||||
: 'default'
|
: 'default'
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
|
||||||
let syntax = CodeMirror.findModeByName(el.className)
|
let syntax = CodeMirror.findModeByName(convertModeName(el.className))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
CodeMirror.requireMode(syntax.mode, () => {
|
CodeMirror.requireMode(syntax.mode, () => {
|
||||||
let content = htmlTextHelper.decodeEntities(el.innerHTML)
|
const content = htmlTextHelper.decodeEntities(el.innerHTML)
|
||||||
const copyIcon = document.createElement('i')
|
const copyIcon = document.createElement('i')
|
||||||
copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
|
copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
|
||||||
copyIcon.onclick = (e) => {
|
copyIcon.onclick = (e) => {
|
||||||
copy(content)
|
copy(content)
|
||||||
this.notify('Saved to Clipboard!', {
|
if (showCopyNotification) {
|
||||||
body: 'Paste it wherever you want!',
|
this.notify('Saved to Clipboard!', {
|
||||||
silent: true
|
body: 'Paste it wherever you want!',
|
||||||
})
|
silent: true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
el.parentNode.appendChild(copyIcon)
|
el.parentNode.appendChild(copyIcon)
|
||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
|
if (codeBlockTheme.indexOf('solarized') === 0) {
|
||||||
|
const [refThema, color] = codeBlockTheme.split(' ')
|
||||||
|
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
|
||||||
|
} else {
|
||||||
|
el.parentNode.className += ` cm-s-${codeBlockTheme}`
|
||||||
|
}
|
||||||
CodeMirror.runMode(content, syntax.mime, el, {
|
CodeMirror.runMode(content, syntax.mime, el, {
|
||||||
tabSize: indentSize
|
tabSize: indentSize
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
let opts = {}
|
const opts = {}
|
||||||
// if (this.props.theme === 'dark') {
|
// if (this.props.theme === 'dark') {
|
||||||
// opts['font-color'] = '#DDD'
|
// opts['font-color'] = '#DDD'
|
||||||
// opts['line-color'] = '#DDD'
|
// opts['line-color'] = '#DDD'
|
||||||
@@ -324,11 +507,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => {
|
||||||
Raphael.setWindow(this.getWindow())
|
Raphael.setWindow(this.getWindow())
|
||||||
try {
|
try {
|
||||||
let diagram = flowchart.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
const diagram = flowchart.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
diagram.drawSVG(el, opts)
|
diagram.drawSVG(el, opts)
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
_.forEach(el.querySelectorAll('a'), (el) => {
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -340,11 +523,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.sequence'), (el) => {
|
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.sequence'), (el) => {
|
||||||
Raphael.setWindow(this.getWindow())
|
Raphael.setWindow(this.getWindow())
|
||||||
try {
|
try {
|
||||||
let diagram = SequenceDiagram.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
const diagram = SequenceDiagram.parse(htmlTextHelper.decodeEntities(el.innerHTML))
|
||||||
el.innerHTML = ''
|
el.innerHTML = ''
|
||||||
diagram.drawSVG(el, {theme: 'simple'})
|
diagram.drawSVG(el, {theme: 'simple'})
|
||||||
_.forEach(el.querySelectorAll('a'), (el) => {
|
_.forEach(el.querySelectorAll('a'), (el) => {
|
||||||
el.addEventListener('click', this.anchorClickHandler)
|
el.addEventListener('click', this.linkClickHandler)
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
@@ -352,6 +535,30 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
el.innerHTML = 'Sequence diagram parse error: ' + e.message
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
_.forEach(
|
||||||
|
this.refs.root.contentWindow.document.querySelectorAll('.chart'),
|
||||||
|
(el) => {
|
||||||
|
try {
|
||||||
|
const chartConfig = JSON.parse(el.innerHTML)
|
||||||
|
el.innerHTML = ''
|
||||||
|
var canvas = document.createElement('canvas')
|
||||||
|
el.appendChild(canvas)
|
||||||
|
/* eslint-disable no-new */
|
||||||
|
new Chart(canvas, chartConfig)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
el.className = 'chart-error'
|
||||||
|
el.innerHTML = 'chartjs diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
_.forEach(
|
||||||
|
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
|
||||||
|
(el) => {
|
||||||
|
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML))
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
focus () {
|
focus () {
|
||||||
@@ -363,11 +570,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollTo (targetRow) {
|
scrollTo (targetRow) {
|
||||||
let blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
|
const blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
|
||||||
|
|
||||||
for (let index = 0; index < blocks.length; index++) {
|
for (let index = 0; index < blocks.length; index++) {
|
||||||
let block = blocks[index]
|
let block = blocks[index]
|
||||||
let row = parseInt(block.getAttribute('data-line'))
|
const row = parseInt(block.getAttribute('data-line'))
|
||||||
if (row > targetRow || index === blocks.length - 1) {
|
if (row > targetRow || index === blocks.length - 1) {
|
||||||
block = blocks[index - 1]
|
block = blocks[index - 1]
|
||||||
block != null && this.getWindow().scrollTo(0, block.offsetTop)
|
block != null && this.getWindow().scrollTo(0, block.offsetTop)
|
||||||
@@ -389,15 +596,48 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlelinkClick (e) {
|
handlelinkClick (e) {
|
||||||
const noteHash = e.target.href.split('/').pop()
|
e.preventDefault()
|
||||||
const regexIsNoteLink = /^(.{20})-(.{20})$/
|
e.stopPropagation()
|
||||||
if (regexIsNoteLink.test(noteHash)) {
|
|
||||||
eventEmitter.emit('list:jump', noteHash)
|
const href = e.target.href
|
||||||
|
const linkHash = href.split('/').pop()
|
||||||
|
|
||||||
|
const regexNoteInternalLink = /main.html#(.+)/
|
||||||
|
if (regexNoteInternalLink.test(linkHash)) {
|
||||||
|
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
|
||||||
|
const targetElement = this.refs.root.contentWindow.document.getElementById(targetId)
|
||||||
|
|
||||||
|
if (targetElement != null) {
|
||||||
|
this.getWindow().scrollTo(0, targetElement.offsetTop)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this will match the new uuid v4 hash and the old hash
|
||||||
|
// e.g.
|
||||||
|
// :note:1c211eb7dcb463de6490 and
|
||||||
|
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
|
||||||
|
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
|
||||||
|
if (regexIsNoteLink.test(linkHash)) {
|
||||||
|
eventEmitter.emit('list:jump', linkHash.replace(':note:', ''))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// this will match the old link format storage.key-note.key
|
||||||
|
// e.g.
|
||||||
|
// 877f99c3268608328037-1c211eb7dcb463de6490
|
||||||
|
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
|
||||||
|
if (regexIsLegacyNoteLink.test(linkHash)) {
|
||||||
|
eventEmitter.emit('list:jump', linkHash.split('-')[1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// other case
|
||||||
|
shell.openExternal(href)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className, style, tabIndex } = this.props
|
const { className, style, tabIndex } = this.props
|
||||||
return (
|
return (
|
||||||
<iframe className={className != null
|
<iframe className={className != null
|
||||||
? 'MarkdownPreview ' + className
|
? 'MarkdownPreview ' + className
|
||||||
@@ -416,7 +656,12 @@ MarkdownPreview.propTypes = {
|
|||||||
onDoubleClick: PropTypes.func,
|
onDoubleClick: PropTypes.func,
|
||||||
onMouseUp: PropTypes.func,
|
onMouseUp: PropTypes.func,
|
||||||
onMouseDown: PropTypes.func,
|
onMouseDown: PropTypes.func,
|
||||||
|
onContextMenu: PropTypes.func,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
storagePath: PropTypes.string
|
showCopyNotification: PropTypes.bool,
|
||||||
|
storagePath: PropTypes.string,
|
||||||
|
smartQuotes: PropTypes.bool,
|
||||||
|
smartArrows: PropTypes.bool,
|
||||||
|
breaks: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|||||||
200
browser/components/MarkdownSplitEditor.js
Normal file
200
browser/components/MarkdownSplitEditor.js
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
|
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||||
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
import styles from './MarkdownSplitEditor.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
class MarkdownSplitEditor extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.value = props.value
|
||||||
|
this.focus = () => this.refs.code.focus()
|
||||||
|
this.reload = () => this.refs.code.reload()
|
||||||
|
this.userScroll = true
|
||||||
|
this.state = {
|
||||||
|
isSliderFocused: false,
|
||||||
|
codeEditorWidthInPercent: 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnChange () {
|
||||||
|
this.value = this.refs.code.value
|
||||||
|
this.props.onChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll (e) {
|
||||||
|
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
|
||||||
|
const codeDoc = _.get(this, 'refs.code.editor.doc')
|
||||||
|
let srcTop, srcHeight, targetTop, targetHeight
|
||||||
|
|
||||||
|
if (this.userScroll) {
|
||||||
|
if (e.doc) {
|
||||||
|
srcTop = _.get(e, 'doc.scrollTop')
|
||||||
|
srcHeight = _.get(e, 'doc.height')
|
||||||
|
targetTop = _.get(previewDoc, 'body.scrollTop')
|
||||||
|
targetHeight = _.get(previewDoc, 'body.scrollHeight')
|
||||||
|
} else {
|
||||||
|
srcTop = _.get(previewDoc, 'body.scrollTop')
|
||||||
|
srcHeight = _.get(previewDoc, 'body.scrollHeight')
|
||||||
|
targetTop = _.get(codeDoc, 'scrollTop')
|
||||||
|
targetHeight = _.get(codeDoc, 'height')
|
||||||
|
}
|
||||||
|
|
||||||
|
const distance = (targetHeight * srcTop / srcHeight) - targetTop
|
||||||
|
const framerate = 1000 / 60
|
||||||
|
const frames = 20
|
||||||
|
const refractory = frames * framerate
|
||||||
|
|
||||||
|
this.userScroll = false
|
||||||
|
|
||||||
|
let frame = 0
|
||||||
|
let scrollPos, time
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
time = frame / frames
|
||||||
|
scrollPos = time < 0.5
|
||||||
|
? 2 * time * time // ease in
|
||||||
|
: -1 + (4 - 2 * time) * time // ease out
|
||||||
|
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
|
||||||
|
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
|
||||||
|
if (frame >= frames) {
|
||||||
|
clearInterval(timer)
|
||||||
|
setTimeout(() => { this.userScroll = true }, refractory)
|
||||||
|
}
|
||||||
|
frame++
|
||||||
|
}, framerate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCheckboxClick (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
const idMatch = /checkbox-([0-9]+)/
|
||||||
|
const checkedMatch = /\[x\]/i
|
||||||
|
const uncheckedMatch = /\[ \]/
|
||||||
|
if (idMatch.test(e.target.getAttribute('id'))) {
|
||||||
|
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
|
||||||
|
const lines = this.refs.code.value
|
||||||
|
.split('\n')
|
||||||
|
|
||||||
|
const targetLine = lines[lineIndex]
|
||||||
|
|
||||||
|
if (targetLine.match(checkedMatch)) {
|
||||||
|
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
|
||||||
|
}
|
||||||
|
if (targetLine.match(uncheckedMatch)) {
|
||||||
|
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
|
||||||
|
}
|
||||||
|
this.refs.code.setValue(lines.join('\n'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseMove (e) {
|
||||||
|
if (this.state.isSliderFocused) {
|
||||||
|
const rootRect = this.refs.root.getBoundingClientRect()
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseUp (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
isSliderFocused: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseDown (e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.setState({
|
||||||
|
isSliderFocused: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {config, value, storageKey, noteKey} = this.props
|
||||||
|
const storage = findStorage(storageKey)
|
||||||
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
const previewStyle = {}
|
||||||
|
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
|
||||||
|
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
|
||||||
|
return (
|
||||||
|
<div styleName='root' ref='root'
|
||||||
|
onMouseMove={e => this.handleMouseMove(e)}
|
||||||
|
onMouseUp={e => this.handleMouseUp(e)}>
|
||||||
|
<CodeEditor
|
||||||
|
styleName='codeEditor'
|
||||||
|
ref='code'
|
||||||
|
width={this.state.codeEditorWidthInPercent + '%'}
|
||||||
|
mode='GitHub Flavored Markdown'
|
||||||
|
value={value}
|
||||||
|
theme={config.editor.theme}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
|
fontFamily={config.editor.fontFamily}
|
||||||
|
fontSize={editorFontSize}
|
||||||
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
|
indentType={config.editor.indentType}
|
||||||
|
indentSize={editorIndentSize}
|
||||||
|
enableRulers={config.editor.enableRulers}
|
||||||
|
rulers={config.editor.rulers}
|
||||||
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
|
storageKey={storageKey}
|
||||||
|
noteKey={noteKey}
|
||||||
|
onChange={this.handleOnChange.bind(this)}
|
||||||
|
onScroll={this.handleScroll.bind(this)}
|
||||||
|
/>
|
||||||
|
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
||||||
|
<div styleName='slider-hitbox' />
|
||||||
|
</div>
|
||||||
|
<MarkdownPreview
|
||||||
|
style={previewStyle}
|
||||||
|
styleName='preview'
|
||||||
|
theme={config.ui.theme}
|
||||||
|
keyMap={config.editor.keyMap}
|
||||||
|
fontSize={config.preview.fontSize}
|
||||||
|
fontFamily={config.preview.fontFamily}
|
||||||
|
codeBlockTheme={config.preview.codeBlockTheme}
|
||||||
|
codeBlockFontFamily={config.editor.fontFamily}
|
||||||
|
lineNumber={config.preview.lineNumber}
|
||||||
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
|
smartQuotes={config.preview.smartQuotes}
|
||||||
|
smartArrows={config.preview.smartArrows}
|
||||||
|
breaks={config.preview.breaks}
|
||||||
|
sanitize={config.preview.sanitize}
|
||||||
|
ref='preview'
|
||||||
|
tabInde='0'
|
||||||
|
value={value}
|
||||||
|
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
|
||||||
|
onScroll={this.handleScroll.bind(this)}
|
||||||
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
|
storagePath={storage.path}
|
||||||
|
noteKey={noteKey}
|
||||||
|
customCSS={config.preview.customCSS}
|
||||||
|
allowCustomCSS={config.preview.allowCustomCSS}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(MarkdownSplitEditor, styles)
|
||||||
16
browser/components/MarkdownSplitEditor.styl
Normal file
16
browser/components/MarkdownSplitEditor.styl
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.root
|
||||||
|
width 100%
|
||||||
|
height 100%
|
||||||
|
font-size 30px
|
||||||
|
display flex
|
||||||
|
.slider
|
||||||
|
absolute top bottom
|
||||||
|
top -2px
|
||||||
|
width 0
|
||||||
|
z-index 0
|
||||||
|
.slider-hitbox
|
||||||
|
absolute top bottom left right
|
||||||
|
width 7px
|
||||||
|
left -3px
|
||||||
|
z-index 10
|
||||||
|
cursor col-resize
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, {PropTypes} from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './ModalEscButton.styl'
|
import styles from './ModalEscButton.styl'
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ const ModalEscButton = ({
|
|||||||
handleEscButtonClick
|
handleEscButtonClick
|
||||||
}) => (
|
}) => (
|
||||||
<button styleName='escButton' onClick={handleEscButtonClick}>
|
<button styleName='escButton' onClick={handleEscButtonClick}>
|
||||||
<div styleName='esc-mark'>x</div>
|
<div styleName='esc-mark'>×</div>
|
||||||
<div styleName='esc-text'>esc</div>
|
<div styleName='esc-text'>esc</div>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,4 +11,6 @@
|
|||||||
height top-bar-height
|
height top-bar-height
|
||||||
|
|
||||||
.esc-mark
|
.esc-mark
|
||||||
font-size 15px
|
font-size 28px
|
||||||
|
margin-top -5px
|
||||||
|
margin-bottom -7px
|
||||||
30
browser/components/NavToggleButton.js
Normal file
30
browser/components/NavToggleButton.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for toggle SideNav
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import styles from './NavToggleButton.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {boolean} isFolded
|
||||||
|
* @param {Function} handleToggleButtonClick
|
||||||
|
*/
|
||||||
|
|
||||||
|
const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
|
||||||
|
<button styleName='navToggle'
|
||||||
|
onClick={(e) => handleToggleButtonClick(e)}
|
||||||
|
>
|
||||||
|
{isFolded
|
||||||
|
? <i className='fa fa-angle-double-right' />
|
||||||
|
: <i className='fa fa-angle-double-left' />
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
NavToggleButton.propTypes = {
|
||||||
|
isFolded: PropTypes.bool.isRequired,
|
||||||
|
handleToggleButtonClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(NavToggleButton, styles)
|
||||||
26
browser/components/NavToggleButton.styl
Normal file
26
browser/components/NavToggleButton.styl
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
.navToggle
|
||||||
|
navButtonColor()
|
||||||
|
display block
|
||||||
|
position absolute
|
||||||
|
left 5px
|
||||||
|
bottom 5px
|
||||||
|
border-radius 16.5px
|
||||||
|
height 34px
|
||||||
|
width 34px
|
||||||
|
line-height 32px
|
||||||
|
padding 0
|
||||||
|
&:hover
|
||||||
|
border: 1px solid #1EC38B;
|
||||||
|
background-color: alpha(#1EC38B, 30%)
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
navWhiteButtonColor()
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.navToggle
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Note item component.
|
* @fileoverview Note item component.
|
||||||
*/
|
*/
|
||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import { isArray } from 'lodash'
|
import { isArray } from 'lodash'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import { getTodoStatus } from 'browser/lib/getTodoStatus'
|
||||||
import styles from './NoteItem.styl'
|
import styles from './NoteItem.styl'
|
||||||
|
import TodoProcess from './TodoProcess'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Tag element component.
|
* @description Tag element component.
|
||||||
@@ -39,16 +43,29 @@ const TagElementList = (tags) => {
|
|||||||
* @param {boolean} isActive
|
* @param {boolean} isActive
|
||||||
* @param {Object} note
|
* @param {Object} note
|
||||||
* @param {Function} handleNoteClick
|
* @param {Function} handleNoteClick
|
||||||
|
* @param {Function} handleNoteContextMenu
|
||||||
* @param {Function} handleDragStart
|
* @param {Function} handleDragStart
|
||||||
* @param {string} dateDisplay
|
* @param {string} dateDisplay
|
||||||
*/
|
*/
|
||||||
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStart }) => (
|
const NoteItem = ({
|
||||||
|
isActive,
|
||||||
|
note,
|
||||||
|
dateDisplay,
|
||||||
|
handleNoteClick,
|
||||||
|
handleNoteContextMenu,
|
||||||
|
handleDragStart,
|
||||||
|
pathname,
|
||||||
|
storageName,
|
||||||
|
folderName,
|
||||||
|
viewType
|
||||||
|
}) => (
|
||||||
<div styleName={isActive
|
<div styleName={isActive
|
||||||
? 'item--active'
|
? 'item--active'
|
||||||
: 'item'
|
: 'item'
|
||||||
}
|
}
|
||||||
key={`${note.storage}-${note.key}`}
|
key={note.key}
|
||||||
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
onClick={e => handleNoteClick(e, note.key)}
|
||||||
|
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
||||||
onDragStart={e => handleDragStart(e, note)}
|
onDragStart={e => handleDragStart(e, note)}
|
||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
@@ -60,19 +77,36 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStar
|
|||||||
<div styleName='item-title'>
|
<div styleName='item-title'>
|
||||||
{note.title.trim().length > 0
|
{note.title.trim().length > 0
|
||||||
? note.title
|
? note.title
|
||||||
: <span styleName='item-title-empty'>Empty</span>
|
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
{['ALL', 'STORAGE'].includes(viewType) && <div styleName='item-middle'>
|
||||||
|
<div styleName='item-middle-time'>{dateDisplay}</div>
|
||||||
|
<div styleName='item-middle-app-meta'>
|
||||||
|
<div title={viewType === 'ALL' ? storageName : viewType === 'STORAGE' ? folderName : null} styleName='item-middle-app-meta-label'>
|
||||||
|
{viewType === 'ALL' && storageName}
|
||||||
|
{viewType === 'STORAGE' && folderName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
|
||||||
<div styleName='item-bottom-time'>{dateDisplay}</div>
|
|
||||||
{note.isStarred
|
|
||||||
? <i styleName='item-star' className='fa fa-star' /> : ''
|
|
||||||
}
|
|
||||||
<div styleName='item-bottom'>
|
<div styleName='item-bottom'>
|
||||||
<div styleName='item-bottom-tagList'>
|
<div styleName='item-bottom-tagList'>
|
||||||
{note.tags.length > 0
|
{note.tags.length > 0
|
||||||
? TagElementList(note.tags)
|
? TagElementList(note.tags)
|
||||||
: <span styleName='item-bottom-tagList-empty' />
|
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>{i18n.__('No tags')}</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{note.isStarred
|
||||||
|
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
|
||||||
|
}
|
||||||
|
{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>
|
||||||
@@ -90,9 +124,14 @@ NoteItem.propTypes = {
|
|||||||
title: PropTypes.string.isrequired,
|
title: PropTypes.string.isrequired,
|
||||||
tags: PropTypes.array,
|
tags: PropTypes.array,
|
||||||
isStarred: PropTypes.bool.isRequired,
|
isStarred: PropTypes.bool.isRequired,
|
||||||
isTrashed: PropTypes.bool.isRequired
|
isTrashed: PropTypes.bool.isRequired,
|
||||||
|
blog: {
|
||||||
|
blogLink: PropTypes.string,
|
||||||
|
blogId: PropTypes.number
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
handleNoteClick: PropTypes.func.isRequired,
|
handleNoteClick: PropTypes.func.isRequired,
|
||||||
|
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||||
handleDragStart: PropTypes.func.isRequired,
|
handleDragStart: PropTypes.func.isRequired,
|
||||||
handleDragEnd: PropTypes.func.isRequired
|
handleDragEnd: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ $control-height = 30px
|
|||||||
user-select none
|
user-select none
|
||||||
cursor pointer
|
cursor pointer
|
||||||
background-color $ui-noteList-backgroundColor
|
background-color $ui-noteList-backgroundColor
|
||||||
transition background-color 0.2s
|
transition 0.2s
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
.item-title
|
.item-title
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
@@ -25,7 +25,7 @@ $control-height = 30px
|
|||||||
.item-star
|
.item-star
|
||||||
color $ui-favorite-star-button-color
|
color $ui-favorite-star-button-color
|
||||||
&:active
|
&:active
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
.item-title
|
.item-title
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
@@ -39,10 +39,11 @@ $control-height = 30px
|
|||||||
.item-wrapper
|
.item-wrapper
|
||||||
padding 15px 0
|
padding 15px 0
|
||||||
border-bottom $ui-border
|
border-bottom $ui-border
|
||||||
|
position relative
|
||||||
|
|
||||||
.item--active
|
.item--active
|
||||||
@extend .item
|
@extend .item
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
.item-title
|
.item-title
|
||||||
.item-title-empty
|
.item-title-empty
|
||||||
@@ -58,20 +59,30 @@ $control-height = 30px
|
|||||||
.item-star
|
.item-star
|
||||||
color $ui-favorite-star-button-color
|
color $ui-favorite-star-button-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
position relative
|
position relative
|
||||||
font-size 12px
|
font-size 12px
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
top 2px
|
||||||
|
|
||||||
.item-title
|
.item-title
|
||||||
font-size 14px
|
font-size 15px
|
||||||
|
font-weight 700
|
||||||
position relative
|
position relative
|
||||||
top -12px
|
top -12px
|
||||||
left 20px
|
left 20px
|
||||||
padding-right 15px
|
padding 0px 15px 0px 0px
|
||||||
padding-bottom 4px
|
margin-bottom 4px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
@@ -79,52 +90,91 @@ $control-height = 30px
|
|||||||
font-weight normal
|
font-weight normal
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-middle
|
||||||
|
font-size 13px
|
||||||
|
padding-left 2px
|
||||||
|
padding-bottom 2px
|
||||||
|
|
||||||
|
.item-middle-time
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
display inline-block
|
||||||
|
|
||||||
|
.item-middle-app-meta
|
||||||
|
float right
|
||||||
|
.item-middle-app-meta-label
|
||||||
|
opacity 0.4
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding 0 3px
|
||||||
|
white-space nowrap
|
||||||
|
text-overflow ellipsis
|
||||||
|
overflow hidden
|
||||||
|
max-width 200px
|
||||||
|
|
||||||
.item-bottom
|
.item-bottom
|
||||||
position relative
|
position relative
|
||||||
bottom 0px
|
bottom 0px
|
||||||
margin-top 2px
|
margin-top 10px
|
||||||
font-size 12px
|
font-size 12px
|
||||||
line-height 20px
|
line-height 20px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
display flex
|
display block
|
||||||
|
|
||||||
.item-bottom-tagList
|
.item-bottom-tagList
|
||||||
flex 1
|
flex 1
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
line-height 20px
|
line-height 25px
|
||||||
padding-top 7px
|
|
||||||
padding-left 2px
|
padding-left 2px
|
||||||
margin-right 27px
|
margin-right 40px
|
||||||
|
|
||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
font-size 11px
|
font-size 11px
|
||||||
margin-right 8px
|
margin-right 8px
|
||||||
padding 0
|
padding 0
|
||||||
height 20px
|
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
padding 1px 2px
|
padding 4px
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
background-color white
|
background-color white
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
font-size 11px
|
font-size 13px
|
||||||
padding-left 2px
|
padding-left 2px
|
||||||
padding-bottom 2px
|
padding-bottom 2px
|
||||||
|
|
||||||
.item-star
|
.item-star
|
||||||
position absolute
|
position absolute
|
||||||
right 5px
|
right 2px
|
||||||
bottom 0px
|
top 5px
|
||||||
width 34px
|
|
||||||
height 34px
|
|
||||||
color alpha($ui-favorite-star-button-color, 60%)
|
color alpha($ui-favorite-star-button-color, 60%)
|
||||||
font-size 12px
|
font-size 12px
|
||||||
padding 0
|
padding 0
|
||||||
border-radius 17px
|
border-radius 17px
|
||||||
|
|
||||||
|
.item-pin
|
||||||
|
position absolute
|
||||||
|
right 25px
|
||||||
|
top 7px
|
||||||
|
color #E54D42
|
||||||
|
font-size 14px
|
||||||
|
padding 0
|
||||||
|
border-radius 17px
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.item
|
||||||
|
background-color $ui-white-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
&:active
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.item--active
|
||||||
|
@extend .item
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
@@ -136,6 +186,7 @@ body[data-theme="dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
color $ui-dark-text-color
|
||||||
.item-title
|
.item-title
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
@@ -143,11 +194,12 @@ 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($ui-dark-button--active-backgroundColor, 40%)
|
background-color alpha(#fff, 20%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:active
|
&:active
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
color $ui-dark-text-color
|
||||||
.item-title
|
.item-title
|
||||||
.item-title-icon
|
.item-title-icon
|
||||||
.item-bottom-time
|
.item-bottom-time
|
||||||
@@ -173,6 +225,158 @@ body[data-theme="dark"]
|
|||||||
.item-bottom-tagList-item
|
.item-bottom-tagList-item
|
||||||
background-color alpha(white, 10%)
|
background-color alpha(white, 10%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
|
.item-title
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title-icon
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-tagList-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
vertical-align middle
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.item
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
// background-color alpha($ui-solarized-dark-noteList-backgroundColor, 20%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha($ui-solarized-dark-noteList-backgroundColor, 20%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:active
|
||||||
|
transition 0.15s
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha($ui-solarized-dark-noteList-backgroundColor, 10%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.item-wrapper
|
||||||
|
border-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
|
.item--active
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
.item-wrapper
|
||||||
|
border-color transparent
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
// background-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
|
.item-title
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title-icon
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-title-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-bottom-tagList-empty
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
vertical-align middle
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
|
||||||
|
.item
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
&: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
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
&:active
|
||||||
|
transition 0.15s
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-monokai-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
|
||||||
|
border-color transparent
|
||||||
|
.item-title
|
||||||
|
.item-title-icon
|
||||||
|
.item-bottom-time
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
&:hover
|
||||||
|
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
.item-title
|
.item-title
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|||||||
@@ -1,24 +1,37 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Note item component with simple display mode.
|
* @fileoverview Note item component with simple display mode.
|
||||||
*/
|
*/
|
||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './NoteItemSimple.styl'
|
import styles from './NoteItemSimple.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Note item component when using simple display mode.
|
* @description Note item component when using simple display mode.
|
||||||
* @param {boolean} isActive
|
* @param {boolean} isActive
|
||||||
* @param {Object} note
|
* @param {Object} note
|
||||||
* @param {Function} handleNoteClick
|
* @param {Function} handleNoteClick
|
||||||
|
* @param {Function} handleNoteContextMenu
|
||||||
* @param {Function} handleDragStart
|
* @param {Function} handleDragStart
|
||||||
*/
|
*/
|
||||||
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleDragStart }) => (
|
const NoteItemSimple = ({
|
||||||
|
isActive,
|
||||||
|
isAllNotesView,
|
||||||
|
note,
|
||||||
|
handleNoteClick,
|
||||||
|
handleNoteContextMenu,
|
||||||
|
handleDragStart,
|
||||||
|
pathname,
|
||||||
|
storage
|
||||||
|
}) => (
|
||||||
<div styleName={isActive
|
<div styleName={isActive
|
||||||
? 'item-simple--active'
|
? 'item-simple--active'
|
||||||
: 'item-simple'
|
: 'item-simple'
|
||||||
}
|
}
|
||||||
key={`${note.storage}-${note.key}`}
|
key={note.key}
|
||||||
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
|
onClick={e => handleNoteClick(e, note.key)}
|
||||||
|
onContextMenu={e => handleNoteContextMenu(e, note.key)}
|
||||||
onDragStart={e => handleDragStart(e, note)}
|
onDragStart={e => handleDragStart(e, note)}
|
||||||
draggable='true'
|
draggable='true'
|
||||||
>
|
>
|
||||||
@@ -27,10 +40,19 @@ const NoteItemSimple = ({ isActive, note, handleNoteClick, handleDragStart }) =>
|
|||||||
? <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 styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' />
|
||||||
}
|
}
|
||||||
|
{note.isPinned && !pathname.match(/\/starred|\/trash/)
|
||||||
|
? <i styleName='item-pin' className='fa fa-thumb-tack' />
|
||||||
|
: ''
|
||||||
|
}
|
||||||
{note.title.trim().length > 0
|
{note.title.trim().length > 0
|
||||||
? note.title
|
? note.title
|
||||||
: <span styleName='item-simple-title-empty'>Empty</span>
|
: <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>
|
||||||
)
|
)
|
||||||
@@ -44,6 +66,7 @@ NoteItemSimple.propTypes = {
|
|||||||
title: PropTypes.string.isrequired
|
title: PropTypes.string.isrequired
|
||||||
}),
|
}),
|
||||||
handleNoteClick: PropTypes.func.isRequired,
|
handleNoteClick: PropTypes.func.isRequired,
|
||||||
|
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||||
handleDragStart: PropTypes.func.isRequired
|
handleDragStart: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ $control-height = 30px
|
|||||||
user-select none
|
user-select none
|
||||||
cursor pointer
|
cursor pointer
|
||||||
background-color $ui-noteList-backgroundColor
|
background-color $ui-noteList-backgroundColor
|
||||||
transition background-color 0.15s
|
transition 0.2s
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
.item-simple-title-empty
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
&:active
|
&:active
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
.item-simple-title-empty
|
.item-simple-title-empty
|
||||||
@@ -28,7 +28,7 @@ $control-height = 30px
|
|||||||
|
|
||||||
.item-simple--active
|
.item-simple--active
|
||||||
@extend .item-simple
|
@extend .item-simple
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
.item-simple-title-empty
|
.item-simple-title-empty
|
||||||
@@ -37,17 +37,27 @@ $control-height = 30px
|
|||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 40%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
font-size 13px
|
font-size 13px
|
||||||
height 40px
|
height 40px
|
||||||
|
padding-right 20px
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
line-height 24px
|
line-height 24px
|
||||||
padding-top 8px
|
padding-top 8px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
border-bottom $ui-border
|
border-bottom $ui-border
|
||||||
|
position relative
|
||||||
|
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
font-size 12px
|
font-size 12px
|
||||||
@@ -58,6 +68,29 @@ $control-height = 30px
|
|||||||
font-weight normal
|
font-weight normal
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.item-pin
|
||||||
|
position absolute
|
||||||
|
right 0px
|
||||||
|
top 12px
|
||||||
|
color #E54D42
|
||||||
|
font-size 14px
|
||||||
|
padding 0
|
||||||
|
border-radius 17px
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.item-simple
|
||||||
|
background-color $ui-white-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
&:active
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.item-simple--active
|
||||||
|
@extend .item-simple
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
@@ -66,33 +99,53 @@ body[data-theme="dark"]
|
|||||||
.item-simple
|
.item-simple
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
&:active
|
&:hover
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
transition 0.15s
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
color $ui-dark-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
.item-simple-bottom-tagList-item
|
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:hover
|
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
|
||||||
.item-simple-title
|
|
||||||
.item-simple-title-icon
|
|
||||||
.item-simple-bottom-time
|
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:active
|
||||||
|
transition 0.15s
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha(white, 10%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.item-simple--active
|
.item-simple--active
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
.item-simple-wrapper
|
||||||
|
border-color transparent
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
.item-simple-title-icon
|
.item-simple-title-icon
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
background-color transparent
|
background-color alpha(white, 10%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
@@ -103,3 +156,133 @@ body[data-theme="dark"]
|
|||||||
|
|
||||||
.item-simple-title-empty
|
.item-simple-title-empty
|
||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.item-simple
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:active
|
||||||
|
transition 0.15s
|
||||||
|
// background-color $ui-solarized-dark-button--active-backgroundColor
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.item-simple--active
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-tag-backgroundColor
|
||||||
|
.item-simple-wrapper
|
||||||
|
border-color transparent
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
color $ui-dark-text-color
|
||||||
|
.item-simple-bottom-time
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
.item-simple-title
|
||||||
|
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
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
|
||||||
|
.item-simple
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
&:hover
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha($ui-monokai-button-backgroundColor, 60%)
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.item-simple-title
|
||||||
|
.item-simple-title-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
transition 0.15s
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
transition 0.15s
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
&:active
|
||||||
|
transition 0.15s
|
||||||
|
background-color $ui-monokai-button--active-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.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-empty
|
||||||
|
.item-simple-title-icon
|
||||||
|
.item-simple-bottom-time
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color alpha(white, 10%)
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
&:hover
|
||||||
|
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
|
color #c0392b
|
||||||
|
.item-simple-bottom-tagList-item
|
||||||
|
background-color alpha(#fff, 20%)
|
||||||
|
.item-simple-title
|
||||||
|
color $ui-dark-text-color
|
||||||
|
border-bottom $ui-dark-borderColor
|
||||||
|
.item-simple-right
|
||||||
|
float right
|
||||||
|
.item-simple-right-storageName
|
||||||
|
padding-left 4px
|
||||||
|
opacity 0.4
|
||||||
|
|||||||
55
browser/components/RealtimeNotification.js
Normal file
55
browser/components/RealtimeNotification.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './RealtimeNotification.styl'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const { shell } = electron
|
||||||
|
|
||||||
|
class RealtimeNotification extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
notifications: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.fetchNotifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchNotifications () {
|
||||||
|
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
|
||||||
|
fetch(notificationsUrl)
|
||||||
|
.then(response => {
|
||||||
|
return response.json()
|
||||||
|
})
|
||||||
|
.then(json => {
|
||||||
|
this.setState({notifications: json.notifications})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLinkClick (e) {
|
||||||
|
shell.openExternal(e.currentTarget.href)
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { notifications } = this.state
|
||||||
|
const link = notifications.length > 0
|
||||||
|
? <a styleName='notification-link' href={notifications[0].linkUrl}
|
||||||
|
onClick={(e) => this.handleLinkClick(e)}
|
||||||
|
>
|
||||||
|
Info: {notifications[0].text}
|
||||||
|
</a>
|
||||||
|
: ''
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div styleName='notification-area' style={this.props.style}>{link}</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RealtimeNotification.propTypes = {}
|
||||||
|
|
||||||
|
export default CSSModules(RealtimeNotification, styles)
|
||||||
54
browser/components/RealtimeNotification.styl
Normal file
54
browser/components/RealtimeNotification.styl
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
.notification-area
|
||||||
|
z-index 1000
|
||||||
|
font-size 12px
|
||||||
|
position: relative
|
||||||
|
top: 12px
|
||||||
|
background-color none
|
||||||
|
|
||||||
|
.notification-link
|
||||||
|
position absolute
|
||||||
|
text-decoration none
|
||||||
|
color #282A36
|
||||||
|
font-size 14px
|
||||||
|
border 1px solid #6FA8E6
|
||||||
|
background-color alpha(#6FA8E6, 0.2)
|
||||||
|
padding 5px 12px
|
||||||
|
border-radius 2px
|
||||||
|
transition 0.2s
|
||||||
|
&:hover
|
||||||
|
color #1378BD
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.notification-area
|
||||||
|
background-color none
|
||||||
|
|
||||||
|
.notification-link
|
||||||
|
color #fff
|
||||||
|
border 1px solid alpha(#5CB85C, 0.6)
|
||||||
|
background-color alpha(#5CB85C, 0.2)
|
||||||
|
transition 0.2s
|
||||||
|
&:hover
|
||||||
|
color #5CB85C
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.notification-area
|
||||||
|
background-color none
|
||||||
|
|
||||||
|
.notification-link
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
border none
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color #5CB85C
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.notification-area
|
||||||
|
background-color none
|
||||||
|
|
||||||
|
.notification-link
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
border none
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color #5CB85C
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Filter for all notes.
|
* @fileoverview Filter for all notes.
|
||||||
*/
|
*/
|
||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './SideNavFilter.styl'
|
import styles from './SideNavFilter.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} isFolded
|
* @param {boolean} isFolded
|
||||||
@@ -15,27 +17,53 @@ import styles from './SideNavFilter.styl'
|
|||||||
*/
|
*/
|
||||||
const SideNavFilter = ({
|
const SideNavFilter = ({
|
||||||
isFolded, isHomeActive, handleAllNotesButtonClick,
|
isFolded, isHomeActive, handleAllNotesButtonClick,
|
||||||
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick
|
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
|
||||||
|
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu
|
||||||
}) => (
|
}) => (
|
||||||
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
|
||||||
|
|
||||||
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
|
||||||
onClick={handleAllNotesButtonClick}
|
onClick={handleAllNotesButtonClick}
|
||||||
>
|
>
|
||||||
<i className='fa fa-archive fa-fw' />
|
<div styleName='iconWrap'>
|
||||||
<span styleName='menu-button-label'>All Notes</span>
|
<img src={isHomeActive
|
||||||
|
? '../resources/icon/icon-all-active.svg'
|
||||||
|
: '../resources/icon/icon-all.svg'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span styleName='menu-button-label'>{i18n.__('All Notes')}</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}
|
||||||
>
|
>
|
||||||
<i className='fa fa-star fa-fw' />
|
<div styleName='iconWrap'>
|
||||||
<span styleName='menu-button-label'>Starred</span>
|
<img src={isStarredActive
|
||||||
|
? '../resources/icon/icon-star-active.svg'
|
||||||
|
: '../resources/icon/icon-star-sidenav.svg'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span styleName='menu-button-label'>{i18n.__('Starred')}</span>
|
||||||
|
<span styleName='counters'>{counterStarredNote}</span>
|
||||||
</button>
|
</button>
|
||||||
<button styleName={isTrashedActive ? 'menu-button--active' : 'menu-button'}
|
|
||||||
onClick={handleTrashedButtonClick}
|
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
|
||||||
|
onClick={handleTrashedButtonClick} onContextMenu={handleFilterButtonContextMenu}
|
||||||
>
|
>
|
||||||
<i className='fa fa-trash fa-fw' />
|
<div styleName='iconWrap'>
|
||||||
<span styleName='menu-button-label'>Trash</span>
|
<img src={isTrashedActive
|
||||||
|
? '../resources/icon/icon-trash-active.svg'
|
||||||
|
: '../resources/icon/icon-trash-sidenav.svg'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span styleName='menu-button-label'>{i18n.__('Trash')}</span>
|
||||||
|
<span styleName='counters'>{counterDelNote}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,75 +3,140 @@
|
|||||||
|
|
||||||
.menu-button
|
.menu-button
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
height 32px
|
height 36px
|
||||||
padding 0 15px
|
padding 0 15px 0 20px
|
||||||
font-size 12px
|
font-size 14px
|
||||||
width 100%
|
width 100%
|
||||||
text-align left
|
text-align left
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
&:hover, &:active, &:active:hover
|
||||||
|
color #1EC38B
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
|
||||||
|
.iconWrap
|
||||||
|
width 20px
|
||||||
|
text-align center
|
||||||
|
|
||||||
|
.counters
|
||||||
|
float right
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
.menu-button--active
|
.menu-button--active
|
||||||
@extend .menu-button
|
@extend .menu-button
|
||||||
color #e74c3c
|
SideNavFilter()
|
||||||
background-color $ui-button--active-backgroundColor
|
color #1EC38B
|
||||||
.menu-button-label
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
color $ui-text-color
|
.menu-button-label, .counters
|
||||||
|
color #1EC38B
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-button--active-backgroundColor
|
color #1EC38B
|
||||||
color #e74c3c
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-text-color
|
|
||||||
&:active, &:active:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color #e74c3c
|
|
||||||
.menu-button-label
|
|
||||||
color $ui-text-color
|
|
||||||
|
|
||||||
.menu-button-star--active
|
.menu-button-star--active
|
||||||
@extend .menu-button
|
@extend .menu-button
|
||||||
color #F9BF3B
|
SideNavFilter()
|
||||||
background-color $ui-button--active-backgroundColor
|
color #1EC38B
|
||||||
.menu-button-label
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
color $ui-text-color
|
.menu-button-label, .counters
|
||||||
&:hover
|
color #1EC38B
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color #F9BF3B
|
.menu-button-trash--active
|
||||||
.menu-button-label
|
@extend .menu-button
|
||||||
color $ui-text-color
|
SideNavFilter()
|
||||||
&:active, &:active:hover
|
color #1EC38B
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
color #F9BF3B
|
.menu-button-label, .counters
|
||||||
.menu-button-label
|
color #1EC38B
|
||||||
color $ui-text-color
|
|
||||||
|
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
margin-left 5px
|
margin-left 10px
|
||||||
|
flex 1
|
||||||
|
|
||||||
.menu--folded
|
.menu--folded
|
||||||
@extend .menu
|
@extend .menu
|
||||||
.menu-button, .menu-button--active
|
.menu-button, .menu-button--active, .menu-button-star--active, .menu-button-trash--active
|
||||||
text-align center
|
text-align center
|
||||||
|
padding 0 12px
|
||||||
&:hover .menu-button-label
|
&:hover .menu-button-label
|
||||||
transition opacity 0.15s
|
transition opacity 0.15s
|
||||||
opacity 1
|
opacity 1
|
||||||
|
color $ui-tooltip-text-color
|
||||||
|
background-color $ui-tooltip-backgroundColor
|
||||||
|
|
||||||
|
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
position fixed
|
position fixed
|
||||||
display inline-block
|
display inline-block
|
||||||
height 32px
|
height 36px
|
||||||
left 44px
|
left 44px
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
margin-top -8px
|
|
||||||
margin-left 0
|
margin-left 0
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
background-color $ui-tooltip-backgroundColor
|
|
||||||
z-index 10
|
z-index 10
|
||||||
color white
|
|
||||||
line-height 32px
|
line-height 32px
|
||||||
border-top-right-radius 2px
|
border-top-right-radius 2px
|
||||||
border-bottom-right-radius 2px
|
border-bottom-right-radius 2px
|
||||||
pointer-events none
|
pointer-events none
|
||||||
opacity 0
|
opacity 0
|
||||||
font-size 12px
|
font-size 13px
|
||||||
|
.counters
|
||||||
|
display none
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.menu-button
|
||||||
|
navWhiteButtonColor()
|
||||||
|
|
||||||
|
.counters
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.menu-button--active
|
||||||
|
color #e74c3c
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #e74c3c
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.menu-button-star--active
|
||||||
|
color #F9BF3B
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #F9BF3B
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #F9BF3B
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.menu-button-trash--active
|
||||||
|
color #5D9E36
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #5D9E36
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
&:active, &:active:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
color #5D9E36
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.menu-button
|
.menu-button
|
||||||
@@ -88,7 +153,7 @@ body[data-theme="dark"]
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
color #c0392b
|
color #c0392b
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
@@ -99,7 +164,103 @@ body[data-theme="dark"]
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
&:hover
|
&:hover
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
color $ui-favorite-star-button-color
|
color $ui-favorite-star-button-color
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.menu-button-trash--active
|
||||||
|
color #5D9E36
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
|
color #5D9E36
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.menu-button
|
||||||
|
&:active
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.menu-button--active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.menu-button-star--active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.menu-button-trash--active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
.menu-button-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.menu-button
|
||||||
|
&: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
|
||||||
|
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-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
|
||||||
@@ -2,6 +2,7 @@ import React from 'react'
|
|||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './SnippetTab.styl'
|
import styles from './SnippetTab.styl'
|
||||||
import context from 'browser/lib/context'
|
import context from 'browser/lib/context'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class SnippetTab extends React.Component {
|
class SnippetTab extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -28,7 +29,7 @@ class SnippetTab extends React.Component {
|
|||||||
handleContextMenu (e) {
|
handleContextMenu (e) {
|
||||||
context.popup([
|
context.popup([
|
||||||
{
|
{
|
||||||
label: 'Rename',
|
label: i18n.__('Rename'),
|
||||||
click: (e) => this.handleRenameClick(e)
|
click: (e) => this.handleRenameClick(e)
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@@ -54,10 +55,10 @@ class SnippetTab extends React.Component {
|
|||||||
this.handleRename()
|
this.handleRename()
|
||||||
break
|
break
|
||||||
case 27:
|
case 27:
|
||||||
this.setState({
|
this.setState((prevState, props) => ({
|
||||||
name: this.props.snippet.name,
|
name: props.snippet.name,
|
||||||
isRenaming: false
|
isRenaming: false
|
||||||
})
|
}))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,8 +86,17 @@ class SnippetTab extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleDragStart (e) {
|
||||||
|
e.dataTransfer.dropEffect = 'move'
|
||||||
|
this.props.onDragStart(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDrop (e) {
|
||||||
|
this.props.onDrop(e)
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { isActive, snippet, isDeletable } = this.props
|
const { isActive, snippet, isDeletable } = this.props
|
||||||
return (
|
return (
|
||||||
<div styleName={isActive
|
<div styleName={isActive
|
||||||
? 'root--active'
|
? 'root--active'
|
||||||
@@ -98,11 +108,14 @@ class SnippetTab extends React.Component {
|
|||||||
onClick={(e) => this.handleClick(e)}
|
onClick={(e) => this.handleClick(e)}
|
||||||
onDoubleClick={(e) => this.handleRenameClick(e)}
|
onDoubleClick={(e) => this.handleRenameClick(e)}
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
|
onDragStart={(e) => this.handleDragStart(e)}
|
||||||
|
onDrop={(e) => this.handleDrop(e)}
|
||||||
|
draggable='true'
|
||||||
>
|
>
|
||||||
{snippet.name.trim().length > 0
|
{snippet.name.trim().length > 0
|
||||||
? snippet.name
|
? snippet.name
|
||||||
: <span styleName='button-unnamed'>
|
: <span styleName='button-unnamed'>
|
||||||
Unnamed
|
{i18n.__('Unnamed')}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
@@ -127,6 +140,7 @@ class SnippetTab extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SnippetTab.propTypes = {
|
SnippetTab.propTypes = {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(SnippetTab, styles)
|
export default CSSModules(SnippetTab, styles)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
.root
|
.root
|
||||||
position relative
|
position relative
|
||||||
flex 1
|
flex 1
|
||||||
|
min-width 70px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
&:hover
|
&:hover
|
||||||
.deleteButton
|
.deleteButton
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
height 29px
|
height 29px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
text-align left
|
text-align left
|
||||||
padding-right 30px
|
padding-right 23px
|
||||||
border none
|
border none
|
||||||
background-color transparent
|
background-color transparent
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
text-align center
|
text-align center
|
||||||
border none
|
border none
|
||||||
padding 0
|
padding 0
|
||||||
color transparent
|
color $ui-inactive-text-color
|
||||||
background-color transparent
|
background-color transparent
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
|
|
||||||
@@ -89,3 +90,50 @@ body[data-theme="dark"]
|
|||||||
.input
|
.input
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
background-color $ui-dark-button--hover-backgroundColor
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.deleteButton
|
||||||
|
color alpha($ui-dark-text-color, 30%)
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
border-color $ui-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
.deleteButton
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
|
||||||
|
&:active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.root--active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
.deleteButton
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:hover
|
||||||
|
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
|
||||||
|
&:active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.button
|
||||||
|
border none
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color transparent
|
||||||
|
transition color background-color 0.15s
|
||||||
|
border-left 4px solid transparent
|
||||||
|
&:hover
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.input
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.deleteButton
|
||||||
|
color alpha($ui-solarized-dark-text-color, 30%)
|
||||||
@@ -1,10 +1,23 @@
|
|||||||
/**
|
/**
|
||||||
* @fileoverview Micro component for showing storage.
|
* @fileoverview Micro component for showing storage.
|
||||||
*/
|
*/
|
||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import styles from './StorageItem.styl'
|
import styles from './StorageItem.styl'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import { isNumber } from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import { SortableHandle } from 'react-sortable-hoc'
|
||||||
|
|
||||||
|
const DraggableIcon = SortableHandle(({ className }) => (
|
||||||
|
<i className={`fa ${className}`} />
|
||||||
|
))
|
||||||
|
|
||||||
|
const FolderIcon = ({ className, color, isActive }) => {
|
||||||
|
const iconStyle = isActive ? 'fa-folder-open-o' : 'fa-folder-o'
|
||||||
|
return (
|
||||||
|
<i className={`fa ${iconStyle} ${className}`} style={{ color: color }} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} isActive
|
* @param {boolean} isActive
|
||||||
@@ -20,36 +33,54 @@ import { isNumber } from 'lodash'
|
|||||||
* @return {React.Component}
|
* @return {React.Component}
|
||||||
*/
|
*/
|
||||||
const StorageItem = ({
|
const StorageItem = ({
|
||||||
isActive, handleButtonClick, handleContextMenu, folderName,
|
styles,
|
||||||
folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave
|
isActive,
|
||||||
}) => (
|
handleButtonClick,
|
||||||
<button styleName={isActive
|
handleContextMenu,
|
||||||
? 'folderList-item--active'
|
folderName,
|
||||||
: 'folderList-item'
|
folderColor,
|
||||||
}
|
isFolded,
|
||||||
onClick={handleButtonClick}
|
noteCount,
|
||||||
onContextMenu={handleContextMenu}
|
handleDrop,
|
||||||
onDrop={handleDrop}
|
handleDragEnter,
|
||||||
onDragEnter={handleDragEnter}
|
handleDragLeave
|
||||||
onDragLeave={handleDragLeave}
|
}) => {
|
||||||
>
|
return (
|
||||||
<span styleName={isFolded
|
<button
|
||||||
? 'folderList-item-name--folded' : 'folderList-item-name'
|
styleName={isActive ? 'folderList-item--active' : 'folderList-item'}
|
||||||
}
|
onClick={handleButtonClick}
|
||||||
style={{borderColor: folderColor}}
|
onContextMenu={handleContextMenu}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
onDragEnter={handleDragEnter}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
>
|
>
|
||||||
{isFolded ? folderName.substring(0, 1) : folderName}
|
{!isFolded && (
|
||||||
</span>
|
<DraggableIcon className={styles['folderList-item-reorder']} />
|
||||||
{(!isFolded && isNumber(noteCount)) &&
|
)}
|
||||||
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
<span
|
||||||
}
|
styleName={
|
||||||
{isFolded &&
|
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
|
||||||
<span styleName='folderList-item-tooltip'>
|
}
|
||||||
{folderName}
|
>
|
||||||
|
<FolderIcon
|
||||||
|
styleName='folderList-item-icon'
|
||||||
|
color={folderColor}
|
||||||
|
isActive={isActive}
|
||||||
|
/>
|
||||||
|
{isFolded
|
||||||
|
? _.truncate(folderName, { length: 1, omission: '' })
|
||||||
|
: folderName}
|
||||||
</span>
|
</span>
|
||||||
}
|
{!isFolded &&
|
||||||
</button>
|
_.isNumber(noteCount) && (
|
||||||
)
|
<span styleName='folderList-item-noteCount'>{noteCount}</span>
|
||||||
|
)}
|
||||||
|
{isFolded && (
|
||||||
|
<span styleName='folderList-item-tooltip'>{folderName}</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
StorageItem.propTypes = {
|
StorageItem.propTypes = {
|
||||||
isActive: PropTypes.bool.isRequired,
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
|||||||
@@ -5,39 +5,37 @@
|
|||||||
.folderList-item
|
.folderList-item
|
||||||
display flex
|
display flex
|
||||||
width 100%
|
width 100%
|
||||||
height 26px
|
height 34px
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
padding 0
|
padding 0
|
||||||
margin-bottom 5px
|
|
||||||
text-align left
|
text-align left
|
||||||
border none
|
border none
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
font-size 12px
|
font-size 14px
|
||||||
|
align-items: center
|
||||||
&:first-child
|
&:first-child
|
||||||
margin-top 0
|
margin-top 0
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-text-color
|
color #1EC38B;
|
||||||
background-color alpha($ui-button--active-backgroundColor, 20%)
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
transition background-color 0.15s
|
transition background-color 0.15s
|
||||||
&:active
|
&:active
|
||||||
color $ui-text-color
|
color $$ui-button-default-color
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
|
||||||
.folderList-item--active
|
.folderList-item--active
|
||||||
@extend .folderList-item
|
@extend .folderList-item
|
||||||
color $ui-text-color
|
color #1EC38B
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-text-color
|
color #1EC38B;
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color alpha($ui-button-default--active-backgroundColor, 50%)
|
||||||
|
|
||||||
.folderList-item-name
|
.folderList-item-name
|
||||||
display block
|
display block
|
||||||
flex 1
|
flex 1
|
||||||
padding 0 25px
|
padding-right: 10px
|
||||||
height 26px
|
|
||||||
line-height 26px
|
|
||||||
border-width 0 0 0 2px
|
border-width 0 0 0 2px
|
||||||
border-style solid
|
border-style solid
|
||||||
border-color transparent
|
border-color transparent
|
||||||
@@ -48,7 +46,7 @@
|
|||||||
float right
|
float right
|
||||||
line-height 26px
|
line-height 26px
|
||||||
padding-right 15px
|
padding-right 15px
|
||||||
font-size 12px
|
font-size 13px
|
||||||
|
|
||||||
.folderList-item-tooltip
|
.folderList-item-tooltip
|
||||||
tooltip()
|
tooltip()
|
||||||
@@ -60,8 +58,8 @@
|
|||||||
opacity 0
|
opacity 0
|
||||||
border-top-right-radius 2px
|
border-top-right-radius 2px
|
||||||
border-bottom-right-radius 2px
|
border-bottom-right-radius 2px
|
||||||
height 26px
|
height 34px
|
||||||
line-height 26px
|
line-height 32px
|
||||||
|
|
||||||
.folderList-item:hover, .folderList-item--active:hover
|
.folderList-item:hover, .folderList-item--active:hover
|
||||||
.folderList-item-tooltip
|
.folderList-item-tooltip
|
||||||
@@ -69,8 +67,39 @@
|
|||||||
|
|
||||||
.folderList-item-name--folded
|
.folderList-item-name--folded
|
||||||
@extend .folderList-item-name
|
@extend .folderList-item-name
|
||||||
padding-left 12px
|
padding-left 7px
|
||||||
|
.folderList-item-icon
|
||||||
|
font-size 9px
|
||||||
|
|
||||||
|
.folderList-item-icon
|
||||||
|
padding-right: 10px
|
||||||
|
|
||||||
|
.folderList-item-reorder
|
||||||
|
font-size: 9px
|
||||||
|
padding: 10px 8px 10px 9px;
|
||||||
|
color: rgba(147, 147, 149, 0.3)
|
||||||
|
cursor: ns-resize
|
||||||
|
&:before
|
||||||
|
content: "\f142 \f142"
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.folderList-item
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-text-color
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
|
transition background-color 0.15s
|
||||||
|
&:active
|
||||||
|
color $ui-text-color
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.folderList-item--active
|
||||||
|
@extend .folderList-item
|
||||||
|
color $ui-text-color
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color $ui-text-color
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 50%)
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.folderList-item
|
.folderList-item
|
||||||
@@ -86,7 +115,45 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
&:active
|
&:active
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.folderList-item
|
||||||
|
&:hover
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
&:active
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
|
||||||
|
.folderList-item--active
|
||||||
|
@extend .folderList-item
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
&:active
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.folderList-item
|
||||||
|
&: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
|
||||||
|
@extend .folderList-item
|
||||||
|
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
|
||||||
24
browser/components/StorageList.js
Normal file
24
browser/components/StorageList.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for showing StorageList
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import styles from './StorageList.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array} storgaeList
|
||||||
|
*/
|
||||||
|
|
||||||
|
const StorageList = ({storageList, isFolded}) => (
|
||||||
|
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
|
||||||
|
{storageList.length > 0 ? storageList : (
|
||||||
|
<div styleName='storgaeList-empty'>No storage mount.</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
StorageList.propTypes = {
|
||||||
|
storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired
|
||||||
|
}
|
||||||
|
export default CSSModules(StorageList, styles)
|
||||||
24
browser/components/StorageList.styl
Normal file
24
browser/components/StorageList.styl
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
.storageList
|
||||||
|
absolute left right
|
||||||
|
bottom 37px
|
||||||
|
top 180px
|
||||||
|
overflow-y auto
|
||||||
|
|
||||||
|
.storageList-folded
|
||||||
|
@extend .storageList
|
||||||
|
width 44px
|
||||||
|
|
||||||
|
.storageList-empty
|
||||||
|
padding 0 10px
|
||||||
|
margin-top 15px
|
||||||
|
line-height 24px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.storageList-empty
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
|
||||||
|
.root-folded
|
||||||
|
.storageList-empty
|
||||||
|
white-space nowrap
|
||||||
|
transform rotate(90deg)
|
||||||
39
browser/components/TagListItem.js
Normal file
39
browser/components/TagListItem.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Micro component for showing TagList.
|
||||||
|
*/
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import styles from './TagListItem.styl'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} name
|
||||||
|
* @param {Function} handleClickTagListItem
|
||||||
|
* @param {Function} handleClickNarrowToTag
|
||||||
|
* @param {bool} isActive
|
||||||
|
* @param {bool} isRelated
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => (
|
||||||
|
<div styleName='tagList-itemContainer'>
|
||||||
|
{isRelated
|
||||||
|
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
|
||||||
|
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
|
||||||
|
</button>
|
||||||
|
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
|
||||||
|
}
|
||||||
|
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
|
||||||
|
<span styleName='tagList-item-name'>
|
||||||
|
{`# ${name}`}
|
||||||
|
<span styleName='tagList-item-count'>{count}</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
TagListItem.propTypes = {
|
||||||
|
name: PropTypes.string.isRequired,
|
||||||
|
handleClickTagListItem: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(TagListItem, styles)
|
||||||
111
browser/components/TagListItem.styl
Normal file
111
browser/components/TagListItem.styl
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
.tagList-itemContainer
|
||||||
|
display flex
|
||||||
|
|
||||||
|
.tagList-item
|
||||||
|
display flex
|
||||||
|
flex 1
|
||||||
|
width 100%
|
||||||
|
height 26px
|
||||||
|
background-color transparent
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding 0
|
||||||
|
margin-bottom 5px
|
||||||
|
text-align left
|
||||||
|
border none
|
||||||
|
overflow ellipsis
|
||||||
|
font-size 13px
|
||||||
|
&:first-child
|
||||||
|
margin-top 0
|
||||||
|
&:hover
|
||||||
|
color $ui-button-default-color
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 20%)
|
||||||
|
transition background-color 0.15s
|
||||||
|
&:active, &:active:hover
|
||||||
|
color $ui-button-default-color
|
||||||
|
background-color $ui-button-default--active-backgroundColor
|
||||||
|
|
||||||
|
.tagList-itemNarrow
|
||||||
|
composes tagList-item
|
||||||
|
flex none
|
||||||
|
width 20px
|
||||||
|
padding 0 4px
|
||||||
|
|
||||||
|
.tagList-item-active
|
||||||
|
background-color $ui-button-default--active-backgroundColor
|
||||||
|
display flex
|
||||||
|
flex 1
|
||||||
|
width 100%
|
||||||
|
height 26px
|
||||||
|
padding 0
|
||||||
|
margin-bottom 5px
|
||||||
|
text-align left
|
||||||
|
border none
|
||||||
|
overflow ellipsis
|
||||||
|
font-size 13px
|
||||||
|
color $ui-button-default-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button-default--active-backgroundColor, 60%)
|
||||||
|
transition 0.2s
|
||||||
|
|
||||||
|
.tagList-itemNarrow-active
|
||||||
|
composes tagList-item-active
|
||||||
|
flex none
|
||||||
|
width 20px
|
||||||
|
padding 0 4px
|
||||||
|
|
||||||
|
.tagList-item-name
|
||||||
|
display block
|
||||||
|
flex 1
|
||||||
|
padding 0 8px 0 4px
|
||||||
|
height 26px
|
||||||
|
line-height 26px
|
||||||
|
border-width 0 0 0 2px
|
||||||
|
border-style solid
|
||||||
|
border-color transparent
|
||||||
|
overflow hidden
|
||||||
|
text-overflow ellipsis
|
||||||
|
|
||||||
|
.tagList-item-count
|
||||||
|
float right
|
||||||
|
line-height 26px
|
||||||
|
padding-right 15px
|
||||||
|
font-size 13px
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.tagList-item
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-text-color
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 20%)
|
||||||
|
&:active
|
||||||
|
color $ui-text-color
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
|
||||||
|
.tagList-item-active
|
||||||
|
background-color $ui-button--active-backgroundColor
|
||||||
|
color $ui-text-color
|
||||||
|
&:hover
|
||||||
|
background-color alpha($ui-button--active-backgroundColor, 60%)
|
||||||
|
.tagList-item-count
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.tagList-item
|
||||||
|
color $ui-dark-inactive-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
|
&:active
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
|
||||||
|
.tagList-item-active
|
||||||
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:active
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
|
&:hover
|
||||||
|
color $ui-dark-text-color
|
||||||
|
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
|
||||||
|
.tagList-item-count
|
||||||
|
color $ui-dark-button--active-color
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
* @fileoverview Percentage of todo achievement.
|
* @fileoverview Percentage of todo achievement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './TodoListPercentage.styl'
|
import styles from './TodoListPercentage.styl'
|
||||||
|
|
||||||
@@ -15,7 +16,9 @@ const TodoListPercentage = ({
|
|||||||
}) => (
|
}) => (
|
||||||
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
|
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
|
||||||
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
|
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
|
||||||
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
<div styleName='progressBarInner'>
|
||||||
|
<p styleName='percentageText'>{percentageOfTodo}%</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,31 +1,61 @@
|
|||||||
.percentageBar
|
.percentageBar
|
||||||
position absolute
|
position absolute
|
||||||
top 58px
|
top 72px
|
||||||
right: 0px
|
right 0px
|
||||||
left 0px
|
left 0px
|
||||||
background-color #DADFE1
|
background-color #DADFE1
|
||||||
width 100%
|
width 100%
|
||||||
height: 15px
|
height: 17px
|
||||||
font-size: 12px
|
font-size: 12px
|
||||||
z-index 100
|
z-index 100
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
|
|
||||||
.progressBar
|
.progressBar
|
||||||
background-color: #6C7A89
|
background-color: #1EC38B
|
||||||
height 15px
|
height 17px
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
transition 0.3s
|
transition 0.4s cubic-bezier(0.4, 0.4, 0, 1)
|
||||||
|
|
||||||
|
.progressBarInner
|
||||||
|
padding 0 10px
|
||||||
|
min-width 1px
|
||||||
|
height 100%
|
||||||
|
display -webkit-box
|
||||||
|
display box
|
||||||
|
justify-content center
|
||||||
|
align-items center
|
||||||
|
|
||||||
|
|
||||||
.percentageText
|
.percentageText
|
||||||
color #f4f4f4
|
color #f4f4f4
|
||||||
padding: 2px 43%
|
font-weight 600
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.percentageBar
|
.percentageBar
|
||||||
background-color #363A3D
|
background-color #444444
|
||||||
|
|
||||||
.progressBar
|
.progressBar
|
||||||
background-color: alpha(#939395, 50%)
|
background-color: #1EC38B
|
||||||
|
|
||||||
.percentageText
|
.percentageText
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.percentageBar
|
||||||
|
background-color #002b36
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: #2aa198
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color #fdf6e3
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.percentageBar
|
||||||
|
background-color #f92672
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: #373831
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color #fdf6e3
|
||||||
34
browser/components/TodoProcess.js
Normal file
34
browser/components/TodoProcess.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Percentage of todo achievement.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './TodoProcess.styl'
|
||||||
|
|
||||||
|
const TodoProcess = ({
|
||||||
|
todoStatus: {
|
||||||
|
total: totalTodo,
|
||||||
|
completed: completedTodo
|
||||||
|
}
|
||||||
|
}) => (
|
||||||
|
<div styleName='todo-process' style={{display: totalTodo > 0 ? '' : 'none'}}>
|
||||||
|
<div styleName='todo-process-text'>
|
||||||
|
<i className='fa fa-fw fa-check-square-o' />
|
||||||
|
{completedTodo} of {totalTodo}
|
||||||
|
</div>
|
||||||
|
<div styleName='todo-process-bar'>
|
||||||
|
<div styleName='todo-process-bar--inner' style={{width: parseInt(completedTodo / totalTodo * 100) + '%'}} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
TodoProcess.propTypes = {
|
||||||
|
todoStatus: {
|
||||||
|
total: PropTypes.number.isRequired,
|
||||||
|
completed: PropTypes.number.isRequired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(TodoProcess, styles)
|
||||||
45
browser/components/TodoProcess.styl
Normal file
45
browser/components/TodoProcess.styl
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
.todo-process
|
||||||
|
font-size 12px
|
||||||
|
display flex
|
||||||
|
padding-top 15px
|
||||||
|
width 85%
|
||||||
|
|
||||||
|
.todo-process-text
|
||||||
|
display inline-block
|
||||||
|
padding-right 10px
|
||||||
|
white-space nowrap
|
||||||
|
text-overflow ellipsis
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
i
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding-right 5px
|
||||||
|
|
||||||
|
.todo-process-bar
|
||||||
|
display inline-block
|
||||||
|
margin auto
|
||||||
|
height 4px
|
||||||
|
border-radius 10px
|
||||||
|
background-color #DADFE1
|
||||||
|
border-radius 2px
|
||||||
|
flex-grow 1
|
||||||
|
border 1px solid alpha(#6C7A89, 10%)
|
||||||
|
|
||||||
|
.todo-process-bar--inner
|
||||||
|
height 100%
|
||||||
|
border-radius 5px
|
||||||
|
background-color #6C7A89
|
||||||
|
transition 0.3s
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.todo-process
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.todo-process-bar
|
||||||
|
background-color #363A3D
|
||||||
|
|
||||||
|
.todo-process-text
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.todo-process-bar--inner
|
||||||
|
background-color: alpha(#939395, 50%)
|
||||||
@@ -58,7 +58,7 @@ body
|
|||||||
.katex
|
.katex
|
||||||
font 400 1.2em 'KaTeX_Main'
|
font 400 1.2em 'KaTeX_Main'
|
||||||
line-height 1.2em
|
line-height 1.2em
|
||||||
white-space nowrap
|
white-space initial
|
||||||
text-indent 0
|
text-indent 0
|
||||||
.katex .mfrac>.vlist>span:nth-child(2)
|
.katex .mfrac>.vlist>span:nth-child(2)
|
||||||
top 0 !important
|
top 0 !important
|
||||||
@@ -68,7 +68,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
|
||||||
@@ -76,7 +76,10 @@ body
|
|||||||
justify-content left
|
justify-content left
|
||||||
li
|
li
|
||||||
label.taskListItem
|
label.taskListItem
|
||||||
margin-left -2em
|
margin-left -1.8em
|
||||||
|
&.checked
|
||||||
|
text-decoration line-through
|
||||||
|
opacity 0.5
|
||||||
div.math-rendered
|
div.math-rendered
|
||||||
text-align center
|
text-align center
|
||||||
.math-failed
|
.math-failed
|
||||||
@@ -102,7 +105,6 @@ a
|
|||||||
border-radius 5px
|
border-radius 5px
|
||||||
margin -5px
|
margin -5px
|
||||||
transition .1s
|
transition .1s
|
||||||
display inline-block
|
|
||||||
img
|
img
|
||||||
vertical-align sub
|
vertical-align sub
|
||||||
&:hover
|
&:hover
|
||||||
@@ -117,8 +119,9 @@ hr
|
|||||||
margin 15px 0
|
margin 15px 0
|
||||||
h1, h2, h3, h4, h5, h6
|
h1, h2, h3, h4, h5, h6
|
||||||
font-weight bold
|
font-weight bold
|
||||||
|
word-wrap break-word
|
||||||
h1
|
h1
|
||||||
font-size 2.25em
|
font-size 2.55em
|
||||||
padding-bottom 0.3em
|
padding-bottom 0.3em
|
||||||
line-height 1.2em
|
line-height 1.2em
|
||||||
border-bottom solid 1px borderColor
|
border-bottom solid 1px borderColor
|
||||||
@@ -154,6 +157,7 @@ p
|
|||||||
line-height 1.6em
|
line-height 1.6em
|
||||||
margin 0 0 1em
|
margin 0 0 1em
|
||||||
white-space pre-line
|
white-space pre-line
|
||||||
|
word-wrap break-word
|
||||||
img
|
img
|
||||||
max-width 100%
|
max-width 100%
|
||||||
strong, b
|
strong, b
|
||||||
@@ -174,6 +178,8 @@ ul
|
|||||||
margin-bottom 1em
|
margin-bottom 1em
|
||||||
li
|
li
|
||||||
display list-item
|
display list-item
|
||||||
|
&.taskListItem
|
||||||
|
list-style none
|
||||||
p
|
p
|
||||||
margin 0
|
margin 0
|
||||||
&>li>ul, &>li>ol
|
&>li>ul, &>li>ol
|
||||||
@@ -207,12 +213,13 @@ pre
|
|||||||
margin 0 0 1em
|
margin 0 0 1em
|
||||||
display flex
|
display flex
|
||||||
line-height 1.4em
|
line-height 1.4em
|
||||||
&.flowchart, &.sequence
|
&.flowchart, &.sequence, &.chart
|
||||||
display flex
|
display flex
|
||||||
justify-content center
|
justify-content center
|
||||||
background-color white
|
background-color white
|
||||||
&.CodeMirror
|
&.CodeMirror
|
||||||
height initial
|
height initial
|
||||||
|
flex-wrap wrap
|
||||||
&>code
|
&>code
|
||||||
flex 1
|
flex 1
|
||||||
overflow-x auto
|
overflow-x auto
|
||||||
@@ -222,6 +229,13 @@ pre
|
|||||||
padding 0
|
padding 0
|
||||||
border none
|
border none
|
||||||
border-radius 0
|
border-radius 0
|
||||||
|
&>span.filename
|
||||||
|
width 100%
|
||||||
|
border-radius: 5px 0px 0px 0px
|
||||||
|
margin -8px 100% 8px -8px
|
||||||
|
padding 0px 6px
|
||||||
|
background-color #777;
|
||||||
|
color white
|
||||||
&>span.lineNumber
|
&>span.lineNumber
|
||||||
display none
|
display none
|
||||||
font-size 1em
|
font-size 1em
|
||||||
@@ -268,6 +282,94 @@ table
|
|||||||
border-color borderColor
|
border-color borderColor
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px borderColor
|
border-right solid 1px borderColor
|
||||||
|
kbd
|
||||||
|
background-color #fafbfc
|
||||||
|
border solid 1px borderColor
|
||||||
|
border-bottom-color btnColor
|
||||||
|
border-radius 3px
|
||||||
|
box-shadow inset 0 -1px 0 #959da5
|
||||||
|
display inline-block
|
||||||
|
font-size .8em
|
||||||
|
line-height 1
|
||||||
|
padding 3px 5px
|
||||||
|
|
||||||
|
$admonition
|
||||||
|
box-shadow 0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2)
|
||||||
|
position relative
|
||||||
|
margin 1.5625em 0
|
||||||
|
padding 0 1.2rem
|
||||||
|
border-left .4rem solid #448aff
|
||||||
|
border-radius .2rem
|
||||||
|
overflow auto
|
||||||
|
|
||||||
|
html .admonition>:last-child
|
||||||
|
margin-bottom 1.2rem
|
||||||
|
|
||||||
|
.admonition .admonition
|
||||||
|
margin 1em 0
|
||||||
|
|
||||||
|
.admonition p
|
||||||
|
margin-top: 0.5em
|
||||||
|
|
||||||
|
$admonition-icon
|
||||||
|
position absolute
|
||||||
|
left 1.2rem
|
||||||
|
font-family: "Material Icons"
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 24px
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
text-transform: none;
|
||||||
|
letter-spacing: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
direction: ltr;
|
||||||
|
|
||||||
|
/* Support for all WebKit browsers. */
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
/* Support for Safari and Chrome. */
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
|
||||||
|
/* Support for Firefox. */
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
|
||||||
|
/* Support for IE. */
|
||||||
|
font-feature-settings: 'liga';
|
||||||
|
|
||||||
|
$admonition-title
|
||||||
|
margin 0 -1.2rem
|
||||||
|
padding .8rem 1.2rem .8rem 4rem
|
||||||
|
border-bottom .1rem solid rgba(68,138,255,.1)
|
||||||
|
background-color rgba(68,138,255,.1)
|
||||||
|
font-weight 700
|
||||||
|
|
||||||
|
.admonition>.admonition-title:last-child
|
||||||
|
margin-bottom 0
|
||||||
|
|
||||||
|
admonition_types = {
|
||||||
|
note: {color: #0288D1, icon: "note"},
|
||||||
|
hint: {color: #009688, icon: "info_outline"},
|
||||||
|
danger: {color: #c2185b, icon: "block"},
|
||||||
|
caution: {color: #ffa726, icon: "warning"},
|
||||||
|
error: {color: #d32f2f, icon: "error_outline"},
|
||||||
|
attention: {color: #455a64, icon: "priority_high"}
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, val in admonition_types
|
||||||
|
.admonition.{name}
|
||||||
|
@extend $admonition
|
||||||
|
border-left-color: val[color]
|
||||||
|
|
||||||
|
.admonition.{name}>.admonition-title
|
||||||
|
@extend $admonition-title
|
||||||
|
border-bottom-color: .1rem solid rgba(val[color], 0.2)
|
||||||
|
background-color: rgba(val[color], 0.2)
|
||||||
|
|
||||||
|
.admonition.{name}>.admonition-title:before
|
||||||
|
@extend $admonition-icon
|
||||||
|
color: val[color]
|
||||||
|
content: val[icon]
|
||||||
|
|
||||||
themeDarkBackground = darken(#21252B, 10%)
|
themeDarkBackground = darken(#21252B, 10%)
|
||||||
themeDarkText = #f9f9f9
|
themeDarkText = #f9f9f9
|
||||||
@@ -316,3 +418,62 @@ body[data-theme="dark"]
|
|||||||
border-color themeDarkTableBorder
|
border-color themeDarkTableBorder
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px themeDarkTableBorder
|
border-right solid 1px themeDarkTableBorder
|
||||||
|
kbd
|
||||||
|
background-color themeDarkBorder
|
||||||
|
color themeDarkText
|
||||||
|
|
||||||
|
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
|
||||||
|
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
|
||||||
|
themeSolarizedDarkTableBorder = themeDarkBorder
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
border-color themeDarkBorder
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
table
|
||||||
|
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
|
||||||
|
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
|
||||||
|
themeMonokaiTableHead = themeMonokaiTableEven
|
||||||
|
themeMonokaiTableBorder = themeDarkBorder
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
border-color themeDarkBorder
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
table
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
background-color themeMonokaiTableHead
|
||||||
|
th
|
||||||
|
border-color themeMonokaiTableBorder
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px themeMonokaiTableBorder
|
||||||
|
tbody
|
||||||
|
tr:nth-child(2n + 1)
|
||||||
|
background-color themeMonokaiTableOdd
|
||||||
|
tr:nth-child(2n)
|
||||||
|
background-color themeMonokaiTableEven
|
||||||
|
td
|
||||||
|
border-color themeMonokaiTableBorder
|
||||||
|
&:last-child
|
||||||
|
border-right solid 1px themeMonokaiTableBorder
|
||||||
|
kbd
|
||||||
|
background-color themeDarkBackground
|
||||||
28
browser/components/render/MermaidRender.js
Normal file
28
browser/components/render/MermaidRender.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import mermaidAPI from 'mermaid'
|
||||||
|
|
||||||
|
function getRandomInt (min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min)) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
function getId () {
|
||||||
|
var pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
|
var id = 'm-'
|
||||||
|
for (var i = 0; i < 7; i++) {
|
||||||
|
id += pool[getRandomInt(0, 16)]
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
function render (element, content) {
|
||||||
|
try {
|
||||||
|
mermaidAPI.render(getId(), content, (svgGraph) => {
|
||||||
|
element.innerHTML = svgGraph
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
element.className = 'mermaid-error'
|
||||||
|
element.innerHTML = 'mermaid diagram parse error: ' + e.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default render
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
$search-height = 50px
|
|
||||||
$nav-width = 175px
|
|
||||||
$list-width = 250px
|
|
||||||
|
|
||||||
.root
|
|
||||||
absolute top left right bottom
|
|
||||||
|
|
||||||
.search
|
|
||||||
height $search-height
|
|
||||||
padding 10px
|
|
||||||
box-sizing border-box
|
|
||||||
border-bottom $ui-border
|
|
||||||
text-align center
|
|
||||||
|
|
||||||
.search-input
|
|
||||||
height 30px
|
|
||||||
width 100%
|
|
||||||
margin 0 auto
|
|
||||||
font-size 18px
|
|
||||||
border none
|
|
||||||
outline none
|
|
||||||
text-align center
|
|
||||||
background-color transparent
|
|
||||||
|
|
||||||
.result
|
|
||||||
absolute left right bottom
|
|
||||||
top $search-height
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.result-nav
|
|
||||||
user-select none
|
|
||||||
absolute left top bottom
|
|
||||||
width $nav-width
|
|
||||||
background-color $ui-backgroundColor
|
|
||||||
|
|
||||||
.result-nav-filter
|
|
||||||
margin-bottom 10px
|
|
||||||
|
|
||||||
.result-nav-filter-option
|
|
||||||
height 25px
|
|
||||||
line-height 25px
|
|
||||||
padding 0 10px
|
|
||||||
label
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
.result-nav-menu
|
|
||||||
navButtonColor()
|
|
||||||
height 32px
|
|
||||||
padding 0 10px
|
|
||||||
font-size 14px
|
|
||||||
width 100%
|
|
||||||
outline none
|
|
||||||
text-align left
|
|
||||||
line-height 32px
|
|
||||||
box-sizing border-box
|
|
||||||
cursor pointer
|
|
||||||
|
|
||||||
.result-nav-menu--active
|
|
||||||
@extend .result-nav-menu
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color $ui-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
|
|
||||||
.result-nav-storageList
|
|
||||||
absolute bottom left right
|
|
||||||
top 80px + 32px + 10px + 10px
|
|
||||||
overflow-y auto
|
|
||||||
|
|
||||||
.result-list
|
|
||||||
user-select none
|
|
||||||
absolute top bottom
|
|
||||||
left $nav-width
|
|
||||||
width $list-width
|
|
||||||
box-sizing border-box
|
|
||||||
overflow-y auto
|
|
||||||
box-shadow 2px 0 15px -8px #b1b1b1
|
|
||||||
z-index 1
|
|
||||||
|
|
||||||
.result-detail
|
|
||||||
absolute top bottom right
|
|
||||||
left $nav-width + $list-width
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
|
||||||
.root
|
|
||||||
background-color $ui-dark-backgroundColor
|
|
||||||
.search
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
.search-input
|
|
||||||
color $ui-dark-text-color
|
|
||||||
|
|
||||||
.result
|
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
|
||||||
|
|
||||||
.result-nav
|
|
||||||
background-color $ui-dark-backgroundColor
|
|
||||||
label
|
|
||||||
color $ui-dark-text-color
|
|
||||||
|
|
||||||
.result-nav-menu
|
|
||||||
navDarkButtonColor()
|
|
||||||
|
|
||||||
.result-nav-menu--active
|
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
|
||||||
color $ui-dark-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dark-button--active-backgroundColor
|
|
||||||
|
|
||||||
.result-list
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
box-shadow none
|
|
||||||
top 0
|
|
||||||
|
|
||||||
.result-detail
|
|
||||||
absolute top bottom right
|
|
||||||
left $nav-width + $list-width
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
|
||||||
import styles from './NoteDetail.styl'
|
|
||||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
|
||||||
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
|
||||||
import CodeEditor from 'browser/components/CodeEditor'
|
|
||||||
import CodeMirror from 'codemirror'
|
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const { clipboard } = electron
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
function pass (name) {
|
|
||||||
switch (name) {
|
|
||||||
case 'ejs':
|
|
||||||
return 'Embedded Javascript'
|
|
||||||
case 'html_ruby':
|
|
||||||
return 'Embedded Ruby'
|
|
||||||
case 'objectivec':
|
|
||||||
return 'Objective C'
|
|
||||||
case 'text':
|
|
||||||
return 'Plain Text'
|
|
||||||
default:
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function notify (title, options) {
|
|
||||||
if (global.process.platform === 'win32') {
|
|
||||||
options.icon = path.join('file://', global.__dirname, '../../resources/app.png')
|
|
||||||
}
|
|
||||||
return new window.Notification(title, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
class NoteDetail extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
snippetIndex: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (nextProps.note !== this.props.note) {
|
|
||||||
this.setState({
|
|
||||||
snippetIndex: 0
|
|
||||||
}, () => {
|
|
||||||
if (nextProps.note.type === 'SNIPPET_NOTE') {
|
|
||||||
nextProps.note.snippets.forEach((snippet, index) => {
|
|
||||||
this.refs['code-' + index].reload()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectPriorSnippet () {
|
|
||||||
let { note } = this.props
|
|
||||||
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
|
||||||
this.setState({
|
|
||||||
snippetIndex: (this.state.snippetIndex + note.snippets.length - 1) % note.snippets.length
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectNextSnippet () {
|
|
||||||
let { note } = this.props
|
|
||||||
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
|
|
||||||
this.setState({
|
|
||||||
snippetIndex: (this.state.snippetIndex + 1) % note.snippets.length
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveToClipboard () {
|
|
||||||
let { note } = this.props
|
|
||||||
|
|
||||||
if (note.type === 'MARKDOWN_NOTE') {
|
|
||||||
clipboard.writeText(note.content)
|
|
||||||
} else {
|
|
||||||
clipboard.writeText(note.snippets[this.state.snippetIndex].content)
|
|
||||||
}
|
|
||||||
|
|
||||||
notify('Saved to Clipboard!', {
|
|
||||||
body: 'Paste it wherever you want!',
|
|
||||||
silent: true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTabButtonClick (e, index) {
|
|
||||||
this.setState({
|
|
||||||
snippetIndex: index
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { note, config } = this.props
|
|
||||||
if (note == null) {
|
|
||||||
return (
|
|
||||||
<div styleName='root' />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
|
||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
|
||||||
|
|
||||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
|
||||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
|
||||||
const storage = _.find(cachedStorageList, {key: note.storage})
|
|
||||||
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
|
||||||
|
|
||||||
if (note.type === 'SNIPPET_NOTE') {
|
|
||||||
let tabList = note.snippets.map((snippet, index) => {
|
|
||||||
let isActive = this.state.snippetIndex === index
|
|
||||||
return <div styleName={isActive
|
|
||||||
? 'tabList-item--active'
|
|
||||||
: 'tabList-item'
|
|
||||||
}
|
|
||||||
key={index}
|
|
||||||
>
|
|
||||||
<button styleName='tabList-item-button'
|
|
||||||
onClick={(e) => this.handleTabButtonClick(e, index)}
|
|
||||||
>
|
|
||||||
{snippet.name.trim().length > 0
|
|
||||||
? snippet.name
|
|
||||||
: <span styleName='tabList-item-unnamed'>
|
|
||||||
Unnamed
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
})
|
|
||||||
|
|
||||||
let viewList = note.snippets.map((snippet, index) => {
|
|
||||||
let isActive = this.state.snippetIndex === index
|
|
||||||
|
|
||||||
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
|
||||||
|
|
||||||
return <div styleName='tabView'
|
|
||||||
key={index}
|
|
||||||
style={{zIndex: isActive ? 5 : 4}}
|
|
||||||
>
|
|
||||||
{snippet.mode === 'markdown'
|
|
||||||
? <MarkdownEditor styleName='tabView-content'
|
|
||||||
config={config}
|
|
||||||
value={snippet.content}
|
|
||||||
ref={'code-' + index}
|
|
||||||
/>
|
|
||||||
: <CodeEditor styleName='tabView-content'
|
|
||||||
mode={snippet.mode}
|
|
||||||
value={snippet.content}
|
|
||||||
theme={config.editor.theme}
|
|
||||||
fontFamily={config.editor.fontFamily}
|
|
||||||
fontSize={editorFontSize}
|
|
||||||
indentType={config.editor.indentType}
|
|
||||||
indentSize={editorIndentSize}
|
|
||||||
keyMap={config.editor.keyMap}
|
|
||||||
readOnly
|
|
||||||
ref={'code-' + index}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div styleName='root'>
|
|
||||||
<div styleName='description'>
|
|
||||||
<textarea styleName='description-textarea'
|
|
||||||
style={{
|
|
||||||
fontFamily: config.preview.fontFamily,
|
|
||||||
fontSize: parseInt(config.preview.fontSize, 10)
|
|
||||||
}}
|
|
||||||
ref='description'
|
|
||||||
placeholder='Description...'
|
|
||||||
value={note.description}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div styleName='tabList'>
|
|
||||||
{tabList}
|
|
||||||
</div>
|
|
||||||
{viewList}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MarkdownPreview styleName='root'
|
|
||||||
theme={config.ui.theme}
|
|
||||||
fontSize={config.preview.fontSize}
|
|
||||||
fontFamily={config.preview.fontFamily}
|
|
||||||
codeBlockTheme={config.preview.codeBlockTheme}
|
|
||||||
codeBlockFontFamily={config.editor.fontFamily}
|
|
||||||
lineNumber={config.preview.lineNumber}
|
|
||||||
indentSize={editorIndentSize}
|
|
||||||
value={note.content}
|
|
||||||
storagePath={storage.path}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NoteDetail.propTypes = {
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CSSModules(NoteDetail, styles)
|
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
@import('../main/Detail/DetailVars.styl')
|
|
||||||
|
|
||||||
.root
|
|
||||||
absolute top bottom left right
|
|
||||||
left $note-detail-left-margin
|
|
||||||
right $note-detail-right-margin
|
|
||||||
height 100%
|
|
||||||
width 365px
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.description
|
|
||||||
absolute top left right
|
|
||||||
height 80px
|
|
||||||
box-sizing border-box
|
|
||||||
|
|
||||||
.description-textarea
|
|
||||||
display block
|
|
||||||
height 100%
|
|
||||||
width 100%
|
|
||||||
resize none
|
|
||||||
border none
|
|
||||||
padding 10px
|
|
||||||
line-height 1.6
|
|
||||||
box-sizing border-box
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.tabList
|
|
||||||
absolute left right
|
|
||||||
top 80px
|
|
||||||
box-sizing border-box
|
|
||||||
height 30px
|
|
||||||
display flex
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.tabList-item
|
|
||||||
position relative
|
|
||||||
flex 1
|
|
||||||
overflow hidden
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--hover-backgroundColorg
|
|
||||||
|
|
||||||
.tabList-item--active
|
|
||||||
@extend .tabList-item
|
|
||||||
border-bottom $ui-border
|
|
||||||
|
|
||||||
.tabList-item-button
|
|
||||||
width 100%
|
|
||||||
height 29px
|
|
||||||
overflow ellipsis
|
|
||||||
text-align left
|
|
||||||
padding-right 30px
|
|
||||||
padding-left 10px
|
|
||||||
border none
|
|
||||||
background-color transparent
|
|
||||||
transition 0.15s
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--hover-backgroundColor
|
|
||||||
|
|
||||||
.tabView
|
|
||||||
absolute left right bottom
|
|
||||||
top 130px
|
|
||||||
|
|
||||||
.tabView-content
|
|
||||||
absolute top left right bottom
|
|
||||||
box-sizing border-box
|
|
||||||
height 100%
|
|
||||||
width 100%
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
|
||||||
.root
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.description
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.description-textarea
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
color white
|
|
||||||
|
|
||||||
.tabList
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
|
||||||
|
|
||||||
.tabList-item
|
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
&:hover
|
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
|
||||||
|
|
||||||
.tabList-item-button
|
|
||||||
border none
|
|
||||||
color $ui-dark-text-color
|
|
||||||
background-color transparent
|
|
||||||
transition color background-color 0.15s
|
|
||||||
border-left 4px solid transparent
|
|
||||||
&:hover
|
|
||||||
color white
|
|
||||||
background-color $ui-dark-button--hover-backgroundColor
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import NoteItem from 'browser/components/NoteItem'
|
|
||||||
import moment from 'moment'
|
|
||||||
|
|
||||||
class NoteList extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
range: 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
if (this.props.search !== nextProps.search) {
|
|
||||||
this.resetScroll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate () {
|
|
||||||
let { index } = this.props
|
|
||||||
|
|
||||||
if (index > -1) {
|
|
||||||
let list = this.refs.root
|
|
||||||
let item = list.childNodes[index]
|
|
||||||
if (item == null) return null
|
|
||||||
|
|
||||||
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
|
|
||||||
if (overflowBelow) {
|
|
||||||
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
|
|
||||||
}
|
|
||||||
let overflowAbove = list.scrollTop > item.offsetTop
|
|
||||||
if (overflowAbove) {
|
|
||||||
list.scrollTop = item.offsetTop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resetScroll () {
|
|
||||||
this.refs.root.scrollTop = 0
|
|
||||||
this.setState({
|
|
||||||
range: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleScroll (e) {
|
|
||||||
let { notes } = this.props
|
|
||||||
|
|
||||||
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 100 && notes.length > this.state.range * 10 + 10) {
|
|
||||||
this.setState({
|
|
||||||
range: this.state.range + 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { notes, index } = this.props
|
|
||||||
|
|
||||||
let notesList = notes
|
|
||||||
.slice(0, 10 + 10 * this.state.range)
|
|
||||||
.map((note, _index) => {
|
|
||||||
const isActive = (index === _index)
|
|
||||||
const key = `${note.storage}-${note.key}`
|
|
||||||
const dateDisplay = moment(note.updatedAt).fromNow()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NoteItem
|
|
||||||
isActive={isActive}
|
|
||||||
note={note}
|
|
||||||
dateDisplay={dateDisplay}
|
|
||||||
key={key}
|
|
||||||
handleNoteClick={(e) => this.props.handleNoteClick(e, _index)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<div className={this.props.className}
|
|
||||||
onScroll={(e) => this.handleScroll(e)}
|
|
||||||
ref='root'
|
|
||||||
>
|
|
||||||
{notesList}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NoteList.propTypes = {
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NoteList
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
|
||||||
import styles from './StorageSection.styl'
|
|
||||||
import StorageItem from 'browser/components/StorageItem'
|
|
||||||
|
|
||||||
class StorageSection extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isOpen: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleToggleButtonClick (e) {
|
|
||||||
this.setState({
|
|
||||||
isOpen: !this.state.isOpen
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleHeaderClick (e) {
|
|
||||||
let { storage } = this.props
|
|
||||||
this.props.handleStorageButtonClick(e, storage.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFolderClick (e, folder) {
|
|
||||||
let { storage } = this.props
|
|
||||||
this.props.handleFolderButtonClick(e, storage.key, folder.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { storage, filter } = this.props
|
|
||||||
let folderList = storage.folders
|
|
||||||
.map(folder => (
|
|
||||||
<StorageItem
|
|
||||||
key={folder.key}
|
|
||||||
isActive={filter.type === 'FOLDER' && filter.folder === folder.key && filter.storage === storage.key}
|
|
||||||
handleButtonClick={(e) => this.handleFolderClick(e, folder)}
|
|
||||||
folderName={folder.name}
|
|
||||||
folderColor={folder.color}
|
|
||||||
isFolded={false}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div styleName='root'>
|
|
||||||
<div styleName='header'>
|
|
||||||
<button styleName='header-toggleButton'
|
|
||||||
onClick={(e) => this.handleToggleButtonClick(e)}
|
|
||||||
>
|
|
||||||
<i className={this.state.isOpen
|
|
||||||
? 'fa fa-caret-down'
|
|
||||||
: 'fa fa-caret-right'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<button styleName={filter.type === 'STORAGE' && filter.storage === storage.key
|
|
||||||
? 'header-name--active'
|
|
||||||
: 'header-name'
|
|
||||||
}
|
|
||||||
onClick={(e) => this.handleHeaderClick(e)}
|
|
||||||
>{storage.name}</button>
|
|
||||||
</div>
|
|
||||||
{this.state.isOpen &&
|
|
||||||
<div styleName='folderList'>
|
|
||||||
{folderList}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StorageSection.propTypes = {
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CSSModules(StorageSection, styles)
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
.root
|
|
||||||
position relative
|
|
||||||
|
|
||||||
.header
|
|
||||||
height 26px
|
|
||||||
.header-toggleButton
|
|
||||||
absolute top left
|
|
||||||
width 25px
|
|
||||||
height 26px
|
|
||||||
navButtonColor()
|
|
||||||
border none
|
|
||||||
outline none
|
|
||||||
.header-name
|
|
||||||
display block
|
|
||||||
height 26px
|
|
||||||
navButtonColor()
|
|
||||||
padding 0 10px 0 25px
|
|
||||||
font-size 14px
|
|
||||||
width 100%
|
|
||||||
text-align left
|
|
||||||
line-height 26px
|
|
||||||
box-sizing border-box
|
|
||||||
cursor pointer
|
|
||||||
outline none
|
|
||||||
|
|
||||||
.header-name--active
|
|
||||||
@extend .header-name
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color $ui-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
|
|
||||||
.folderList-item
|
|
||||||
display block
|
|
||||||
width 100%
|
|
||||||
height 26px
|
|
||||||
navButtonColor()
|
|
||||||
padding 0 10px 0 25px
|
|
||||||
font-size 14px
|
|
||||||
width 100%
|
|
||||||
text-align left
|
|
||||||
line-height 26px
|
|
||||||
box-sizing border-box
|
|
||||||
cursor pointer
|
|
||||||
outline none
|
|
||||||
padding 0 10px
|
|
||||||
margin 2px 0
|
|
||||||
height 26px
|
|
||||||
line-height 26px
|
|
||||||
border-width 0 0 0 6px
|
|
||||||
border-style solid
|
|
||||||
border-color transparent
|
|
||||||
|
|
||||||
.folderList-item--active
|
|
||||||
@extend .folderList-item
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color $ui-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
|
||||||
.header-toggleButton
|
|
||||||
navDarkButtonColor()
|
|
||||||
.header-name
|
|
||||||
navDarkButtonColor()
|
|
||||||
|
|
||||||
.header-name--active
|
|
||||||
@extend .header-name
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color $ui-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
|
|
||||||
.folderList-item
|
|
||||||
navDarkButtonColor()
|
|
||||||
border-width 0 0 0 6px
|
|
||||||
border-style solid
|
|
||||||
border-color transparent
|
|
||||||
|
|
||||||
.folderList-item--active
|
|
||||||
@extend .folderList-item
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
color $ui-button--active-color
|
|
||||||
&:hover
|
|
||||||
background-color $ui-button--active-backgroundColor
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
import React, { PropTypes } from 'react'
|
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import { connect, Provider } from 'react-redux'
|
|
||||||
import _ from 'lodash'
|
|
||||||
import ipc from './ipcClient'
|
|
||||||
import store from './store'
|
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
|
||||||
import styles from './FinderMain.styl'
|
|
||||||
import StorageSection from './StorageSection'
|
|
||||||
import NoteList from './NoteList'
|
|
||||||
import NoteDetail from './NoteDetail'
|
|
||||||
import SideNavFilter from 'browser/components/SideNavFilter'
|
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
|
||||||
require('!!style!css!stylus?sourceMap!../main/global.styl')
|
|
||||||
require('../lib/customMeta')
|
|
||||||
|
|
||||||
const electron = require('electron')
|
|
||||||
const { remote } = electron
|
|
||||||
const { Menu } = remote
|
|
||||||
|
|
||||||
function hideFinder () {
|
|
||||||
let finderWindow = remote.getCurrentWindow()
|
|
||||||
if (global.process.platform === 'win32') {
|
|
||||||
finderWindow.blur()
|
|
||||||
finderWindow.hide()
|
|
||||||
}
|
|
||||||
if (global.process.platform === 'darwin') {
|
|
||||||
Menu.sendActionToFirstResponder('hide:')
|
|
||||||
}
|
|
||||||
remote.getCurrentWindow().hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
require('!!style!css!stylus?sourceMap!../styles/finder/index.styl')
|
|
||||||
|
|
||||||
class FinderMain extends React.Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
search: '',
|
|
||||||
index: 0,
|
|
||||||
filter: {
|
|
||||||
includeSnippet: true,
|
|
||||||
includeMarkdown: false,
|
|
||||||
type: 'ALL',
|
|
||||||
storage: null,
|
|
||||||
folder: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.focusHandler = (e) => this.handleWindowFocus(e)
|
|
||||||
this.blurHandler = (e) => this.handleWindowBlur(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
this.refs.search.focus()
|
|
||||||
window.addEventListener('focus', this.focusHandler)
|
|
||||||
window.addEventListener('blur', this.blurHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount () {
|
|
||||||
window.removeEventListener('focus', this.focusHandler)
|
|
||||||
window.removeEventListener('blur', this.blurHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleWindowFocus (e) {
|
|
||||||
this.refs.search.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
handleWindowBlur (e) {
|
|
||||||
this.setState({
|
|
||||||
search: ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyDown (e) {
|
|
||||||
this.refs.search.focus()
|
|
||||||
if (e.keyCode === 9) {
|
|
||||||
if (e.shiftKey) {
|
|
||||||
this.refs.detail.selectPriorSnippet()
|
|
||||||
} else {
|
|
||||||
this.refs.detail.selectNextSnippet()
|
|
||||||
}
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
if (e.keyCode === 38) {
|
|
||||||
this.selectPrevious()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 40) {
|
|
||||||
this.selectNext()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
this.refs.detail.saveToClipboard()
|
|
||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('COPY_FINDER')
|
|
||||||
hideFinder()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
if (e.keyCode === 27) {
|
|
||||||
hideFinder()
|
|
||||||
e.preventDefault()
|
|
||||||
}
|
|
||||||
if (e.keyCode === 91 || e.metaKey) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSearchChange (e) {
|
|
||||||
this.setState({
|
|
||||||
search: e.target.value,
|
|
||||||
index: 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
selectArticle (article) {
|
|
||||||
this.setState({currentArticle: article})
|
|
||||||
}
|
|
||||||
|
|
||||||
selectPrevious () {
|
|
||||||
if (this.state.index > 0) {
|
|
||||||
this.setState({
|
|
||||||
index: this.state.index - 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
selectNext () {
|
|
||||||
if (this.state.index < this.noteCount - 1) {
|
|
||||||
this.setState({
|
|
||||||
index: this.state.index + 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnlySnippetCheckboxChange (e) {
|
|
||||||
let { filter } = this.state
|
|
||||||
filter.includeSnippet = e.target.checked
|
|
||||||
this.setState({
|
|
||||||
filter: filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnlyMarkdownCheckboxChange (e) {
|
|
||||||
let { filter } = this.state
|
|
||||||
filter.includeMarkdown = e.target.checked
|
|
||||||
this.refs.list.resetScroll()
|
|
||||||
this.setState({
|
|
||||||
filter: filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleAllNotesButtonClick (e) {
|
|
||||||
let { filter } = this.state
|
|
||||||
filter.type = 'ALL'
|
|
||||||
this.refs.list.resetScroll()
|
|
||||||
this.setState({
|
|
||||||
filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleStarredButtonClick (e) {
|
|
||||||
let { filter } = this.state
|
|
||||||
filter.type = 'STARRED'
|
|
||||||
this.refs.list.resetScroll()
|
|
||||||
this.setState({
|
|
||||||
filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleStorageButtonClick (e, storage) {
|
|
||||||
let { filter } = this.state
|
|
||||||
filter.type = 'STORAGE'
|
|
||||||
filter.storage = storage
|
|
||||||
this.refs.list.resetScroll()
|
|
||||||
this.setState({
|
|
||||||
filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleFolderButtonClick (e, storage, folder) {
|
|
||||||
let { filter } = this.state
|
|
||||||
filter.type = 'FOLDER'
|
|
||||||
filter.storage = storage
|
|
||||||
filter.folder = folder
|
|
||||||
this.refs.list.resetScroll()
|
|
||||||
this.setState({
|
|
||||||
filter,
|
|
||||||
index: 0
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleNoteClick (e, index) {
|
|
||||||
this.setState({
|
|
||||||
index
|
|
||||||
}, () => {
|
|
||||||
this.refs.search.focus()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
let { data, config } = this.props
|
|
||||||
let { filter, search } = this.state
|
|
||||||
let storageList = []
|
|
||||||
for (let key in data.storageMap) {
|
|
||||||
let storage = data.storageMap[key]
|
|
||||||
let item = (
|
|
||||||
<StorageSection
|
|
||||||
filter={filter}
|
|
||||||
storage={storage}
|
|
||||||
key={storage.key}
|
|
||||||
handleStorageButtonClick={(e, storage) => this.handleStorageButtonClick(e, storage)}
|
|
||||||
handleFolderButtonClick={(e, storage, folder) => this.handleFolderButtonClick(e, storage, folder)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
storageList.push(item)
|
|
||||||
}
|
|
||||||
let notes = []
|
|
||||||
let noteIds
|
|
||||||
|
|
||||||
switch (filter.type) {
|
|
||||||
case 'STORAGE':
|
|
||||||
noteIds = data.storageNoteMap[filter.storage]
|
|
||||||
break
|
|
||||||
case 'FOLDER':
|
|
||||||
noteIds = data.folderNoteMap[filter.storage + '-' + filter.folder]
|
|
||||||
break
|
|
||||||
case 'STARRED':
|
|
||||||
noteIds = data.starredSet
|
|
||||||
}
|
|
||||||
if (noteIds != null) {
|
|
||||||
noteIds.forEach((id) => {
|
|
||||||
notes.push(data.noteMap[id])
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
for (let key in data.noteMap) {
|
|
||||||
notes.push(data.noteMap[key])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filter.includeSnippet && filter.includeMarkdown) {
|
|
||||||
notes = notes.filter((note) => note.type === 'MARKDOWN_NOTE')
|
|
||||||
} else if (filter.includeSnippet && !filter.includeMarkdown) {
|
|
||||||
notes = notes.filter((note) => note.type === 'SNIPPET_NOTE')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search.trim().length > 0) {
|
|
||||||
let needle = new RegExp(_.escapeRegExp(search.trim()), 'i')
|
|
||||||
notes = notes.filter((note) => note.title.match(needle))
|
|
||||||
}
|
|
||||||
notes = notes
|
|
||||||
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
|
|
||||||
|
|
||||||
let activeNote = notes[this.state.index]
|
|
||||||
this.noteCount = notes.length
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='Finder'
|
|
||||||
styleName='root'
|
|
||||||
ref='-1'
|
|
||||||
onKeyDown={(e) => this.handleKeyDown(e)}
|
|
||||||
>
|
|
||||||
<div styleName='search'>
|
|
||||||
<input
|
|
||||||
styleName='search-input'
|
|
||||||
ref='search'
|
|
||||||
value={search}
|
|
||||||
placeholder='Search...'
|
|
||||||
onChange={(e) => this.handleSearchChange(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div styleName='result'>
|
|
||||||
<div styleName='result-nav'>
|
|
||||||
<div styleName='result-nav-filter'>
|
|
||||||
<div styleName='result-nav-filter-option'>
|
|
||||||
<label>
|
|
||||||
<input type='checkbox'
|
|
||||||
checked={filter.includeSnippet}
|
|
||||||
onChange={(e) => this.handleOnlySnippetCheckboxChange(e)}
|
|
||||||
/> Only Snippets</label>
|
|
||||||
</div>
|
|
||||||
<div styleName='result-nav-filter-option'>
|
|
||||||
<label>
|
|
||||||
<input type='checkbox'
|
|
||||||
checked={filter.includeMarkdown}
|
|
||||||
onChange={(e) => this.handleOnlyMarkdownCheckboxChange(e)}
|
|
||||||
/> Only Markdown</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<SideNavFilter
|
|
||||||
isHomeActive={filter.type === 'ALL'}
|
|
||||||
handleAllNotesButtonClick={(e) => this.handleAllNotesButtonClick(e)}
|
|
||||||
isStarredActive={filter.type === 'STARRED'}
|
|
||||||
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
|
|
||||||
/>
|
|
||||||
<div styleName='result-nav-storageList'>
|
|
||||||
{storageList}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<NoteList styleName='result-list'
|
|
||||||
storageMap={data.storageMap}
|
|
||||||
notes={notes}
|
|
||||||
ref='list'
|
|
||||||
search={search}
|
|
||||||
index={this.state.index}
|
|
||||||
handleNoteClick={(e, _index) => this.handleNoteClick(e, _index)}
|
|
||||||
/>
|
|
||||||
<div styleName='result-detail'>
|
|
||||||
<NoteDetail
|
|
||||||
note={activeNote}
|
|
||||||
config={config}
|
|
||||||
ref='detail'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FinderMain.propTypes = {
|
|
||||||
dispatch: PropTypes.func
|
|
||||||
}
|
|
||||||
|
|
||||||
var Finder = connect((x) => x)(CSSModules(FinderMain, styles))
|
|
||||||
|
|
||||||
function refreshData () {
|
|
||||||
// let data = dataStore.getData(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactDOM.render((
|
|
||||||
<Provider store={store}>
|
|
||||||
<Finder />
|
|
||||||
</Provider>
|
|
||||||
), document.getElementById('content'), function () {
|
|
||||||
refreshData()
|
|
||||||
})
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
const nodeIpc = require('node-ipc')
|
|
||||||
const { remote, ipcRenderer } = require('electron')
|
|
||||||
const { app, Menu } = remote
|
|
||||||
const path = require('path')
|
|
||||||
const store = require('./store')
|
|
||||||
const consts = require('browser/lib/consts')
|
|
||||||
|
|
||||||
nodeIpc.config.id = 'finder'
|
|
||||||
nodeIpc.config.retry = 1500
|
|
||||||
nodeIpc.config.silent = true
|
|
||||||
|
|
||||||
function killFinder () {
|
|
||||||
let finderWindow = remote.getCurrentWindow()
|
|
||||||
finderWindow.removeAllListeners()
|
|
||||||
if (global.process.platform === 'darwin') {
|
|
||||||
// Only OSX has another app process.
|
|
||||||
nodeIpc.of.node.emit('quit-from-finder')
|
|
||||||
} else {
|
|
||||||
finderWindow.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleFinder () {
|
|
||||||
let finderWindow = remote.getCurrentWindow()
|
|
||||||
if (global.process.platform === 'darwin') {
|
|
||||||
if (finderWindow.isVisible()) {
|
|
||||||
finderWindow.hide()
|
|
||||||
Menu.sendActionToFirstResponder('hide:')
|
|
||||||
} else {
|
|
||||||
nodeIpc.of.node.emit('request-data-from-finder')
|
|
||||||
finderWindow.show()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (finderWindow.isVisible()) {
|
|
||||||
finderWindow.blur()
|
|
||||||
finderWindow.hide()
|
|
||||||
} else {
|
|
||||||
nodeIpc.of.node.emit('request-data-from-finder')
|
|
||||||
finderWindow.show()
|
|
||||||
finderWindow.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeIpc.connectTo(
|
|
||||||
'node',
|
|
||||||
path.join(app.getPath('userData'), 'boostnote.service'),
|
|
||||||
function () {
|
|
||||||
nodeIpc.of.node.on('error', function (err) {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
nodeIpc.of.node.on('connect', function () {
|
|
||||||
console.log('Conncted successfully')
|
|
||||||
})
|
|
||||||
nodeIpc.of.node.on('disconnect', function () {
|
|
||||||
console.log('disconnected')
|
|
||||||
})
|
|
||||||
|
|
||||||
nodeIpc.of.node.on('open-finder', function () {
|
|
||||||
toggleFinder()
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcRenderer.on('open-finder-from-tray', function () {
|
|
||||||
toggleFinder()
|
|
||||||
})
|
|
||||||
ipcRenderer.on('open-main-from-tray', function () {
|
|
||||||
nodeIpc.of.node.emit('open-main-from-finder')
|
|
||||||
})
|
|
||||||
|
|
||||||
ipcRenderer.on('quit-from-tray', function () {
|
|
||||||
nodeIpc.of.node.emit('quit-from-finder')
|
|
||||||
killFinder()
|
|
||||||
})
|
|
||||||
|
|
||||||
nodeIpc.of.node.on('throttle-data', function (payload) {
|
|
||||||
console.log('Received data from Main renderer')
|
|
||||||
store.default.dispatch({
|
|
||||||
type: 'THROTTLE_DATA',
|
|
||||||
data: payload
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
nodeIpc.of.node.on('config-renew', function (payload) {
|
|
||||||
const { config } = payload
|
|
||||||
if (config.ui.theme === 'dark') {
|
|
||||||
document.body.setAttribute('data-theme', 'dark')
|
|
||||||
} else {
|
|
||||||
document.body.setAttribute('data-theme', 'default')
|
|
||||||
}
|
|
||||||
|
|
||||||
let editorTheme = document.getElementById('editorTheme')
|
|
||||||
if (editorTheme == null) {
|
|
||||||
editorTheme = document.createElement('link')
|
|
||||||
editorTheme.setAttribute('id', 'editorTheme')
|
|
||||||
editorTheme.setAttribute('rel', 'stylesheet')
|
|
||||||
document.head.appendChild(editorTheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
config.editor.theme = consts.THEMES.some((theme) => theme === config.editor.theme)
|
|
||||||
? config.editor.theme
|
|
||||||
: 'default'
|
|
||||||
|
|
||||||
if (config.editor.theme !== 'default') {
|
|
||||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
|
|
||||||
}
|
|
||||||
|
|
||||||
store.default.dispatch({
|
|
||||||
type: 'SET_CONFIG',
|
|
||||||
config: config
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
nodeIpc.of.node.on('quit-finder-app', function () {
|
|
||||||
nodeIpc.of.node.emit('quit-finder-app-confirm')
|
|
||||||
killFinder()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const ipc = {}
|
|
||||||
|
|
||||||
module.exports = ipc
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { combineReducers, createStore } from 'redux'
|
|
||||||
import { routerReducer } from 'react-router-redux'
|
|
||||||
import { DEFAULT_CONFIG } from 'browser/main/lib/ConfigManager'
|
|
||||||
|
|
||||||
let defaultData = {
|
|
||||||
storageMap: {},
|
|
||||||
noteMap: {},
|
|
||||||
starredSet: [],
|
|
||||||
storageNoteMap: {},
|
|
||||||
folderNoteMap: {},
|
|
||||||
tagNoteMap: {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function data (state = defaultData, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'THROTTLE_DATA':
|
|
||||||
console.log(action)
|
|
||||||
state = action.data
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
function config (state = DEFAULT_CONFIG, action) {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'INIT_CONFIG':
|
|
||||||
case 'SET_CONFIG':
|
|
||||||
return Object.assign({}, state, action.config)
|
|
||||||
case 'SET_IS_SIDENAV_FOLDED':
|
|
||||||
state.isSideNavFolded = action.isFolded
|
|
||||||
return Object.assign({}, state)
|
|
||||||
case 'SET_ZOOM':
|
|
||||||
state.zoom = action.zoom
|
|
||||||
return Object.assign({}, state)
|
|
||||||
case 'SET_LIST_WIDTH':
|
|
||||||
state.listWidth = action.listWidth
|
|
||||||
return Object.assign({}, state)
|
|
||||||
case 'SET_UI':
|
|
||||||
return Object.assign({}, state, action.config)
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
let reducer = combineReducers({
|
|
||||||
data,
|
|
||||||
config,
|
|
||||||
routing: routerReducer
|
|
||||||
})
|
|
||||||
|
|
||||||
let store = createStore(reducer)
|
|
||||||
|
|
||||||
export default store
|
|
||||||
78
browser/lib/Languages.js
Normal file
78
browser/lib/Languages.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
const languages = [
|
||||||
|
{
|
||||||
|
name: 'Albanian',
|
||||||
|
locale: 'sq'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chinese (zh-CN)',
|
||||||
|
locale: 'zh-CN'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Chinese (zh-TW)',
|
||||||
|
locale: 'zh-TW'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Danish',
|
||||||
|
locale: 'da'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'English',
|
||||||
|
locale: 'en'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'French',
|
||||||
|
locale: 'fr'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'German',
|
||||||
|
locale: 'de'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Hungarian',
|
||||||
|
locale: 'hu'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Japanese',
|
||||||
|
locale: 'ja'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Korean',
|
||||||
|
locale: 'ko'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Norwegian',
|
||||||
|
locale: 'no'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Polish',
|
||||||
|
locale: 'pl'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Portuguese',
|
||||||
|
locale: 'pt'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Russian',
|
||||||
|
locale: 'ru'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Spanish',
|
||||||
|
locale: 'es-ES'
|
||||||
|
}, {
|
||||||
|
name: 'Turkish',
|
||||||
|
locale: 'tr'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getLocales () {
|
||||||
|
return languages.reduce(function (localeList, locale) {
|
||||||
|
localeList.push(locale.locale)
|
||||||
|
return localeList
|
||||||
|
}, [])
|
||||||
|
},
|
||||||
|
getLanguages () {
|
||||||
|
return languages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -38,15 +38,15 @@ class MutableMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
map (cb) {
|
map (cb) {
|
||||||
let result = []
|
const result = []
|
||||||
for (let [key, value] of this._map) {
|
for (const [key, value] of this._map) {
|
||||||
result.push(cb(value, key))
|
result.push(cb(value, key))
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
toJS () {
|
toJS () {
|
||||||
let result = {}
|
const result = {}
|
||||||
for (let [key, value] of this._map) {
|
for (let [key, value] of this._map) {
|
||||||
if (value instanceof MutableSet || value instanceof MutableMap) {
|
if (value instanceof MutableSet || value instanceof MutableMap) {
|
||||||
value = value.toJS()
|
value = value.toJS()
|
||||||
@@ -85,7 +85,7 @@ class MutableSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
map (cb) {
|
map (cb) {
|
||||||
let result = []
|
const result = []
|
||||||
this._set.forEach(function (value, key) {
|
this._set.forEach(function (value, key) {
|
||||||
result.push(cb(value, key))
|
result.push(cb(value, key))
|
||||||
})
|
})
|
||||||
|
|||||||
21
browser/lib/RcParser.js
Normal file
21
browser/lib/RcParser.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import path from 'path'
|
||||||
|
import sander from 'sander'
|
||||||
|
|
||||||
|
const BOOSTNOTERC = '.boostnoterc'
|
||||||
|
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
|
||||||
|
const _boostnotercPath = path.join(homePath, BOOSTNOTERC)
|
||||||
|
|
||||||
|
export function parse (boostnotercPath = _boostnotercPath) {
|
||||||
|
if (!sander.existsSync(boostnotercPath)) return {}
|
||||||
|
try {
|
||||||
|
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e)
|
||||||
|
console.warn('Your .boostnoterc is broken so it\'s not used.')
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
parse
|
||||||
|
}
|
||||||
53
browser/lib/TextEditorInterface.js
Normal file
53
browser/lib/TextEditorInterface.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { Point } from '@susisu/mte-kernel'
|
||||||
|
|
||||||
|
export default class TextEditorInterface {
|
||||||
|
constructor (editor) {
|
||||||
|
this.editor = editor
|
||||||
|
}
|
||||||
|
|
||||||
|
getCursorPosition () {
|
||||||
|
const pos = this.editor.getCursor()
|
||||||
|
return new Point(pos.line, pos.ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
setCursorPosition (pos) {
|
||||||
|
this.editor.setCursor({line: pos.row, ch: pos.column})
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectionRange (range) {
|
||||||
|
this.editor.setSelection({
|
||||||
|
anchor: {line: range.start.row, ch: range.start.column},
|
||||||
|
head: {line: range.end.row, ch: range.end.column}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastRow () {
|
||||||
|
return this.editor.lastLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
acceptsTableEdit (row) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
getLine (row) {
|
||||||
|
return this.editor.getLine(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
insertLine (row, line) {
|
||||||
|
this.editor.replaceRange(line, {line: row, ch: 0})
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteLine (row) {
|
||||||
|
this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length})
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceLines (startRow, endRow, lines) {
|
||||||
|
endRow-- // because endRow is a first line after a table.
|
||||||
|
const endRowCh = this.editor.getLine(endRow).length
|
||||||
|
this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh})
|
||||||
|
}
|
||||||
|
|
||||||
|
transact (func) {
|
||||||
|
func()
|
||||||
|
}
|
||||||
|
}
|
||||||
23
browser/lib/confirmDeleteNote.js
Normal file
23
browser/lib/confirmDeleteNote.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import electron from 'electron'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
const { remote } = electron
|
||||||
|
const { dialog } = remote
|
||||||
|
|
||||||
|
export function confirmDeleteNote (confirmDeletion, permanent) {
|
||||||
|
if (confirmDeletion || permanent) {
|
||||||
|
const alertConfig = {
|
||||||
|
ype: 'warning',
|
||||||
|
message: i18n.__('Confirm note deletion'),
|
||||||
|
detail: i18n.__('This will permanently remove this note.'),
|
||||||
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogButtonIndex = dialog.showMessageBox(
|
||||||
|
remote.getCurrentWindow(), alertConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
return dialogButtonIndex === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -10,6 +10,11 @@ const themes = fs.readdirSync(themePath)
|
|||||||
.map((themePath) => {
|
.map((themePath) => {
|
||||||
return themePath.substring(0, themePath.lastIndexOf('.'))
|
return themePath.substring(0, themePath.lastIndexOf('.'))
|
||||||
})
|
})
|
||||||
|
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
|
||||||
|
|
||||||
|
const 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: [
|
||||||
@@ -30,7 +35,16 @@ const consts = {
|
|||||||
'Dodger Blue',
|
'Dodger Blue',
|
||||||
'Violet Eggplant'
|
'Violet Eggplant'
|
||||||
],
|
],
|
||||||
THEMES: ['default'].concat(themes)
|
THEMES: ['default'].concat(themes),
|
||||||
|
SNIPPET_FILE: snippetFile,
|
||||||
|
DEFAULT_EDITOR_FONT_FAMILY: [
|
||||||
|
'Monaco',
|
||||||
|
'Menlo',
|
||||||
|
'Ubuntu Mono',
|
||||||
|
'Consolas',
|
||||||
|
'source-code-pro',
|
||||||
|
'monospace'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = consts
|
module.exports = consts
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const { remote } = require('electron')
|
|||||||
const { Menu, MenuItem } = remote
|
const { Menu, MenuItem } = remote
|
||||||
|
|
||||||
function popup (templates) {
|
function popup (templates) {
|
||||||
let menu = new Menu()
|
const menu = new Menu()
|
||||||
templates.forEach((item) => {
|
templates.forEach((item) => {
|
||||||
menu.append(new MenuItem(item))
|
menu.append(new MenuItem(item))
|
||||||
})
|
})
|
||||||
|
|||||||
14
browser/lib/convertModeName.js
Normal file
14
browser/lib/convertModeName.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export default function convertModeName (name) {
|
||||||
|
switch (name) {
|
||||||
|
case 'ejs':
|
||||||
|
return 'Embedded Javascript'
|
||||||
|
case 'html_ruby':
|
||||||
|
return 'Embedded Ruby'
|
||||||
|
case 'objectivec':
|
||||||
|
return 'Objective C'
|
||||||
|
case 'text':
|
||||||
|
return 'Plain Text'
|
||||||
|
default:
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
import 'codemirror-mode-elixir'
|
||||||
|
|
||||||
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
|
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
|
||||||
|
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
export function findNoteTitle (value) {
|
export function findNoteTitle (value) {
|
||||||
let 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) => {
|
splitted.some((line, index) => {
|
||||||
let trimmedLine = line.trim()
|
const trimmedLine = line.trim()
|
||||||
let trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
|
||||||
if (trimmedLine.match('```')) {
|
if (trimmedLine.match('```')) {
|
||||||
isInsideCodeBlock = !isInsideCodeBlock
|
isInsideCodeBlock = !isInsideCodeBlock
|
||||||
}
|
}
|
||||||
|
|||||||
14
browser/lib/findStorage.js
Normal file
14
browser/lib/findStorage.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const _ = require('lodash')
|
||||||
|
|
||||||
|
export function findStorage (storageKey) {
|
||||||
|
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||||
|
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||||
|
const storage = _.find(cachedStorageList, {key: storageKey})
|
||||||
|
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
||||||
|
|
||||||
|
return storage
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
findStorage
|
||||||
|
}
|
||||||
25
browser/lib/getTodoStatus.js
Normal file
25
browser/lib/getTodoStatus.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export function getTodoStatus (content) {
|
||||||
|
const splitted = content.split('\n')
|
||||||
|
let numberOfTodo = 0
|
||||||
|
let numberOfCompletedTodo = 0
|
||||||
|
|
||||||
|
splitted.forEach((line) => {
|
||||||
|
const trimmedLine = line.trim()
|
||||||
|
if (trimmedLine.match(/^[\+\-\*] \[(\s|x)\] ./)) {
|
||||||
|
numberOfTodo++
|
||||||
|
}
|
||||||
|
if (trimmedLine.match(/^[\+\-\*] \[x\] ./)) {
|
||||||
|
numberOfCompletedTodo++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: numberOfTodo,
|
||||||
|
completed: numberOfCompletedTodo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTodoPercentageOfCompleted (content) {
|
||||||
|
const state = getTodoStatus(content)
|
||||||
|
return Math.floor(state.completed / state.total * 100)
|
||||||
|
}
|
||||||
17
browser/lib/i18n.js
Normal file
17
browser/lib/i18n.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const path = require('path')
|
||||||
|
const { remote } = require('electron')
|
||||||
|
const { app } = remote
|
||||||
|
const { getLocales } = require('./Languages.js')
|
||||||
|
|
||||||
|
// load package for localization
|
||||||
|
const i18n = new (require('i18n-2'))({
|
||||||
|
// setup some locales - other locales default to the first locale
|
||||||
|
locales: getLocales(),
|
||||||
|
extension: '.json',
|
||||||
|
directory: process.env.NODE_ENV === 'production'
|
||||||
|
? path.join(app.getAppPath(), './locales')
|
||||||
|
: path.resolve('./locales'),
|
||||||
|
devMode: false
|
||||||
|
})
|
||||||
|
|
||||||
|
export default i18n
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
const uuidv4 = require('uuid/v4')
|
||||||
|
|
||||||
module.exports = function (length) {
|
module.exports = function (uuid) {
|
||||||
if (!_.isFinite(length)) length = 10
|
if (typeof uuid === typeof true && uuid) {
|
||||||
|
return uuidv4()
|
||||||
|
}
|
||||||
|
const length = 10
|
||||||
return crypto.randomBytes(length).toString('hex')
|
return crypto.randomBytes(length).toString('hex')
|
||||||
}
|
}
|
||||||
|
|||||||
26
browser/lib/markdown-it-sanitize-html.js
Normal file
26
browser/lib/markdown-it-sanitize-html.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
'use strict'
|
||||||
|
|
||||||
|
import sanitizeHtml from 'sanitize-html'
|
||||||
|
|
||||||
|
module.exports = function sanitizePlugin (md, options) {
|
||||||
|
options = options || {}
|
||||||
|
|
||||||
|
md.core.ruler.after('linkify', 'sanitize_inline', state => {
|
||||||
|
for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) {
|
||||||
|
if (state.tokens[tokenIdx].type === 'html_block') {
|
||||||
|
state.tokens[tokenIdx].content = sanitizeHtml(state.tokens[tokenIdx].content, options)
|
||||||
|
}
|
||||||
|
if (state.tokens[tokenIdx].type === 'fence') {
|
||||||
|
state.tokens[tokenIdx].content = state.tokens[tokenIdx].content.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
||||||
|
}
|
||||||
|
if (state.tokens[tokenIdx].type === 'inline') {
|
||||||
|
const inlineTokens = state.tokens[tokenIdx].children
|
||||||
|
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
|
||||||
|
if (inlineTokens[childIdx].type === 'html_inline') {
|
||||||
|
inlineTokens[childIdx].content = sanitizeHtml(inlineTokens[childIdx].content, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,167 +1,253 @@
|
|||||||
import markdownit from 'markdown-it'
|
import markdownit from 'markdown-it'
|
||||||
|
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 smartArrows from 'markdown-it-smartarrows'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
|
import katex from 'katex'
|
||||||
|
import { lastFindInArray } from './utils'
|
||||||
|
|
||||||
const katex = window.katex
|
function createGutter (str, firstLineNumber) {
|
||||||
|
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
|
||||||
function createGutter (str) {
|
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
|
||||||
let lc = (str.match(/\n/g) || []).length
|
const lines = []
|
||||||
let lines = []
|
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
|
||||||
for (let i = 1; i <= lc; 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>'
|
||||||
}
|
}
|
||||||
|
|
||||||
var md = markdownit({
|
class Markdown {
|
||||||
typographer: true,
|
constructor (options = {}) {
|
||||||
linkify: true,
|
const config = ConfigManager.get()
|
||||||
html: true,
|
const defaultOptions = {
|
||||||
xhtmlOut: true,
|
typographer: config.preview.smartQuotes,
|
||||||
breaks: true,
|
linkify: true,
|
||||||
highlight: function (str, lang) {
|
html: true,
|
||||||
if (lang === 'flowchart') {
|
xhtmlOut: true,
|
||||||
return `<pre class="flowchart">${str}</pre>`
|
breaks: config.preview.breaks,
|
||||||
}
|
highlight: function (str, lang) {
|
||||||
if (lang === 'sequence') {
|
const delimiter = ':'
|
||||||
return `<pre class="sequence">${str}</pre>`
|
const langInfo = lang.split(delimiter)
|
||||||
}
|
const langType = langInfo[0]
|
||||||
return '<pre class="code">' +
|
const fileName = langInfo[1] || ''
|
||||||
createGutter(str) +
|
const firstLineNumber = parseInt(langInfo[2], 10)
|
||||||
'<code class="' + lang + '">' +
|
|
||||||
str +
|
|
||||||
'</code></pre>'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
md.use(emoji, {
|
|
||||||
shortcuts: {}
|
|
||||||
})
|
|
||||||
md.use(math, {
|
|
||||||
inlineRenderer: function (str) {
|
|
||||||
let output = ''
|
|
||||||
try {
|
|
||||||
output = katex.renderToString(str.trim())
|
|
||||||
} catch (err) {
|
|
||||||
output = `<span class="katex-error">${err.message}</span>`
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
},
|
|
||||||
blockRenderer: function (str) {
|
|
||||||
let output = ''
|
|
||||||
try {
|
|
||||||
output = katex.renderToString(str.trim(), {displayMode: true})
|
|
||||||
} catch (err) {
|
|
||||||
output = `<div class="katex-error">${err.message}</div>`
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
})
|
|
||||||
md.use(require('markdown-it-imsize'))
|
|
||||||
md.use(require('markdown-it-footnote'))
|
|
||||||
// Override task item
|
|
||||||
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
|
||||||
let content, terminate, i, l, token
|
|
||||||
let nextLine = startLine + 1
|
|
||||||
let terminatorRules = state.md.block.ruler.getRules('paragraph')
|
|
||||||
let endLine = state.lineMax
|
|
||||||
|
|
||||||
// jump line-by-line until empty one or EOF
|
if (langType === 'flowchart') {
|
||||||
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
return `<pre class="flowchart">${str}</pre>`
|
||||||
// this would be a code block normally, but after paragraph
|
}
|
||||||
// it's considered a lazy continuation regardless of what's there
|
if (langType === 'sequence') {
|
||||||
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
|
return `<pre class="sequence">${str}</pre>`
|
||||||
|
}
|
||||||
|
if (langType === 'chart') {
|
||||||
|
return `<pre class="chart">${str}</pre>`
|
||||||
|
}
|
||||||
|
if (langType === 'mermaid') {
|
||||||
|
return `<pre class="mermaid">${str}</pre>`
|
||||||
|
}
|
||||||
|
return '<pre class="code CodeMirror">' +
|
||||||
|
'<span class="filename">' + fileName + '</span>' +
|
||||||
|
createGutter(str, firstLineNumber) +
|
||||||
|
'<code class="' + langType + '">' +
|
||||||
|
str +
|
||||||
|
'</code></pre>'
|
||||||
|
},
|
||||||
|
sanitize: 'STRICT'
|
||||||
|
}
|
||||||
|
|
||||||
// quirk for blockquotes, this line should already be checked by that rule
|
const updatedOptions = Object.assign(defaultOptions, options)
|
||||||
if (state.sCount[nextLine] < 0) { continue }
|
this.md = markdownit(updatedOptions)
|
||||||
|
|
||||||
// Some tags can terminate paragraph without empty line.
|
if (updatedOptions.sanitize !== 'NONE') {
|
||||||
terminate = false
|
const allowedTags = ['iframe', 'input', 'b',
|
||||||
for (i = 0, l = terminatorRules.length; i < l; i++) {
|
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
|
||||||
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote',
|
||||||
terminate = true
|
'dl', 'dt', 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details'
|
||||||
break
|
]
|
||||||
|
const allowedAttributes = [
|
||||||
|
'abbr', 'accept', 'accept-charset',
|
||||||
|
'accesskey', 'action', 'align', 'alt', 'axis',
|
||||||
|
'border', 'cellpadding', 'cellspacing', 'char',
|
||||||
|
'charoff', 'charset', 'checked',
|
||||||
|
'clear', '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') {
|
||||||
|
allowedTags.push('style')
|
||||||
|
allowedAttributes.push('style')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sanitize use rinput before other plugins
|
||||||
|
this.md.use(sanitize, {
|
||||||
|
allowedTags,
|
||||||
|
allowedAttributes: {
|
||||||
|
'*': allowedAttributes,
|
||||||
|
'a': ['href'],
|
||||||
|
'div': ['itemscope', 'itemtype'],
|
||||||
|
'blockquote': ['cite'],
|
||||||
|
'del': ['cite'],
|
||||||
|
'ins': ['cite'],
|
||||||
|
'q': ['cite'],
|
||||||
|
'img': ['src', 'width', 'height'],
|
||||||
|
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
|
||||||
|
'input': ['type', 'id', 'checked']
|
||||||
|
},
|
||||||
|
allowedIframeHostnames: ['www.youtube.com']
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if (terminate) { break }
|
|
||||||
|
this.md.use(emoji, {
|
||||||
|
shortcuts: {}
|
||||||
|
})
|
||||||
|
this.md.use(math, {
|
||||||
|
inlineOpen: config.preview.latexInlineOpen,
|
||||||
|
inlineClose: config.preview.latexInlineClose,
|
||||||
|
blockOpen: config.preview.latexBlockOpen,
|
||||||
|
blockClose: config.preview.latexBlockClose,
|
||||||
|
inlineRenderer: function (str) {
|
||||||
|
let output = ''
|
||||||
|
try {
|
||||||
|
output = katex.renderToString(str.trim())
|
||||||
|
} catch (err) {
|
||||||
|
output = `<span class="katex-error">${err.message}</span>`
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
},
|
||||||
|
blockRenderer: function (str) {
|
||||||
|
let output = ''
|
||||||
|
try {
|
||||||
|
output = katex.renderToString(str.trim(), { displayMode: true })
|
||||||
|
} catch (err) {
|
||||||
|
output = `<div class="katex-error">${err.message}</div>`
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.md.use(require('markdown-it-imsize'))
|
||||||
|
this.md.use(require('markdown-it-footnote'))
|
||||||
|
this.md.use(require('markdown-it-multimd-table'))
|
||||||
|
this.md.use(require('markdown-it-named-headers'), {
|
||||||
|
slugify: (header) => {
|
||||||
|
return encodeURI(header.trim()
|
||||||
|
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
|
||||||
|
.replace(/\s+/g, '-'))
|
||||||
|
.replace(/\-+$/, '')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.md.use(require('markdown-it-kbd'))
|
||||||
|
this.md.use(require('markdown-it-admonition'))
|
||||||
|
|
||||||
|
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||||
|
this.md.use(require('markdown-it-plantuml'), '', {
|
||||||
|
generateSource: function (umlCode) {
|
||||||
|
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
|
||||||
|
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
|
||||||
|
const s = unescape(encodeURIComponent(umlCode))
|
||||||
|
const zippedCode = deflate.encode64(
|
||||||
|
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
|
||||||
|
)
|
||||||
|
return `${serverAddress}/${zippedCode}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Override task item
|
||||||
|
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
|
||||||
|
let content, terminate, i, l, token
|
||||||
|
let nextLine = startLine + 1
|
||||||
|
const terminatorRules = state.md.block.ruler.getRules('paragraph')
|
||||||
|
const endLine = state.lineMax
|
||||||
|
|
||||||
|
// jump line-by-line until empty one or EOF
|
||||||
|
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
|
||||||
|
// this would be a code block normally, but after paragraph
|
||||||
|
// it's considered a lazy continuation regardless of what's there
|
||||||
|
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
|
||||||
|
|
||||||
|
// quirk for blockquotes, this line should already be checked by that rule
|
||||||
|
if (state.sCount[nextLine] < 0) { continue }
|
||||||
|
|
||||||
|
// Some tags can terminate paragraph without empty line.
|
||||||
|
terminate = false
|
||||||
|
for (i = 0, l = terminatorRules.length; i < l; i++) {
|
||||||
|
if (terminatorRules[i](state, nextLine, endLine, true)) {
|
||||||
|
terminate = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (terminate) { break }
|
||||||
|
}
|
||||||
|
|
||||||
|
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
|
||||||
|
|
||||||
|
state.line = nextLine
|
||||||
|
|
||||||
|
token = state.push('paragraph_open', 'p', 1)
|
||||||
|
token.map = [startLine, state.line]
|
||||||
|
|
||||||
|
if (state.parentType === 'list') {
|
||||||
|
const match = content.match(/^\[( |x)\] ?(.+)/i)
|
||||||
|
if (match) {
|
||||||
|
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
|
||||||
|
if (liToken) {
|
||||||
|
if (!liToken.attrs) {
|
||||||
|
liToken.attrs = []
|
||||||
|
}
|
||||||
|
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>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token = state.push('inline', '', 0)
|
||||||
|
token.content = content
|
||||||
|
token.map = [startLine, state.line]
|
||||||
|
token.children = []
|
||||||
|
|
||||||
|
token = state.push('paragraph_close', 'p', -1)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if (config.preview.smartArrows) {
|
||||||
|
this.md.use(smartArrows)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add line number attribute for scrolling
|
||||||
|
const originalRender = this.md.renderer.render
|
||||||
|
this.md.renderer.render = (tokens, options, env) => {
|
||||||
|
tokens.forEach((token) => {
|
||||||
|
switch (token.type) {
|
||||||
|
case 'heading_open':
|
||||||
|
case 'paragraph_open':
|
||||||
|
case 'blockquote_open':
|
||||||
|
case 'table_open':
|
||||||
|
token.attrPush(['data-line', token.map[0]])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const result = originalRender.call(this.md.renderer, tokens, options, env)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
// FIXME We should not depend on global variable.
|
||||||
|
window.md = this.md
|
||||||
}
|
}
|
||||||
|
|
||||||
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
|
render (content) {
|
||||||
|
|
||||||
state.line = nextLine
|
|
||||||
|
|
||||||
token = state.push('paragraph_open', 'p', 1)
|
|
||||||
token.map = [ startLine, state.line ]
|
|
||||||
|
|
||||||
if (state.parentType === 'list') {
|
|
||||||
let match = content.match(/^\[( |x)\] ?(.+)/i)
|
|
||||||
if (match) {
|
|
||||||
content = `<label class='taskListItem' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
token = state.push('inline', '', 0)
|
|
||||||
token.content = content
|
|
||||||
token.map = [ startLine, state.line ]
|
|
||||||
token.children = []
|
|
||||||
|
|
||||||
token = state.push('paragraph_close', 'p', -1)
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add line number attribute for scrolling
|
|
||||||
let originalRender = md.renderer.render
|
|
||||||
md.renderer.render = function render (tokens, options, env) {
|
|
||||||
tokens.forEach((token) => {
|
|
||||||
switch (token.type) {
|
|
||||||
case 'heading_open':
|
|
||||||
case 'paragraph_open':
|
|
||||||
case 'blockquote_open':
|
|
||||||
case 'table_open':
|
|
||||||
token.attrPush(['data-line', token.map[0]])
|
|
||||||
}
|
|
||||||
})
|
|
||||||
let result = originalRender.call(md.renderer, tokens, options, env)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
window.md = md
|
|
||||||
|
|
||||||
function strip (input) {
|
|
||||||
var output = input
|
|
||||||
try {
|
|
||||||
output = output
|
|
||||||
.replace(/^([\s\t]*)([\*\-\+]|\d\.)\s+/gm, '$1')
|
|
||||||
.replace(/\n={2,}/g, '\n')
|
|
||||||
.replace(/~~/g, '')
|
|
||||||
.replace(/`{3}.*\n/g, '')
|
|
||||||
.replace(/<(.*?)>/g, '$1')
|
|
||||||
.replace(/^[=\-]{2,}\s*$/g, '')
|
|
||||||
.replace(/\[\^.+?\](: .*?$)?/g, '')
|
|
||||||
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
|
|
||||||
.replace(/!\[.*?\][\[\(].*?[\]\)]/g, '')
|
|
||||||
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
|
||||||
.replace(/>/g, '')
|
|
||||||
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
|
||||||
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
|
|
||||||
.replace(/([\*_]{1,3})(\S.*?\S)\1/g, '$2')
|
|
||||||
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
|
||||||
.replace(/^-{3,}\s*$/g, '')
|
|
||||||
.replace(/`(.+?)`/g, '$1')
|
|
||||||
.replace(/\n{2,}/g, '\n\n')
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
const markdown = {
|
|
||||||
render: function markdown (content) {
|
|
||||||
if (!_.isString(content)) content = ''
|
if (!_.isString(content)) content = ''
|
||||||
const renderedContent = md.render(content)
|
return this.md.render(content)
|
||||||
return md.normalizeLinkText(renderedContent)
|
}
|
||||||
},
|
|
||||||
strip
|
|
||||||
}
|
}
|
||||||
export default markdown
|
|
||||||
|
export default Markdown
|
||||||
|
|||||||
0
browser/lib/markdown2.js
Normal file
0
browser/lib/markdown2.js
Normal file
39
browser/lib/markdownTextHelper.js
Normal file
39
browser/lib/markdownTextHelper.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview Text trimmer for markdown note.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} input
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
export function strip (input) {
|
||||||
|
let output = input
|
||||||
|
try {
|
||||||
|
output = output
|
||||||
|
.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, '$1')
|
||||||
|
.replace(/\n={2,}/g, '\n')
|
||||||
|
.replace(/~~/g, '')
|
||||||
|
.replace(/`{3}.*\n/g, '')
|
||||||
|
.replace(/<(.*?)>/g, '$1')
|
||||||
|
.replace(/^[=\-]{2,}\s*$/g, '')
|
||||||
|
.replace(/\[\^.+?\](: .*?$)?/g, '')
|
||||||
|
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
|
||||||
|
.replace(/!\[.*?\][\[\(].*?[\]\)]/g, '')
|
||||||
|
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
|
||||||
|
.replace(/>/g, '')
|
||||||
|
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
|
||||||
|
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
|
||||||
|
.replace(/(`{3,})(.*?)\1/gm, '$2')
|
||||||
|
.replace(/^-{3,}\s*$/g, '')
|
||||||
|
.replace(/`(.+?)`/g, '$1')
|
||||||
|
.replace(/\n{2,}/g, '\n\n')
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
strip
|
||||||
|
}
|
||||||
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,42 +1,31 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
export default function searchFromNotes (data, search) {
|
export default function searchFromNotes (notes, search) {
|
||||||
let notes = data.noteMap.map((note) => note)
|
|
||||||
if (search.trim().length === 0) return []
|
if (search.trim().length === 0) return []
|
||||||
let searchBlocks = search.split(' ')
|
const searchBlocks = search.split(' ').filter(block => { return block !== '' })
|
||||||
|
|
||||||
|
let foundNotes = notes
|
||||||
searchBlocks.forEach((block) => {
|
searchBlocks.forEach((block) => {
|
||||||
if (block.match(/^#.+/)) {
|
foundNotes = findByWordOrTag(foundNotes, block)
|
||||||
notes = findByTag(notes, block)
|
|
||||||
} else {
|
|
||||||
notes = findByWord(notes, block)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return notes
|
return foundNotes
|
||||||
}
|
}
|
||||||
|
|
||||||
function findByTag (notes, block) {
|
function findByWordOrTag (notes, block) {
|
||||||
const tag = block.match(/#(.+)/)[1]
|
let tag = block
|
||||||
let regExp = new RegExp(_.escapeRegExp(tag), 'i')
|
if (tag.match(/^#.+/)) {
|
||||||
|
tag = tag.match(/#(.+)/)[1]
|
||||||
|
}
|
||||||
|
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
|
||||||
|
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
|
||||||
return notes.filter((note) => {
|
return notes.filter((note) => {
|
||||||
if (!_.isArray(note.tags)) return false
|
if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) {
|
||||||
return note.tags.some((_tag) => {
|
|
||||||
return _tag.match(regExp)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function findByWord (notes, block) {
|
|
||||||
let regExp = new RegExp(_.escapeRegExp(block), 'i')
|
|
||||||
return notes.filter((note) => {
|
|
||||||
if (_.isArray(note.tags) && note.tags.some((_tag) => {
|
|
||||||
return _tag.match(regExp)
|
|
||||||
})) {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (note.type === 'SNIPPET_NOTE') {
|
if (note.type === 'SNIPPET_NOTE') {
|
||||||
return note.description.match(regExp)
|
return note.description.match(wordRegExp)
|
||||||
} else if (note.type === 'MARKDOWN_NOTE') {
|
} else if (note.type === 'MARKDOWN_NOTE') {
|
||||||
return note.content.match(regExp)
|
return note.content.match(wordRegExp)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|||||||
90
browser/lib/utils.js
Normal file
90
browser/lib/utils.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
export function lastFindInArray (array, callback) {
|
||||||
|
for (let i = array.length - 1; i >= 0; --i) {
|
||||||
|
if (callback(array[i], i, array)) {
|
||||||
|
return array[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function escapeHtmlCharacters (html, opt = { detectCodeBlock: false }) {
|
||||||
|
const matchHtmlRegExp = /["'&<>]/g
|
||||||
|
const escapes = ['"', '&', ''', '<', '>']
|
||||||
|
let match = null
|
||||||
|
const replaceAt = (str, index, replace) =>
|
||||||
|
str.substr(0, index) +
|
||||||
|
replace +
|
||||||
|
str.substr(index + replace.length - (replace.length - 1))
|
||||||
|
|
||||||
|
// detecting code block
|
||||||
|
while ((match = matchHtmlRegExp.exec(html)) != null) {
|
||||||
|
const current = { char: match[0], index: match.index }
|
||||||
|
if (opt.detectCodeBlock) {
|
||||||
|
// position of the nearest line start
|
||||||
|
let previousLineEnd = current.index - 1
|
||||||
|
while (html[previousLineEnd] !== '\n' && previousLineEnd !== -1) {
|
||||||
|
previousLineEnd--
|
||||||
|
}
|
||||||
|
// 4 spaces means this character is in a code block
|
||||||
|
if (
|
||||||
|
html[previousLineEnd + 1] === ' ' &&
|
||||||
|
html[previousLineEnd + 2] === ' ' &&
|
||||||
|
html[previousLineEnd + 3] === ' ' &&
|
||||||
|
html[previousLineEnd + 4] === ' '
|
||||||
|
) {
|
||||||
|
// so skip it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// otherwise, escape it !!!
|
||||||
|
if (current.char === '&') {
|
||||||
|
let nextStr = ''
|
||||||
|
let nextIndex = current.index
|
||||||
|
let escapedStr = false
|
||||||
|
// maximum length of an escape string is 5. For example ('"')
|
||||||
|
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 === "'") {
|
||||||
|
html = replaceAt(html, current.index, ''')
|
||||||
|
} else if (current.char === '<') {
|
||||||
|
html = replaceAt(html, current.index, '<')
|
||||||
|
} else if (current.char === '>') {
|
||||||
|
html = replaceAt(html, current.index, '>')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return html
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isObjectEqual (a, b) {
|
||||||
|
const aProps = Object.getOwnPropertyNames(a)
|
||||||
|
const bProps = Object.getOwnPropertyNames(b)
|
||||||
|
|
||||||
|
if (aProps.length !== bProps.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < aProps.length; i++) {
|
||||||
|
const propName = aProps[i]
|
||||||
|
if (a[propName] !== b[propName]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
lastFindInArray,
|
||||||
|
escapeHtmlCharacters,
|
||||||
|
isObjectEqual
|
||||||
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
.root
|
.root
|
||||||
absolute top bottom right
|
absolute top bottom right
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
|
||||||
.empty
|
.empty
|
||||||
height 320px
|
height 320px
|
||||||
@@ -8,13 +11,29 @@
|
|||||||
|
|
||||||
.empty-message
|
.empty-message
|
||||||
width 100%
|
width 100%
|
||||||
font-size 42px
|
font-size 36px
|
||||||
line-height 72px
|
font-weight 600
|
||||||
|
line-height 56px
|
||||||
text-align center
|
text-align center
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
background-color $ui-dark-backgroundColor
|
background-color $ui-dark-backgroundColor
|
||||||
|
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"]
|
||||||
|
.root
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
border-left 1px solid $ui-monokai-borderColor
|
||||||
|
.empty-message
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Margin on the left side and the right side for NoteDetail component.
|
// Margin on the left side and the right side for NoteDetail component.
|
||||||
$note-detail-left-margin = 25px
|
$note-detail-left-margin = 100px
|
||||||
$note-detail-right-margin = 25px
|
$note-detail-right-margin = 120px
|
||||||
|
$snippet-note-detail-left-margin = 60px
|
||||||
|
$snippet-note-detail-right-margin = 80px
|
||||||
|
|
||||||
$note-detail-box-shadow = 2px 0 15px -8px #b1b1b1 inset
|
$note-detail-box-shadow = 2px 0 15px -8px #b1b1b1 inset
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './FolderSelect.styl'
|
import styles from './FolderSelect.styl'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class FolderSelect extends React.Component {
|
class FolderSelect extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -73,8 +75,8 @@ class FolderSelect extends React.Component {
|
|||||||
case 9:
|
case 9:
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
let tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
|
const tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
|
||||||
let previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
const previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
|
||||||
if (previousEl != null) previousEl.focus()
|
if (previousEl != null) previousEl.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,9 +91,9 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSearchInputChange (e) {
|
handleSearchInputChange (e) {
|
||||||
let { folders } = this.props
|
const { folders } = this.props
|
||||||
let search = this.refs.search.value
|
const search = this.refs.search.value
|
||||||
let optionIndex = search.length > 0
|
const optionIndex = search.length > 0
|
||||||
? _.findIndex(folders, (folder) => {
|
? _.findIndex(folders, (folder) => {
|
||||||
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
|
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
|
||||||
})
|
})
|
||||||
@@ -129,7 +131,7 @@ class FolderSelect extends React.Component {
|
|||||||
|
|
||||||
nextOption () {
|
nextOption () {
|
||||||
let { optionIndex } = this.state
|
let { optionIndex } = this.state
|
||||||
let { folders } = this.props
|
const { folders } = this.props
|
||||||
|
|
||||||
optionIndex++
|
optionIndex++
|
||||||
if (optionIndex >= folders.length) optionIndex = 0
|
if (optionIndex >= folders.length) optionIndex = 0
|
||||||
@@ -140,7 +142,7 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
previousOption () {
|
previousOption () {
|
||||||
let { folders } = this.props
|
const { folders } = this.props
|
||||||
let { optionIndex } = this.state
|
let { optionIndex } = this.state
|
||||||
|
|
||||||
optionIndex--
|
optionIndex--
|
||||||
@@ -152,10 +154,10 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectOption () {
|
selectOption () {
|
||||||
let { folders } = this.props
|
const { folders } = this.props
|
||||||
let optionIndex = this.state.optionIndex
|
const optionIndex = this.state.optionIndex
|
||||||
|
|
||||||
let folder = folders[optionIndex]
|
const folder = folders[optionIndex]
|
||||||
if (folder != null) {
|
if (folder != null) {
|
||||||
this.setState({
|
this.setState({
|
||||||
status: 'FOCUS'
|
status: 'FOCUS'
|
||||||
@@ -184,10 +186,10 @@ class FolderSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className, data, value } = this.props
|
const { className, data, value } = this.props
|
||||||
let splitted = value.split('-')
|
const splitted = value.split('-')
|
||||||
let storageKey = splitted.shift()
|
const storageKey = splitted.shift()
|
||||||
let folderKey = splitted.shift()
|
const folderKey = splitted.shift()
|
||||||
let options = []
|
let options = []
|
||||||
data.storageMap.forEach((storage, index) => {
|
data.storageMap.forEach((storage, index) => {
|
||||||
storage.folders.forEach((folder) => {
|
storage.folders.forEach((folder) => {
|
||||||
@@ -198,14 +200,14 @@ class FolderSelect extends React.Component {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||||
|
|
||||||
if (this.state.search.trim().length > 0) {
|
if (this.state.search.trim().length > 0) {
|
||||||
let filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
|
||||||
options = options.filter((option) => filter.test(option.folder.name))
|
options = options.filter((option) => filter.test(option.folder.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
let optionList = options
|
const optionList = options
|
||||||
.map((option, index) => {
|
.map((option, index) => {
|
||||||
return (
|
return (
|
||||||
<div styleName={index === this.state.optionIndex
|
<div styleName={index === this.state.optionIndex
|
||||||
@@ -248,7 +250,7 @@ class FolderSelect extends React.Component {
|
|||||||
<input styleName='search-input'
|
<input styleName='search-input'
|
||||||
ref='search'
|
ref='search'
|
||||||
value={this.state.search}
|
value={this.state.search}
|
||||||
placeholder='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)}
|
||||||
@@ -259,12 +261,11 @@ class FolderSelect extends React.Component {
|
|||||||
{optionList}
|
{optionList}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
: <div styleName='idle'>
|
: <div styleName='idle' style={{color: currentOption.folder.color}}>
|
||||||
<div styleName='idle-label'>
|
<div styleName='idle-label'>
|
||||||
<span styleName='idle-label-name'
|
<i className='fa fa-folder' />
|
||||||
style={{color: currentOption.folder.color}}
|
<span styleName='idle-label-name'>
|
||||||
>
|
{currentOption.folder.name}
|
||||||
{currentOption.folder.name} /
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
.root
|
.root
|
||||||
position relative
|
position relative
|
||||||
border solid 1px transparent
|
border solid 1px transparent
|
||||||
line-height 26px
|
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
|
height 30px
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
user-select none
|
user-select none
|
||||||
&:hover
|
margin-right 10px
|
||||||
background-color $ui-button--hover-backgroundColor
|
|
||||||
|
|
||||||
.root--search, .root--focus
|
.root--search, .root--focus
|
||||||
@extend .root
|
@extend .root
|
||||||
background-color $ui-noteDetail-backgroundColor = #F4F4F4
|
|
||||||
border-color $ui-input--focus-borderColor
|
border-color $ui-input--focus-borderColor
|
||||||
width 100px
|
|
||||||
&:hover
|
|
||||||
border-color $ui-input--focus-borderColor
|
|
||||||
|
|
||||||
.idle
|
.idle
|
||||||
position relative
|
position relative
|
||||||
@@ -24,13 +19,16 @@
|
|||||||
.idle-label
|
.idle-label
|
||||||
right 20px
|
right 20px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
|
||||||
.idle-label-name
|
.idle-label-name
|
||||||
font-size 14px
|
font-size 13px
|
||||||
padding 2px
|
font-weight 600
|
||||||
|
margin-left 4px
|
||||||
|
|
||||||
.idle-label-name-surfix
|
.idle-label-name-surfix
|
||||||
font-size 10px
|
font-size 15px
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
.idle-caret
|
.idle-caret
|
||||||
@@ -39,35 +37,37 @@
|
|||||||
width 20px
|
width 20px
|
||||||
line-height 34px
|
line-height 34px
|
||||||
|
|
||||||
.search
|
|
||||||
absolute top left right bottom
|
|
||||||
line-height 34px
|
|
||||||
|
|
||||||
.search-input
|
.search-input
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
position relative
|
position relative
|
||||||
top -2px
|
top 0
|
||||||
|
font-size 14px
|
||||||
outline none
|
outline none
|
||||||
border none
|
border none
|
||||||
height 20px
|
width 100%
|
||||||
line-height 20px
|
|
||||||
background-color transparent
|
background-color transparent
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
|
|
||||||
.search-optionList
|
.search-optionList
|
||||||
position fixed
|
position absolute
|
||||||
|
top 30px
|
||||||
max-height 450px
|
max-height 450px
|
||||||
|
min-width 150px
|
||||||
overflow auto
|
overflow auto
|
||||||
z-index 200
|
z-index 200
|
||||||
border $ui-border
|
border $ui-border
|
||||||
background-color white
|
background-color white
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
|
padding 10px 6px
|
||||||
|
|
||||||
.search-optionList-item
|
.search-optionList-item
|
||||||
|
width 140px
|
||||||
height 34px
|
height 34px
|
||||||
width 250px
|
display flex
|
||||||
|
align-items center
|
||||||
box-sizing border-box
|
box-sizing border-box
|
||||||
padding 0 5px
|
padding 0
|
||||||
|
margin-bottom 10px
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
cursor pointer
|
cursor pointer
|
||||||
&:hover
|
&:hover
|
||||||
@@ -81,13 +81,17 @@
|
|||||||
background-color $ui-button--active-backgroundColor
|
background-color $ui-button--active-backgroundColor
|
||||||
color $ui-button--active-color
|
color $ui-button--active-color
|
||||||
.search-optionList-item-name
|
.search-optionList-item-name
|
||||||
border-left solid 2px transparent
|
border-left solid 3px transparent
|
||||||
padding 2px 5px
|
padding 6px
|
||||||
.search-optionList-item-name-surfix
|
.search-optionList-item-name-surfix
|
||||||
font-size 10px
|
font-size 10px
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
margin-left 5px
|
margin-left 5px
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
@@ -129,3 +133,29 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-button--active-color
|
color $ui-dark-button--active-color
|
||||||
.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"]
|
||||||
|
.root
|
||||||
|
color $ui-dark-text-color
|
||||||
|
&:hover
|
||||||
|
color white
|
||||||
|
background-color $ui-monokai-button--hover-backgroundColor
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.search-optionList
|
||||||
|
color white
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
|
||||||
|
.search-optionList-item
|
||||||
|
&:hover
|
||||||
|
background-color lighten($ui-monokai-button--hover-backgroundColor, 15%)
|
||||||
|
|
||||||
|
.search-optionList-item--active
|
||||||
|
background-color $ui-monokai-button--active-backgroundColor
|
||||||
|
color $ui-monokai-button--active-color
|
||||||
|
&:hover
|
||||||
|
background-color $ui-monokai-button--active-backgroundColor
|
||||||
|
color $ui-monokai-button--active-color
|
||||||
|
.search-optionList-item-name-surfix
|
||||||
|
color $ui-monokai-inactive-text-color
|
||||||
|
|||||||
22
browser/main/Detail/FullscreenButton.js
Normal file
22
browser/main/Detail/FullscreenButton.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './FullscreenButton.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
|
||||||
|
const FullscreenButton = ({
|
||||||
|
onClick
|
||||||
|
}) => (
|
||||||
|
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
|
||||||
|
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
||||||
|
<span styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
FullscreenButton.propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(FullscreenButton, styles)
|
||||||
22
browser/main/Detail/FullscreenButton.styl
Normal file
22
browser/main/Detail/FullscreenButton.styl
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
.control-fullScreenButton
|
||||||
|
top 80px
|
||||||
|
topBarButtonRight()
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 50px
|
||||||
|
right 70px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.control-fullScreenButton
|
||||||
|
topBarButtonDark()
|
||||||
@@ -1,14 +1,17 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './InfoButton.styl'
|
import styles from './InfoButton.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const InfoButton = ({
|
const InfoButton = ({
|
||||||
onClick
|
onClick
|
||||||
}) => (
|
}) => (
|
||||||
<button styleName='control-infoButton'
|
<button styleName='control-infoButton'
|
||||||
onClick={onClick}
|
onClick={(e) => onClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-info infoButton' styleName='info-button' />
|
<img className='infoButton' src='../resources/icon/icon-info.svg' />
|
||||||
|
<span styleName='tooltip'>{i18n.__('Info')}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
.control-infoButton
|
.control-infoButton
|
||||||
float right
|
top 10px
|
||||||
topBarButtonLight()
|
topBarButtonRight()
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
.control-infoPanel
|
.tooltip
|
||||||
position fixed
|
tooltip()
|
||||||
|
position absolute
|
||||||
pointer-events none
|
pointer-events none
|
||||||
top 50px
|
top 50px
|
||||||
|
right 20px
|
||||||
z-index 200
|
z-index 200
|
||||||
|
padding 5px
|
||||||
line-height normal
|
line-height normal
|
||||||
border-radius 2px
|
border-radius 2px
|
||||||
opacity 0
|
opacity 0
|
||||||
@@ -14,7 +19,6 @@
|
|||||||
|
|
||||||
.infoButton
|
.infoButton
|
||||||
padding 0px
|
padding 0px
|
||||||
margin 15px 0
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.control-infoButton
|
.control-infoButton
|
||||||
|
|||||||
@@ -1,70 +1,99 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './InfoPanel.styl'
|
import styles from './InfoPanel.styl'
|
||||||
|
import copy from 'copy-to-clipboard'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const InfoPanel = ({
|
class InfoPanel extends React.Component {
|
||||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt
|
copyNoteLink () {
|
||||||
}) => (
|
const {noteLink} = this.props
|
||||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
this.refs.noteLink.select()
|
||||||
<div styleName='group-section'>
|
copy(noteLink)
|
||||||
<div styleName='group-section-label'>
|
}
|
||||||
Storage
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section-control'>
|
|
||||||
{storageName}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section'>
|
|
||||||
<div styleName='group-section-label'>
|
|
||||||
Folder
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section-control'>
|
|
||||||
{folderName}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section'>
|
|
||||||
<div styleName='group-section-label'>
|
|
||||||
Created at
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section-control'>
|
|
||||||
{createdAt}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section'>
|
|
||||||
<div styleName='group-section-label'>
|
|
||||||
Updated at
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section-control'>
|
|
||||||
{updatedAt}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section'>
|
|
||||||
<div styleName='group-section-label'>
|
|
||||||
Note Link
|
|
||||||
</div>
|
|
||||||
<div styleName='group-section-control'>
|
|
||||||
<input value={noteLink} onClick={(e) => { e.target.select() }} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id='export-wrap'>
|
render () {
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
const {
|
||||||
<i className='fa fa-file-code-o fa-fw' />
|
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
|
||||||
<p>.md</p>
|
} = this.props
|
||||||
</button>
|
return (
|
||||||
|
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||||
|
<div>
|
||||||
|
<p styleName='modification-date'>{updatedAt}</p>
|
||||||
|
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
<hr />
|
||||||
<i className='fa fa-file-text-o fa-fw' />
|
|
||||||
<p>.txt</p>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button styleName='export--unable'>
|
{type === 'SNIPPET_NOTE'
|
||||||
<i className='fa fa-file-pdf-o fa-fw' />
|
? ''
|
||||||
<p>.pdf</p>
|
: <div styleName='count-wrap'>
|
||||||
</button>
|
<div styleName='count-number'>
|
||||||
</div>
|
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
|
||||||
</div>
|
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p>
|
||||||
)
|
</div>
|
||||||
|
<div styleName='count-number'>
|
||||||
|
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
|
||||||
|
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{type === 'SNIPPET_NOTE'
|
||||||
|
? ''
|
||||||
|
: <hr />
|
||||||
|
}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{storageName}</p>
|
||||||
|
<p styleName='infoPanel-sub'>{i18n.__('STORAGE')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{folderName}</p>
|
||||||
|
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{createdAt}</p>
|
||||||
|
<p styleName='infoPanel-sub'>{i18n.__('CREATION DATE')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input styleName='infoPanel-noteLink' ref='noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
|
||||||
|
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
|
||||||
|
<i className='fa fa-clipboard' />
|
||||||
|
</button>
|
||||||
|
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div id='export-wrap'>
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||||
|
<i className='fa fa-file-code-o' />
|
||||||
|
<p>{i18n.__('.md')}</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
||||||
|
<i className='fa fa-file-text-o' />
|
||||||
|
<p>{i18n.__('.txt')}</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
|
||||||
|
<i className='fa fa-html5' />
|
||||||
|
<p>{i18n.__('.html')}</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--enable' onClick={(e) => print(e)}>
|
||||||
|
<i className='fa fa-print' />
|
||||||
|
<p>{i18n.__('Print')}</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
InfoPanel.propTypes = {
|
InfoPanel.propTypes = {
|
||||||
storageName: PropTypes.string.isRequired,
|
storageName: PropTypes.string.isRequired,
|
||||||
@@ -73,7 +102,12 @@ InfoPanel.propTypes = {
|
|||||||
updatedAt: PropTypes.string.isRequired,
|
updatedAt: PropTypes.string.isRequired,
|
||||||
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,
|
||||||
|
wordCount: PropTypes.number,
|
||||||
|
letterCount: PropTypes.number,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
print: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(InfoPanel, styles)
|
export default CSSModules(InfoPanel, styles)
|
||||||
|
|||||||
@@ -10,35 +10,102 @@
|
|||||||
|
|
||||||
.control-infoButton-panel
|
.control-infoButton-panel
|
||||||
z-index 200
|
z-index 200
|
||||||
margin-top 45px
|
margin-top 0px
|
||||||
margin-left -210px
|
top: 50px
|
||||||
|
right 25px
|
||||||
position absolute
|
position absolute
|
||||||
padding 20px 20px 0 20px
|
padding 20px 25px 0 25px
|
||||||
width 340px
|
width 300px
|
||||||
|
overflow auto
|
||||||
background-color $ui-noteList-backgroundColor
|
background-color $ui-noteList-backgroundColor
|
||||||
border 1px solid $border-color
|
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||||
|
border-radius 2px
|
||||||
|
|
||||||
.group-section
|
.modification-date
|
||||||
display flex
|
font-size 18px
|
||||||
line-height 30px
|
line-height 30px
|
||||||
font-size 13px
|
|
||||||
|
|
||||||
.group-section-label
|
|
||||||
width 70px
|
|
||||||
height 20px
|
|
||||||
text-align left
|
|
||||||
margin-right 50px
|
|
||||||
font-size 13px
|
|
||||||
color $ui-inactive-text-color
|
|
||||||
|
|
||||||
.group-section-control
|
|
||||||
flex 1
|
|
||||||
font-size 13px
|
|
||||||
color $ui-text-color
|
color $ui-text-color
|
||||||
|
|
||||||
.group-section-control input
|
.modification-date-desc
|
||||||
width 160px
|
font-size 18px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.control-infoButton-panel-trash
|
||||||
|
z-index 200
|
||||||
|
margin-top 0px
|
||||||
|
right 0px
|
||||||
|
position absolute
|
||||||
|
padding 20px 25px 0 25px
|
||||||
|
width 300px
|
||||||
|
background-color $ui-noteList-backgroundColor
|
||||||
|
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||||
|
border-radius 2px
|
||||||
|
|
||||||
|
.count-wrap
|
||||||
|
display flex
|
||||||
|
position relative
|
||||||
|
width 100%
|
||||||
|
|
||||||
|
.count-number
|
||||||
|
position relative
|
||||||
|
display block
|
||||||
|
width 50%
|
||||||
|
overflow hidden
|
||||||
|
margin 0
|
||||||
|
padding 0
|
||||||
|
|
||||||
|
.infoPanel-defaul-count
|
||||||
|
font-size 16px
|
||||||
|
line-height 30px
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.infoPanel-sub-count
|
||||||
|
font-size 16px
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding-bottom 8px
|
||||||
|
|
||||||
|
.infoPanel-default
|
||||||
|
font-size 14px
|
||||||
|
line-height 30px
|
||||||
|
color $ui-text-color
|
||||||
|
|
||||||
|
.infoPanel-sub
|
||||||
|
font-size 12px
|
||||||
|
font-weight 600
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
padding-bottom 8px
|
||||||
|
|
||||||
|
.infoPanel-noteLink
|
||||||
|
padding-right 5px
|
||||||
|
width 210px
|
||||||
height 25px
|
height 25px
|
||||||
|
margin 6px 0
|
||||||
|
|
||||||
|
.infoPanel-copyButton
|
||||||
|
outline none
|
||||||
|
font-size 16px
|
||||||
|
color #A0A0A0
|
||||||
|
background-color transparent
|
||||||
|
border none
|
||||||
|
margin 0 5px
|
||||||
|
border-radius 5px
|
||||||
|
cursor pointer
|
||||||
|
&:hover
|
||||||
|
transition 0.2s
|
||||||
|
background-color alpha($ui-button--hover-backgroundColor, 30%)
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.infoPanel-trash
|
||||||
|
color #EA4447
|
||||||
|
font-weight 600
|
||||||
|
font-size 14px
|
||||||
|
width 70px
|
||||||
|
background-color rgba(226,33,113,0.1)
|
||||||
|
border none
|
||||||
|
outline none
|
||||||
|
border-radius 2px
|
||||||
|
margin-right 5px
|
||||||
|
padding 2px 5px
|
||||||
|
|
||||||
[id=export-wrap]
|
[id=export-wrap]
|
||||||
height 90px
|
height 90px
|
||||||
@@ -73,16 +140,30 @@
|
|||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.control-infoButton-panel
|
.control-infoButton-panel
|
||||||
background-color $ui-dark-noteList-backgroundColor
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
border 1px solid $ui-dark-borderColor
|
|
||||||
|
|
||||||
.group-section-label
|
.control-infoButton-panel-trash
|
||||||
color $ui-inactive-text-color
|
background-color $ui-dark-noteList-backgroundColor
|
||||||
|
|
||||||
.group-section-control
|
.modification-date
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
.group-section-control input
|
.modification-date-desc
|
||||||
background-color alpha($ui-dark-borderColor, 80%)
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.infoPanel-defaul-count
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.infoPanel-sub-count
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.infoPanel-default
|
||||||
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
.infoPanel-sub
|
||||||
|
color $ui-inactive-text-color
|
||||||
|
|
||||||
|
.infoPanel-noteLink
|
||||||
|
background-color alpha($ui-dark-borderColor, 60%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
[id=export-wrap]
|
[id=export-wrap]
|
||||||
@@ -95,3 +176,83 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.control-infoButton-panel
|
||||||
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.control-infoButton-panel-trash
|
||||||
|
background-color $ui-solarized-ark-noteList-backgroundColor
|
||||||
|
|
||||||
|
.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
|
||||||
|
|||||||
67
browser/main/Detail/InfoPanelTrashed.js
Normal file
67
browser/main/Detail/InfoPanelTrashed.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './InfoPanel.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const InfoPanelTrashed = ({
|
||||||
|
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
|
||||||
|
}) => (
|
||||||
|
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||||
|
<div>
|
||||||
|
<p styleName='modification-date'>{updatedAt}</p>
|
||||||
|
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{storageName}</p>
|
||||||
|
<p styleName='infoPanel-sub'>{i18n.__('STORAGE')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
|
||||||
|
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p styleName='infoPanel-default'>{createdAt}</p>
|
||||||
|
<p styleName='infoPanel-sub'>{i18n.__('CREATION DATE')}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id='export-wrap'>
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||||
|
<i className='fa fa-file-code-o' />
|
||||||
|
<p>.md</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
||||||
|
<i className='fa fa-file-text-o' />
|
||||||
|
<p>.txt</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
|
||||||
|
<i className='fa fa-html5' />
|
||||||
|
<p>.html</p>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button styleName='export--unable'>
|
||||||
|
<i className='fa fa-file-pdf-o' />
|
||||||
|
<p>.pdf</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
InfoPanelTrashed.propTypes = {
|
||||||
|
storageName: PropTypes.string.isRequired,
|
||||||
|
folderName: PropTypes.string.isRequired,
|
||||||
|
updatedAt: PropTypes.string.isRequired,
|
||||||
|
createdAt: PropTypes.string.isRequired,
|
||||||
|
exportAsMd: PropTypes.func.isRequired,
|
||||||
|
exportAsTxt: PropTypes.func.isRequired,
|
||||||
|
exportAsHtml: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(InfoPanelTrashed, styles)
|
||||||
281
browser/main/Detail/MarkdownNoteDetail.js
Normal file → Executable file
281
browser/main/Detail/MarkdownNoteDetail.js
Normal file → Executable file
@@ -1,7 +1,9 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './MarkdownNoteDetail.styl'
|
import styles from './MarkdownNoteDetail.styl'
|
||||||
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||||
|
import MarkdownSplitEditor from 'browser/components/MarkdownSplitEditor'
|
||||||
import TodoListPercentage from 'browser/components/TodoListPercentage'
|
import TodoListPercentage from 'browser/components/TodoListPercentage'
|
||||||
import StarButton from './StarButton'
|
import StarButton from './StarButton'
|
||||||
import TagSelect from './TagSelect'
|
import TagSelect from './TagSelect'
|
||||||
@@ -9,19 +11,24 @@ import FolderSelect from './FolderSelect'
|
|||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import { hashHistory } from 'react-router'
|
import { hashHistory } from 'react-router'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import markdown from 'browser/lib/markdown'
|
import markdown from 'browser/lib/markdownTextHelper'
|
||||||
import StatusBar from '../StatusBar'
|
import StatusBar from '../StatusBar'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import TrashButton from './TrashButton'
|
import TrashButton from './TrashButton'
|
||||||
|
import FullscreenButton from './FullscreenButton'
|
||||||
|
import RestoreButton from './RestoreButton'
|
||||||
|
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||||
import InfoButton from './InfoButton'
|
import InfoButton from './InfoButton'
|
||||||
|
import ToggleModeButton from './ToggleModeButton'
|
||||||
import InfoPanel from './InfoPanel'
|
import InfoPanel from './InfoPanel'
|
||||||
|
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||||
import { formatDate } from 'browser/lib/date-formatter'
|
import { formatDate } from 'browser/lib/date-formatter'
|
||||||
|
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||||
const electron = require('electron')
|
import striptags from 'striptags'
|
||||||
const { remote } = electron
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
const { Menu, MenuItem, dialog } = remote
|
|
||||||
|
|
||||||
class MarkdownNoteDetail extends React.Component {
|
class MarkdownNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -34,7 +41,8 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
content: ''
|
content: ''
|
||||||
}, props.note),
|
}, props.note),
|
||||||
isLockButtonShown: false,
|
isLockButtonShown: false,
|
||||||
isLocked: false
|
isLocked: false,
|
||||||
|
editorType: props.config.editor.type
|
||||||
}
|
}
|
||||||
this.dispatchTimer = null
|
this.dispatchTimer = null
|
||||||
|
|
||||||
@@ -47,10 +55,14 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
|
ee.on('topbar:togglemodebutton', () => {
|
||||||
|
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||||
|
this.handleSwitchMode(reversedType)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
this.setState({
|
this.setState({
|
||||||
note: Object.assign({}, nextProps.note)
|
note: Object.assign({}, nextProps.note)
|
||||||
@@ -62,42 +74,26 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUnmount () {
|
handleUpdateTag () {
|
||||||
ee.off('topbar:togglelockbutton', this.toggleLockButton)
|
const { note } = this.state
|
||||||
}
|
|
||||||
|
|
||||||
getPercentageOfCompleteTodo (noteContent) {
|
|
||||||
let splitted = noteContent.split('\n')
|
|
||||||
let numberOfTodo = 0
|
|
||||||
let numberOfCompletedTodo = 0
|
|
||||||
|
|
||||||
splitted.forEach((line) => {
|
|
||||||
let trimmedLine = line.trim()
|
|
||||||
if (trimmedLine.match(/^[\+\-\*] \[\s|x\] ./)) {
|
|
||||||
numberOfTodo++
|
|
||||||
}
|
|
||||||
if (trimmedLine.match(/^[\+\-\*] \[x\] ./)) {
|
|
||||||
numberOfCompletedTodo++
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return Math.floor(numberOfCompletedTodo / numberOfTodo * 100)
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange (e) {
|
|
||||||
let { note } = this.state
|
|
||||||
|
|
||||||
note.content = this.refs.content.value
|
|
||||||
if (this.refs.tags) note.tags = this.refs.tags.value
|
if (this.refs.tags) note.tags = this.refs.tags.value
|
||||||
note.title = markdown.strip(findNoteTitle(note.content))
|
this.updateNote(note)
|
||||||
note.updatedAt = new Date()
|
}
|
||||||
|
|
||||||
this.setState({
|
handleUpdateContent () {
|
||||||
note
|
const { note } = this.state
|
||||||
}, () => {
|
note.content = this.refs.content.value
|
||||||
|
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
|
||||||
|
this.updateNote(note)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNote (note) {
|
||||||
|
note.updatedAt = new Date()
|
||||||
|
this.setState({note}, () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -110,7 +106,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveNow () {
|
saveNow () {
|
||||||
let { note, dispatch } = this.props
|
const { note, dispatch } = this.props
|
||||||
clearTimeout(this.saveQueue)
|
clearTimeout(this.saveQueue)
|
||||||
this.saveQueue = null
|
this.saveQueue = null
|
||||||
|
|
||||||
@@ -126,11 +122,11 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFolderChange (e) {
|
handleFolderChange (e) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
let value = this.refs.folder.value
|
const value = this.refs.folder.value
|
||||||
let splitted = value.split('-')
|
const splitted = value.split('-')
|
||||||
let newStorageKey = splitted.shift()
|
const newStorageKey = splitted.shift()
|
||||||
let newFolderKey = splitted.shift()
|
const newFolderKey = splitted.shift()
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||||
@@ -139,7 +135,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
isMovingNote: true,
|
isMovingNote: true,
|
||||||
note: Object.assign({}, newNote)
|
note: Object.assign({}, newNote)
|
||||||
}, () => {
|
}, () => {
|
||||||
let { dispatch, location } = this.props
|
const { dispatch, location } = this.props
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
originNote: note,
|
originNote: note,
|
||||||
@@ -148,7 +144,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
hashHistory.replace({
|
hashHistory.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: newNote.storage + '-' + newNote.key
|
key: newNote.key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -159,7 +155,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleStarButtonClick (e) {
|
handleStarButtonClick (e) {
|
||||||
let { 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
|
||||||
@@ -183,45 +179,49 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
ee.emit('export:save-text')
|
ee.emit('export:save-text')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportAsHtml () {
|
||||||
|
ee.emit('export:save-html')
|
||||||
|
}
|
||||||
|
|
||||||
handleTrashButtonClick (e) {
|
handleTrashButtonClick (e) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
const { isTrashed } = note
|
const { isTrashed } = note
|
||||||
|
const { confirmDeletion } = this.props.config.ui
|
||||||
|
|
||||||
if (isTrashed) {
|
if (isTrashed) {
|
||||||
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
if (confirmDeleteNote(confirmDeletion, true)) {
|
||||||
type: 'warning',
|
const {note, dispatch} = this.props
|
||||||
message: 'Delete a note',
|
dataApi
|
||||||
detail: 'This work cannot be undone.',
|
.deleteNote(note.storage, note.key)
|
||||||
buttons: ['Confirm', 'Cancel']
|
.then((data) => {
|
||||||
})
|
const dispatchHandler = () => {
|
||||||
if (dialogueButtonIndex === 1) return
|
dispatch({
|
||||||
let { note, dispatch } = this.props
|
type: 'DELETE_NOTE',
|
||||||
dataApi
|
storageKey: data.storageKey,
|
||||||
.deleteNote(note.storage, note.key)
|
noteKey: data.noteKey
|
||||||
.then((data) => {
|
})
|
||||||
let dispatchHandler = () => {
|
}
|
||||||
dispatch({
|
ee.once('list:next', dispatchHandler)
|
||||||
type: 'DELETE_NOTE',
|
})
|
||||||
storageKey: data.storageKey,
|
.then(() => ee.emit('list:next'))
|
||||||
noteKey: data.noteKey
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
ee.once('list:moved', dispatchHandler)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
note.isTrashed = true
|
if (confirmDeleteNote(confirmDeletion, false)) {
|
||||||
|
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) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
note.isTrashed = false
|
note.isTrashed = false
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getToggleLockButton () {
|
getToggleLockButton () {
|
||||||
return this.state.isLocked ? 'fa-lock' : 'fa-unlock'
|
return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteKeyDown (e) {
|
handleDeleteKeyDown (e) {
|
||||||
@@ -271,13 +271,53 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
print (e) {
|
||||||
let { data, config, location } = this.props
|
ee.emit('print')
|
||||||
let { note } = this.state
|
}
|
||||||
let storageKey = note.storage
|
|
||||||
let folderKey = note.folder
|
|
||||||
|
|
||||||
let options = []
|
handleSwitchMode (type) {
|
||||||
|
this.setState({ editorType: type }, () => {
|
||||||
|
this.focus()
|
||||||
|
const newConfig = Object.assign({}, this.props.config)
|
||||||
|
newConfig.editor.type = type
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderEditor () {
|
||||||
|
const { config, ignorePreviewPointerEvents } = this.props
|
||||||
|
const { note } = this.state
|
||||||
|
if (this.state.editorType === 'EDITOR_PREVIEW') {
|
||||||
|
return <MarkdownEditor
|
||||||
|
ref='content'
|
||||||
|
styleName='body-noteEditor'
|
||||||
|
config={config}
|
||||||
|
value={note.content}
|
||||||
|
storageKey={note.storage}
|
||||||
|
noteKey={note.key}
|
||||||
|
onChange={this.handleUpdateContent.bind(this)}
|
||||||
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
/>
|
||||||
|
} else {
|
||||||
|
return <MarkdownSplitEditor
|
||||||
|
ref='content'
|
||||||
|
config={config}
|
||||||
|
value={note.content}
|
||||||
|
storageKey={note.storage}
|
||||||
|
noteKey={note.key}
|
||||||
|
onChange={this.handleUpdateContent.bind(this)}
|
||||||
|
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { data, location } = this.props
|
||||||
|
const { note, editorType } = this.state
|
||||||
|
const storageKey = note.storage
|
||||||
|
const folderKey = note.folder
|
||||||
|
|
||||||
|
const options = []
|
||||||
data.storageMap.forEach((storage, index) => {
|
data.storageMap.forEach((storage, index) => {
|
||||||
storage.folders.forEach((folder) => {
|
storage.folders.forEach((folder) => {
|
||||||
options.push({
|
options.push({
|
||||||
@@ -286,26 +326,31 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||||
|
|
||||||
const trashTopBar = <div styleName='info'>
|
const trashTopBar = <div styleName='info'>
|
||||||
<div styleName='info-left'>
|
<div styleName='info-left'>
|
||||||
<i styleName='undo-button'
|
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
||||||
className='fa fa-undo fa-fw'
|
|
||||||
onClick={(e) => this.handleUndoButtonClick(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
const detailTopBar = <div styleName='info'>
|
const detailTopBar = <div styleName='info'>
|
||||||
<div styleName='info-left'>
|
<div styleName='info-left'>
|
||||||
<StarButton styleName='info-left-button'
|
|
||||||
onClick={(e) => this.handleStarButtonClick(e)}
|
|
||||||
isActive={note.isStarred}
|
|
||||||
/>
|
|
||||||
<div styleName='info-left-top'>
|
<div styleName='info-left-top'>
|
||||||
<FolderSelect styleName='info-left-top-folderSelect'
|
<FolderSelect styleName='info-left-top-folderSelect'
|
||||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
@@ -318,46 +363,54 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
<TagSelect
|
<TagSelect
|
||||||
ref='tags'
|
ref='tags'
|
||||||
value={this.state.note.tags}
|
value={this.state.note.tags}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={this.handleUpdateTag.bind(this)}
|
||||||
/>
|
|
||||||
<TodoListPercentage
|
|
||||||
percentageOfTodo={this.getPercentageOfCompleteTodo(note.content)}
|
|
||||||
/>
|
/>
|
||||||
|
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
|
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
|
||||||
|
<StarButton
|
||||||
|
onClick={(e) => this.handleStarButtonClick(e)}
|
||||||
|
isActive={note.isStarred}
|
||||||
|
/>
|
||||||
|
|
||||||
{(() => {
|
{(() => {
|
||||||
const faClassName = `fa ${this.getToggleLockButton()}`
|
const imgSrc = `${this.getToggleLockButton()}`
|
||||||
const lockButtonComponent =
|
const lockButtonComponent =
|
||||||
<button styleName='control-lockButton'
|
<button styleName='control-lockButton'
|
||||||
onFocus={(e) => this.handleFocus(e)}
|
onFocus={(e) => this.handleFocus(e)}
|
||||||
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
|
||||||
>
|
>
|
||||||
<i className={faClassName} styleName='lock-button' />
|
<img styleName='iconInfo' src={imgSrc} />
|
||||||
<span styleName='control-lockButton-tooltip'>
|
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
|
||||||
{this.state.isLocked ? 'Unlock' : 'Lock'}
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.state.isLockButtonShown ? lockButtonComponent : ''
|
this.state.isLockButtonShown ? lockButtonComponent : ''
|
||||||
)
|
)
|
||||||
})()}
|
})()}
|
||||||
|
|
||||||
|
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
|
||||||
|
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||||
<button styleName='control-fullScreenButton'
|
|
||||||
onMouseDown={(e) => this.handleFullScreenButton(e)}
|
|
||||||
>
|
|
||||||
<i className='fa fa-expand' styleName='fullScreen-button' />
|
|
||||||
</button>
|
|
||||||
<InfoButton
|
<InfoButton
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InfoPanel
|
<InfoPanel
|
||||||
storageName={currentOption.storage.name}
|
storageName={currentOption.storage.name}
|
||||||
folderName={currentOption.folder.name}
|
folderName={currentOption.folder.name}
|
||||||
noteLink={`[${note.title}](${location.query.key})`}
|
noteLink={`[${note.title}](:note:${location.query.key})`}
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.exportAsMd}
|
exportAsMd={this.exportAsMd}
|
||||||
exportAsTxt={this.exportAsTxt}
|
exportAsTxt={this.exportAsTxt}
|
||||||
|
exportAsHtml={this.exportAsHtml}
|
||||||
|
wordCount={note.content.split(' ').length}
|
||||||
|
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||||
|
type={note.type}
|
||||||
|
print={this.print}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -371,15 +424,7 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
|
||||||
|
|
||||||
<div styleName='body'>
|
<div styleName='body'>
|
||||||
<MarkdownEditor
|
{this.renderEditor()}
|
||||||
ref='content'
|
|
||||||
styleName='body-noteEditor'
|
|
||||||
config={config}
|
|
||||||
value={this.state.note.content}
|
|
||||||
storageKey={this.state.note.storage}
|
|
||||||
onChange={(e) => this.handleChange(e)}
|
|
||||||
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusBar
|
<StatusBar
|
||||||
|
|||||||
@@ -3,50 +3,59 @@
|
|||||||
|
|
||||||
.root
|
.root
|
||||||
absolute top right bottom
|
absolute top right bottom
|
||||||
border-width 0 0 1px
|
border-left 1px solid alpha(#DEDEDE, 60%)
|
||||||
border-style solid
|
|
||||||
border-color $ui-borderColor
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
box-shadow $note-detail-box-shadow
|
box-shadow none
|
||||||
|
padding 20px 40px
|
||||||
|
overflow hidden
|
||||||
|
|
||||||
.lock-button
|
.lock-button
|
||||||
padding-bottom 3px
|
padding-bottom 3px
|
||||||
|
|
||||||
.control-lockButton
|
.control-lockButton
|
||||||
topBarButtonLight()
|
topBarButtonRight()
|
||||||
|
position absolute
|
||||||
|
right 225px
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
.control-lockButton-tooltip
|
.tooltip
|
||||||
tooltip()
|
tooltip()
|
||||||
position fixed
|
position absolute
|
||||||
pointer-events none
|
pointer-events none
|
||||||
top 50px
|
top 35px
|
||||||
z-index 200
|
right -10px
|
||||||
padding 5px
|
width 50px
|
||||||
line-height normal
|
z-index 200
|
||||||
border-radius 2px
|
padding 5px
|
||||||
opacity 0
|
line-height normal
|
||||||
transition 0.1s
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
.control-fullScreenButton
|
.trashed-infopanel
|
||||||
float right
|
position relative
|
||||||
padding 0 0 2px 0
|
|
||||||
topBarButtonLight()
|
|
||||||
|
|
||||||
.body
|
.body
|
||||||
absolute left right
|
absolute left right
|
||||||
left $note-detail-left-margin
|
left 0
|
||||||
right $note-detail-right-margin
|
right 0
|
||||||
top $info-height + $info-margin-under-border
|
top $info-height + $info-margin-under-border
|
||||||
bottom $statusBar-height
|
bottom $statusBar-height
|
||||||
|
margin 0 30px
|
||||||
.body-noteEditor
|
.body-noteEditor
|
||||||
absolute top bottom left right
|
absolute top bottom left right
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.root
|
||||||
|
box-shadow $note-detail-box-shadow
|
||||||
|
border none
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
box-shadow none
|
box-shadow none
|
||||||
|
border-left 1px solid $ui-dark-borderColor
|
||||||
|
|
||||||
.control-lockButton
|
.control-lockButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
@@ -56,3 +65,14 @@ body[data-theme="dark"]
|
|||||||
|
|
||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
|||||||
@@ -1,33 +1,30 @@
|
|||||||
@import('DetailVars')
|
@import('DetailVars')
|
||||||
|
|
||||||
$info-height = 60px
|
$info-height = 60px
|
||||||
$info-margin-under-border = 27px
|
$info-margin-under-border = 30px
|
||||||
|
|
||||||
.info
|
.info
|
||||||
absolute top left right
|
absolute top left right
|
||||||
left $note-detail-left-margin
|
left 0
|
||||||
right $note-detail-right-margin
|
right 0
|
||||||
height $info-height
|
height $info-height
|
||||||
border-bottom $ui-border
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
width 100%
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
padding 0 20px
|
||||||
|
|
||||||
.info-left
|
.info-left
|
||||||
float left
|
padding 0 10px
|
||||||
padding 0 5px
|
width 100%
|
||||||
margin 0px 2px
|
display flex
|
||||||
.info-left-top
|
align-items center
|
||||||
display inline-block
|
|
||||||
height $info-height
|
|
||||||
line-height $info-height
|
|
||||||
|
|
||||||
.info-left-top-folderSelect
|
.info-left-top-folderSelect
|
||||||
padding 0 3px
|
|
||||||
height 34px
|
|
||||||
line-height 26px
|
|
||||||
display flex
|
display flex
|
||||||
align-items center
|
align-items center
|
||||||
justify-content center
|
justify-content center
|
||||||
border-radius 3px
|
|
||||||
.info-left-button
|
.info-left-button
|
||||||
width 34px
|
width 34px
|
||||||
height 34px
|
height 34px
|
||||||
@@ -47,19 +44,16 @@ $info-margin-under-border = 27px
|
|||||||
color $ui-button--color
|
color $ui-button--color
|
||||||
|
|
||||||
.info-right
|
.info-right
|
||||||
position absolute
|
z-index 101
|
||||||
right 0
|
display inline-flex
|
||||||
top 0
|
margin-top 3px
|
||||||
background $ui-noteDetail-backgroundColor
|
|
||||||
bottom 1px
|
|
||||||
padding-left 30px
|
|
||||||
|
|
||||||
.undo-button
|
.undo-button
|
||||||
width 34px
|
width 34px
|
||||||
height 34px
|
height 34px
|
||||||
border-radius 17px
|
border-radius 17px
|
||||||
font-size 14px
|
font-size 14px
|
||||||
margin 15px 7px
|
margin 5px 0px
|
||||||
border none
|
border none
|
||||||
color $ui-button-color
|
color $ui-button-color
|
||||||
display flex
|
display flex
|
||||||
@@ -72,6 +66,7 @@ $info-margin-under-border = 27px
|
|||||||
border-color $ui-button--active-backgroundColor
|
border-color $ui-button--active-backgroundColor
|
||||||
&:hover
|
&:hover
|
||||||
background-color alpha($ui-button--hover-backgroundColor, 60%)
|
background-color alpha($ui-button--hover-backgroundColor, 60%)
|
||||||
|
transition 0.2s
|
||||||
.control-lockButton-tooltip
|
.control-lockButton-tooltip
|
||||||
opacity 1
|
opacity 1
|
||||||
|
|
||||||
@@ -97,3 +92,13 @@ body[data-theme="dark"]
|
|||||||
|
|
||||||
.undo-button
|
.undo-button
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.info
|
||||||
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.info
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
22
browser/main/Detail/PermanentDeleteButton.js
Normal file
22
browser/main/Detail/PermanentDeleteButton.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './TrashButton.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const PermanentDeleteButton = ({
|
||||||
|
onClick
|
||||||
|
}) => (
|
||||||
|
<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>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
PermanentDeleteButton.propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(PermanentDeleteButton, styles)
|
||||||
22
browser/main/Detail/RestoreButton.js
Normal file
22
browser/main/Detail/RestoreButton.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './RestoreButton.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const RestoreButton = ({
|
||||||
|
onClick
|
||||||
|
}) => (
|
||||||
|
<button styleName='control-restoreButton'
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
|
||||||
|
<span styleName='tooltip'>{i18n.__('Restore')}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
|
||||||
|
RestoreButton.propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(RestoreButton, styles)
|
||||||
22
browser/main/Detail/RestoreButton.styl
Normal file
22
browser/main/Detail/RestoreButton.styl
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
.control-restoreButton
|
||||||
|
top 115px
|
||||||
|
topBarButtonRight()
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 50px
|
||||||
|
left 25px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.control-restoreButton
|
||||||
|
topBarButtonDark()
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './SnippetNoteDetail.styl'
|
import styles from './SnippetNoteDetail.styl'
|
||||||
import CodeEditor from 'browser/components/CodeEditor'
|
import CodeEditor from 'browser/components/CodeEditor'
|
||||||
@@ -7,39 +8,31 @@ import StarButton from './StarButton'
|
|||||||
import TagSelect from './TagSelect'
|
import TagSelect from './TagSelect'
|
||||||
import FolderSelect from './FolderSelect'
|
import FolderSelect from './FolderSelect'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
import { hashHistory } from 'react-router'
|
import {hashHistory} from 'react-router'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
|
import 'codemirror-mode-elixir'
|
||||||
import SnippetTab from 'browser/components/SnippetTab'
|
import SnippetTab from 'browser/components/SnippetTab'
|
||||||
import StatusBar from '../StatusBar'
|
import StatusBar from '../StatusBar'
|
||||||
import context from 'browser/lib/context'
|
import context from 'browser/lib/context'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { findNoteTitle } from 'browser/lib/findNoteTitle'
|
import {findNoteTitle} from 'browser/lib/findNoteTitle'
|
||||||
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
import TrashButton from './TrashButton'
|
import TrashButton from './TrashButton'
|
||||||
|
import RestoreButton from './RestoreButton'
|
||||||
|
import PermanentDeleteButton from './PermanentDeleteButton'
|
||||||
import InfoButton from './InfoButton'
|
import InfoButton from './InfoButton'
|
||||||
import InfoPanel from './InfoPanel'
|
import InfoPanel from './InfoPanel'
|
||||||
|
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||||
import { formatDate } from 'browser/lib/date-formatter'
|
import { formatDate } from 'browser/lib/date-formatter'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
function pass (name) {
|
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||||
switch (name) {
|
|
||||||
case 'ejs':
|
|
||||||
return 'Embedded Javascript'
|
|
||||||
case 'html_ruby':
|
|
||||||
return 'Embedded Ruby'
|
|
||||||
case 'objectivec':
|
|
||||||
return 'Objective C'
|
|
||||||
case 'text':
|
|
||||||
return 'Plain Text'
|
|
||||||
default:
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { dialog } = remote
|
||||||
|
|
||||||
class SnippetNoteDetail extends React.Component {
|
class SnippetNoteDetail extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -48,18 +41,36 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
isMovingNote: false,
|
isMovingNote: false,
|
||||||
snippetIndex: 0,
|
snippetIndex: 0,
|
||||||
|
showArrows: false,
|
||||||
|
enableLeftArrow: false,
|
||||||
|
enableRightArrow: false,
|
||||||
note: Object.assign({
|
note: Object.assign({
|
||||||
description: ''
|
description: ''
|
||||||
}, props.note, {
|
}, props.note, {
|
||||||
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
|
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.scrollToNextTabThreshold = 0.7
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const visibleTabs = this.visibleTabs
|
||||||
|
const allTabs = this.allTabs
|
||||||
|
|
||||||
|
if (visibleTabs.offsetWidth < allTabs.scrollWidth) {
|
||||||
|
this.setState({
|
||||||
|
showArrows: visibleTabs.offsetWidth < allTabs.scrollWidth,
|
||||||
|
enableRightArrow: allTabs.offsetLeft !== visibleTabs.offsetWidth - allTabs.scrollWidth,
|
||||||
|
enableLeftArrow: allTabs.offsetLeft !== 0
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
let nextNote = Object.assign({
|
const nextNote = Object.assign({
|
||||||
description: ''
|
description: ''
|
||||||
}, nextProps.note, {
|
}, nextProps.note, {
|
||||||
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
|
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
|
||||||
@@ -68,11 +79,12 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
snippetIndex: 0,
|
snippetIndex: 0,
|
||||||
note: nextNote
|
note: nextNote
|
||||||
}, () => {
|
}, () => {
|
||||||
let { snippets } = this.state.note
|
const { snippets } = this.state.note
|
||||||
snippets.forEach((snippet, index) => {
|
snippets.forEach((snippet, index) => {
|
||||||
this.refs['code-' + index].reload()
|
this.refs['code-' + index].reload()
|
||||||
})
|
})
|
||||||
if (this.refs.tags) this.refs.tags.reset()
|
if (this.refs.tags) this.refs.tags.reset()
|
||||||
|
this.setState(this.getArrowsState())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,7 +94,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleChange (e) {
|
handleChange (e) {
|
||||||
let { 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
|
||||||
note.description = this.refs.description.value
|
note.description = this.refs.description.value
|
||||||
@@ -104,7 +116,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveNow () {
|
saveNow () {
|
||||||
let { note, dispatch } = this.props
|
const { note, dispatch } = this.props
|
||||||
clearTimeout(this.saveQueue)
|
clearTimeout(this.saveQueue)
|
||||||
this.saveQueue = null
|
this.saveQueue = null
|
||||||
|
|
||||||
@@ -120,11 +132,11 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleFolderChange (e) {
|
handleFolderChange (e) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
let value = this.refs.folder.value
|
const value = this.refs.folder.value
|
||||||
let splitted = value.split('-')
|
const splitted = value.split('-')
|
||||||
let newStorageKey = splitted.shift()
|
const newStorageKey = splitted.shift()
|
||||||
let newFolderKey = splitted.shift()
|
const newFolderKey = splitted.shift()
|
||||||
|
|
||||||
dataApi
|
dataApi
|
||||||
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
|
||||||
@@ -133,7 +145,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
isMovingNote: true,
|
isMovingNote: true,
|
||||||
note: Object.assign({}, newNote)
|
note: Object.assign({}, newNote)
|
||||||
}, () => {
|
}, () => {
|
||||||
let { dispatch, location } = this.props
|
const { dispatch, location } = this.props
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
originNote: note,
|
originNote: note,
|
||||||
@@ -142,7 +154,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
hashHistory.replace({
|
hashHistory.replace({
|
||||||
pathname: location.pathname,
|
pathname: location.pathname,
|
||||||
query: {
|
query: {
|
||||||
key: newNote.storage + '-' + newNote.key
|
key: newNote.key
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -153,7 +165,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleStarButtonClick (e) {
|
handleStarButtonClick (e) {
|
||||||
let { 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
|
||||||
@@ -170,44 +182,44 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTrashButtonClick (e) {
|
handleTrashButtonClick (e) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
const { isTrashed } = note
|
const { isTrashed } = note
|
||||||
|
const { confirmDeletion } = this.props.config.ui
|
||||||
|
|
||||||
if (isTrashed) {
|
if (isTrashed) {
|
||||||
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
if (confirmDeleteNote(confirmDeletion, true)) {
|
||||||
type: 'warning',
|
const {note, dispatch} = this.props
|
||||||
message: 'Delete a note',
|
dataApi
|
||||||
detail: 'This work cannot be undone.',
|
.deleteNote(note.storage, note.key)
|
||||||
buttons: ['Confirm', 'Cancel']
|
.then((data) => {
|
||||||
})
|
const dispatchHandler = () => {
|
||||||
if (dialogueButtonIndex === 1) return
|
dispatch({
|
||||||
let { note, dispatch } = this.props
|
type: 'DELETE_NOTE',
|
||||||
dataApi
|
storageKey: data.storageKey,
|
||||||
.deleteNote(note.storage, note.key)
|
noteKey: data.noteKey
|
||||||
.then((data) => {
|
})
|
||||||
let dispatchHandler = () => {
|
}
|
||||||
dispatch({
|
ee.once('list:next', dispatchHandler)
|
||||||
type: 'DELETE_NOTE',
|
})
|
||||||
storageKey: data.storageKey,
|
.then(() => ee.emit('list:next'))
|
||||||
noteKey: data.noteKey
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
ee.once('list:moved', dispatchHandler)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
note.isTrashed = true
|
if (confirmDeleteNote(confirmDeletion, false)) {
|
||||||
|
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) {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
note.isTrashed = false
|
note.isTrashed = false
|
||||||
|
|
||||||
@@ -223,6 +235,51 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
ee.emit('editor:fullscreen')
|
ee.emit('editor:fullscreen')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTabMoveLeftButtonClick (e) {
|
||||||
|
{
|
||||||
|
const left = this.visibleTabs.scrollLeft
|
||||||
|
|
||||||
|
const tabs = this.allTabs.querySelectorAll('div')
|
||||||
|
const lastVisibleTab = Array.from(tabs).find((tab) => {
|
||||||
|
return tab.offsetLeft + tab.offsetWidth >= left
|
||||||
|
})
|
||||||
|
|
||||||
|
if (lastVisibleTab) {
|
||||||
|
const visiblePart = lastVisibleTab.offsetWidth + lastVisibleTab.offsetLeft - left
|
||||||
|
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
|
||||||
|
const scrollToTab = (isFullyVisible && lastVisibleTab.previousSibling)
|
||||||
|
? lastVisibleTab.previousSibling
|
||||||
|
: lastVisibleTab
|
||||||
|
|
||||||
|
// FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll)
|
||||||
|
this.moveToTab(scrollToTab)
|
||||||
|
// scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'start', block: 'start'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTabMoveRightButtonClick (e) {
|
||||||
|
const left = this.visibleTabs.scrollLeft
|
||||||
|
const width = this.visibleTabs.offsetWidth
|
||||||
|
|
||||||
|
const tabs = this.allTabs.querySelectorAll('div')
|
||||||
|
const lastVisibleTab = Array.from(tabs).find((tab) => {
|
||||||
|
return tab.offsetLeft + tab.offsetWidth >= width + left
|
||||||
|
})
|
||||||
|
|
||||||
|
if (lastVisibleTab) {
|
||||||
|
const visiblePart = width + left - lastVisibleTab.offsetLeft
|
||||||
|
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
|
||||||
|
const scrollToTab = (isFullyVisible && lastVisibleTab.nextSibling)
|
||||||
|
? lastVisibleTab.nextSibling
|
||||||
|
: lastVisibleTab
|
||||||
|
|
||||||
|
// FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll)
|
||||||
|
this.moveToTab(scrollToTab)
|
||||||
|
// scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'end', block: 'end'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleTabPlusButtonClick (e) {
|
handleTabPlusButtonClick (e) {
|
||||||
this.addSnippet()
|
this.addSnippet()
|
||||||
}
|
}
|
||||||
@@ -233,14 +290,35 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleTabDragStart (e, index) {
|
||||||
|
e.dataTransfer.setData('text/plain', index)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTabDrop (e, index) {
|
||||||
|
const oldIndex = parseInt(e.dataTransfer.getData('text'))
|
||||||
|
|
||||||
|
const snippets = this.state.note.snippets.slice()
|
||||||
|
const draggedSnippet = snippets[oldIndex]
|
||||||
|
snippets[oldIndex] = snippets[index]
|
||||||
|
snippets[index] = draggedSnippet
|
||||||
|
const snippetIndex = index
|
||||||
|
|
||||||
|
const note = Object.assign({}, this.state.note, {snippets})
|
||||||
|
this.setState({ note, snippetIndex }, () => {
|
||||||
|
this.save()
|
||||||
|
this.refs['code-' + index].reload()
|
||||||
|
this.refs['code-' + oldIndex].reload()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleTabDeleteButtonClick (e, index) {
|
handleTabDeleteButtonClick (e, index) {
|
||||||
if (this.state.note.snippets.length > 1) {
|
if (this.state.note.snippets.length > 1) {
|
||||||
if (this.state.note.snippets[index].content.trim().length > 0) {
|
if (this.state.note.snippets[index].content.trim().length > 0) {
|
||||||
let dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Delete a snippet',
|
message: i18n.__('Delete a snippet'),
|
||||||
detail: 'This work cannot be undone.',
|
detail: i18n.__('This work cannot be undone.'),
|
||||||
buttons: ['Confirm', 'Cancel']
|
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
|
||||||
})
|
})
|
||||||
if (dialogIndex === 0) {
|
if (dialogIndex === 0) {
|
||||||
this.deleteSnippetByIndex(index)
|
this.deleteSnippetByIndex(index)
|
||||||
@@ -261,46 +339,70 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
this.setState({ note, snippetIndex }, () => {
|
this.setState({ note, snippetIndex }, () => {
|
||||||
this.save()
|
this.save()
|
||||||
this.refs['code-' + this.state.snippetIndex].reload()
|
this.refs['code-' + this.state.snippetIndex].reload()
|
||||||
|
|
||||||
|
if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) {
|
||||||
|
console.log('no need for arrows')
|
||||||
|
this.moveTabBarBy(0)
|
||||||
|
} else {
|
||||||
|
const lastTab = this.allTabs.lastChild
|
||||||
|
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
|
||||||
|
console.log('need to scroll')
|
||||||
|
const width = this.visibleTabs.offsetWidth
|
||||||
|
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
|
||||||
|
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
|
||||||
|
} else {
|
||||||
|
this.setState(this.getArrowsState())
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
renameSnippetByIndex (index, name) {
|
renameSnippetByIndex (index, name) {
|
||||||
let snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].name = name
|
snippets[index].name = name
|
||||||
let syntax = CodeMirror.findModeByFileName(name.trim())
|
const syntax = CodeMirror.findModeByFileName(name.trim())
|
||||||
let mode = syntax != null ? syntax.name : null
|
const mode = syntax != null ? syntax.name : null
|
||||||
if (mode != null) snippets[index].mode = mode
|
if (mode != null) {
|
||||||
this.state.note.snippets = snippets
|
snippets[index].mode = mode
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SNIPPET_LANG', {
|
||||||
|
name: mode
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
||||||
|
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
note: this.state.note
|
note: state.note
|
||||||
}, () => {
|
}), () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleModeOptionClick (index, name) {
|
handleModeOptionClick (index, name) {
|
||||||
return (e) => {
|
return (e) => {
|
||||||
let snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].mode = name
|
snippets[index].mode = name
|
||||||
this.state.note.snippets = snippets
|
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
||||||
|
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
note: this.state.note
|
note: state.note
|
||||||
}, () => {
|
}), () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SELECT_LANG', {
|
||||||
|
name
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCodeChange (index) {
|
handleCodeChange (index) {
|
||||||
return (e) => {
|
return (e) => {
|
||||||
let snippets = this.state.note.snippets.slice()
|
const snippets = this.state.note.snippets.slice()
|
||||||
snippets[index].content = this.refs['code-' + index].value
|
snippets[index].content = this.refs['code-' + index].value
|
||||||
this.state.note.snippets = snippets
|
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
note: this.state.note
|
note: state.note
|
||||||
}, () => {
|
}), () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -308,6 +410,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
|
|
||||||
handleKeyDown (e) {
|
handleKeyDown (e) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
|
// tab key
|
||||||
case 9:
|
case 9:
|
||||||
if (e.ctrlKey && !e.shiftKey) {
|
if (e.ctrlKey && !e.shiftKey) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -320,9 +423,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
// L key
|
||||||
case 76:
|
case 76:
|
||||||
{
|
{
|
||||||
let isSuper = global.process.platform === 'darwin'
|
const isSuper = global.process.platform === 'darwin'
|
||||||
? e.metaKey
|
? e.metaKey
|
||||||
: e.ctrlKey
|
: e.ctrlKey
|
||||||
if (isSuper) {
|
if (isSuper) {
|
||||||
@@ -331,9 +435,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
// T key
|
||||||
case 84:
|
case 84:
|
||||||
{
|
{
|
||||||
let isSuper = global.process.platform === 'darwin'
|
const isSuper = global.process.platform === 'darwin'
|
||||||
? e.metaKey
|
? e.metaKey
|
||||||
: e.ctrlKey
|
: e.ctrlKey
|
||||||
if (isSuper) {
|
if (isSuper) {
|
||||||
@@ -346,14 +451,14 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleModeButtonClick (e, index) {
|
handleModeButtonClick (e, index) {
|
||||||
let menu = new Menu()
|
const templetes = []
|
||||||
CodeMirror.modeInfo.forEach((mode) => {
|
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
|
||||||
menu.append(new MenuItem({
|
templetes.push({
|
||||||
label: mode.name,
|
label: mode.name,
|
||||||
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
|
||||||
}))
|
})
|
||||||
})
|
})
|
||||||
menu.popup(remote.getCurrentWindow())
|
context.popup(templetes)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleIndentTypeButtonClick (e) {
|
handleIndentTypeButtonClick (e) {
|
||||||
@@ -387,8 +492,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleIndentSizeItemClick (e, indentSize) {
|
handleIndentSizeItemClick (e, indentSize) {
|
||||||
let { config, dispatch } = this.props
|
const { config, dispatch } = this.props
|
||||||
let editor = Object.assign({}, config.editor, {
|
const editor = Object.assign({}, config.editor, {
|
||||||
indentSize
|
indentSize
|
||||||
})
|
})
|
||||||
ConfigManager.set({
|
ConfigManager.set({
|
||||||
@@ -403,8 +508,8 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleIndentTypeItemClick (e, indentType) {
|
handleIndentTypeItemClick (e, indentType) {
|
||||||
let { config, dispatch } = this.props
|
const { config, dispatch } = this.props
|
||||||
let editor = Object.assign({}, config.editor, {
|
const editor = Object.assign({}, config.editor, {
|
||||||
indentType
|
indentType
|
||||||
})
|
})
|
||||||
ConfigManager.set({
|
ConfigManager.set({
|
||||||
@@ -422,36 +527,87 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
this.refs.description.focus()
|
this.refs.description.focus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moveToTab (tab) {
|
||||||
|
const easeOutCubic = t => (--t) * t * t + 1
|
||||||
|
const startScrollPosition = this.visibleTabs.scrollLeft
|
||||||
|
const animationTiming = 300
|
||||||
|
const scrollMoreCoeff = 1.4 // introduce coefficient, because we want to scroll a bit further to see next tab
|
||||||
|
|
||||||
|
let scrollBy = (tab.offsetLeft - startScrollPosition)
|
||||||
|
|
||||||
|
if (tab.offsetLeft > startScrollPosition) {
|
||||||
|
// if tab is on the right side and we want to show the whole tab in visible area,
|
||||||
|
// we need to include width of the tab and visible area in the formula
|
||||||
|
// ___________________________________________
|
||||||
|
// |____|_______|________|________|_show_this_|
|
||||||
|
// ↑_____________________↑
|
||||||
|
// visible area
|
||||||
|
scrollBy += (tab.offsetWidth - this.visibleTabs.offsetWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
let startTime = null
|
||||||
|
const scrollAnimation = time => {
|
||||||
|
startTime = startTime || time
|
||||||
|
const elapsed = (time - startTime) / animationTiming
|
||||||
|
|
||||||
|
this.visibleTabs.scrollLeft = startScrollPosition + easeOutCubic(elapsed) * scrollBy * scrollMoreCoeff
|
||||||
|
if (elapsed < 1) {
|
||||||
|
window.requestAnimationFrame(scrollAnimation)
|
||||||
|
} else {
|
||||||
|
this.setState(this.getArrowsState())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.requestAnimationFrame(scrollAnimation)
|
||||||
|
}
|
||||||
|
|
||||||
|
getArrowsState () {
|
||||||
|
const allTabs = this.allTabs
|
||||||
|
const visibleTabs = this.visibleTabs
|
||||||
|
|
||||||
|
const showArrows = visibleTabs.offsetWidth < allTabs.scrollWidth
|
||||||
|
const enableRightArrow = visibleTabs.scrollLeft !== allTabs.scrollWidth - visibleTabs.offsetWidth
|
||||||
|
const enableLeftArrow = visibleTabs.scrollLeft !== 0
|
||||||
|
|
||||||
|
return {showArrows, enableRightArrow, enableLeftArrow}
|
||||||
|
}
|
||||||
|
|
||||||
addSnippet () {
|
addSnippet () {
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
note.snippets = note.snippets.concat([{
|
note.snippets = note.snippets.concat([{
|
||||||
name: '',
|
name: '',
|
||||||
mode: 'Plain Text',
|
mode: 'Plain Text',
|
||||||
content: ''
|
content: ''
|
||||||
}])
|
}])
|
||||||
let snippetIndex = note.snippets.length - 1
|
const snippetIndex = note.snippets.length - 1
|
||||||
|
|
||||||
this.setState({
|
this.setState(Object.assign({
|
||||||
note,
|
note,
|
||||||
snippetIndex
|
snippetIndex
|
||||||
}, () => {
|
}, this.getArrowsState()), () => {
|
||||||
|
if (this.state.showArrows) {
|
||||||
|
const tabs = this.allTabs.querySelectorAll('div')
|
||||||
|
if (tabs) {
|
||||||
|
this.moveToTab(tabs[snippetIndex])
|
||||||
|
}
|
||||||
|
}
|
||||||
this.refs['tab-' + snippetIndex].startRenaming()
|
this.refs['tab-' + snippetIndex].startRenaming()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpNextTab () {
|
jumpNextTab () {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length
|
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
|
||||||
}, () => {
|
}), () => {
|
||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpPrevTab () {
|
jumpPrevTab () {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length
|
snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
|
||||||
}, () => {
|
}), () => {
|
||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -469,26 +625,26 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
showWarning () {
|
showWarning () {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Sorry!',
|
message: i18n.__('Sorry!'),
|
||||||
detail: 'md/text import is available only a markdown note.',
|
detail: i18n.__('md/text import is available only a markdown note.'),
|
||||||
buttons: ['OK']
|
buttons: [i18n.__('OK')]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { data, config, location } = this.props
|
const { data, config, location } = this.props
|
||||||
let { note } = this.state
|
const { note } = this.state
|
||||||
|
|
||||||
let storageKey = note.storage
|
const storageKey = note.storage
|
||||||
let folderKey = note.folder
|
const folderKey = note.folder
|
||||||
|
|
||||||
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
let editorFontSize = parseInt(config.editor.fontSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
|
||||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||||
|
|
||||||
let tabList = note.snippets.map((snippet, index) => {
|
const tabList = note.snippets.map((snippet, index) => {
|
||||||
let isActive = this.state.snippetIndex === index
|
const isActive = this.state.snippetIndex === index
|
||||||
|
|
||||||
return <SnippetTab
|
return <SnippetTab
|
||||||
key={index}
|
key={index}
|
||||||
@@ -499,13 +655,15 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
onDelete={(e) => this.handleTabDeleteButtonClick(e, index)}
|
onDelete={(e) => this.handleTabDeleteButtonClick(e, index)}
|
||||||
onRename={(name) => this.renameSnippetByIndex(index, name)}
|
onRename={(name) => this.renameSnippetByIndex(index, name)}
|
||||||
isDeletable={note.snippets.length > 1}
|
isDeletable={note.snippets.length > 1}
|
||||||
|
onDragStart={(e) => this.handleTabDragStart(e, index)}
|
||||||
|
onDrop={(e) => this.handleTabDrop(e, index)}
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
|
|
||||||
let viewList = note.snippets.map((snippet, index) => {
|
const viewList = note.snippets.map((snippet, index) => {
|
||||||
let isActive = this.state.snippetIndex === index
|
const isActive = this.state.snippetIndex === index
|
||||||
|
|
||||||
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
return <div styleName='tabView'
|
return <div styleName='tabView'
|
||||||
@@ -519,6 +677,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||||
ref={'code-' + index}
|
ref={'code-' + index}
|
||||||
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
||||||
|
storageKey={storageKey}
|
||||||
/>
|
/>
|
||||||
: <CodeEditor styleName='tabView-content'
|
: <CodeEditor styleName='tabView-content'
|
||||||
mode={snippet.mode}
|
mode={snippet.mode}
|
||||||
@@ -528,7 +687,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
fontSize={editorFontSize}
|
fontSize={editorFontSize}
|
||||||
indentType={config.editor.indentType}
|
indentType={config.editor.indentType}
|
||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
|
displayLineNumbers={config.editor.displayLineNumbers}
|
||||||
keyMap={config.editor.keyMap}
|
keyMap={config.editor.keyMap}
|
||||||
|
scrollPastEnd={config.editor.scrollPastEnd}
|
||||||
|
fetchUrlTitle={config.editor.fetchUrlTitle}
|
||||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||||
ref={'code-' + index}
|
ref={'code-' + index}
|
||||||
/>
|
/>
|
||||||
@@ -536,7 +698,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
})
|
})
|
||||||
|
|
||||||
let 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({
|
||||||
@@ -545,26 +707,31 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
|
||||||
|
|
||||||
const trashTopBar = <div styleName='info'>
|
const trashTopBar = <div styleName='info'>
|
||||||
<div styleName='info-left'>
|
<div styleName='info-left'>
|
||||||
<i styleName='undo-button'
|
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
|
||||||
className='fa fa-undo fa-fw'
|
|
||||||
onClick={(e) => this.handleUndoButtonClick(e)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||||
|
<InfoButton
|
||||||
|
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||||
|
/>
|
||||||
|
<InfoPanelTrashed
|
||||||
|
storageName={currentOption.storage.name}
|
||||||
|
folderName={currentOption.folder.name}
|
||||||
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
|
createdAt={formatDate(note.createdAt)}
|
||||||
|
exportAsMd={this.showWarning}
|
||||||
|
exportAsTxt={this.showWarning}
|
||||||
|
exportAsHtml={this.showWarning}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
const detailTopBar = <div styleName='info'>
|
const detailTopBar = <div styleName='info'>
|
||||||
<div styleName='info-left'>
|
<div styleName='info-left'>
|
||||||
<StarButton styleName='info-left-button'
|
|
||||||
onClick={(e) => this.handleStarButtonClick(e)}
|
|
||||||
isActive={note.isStarred}
|
|
||||||
/>
|
|
||||||
<div styleName='info-left-top'>
|
<div styleName='info-left-top'>
|
||||||
<FolderSelect styleName='info-left-top-folderSelect'
|
<FolderSelect styleName='info-left-top-folderSelect'
|
||||||
value={this.state.note.storage + '-' + this.state.note.folder}
|
value={this.state.note.storage + '-' + this.state.note.folder}
|
||||||
@@ -581,23 +748,32 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='info-right'>
|
<div styleName='info-right'>
|
||||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
<StarButton
|
||||||
<button styleName='control-fullScreenButton'
|
onClick={(e) => this.handleStarButtonClick(e)}
|
||||||
onMouseDown={(e) => this.handleFullScreenButton(e)}
|
isActive={note.isStarred}
|
||||||
>
|
/>
|
||||||
<i className='fa fa-expand' styleName='fullScreen-button' />
|
|
||||||
|
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')}
|
||||||
|
onMouseDown={(e) => this.handleFullScreenButton(e)}>
|
||||||
|
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
|
||||||
|
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||||
|
|
||||||
<InfoButton
|
<InfoButton
|
||||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InfoPanel
|
<InfoPanel
|
||||||
storageName={currentOption.storage.name}
|
storageName={currentOption.storage.name}
|
||||||
folderName={currentOption.folder.name}
|
folderName={currentOption.folder.name}
|
||||||
noteLink={`[${note.title}](${location.query.key})`}
|
noteLink={`[${note.title}](:note:${location.query.key})`}
|
||||||
updatedAt={formatDate(note.updatedAt)}
|
updatedAt={formatDate(note.updatedAt)}
|
||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.showWarning}
|
exportAsMd={this.showWarning}
|
||||||
exportAsTxt={this.showWarning}
|
exportAsTxt={this.showWarning}
|
||||||
|
type={note.type}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -618,16 +794,32 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
fontSize: parseInt(config.preview.fontSize, 10)
|
fontSize: parseInt(config.preview.fontSize, 10)
|
||||||
}}
|
}}
|
||||||
ref='description'
|
ref='description'
|
||||||
placeholder='Description...'
|
placeholder={i18n.__('Description...')}
|
||||||
value={this.state.note.description}
|
value={this.state.note.description}
|
||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div styleName='tabList'>
|
<div styleName='tabList'>
|
||||||
<div styleName='list'>
|
<button styleName='tabButton'
|
||||||
{tabList}
|
hidden={!this.state.showArrows}
|
||||||
|
disabled={!this.state.enableLeftArrow}
|
||||||
|
onClick={(e) => this.handleTabMoveLeftButtonClick(e)}
|
||||||
|
>
|
||||||
|
<i className='fa fa-chevron-left' />
|
||||||
|
</button>
|
||||||
|
<div styleName='list' onScroll={(e) => { this.setState(this.getArrowsState()) }} ref={(tabs) => { this.visibleTabs = tabs }}>
|
||||||
|
<div styleName='allTabs' ref={(tabs) => { this.allTabs = tabs }}>
|
||||||
|
{tabList}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button styleName='plusButton'
|
<button styleName='tabButton'
|
||||||
|
hidden={!this.state.showArrows}
|
||||||
|
disabled={!this.state.enableRightArrow}
|
||||||
|
onClick={(e) => this.handleTabMoveRightButtonClick(e)}
|
||||||
|
>
|
||||||
|
<i className='fa fa-chevron-right' />
|
||||||
|
</button>
|
||||||
|
<button styleName='tabButton'
|
||||||
onClick={(e) => this.handleTabPlusButtonClick(e)}
|
onClick={(e) => this.handleTabPlusButtonClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-plus' />
|
<i className='fa fa-plus' />
|
||||||
@@ -641,7 +833,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)}
|
onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)}
|
||||||
>
|
>
|
||||||
{this.state.note.snippets[this.state.snippetIndex].mode == null
|
{this.state.note.snippets[this.state.snippetIndex].mode == null
|
||||||
? 'Select Syntax...'
|
? i18n.__('Select Syntax...')
|
||||||
: this.state.note.snippets[this.state.snippetIndex].mode
|
: this.state.note.snippets[this.state.snippetIndex].mode
|
||||||
}
|
}
|
||||||
<i className='fa fa-caret-down' />
|
<i className='fa fa-caret-down' />
|
||||||
|
|||||||
@@ -3,23 +3,20 @@
|
|||||||
|
|
||||||
.root
|
.root
|
||||||
absolute top bottom right
|
absolute top bottom right
|
||||||
border-width 0 0 1px
|
border-left 1px solid alpha(#DEDEDE, 60%)
|
||||||
border-style solid
|
|
||||||
border-color $ui-borderColor
|
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
box-shadow $note-detail-box-shadow
|
box-shadow none
|
||||||
|
|
||||||
.body
|
.body
|
||||||
absolute left right
|
absolute left right
|
||||||
left $note-detail-left-margin
|
margin 0 30px
|
||||||
right $note-detail-right-margin
|
|
||||||
top $info-height + $info-margin-under-border
|
top $info-height + $info-margin-under-border
|
||||||
bottom $statusBar-height
|
bottom $statusBar-height
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|
||||||
.body .description
|
.body .description
|
||||||
absolute top left right
|
absolute top left right
|
||||||
height 80px
|
height 50px
|
||||||
|
|
||||||
.body .description textarea
|
.body .description textarea
|
||||||
outline none
|
outline none
|
||||||
@@ -27,53 +24,88 @@
|
|||||||
height 100%
|
height 100%
|
||||||
width 100%
|
width 100%
|
||||||
resize none
|
resize none
|
||||||
border none
|
border 1px solid $ui-borderColor
|
||||||
padding 10px
|
padding 2px 5px
|
||||||
line-height 1.6
|
line-height 1.6
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
|
||||||
.tabList
|
.tabList
|
||||||
absolute left right
|
absolute left right
|
||||||
top 80px
|
top 55px
|
||||||
height 30px
|
height 30px
|
||||||
display flex
|
display flex
|
||||||
background-color $ui-noteDetail-backgroundColor
|
background-color $ui-noteDetail-backgroundColor
|
||||||
|
overflow hidden
|
||||||
|
|
||||||
.tabList .list
|
.tabList .list
|
||||||
flex 1
|
flex 1
|
||||||
display flex
|
|
||||||
overflow hidden
|
overflow hidden
|
||||||
|
overflow-x scroll
|
||||||
|
position relative
|
||||||
|
|
||||||
.tabList .plusButton
|
&::-webkit-scrollbar {
|
||||||
navButtonColor()
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.allTabs
|
||||||
|
display flex
|
||||||
|
position relative
|
||||||
|
overflow visible
|
||||||
|
left 0
|
||||||
|
transition left 0.1s
|
||||||
|
|
||||||
|
.tabList .tabButton
|
||||||
|
navWhiteButtonColor()
|
||||||
width 30px
|
width 30px
|
||||||
|
|
||||||
.tabView
|
.tabView
|
||||||
absolute left right bottom
|
absolute left right bottom
|
||||||
top 130px
|
top 100px
|
||||||
|
|
||||||
.tabView-content
|
.tabView-content
|
||||||
absolute top left right bottom
|
absolute top left right bottom
|
||||||
|
|
||||||
.override
|
.override
|
||||||
absolute bottom left
|
absolute bottom left
|
||||||
|
bottom 1px
|
||||||
left 60px
|
left 60px
|
||||||
height 23px
|
z-index 101
|
||||||
z-index 1
|
|
||||||
button
|
button
|
||||||
navButtonColor()
|
navButtonColor()
|
||||||
height 24px
|
padding 0 6px
|
||||||
|
&:hover
|
||||||
|
color $ui-active-color
|
||||||
&:active .update-icon
|
&:active .update-icon
|
||||||
color white
|
color $ui-active-color
|
||||||
|
|
||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
float right
|
top 80px
|
||||||
padding 0 0 2px 0
|
margin-bottom 10px
|
||||||
topBarButtonLight()
|
topBarButtonRight()
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 50px
|
||||||
|
right 70px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
|
body[data-theme="white"]
|
||||||
|
.root
|
||||||
|
box-shadow $note-detail-box-shadow
|
||||||
|
border none
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
border-color $ui-dark-borderColor
|
border-left 1px solid $ui-dark-borderColor
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
box-shadow none
|
box-shadow none
|
||||||
|
|
||||||
@@ -83,6 +115,7 @@ body[data-theme="dark"]
|
|||||||
.body .description textarea
|
.body .description textarea
|
||||||
background-color $ui-dark-noteDetail-backgroundColor
|
background-color $ui-dark-noteDetail-backgroundColor
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
border 1px solid $ui-dark-borderColor
|
||||||
|
|
||||||
.tabList
|
.tabList
|
||||||
background-color $ui-button--active-backgroundColor
|
background-color $ui-button--active-backgroundColor
|
||||||
@@ -103,3 +136,37 @@ body[data-theme="dark"]
|
|||||||
|
|
||||||
.control-fullScreenButton
|
.control-fullScreenButton
|
||||||
topBarButtonDark()
|
topBarButtonDark()
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-solarized-dark-borderColor
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body .description textarea
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
border 1px solid $ui-solarized-dark-borderColor
|
||||||
|
|
||||||
|
.tabList
|
||||||
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body .description textarea
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
border 1px solid $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.tabList
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './StarButton.styl'
|
import styles from './StarButton.styl'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class StarButton extends React.Component {
|
class StarButton extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -31,7 +33,7 @@ class StarButton extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { className } = this.props
|
const { className } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button className={_.isString(className)
|
<button className={_.isString(className)
|
||||||
@@ -45,14 +47,14 @@ class StarButton extends React.Component {
|
|||||||
onMouseDown={(e) => this.handleMouseDown(e)}
|
onMouseDown={(e) => this.handleMouseDown(e)}
|
||||||
onMouseUp={(e) => this.handleMouseUp(e)}
|
onMouseUp={(e) => this.handleMouseUp(e)}
|
||||||
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
onMouseLeave={(e) => this.handleMouseLeave(e)}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}>
|
||||||
>
|
<img styleName='icon'
|
||||||
<i styleName='icon'
|
src={this.state.isActive || this.props.isActive
|
||||||
className={this.state.isActive || this.props.isActive
|
? '../resources/icon/icon-starred.svg'
|
||||||
? 'fa fa-star'
|
: '../resources/icon/icon-star.svg'
|
||||||
: 'fa fa-star-o'
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<span styleName='tooltip'>{i18n.__('Star')}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +1,40 @@
|
|||||||
.root
|
.root
|
||||||
left 7px
|
top 45px
|
||||||
top 0
|
topBarButtonRight()
|
||||||
padding 0
|
|
||||||
color alpha($ui-favorite-star-button-color, 60%)
|
|
||||||
&:hover
|
&:hover
|
||||||
transition 0.15s
|
transition 0.2s
|
||||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
color $ui-favorite-star-button-color
|
&:hover .tooltip
|
||||||
&:active
|
opacity 1
|
||||||
transition 0.15s
|
|
||||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
.tooltip
|
||||||
color $ui-favorite-star-button-color
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 50px
|
||||||
|
right 115px
|
||||||
|
width 40px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
.root--active
|
.root--active
|
||||||
@extend .root
|
@extend .root
|
||||||
|
transition 0.15s
|
||||||
color $ui-favorite-star-button-color
|
color $ui-favorite-star-button-color
|
||||||
&:hover
|
&:hover
|
||||||
transition 0.15s
|
transition 0.2s
|
||||||
color $ui-favorite-star-button-color
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
|
||||||
&:active
|
|
||||||
transition 0.15s
|
|
||||||
color $ui-favorite-star-button-color
|
|
||||||
background-color alpha($ui-button--active-backgroundColor, 40%)
|
|
||||||
|
|
||||||
.icon
|
.icon
|
||||||
transition transform 0.15s
|
transition transform 0.15s
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
|
topBarButtonDark()
|
||||||
&:hover
|
&:hover
|
||||||
transition 0.15s
|
transition 0.2s
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
color alpha($ui-favorite-star-button-color, 0.6)
|
||||||
color $ui-favorite-star-button-color
|
|
||||||
&:active
|
|
||||||
transition 0.15s
|
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
|
||||||
color $ui-favorite-star-button-color
|
|
||||||
|
|
||||||
.root--active
|
|
||||||
@extend .root
|
|
||||||
color $ui-favorite-star-button-color
|
|
||||||
&:hover
|
|
||||||
transition 0.15s
|
|
||||||
color $ui-favorite-star-button-color
|
|
||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './TagSelect.styl'
|
import styles from './TagSelect.styl'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
class TagSelect extends React.Component {
|
class TagSelect extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -37,17 +39,14 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleNewTagBlur (e) {
|
||||||
|
this.submitTag()
|
||||||
|
}
|
||||||
|
|
||||||
removeLastTag () {
|
removeLastTag () {
|
||||||
let { value } = this.props
|
this.removeTagByCallback((value) => {
|
||||||
|
value.pop()
|
||||||
value = _.isArray(value)
|
})
|
||||||
? value.slice()
|
|
||||||
: []
|
|
||||||
value.pop()
|
|
||||||
value = _.uniq(value)
|
|
||||||
|
|
||||||
this.value = value
|
|
||||||
this.props.onChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset () {
|
reset () {
|
||||||
@@ -60,6 +59,7 @@ class TagSelect extends React.Component {
|
|||||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
|
||||||
let { value } = this.props
|
let { value } = this.props
|
||||||
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
|
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
|
||||||
|
newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag
|
||||||
|
|
||||||
if (newTag.length <= 0) {
|
if (newTag.length <= 0) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -89,21 +89,28 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTagRemoveButtonClick (tag) {
|
handleTagRemoveButtonClick (tag) {
|
||||||
return (e) => {
|
this.removeTagByCallback((value, tag) => {
|
||||||
let { value } = this.props
|
|
||||||
|
|
||||||
value.splice(value.indexOf(tag), 1)
|
value.splice(value.indexOf(tag), 1)
|
||||||
value = _.uniq(value)
|
}, tag)
|
||||||
|
}
|
||||||
|
|
||||||
this.value = value
|
removeTagByCallback (callback, tag = null) {
|
||||||
this.props.onChange()
|
let { value } = this.props
|
||||||
}
|
|
||||||
|
value = _.isArray(value)
|
||||||
|
? value.slice()
|
||||||
|
: []
|
||||||
|
callback(value, tag)
|
||||||
|
value = _.uniq(value)
|
||||||
|
|
||||||
|
this.value = value
|
||||||
|
this.props.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { value, className } = this.props
|
const { value, className } = this.props
|
||||||
|
|
||||||
let tagList = _.isArray(value)
|
const tagList = _.isArray(value)
|
||||||
? value.map((tag) => {
|
? value.map((tag) => {
|
||||||
return (
|
return (
|
||||||
<span styleName='tag'
|
<span styleName='tag'
|
||||||
@@ -111,9 +118,9 @@ class TagSelect extends React.Component {
|
|||||||
>
|
>
|
||||||
<span styleName='tag-label'>#{tag}</span>
|
<span styleName='tag-label'>#{tag}</span>
|
||||||
<button styleName='tag-removeButton'
|
<button styleName='tag-removeButton'
|
||||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)}
|
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-times fa-fw tag-removeButton-icon' />
|
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
@@ -131,9 +138,10 @@ class TagSelect extends React.Component {
|
|||||||
<input styleName='newTag'
|
<input styleName='newTag'
|
||||||
ref='newTag'
|
ref='newTag'
|
||||||
value={this.state.newTag}
|
value={this.state.newTag}
|
||||||
placeholder='Add tag...'
|
placeholder={i18n.__('Add tag...')}
|
||||||
onChange={(e) => this.handleNewTagInputChange(e)}
|
onChange={(e) => this.handleNewTagInputChange(e)}
|
||||||
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
|
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
|
||||||
|
onBlur={(e) => this.handleNewTagBlur(e)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,61 +1,54 @@
|
|||||||
.root
|
.root
|
||||||
display inline-block
|
display flex
|
||||||
|
align-items center
|
||||||
user-select none
|
user-select none
|
||||||
height 23px
|
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
width 300px
|
width 100%
|
||||||
overflow-x scroll
|
overflow-x scroll
|
||||||
white-space nowrap
|
white-space nowrap
|
||||||
|
margin-top 31px
|
||||||
|
position absolute
|
||||||
|
|
||||||
.root::-webkit-scrollbar
|
.root::-webkit-scrollbar
|
||||||
display none
|
display none
|
||||||
|
|
||||||
.tag
|
.tag
|
||||||
display inline-block
|
display flex
|
||||||
margin 1px 3px
|
align-items center
|
||||||
padding 0
|
margin 0px 2px
|
||||||
height 20px
|
padding 2px 4px
|
||||||
background-color alpha($ui-tag-backgroundColor, 10%)
|
background-color alpha($ui-tag-backgroundColor, 3%)
|
||||||
border-radius 3px
|
border-radius 4px
|
||||||
overflow hidden
|
position relative
|
||||||
clearfix()
|
clearfix()
|
||||||
|
|
||||||
.tag-removeButton
|
.tag-removeButton
|
||||||
float right
|
|
||||||
height 20px
|
|
||||||
width 18px
|
|
||||||
margin 0
|
margin 0
|
||||||
padding 0
|
padding 0
|
||||||
border-style solid
|
border-style solid
|
||||||
border-width 0
|
border-width 0
|
||||||
border-radius 20px
|
border-radius 20px
|
||||||
line-height 18px
|
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-button-color
|
color $ui-button-color
|
||||||
|
position absolute
|
||||||
|
right 6px
|
||||||
|
|
||||||
.tag-removeButton-icon
|
.tag-removeButton-icon
|
||||||
width 5px
|
width 5px
|
||||||
padding-right 4px
|
padding-right 4px
|
||||||
|
|
||||||
.tag-label
|
.tag-label
|
||||||
font-size 11px
|
font-size 13px
|
||||||
font-weight 600
|
color: $ui-text-color
|
||||||
color: alpha($ui-text-color, 80%)
|
padding 4px 16px 4px 8px
|
||||||
float left
|
|
||||||
height 20px
|
|
||||||
line-height 20px
|
|
||||||
padding 0 6px
|
|
||||||
|
|
||||||
.newTag
|
.newTag
|
||||||
display inline-block
|
box-sizing border-box
|
||||||
margin 2px 0 15px 2px
|
|
||||||
vertical-align middle
|
|
||||||
height 18px
|
|
||||||
box-sizing borde-box
|
|
||||||
border none
|
border none
|
||||||
background-color transparent
|
background-color transparent
|
||||||
outline none
|
outline none
|
||||||
padding 0 4px
|
padding 0 4px
|
||||||
|
font-size 13px
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.tag
|
.tag
|
||||||
@@ -73,3 +66,35 @@ body[data-theme="dark"]
|
|||||||
border-color none
|
border-color none
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.tag
|
||||||
|
background-color $ui-solarized-dark-tag-backgroundColor
|
||||||
|
|
||||||
|
.tag-removeButton
|
||||||
|
border-color $ui-button--focus-borderColor
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.tag-label
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
.newTag
|
||||||
|
border-color none
|
||||||
|
background-color transparent
|
||||||
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.tag
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
|
||||||
|
.tag-removeButton
|
||||||
|
border-color $ui-button--focus-borderColor
|
||||||
|
background-color transparent
|
||||||
|
|
||||||
|
.tag-label
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.newTag
|
||||||
|
border-color none
|
||||||
|
background-color transparent
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|||||||
26
browser/main/Detail/ToggleModeButton.js
Normal file
26
browser/main/Detail/ToggleModeButton.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
|
import styles from './ToggleModeButton.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
|
const ToggleModeButton = ({
|
||||||
|
onClick, editorType
|
||||||
|
}) => (
|
||||||
|
<div styleName='control-toggleModeButton'>
|
||||||
|
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
|
||||||
|
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
|
||||||
|
</div>
|
||||||
|
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
|
||||||
|
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
|
||||||
|
</div>
|
||||||
|
<span styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
ToggleModeButton.propTypes = {
|
||||||
|
onClick: PropTypes.func.isRequired,
|
||||||
|
editorType: PropTypes.string.Required
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(ToggleModeButton, styles)
|
||||||
65
browser/main/Detail/ToggleModeButton.styl
Normal file
65
browser/main/Detail/ToggleModeButton.styl
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
.control-toggleModeButton
|
||||||
|
height 25px
|
||||||
|
border-radius 50px
|
||||||
|
background-color #F4F4F4
|
||||||
|
width 52px
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
position: relative
|
||||||
|
top 2px
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
width 33px
|
||||||
|
height 24px
|
||||||
|
box-shadow 2px 0px 7px #eee
|
||||||
|
z-index 1
|
||||||
|
|
||||||
|
div
|
||||||
|
width 40px
|
||||||
|
height 100%
|
||||||
|
border-radius 50%
|
||||||
|
display flex
|
||||||
|
align-items center
|
||||||
|
justify-content center
|
||||||
|
cursor pointer
|
||||||
|
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 33px
|
||||||
|
left -10px
|
||||||
|
z-index 200
|
||||||
|
width 80px
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
|
body[data-theme="dark"]
|
||||||
|
.control-fullScreenButton
|
||||||
|
topBarButtonDark()
|
||||||
|
|
||||||
|
.control-toggleModeButton
|
||||||
|
background-color #3A404C
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
box-shadow 2px 0px 7px #444444
|
||||||
|
|
||||||
|
body[data-theme="solarized-dark"]
|
||||||
|
.control-toggleModeButton
|
||||||
|
background-color #002B36
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
box-shadow 2px 0px 7px #222222
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.control-toggleModeButton
|
||||||
|
background-color #272822
|
||||||
|
.active
|
||||||
|
background-color #1EC38B
|
||||||
|
box-shadow 2px 0px 7px #222222
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './TrashButton.styl'
|
import styles from './TrashButton.styl'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
|
||||||
const TrashButton = ({
|
const TrashButton = ({
|
||||||
onClick
|
onClick
|
||||||
@@ -8,7 +10,8 @@ const TrashButton = ({
|
|||||||
<button styleName='control-trashButton'
|
<button styleName='control-trashButton'
|
||||||
onClick={(e) => onClick(e)}
|
onClick={(e) => onClick(e)}
|
||||||
>
|
>
|
||||||
<i className='fa fa-trash trashButton' styleName='info-button' />
|
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
|
||||||
|
<span styleName='tooltip'>{i18n.__('Trash')}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,28 @@
|
|||||||
.control-trashButton
|
.control-trashButton
|
||||||
float right
|
top 115px
|
||||||
topBarButtonLight()
|
topBarButtonRight()
|
||||||
|
&:hover .tooltip
|
||||||
|
opacity 1
|
||||||
|
|
||||||
|
.tooltip
|
||||||
|
tooltip()
|
||||||
|
position absolute
|
||||||
|
pointer-events none
|
||||||
|
top 50px
|
||||||
|
right 50px
|
||||||
|
z-index 200
|
||||||
|
padding 5px
|
||||||
|
line-height normal
|
||||||
|
border-radius 2px
|
||||||
|
opacity 0
|
||||||
|
transition 0.1s
|
||||||
|
|
||||||
|
.control-trashButton--in-trash
|
||||||
|
top 60px
|
||||||
|
topBarButtonRight()
|
||||||
|
|
||||||
.trashButton
|
.trashButton
|
||||||
padding 0px
|
padding 0px
|
||||||
margin 15px 0
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.control-trashButton
|
.control-trashButton
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { PropTypes } from 'react'
|
import PropTypes from 'prop-types'
|
||||||
|
import React from 'react'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './Detail.styl'
|
import styles from './Detail.styl'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
@@ -6,6 +7,9 @@ import MarkdownNoteDetail from './MarkdownNoteDetail'
|
|||||||
import SnippetNoteDetail from './SnippetNoteDetail'
|
import SnippetNoteDetail from './SnippetNoteDetail'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import StatusBar from '../StatusBar'
|
import StatusBar from '../StatusBar'
|
||||||
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import debounceRender from 'react-debounce-render'
|
||||||
|
import searchFromNotes from 'browser/lib/search'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
|
|
||||||
@@ -32,14 +36,38 @@ class Detail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let { location, data, config } = this.props
|
const { location, data, params, config } = this.props
|
||||||
let note = null
|
let note = null
|
||||||
if (location.query.key != null) {
|
|
||||||
let splitted = location.query.key.split('-')
|
|
||||||
let storageKey = splitted.shift()
|
|
||||||
let noteKey = splitted.shift()
|
|
||||||
|
|
||||||
note = data.noteMap.get(storageKey + '-' + noteKey)
|
if (location.query.key != null) {
|
||||||
|
const noteKey = location.query.key
|
||||||
|
const allNotes = data.noteMap.map(note => note)
|
||||||
|
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||||
|
let displayedNotes = allNotes
|
||||||
|
|
||||||
|
if (location.pathname.match(/\/searched/)) {
|
||||||
|
const searchStr = params.searchword
|
||||||
|
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
|
||||||
|
: searchFromNotes(allNotes, searchStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location.pathname.match(/\/tags/)) {
|
||||||
|
const listOfTags = params.tagname.split(' ')
|
||||||
|
displayedNotes = data.noteMap.map(note => note).filter(note =>
|
||||||
|
listOfTags.every(tag => note.tags.includes(tag))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location.pathname.match(/\/trashed/)) {
|
||||||
|
displayedNotes = trashedNotes
|
||||||
|
} else {
|
||||||
|
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const noteKeys = displayedNotes.map(note => note.key)
|
||||||
|
if (noteKeys.includes(noteKey)) {
|
||||||
|
note = data.noteMap.get(noteKey)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
@@ -49,7 +77,7 @@ class Detail extends React.Component {
|
|||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
>
|
>
|
||||||
<div styleName='empty'>
|
<div styleName='empty'>
|
||||||
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new post</div>
|
<div styleName='empty-message'>{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />{i18n.__('to create a new note')}</div>
|
||||||
</div>
|
</div>
|
||||||
<StatusBar
|
<StatusBar
|
||||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||||
@@ -100,4 +128,4 @@ Detail.propTypes = {
|
|||||||
ignorePreviewPointerEvents: PropTypes.bool
|
ignorePreviewPointerEvents: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CSSModules(Detail, styles)
|
export default debounceRender(CSSModules(Detail, styles))
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user