mirror of
https://github.com/diaspora/diaspora.git
synced 2026-01-11 08:07:59 -05:00
Compare commits
883 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49f3442f64 | ||
|
|
aadd8396cc | ||
|
|
251e4329c5 | ||
|
|
452f46a548 | ||
|
|
c2589f10cc | ||
|
|
c24ca7d56c | ||
|
|
e569ec72c7 | ||
|
|
d26b917e6f | ||
|
|
0ea682b1d5 | ||
|
|
3fcd5708e9 | ||
|
|
b9c36fadfd | ||
|
|
150331ec9d | ||
|
|
eede6e0f68 | ||
|
|
37096d9c88 | ||
|
|
53b9e3cbf3 | ||
|
|
094cb73b05 | ||
|
|
83a97420d9 | ||
|
|
94a72e1108 | ||
|
|
d81aa12d54 | ||
|
|
7355df5202 | ||
|
|
70e293ba94 | ||
|
|
0d41b7ca13 | ||
|
|
619722490c | ||
|
|
fddfd8b8c0 | ||
|
|
c3eaa212af | ||
|
|
7ec2a68256 | ||
|
|
80beedfe63 | ||
|
|
cc50077188 | ||
|
|
07f6a91d02 | ||
|
|
02f8629254 | ||
|
|
8b6adee359 | ||
|
|
7a8bc40bb8 | ||
|
|
69637608e8 | ||
|
|
14ddc750d0 | ||
|
|
794545278b | ||
|
|
195be4fc83 | ||
|
|
342b82dfd2 | ||
|
|
2f78f97ded | ||
|
|
7726855d1c | ||
|
|
4d38b8592f | ||
|
|
6321215bb4 | ||
|
|
e8fc097728 | ||
|
|
fbdb9edbf2 | ||
|
|
781fda001b | ||
|
|
140168ea2d | ||
|
|
f1523b0260 | ||
|
|
2878537ca0 | ||
|
|
1bf1b7bda1 | ||
|
|
7e8bf20299 | ||
|
|
2e5224c50d | ||
|
|
dc83503868 | ||
|
|
ddc1f65b57 | ||
|
|
34c2b87ed7 | ||
|
|
e7549a07e2 | ||
|
|
68575f6c58 | ||
|
|
2123e1f311 | ||
|
|
e64cd6038c | ||
|
|
a1abcdb1c0 | ||
|
|
027c0420c9 | ||
|
|
1c621dc999 | ||
|
|
12e21555ee | ||
|
|
84794a1b25 | ||
|
|
e8a374a16d | ||
|
|
046280c480 | ||
|
|
813e44c0a9 | ||
|
|
dcd29a6968 | ||
|
|
769c6b1a8c | ||
|
|
a080d2a01a | ||
|
|
541da9f682 | ||
|
|
016b2f15c5 | ||
|
|
cb0c02f9f2 | ||
|
|
47b7181061 | ||
|
|
2c34f271d9 | ||
|
|
0615ac112b | ||
|
|
7dc1e2d026 | ||
|
|
abb59cc1b4 | ||
|
|
5eec93d2ef | ||
|
|
d390499e86 | ||
|
|
630b70bcf0 | ||
|
|
61592cca67 | ||
|
|
9ed57596c1 | ||
|
|
7910103f60 | ||
|
|
eb12b5596c | ||
|
|
5e13a7208d | ||
|
|
4b49270be7 | ||
|
|
649d8c5b56 | ||
|
|
52424fba6d | ||
|
|
a83d34dcda | ||
|
|
71e6f20740 | ||
|
|
14cf4ff85d | ||
|
|
ae3b7804f0 | ||
|
|
7cc48bec54 | ||
|
|
9e61693e20 | ||
|
|
1e1130e211 | ||
|
|
89f906b1b1 | ||
|
|
7782a32921 | ||
|
|
389b1870d3 | ||
|
|
42ffd6322f | ||
|
|
686f67d2f8 | ||
|
|
b0c196aea0 | ||
|
|
3e1407d242 | ||
|
|
df8275f000 | ||
|
|
edfb603965 | ||
|
|
5153534f4c | ||
|
|
1b2f85c384 | ||
|
|
71d071be60 | ||
|
|
ad6c9dd55f | ||
|
|
9c0f8cdf11 | ||
|
|
c2a2fb63b0 | ||
|
|
67f8ba5d57 | ||
|
|
9b19be18f2 | ||
|
|
06a0dc68a9 | ||
|
|
35f1cd61b1 | ||
|
|
8d6548b610 | ||
|
|
82ff57a750 | ||
|
|
4f798fc5d8 | ||
|
|
587e106095 | ||
|
|
bb882daeae | ||
|
|
c791421280 | ||
|
|
744f5449fb | ||
|
|
f042f5d490 | ||
|
|
42b835f0c0 | ||
|
|
8d38193096 | ||
|
|
994f003b5f | ||
|
|
49198904f3 | ||
|
|
28b7a62939 | ||
|
|
f05d9bac69 | ||
|
|
14c4010471 | ||
|
|
040e1dc9ee | ||
|
|
7a8e1fe2d5 | ||
|
|
c665d01cc5 | ||
|
|
62fdac807e | ||
|
|
1bd0c7bb2d | ||
|
|
c5699b7a43 | ||
|
|
bdfaff5009 | ||
|
|
50422c5902 | ||
|
|
313e96d5c3 | ||
|
|
00b1ed3e76 | ||
|
|
302bd03923 | ||
|
|
3c3d73fedb | ||
|
|
f2c0688aed | ||
|
|
d180cd2eaf | ||
|
|
dbc095c895 | ||
|
|
4fbd5b226b | ||
|
|
d37193f24c | ||
|
|
e3149bdea8 | ||
|
|
bb8486eec5 | ||
|
|
7cefa8577a | ||
|
|
64887e75c5 | ||
|
|
a33803ac19 | ||
|
|
9f5d2a8ac6 | ||
|
|
ab08d97dda | ||
|
|
4068de6d75 | ||
|
|
8ccca9609f | ||
|
|
386717b6c4 | ||
|
|
a7320a872c | ||
|
|
f8fe914212 | ||
|
|
184bd7c7fd | ||
|
|
abedbf10bd | ||
|
|
8184031c4a | ||
|
|
3aa44edd49 | ||
|
|
211f34806b | ||
|
|
26b99671c4 | ||
|
|
ed51a17ea6 | ||
|
|
509b302073 | ||
|
|
462d3936a1 | ||
|
|
7d7305028d | ||
|
|
c11928111d | ||
|
|
a59505574a | ||
|
|
cc4d2e0832 | ||
|
|
19c48d5738 | ||
|
|
906cc19eb0 | ||
|
|
ab02c86fa8 | ||
|
|
dbe644ede6 | ||
|
|
ebcbdd823c | ||
|
|
a784e32840 | ||
|
|
c286123cc1 | ||
|
|
033492f2ab | ||
|
|
6288fe3dde | ||
|
|
78925beec5 | ||
|
|
6430aa3bad | ||
|
|
c095959e6a | ||
|
|
3b07b16962 | ||
|
|
f420a78a79 | ||
|
|
b34a184b0a | ||
|
|
297fd722ab | ||
|
|
9dfce77a4d | ||
|
|
faf9390e70 | ||
|
|
72e1daa5e3 | ||
|
|
7934f3e916 | ||
|
|
9cdfd3a55b | ||
|
|
324851eeb5 | ||
|
|
7ce4309fcb | ||
|
|
ae813f0cf2 | ||
|
|
d790e3dcba | ||
|
|
3c10dbc547 | ||
|
|
0a545c7092 | ||
|
|
02182e3b59 | ||
|
|
cf49899069 | ||
|
|
d5a338ad0e | ||
|
|
5ce5cfdecf | ||
|
|
5508401ed8 | ||
|
|
dc9a18e24d | ||
|
|
6f802417c6 | ||
|
|
87f17fe907 | ||
|
|
8ebfd4892f | ||
|
|
e7e3d3c326 | ||
|
|
bb7e5a369d | ||
|
|
5425f5cfa6 | ||
|
|
ce32a7d16b | ||
|
|
e29f9e2fa2 | ||
|
|
6510bafa88 | ||
|
|
11107ee637 | ||
|
|
763dffa328 | ||
|
|
f3a6cd9a7f | ||
|
|
0c163b8c49 | ||
|
|
83a2274f47 | ||
|
|
2fe5a7bd40 | ||
|
|
b0b2083fea | ||
|
|
3d84ae18a7 | ||
|
|
5f6b01e086 | ||
|
|
80c0888176 | ||
|
|
acc76a383f | ||
|
|
6af305b2be | ||
|
|
fbc096c7a7 | ||
|
|
35c254c88c | ||
|
|
800f394870 | ||
|
|
8334eeeeff | ||
|
|
6bdf6f03b4 | ||
|
|
bfe1b84a2e | ||
|
|
f6105e54a9 | ||
|
|
abcbfcef15 | ||
|
|
7e15d9ec8a | ||
|
|
8b74138e5a | ||
|
|
cbbb0a55c2 | ||
|
|
bd2a45615f | ||
|
|
ee593933a1 | ||
|
|
536c96f217 | ||
|
|
185f8a1404 | ||
|
|
03796e8fe2 | ||
|
|
57cdc288e2 | ||
|
|
19b32cf6e3 | ||
|
|
c47258a873 | ||
|
|
6dd8af70f7 | ||
|
|
1358f6fbb8 | ||
|
|
a4d45358a2 | ||
|
|
84df8eed33 | ||
|
|
65456c7f5e | ||
|
|
6ddd16267a | ||
|
|
7c450b4446 | ||
|
|
af0b1c55e3 | ||
|
|
ae4cbb18f7 | ||
|
|
1c72dcc412 | ||
|
|
52f206fa8a | ||
|
|
9075dfa470 | ||
|
|
97cfc80a1f | ||
|
|
bb80ca3394 | ||
|
|
bfd42a1914 | ||
|
|
e77d785d9c | ||
|
|
61d7eb100a | ||
|
|
a6e7c8e2d5 | ||
|
|
f3c01d5a46 | ||
|
|
93d61c7f21 | ||
|
|
66a0994c91 | ||
|
|
8a249c06c7 | ||
|
|
6afaa264ee | ||
|
|
8e01a66cb5 | ||
|
|
1c064e016f | ||
|
|
d18243b5cf | ||
|
|
71c856e330 | ||
|
|
fa39f7d348 | ||
|
|
29fa1e582a | ||
|
|
0ede0233df | ||
|
|
6687f85164 | ||
|
|
58483bdd2c | ||
|
|
77af1b9942 | ||
|
|
416c806012 | ||
|
|
496ec4b059 | ||
|
|
a661b0b608 | ||
|
|
429a47d64d | ||
|
|
9b6a2268e9 | ||
|
|
a88a25a5eb | ||
|
|
78ea344454 | ||
|
|
1be79fb40e | ||
|
|
2af2bd80f7 | ||
|
|
37a00173f5 | ||
|
|
419ed4d9fc | ||
|
|
ed8e340fa2 | ||
|
|
662635fe35 | ||
|
|
fdcea3e824 | ||
|
|
95a9c329af | ||
|
|
08e6f1e2a3 | ||
|
|
9b24407b68 | ||
|
|
975afe03bb | ||
|
|
d6436f4d03 | ||
|
|
022f367692 | ||
|
|
93c69c4d42 | ||
|
|
111b3cdc8e | ||
|
|
c2acf6168a | ||
|
|
8299aabc25 | ||
|
|
03d2001cf2 | ||
|
|
e7e34a8c24 | ||
|
|
4edaebb94f | ||
|
|
d4079070ed | ||
|
|
1b826eff9b | ||
|
|
0cd1080b76 | ||
|
|
ad266e5fda | ||
|
|
7b3ff37079 | ||
|
|
ac39716ab4 | ||
|
|
1206b3c37d | ||
|
|
f702d3c872 | ||
|
|
3c3b988603 | ||
|
|
d0af34c079 | ||
|
|
5669ba6b48 | ||
|
|
f995e6af0d | ||
|
|
551841e0ad | ||
|
|
e6e6299588 | ||
|
|
db917caaf5 | ||
|
|
406bb4af68 | ||
|
|
b8b448ae40 | ||
|
|
834f158d01 | ||
|
|
c6dda6cf2d | ||
|
|
1e3386c77a | ||
|
|
492ac74819 | ||
|
|
1b2270572b | ||
|
|
add707252a | ||
|
|
792d034059 | ||
|
|
18f63250e1 | ||
|
|
84b33f8c63 | ||
|
|
2ce99d5b5a | ||
|
|
6c9e7f283f | ||
|
|
865c36bc8a | ||
|
|
22ac0872bd | ||
|
|
0cab9f595b | ||
|
|
96c5146ebd | ||
|
|
5d81555ae1 | ||
|
|
2b1f27a850 | ||
|
|
36778dbeac | ||
|
|
7e889f71eb | ||
|
|
37a7c0b35d | ||
|
|
d3c2407df1 | ||
|
|
e9f7bf382e | ||
|
|
34528521f2 | ||
|
|
96493b4a5c | ||
|
|
1eb2c59cce | ||
|
|
6c4c6f8889 | ||
|
|
ced6905cbc | ||
|
|
b1a2cf616e | ||
|
|
4902a35972 | ||
|
|
d531b64d66 | ||
|
|
d1e0b163e2 | ||
|
|
36e6b31135 | ||
|
|
95c0bb9ef2 | ||
|
|
5e47c284b6 | ||
|
|
0e6caf61ff | ||
|
|
d9116efb85 | ||
|
|
274edf7589 | ||
|
|
991148bd30 | ||
|
|
c05079d1d3 | ||
|
|
e902261c45 | ||
|
|
c740660c0c | ||
|
|
0e7c91aeac | ||
|
|
85a5744867 | ||
|
|
2081f39a72 | ||
|
|
750186319e | ||
|
|
4630b49ec4 | ||
|
|
9723bd37a7 | ||
|
|
1ec0314752 | ||
|
|
413926b56e | ||
|
|
cdb50edabc | ||
|
|
e2b96c81f2 | ||
|
|
d02848c216 | ||
|
|
8d5abe8892 | ||
|
|
2e6ae8c967 | ||
|
|
1ec7bd7ce1 | ||
|
|
7896dbada5 | ||
|
|
0810fa77e9 | ||
|
|
4c46ca1a94 | ||
|
|
caeeac6c59 | ||
|
|
d27eefeb34 | ||
|
|
5fcc60fea8 | ||
|
|
60f9dbcdbd | ||
|
|
123b8b906c | ||
|
|
1dd2382d03 | ||
|
|
a6c79f5e57 | ||
|
|
a17fca02ff | ||
|
|
47a603a346 | ||
|
|
61de6e117d | ||
|
|
64d65269d8 | ||
|
|
c937e17335 | ||
|
|
85bb022b2a | ||
|
|
7fae5ca3b8 | ||
|
|
2db1d5d641 | ||
|
|
4147249d2d | ||
|
|
34d9d9c3ee | ||
|
|
ddee980426 | ||
|
|
e8442021b6 | ||
|
|
6c3269c6d5 | ||
|
|
0638619f81 | ||
|
|
69c6305e62 | ||
|
|
76f759d9f7 | ||
|
|
705bef1dea | ||
|
|
b9f570c68d | ||
|
|
4f9257985c | ||
|
|
98d1b54a82 | ||
|
|
28213b1a47 | ||
|
|
f4dc6d0dc7 | ||
|
|
5f00b4a4e6 | ||
|
|
2c8bf3b5da | ||
|
|
cc5fca99be | ||
|
|
3e6bb01199 | ||
|
|
671e8476ba | ||
|
|
e0af180c9b | ||
|
|
7d9f18fda1 | ||
|
|
bbeb24b398 | ||
|
|
ddaab0d8e8 | ||
|
|
d4f92a8fae | ||
|
|
4c74136c53 | ||
|
|
ecd2b87475 | ||
|
|
1ef0cef74c | ||
|
|
6c49ba1d2e | ||
|
|
195dfe6970 | ||
|
|
a178e60907 | ||
|
|
37e4242fdf | ||
|
|
5bb0798311 | ||
|
|
caa8a29605 | ||
|
|
4ea2a77a86 | ||
|
|
d9db761c79 | ||
|
|
a7ff52f366 | ||
|
|
07ec46e151 | ||
|
|
d2acad1aed | ||
|
|
e0995e540b | ||
|
|
9a637213b9 | ||
|
|
e167584f7f | ||
|
|
611e445e17 | ||
|
|
9021583d37 | ||
|
|
bbd4a68151 | ||
|
|
89b2e9ed3a | ||
|
|
6a4a983fa1 | ||
|
|
a57ee1efb3 | ||
|
|
5c2e454ebd | ||
|
|
5304fb0c6f | ||
|
|
04138da009 | ||
|
|
0b51bff255 | ||
|
|
6109c93556 | ||
|
|
146045dbf8 | ||
|
|
f842da5488 | ||
|
|
af649adcd9 | ||
|
|
25a70c2b16 | ||
|
|
0aba20f85c | ||
|
|
d27cd175df | ||
|
|
31e13dcff7 | ||
|
|
81bc438c98 | ||
|
|
7fabd9d17f | ||
|
|
651a271450 | ||
|
|
1f32999125 | ||
|
|
e81f07f0cf | ||
|
|
e10cf9cc85 | ||
|
|
b75254bdda | ||
|
|
15c11b87ca | ||
|
|
9df2e95724 | ||
|
|
517e3b22ba | ||
|
|
f2b118dc32 | ||
|
|
2f608d4a78 | ||
|
|
44bbb44c81 | ||
|
|
fb46e77339 | ||
|
|
6519ded5ec | ||
|
|
868f0a8eec | ||
|
|
31b28e731d | ||
|
|
e0cf63f1d3 | ||
|
|
bcf5406f53 | ||
|
|
002d427f34 | ||
|
|
d13bcc00e0 | ||
|
|
221ef7b932 | ||
|
|
1a7b2b0c31 | ||
|
|
2d28ddc1ef | ||
|
|
6278925ce2 | ||
|
|
cd6e02ccec | ||
|
|
e9242d7754 | ||
|
|
00df0b7bda | ||
|
|
984b739eb4 | ||
|
|
4139ae2549 | ||
|
|
6dbef95951 | ||
|
|
8068d8747b | ||
|
|
cd0995abf3 | ||
|
|
04d0d6dccb | ||
|
|
08d4f87a2d | ||
|
|
6df742a7cb | ||
|
|
dbbf743920 | ||
|
|
bf55d07580 | ||
|
|
2e7526bac5 | ||
|
|
dcbd02cf7f | ||
|
|
884de9008f | ||
|
|
b1f357849b | ||
|
|
5921cd0176 | ||
|
|
8cae234f45 | ||
|
|
b921b71b97 | ||
|
|
0754c92116 | ||
|
|
fbd0a51829 | ||
|
|
6bbcb7415b | ||
|
|
04744b4dac | ||
|
|
e8b9a70fbf | ||
|
|
9e762fcc31 | ||
|
|
2da33408f9 | ||
|
|
f35f55cb25 | ||
|
|
654b81b8f1 | ||
|
|
35bfbc9c82 | ||
|
|
0935451cd8 | ||
|
|
500763294d | ||
|
|
1da075e30b | ||
|
|
8d690a9e33 | ||
|
|
16b242fa0f | ||
|
|
9bb1a36e3d | ||
|
|
3abf6b6f41 | ||
|
|
1bf05e7921 | ||
|
|
39c863ead9 | ||
|
|
6b8cd5d390 | ||
|
|
9e18b19d6a | ||
|
|
c348a763cf | ||
|
|
d08b31f2ed | ||
|
|
dad54db7f4 | ||
|
|
2f7acbe4b3 | ||
|
|
ab04633474 | ||
|
|
07acfba488 | ||
|
|
6d6e846916 | ||
|
|
cb679371ac | ||
|
|
16e754f4c7 | ||
|
|
1cbb3f9a7c | ||
|
|
6bb2e2fadd | ||
|
|
862fa38f8b | ||
|
|
995f3394a8 | ||
|
|
8cab64b715 | ||
|
|
e63fa7a398 | ||
|
|
11caf4f86f | ||
|
|
e0b8b295df | ||
|
|
a26e20ab7d | ||
|
|
034d78e3e4 | ||
|
|
e3de008453 | ||
|
|
e92eb88782 | ||
|
|
6f14f1b850 | ||
|
|
0f0b3edec2 | ||
|
|
700e5588e0 | ||
|
|
d5b7c6d779 | ||
|
|
0df2f519f0 | ||
|
|
314239ff2a | ||
|
|
cb5f26a709 | ||
|
|
dfeea521f5 | ||
|
|
74c3debefe | ||
|
|
d25d9f96f9 | ||
|
|
d486fbce8c | ||
|
|
f7103267a1 | ||
|
|
1da118780e | ||
|
|
ce597380e6 | ||
|
|
8372fb2240 | ||
|
|
8b35356709 | ||
|
|
a19891174e | ||
|
|
d58646085f | ||
|
|
cf350c3e92 | ||
|
|
3c94c1bd29 | ||
|
|
6ba9b24c1a | ||
|
|
ee0c3e9865 | ||
|
|
214c2d7af7 | ||
|
|
91b6a807c7 | ||
|
|
52e4e9f903 | ||
|
|
af59bf3265 | ||
|
|
8afa17f940 | ||
|
|
9b8f10358a | ||
|
|
b5db8820d6 | ||
|
|
f7324adb9c | ||
|
|
bffe2b651c | ||
|
|
882e4f9868 | ||
|
|
a3196a1089 | ||
|
|
597d9e0275 | ||
|
|
dd9ac758e8 | ||
|
|
f774c46db3 | ||
|
|
6b88d2f4e0 | ||
|
|
f7a27f0c07 | ||
|
|
99411fced7 | ||
|
|
4e22c4b211 | ||
|
|
f85f167f50 | ||
|
|
e3c05b5620 | ||
|
|
b1cc37e939 | ||
|
|
5df1e08610 | ||
|
|
3f77507b15 | ||
|
|
e0a6189ec4 | ||
|
|
fc0de7bbd0 | ||
|
|
c30ea112df | ||
|
|
d5735c7981 | ||
|
|
707e358e48 | ||
|
|
cdfcccd75d | ||
|
|
97805e6602 | ||
|
|
7ad758a8d9 | ||
|
|
cf1b0fb414 | ||
|
|
2b2852267a | ||
|
|
3080d1c559 | ||
|
|
ce01946eb0 | ||
|
|
89f918c486 | ||
|
|
e127502be5 | ||
|
|
9c730fc0f3 | ||
|
|
a53495c9aa | ||
|
|
16bf5d8130 | ||
|
|
02cf6a9eb2 | ||
|
|
7dba616e4e | ||
|
|
2f432eb560 | ||
|
|
09c13fdf67 | ||
|
|
803abba5c1 | ||
|
|
0f4789a5cb | ||
|
|
7ae36de2cf | ||
|
|
db7fe69ce4 | ||
|
|
173461ac3d | ||
|
|
45c8277f2f | ||
|
|
4923338bcf | ||
|
|
48b1428c57 | ||
|
|
bb2261b47d | ||
|
|
f64a8e04ed | ||
|
|
7ee9565a04 | ||
|
|
a56d998499 | ||
|
|
758c673f68 | ||
|
|
5326ddf6c3 | ||
|
|
df11297654 | ||
|
|
84ac19a090 | ||
|
|
f8969ddfeb | ||
|
|
ec47fc67ab | ||
|
|
317b78394a | ||
|
|
bf2a188f82 | ||
|
|
ffd5f575bc | ||
|
|
646c743385 | ||
|
|
f6b57384e7 | ||
|
|
ec18844e8f | ||
|
|
b4dc13f1ce | ||
|
|
e6fd043206 | ||
|
|
36bc122510 | ||
|
|
064beb6f4e | ||
|
|
72a3b92b50 | ||
|
|
2a8c0ddd51 | ||
|
|
13e2841f13 | ||
|
|
f9e0dee2dc | ||
|
|
5040363f7a | ||
|
|
2be3e9eaf3 | ||
|
|
fa53656b3b | ||
|
|
f8ba88408b | ||
|
|
559f370116 | ||
|
|
b37c14ce0e | ||
|
|
c45b785370 | ||
|
|
f0aecd5790 | ||
|
|
35860e2866 | ||
|
|
5a0759a3d9 | ||
|
|
6cad0a965a | ||
|
|
41750e38da | ||
|
|
c9ba1ee197 | ||
|
|
47dd44ff39 | ||
|
|
552d3efb29 | ||
|
|
6b9b19c284 | ||
|
|
f07912ebdd | ||
|
|
3fe0ef350f | ||
|
|
8ffb814166 | ||
|
|
a8d69c48dc | ||
|
|
6ba26496ec | ||
|
|
7109773b83 | ||
|
|
43c111bd98 | ||
|
|
ff6d0064d0 | ||
|
|
32157036d3 | ||
|
|
8851f664e5 | ||
|
|
f284d379dd | ||
|
|
68d7d59286 | ||
|
|
df756c5f9f | ||
|
|
c3852a8e9c | ||
|
|
a5bdabea9b | ||
|
|
ef762c4920 | ||
|
|
2bfeefe3b3 | ||
|
|
6e469825cd | ||
|
|
3257e569b9 | ||
|
|
8a60870f04 | ||
|
|
6824ef5baf | ||
|
|
0d45e709f2 | ||
|
|
5b0066a426 | ||
|
|
24fbd59d21 | ||
|
|
8fc75e8955 | ||
|
|
81c833c4e3 | ||
|
|
5bc32d0aad | ||
|
|
b30c4e4e97 | ||
|
|
b17e519cb4 | ||
|
|
6fa99072d1 | ||
|
|
d5e1cbeefa | ||
|
|
82978bb67f | ||
|
|
97af2441ee | ||
|
|
6924781bb0 | ||
|
|
9757637afa | ||
|
|
29058d028f | ||
|
|
652b0064e6 | ||
|
|
6bae98d980 | ||
|
|
ddaa5c0064 | ||
|
|
cd16f29c39 | ||
|
|
ffe6fc0aa1 | ||
|
|
cee30c36a0 | ||
|
|
f428f840f6 | ||
|
|
61b134f81d | ||
|
|
8b6c32e655 | ||
|
|
5bfe7048eb | ||
|
|
169136f292 | ||
|
|
6798ba974c | ||
|
|
0c2cd2ef1b | ||
|
|
d3bb7ade78 | ||
|
|
b8198efc6c | ||
|
|
038b6f49a9 | ||
|
|
9faf38671b | ||
|
|
d6915ff5d0 | ||
|
|
2c94994f32 | ||
|
|
4370315d6c | ||
|
|
a7ea3ba254 | ||
|
|
79fe1c2a7e | ||
|
|
27ea63979f | ||
|
|
c1ece44c53 | ||
|
|
bcbcf6bce3 | ||
|
|
55dbbad869 | ||
|
|
1b85655d71 | ||
|
|
348790292b | ||
|
|
39265ab9b5 | ||
|
|
797afb1c9b | ||
|
|
100b1a4286 | ||
|
|
3532a340c2 | ||
|
|
2d40801bb4 | ||
|
|
f23e947d0c | ||
|
|
b00df8c2e7 | ||
|
|
50e034769f | ||
|
|
71d324a8e4 | ||
|
|
3f00195eed | ||
|
|
bbbe3aea7f | ||
|
|
454be1b468 | ||
|
|
d03b830b07 | ||
|
|
84f972b368 | ||
|
|
43a8cbff5d | ||
|
|
0af04a5c1e | ||
|
|
1879adcde4 | ||
|
|
40d42f4b58 | ||
|
|
604075c570 | ||
|
|
f4f7eb8726 | ||
|
|
f66bd9e1af | ||
|
|
e84e3e64c6 | ||
|
|
44742109b0 | ||
|
|
ea3395378f | ||
|
|
47c7de22ae | ||
|
|
c432bb4891 | ||
|
|
c1e5f61502 | ||
|
|
0da2e9c102 | ||
|
|
a201e3b2c2 | ||
|
|
21f9a9d134 | ||
|
|
53a7508222 | ||
|
|
a928e40d7f | ||
|
|
bb84767a96 | ||
|
|
9bab794ea4 | ||
|
|
26212ba3e0 | ||
|
|
a91579e92a | ||
|
|
3ebe0e56a8 | ||
|
|
fe5c95e0ba | ||
|
|
883eb54ecf | ||
|
|
24c7e472d4 | ||
|
|
026e63b961 | ||
|
|
c9f65332de | ||
|
|
232f76e69d | ||
|
|
f66248c315 | ||
|
|
60b5443850 | ||
|
|
11dac58dc7 | ||
|
|
4c4c3d8bf0 | ||
|
|
2eb17e109b | ||
|
|
76c64ec1f8 | ||
|
|
9b3f90dc90 | ||
|
|
7fef695e29 | ||
|
|
d06ef4505c | ||
|
|
80bc90afa9 | ||
|
|
e5687dd7e0 | ||
|
|
1289b3b541 | ||
|
|
ea15403d57 | ||
|
|
dcbdb69e22 | ||
|
|
af02d01d41 | ||
|
|
9ff1646cbb | ||
|
|
4aa08ddf14 | ||
|
|
b94ef57bbc | ||
|
|
f9a59f2ff0 | ||
|
|
0b9ba559c7 | ||
|
|
299c04a24a | ||
|
|
8983901b1a | ||
|
|
f3d2a54050 | ||
|
|
212da3a2b7 | ||
|
|
f863cf08a0 | ||
|
|
0cb212e6cb | ||
|
|
2a60cb30a1 | ||
|
|
22d1ad995c | ||
|
|
360ea20367 | ||
|
|
74bac8c495 | ||
|
|
c061425022 | ||
|
|
c643743df0 | ||
|
|
d5f5e1991b | ||
|
|
8daf934c45 | ||
|
|
9171f6b5ef | ||
|
|
25fc4c746a | ||
|
|
bd175cfb12 | ||
|
|
c1ebc4d338 | ||
|
|
66175f6908 | ||
|
|
c0a4895854 | ||
|
|
89f7f97294 | ||
|
|
ae17248b2e | ||
|
|
c84411ea62 | ||
|
|
b7fa28a5a1 | ||
|
|
a867d64631 | ||
|
|
38d92cc84b | ||
|
|
0d07d9f3de | ||
|
|
e1b1491f75 | ||
|
|
12cb2be641 | ||
|
|
6d55b15604 | ||
|
|
29daea1b0a | ||
|
|
f14547b2ea | ||
|
|
b263cf7e6d | ||
|
|
e7c6496a09 | ||
|
|
23e4062342 | ||
|
|
c4839613ea | ||
|
|
f1d71eb592 | ||
|
|
198dddd69e | ||
|
|
ab903af80b | ||
|
|
5d23c6bfcf | ||
|
|
3bc0fc16a2 | ||
|
|
1903bfd307 | ||
|
|
0cb2a8f4d7 | ||
|
|
0b921c6657 | ||
|
|
d7abaaced0 | ||
|
|
049db8626d | ||
|
|
948e31b10c | ||
|
|
e9e08831e6 | ||
|
|
fc2397c062 | ||
|
|
ea606b374a | ||
|
|
3121de7954 | ||
|
|
f6e359a669 | ||
|
|
750481497f | ||
|
|
1d683295e1 | ||
|
|
9ec07911de | ||
|
|
b3d3445a7e | ||
|
|
13d24cc611 | ||
|
|
e059b8ad48 | ||
|
|
d3632e116a | ||
|
|
a2ca74265c | ||
|
|
03ee954c10 | ||
|
|
151a169791 | ||
|
|
fb084e47af | ||
|
|
c3c0059734 | ||
|
|
d42801a737 | ||
|
|
c8d1043590 | ||
|
|
21980681b1 | ||
|
|
6351399197 | ||
|
|
42a8129016 | ||
|
|
30a940f483 | ||
|
|
cae06d95ea | ||
|
|
66095e3f03 | ||
|
|
d6bec055e7 | ||
|
|
4bbb25fedf | ||
|
|
0029f2c1da | ||
|
|
829882cf58 | ||
|
|
760b928902 | ||
|
|
d410b62323 | ||
|
|
9f18d5ba82 | ||
|
|
26a9e50ef9 | ||
|
|
c9ba7f697b | ||
|
|
c1022ef8ad | ||
|
|
94e904464f | ||
|
|
f9029dbdaf | ||
|
|
295bb15f73 | ||
|
|
e7a1fe6e78 | ||
|
|
95ac7d0123 | ||
|
|
974fbd4d03 | ||
|
|
2ffdbc4b05 | ||
|
|
e39df04dc4 | ||
|
|
697d626269 | ||
|
|
f5fc142f63 | ||
|
|
8d5d09521a | ||
|
|
680894d121 | ||
|
|
9ffbf2f788 | ||
|
|
eb70d24dfa | ||
|
|
0ae9dbf77f | ||
|
|
6569bbaf1f | ||
|
|
3b6a98b7b4 | ||
|
|
19a967328d | ||
|
|
68145723bb | ||
|
|
db14b58eeb | ||
|
|
11372035b8 |
@@ -2,7 +2,8 @@
|
||||
"env": {
|
||||
"browser": true,
|
||||
"jasmine": true,
|
||||
"jquery": true
|
||||
"jquery": true,
|
||||
"es6": true
|
||||
},
|
||||
|
||||
"globals": {
|
||||
@@ -15,7 +16,6 @@
|
||||
"Handlebars": false,
|
||||
"HandlebarsTemplates": false,
|
||||
"ImagePaths": false,
|
||||
"jsxc": false,
|
||||
"L": false,
|
||||
"OSM": false,
|
||||
"PerfectScrollbar": false,
|
||||
@@ -97,7 +97,7 @@
|
||||
"no-catch-shadow": 0,
|
||||
"no-class-assign": 2,
|
||||
"no-cond-assign": 2,
|
||||
"no-confusing-arrow": 2,
|
||||
"no-confusing-arrow": [2, {"allowParens": true}],
|
||||
"no-console": 2,
|
||||
"no-const-assign": 2,
|
||||
"no-constant-condition": 2,
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -15,8 +15,8 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ruby:
|
||||
- 2.7
|
||||
- 2.6
|
||||
- '3.2'
|
||||
- '3.3'
|
||||
db:
|
||||
- mysql
|
||||
- postgresql
|
||||
@@ -44,11 +44,11 @@ jobs:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- name: Install system dependencies
|
||||
run: sudo apt update && sudo apt install -y build-essential curl git gsfonts imagemagick libcurl4-openssl-dev libidn11-dev libmagickwand-dev libssl-dev libxml2-dev libxslt1-dev
|
||||
run: sudo apt update && sudo apt install -y build-essential curl git gsfonts imagemagick libcurl4-openssl-dev libidn11-dev libmagickwand-dev libssl-dev libxml2-dev libxslt1-dev yarnpkg
|
||||
- name: Start MySQL
|
||||
run: sudo systemctl start mysql.service
|
||||
if: matrix.db == 'mysql'
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby }}
|
||||
|
||||
62
.github/workflows/lint.yml
vendored
Normal file
62
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
## SECURITY WARNING:
|
||||
##
|
||||
## Do not change this job unless you know what you're doing.
|
||||
##
|
||||
## This GitHub Action runs on: pull_request_target, which means the jobs run in
|
||||
## a context where they have access to a Access Token with write access to the
|
||||
## target repo, even if the PR is opened from an external contributor from their
|
||||
## fork.
|
||||
##
|
||||
## This means that if we're not careful, we could be running third-party code
|
||||
## within an authenticated scope, which isn't good. To mitigate this, this
|
||||
## implementation does:
|
||||
##
|
||||
## 1. checkout the target branch (i.e. the project's original sources)
|
||||
## 2. install the Gems from there, and install them into a directory that's
|
||||
## outside the repository contents.
|
||||
## 3. checkout the PRs HEAD
|
||||
## 4. restore a bunch of files that would allow code execution from the
|
||||
## project's upstream sources, namely:
|
||||
## - bin/bundle - we'll run that in our Job
|
||||
## - Gemfile/Gemfile.lock - to avoid loading a gem with an identical
|
||||
## version number from a in-repo vendored directory
|
||||
|
||||
name: Lint
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
statuses: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
pronto:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install system dependencies
|
||||
run: sudo apt update && sudo apt install -y build-essential curl git gsfonts imagemagick libcurl4-openssl-dev libidn11-dev libmagickwand-dev libssl-dev libxml2-dev libxslt1-dev yarnpkg
|
||||
- name: Checkout Target branch
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
fetch-depth: 0
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: "3.1"
|
||||
bundler-cache: true
|
||||
- name: Checkout PR HEAD
|
||||
run: |
|
||||
git fetch -q origin +refs/pull/${{ github.event.pull_request.number }}/head:
|
||||
git checkout -qf FETCH_HEAD
|
||||
- name: Restore the bundle binstub and Gemfiles from the target branch
|
||||
run: |
|
||||
git restore -s ${{ github.base_ref }} -- bin/bundle
|
||||
git restore -s ${{ github.base_ref }} -- Gemfile
|
||||
git restore -s ${{ github.base_ref }} -- Gemfile.lock
|
||||
- name: Run Pronto
|
||||
run: bin/bundle exec pronto run -f github_status github_pr_review -c ${{ github.base_ref }}
|
||||
env:
|
||||
PRONTO_PULL_REQUEST_ID: ${{ github.event.pull_request.number }}
|
||||
PRONTO_GITHUB_ACCESS_TOKEN: ${{ github.token }}
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,8 +1,3 @@
|
||||
# XMPP certificates, keys and user data
|
||||
config/certs/*.crt
|
||||
config/certs/*.key
|
||||
config/prosody.cfg.lua
|
||||
|
||||
# Trademark sillyness
|
||||
app/views/home/_show.*
|
||||
app/views/terms/terms.*
|
||||
|
||||
14
.rubocop.yml
14
.rubocop.yml
@@ -1,7 +1,7 @@
|
||||
require: rubocop-rails
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.6
|
||||
TargetRubyVersion: 3.0
|
||||
NewCops: enable
|
||||
Exclude:
|
||||
- "bin/**/*"
|
||||
@@ -39,7 +39,7 @@ Metrics/PerceivedComplexity:
|
||||
|
||||
# Some blocks are longer.
|
||||
Metrics/BlockLength:
|
||||
ExcludedMethods:
|
||||
AllowedMethods:
|
||||
- "namespace"
|
||||
- "create_table"
|
||||
Exclude:
|
||||
@@ -73,6 +73,11 @@ Layout/HashAlignment:
|
||||
EnforcedHashRocketStyle: table
|
||||
EnforcedColonStyle: table
|
||||
|
||||
# This rule makes haml files less readable, as there is no 'end' there.
|
||||
Layout/CaseIndentation:
|
||||
Exclude:
|
||||
- "app/views/**/*"
|
||||
|
||||
# Mixing the styles looks just silly.
|
||||
Style/HashSyntax:
|
||||
EnforcedStyle: ruby19_no_mixed_keys
|
||||
@@ -168,6 +173,11 @@ Style/Documentation:
|
||||
Naming/BinaryOperatorParameterName:
|
||||
Enabled: false
|
||||
|
||||
# Defining constants in tests is fine, and it's good to have them close to the test where they are used.
|
||||
Lint/ConstantDefinitionInBlock:
|
||||
Exclude:
|
||||
- "spec/**/*"
|
||||
|
||||
# There are valid cases, for example debugging Cucumber steps,
|
||||
# also they'll fail CI anyway
|
||||
Lint/Debugger:
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.7
|
||||
3.3
|
||||
|
||||
@@ -70,7 +70,7 @@ linters:
|
||||
enabled: true
|
||||
|
||||
IdSelector:
|
||||
enabled: true
|
||||
enabled: false
|
||||
|
||||
ImportantRule:
|
||||
enabled: true
|
||||
|
||||
130
Changelog.md
130
Changelog.md
@@ -1,3 +1,131 @@
|
||||
# 1.0.0 (unreleased)
|
||||
|
||||
## Refactor
|
||||
|
||||
## Bug fixes
|
||||
|
||||
## Features
|
||||
|
||||
# 0.9.1.0 (unreleased)
|
||||
|
||||
## Refactor
|
||||
* Improved compatibility with non-specification-compliant OpenGraph metadata [#8465](https://github.com/diaspora/diaspora/pull/8465)
|
||||
|
||||
## Bug fixes
|
||||
* Fix processing for a specific set of uploaded images, like scenes full of snow, by allowing for a larger on-disk cache for ImageMagick [#8460](https://github.com/diaspora/diaspora/pull/8460)
|
||||
* Fix a bug with parsing certain OpenGraph metadata structures [#8463](https://github.com/diaspora/diaspora/pull/8463)
|
||||
|
||||
## Features
|
||||
* For admins, the offending content's author is now visible in the reports overview [#8464](https://github.com/diaspora/diaspora/pull/8464)
|
||||
|
||||
# 0.9.0.0
|
||||
|
||||
## New configuration file!
|
||||
|
||||
Diaspora\* now uses TOML for the configuration file. We recommend you to migrate to this new format, as with the next major release (1.0) diaspora\* will no longer read the YAML based configuration file at `config/diaspora.yml`. To do so, please copy `config/diaspora.toml.example` to `config/diaspora.toml` and migrate your configuration.
|
||||
|
||||
## API!
|
||||
|
||||
With the release of diaspora\* Version 0.9, we now officially support building applications on top of the diaspora\* API! Please check out [the official API documentation](https://diaspora.github.io/api-documentation/) for instructions, and please do file bugs if you notice something that could be improved!
|
||||
|
||||
We are looking forward to seeing many creative applications!
|
||||
|
||||
## The chat integration has been removed
|
||||
|
||||
After [a discussion with our community on Discourse](https://discourse.diasporafoundation.org/t/2718), we decided to remove the pieces of XMPP chat integration that were put in place a while ago. When we first added the chat support, we merged the implementation in an unfinished state in the hopes that the open issues will be addressed eventually, and the implementation would end up more polished. This ended up not being the case. After careful consideration and discussion, we did not manage to come up with clear reasons why we need a chat implementation, so we decided that the best way forward would be to remove it.
|
||||
|
||||
Although the chat was never enabled per default and was marked as experimental, some production pods did set up the integration and offered an XMPP service to their users. After this release, diaspora\* will no longer contain a chat applet, so users will no longer be able to use the webchat inside diaspora\*. The existing module that is used to enable users to authenticate to Prosody using their diaspora\* credentials will continue to work, but contact list synchronization might not work without further changes to the Prosody module, which is developed independently from this project.
|
||||
|
||||
## Changes around the appserver and related configuration
|
||||
|
||||
With this release, we switched from `unicorn` to `puma` to run our applications. For podmins running the default setup, this should significantly reduce memory usage, with similar or even better frontend performance! However, as great as this change is, some configuration changes are required.
|
||||
|
||||
- The `single_process_mode` and `embed_sidekiq_worker` configurations have been removed. This mode was never truly a "single-process" mode, as it just spawned the Background Workers inside the runserver. If you're using `script/server` to start your pod, this change does not impact you, but if you're running diaspora\* using other means, and you relied on this "single"-process mode, please ensure that Sidekiq workers get started.
|
||||
- The format of the `listen` configuration has changed. If you have not set that field in your configuration, you can skip this. Otherwise, make sure to adjust your configuration accordingly:
|
||||
- Listening to Unix sockets with a relative path has changed from `unix:tmp/diaspora.sock` into `unix://tmp/diaspora.sock`.
|
||||
- Listening to Unix sockets with an absolute path has changed from `unix:/run/diaspora/diaspora.sock` to `unix:///run/diaspora/diaspora.sock`.
|
||||
- Listening to a local port has changed from `127.0.0.1:3000` to `tcp://127.0.0.1:3000`.
|
||||
- The `PORT` environment variable and the `-p` parameter to `script/server` have been removed. If you used that to run diaspora\* on a non-standard port, please use the `listen` configuration.
|
||||
- The `unicorn_worker` configuration has been dropped. With Puma, there should not be a need to increase the number of workers above a single worker in any pod of any size.
|
||||
- The `unicorn_timeout` configuration has been renamed to `web_timeout`.
|
||||
- **If you don't run your pod with `script/server`**, you have to update your setup. If you previously called `bin/bundle exec unicorn -c config/unicorn.rb` to run diaspora\*, you now have to run `bin/puma -C config/puma.rb`! Please update your systemd-Units or similar accordingly.
|
||||
|
||||
## Yarn for frontend dependencies
|
||||
|
||||
We use yarn to install the frontend dependencies now, so you need to have that installed. See here for how to install it: https://yarnpkg.com/en/docs/install
|
||||
|
||||
## Suggested Ruby version: 3.3
|
||||
|
||||
We recommend setting up new pods using Ruby 3.3, and updating existing pods to this version as well. Ruby 2.7 is EOL and no longer supported.
|
||||
|
||||
## Changes to script/server for production pods
|
||||
|
||||
If you're currently running your production pod with `./script/server` in a tmux or something similar, please be careful. We made some internal changes that result in the script no longer automatically restarting the server if it crashes - instead, it will just shut down. We strongly recommend running your pod using your system's unit manager, for example with [this systemd unit](https://wiki.diasporafoundation.org/Automatic_startup_methods#Recommended:_systemd).
|
||||
|
||||
## Security
|
||||
|
||||
* Fix a potential 2FA brute force attack ([CVE-2024-0227](https://github.com/devise-two-factor/devise-two-factor/security/advisories/GHSA-chcr-x7hc-8fp8)).
|
||||
Thanks to Christian Reitter ([Radically Open Security](https://www.radicallyopensecurity.com/)) and Chris MacNaughton ([Centauri Solutions](https://centauri.solutions)).
|
||||
|
||||
## Refactor
|
||||
* Add bootstrapping for using ECMAScript 6 with automatic transpiling for compatibility [#7581](https://github.com/diaspora/diaspora/pull/7581) [#8397](https://github.com/diaspora/diaspora/pull/8397)
|
||||
* Remove backporting of mention syntax [#7788](https://github.com/diaspora/diaspora/pull/7788)
|
||||
* Enable Content-Security-Policy header by default [#7781](https://github.com/diaspora/diaspora/pull/7781)
|
||||
* Do not show getting started after account import [#8036](https://github.com/diaspora/diaspora/pull/8036)
|
||||
* Remove the JSXC/Prosody integration [#8069](https://github.com/diaspora/diaspora/pull/8069) [#8341](https://github.com/diaspora/diaspora/pull/8341)
|
||||
* Replace `factory_girl` with `factory_bot` [#8218](https://github.com/diaspora/diaspora/pull/8218)
|
||||
* Drop relay support [#8243](https://github.com/diaspora/diaspora/pull/8243)
|
||||
* Use yarn to manage the frontend dependencies [#8364](https://github.com/diaspora/diaspora/pull/8364)
|
||||
* Upgrade to latest `diaspora_federation`, remove support for old federation protocol [#8368](https://github.com/diaspora/diaspora/pull/8368)
|
||||
* Remove support for `therubyracer` [#8337](https://github.com/diaspora/diaspora/issues/8337)
|
||||
* Replace `unicorn` with `puma` [#8392](https://github.com/diaspora/diaspora/pull/8392)
|
||||
* Drop `strip_exif` flag and always remove exif data from uploaded images [#8417](https://github.com/diaspora/diaspora/pull/8417)
|
||||
* Replace `apparition` with `cuprite` [#8418](https://github.com/diaspora/diaspora/pull/8418)
|
||||
* Remove `i18n-inflector-rails` for translations [#8420](https://github.com/diaspora/diaspora/pull/8420)
|
||||
* Add ruby 3 support [#8423](https://github.com/diaspora/diaspora/pull/8423) [#8426](https://github.com/diaspora/diaspora/pull/8426) [#8427](https://github.com/diaspora/diaspora/pull/8427) [#8448](https://github.com/diaspora/diaspora/pull/8448)
|
||||
* Add CORS headers to nodeinfo endpoints to allow for client-side fetching [#8436](https://github.com/diaspora/diaspora/pull/8436)
|
||||
* Replace eye with foreman [#8449](https://github.com/diaspora/diaspora/pull/8449)
|
||||
|
||||
## Bug fixes
|
||||
* Fix multiple photos upload progress bar [#7655](https://github.com/diaspora/diaspora/pull/7655)
|
||||
* Photo-upload file picker now correctly restricts possible file types [#8205](https://github.com/diaspora/diaspora/pull/8205)
|
||||
* Make inline code inside links show the link color [#8387](https://github.com/diaspora/diaspora/pull/8387)
|
||||
* Fix fetching public posts on first account search was missing some data [#8390](https://github.com/diaspora/diaspora/pull/8390)
|
||||
* Add redirect from mobile UI photo URLs to post when not using mobile UI [#8400](https://github.com/diaspora/diaspora/pull/8400)
|
||||
* Escape mentions before markdown parsing in mobile UI [#8398](https://github.com/diaspora/diaspora/pull/8398)
|
||||
* Cleanup duplicate pods in database [#8403](https://github.com/diaspora/diaspora/pull/8403)
|
||||
* Fix scrolling issue after closing photo viewer on photos page [#8404](https://github.com/diaspora/diaspora/pull/8404)
|
||||
* Filter unicode emojis from email headers [#8421](https://github.com/diaspora/diaspora/pull/8421)
|
||||
* Do not show disabled services anymore [#8406](https://github.com/diaspora/diaspora/pull/8406)
|
||||
* Update search endpoint to be aware of ignored users [#8363](https://github.com/diaspora/diaspora/pull/8363)
|
||||
|
||||
## Features
|
||||
* Add client-side cropping of profile image uploads [#7581](https://github.com/diaspora/diaspora/pull/7581)
|
||||
* Add client-site rescaling of post images if they exceed the maximum possible size [#7734](https://github.com/diaspora/diaspora/pull/7734)
|
||||
* Add backend for archive import [#7660](https://github.com/diaspora/diaspora/pull/7660) [#8254](https://github.com/diaspora/diaspora/pull/8254) [#8264](https://github.com/diaspora/diaspora/pull/8264) [#8010](https://github.com/diaspora/diaspora/pull/8010) [#8260](https://github.com/diaspora/diaspora/pull/8260) [#8302](https://github.com/diaspora/diaspora/pull/8302) [#8298](https://github.com/diaspora/diaspora/pull/8298)
|
||||
* For pods running PostgreSQL, make sure that no upper-case/mixed-case tags exist, and create a `lower(name)` index on tags to speed up ActsAsTaggableOn [#8206](https://github.com/diaspora/diaspora/pull/8206)
|
||||
* Allow podmins/moderators to see all local public posts to improve moderation [#8232](https://github.com/diaspora/diaspora/pull/8232) [#8320](https://github.com/diaspora/diaspora/pull/8320)
|
||||
* Add support for directly paste images to upload them [#8237](https://github.com/diaspora/diaspora/pull/8237)
|
||||
* Add support for webp images and convert new png/jpg to webp to save space and bandwidth [#8358](https://github.com/diaspora/diaspora/pull/8358)
|
||||
* Show total and active pods count in the pods list for podmins [#8383](https://github.com/diaspora/diaspora/pull/8383)
|
||||
* Allow to select multiple aspects when posting on mobile [#8217](https://github.com/diaspora/diaspora/pull/8217)
|
||||
* Add info links to drawer in mobile UI [#8405](https://github.com/diaspora/diaspora/pull/8405)
|
||||
* Tell users that there is no help in mobile version, allow to switch to desktop [#8407](https://github.com/diaspora/diaspora/pull/8407)
|
||||
* Add Smart App Banner on iOS devices [#8409](https://github.com/diaspora/diaspora/pull/8409)
|
||||
* Add a more detailed modal when reporting a post or a comment [#8035](https://github.com/diaspora/diaspora/pull/8035)
|
||||
* Re-introduce likes on comments [#8203](https://github.com/diaspora/diaspora/pull/8203) [#8439](https://github.com/diaspora/diaspora/pull/8439) [#8442](https://github.com/diaspora/diaspora/pull/8442)
|
||||
* New redesigned registration page [#8285](https://github.com/diaspora/diaspora/pull/8285)
|
||||
* Allow comments to be fetched [#8441](https://github.com/diaspora/diaspora/pull/8441)
|
||||
|
||||
# 0.7.18.2
|
||||
|
||||
To avoid potential security issues, diaspora\* now makes sure that ImageMagick image processing always runs with a restricted `policy.xml`, regardless of the global system settings.
|
||||
|
||||
# 0.7.18.1
|
||||
|
||||
## Bug fixes
|
||||
* Update binstubs to fix diaspora\* being unable to start when multiple bundler versions were available [#8392](https://github.com/diaspora/diaspora/pull/8392/commits/bfd42a1914a99ac9c71ecb16bbf6fa5bb118148a)
|
||||
|
||||
# 0.7.18.0
|
||||
|
||||
## Refactor
|
||||
@@ -69,7 +197,7 @@
|
||||
* Testing: Replaced phantomjs with headless Chrome/Chromium [#8234](https://github.com/diaspora/diaspora/pull/8234)
|
||||
|
||||
## Bug fixes
|
||||
* Update comment counter when weleting a comment in the Single Post View [#7938](https://github.com/diaspora/diaspora/pull/7938)
|
||||
* Update comment counter when deleting a comment in the Single Post View [#7938](https://github.com/diaspora/diaspora/pull/7938)
|
||||
* Link diaspora only poduptime list [#8174](https://github.com/diaspora/diaspora/pull/8174)
|
||||
* Delete a user's invitation code during account deletion [#8202](https://github.com/diaspora/diaspora/pull/8202)
|
||||
* Bump mimemagic [#8231](https://github.com/diaspora/diaspora/pull/8231)
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
web1: env RAILS_ENV=integration1 bundle exec rails s -p 3001
|
||||
worker1: env RAILS_ENV=integration1 VVERBOSE=1 QUEUE=* bundle exec rake resque:work
|
||||
redis1: env RAILS_ENV=integration1 redis-server ./redis-integration1.conf
|
||||
web2: env RAILS_ENV=integration2 bundle exec rails s -p 3002
|
||||
worker2: env RAILS_ENV=integration2 VVERBOSE=1 QUEUE=* bundle exec rake resque:work
|
||||
redis2: env RAILS_ENV=integration2 redis-server ./redis-integration2.conf
|
||||
197
Gemfile
197
Gemfile
@@ -2,34 +2,39 @@
|
||||
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "rails", "6.1.6.1"
|
||||
gem "rails", "6.1.7.8"
|
||||
|
||||
# needed for actionmailer, can be removed when upgrading to rails 7
|
||||
gem "net-imap", require: false
|
||||
gem "net-pop", require: false
|
||||
gem "net-smtp", require: false
|
||||
|
||||
# Legacy Rails features, remove me!
|
||||
# responders (class level)
|
||||
gem "responders", "3.0.1"
|
||||
gem "responders", "3.1.1"
|
||||
|
||||
# Appserver
|
||||
|
||||
gem "unicorn", "6.1.0", require: false
|
||||
gem "unicorn-worker-killer", "0.4.5"
|
||||
gem "puma", "6.4.2", require: false
|
||||
|
||||
# Federation
|
||||
|
||||
gem "diaspora_federation-json_schema", "0.2.8"
|
||||
gem "diaspora_federation-rails", "0.2.8"
|
||||
gem "diaspora_federation-json_schema", "1.1.0"
|
||||
gem "diaspora_federation-rails", "1.1.0"
|
||||
|
||||
# API and JSON
|
||||
|
||||
gem "acts_as_api", "1.0.1"
|
||||
gem "json", "2.6.2"
|
||||
gem "json-schema", "3.0.0"
|
||||
gem "json", "2.7.2"
|
||||
gem "json-schema", "4.3.0"
|
||||
gem "yajl-ruby", "1.4.3"
|
||||
|
||||
# Authentication
|
||||
|
||||
gem "devise", "4.8.1"
|
||||
gem "devise", "4.9.4"
|
||||
gem "devise_lastseenable", "0.0.6"
|
||||
gem "devise-two-factor", "4.0.2"
|
||||
gem "rqrcode", "2.1.1"
|
||||
gem "devise-two-factor", "4.1.0"
|
||||
gem "rqrcode", "2.2.0"
|
||||
|
||||
# Captcha
|
||||
|
||||
@@ -37,29 +42,29 @@ gem "simple_captcha2", "0.5.0", require: "simple_captcha"
|
||||
|
||||
# Background processing
|
||||
|
||||
gem "redis", "4.7.0"
|
||||
gem "sidekiq", "6.5.1"
|
||||
gem "redis", "4.8.1"
|
||||
gem "sidekiq", "6.5.12"
|
||||
|
||||
# Scheduled processing
|
||||
|
||||
gem "sidekiq-cron", "1.6.0"
|
||||
gem "sidekiq-cron", "1.12.0"
|
||||
|
||||
# Compression
|
||||
|
||||
gem "terser", "1.1.10"
|
||||
gem "terser", "1.2.2"
|
||||
|
||||
# Configuration
|
||||
|
||||
gem "configurate", "0.5.0"
|
||||
gem "toml-rb", "2.1.2"
|
||||
gem "configurate", "0.6.0"
|
||||
gem "toml-rb", "3.0.1"
|
||||
|
||||
# Cross-origin resource sharing
|
||||
|
||||
gem "rack-cors", "1.1.1", require: "rack/cors"
|
||||
gem "rack-cors", "2.0.2", require: "rack/cors"
|
||||
|
||||
# CSS
|
||||
|
||||
gem "autoprefixer-rails", "10.4.7.0"
|
||||
gem "autoprefixer-rails", "10.4.16.0"
|
||||
gem "bootstrap-sass", "3.4.1"
|
||||
gem "bootstrap-switch-rails", "3.3.3" # 3.3.4 and 3.3.5 is broken, see https://github.com/Bttstrp/bootstrap-switch/issues/691
|
||||
gem "sassc-rails", "2.1.2"
|
||||
@@ -68,78 +73,51 @@ gem "sprockets-rails", "3.4.2"
|
||||
# Database
|
||||
|
||||
group :mysql, optional: true do
|
||||
gem "mysql2", "0.5.4"
|
||||
gem "mysql2", "0.5.6"
|
||||
end
|
||||
group :postgresql, optional: true do
|
||||
gem "pg", "1.4.1"
|
||||
gem "pg", "1.5.6"
|
||||
end
|
||||
|
||||
gem "activerecord-import", "1.4.0"
|
||||
gem "activerecord-import", "1.7.0"
|
||||
|
||||
# File uploading
|
||||
|
||||
gem "carrierwave", "2.2.2"
|
||||
gem "fog-aws", "3.14.0"
|
||||
gem "mini_magick", "4.11.0"
|
||||
gem "carrierwave", "3.0.7"
|
||||
gem "fog-aws", "3.22.0"
|
||||
gem "mini_magick", "4.12.0"
|
||||
|
||||
# GUID generation
|
||||
gem "uuid", "2.3.9"
|
||||
|
||||
# JavaScript
|
||||
|
||||
gem "babel-transpiler", "0.7.0"
|
||||
gem "handlebars_assets", "0.23.9"
|
||||
gem "jquery-rails", "4.5.0"
|
||||
gem "jquery-rails", "4.6.0"
|
||||
gem "jquery-ui-rails", "7.0.0"
|
||||
gem "js_image_paths", "0.2.0"
|
||||
gem "js-routes", "2.2.4"
|
||||
|
||||
source "https://gems.diasporafoundation.org" do
|
||||
gem "rails-assets-jquery", "3.6.0" # Should be kept in sync with jquery-rails
|
||||
gem "rails-assets-jquery.ui", "1.11.4"
|
||||
|
||||
gem "rails-assets-highlightjs", "9.12.0"
|
||||
gem "rails-assets-markdown-it", "8.4.2"
|
||||
gem "rails-assets-markdown-it-diaspora-mention", "1.2.0"
|
||||
gem "rails-assets-markdown-it-footnote", "3.0.3"
|
||||
gem "rails-assets-markdown-it-hashtag", "0.4.0"
|
||||
gem "rails-assets-markdown-it--markdown-it-for-inline", "0.1.1"
|
||||
gem "rails-assets-markdown-it-sanitizer", "0.4.3"
|
||||
gem "rails-assets-markdown-it-sub", "1.0.0"
|
||||
gem "rails-assets-markdown-it-sup", "1.0.0"
|
||||
|
||||
gem "rails-assets-backbone", "1.3.3"
|
||||
gem "rails-assets-bootstrap", "3.4.1"
|
||||
gem "rails-assets-bootstrap-markdown", "2.10.0"
|
||||
gem "rails-assets-corejs-typeahead", "1.2.1"
|
||||
gem "rails-assets-fine-uploader", "5.13.0"
|
||||
|
||||
# jQuery plugins
|
||||
|
||||
gem "rails-assets-autosize", "4.0.2"
|
||||
gem "rails-assets-blueimp-gallery", "2.33.0"
|
||||
gem "rails-assets-jquery.are-you-sure", "1.9.0"
|
||||
gem "rails-assets-jquery-placeholder", "2.3.1"
|
||||
gem "rails-assets-jquery-textchange", "0.2.3"
|
||||
gem "rails-assets-utatti-perfect-scrollbar", "1.4.0"
|
||||
end
|
||||
|
||||
gem "markdown-it-html5-embed", "1.0.0"
|
||||
gem "js-routes", "2.2.8"
|
||||
|
||||
# Localization
|
||||
|
||||
gem "http_accept_language", "2.1.1"
|
||||
gem "i18n-inflector-rails", "1.0.7"
|
||||
gem "rails-i18n", "6.0.0"
|
||||
gem "rails-i18n", "7.0.9"
|
||||
|
||||
# Map
|
||||
gem "leaflet-rails", "1.7.0"
|
||||
gem "leaflet-rails", "1.9.4"
|
||||
|
||||
# Parsing
|
||||
|
||||
gem "nokogiri", "1.13.7"
|
||||
gem "open_graph_reader", "0.7.2" # also update User-Agent in features/support/webmock.rb and open_graph_cache_spec.rb
|
||||
gem "redcarpet", "3.5.1"
|
||||
gem "ruby-oembed", "0.16.1"
|
||||
gem "twitter-text", "1.14.7"
|
||||
gem "nokogiri", "1.16.5"
|
||||
gem "open_graph_reader", "0.9.1" # also update User-Agent in features/support/webmock.rb and open_graph_cache_spec.rb
|
||||
gem "redcarpet", "3.6.0"
|
||||
gem "ruby-oembed", "0.17.0"
|
||||
gem "twitter-text", "3.1.0"
|
||||
|
||||
# Rate limitting
|
||||
|
||||
gem "rack-attack", "6.7.0"
|
||||
|
||||
# RTL support
|
||||
|
||||
@@ -147,47 +125,44 @@ gem "string-direction", "1.2.2"
|
||||
|
||||
# Security Headers
|
||||
|
||||
gem "secure_headers", "6.3.3"
|
||||
gem "secure_headers", "6.5.0"
|
||||
|
||||
# Services
|
||||
|
||||
gem "omniauth", "2.1.0"
|
||||
gem "omniauth-rails_csrf_protection", "1.0.1"
|
||||
gem "omniauth", "2.1.2"
|
||||
gem "omniauth-rails_csrf_protection", "1.0.2"
|
||||
gem "omniauth-tumblr", "1.2"
|
||||
gem "omniauth-twitter", "1.4.0"
|
||||
gem "omniauth-wordpress", "0.2.2"
|
||||
gem "twitter", "7.0.0"
|
||||
gem "twitter", "8.0.0"
|
||||
|
||||
# OpenID Connect
|
||||
gem "openid_connect", "1.3.0"
|
||||
gem "openid_connect", "2.3.0"
|
||||
|
||||
# Serializers
|
||||
|
||||
gem "active_model_serializers", "0.9.8"
|
||||
|
||||
# XMPP chat dependencies
|
||||
gem "diaspora-prosody-config", "0.0.7"
|
||||
gem "rails-assets-diaspora_jsxc", "0.1.5.develop.7", source: "https://gems.diasporafoundation.org"
|
||||
gem "active_model_serializers", "0.9.12"
|
||||
|
||||
# Tags
|
||||
|
||||
gem "acts-as-taggable-on", "9.0.1"
|
||||
gem "acts-as-taggable-on", "10.0.0"
|
||||
|
||||
# URIs and HTTP
|
||||
|
||||
gem "addressable", "2.8.0", require: "addressable/uri"
|
||||
gem "faraday", "0.17.5"
|
||||
gem "faraday-cookie_jar", "0.0.7"
|
||||
gem "faraday_middleware", "0.14.0"
|
||||
gem "typhoeus", "1.4.0"
|
||||
gem "addressable", "2.8.6", require: "addressable/uri"
|
||||
gem "faraday", "2.9.0"
|
||||
gem "faraday-cookie_jar", "0.0.7"
|
||||
gem "faraday-follow_redirects", "0.3.0"
|
||||
gem "faraday-typhoeus", "1.1.0", require: false
|
||||
gem "typhoeus", "1.4.1"
|
||||
|
||||
# Views
|
||||
|
||||
gem "gon", "6.4.0"
|
||||
gem "hamlit", "2.16.0"
|
||||
gem "hamlit", "3.0.3"
|
||||
gem "mobile-fu", "1.4.0"
|
||||
gem "rails-timeago", "2.20.0"
|
||||
gem "will_paginate", "3.3.1"
|
||||
gem "will_paginate", "4.0.0"
|
||||
|
||||
# Logging
|
||||
|
||||
@@ -202,17 +177,10 @@ gem "rubyzip", "2.3.2", require: "zip"
|
||||
# https://github.com/gitlabhq/gitlabhq/issues/3826
|
||||
# https://github.com/gitlabhq/gitlabhq/pull/3852
|
||||
# https://github.com/discourse/discourse/pull/238
|
||||
gem "minitest", "5.15.0"
|
||||
gem "minitest", "5.23.1"
|
||||
|
||||
gem "versionist", "2.0.1"
|
||||
|
||||
# Windows and OSX have an execjs compatible runtime built-in, Linux users should
|
||||
# install Node.js or use "therubyracer".
|
||||
#
|
||||
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
|
||||
|
||||
# gem "therubyracer", :platform => :ruby
|
||||
|
||||
group :production do # we don"t install these on travis to speed up test runs
|
||||
# Analytics
|
||||
|
||||
@@ -221,7 +189,7 @@ group :production do # we don"t install these on travis to speed up test runs
|
||||
|
||||
# Process management
|
||||
|
||||
gem "eye", "0.10.0"
|
||||
gem "foreman", "0.88.1", require: false
|
||||
|
||||
# Redirects
|
||||
|
||||
@@ -230,30 +198,32 @@ group :production do # we don"t install these on travis to speed up test runs
|
||||
|
||||
# Third party asset hosting
|
||||
|
||||
gem "asset_sync", "2.15.2", require: false
|
||||
gem "asset_sync", "2.19.1", require: false
|
||||
end
|
||||
|
||||
group :development do
|
||||
# Linters
|
||||
gem "haml_lint", "0.40.0", require: false
|
||||
gem "pronto", "0.11.0", require: false
|
||||
gem "pronto-eslint", "0.11.0", require: false
|
||||
gem "haml_lint", "0.58.0", require: false
|
||||
gem "pronto", "0.11.2", require: false
|
||||
gem "pronto-eslint", "0.11.1", require: false
|
||||
gem "pronto-haml", "0.11.1", require: false
|
||||
gem "pronto-rubocop", "0.11.1", require: false
|
||||
gem "pronto-rubocop", "0.11.5", require: false
|
||||
gem "pronto-scss", "0.11.0", require: false
|
||||
gem "rubocop", "0.93.1", require: false
|
||||
gem "rubocop-rails", "2.9.1", require: false
|
||||
gem "rubocop", "1.64.0", require: false
|
||||
gem "rubocop-rails", "2.25.0", require: false
|
||||
|
||||
gem "faraday-retry", require: false # used by pronto/octokit
|
||||
|
||||
# Debugging
|
||||
gem "pry"
|
||||
gem "pry-byebug"
|
||||
|
||||
# test coverage
|
||||
gem "simplecov", "0.21.2", require: false
|
||||
gem "simplecov", "0.22.0", require: false
|
||||
|
||||
gem "turbo_dev_assets", "0.0.2"
|
||||
|
||||
gem "listen", "3.7.1"
|
||||
gem "listen", "3.9.0"
|
||||
end
|
||||
|
||||
group :test do
|
||||
@@ -265,34 +235,33 @@ group :test do
|
||||
|
||||
# Cucumber (integration tests)
|
||||
|
||||
gem "apparition", "0.6.0"
|
||||
gem "capybara", "3.35.3"
|
||||
gem "database_cleaner-active_record", "2.0.1"
|
||||
gem "capybara", "3.40.0"
|
||||
gem "cuprite", "0.15"
|
||||
gem "database_cleaner-active_record", "2.1.0"
|
||||
|
||||
gem "cucumber-api-steps", "0.14", require: false
|
||||
|
||||
# General helpers
|
||||
|
||||
gem "factory_girl_rails", "4.9.0"
|
||||
gem "shoulda-matchers", "4.5.1"
|
||||
gem "timecop", "0.9.5"
|
||||
gem "webmock", "3.14.0", require: false
|
||||
gem "factory_bot_rails", "6.4.3"
|
||||
gem "shoulda-matchers", "6.2.0"
|
||||
gem "timecop", "0.9.8"
|
||||
gem "webmock", "3.23.1", require: false
|
||||
|
||||
gem "diaspora_federation-test", "0.2.8"
|
||||
gem "diaspora_federation-test", "1.1.0"
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
# RSpec (unit tests, some integration tests)
|
||||
gem "rspec-rails", "5.1.2"
|
||||
gem "rspec-rails", "6.1.2"
|
||||
|
||||
# Cucumber (integration tests)
|
||||
gem "cucumber-rails", "2.5.1", require: false
|
||||
gem "cucumber-rails", "3.0.0", require: false
|
||||
|
||||
# Jasmine (client side application tests (JS))
|
||||
gem "chrome_remote", "0.3.0"
|
||||
gem "jasmine", "3.10.0"
|
||||
gem "jasmine-jquery-rails", "2.0.3"
|
||||
gem "rails-assets-jasmine-ajax", "4.0.0", source: "https://gems.diasporafoundation.org"
|
||||
gem "sinon-rails", "1.15.0"
|
||||
|
||||
# For `assigns` in controller specs
|
||||
|
||||
1033
Gemfile.lock
1033
Gemfile.lock
File diff suppressed because it is too large
Load Diff
2
Procfile
2
Procfile
@@ -1,2 +1,2 @@
|
||||
web: bin/bundle exec unicorn -c config/unicorn.rb -p $PORT
|
||||
web: bin/puma -C config/puma.rb
|
||||
sidekiq: bin/bundle exec sidekiq
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
//= link contact-list.js
|
||||
//= link jquery3.js
|
||||
//= link jquery_ujs.js
|
||||
//= link jsxc.js
|
||||
//= link bookmarklet.js
|
||||
//= link mobile/bookmarklet.js
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
//= require_tree ./collections
|
||||
//= require_tree ./views
|
||||
|
||||
//= require utatti-perfect-scrollbar/dist/perfect-scrollbar
|
||||
//= require perfect-scrollbar/dist/perfect-scrollbar
|
||||
|
||||
var app = {
|
||||
collections: {},
|
||||
|
||||
@@ -12,12 +12,17 @@ app.collections.Comments = Backbone.Collection.extend({
|
||||
|
||||
make : function(text) {
|
||||
var self = this;
|
||||
var comment = new app.models.Comment({ "text": text });
|
||||
var comment = new app.models.Comment({"text": text}, {post: this.post});
|
||||
|
||||
var deferred = comment.save({}, {
|
||||
url: "/posts/"+ this.post.id +"/comments",
|
||||
success: function() {
|
||||
comment.set({author: app.currentUser.toJSON(), parent: self.post });
|
||||
|
||||
// Need interactions after make
|
||||
comment.interactions = new app.models.LikeInteractions(
|
||||
_.extend({comment: comment, post: self.post}, comment.get("interactions"))
|
||||
);
|
||||
self.add(comment);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,7 +4,11 @@ app.collections.Likes = Backbone.Collection.extend({
|
||||
model: app.models.Like,
|
||||
|
||||
initialize : function(models, options) {
|
||||
this.url = "/posts/" + options.post.id + "/likes"; //not delegating to post.url() because when it is in a stream collection it delegates to that url
|
||||
// A comment- like has a post reference and a comment reference
|
||||
this.url = (options.comment != null) ?
|
||||
// not delegating to post.url() because when it is in a stream collection it delegates to that url
|
||||
"/comments/" + options.comment.id + "/likes" :
|
||||
"/posts/" + options.post.id + "/likes";
|
||||
}
|
||||
});
|
||||
// @license-end
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||
|
||||
app.models.Comment = Backbone.Model.extend({
|
||||
urlRoot: "/comments"
|
||||
urlRoot: "/comments",
|
||||
|
||||
initialize: function(model, options) {
|
||||
options = options || {};
|
||||
this.post = model.post || options.post || this.collection.post;
|
||||
this.interactions = new app.models.LikeInteractions(
|
||||
_.extend({comment: this, post: this.post}, this.get("interactions"))
|
||||
);
|
||||
this.likes = this.interactions.likes;
|
||||
this.likesCount = this.attributes.likes_count;
|
||||
this.userLike = this.interactions.userLike();
|
||||
}
|
||||
});
|
||||
// @license-end
|
||||
|
||||
58
app/assets/javascripts/app/models/like_interactions.js
Normal file
58
app/assets/javascripts/app/models/like_interactions.js
Normal file
@@ -0,0 +1,58 @@
|
||||
// This class contains code extracted from interactions.js to factorize likes management between posts and comments
|
||||
|
||||
app.models.LikeInteractions = Backbone.Model.extend({
|
||||
|
||||
initialize: function(options) {
|
||||
this.likes = new app.collections.Likes(this.get("likes"), options);
|
||||
this.post = options.post;
|
||||
},
|
||||
|
||||
likesCount: function() {
|
||||
return this.get("likes_count");
|
||||
},
|
||||
|
||||
userLike: function() {
|
||||
return this.likes.select(function(like) {
|
||||
return like.get("author") && like.get("author").guid === app.currentUser.get("guid");
|
||||
})[0];
|
||||
},
|
||||
|
||||
toggleLike: function() {
|
||||
if (this.userLike()) {
|
||||
this.unlike();
|
||||
} else {
|
||||
this.like();
|
||||
}
|
||||
},
|
||||
|
||||
like: function() {
|
||||
var self = this;
|
||||
this.likes.create({}, {
|
||||
success: function() {
|
||||
self.post.set({participation: true});
|
||||
self.trigger("change");
|
||||
self.set({"likes_count": self.get("likes_count") + 1});
|
||||
self.likes.trigger("change");
|
||||
},
|
||||
error: function(model, response) {
|
||||
app.flashMessages.handleAjaxError(response);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
unlike: function() {
|
||||
var self = this;
|
||||
this.userLike().destroy({
|
||||
success: function() {
|
||||
// TODO: unlike always sets participation to false in the UI, even if there are more participations left
|
||||
// in the backend (from manually participating, other likes or comments)
|
||||
self.post.set({participation: false});
|
||||
self.trigger("change");
|
||||
self.set({"likes_count": self.get("likes_count") - 1});
|
||||
self.likes.trigger("change");
|
||||
},
|
||||
error: function(model, response) {
|
||||
app.flashMessages.handleAjaxError(response);
|
||||
}});
|
||||
}
|
||||
});
|
||||
@@ -4,7 +4,7 @@ app.models.Post = Backbone.Model.extend(_.extend({}, app.models.formatDateMixin,
|
||||
urlRoot : "/posts",
|
||||
|
||||
initialize : function() {
|
||||
this.interactions = new app.models.Post.Interactions(_.extend({post : this}, this.get("interactions")));
|
||||
this.interactions = new app.models.PostInteractions(_.extend({post: this}, this.get("interactions")));
|
||||
this.delegateToInteractions();
|
||||
},
|
||||
|
||||
|
||||
@@ -1,75 +1,27 @@
|
||||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||
|
||||
//require ../post
|
||||
|
||||
app.models.Post.Interactions = Backbone.Model.extend({
|
||||
initialize : function(options){
|
||||
app.models.PostInteractions = app.models.LikeInteractions.extend({
|
||||
initialize: function(options) {
|
||||
app.models.LikeInteractions.prototype.initialize.apply(this, arguments);
|
||||
this.post = options.post;
|
||||
this.comments = new app.collections.Comments(this.get("comments"), {post : this.post});
|
||||
this.likes = new app.collections.Likes(this.get("likes"), {post : this.post});
|
||||
this.reshares = new app.collections.Reshares(this.get("reshares"), {post : this.post});
|
||||
this.comments = new app.collections.Comments(this.get("comments"), {post: this.post});
|
||||
this.reshares = new app.collections.Reshares(this.get("reshares"), {post: this.post});
|
||||
},
|
||||
|
||||
likesCount : function(){
|
||||
return this.get("likes_count");
|
||||
},
|
||||
|
||||
resharesCount : function(){
|
||||
resharesCount: function() {
|
||||
return this.get("reshares_count");
|
||||
},
|
||||
|
||||
commentsCount : function(){
|
||||
commentsCount: function() {
|
||||
return this.get("comments_count");
|
||||
},
|
||||
|
||||
userLike : function(){
|
||||
return this.likes.select(function(like){
|
||||
return like.get("author") && like.get("author").guid === app.currentUser.get("guid");
|
||||
})[0];
|
||||
},
|
||||
|
||||
userReshare : function(){
|
||||
userReshare: function() {
|
||||
return this.reshares.select(function(reshare){
|
||||
return reshare.get("author") && reshare.get("author").guid === app.currentUser.get("guid");
|
||||
})[0];
|
||||
},
|
||||
|
||||
toggleLike : function() {
|
||||
if(this.userLike()) {
|
||||
this.unlike();
|
||||
} else {
|
||||
this.like();
|
||||
}
|
||||
},
|
||||
|
||||
like : function() {
|
||||
var self = this;
|
||||
this.likes.create({}, {
|
||||
success: function() {
|
||||
self.post.set({participation: true});
|
||||
self.trigger("change");
|
||||
self.set({"likes_count" : self.get("likes_count") + 1});
|
||||
self.likes.trigger("change");
|
||||
},
|
||||
error: function(model, response) {
|
||||
app.flashMessages.handleAjaxError(response);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
unlike : function() {
|
||||
var self = this;
|
||||
this.userLike().destroy({success : function() {
|
||||
self.post.set({participation: false});
|
||||
self.trigger('change');
|
||||
self.set({"likes_count" : self.get("likes_count") - 1});
|
||||
self.likes.trigger("change");
|
||||
},
|
||||
error: function(model, response) {
|
||||
app.flashMessages.handleAjaxError(response);
|
||||
}});
|
||||
},
|
||||
|
||||
comment: function(text, options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
@@ -109,7 +61,7 @@ app.models.Post.Interactions = Backbone.Model.extend({
|
||||
});
|
||||
},
|
||||
|
||||
userCanReshare : function(){
|
||||
userCanReshare: function() {
|
||||
var isReshare = this.post.get("post_type") === "Reshare"
|
||||
, rootExists = (isReshare ? this.post.get("root") : true)
|
||||
, publicPost = this.post.get("public")
|
||||
@@ -29,6 +29,25 @@ app.pages.AdminPods = app.views.Base.extend({
|
||||
|
||||
_showMessages: function() {
|
||||
var msgs = document.createDocumentFragment();
|
||||
if (gon.totalCount && gon.totalCount > 0) {
|
||||
let totalPods = $("<div class='alert alert-info' role='alert' />")
|
||||
.append(Diaspora.I18n.t("admin.pods.total", {count: gon.totalCount}));
|
||||
if (gon.activeCount) {
|
||||
if (gon.activeCount === 0) {
|
||||
totalPods
|
||||
.append(" " + Diaspora.I18n.t("admin.pods.none_active"));
|
||||
}
|
||||
if (gon.activeCount === gon.totalCount) {
|
||||
totalPods
|
||||
.append(" " + Diaspora.I18n.t("admin.pods.all_active"));
|
||||
} else {
|
||||
totalPods
|
||||
.append(" " + Diaspora.I18n.t("admin.pods.active", {count: gon.activeCount}));
|
||||
}
|
||||
}
|
||||
msgs.appendChild(totalPods[0]);
|
||||
}
|
||||
|
||||
if( gon.uncheckedCount && gon.uncheckedCount > 0 ) {
|
||||
var unchecked = $("<div class='alert alert-info' role='alert' />")
|
||||
.append(Diaspora.I18n.t("admin.pods.unchecked", {count: gon.uncheckedCount}));
|
||||
|
||||
@@ -5,14 +5,12 @@ app.pages.Contacts = Backbone.View.extend({
|
||||
el: "#contacts_container",
|
||||
|
||||
events: {
|
||||
"click #chat_privilege_toggle" : "toggleChatPrivilege",
|
||||
"click #change_aspect_name" : "showAspectNameForm",
|
||||
"click .conversation_button": "showMessageModal",
|
||||
"click .invitations-button": "showInvitationsModal"
|
||||
},
|
||||
|
||||
initialize: function(opts) {
|
||||
this.chatToggle = $("#chat_privilege_toggle i");
|
||||
this.stream = opts.stream;
|
||||
this.stream.render();
|
||||
$("#people-stream.contacts .header i").tooltip({"placement": "bottom"});
|
||||
@@ -27,22 +25,6 @@ app.pages.Contacts = Backbone.View.extend({
|
||||
this.setupAspectSorting();
|
||||
},
|
||||
|
||||
toggleChatPrivilege: function() {
|
||||
if (this.chatToggle.hasClass("enabled")) {
|
||||
this.chatToggle.tooltip("destroy")
|
||||
.removeClass("enabled")
|
||||
.removeAttr("data-original-title")
|
||||
.attr("title", Diaspora.I18n.t("contacts.aspect_chat_is_not_enabled"))
|
||||
.tooltip({"placement": "bottom"});
|
||||
} else {
|
||||
this.chatToggle.tooltip("destroy")
|
||||
.addClass("enabled")
|
||||
.removeAttr("data-original-title")
|
||||
.attr("title", Diaspora.I18n.t("contacts.aspect_chat_is_enabled"))
|
||||
.tooltip({"placement": "bottom"});
|
||||
}
|
||||
},
|
||||
|
||||
showAspectNameForm: function() {
|
||||
$(".header > h3").hide();
|
||||
var aspectName = $.trim($(".header h3 #aspect_name").text());
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||
app.pages.Registration = Backbone.View.extend({
|
||||
initialize: function() {
|
||||
$("input#user_email").popover({
|
||||
placement: "left",
|
||||
trigger: "focus"
|
||||
}).popover("show");
|
||||
}
|
||||
});
|
||||
// @license-end
|
||||
@@ -24,6 +24,7 @@ app.Router = Backbone.Router.extend({
|
||||
"posts/:id(/)": "singlePost",
|
||||
"profile/edit(/)": "settings",
|
||||
"public(/)": "stream",
|
||||
"local_public(/)": "stream",
|
||||
"stream(/)": "stream",
|
||||
"tags/:name(/)": "followed_tags",
|
||||
"u/:name(/)": "profile",
|
||||
@@ -172,10 +173,6 @@ app.Router = Backbone.Router.extend({
|
||||
});
|
||||
},
|
||||
|
||||
registration: function() {
|
||||
app.page = new app.pages.Registration();
|
||||
},
|
||||
|
||||
settings: function() {
|
||||
app.page = new app.pages.Settings();
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ app.views.Base = Backbone.View.extend({
|
||||
|
||||
initialize : function() {
|
||||
this.setupRenderEvents();
|
||||
this.setupReport();
|
||||
},
|
||||
|
||||
presenter : function(){
|
||||
@@ -102,22 +103,25 @@ app.views.Base = Backbone.View.extend({
|
||||
this.model.set(_.inject(this.formAttrs, _.bind(setValueFromField, this), {}));
|
||||
},
|
||||
|
||||
report: function(evt) {
|
||||
if(evt) { evt.preventDefault(); }
|
||||
var msg = prompt(Diaspora.I18n.t('report.prompt'), Diaspora.I18n.t('report.prompt_default'));
|
||||
if (msg == null) {
|
||||
return;
|
||||
setupReport: function() {
|
||||
const reportForm = document.getElementById("report-content-form");
|
||||
if (reportForm) {
|
||||
reportForm.addEventListener("submit", this.onSubmitReport);
|
||||
}
|
||||
var data = {
|
||||
report: {
|
||||
item_id: this.model.id,
|
||||
item_type: $(evt.currentTarget).data("type"),
|
||||
text: msg
|
||||
}
|
||||
},
|
||||
|
||||
onSubmitReport: function(ev) {
|
||||
if (ev) { ev.preventDefault(); }
|
||||
const form = ev.currentTarget;
|
||||
$("#reportModal").modal("hide");
|
||||
const textarea = document.getElementById("report-reason-field");
|
||||
const report = {
|
||||
item_id: form.dataset.reportId,
|
||||
item_type: form.dataset.reportType,
|
||||
text: textarea.value
|
||||
};
|
||||
|
||||
var report = new app.models.Report();
|
||||
report.save(data, {
|
||||
new app.models.Report().save({report: report}, {
|
||||
success: function() {
|
||||
app.flashMessages.success(Diaspora.I18n.t("report.status.created"));
|
||||
},
|
||||
@@ -127,11 +131,21 @@ app.views.Base = Backbone.View.extend({
|
||||
});
|
||||
},
|
||||
|
||||
report: function(evt) {
|
||||
if (evt) { evt.preventDefault(); }
|
||||
const form = document.getElementById("report-content-form");
|
||||
form.dataset.reportId = this.model.id;
|
||||
form.dataset.reportType = evt.currentTarget.dataset.type;
|
||||
document.getElementById("report-reason-field").value = "";
|
||||
document.getElementById("report-reason-field").focus();
|
||||
$("#reportModal").modal();
|
||||
},
|
||||
|
||||
destroyConfirmMsg: function() { return Diaspora.I18n.t("confirm_dialog"); },
|
||||
|
||||
destroyModel: function(evt) {
|
||||
evt && evt.preventDefault();
|
||||
var url = this.model.urlRoot + "/" + this.model.id;
|
||||
const url = this.model.urlRoot + "/" + this.model.id;
|
||||
|
||||
if( confirm(_.result(this, "destroyConfirmMsg")) ) {
|
||||
this.$el.addClass("deleting");
|
||||
|
||||
@@ -27,16 +27,14 @@ app.views.AspectsDropdown = app.views.Base.extend({
|
||||
},
|
||||
|
||||
// change class and text of the dropdown button
|
||||
_updateButton: function(inAspectClass) {
|
||||
var button = this.$('.btn.dropdown-toggle'),
|
||||
_updateButton: function() {
|
||||
let button = this.$(".btn.dropdown-toggle"),
|
||||
selectedAspects = this.$(".dropdown-menu > li.selected").length,
|
||||
buttonText;
|
||||
|
||||
if (selectedAspects === 0) {
|
||||
button.removeClass(inAspectClass).addClass('btn-default');
|
||||
buttonText = Diaspora.I18n.t("aspect_dropdown.select_aspects");
|
||||
} else {
|
||||
button.removeClass('btn-default').addClass(inAspectClass);
|
||||
if (selectedAspects === 1) {
|
||||
buttonText = this.$(".dropdown-menu > li.selected .text").first().text();
|
||||
} else {
|
||||
|
||||
@@ -6,35 +6,52 @@ app.views.Comment = app.views.Content.extend({
|
||||
className : "comment media",
|
||||
tooltipSelector: "time",
|
||||
|
||||
events : function() {
|
||||
subviews: {
|
||||
".likes-on-comment": "likesInfoView"
|
||||
},
|
||||
|
||||
events: function() {
|
||||
return _.extend({}, app.views.Content.prototype.events, {
|
||||
"click .comment_delete": "destroyModel",
|
||||
"click .comment_report": "report"
|
||||
"click .comment_report": "report",
|
||||
"click .like": "toggleLike"
|
||||
});
|
||||
},
|
||||
|
||||
initialize : function(options){
|
||||
initialize: function(options) {
|
||||
this.templateName = options.templateName || this.templateName;
|
||||
this.model.interactions.on("change", this.render, this);
|
||||
this.model.on("change", this.render, this);
|
||||
},
|
||||
|
||||
presenter : function() {
|
||||
presenter: function() {
|
||||
return _.extend(this.defaultPresenter(), {
|
||||
canRemove: this.canRemove(),
|
||||
text: app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people"))
|
||||
text: app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people")),
|
||||
likesCount: this.model.attributes.likesCount,
|
||||
userLike: this.model.interactions.userLike()
|
||||
});
|
||||
},
|
||||
|
||||
ownComment : function() {
|
||||
ownComment: function() {
|
||||
return app.currentUser.authenticated() && this.model.get("author").diaspora_id === app.currentUser.get("diaspora_id");
|
||||
},
|
||||
|
||||
postOwner : function() {
|
||||
postOwner: function() {
|
||||
return app.currentUser.authenticated() && this.model.get("parent").author.diaspora_id === app.currentUser.get("diaspora_id");
|
||||
},
|
||||
|
||||
canRemove : function() {
|
||||
canRemove: function() {
|
||||
return app.currentUser.authenticated() && (this.ownComment() || this.postOwner());
|
||||
},
|
||||
|
||||
toggleLike: function(evt) {
|
||||
if (evt) { evt.preventDefault(); }
|
||||
this.model.interactions.toggleLike();
|
||||
},
|
||||
|
||||
likesInfoView: function() {
|
||||
return new app.views.LikesInfo({model: this.model});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@ app.views.Help = app.views.StaticContentView.extend({
|
||||
"click .faq-link-sharing": "sharing",
|
||||
"click .faq-link-posts-and-posting": "postsAndPosting",
|
||||
"click .faq-link-tags": "tags",
|
||||
"click .faq-link-keyboard-shortcuts": "keyboardShortcuts",
|
||||
"click .faq-link-chat": "chat"
|
||||
"click .faq-link-keyboard-shortcuts": "keyboardShortcuts"
|
||||
},
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
@@ -41,31 +40,22 @@ app.views.Help = app.views.StaticContentView.extend({
|
||||
}
|
||||
};
|
||||
|
||||
this.CHAT_SUBS = {
|
||||
add_contact_roster_a: {
|
||||
toggle_privilege: this.getChatIcons(),
|
||||
contacts_page: this.linkHtml(Routes.contacts(), Diaspora.I18n.t("chat.contacts_page"))
|
||||
}
|
||||
};
|
||||
|
||||
this.data = {
|
||||
title_header: Diaspora.I18n.t( 'title_header' ),
|
||||
title_getting_help: Diaspora.I18n.t( 'getting_help.title' ),
|
||||
title_account_and_data_management: Diaspora.I18n.t( 'account_and_data_management.title' ),
|
||||
title_aspects: Diaspora.I18n.t( 'aspects.title' ),
|
||||
title_mentions: Diaspora.I18n.t( 'mentions.title' ),
|
||||
title_pods: Diaspora.I18n.t( 'pods.title' ),
|
||||
title_posts_and_posting: Diaspora.I18n.t( 'posts_and_posting.title' ),
|
||||
title_private_posts: Diaspora.I18n.t( 'private_posts.title' ),
|
||||
title_public_posts: Diaspora.I18n.t( 'public_posts.title' ),
|
||||
title_resharing_posts: Diaspora.I18n.t( 'resharing_posts.title' ),
|
||||
title_header: Diaspora.I18n.t("title_header"),
|
||||
title_getting_help: Diaspora.I18n.t("getting_help.title"),
|
||||
title_account_and_data_management: Diaspora.I18n.t("account_and_data_management.title"),
|
||||
title_aspects: Diaspora.I18n.t("aspects.title"),
|
||||
title_mentions: Diaspora.I18n.t("mentions.title"),
|
||||
title_pods: Diaspora.I18n.t("pods.title"),
|
||||
title_posts_and_posting: Diaspora.I18n.t("posts_and_posting.title"),
|
||||
title_private_posts: Diaspora.I18n.t("private_posts.title"),
|
||||
title_public_posts: Diaspora.I18n.t("public_posts.title"),
|
||||
title_resharing_posts: Diaspora.I18n.t("resharing_posts.title"),
|
||||
title_profile: Diaspora.I18n.t("profile.title"),
|
||||
title_sharing: Diaspora.I18n.t( 'sharing.title' ),
|
||||
title_tags: Diaspora.I18n.t( 'tags.title' ),
|
||||
title_keyboard_shortcuts: Diaspora.I18n.t( 'keyboard_shortcuts.title' ),
|
||||
title_miscellaneous: Diaspora.I18n.t( 'miscellaneous.title' ),
|
||||
title_chat: Diaspora.I18n.t( 'chat.title' ),
|
||||
chat_enabled: this.chatEnabled()
|
||||
title_sharing: Diaspora.I18n.t("sharing.title"),
|
||||
title_tags: Diaspora.I18n.t("tags.title"),
|
||||
title_keyboard_shortcuts: Diaspora.I18n.t("keyboard_shortcuts.title"),
|
||||
title_miscellaneous: Diaspora.I18n.t("miscellaneous.title")
|
||||
};
|
||||
|
||||
return this;
|
||||
@@ -201,27 +191,8 @@ app.views.Help = app.views.StaticContentView.extend({
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
chat: function(e){
|
||||
this.renderStaticSection("chat", "faq_chat", this.CHAT_SUBS);
|
||||
this.menuClicked(e);
|
||||
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
linkHtml: function(url, text) {
|
||||
return "<a href=\"" + url + "\" target=\"_blank\">" + text + "</a>";
|
||||
},
|
||||
|
||||
chatEnabled: function(){
|
||||
return gon.appConfig.chat.enabled;
|
||||
},
|
||||
|
||||
getChatIcons: function(){
|
||||
return "<div class=\"help-chat-icons\">" +
|
||||
" <i class=\"entypo-lock-open\"></i>" +
|
||||
" <i class=\"entypo-chat\"></i>" +
|
||||
" <i class=\"entypo-trash\"></i>" +
|
||||
"</div>";
|
||||
}
|
||||
});
|
||||
// @license-end
|
||||
|
||||
@@ -2,23 +2,16 @@
|
||||
|
||||
app.views.Photos = app.views.InfScroll.extend({
|
||||
className: "clearfix row",
|
||||
|
||||
postClass : app.views.Photo,
|
||||
|
||||
initialize : function() {
|
||||
this.stream = this.model;
|
||||
this.collection = this.stream.items;
|
||||
new app.views.Gallery({el: this.$el});
|
||||
|
||||
// viable for extraction
|
||||
this.stream.fetch();
|
||||
this.setupInfiniteScroll();
|
||||
},
|
||||
|
||||
postRenderTemplate: function(){
|
||||
var photoAttachments = $("#main-stream > div");
|
||||
if(photoAttachments.length > 0) {
|
||||
new app.views.Gallery({ el: photoAttachments });
|
||||
}
|
||||
}
|
||||
});
|
||||
// @license-end
|
||||
|
||||
@@ -27,12 +27,13 @@ app.views.PodEntry = app.views.Base.extend({
|
||||
presenter: function() {
|
||||
return _.extend({}, this.defaultPresenter(), {
|
||||
/* jshint camelcase: false */
|
||||
hasPort: (this.model.get("port") >= 0),
|
||||
is_unchecked: (this.model.get("status")==="unchecked"),
|
||||
has_no_errors: (this.model.get("status")==="no_errors"),
|
||||
has_errors: (this.model.get("status")!=="no_errors"),
|
||||
status_text: Diaspora.I18n.t("admin.pods.states."+this.model.get("status")),
|
||||
pod_url: (this.model.get("ssl") ? "https" : "http") + "://" + this.model.get("host") +
|
||||
(this.model.get("port") ? ":" + this.model.get("port") : ""),
|
||||
(this.model.get("port") >= 0 ? ":" + this.model.get("port") : ""),
|
||||
response_time_fmt: this._fmtResponseTime()
|
||||
/* jshint camelcase: true */
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ app.views.PublisherAspectSelector = app.views.AspectsDropdown.extend({
|
||||
}
|
||||
|
||||
this._updateSelectedAspectIds();
|
||||
this._updateButton('btn-default');
|
||||
this._updateButton();
|
||||
|
||||
// update the globe or lock icon
|
||||
var icon = this.$("#visibility-icon");
|
||||
@@ -48,7 +48,7 @@ app.views.PublisherAspectSelector = app.views.AspectsDropdown.extend({
|
||||
updateAspectsSelector: function(ids){
|
||||
this._selectAspects(ids);
|
||||
this._updateSelectedAspectIds();
|
||||
this._updateButton('btn-default');
|
||||
this._updateButton();
|
||||
},
|
||||
|
||||
// take care of the form fields that will indicate the selected aspects
|
||||
|
||||
@@ -5,139 +5,102 @@
|
||||
// progress. Attaches previews of finished uploads to the publisher.
|
||||
|
||||
app.views.PublisherUploader = Backbone.View.extend({
|
||||
allowedExtensions: ["jpg", "jpeg", "png", "gif"],
|
||||
sizeLimit: 4194304, // bytes
|
||||
|
||||
initialize: function(opts) {
|
||||
this.publisher = opts.publisher;
|
||||
this.uploader = new qq.FineUploaderBasic({
|
||||
element: this.el,
|
||||
button: this.el,
|
||||
|
||||
text: {
|
||||
fileInputTitle: Diaspora.I18n.t("photo_uploader.upload_photos")
|
||||
},
|
||||
request: {
|
||||
endpoint: Routes.photos(),
|
||||
params: {
|
||||
/* eslint-disable camelcase */
|
||||
authenticity_token: $("meta[name='csrf-token']").attr("content"),
|
||||
/* eslint-enable camelcase */
|
||||
photo: {
|
||||
pending: true
|
||||
}
|
||||
}
|
||||
},
|
||||
validation: {
|
||||
allowedExtensions: this.allowedExtensions,
|
||||
sizeLimit: this.sizeLimit
|
||||
},
|
||||
messages: {
|
||||
typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"),
|
||||
sizeError: Diaspora.I18n.t("photo_uploader.size_error"),
|
||||
emptyError: Diaspora.I18n.t("photo_uploader.empty")
|
||||
},
|
||||
callbacks: {
|
||||
onProgress: _.bind(this.progressHandler, this),
|
||||
onSubmit: _.bind(this.submitHandler, this),
|
||||
onComplete: _.bind(this.uploadCompleteHandler, this),
|
||||
onError: function(id, name, errorReason) {
|
||||
if (app.flashMessages) { app.flashMessages.error(errorReason); }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.info = $("<div id=\"fileInfo\" />");
|
||||
this.publisher.wrapperEl.before(this.info);
|
||||
|
||||
this.publisher.photozoneEl.on("click", ".x", _.bind(this._removePhoto, this));
|
||||
},
|
||||
|
||||
progressHandler: function(id, fileName, loaded, total) {
|
||||
var progress = Math.round(loaded / total * 100);
|
||||
this.info.text(fileName + " " + progress + "%").fadeTo(200, 1);
|
||||
this.publisher.photozoneEl
|
||||
.find("li.loading").first().find(".progress-bar")
|
||||
.width(progress + "%");
|
||||
},
|
||||
|
||||
submitHandler: function() {
|
||||
this.$el.addClass("loading");
|
||||
this._addPhotoPlaceholder();
|
||||
// Initialize the PostPhotoUploader and subscribe its events
|
||||
this.uploader = new Diaspora.PostPhotoUploader(this.el, opts.dropZoneElementIds);
|
||||
this.uploader.onUploadStarted = _.bind(this.uploadStartedHandler, this);
|
||||
this.uploader.onProgress = _.bind(this.progressHandler, this);
|
||||
this.uploader.onUploadCompleted = _.bind(this.uploadCompleteHandler, this);
|
||||
},
|
||||
|
||||
// add photo placeholders to the publisher to indicate an upload in progress
|
||||
_addPhotoPlaceholder: function() {
|
||||
_addPhotoPlaceholder: function(id) {
|
||||
var publisher = this.publisher;
|
||||
publisher.setButtonsEnabled(false);
|
||||
|
||||
publisher.wrapperEl.addClass("with_attachments");
|
||||
publisher.photozoneEl.append(
|
||||
"<li class=\"publisher_photo loading\" style=\"position:relative;\">" +
|
||||
"<li id=\"upload-" + id + "\"class=\"publisher_photo loading\" style=\"position:relative;\">" +
|
||||
" <div class=\"progress\">" +
|
||||
" <div class=\"progress-bar progress-bar-striped active\" role=\"progressbar\"></div>"+
|
||||
" </div>" +
|
||||
" <img src=\"\"+Handlebars.helpers.imageUrl(\"ajax-loader2.gif\")+\"\" class=\"ajax-loader\" alt=\"\" />"+
|
||||
" <div class=\"spinner\"></div>" +
|
||||
"</li>"
|
||||
);
|
||||
},
|
||||
|
||||
uploadCompleteHandler: function(_id, fileName, response) {
|
||||
uploadStartedHandler: function(id) {
|
||||
this.$el.addClass("loading");
|
||||
this._addPhotoPlaceholder(id);
|
||||
},
|
||||
|
||||
progressHandler: function(id, fileName, progress) {
|
||||
this.publisher.photozoneEl
|
||||
.find("li.loading#upload-" + id + " .progress-bar")
|
||||
.width(progress + "%");
|
||||
},
|
||||
|
||||
uploadCompleteHandler: function(id, fileName, response) {
|
||||
if (response.success){
|
||||
this.info.text(Diaspora.I18n.t("photo_uploader.completed", {file: fileName})).fadeTo(2000, 0);
|
||||
var photoId = response.data.photo.id,
|
||||
image = response.data.photo.unprocessed_image;
|
||||
|
||||
var id = response.data.photo.id,
|
||||
url = response.data.photo.unprocessed_image.url;
|
||||
|
||||
this._addFinishedPhoto(id, url);
|
||||
this._addFinishedPhoto(id, photoId, image);
|
||||
this.trigger("change");
|
||||
} else {
|
||||
this._cancelPhotoUpload();
|
||||
this.trigger("change");
|
||||
this.info.text(Diaspora.I18n.t("photo_uploader.error", {file: fileName}));
|
||||
this._cancelPhotoUpload(id);
|
||||
this.publisher.wrapperEl.find("#photodropzone_container").first().after(
|
||||
"<div id=\"upload_error\">" +
|
||||
Diaspora.I18n.t("photo_uploader.error", {file: fileName}) +
|
||||
Diaspora.I18n.t("photo_uploader.error", {file: fileName}) +
|
||||
"</div>"
|
||||
);
|
||||
this.trigger("change");
|
||||
}
|
||||
},
|
||||
|
||||
// replace the first photo placeholder with the finished uploaded image and
|
||||
// add the id to the publishers form
|
||||
_addFinishedPhoto: function(id, url) {
|
||||
_addFinishedPhoto: function(id, photoId, image) {
|
||||
var publisher = this.publisher;
|
||||
|
||||
// add form input element
|
||||
publisher.$(".content_creation form").append(
|
||||
"<input type=\"hidden\", value=\""+id+"\" name=\"photos[]\" />"
|
||||
"<input type=\"hidden\", value=\"" + photoId + "\" name=\"photos[]\" />"
|
||||
);
|
||||
|
||||
// replace placeholder
|
||||
var placeholder = publisher.photozoneEl.find("li.loading").first();
|
||||
var placeholder = publisher.photozoneEl.find("li.loading#upload-" + id);
|
||||
|
||||
var imgElement = document.createElement("img");
|
||||
imgElement.src = image.thumb_medium.url;
|
||||
imgElement.classList.add("hidden");
|
||||
imgElement.dataset.id = photoId;
|
||||
imgElement.dataset.small = image.thumb_small.url;
|
||||
imgElement.dataset.scaled = image.scaled_full.url;
|
||||
|
||||
placeholder
|
||||
.removeClass("loading")
|
||||
.prepend(
|
||||
"<div class=\"x\"></div>"+
|
||||
"<div class=\"circle\"></div>"
|
||||
)
|
||||
.find("img").attr({"src": url, "data-id": id}).removeClass("ajax-loader");
|
||||
placeholder
|
||||
.find("div.progress").remove();
|
||||
'<div class="x"></div>' +
|
||||
'<div class="circle"></div>' +
|
||||
imgElement.outerHTML
|
||||
).removeClass("loading");
|
||||
placeholder.find("div.progress").remove();
|
||||
placeholder.find("img").on("load", function(ev) {
|
||||
$(ev.target).removeClass("hidden");
|
||||
placeholder.find(".spinner").remove();
|
||||
});
|
||||
|
||||
// no more placeholders? enable buttons
|
||||
if( publisher.photozoneEl.find("li.loading").length === 0 ) {
|
||||
if (publisher.photozoneEl.find("li.loading").length === 0) {
|
||||
this.$el.removeClass("loading");
|
||||
publisher.setButtonsEnabled(true);
|
||||
}
|
||||
},
|
||||
|
||||
_cancelPhotoUpload: function() {
|
||||
var publisher = this.publisher;
|
||||
var placeholder = publisher.photozoneEl.find("li.loading").first();
|
||||
placeholder
|
||||
.removeClass("loading")
|
||||
.find("img").remove();
|
||||
_cancelPhotoUpload: function(id) {
|
||||
this.publisher.photozoneEl.find("li.loading#upload-" + id).remove();
|
||||
},
|
||||
|
||||
// remove an already uploaded photo
|
||||
@@ -155,7 +118,7 @@ app.views.PublisherUploader = Backbone.View.extend({
|
||||
photo.fadeOut(400, function() {
|
||||
photo.remove();
|
||||
|
||||
if( self.publisher.$(".publisher_photo").length === 0 ) {
|
||||
if (self.publisher.$(".publisher_photo").length === 0) {
|
||||
// no more photos left...
|
||||
self.publisher.wrapperEl.removeClass("with_attachments");
|
||||
}
|
||||
@@ -164,9 +127,7 @@ app.views.PublisherUploader = Backbone.View.extend({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
||||
// @license-end
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
//= require ./publisher/poll_creator_view
|
||||
//= require ./publisher/services_view
|
||||
//= require ./publisher/uploader_view
|
||||
//= require jquery-textchange
|
||||
|
||||
app.views.Publisher = Backbone.View.extend({
|
||||
|
||||
@@ -22,7 +21,7 @@ app.views.Publisher = Backbone.View.extend({
|
||||
"focus textarea" : "open",
|
||||
"submit form" : "createStatusMessage",
|
||||
"click #submit" : "createStatusMessage",
|
||||
"textchange #status_message_text": "checkSubmitAvailability",
|
||||
"input #status_message_text": "checkSubmitAvailability",
|
||||
"click #locator" : "showLocation",
|
||||
"click #poll_creator" : "togglePollCreator",
|
||||
"click #hide_location" : "destroyLocation",
|
||||
@@ -52,10 +51,6 @@ app.views.Publisher = Backbone.View.extend({
|
||||
this.$(".question_mark").hide();
|
||||
}
|
||||
|
||||
// this has to be here, otherwise for some reason the callback for the
|
||||
// textchange event won't be called in Backbone...
|
||||
this.inputEl.bind("textchange", $.noop);
|
||||
|
||||
$("body").click(function(event) {
|
||||
var $target = $(event.target);
|
||||
if ($target.closest("#publisher").length === 0 && !$target.hasClass("dropdown-backdrop")) {
|
||||
@@ -113,6 +108,7 @@ app.views.Publisher = Backbone.View.extend({
|
||||
|
||||
this.viewUploader = new app.views.PublisherUploader({
|
||||
el: this.$("#file-upload"),
|
||||
dropZoneElementIds: ["publisher-textarea-wrapper"],
|
||||
publisher: this
|
||||
});
|
||||
this.viewUploader.on("change", this.checkSubmitAvailability, this);
|
||||
@@ -133,10 +129,6 @@ app.views.Publisher = Backbone.View.extend({
|
||||
if (photoAttachments.length > 0) {
|
||||
new app.views.Gallery({el: photoAttachments});
|
||||
}
|
||||
},
|
||||
|
||||
onChange: function() {
|
||||
self.inputEl.trigger("textchange");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -284,13 +276,13 @@ app.views.Publisher = Backbone.View.extend({
|
||||
getUploadedPhotos: function() {
|
||||
var photos = [];
|
||||
$("li.publisher_photo img").each(function() {
|
||||
var file = $(this).attr("src").substring("/uploads/images/".length);
|
||||
var photo = $(this);
|
||||
photos.push(
|
||||
{
|
||||
"sizes": {
|
||||
"small" : "/uploads/images/thumb_small_" + file,
|
||||
"medium" : "/uploads/images/thumb_medium_" + file,
|
||||
"large" : "/uploads/images/scaled_full_" + file
|
||||
"small": photo.data("small"),
|
||||
"medium": photo.attr("src"),
|
||||
"large": photo.data("scaled")
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -368,8 +360,7 @@ app.views.Publisher = Backbone.View.extend({
|
||||
|
||||
// clear text
|
||||
this.inputEl.val("");
|
||||
this.inputEl.trigger("keyup")
|
||||
.trigger("keydown");
|
||||
this.inputEl.trigger("input");
|
||||
autosize.update(this.inputEl);
|
||||
|
||||
// remove photos
|
||||
@@ -403,9 +394,6 @@ app.views.Publisher = Backbone.View.extend({
|
||||
// clear poll form
|
||||
this.viewPollCreator.clearInputs();
|
||||
|
||||
// force textchange plugin to update lastValue
|
||||
this.inputEl.data("lastValue", "");
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
@@ -445,7 +433,8 @@ app.views.Publisher = Backbone.View.extend({
|
||||
},
|
||||
|
||||
checkSubmitAvailability: function() {
|
||||
if( this._submittable() ) {
|
||||
if (this._submittable()) {
|
||||
this.open();
|
||||
this.setButtonsEnabled(true);
|
||||
} else {
|
||||
this.setButtonsEnabled(false);
|
||||
@@ -484,7 +473,7 @@ app.views.Publisher = Backbone.View.extend({
|
||||
},
|
||||
|
||||
_beforeUnload: function(e) {
|
||||
if(this._submittable() && this.inputEl.val() !== this.prefillText){
|
||||
if (this._submittable() && this.inputEl.val() !== this.prefillText) {
|
||||
var confirmationMessage = Diaspora.I18n.t("confirm_unload");
|
||||
(e || window.event).returnValue = confirmationMessage; //Gecko + IE
|
||||
return confirmationMessage; //Webkit, Safari, Chrome, etc.
|
||||
|
||||
@@ -7,7 +7,7 @@ app.views.StreamPost = app.views.Post.extend({
|
||||
subviews : {
|
||||
".feedback": "feedbackView",
|
||||
".comments": "commentStreamView",
|
||||
".likes": "likesInfoView",
|
||||
".likes-on-post": "likesInfoView",
|
||||
".reshares": "resharesInfoView",
|
||||
".post-controls": "postControlsView",
|
||||
".post-content": "postContentView",
|
||||
|
||||
121
app/assets/javascripts/helpers/post_photo_uploader.es6
Normal file
121
app/assets/javascripts/helpers/post_photo_uploader.es6
Normal file
@@ -0,0 +1,121 @@
|
||||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||
|
||||
Diaspora.PostPhotoUploader = class {
|
||||
/**
|
||||
* Initializes a new instance of PostPhotoUploader
|
||||
* This class handles uploading photos and provides client side scaling
|
||||
*/
|
||||
constructor(el, dropZoneElementIds, aspectIds) {
|
||||
this.element = el;
|
||||
this.dropZoneElementIds = dropZoneElementIds;
|
||||
this.sizeLimit = 4194304;
|
||||
this.aspectIds = aspectIds;
|
||||
|
||||
this.onProgress = null;
|
||||
this.onUploadStarted = null;
|
||||
this.onUploadCompleted = null;
|
||||
|
||||
/**
|
||||
* Shows a message using flash messages or alert for mobile.
|
||||
* @param {string} type - The type of the message, e.g. "error" or "success".
|
||||
* @param text - The text to display.
|
||||
*/
|
||||
this.showMessage = (type, text) => (app.flashMessages ? app.flashMessages[type](text) : alert(text));
|
||||
|
||||
/**
|
||||
* Returns true if the given parameter is a function
|
||||
* @param {param} - The object to check
|
||||
* @returns {boolean}
|
||||
*/
|
||||
this.func = param => (typeof param === "function");
|
||||
|
||||
this.initFineUploader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the fine uploader component
|
||||
*/
|
||||
initFineUploader() {
|
||||
this.fineUploader = new qq.FineUploaderBasic({
|
||||
element: this.element,
|
||||
button: this.element,
|
||||
text: {
|
||||
fileInputTitle: Diaspora.I18n.t("photo_uploader.upload_photos")
|
||||
},
|
||||
request: {
|
||||
endpoint: Routes.photos(),
|
||||
params: {
|
||||
/* eslint-disable camelcase */
|
||||
authenticity_token: $("meta[name='csrf-token']").attr("content"),
|
||||
photo: {
|
||||
pending: true,
|
||||
aspect_ids: this.aspectIds
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
}
|
||||
},
|
||||
paste: {
|
||||
targetElement: document.getElementById("status_message_text"),
|
||||
promptForName: true
|
||||
},
|
||||
validation: {
|
||||
acceptFiles: "image/png, image/jpeg, image/gif, image/webp",
|
||||
allowedExtensions: ["jpg", "jpeg", "png", "gif", "webp"],
|
||||
sizeLimit: (window.Promise && qq.supportedFeatures.scaling ? null : this.sizeLimit)
|
||||
},
|
||||
messages: {
|
||||
typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"),
|
||||
sizeError: Diaspora.I18n.t("photo_uploader.size_error"),
|
||||
emptyError: Diaspora.I18n.t("photo_uploader.empty")
|
||||
},
|
||||
callbacks: {
|
||||
onSubmit: (id, name) => this.onPictureSelected(id, name),
|
||||
onUpload: (id, name) => (this.func(this.onUploadStarted) && this.onUploadStarted(id, name)),
|
||||
onProgress: (id, fileName, loaded, total) =>
|
||||
(this.func(this.onProgress) && this.onProgress(id, fileName, Math.round(loaded / total * 100))),
|
||||
onComplete: (id, name, json) => (this.func(this.onUploadCompleted) && this.onUploadCompleted(id, name, json)),
|
||||
onError: (id, name, errorReason) => this.showMessage("error", errorReason)
|
||||
}
|
||||
});
|
||||
|
||||
if (this.dropZoneElementIds instanceof Array) {
|
||||
var dropZoneElements = this.dropZoneElementIds.map(id => document.getElementById(id)).filter(el => el);
|
||||
if (dropZoneElements.length > 0) {
|
||||
this.dragAndDropModule = new qq.DragAndDrop({
|
||||
dropZoneElements: dropZoneElements,
|
||||
callbacks: {
|
||||
processingDroppedFilesComplete: (files) => this.fineUploader.addFiles(files)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a picture from user's device has been selected.
|
||||
* Scales the images using Pica if the image exceeds the file size limit
|
||||
* @param {number} id - The current file's id.
|
||||
* @param {string} name - The current file's name.
|
||||
*/
|
||||
onPictureSelected(id, name) {
|
||||
// scale image because it's bigger than the size limit and the browser supports it
|
||||
if (this.fineUploader.getSize(id) > this.sizeLimit && window.Promise && qq.supportedFeatures.scaling) {
|
||||
this.fineUploader.scaleImage(id, {
|
||||
maxSize: 3072,
|
||||
customResizer: !qq.ios() && (i => window.pica().resize(i.sourceCanvas, i.targetCanvas))
|
||||
}).then(scaledImage => {
|
||||
this.fineUploader.addFiles({
|
||||
blob: scaledImage,
|
||||
name: name
|
||||
});
|
||||
});
|
||||
|
||||
// since we are adding the smaller scaled image afterwards, we return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// return true to upload the image without scaling
|
||||
return true;
|
||||
}
|
||||
};
|
||||
// @license-end
|
||||
253
app/assets/javascripts/helpers/profile_photo_uploader.es6
Normal file
253
app/assets/javascripts/helpers/profile_photo_uploader.es6
Normal file
@@ -0,0 +1,253 @@
|
||||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||
|
||||
Diaspora.ProfilePhotoUploader = class {
|
||||
/**
|
||||
* Initializes a new instance of ProfilePhotoUploader
|
||||
*/
|
||||
constructor() {
|
||||
// get several elements we will use a few times
|
||||
this.fileInput = document.querySelector("#file-upload");
|
||||
this.picture = document.querySelector("#profile_photo_upload .avatar");
|
||||
this.info = document.querySelector("#fileInfo");
|
||||
this.cropContainer = document.querySelector(".crop-container");
|
||||
this.spinner = document.querySelector("#file-upload-spinner");
|
||||
|
||||
/**
|
||||
* Creates a button
|
||||
* @param {string} icon - The entypo icon class.
|
||||
* @param {function} onClick - Is called when button has been clicked.
|
||||
*/
|
||||
this.createButton = (icon, onClick) =>
|
||||
($(`<button class="btn btn-default" type="button"><i class="entypo-${icon}"></i></button>`)
|
||||
.on("click", onClick));
|
||||
|
||||
/**
|
||||
* Shows a message using flash messages or alert for mobile.
|
||||
* @param {string} type - The type of the message, e.g. "error" or "success".
|
||||
* @param text - The text to display.
|
||||
*/
|
||||
this.showMessage = (type, text) => (app.flashMessages ? app.flashMessages[type](text) : alert(text));
|
||||
|
||||
this.initFineUploader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the fine uploader component
|
||||
*/
|
||||
initFineUploader() {
|
||||
this.fineUploader = new qq.FineUploaderBasic({
|
||||
element: this.fileInput,
|
||||
validation: {
|
||||
acceptFiles: "image/png, image/jpeg",
|
||||
allowedExtensions: ["jpg", "jpeg", "png"]
|
||||
},
|
||||
request: {
|
||||
endpoint: Routes.photos(),
|
||||
params: {
|
||||
/* eslint-disable camelcase */
|
||||
authenticity_token: $("meta[name='csrf-token']").attr("content"),
|
||||
/* eslint-enable camelcase */
|
||||
photo: {"pending": true, "aspect_ids": "all", "set_profile_photo": true}
|
||||
}
|
||||
},
|
||||
button: this.fileInput,
|
||||
autoUpload: false,
|
||||
|
||||
messages: {
|
||||
typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"),
|
||||
sizeError: Diaspora.I18n.t("photo_uploader.size_error"),
|
||||
emptyError: Diaspora.I18n.t("photo_uploader.empty")
|
||||
},
|
||||
|
||||
callbacks: {
|
||||
onProgress: (id, fileName, loaded, total) => {
|
||||
(this.info.innerText = `${fileName} ${Math.round(loaded / total * 100)}%`);
|
||||
},
|
||||
onSubmit: (id, name) => this.onPictureSelected(id, name),
|
||||
onComplete: (id, name, responseJSON) => this.onUploadCompleted(id, name, responseJSON),
|
||||
onError: (id, name) => this.showMessage("error", Diaspora.I18n.t("photo_uploader.error", {file: name}))
|
||||
},
|
||||
|
||||
text: {
|
||||
fileInputTitle: ""
|
||||
},
|
||||
|
||||
scaling: {
|
||||
sendOriginal: false,
|
||||
|
||||
sizes: [
|
||||
{maxSize: 1600}
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a picture from user's device has been selected.
|
||||
* @param {number} id - The current file's id.
|
||||
* @param {string} name - The current file's name.
|
||||
*/
|
||||
onPictureSelected(id, name) {
|
||||
this.setLoading(true);
|
||||
this.fileName = name;
|
||||
const file = this.fileInput.querySelector("input").files[0];
|
||||
|
||||
// ensure browser's file reader support
|
||||
if (FileReader && file) {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = () => this.initCropper(fileReader.result);
|
||||
fileReader.readAsDataURL(file);
|
||||
} else {
|
||||
this.setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the cropper and all controls.
|
||||
* @param {object|string} imageData - The base64 image data
|
||||
*/
|
||||
initCropper(imageData) {
|
||||
// cache the current picture source if the user cancels
|
||||
this.previousPicture = this.picture.getAttribute("src");
|
||||
|
||||
this.mimeType = imageData.split(";base64")[0].substring(5);
|
||||
|
||||
this.picture.onload = () => {
|
||||
// set the preferred size style of the cropper based on picture orientation
|
||||
const isPortrait = this.picture.naturalHeight > this.picture.naturalWidth;
|
||||
this.picture.setAttribute("style", (isPortrait ? "max-height:600px;max-width:none;" : "max-width:600px;"));
|
||||
this.buildControls();
|
||||
|
||||
this.setLoading(false);
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
this.cropper = new Cropper(this.picture, {
|
||||
aspectRatio: 1,
|
||||
zoomable: false,
|
||||
autoCropArea: 1,
|
||||
preview: ".preview"
|
||||
});
|
||||
};
|
||||
this.picture.setAttribute("src", imageData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates image manipulation controls and previews.
|
||||
*/
|
||||
buildControls() {
|
||||
this.controls = {
|
||||
rotateLeft: this.createButton("ccw", () => this.cropper.rotate(-45)),
|
||||
rotateRight: this.createButton("cw", () => this.cropper.rotate(45)),
|
||||
reset: this.createButton("cycle", () => this.cropper.reset()),
|
||||
accept: this.createButton("check", () => this.cropImage()),
|
||||
cancel: this.createButton("trash", () => this.cancel())
|
||||
};
|
||||
|
||||
this.controlRow = $("<div class='controls'>").appendTo(this.cropContainer);
|
||||
|
||||
// rotation buttons on the left
|
||||
this.controlRow.append($("<div class='btn-group buttons-left' role='group'>").append([
|
||||
this.controls.rotateLeft,
|
||||
this.controls.rotateRight
|
||||
]));
|
||||
|
||||
// preview images in the middle
|
||||
this.controlRow.append("<div class='preview'>");
|
||||
|
||||
// main buttons on the right
|
||||
this.controlRow.append($("<div class='btn-group buttons-right' role='group'>").append([
|
||||
this.controls.reset,
|
||||
this.controls.cancel,
|
||||
this.controls.accept
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user clicked accept button. Sets file data and triggers file upload.
|
||||
*/
|
||||
cropImage() {
|
||||
const canvas = this.cropper.getCroppedCanvas();
|
||||
|
||||
// replace the stored file with the new canvas
|
||||
this.fineUploader.clearStoredFiles();
|
||||
this.fineUploader.addFiles([{
|
||||
canvas: canvas,
|
||||
name: this.fileName,
|
||||
quality: 100,
|
||||
type: this.mimeType
|
||||
}]);
|
||||
|
||||
// reset all controls
|
||||
this.cancel();
|
||||
this.picture.setAttribute("src", canvas.toDataURL(this.mimeType));
|
||||
|
||||
// finally start uploading
|
||||
this.setLoading(true);
|
||||
this.fineUploader.uploadStoredFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called after the file upload has been completed and the profile photo changed.
|
||||
* @param {number} id - The current file's id.
|
||||
* @param {string} fileName - The current file's name.
|
||||
* @param {object} responseJSON - The server's json response.
|
||||
*/
|
||||
onUploadCompleted(id, fileName, responseJSON) {
|
||||
this.setLoading(false);
|
||||
this.fileInput.classList.remove("hidden");
|
||||
|
||||
if (responseJSON.data !== undefined) {
|
||||
/* flash message prompt */
|
||||
this.showMessage("success", Diaspora.I18n.t("photo_uploader.looking_good"));
|
||||
|
||||
this.info.innerText = Diaspora.I18n.t("photo_uploader.completed", {"file": fileName});
|
||||
|
||||
const photoId = responseJSON.data.photo.id;
|
||||
const url = responseJSON.data.photo.unprocessed_image.url;
|
||||
const oldPhoto = $("#photo_id");
|
||||
if (oldPhoto.length === 0) {
|
||||
$("#update_profile_form")
|
||||
.prepend(`<input type="hidden" value="${photoId}" id="photo_id" name="photo_id"/>`);
|
||||
} else {
|
||||
oldPhoto.val(photoId);
|
||||
}
|
||||
|
||||
this.picture.setAttribute("src", url);
|
||||
$(`.avatar[alt="${gon.user.diaspora_id}"]`).attr("src", url);
|
||||
} else {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles loading state by hiding or showing several elements
|
||||
* @param {boolean} loading - True if loading state should be enabled.
|
||||
*/
|
||||
setLoading(loading) {
|
||||
if (loading) {
|
||||
this.fileInput.classList.add("hidden");
|
||||
this.picture.classList.add("hidden");
|
||||
this.spinner.classList.remove("hidden");
|
||||
} else {
|
||||
this.picture.classList.remove("hidden");
|
||||
this.spinner.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the cropper and resets all elements to initial state.
|
||||
*/
|
||||
cancel() {
|
||||
this.cropper.destroy();
|
||||
this.picture.onload = null;
|
||||
this.picture.setAttribute("style", "");
|
||||
this.picture.setAttribute("src", this.previousPicture);
|
||||
this.controlRow.remove();
|
||||
this.fileInput.classList.remove("hidden");
|
||||
this.info.innerText = "";
|
||||
|
||||
this.mimeType = null;
|
||||
this.name = null;
|
||||
}
|
||||
};
|
||||
// @license-end
|
||||
@@ -1,9 +1,9 @@
|
||||
//= require jquery3
|
||||
//= require handlebars.runtime
|
||||
//= require main
|
||||
//= require fine-uploader/fine-uploader.core
|
||||
//= require fine-uploader/fine-uploader/fine-uploader
|
||||
//= require mobile/mobile
|
||||
//= require jquery.autoSuggest.custom
|
||||
//= require contact-list
|
||||
//= require sinon
|
||||
//= require jasmine-ajax
|
||||
//= require jasmine-ajax/lib/mock-ajax
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
//= require emojione
|
||||
//= require favico.js/favico
|
||||
|
||||
//= require jquery.ui/ui/resizable
|
||||
//= require jquery.ui/ui/draggable
|
||||
//= require jquery.slimscroll/jquery.slimscroll
|
||||
//= require jquery-colorbox
|
||||
//= require jquery-fullscreen-plugin
|
||||
|
||||
//= require diaspora_jsxc
|
||||
|
||||
// initialize jsxc xmpp client
|
||||
$(document).ready(function() {
|
||||
if (app.currentUser.authenticated()) {
|
||||
$.post("/user/auth_token", null, function(data) {
|
||||
if (jsxc && data['token']) {
|
||||
var jid = app.currentUser.get('diaspora_id');
|
||||
jsxc.init({
|
||||
root: '/assets/diaspora_jsxc',
|
||||
rosterAppend: 'body',
|
||||
otr: {
|
||||
debug: true,
|
||||
SEND_WHITESPACE_TAG: true,
|
||||
WHITESPACE_START_AKE: true
|
||||
},
|
||||
onlineHelp: "/help/chat",
|
||||
priority: {
|
||||
online: 1,
|
||||
chat: 1
|
||||
},
|
||||
displayRosterMinimized: function() {
|
||||
return false;
|
||||
},
|
||||
xmpp: {
|
||||
url: $('script#jsxc').data('endpoint'),
|
||||
username: jid.replace(/@.*?$/g, ''),
|
||||
domain: jid.replace(/^.*?@/g, ''),
|
||||
jid: jid,
|
||||
password: data.token,
|
||||
resource: 'diaspora-jsxc',
|
||||
overwrite: true,
|
||||
onlogin: true
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('No token found! Authenticated!?');
|
||||
}
|
||||
}, 'json');
|
||||
}
|
||||
});
|
||||
@@ -4,23 +4,23 @@
|
||||
*/
|
||||
//= require js_image_paths
|
||||
//= require js-routes
|
||||
//= require underscore
|
||||
//= require backbone
|
||||
//= require autosize
|
||||
//= require underscore/underscore-umd
|
||||
//= require backbone/backbone
|
||||
//= require autosize/dist/autosize
|
||||
//= require charcount
|
||||
//= require jquery-placeholder
|
||||
//= require jquery-placeholder/jquery.placeholder
|
||||
//= require jquery.timeago
|
||||
//= require jquery.ui/ui/core
|
||||
//= require jquery.ui/ui/widget
|
||||
//= require jquery.ui/ui/mouse
|
||||
//= require jquery.ui/ui/sortable
|
||||
//= require jquery-ui/core
|
||||
//= require jquery-ui/widget
|
||||
//= require jquery-ui/widgets/mouse
|
||||
//= require jquery-ui/widgets/sortable
|
||||
//= require keycodes
|
||||
//= require jquery.autoSuggest.custom
|
||||
//= require fine-uploader/fine-uploader.core
|
||||
//= require fine-uploader/fine-uploader/fine-uploader
|
||||
//= require handlebars.runtime
|
||||
//= require_tree ../templates
|
||||
//= require posix-bracket-expressions
|
||||
//= require markdown-it
|
||||
//= require markdown-it/dist/markdown-it
|
||||
//= require markdown-it-diaspora-mention
|
||||
//= require markdown-it-for-inline
|
||||
//= require markdown-it-footnote
|
||||
@@ -40,11 +40,13 @@
|
||||
//= require bootstrap
|
||||
//= require osmlocator
|
||||
//= require bootstrap-switch
|
||||
//= require blueimp-gallery
|
||||
//= require blueimp-gallery/blueimp-gallery-indicator
|
||||
//= require blueimp-gallery/js/blueimp-gallery
|
||||
//= require blueimp-gallery/js/blueimp-gallery-indicator
|
||||
//= require leaflet
|
||||
//= require api/authorization_page
|
||||
//= require bootstrap-markdown/bootstrap-markdown
|
||||
//= require bootstrap-markdown/js/bootstrap-markdown
|
||||
//= require helpers/markdown_editor
|
||||
//= require helpers/protocol_handler
|
||||
//= require jquery.are-you-sure
|
||||
//= require cropperjs/dist/cropper.js
|
||||
//= require pica/dist/pica
|
||||
|
||||
@@ -4,22 +4,22 @@
|
||||
*/
|
||||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
|
||||
|
||||
//= require jquery-textchange
|
||||
//= require charcount
|
||||
//= require js-routes
|
||||
//= require autosize
|
||||
//= require autosize/dist/autosize
|
||||
//= require keycodes
|
||||
//= require jquery.autoSuggest.custom
|
||||
//= require fine-uploader/fine-uploader.core
|
||||
//= require fine-uploader/fine-uploader/fine-uploader
|
||||
//= require jquery.timeago
|
||||
//= require underscore
|
||||
//= require underscore/underscore-umd
|
||||
//= require bootstrap
|
||||
//= require pica/dist/pica
|
||||
//= require diaspora
|
||||
//= require helpers/i18n
|
||||
//= require helpers/profile_photo_uploader
|
||||
//= require helpers/tags_autocomplete
|
||||
//= require bootstrap-markdown/bootstrap-markdown
|
||||
//= require bootstrap-markdown/js/bootstrap-markdown
|
||||
//= require helpers/markdown_editor
|
||||
//= require helpers/post_photo_uploader
|
||||
//= require widgets/timeago
|
||||
//= require mobile/mobile_application
|
||||
//= require mobile/mobile_file_uploader
|
||||
@@ -32,4 +32,5 @@
|
||||
//= require mobile/mobile_conversations
|
||||
//= require mobile/mobile_post_actions
|
||||
//= require mobile/mobile_drawer
|
||||
//= require mobile/mobile_profile_photo_uploader
|
||||
// @license-end
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
|
||||
$.post(form.attr("action") + "?format=mobile", form.serialize(), function(data){
|
||||
Diaspora.Mobile.Comments.updateStream(form, data);
|
||||
// Register new comments
|
||||
$(".stream").trigger("comments.loaded");
|
||||
}, "html").fail(function(response) {
|
||||
Diaspora.Mobile.Alert.handleAjaxError(response);
|
||||
Diaspora.Mobile.Comments.resetCommentBox(form);
|
||||
@@ -107,10 +109,12 @@
|
||||
url: toggleReactionsLink.attr("href"),
|
||||
success: function (data) {
|
||||
toggleReactionsLink.addClass("active").removeClass("loading");
|
||||
$(data).insertAfter(bottomBar.children(".show-comments").first());
|
||||
$(data).insertAfter(bottomBar.children(".post-actions-container").first());
|
||||
self.showCommentBox(commentActionLink);
|
||||
bottomBarContainer.getCommentsContainer().find("time.timeago").timeago();
|
||||
bottomBarContainer.activate();
|
||||
// Inform the comment action for new comments
|
||||
$(".stream").trigger("comments.loaded");
|
||||
},
|
||||
error: function(){
|
||||
bottomBarContainer.deactivate();
|
||||
|
||||
@@ -2,97 +2,74 @@
|
||||
//= require js_image_paths
|
||||
|
||||
function createUploader(){
|
||||
var aspectIds = gon.preloads.aspect_ids;
|
||||
var fileInfo = $("#fileInfo-publisher");
|
||||
|
||||
new qq.FineUploaderBasic({
|
||||
element: document.getElementById("file-upload-publisher"),
|
||||
request: {
|
||||
endpoint: Routes.photos(),
|
||||
params: {
|
||||
/* eslint-disable camelcase */
|
||||
authenticity_token: $("meta[name='csrf-token']").attr("content"),
|
||||
photo: {
|
||||
aspect_ids: aspectIds,
|
||||
/* eslint-enable camelcase */
|
||||
pending: true
|
||||
}
|
||||
}
|
||||
},
|
||||
validation: {
|
||||
allowedExtensions: ["jpg", "jpeg", "png", "gif"],
|
||||
sizeLimit: 4194304
|
||||
},
|
||||
button: document.getElementById("file-upload-publisher"),
|
||||
text: {
|
||||
fileInputTitle: Diaspora.I18n.t("photo_uploader.upload_photos")
|
||||
},
|
||||
// Initialize the PostPhotoUploader and subscribe its events
|
||||
this.uploader = new Diaspora.PostPhotoUploader(document.getElementById("file-upload-publisher"),
|
||||
["publisher-textarea-wrapper", "status_message_text", "file-upload-publisher"]);
|
||||
|
||||
callbacks: {
|
||||
onProgress: function(id, fileName, loaded, total) {
|
||||
var progress = Math.round(loaded / total * 100);
|
||||
$("#fileInfo-publisher").text(fileName + " " + progress + "%");
|
||||
},
|
||||
onSubmit: function() {
|
||||
$("#publisher-textarea-wrapper").addClass("with_attachments");
|
||||
$("#photodropzone").append(
|
||||
"<li class='publisher_photo loading' style='position:relative;'>" +
|
||||
"<img alt='Ajax-loader2' src='" + ImagePaths.get("ajax-loader2.gif") + "' />" +
|
||||
"</li>"
|
||||
);
|
||||
},
|
||||
onComplete: function(_id, fileName, responseJSON) {
|
||||
if (responseJSON.data === undefined) {
|
||||
return;
|
||||
}
|
||||
this.uploader.onUploadStarted = _.bind(uploadStartedHandler, this);
|
||||
this.uploader.onProgress = _.bind(progressHandler, this);
|
||||
this.uploader.onUploadCompleted = _.bind(uploadCompletedHandler, this);
|
||||
|
||||
$("#fileInfo-publisher").text(Diaspora.I18n.t("photo_uploader.completed", {"file": fileName}));
|
||||
var id = responseJSON.data.photo.id,
|
||||
url = responseJSON.data.photo.unprocessed_image.url,
|
||||
currentPlaceholder = $("li.loading").first();
|
||||
function progressHandler(fileName, progress) {
|
||||
fileInfo.text(fileName + " " + progress + "%");
|
||||
}
|
||||
|
||||
$("#publisher-textarea-wrapper").addClass("with_attachments");
|
||||
$("#new_status_message").append("<input type='hidden' value='" + id + "' name='photos[]' />");
|
||||
function uploadStartedHandler() {
|
||||
$("#publisher-textarea-wrapper").addClass("with_attachments");
|
||||
$("#photodropzone").append(
|
||||
"<li class='publisher_photo loading' style='position:relative;'>" +
|
||||
"<img alt='Ajax-loader2' src='" + ImagePaths.get("ajax-loader2.gif") + "' />" +
|
||||
"</li>"
|
||||
);
|
||||
}
|
||||
|
||||
// replace image placeholders
|
||||
var img = currentPlaceholder.find("img");
|
||||
img.attr("src", url);
|
||||
img.attr("data-id", id);
|
||||
currentPlaceholder.removeClass("loading");
|
||||
currentPlaceholder.append("<div class='x'>X</div>" +
|
||||
"<div class='circle'></div>");
|
||||
function uploadCompletedHandler(_id, fileName, responseJSON) {
|
||||
if (responseJSON.data === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var publisher = $("#publisher");
|
||||
fileInfo.text(Diaspora.I18n.t("photo_uploader.completed", {"file": fileName}));
|
||||
var id = responseJSON.data.photo.id,
|
||||
image = responseJSON.data.photo.unprocessed_image,
|
||||
currentPlaceholder = $("li.loading").first();
|
||||
|
||||
publisher.find("input[type='submit']").removeAttr("disabled");
|
||||
$("#publisher-textarea-wrapper").addClass("with_attachments");
|
||||
$("#new_status_message").append("<input type='hidden' value='" + id + "' name='photos[]' />");
|
||||
|
||||
$(".x").bind("click", function() {
|
||||
var photo = $(this).closest(".publisher_photo");
|
||||
photo.addClass("dim");
|
||||
$.ajax({
|
||||
url: "/photos/" + photo.children("img").attr("data-id"),
|
||||
dataType: "json",
|
||||
type: "DELETE",
|
||||
success: function() {
|
||||
photo.fadeOut(400, function() {
|
||||
photo.remove();
|
||||
if ($(".publisher_photo").length === 0) {
|
||||
$("#publisher-textarea-wrapper").removeClass("with_attachments");
|
||||
}
|
||||
});
|
||||
// replace image placeholders
|
||||
var img = currentPlaceholder.find("img");
|
||||
img.attr("src", image.thumb_medium.url);
|
||||
img.attr("data-small", image.thumb_small.url);
|
||||
img.attr("data-scaled", image.scaled_full.url);
|
||||
img.attr("data-id", id);
|
||||
currentPlaceholder.removeClass("loading");
|
||||
currentPlaceholder.append("<div class='x'>X</div>" +
|
||||
"<div class='circle'></div>");
|
||||
|
||||
var publisher = $("#publisher");
|
||||
|
||||
publisher.find("input[type='submit']").removeAttr("disabled");
|
||||
|
||||
$(".x").bind("click", function() {
|
||||
var photo = $(this).closest(".publisher_photo");
|
||||
photo.addClass("dim");
|
||||
$.ajax({
|
||||
url: "/photos/" + photo.children("img").attr("data-id"),
|
||||
dataType: "json",
|
||||
type: "DELETE",
|
||||
success: function() {
|
||||
photo.fadeOut(400, function() {
|
||||
photo.remove();
|
||||
if ($(".publisher_photo").length === 0) {
|
||||
$("#publisher-textarea-wrapper").removeClass("with_attachments");
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
onError: function(id, name, errorReason) {
|
||||
alert(errorReason);
|
||||
}
|
||||
},
|
||||
messages: {
|
||||
typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"),
|
||||
sizeError: Diaspora.I18n.t("photo_uploader.size_error"),
|
||||
emptyError: Diaspora.I18n.t("photo_uploader.empty")
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
window.addEventListener("load", function() {
|
||||
createUploader();
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
initialize: function() {
|
||||
$(".like-action", ".stream").bind("tap click", this.onLike);
|
||||
$(".reshare-action", ".stream").bind("tap click", this.onReshare);
|
||||
// Add handler to newly loaded comments
|
||||
var self = this;
|
||||
$(".stream").bind("comments.loaded", function() {
|
||||
$(".like-action", ".stream").bind("tap click", self.onLike);
|
||||
});
|
||||
},
|
||||
|
||||
showLoader: function(link) {
|
||||
@@ -75,8 +80,8 @@
|
||||
|
||||
onLike: function(evt){
|
||||
evt.preventDefault();
|
||||
var link = $(evt.target).closest(".like-action"),
|
||||
likeCounter = $(evt.target).closest(".stream-element").find(".like-count");
|
||||
var link = $(evt.target).closest(".like-action").first(),
|
||||
likeCounter = $(evt.target).find(".like-count").first();
|
||||
|
||||
if(!link.hasClass("loading") && link.hasClass("inactive")) {
|
||||
Diaspora.Mobile.PostActions.like(likeCounter, link);
|
||||
|
||||
@@ -11,7 +11,8 @@ Diaspora.ProfilePhotoUploader.prototype = {
|
||||
new qq.FineUploaderBasic({
|
||||
element: document.getElementById("file-upload"),
|
||||
validation: {
|
||||
allowedExtensions: ["jpg", "jpeg", "png"],
|
||||
acceptFiles: "image/png, image/jpeg, image/gif, image/webp",
|
||||
allowedExtensions: ["png", "jpg", "jpeg", "gif", "webp"],
|
||||
sizeLimit: 4194304
|
||||
},
|
||||
request: {
|
||||
@@ -48,4 +48,74 @@ $(document).ready(function(){
|
||||
});
|
||||
|
||||
new Diaspora.MarkdownEditor("#status_message_text");
|
||||
|
||||
$(".dropdown-menu > li").bind("tap click", function(evt) {
|
||||
let target = $(evt.target).closest("li");
|
||||
|
||||
// visually toggle the aspect selection
|
||||
if (target.is(".radio")) {
|
||||
_toggleRadio(target);
|
||||
} else if (target.is(".aspect-selector")) {
|
||||
// don't close the dropdown
|
||||
evt.stopPropagation();
|
||||
_toggleCheckbox(target);
|
||||
}
|
||||
|
||||
_updateSelectedAspectIds();
|
||||
_updateButton();
|
||||
|
||||
// update the globe or lock icon
|
||||
let icon = $("#visibility-icon");
|
||||
if (target.find(".text").text().trim() === Diaspora.I18n.t("stream.public")) {
|
||||
icon.removeClass("entypo-lock");
|
||||
icon.addClass("entypo-globe");
|
||||
} else {
|
||||
icon.removeClass("entypo-globe");
|
||||
icon.addClass("entypo-lock");
|
||||
}
|
||||
});
|
||||
|
||||
function _toggleRadio(target) {
|
||||
$(".dropdown-menu > li").removeClass("selected");
|
||||
target.toggleClass("selected");
|
||||
}
|
||||
|
||||
function _toggleCheckbox(target) {
|
||||
$(".dropdown-menu > li.radio").removeClass("selected");
|
||||
target.toggleClass("selected");
|
||||
}
|
||||
|
||||
// take care of the form fields that will indicate the selected aspects
|
||||
function _updateSelectedAspectIds() {
|
||||
let form = $("#new_status_message");
|
||||
|
||||
// remove previous selection
|
||||
form.find('input[name="aspect_ids[]"]').remove();
|
||||
|
||||
// create fields for current selection
|
||||
form.find(".dropdown-menu > li.selected").each(function() {
|
||||
let uid = _.uniqueId("aspect_ids_");
|
||||
let id = $(this).data("aspect_id");
|
||||
form.append('<input id="' + uid + '" name="aspect_ids[]" type="hidden" value="' + id + '">');
|
||||
});
|
||||
}
|
||||
|
||||
// change class and text of the dropdown button
|
||||
function _updateButton() {
|
||||
let button = $(".btn.dropdown-toggle"),
|
||||
selectedAspects = $(".dropdown-menu > li.selected").length,
|
||||
buttonText;
|
||||
|
||||
switch (selectedAspects) {
|
||||
case 0:
|
||||
buttonText = Diaspora.I18n.t("aspect_dropdown.select_aspects");
|
||||
break;
|
||||
case 1:
|
||||
buttonText = $(".dropdown-menu > li.selected .text").first().text();
|
||||
break;
|
||||
default:
|
||||
buttonText = Diaspora.I18n.t("aspect_dropdown.toggle", {count: selectedAspects.toString()});
|
||||
}
|
||||
button.find(".text").text(buttonText);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@import 'utatti-perfect-scrollbar/css/perfect-scrollbar';
|
||||
@import 'perfect-scrollbar/css/perfect-scrollbar';
|
||||
|
||||
@import 'color-variables';
|
||||
@import 'bootstrap-complete';
|
||||
@@ -24,6 +24,9 @@
|
||||
// font overrides
|
||||
@import 'typography';
|
||||
|
||||
// New design, can be used adding the .modern-design class to a <div> around the page which is ported
|
||||
@import 'modern-design';
|
||||
|
||||
// layout
|
||||
@import 'sidebar';
|
||||
|
||||
@@ -40,6 +43,7 @@
|
||||
|
||||
// profile and settings pages
|
||||
@import 'settings';
|
||||
@import 'cropperjs/dist/cropper';
|
||||
|
||||
// new SPV
|
||||
@import 'header';
|
||||
@@ -82,8 +86,6 @@
|
||||
@import 'stream';
|
||||
@import 'stream_element';
|
||||
@import 'comments';
|
||||
@import 'diaspora_jsxc';
|
||||
@import 'chat';
|
||||
@import 'markdown-content';
|
||||
@import 'oembed';
|
||||
@import 'media-embed';
|
||||
@@ -95,14 +97,14 @@
|
||||
|
||||
// code
|
||||
@import 'code';
|
||||
@import 'highlightjs/github';
|
||||
@import 'highlightjs/styles/github';
|
||||
|
||||
// statistics
|
||||
@import 'statistics';
|
||||
|
||||
// gallery
|
||||
@import 'blueimp-gallery';
|
||||
@import 'blueimp-gallery/blueimp-gallery-indicator';
|
||||
@import 'blueimp-gallery/css/blueimp-gallery';
|
||||
@import 'blueimp-gallery/css/blueimp-gallery-indicator';
|
||||
@import 'gallery';
|
||||
|
||||
// settings
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* Mixin file for sass. Here is where we define our variables and
|
||||
browser compatability functions used in all scss/sass files */
|
||||
|
||||
/* Transision defaults */
|
||||
// Transition defaults
|
||||
$speed: 0.1s;
|
||||
$easing: linear;
|
||||
|
||||
@@ -138,3 +138,21 @@ $default-border-radius: 3px;
|
||||
.glyphicon-ok { display: none;}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin aspect-dropdown-link($anchor-size) {
|
||||
$link-text-color: #333;
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
padding-left: 10px;
|
||||
|
||||
.text {
|
||||
color: $link-text-color;
|
||||
font-size: $anchor-size;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $background-grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
@import 'mixins';
|
||||
|
||||
.aspect-dropdown {
|
||||
|
||||
li {
|
||||
@include selectable-list;
|
||||
@include aspect-dropdown-link(1em);
|
||||
|
||||
.status_indicator {
|
||||
width: 19px;
|
||||
height: 14px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
a {
|
||||
.text {
|
||||
color: #333333;
|
||||
}
|
||||
&:hover {
|
||||
background: $background-grey;
|
||||
}
|
||||
cursor: pointer;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aspect-membership {
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
body > .container-fluid.chat-roster-shown {
|
||||
padding-right: 224px;
|
||||
#back-to-top { right: 244px; }
|
||||
}
|
||||
body > .container-fluid.chat-roster-hidden {
|
||||
#back-to-top { right: 54px; }
|
||||
}
|
||||
|
||||
// This element is instanciated by JSXC. Does not have to follow naming conventions
|
||||
// scss-lint:disable IdSelector, SelectorFormat
|
||||
#jsxc_roster {
|
||||
top: $navbar-height;
|
||||
}
|
||||
// scss-lint:enable IdSelector, SelectorFormat
|
||||
@@ -5,3 +5,12 @@ pre {
|
||||
white-space: pre;
|
||||
code { white-space: pre; }
|
||||
}
|
||||
|
||||
// For inline-code inside links, let's force the color to the default link
|
||||
// color to make them be recognizable as links.
|
||||
p a {
|
||||
code,
|
||||
pre {
|
||||
color: $link-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ body {
|
||||
|
||||
pre code { border: 0; }
|
||||
|
||||
@import 'highlightjs/darcula';
|
||||
@import 'highlightjs/styles/darcula';
|
||||
|
||||
#single-post-content .head {
|
||||
.author-name { color: lighten($gray-lighter, 27%); }
|
||||
|
||||
@@ -24,13 +24,9 @@
|
||||
|
||||
.comments > .comment,
|
||||
.comment.new-comment-form-wrapper {
|
||||
.avatar {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
}
|
||||
margin: 0;
|
||||
border-top: 1px dotted $border-grey;
|
||||
padding: 10px 0;
|
||||
padding: 10px 0 0;
|
||||
|
||||
.info {
|
||||
margin-top: 5px;
|
||||
@@ -57,8 +53,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.comment.new-comment-form-wrapper { padding-bottom: 0; }
|
||||
|
||||
.submit-button {
|
||||
margin-top: 10px;
|
||||
input {
|
||||
@@ -84,6 +78,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
.likes-on-comment {
|
||||
&.likes {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.media {
|
||||
margin: 0 0 2px;
|
||||
|
||||
&:not(.display-avatars) .entypo-heart {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.expand-likes {
|
||||
display: inline-block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.new-comment {
|
||||
&:not(.open) .submit-button,
|
||||
&:not(.open) .md-header {
|
||||
|
||||
@@ -37,9 +37,6 @@
|
||||
text-decoration: none;
|
||||
margin-right: 25px;
|
||||
}
|
||||
#chat_privilege_toggle > .enabled {
|
||||
color: $text-color-active;
|
||||
}
|
||||
.contacts-header-icon {
|
||||
font-size: 24.5px;
|
||||
line-height: 40px;
|
||||
|
||||
@@ -14,6 +14,14 @@ $margin: 15px;
|
||||
top: $margin;
|
||||
}
|
||||
|
||||
.slide-error {
|
||||
background-image: image-url("blueimp-gallery/img/error.svg");
|
||||
}
|
||||
|
||||
.slide-loading {
|
||||
background-image: image-url("blueimp-gallery/img/loading.gif");
|
||||
}
|
||||
|
||||
.too-tall {
|
||||
margin-bottom: $margin * 2 + $thumbnail-size;
|
||||
max-height: none;
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
#hello-there {
|
||||
#profile_photo_upload .avatar {
|
||||
max-height: 200px;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.well .avatar {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
@@ -91,18 +91,6 @@ ul#help_nav {
|
||||
border-radius: 0px 0px 4px 4px;
|
||||
background-color: white;
|
||||
padding: 10px 20px;
|
||||
|
||||
div.help-chat-icons{
|
||||
text-align: center;
|
||||
font-size: 50px;
|
||||
line-height: 70px;
|
||||
|
||||
[class^="entypo-"], [class*="entypo-"] {
|
||||
color: $text-color-pale;
|
||||
|
||||
&.entypo-chat { color: $text-color-active; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
.bottom-bar {
|
||||
border-radius: 0 0 5px 5px;
|
||||
z-index: 3;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding: 8px 10px 10px;
|
||||
background: $background-grey;
|
||||
margin-top: 10px;
|
||||
@@ -10,6 +7,17 @@
|
||||
min-height: 22px;
|
||||
overflow: hidden;
|
||||
|
||||
&,
|
||||
.comment-stats {
|
||||
border-bottom-left-radius: $border-radius-small;
|
||||
border-bottom-right-radius: $border-radius-small;
|
||||
}
|
||||
|
||||
.post-actions-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
> a,
|
||||
.show-comments,
|
||||
.show-comments > [class^="entypo"] {
|
||||
@@ -37,8 +45,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.post-stats {
|
||||
float: right;
|
||||
%stats {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -46,9 +53,9 @@
|
||||
color: $text-color;
|
||||
font-family: $font-family-base;
|
||||
font-size: $font-size-base;
|
||||
line-height: 22px;
|
||||
line-height: 24px;
|
||||
margin-left: 5px;
|
||||
vertical-align: top;
|
||||
vertical-align: text-bottom;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@@ -67,17 +74,36 @@
|
||||
}
|
||||
|
||||
.entypo-reshare.active { color: $blue; }
|
||||
|
||||
.entypo-heart.active { color: $red; }
|
||||
}
|
||||
|
||||
.post-action {
|
||||
.post-stats {
|
||||
@extend %stats;
|
||||
}
|
||||
|
||||
.comment-stats {
|
||||
@extend %stats;
|
||||
background: $background-grey;
|
||||
border-top: 1px solid $border-grey;
|
||||
flex-direction: row-reverse;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
%action {
|
||||
display: flex;
|
||||
margin: 0 7px;
|
||||
|
||||
.disabled { color: $medium-gray; }
|
||||
}
|
||||
|
||||
.post-action {
|
||||
@extend %action;
|
||||
}
|
||||
|
||||
.comment-action {
|
||||
@extend %action;
|
||||
}
|
||||
|
||||
.add-comment-switcher { padding-top: 10px; }
|
||||
|
||||
&.inactive {
|
||||
@@ -91,16 +117,19 @@
|
||||
|
||||
.stream-element .comments {
|
||||
margin: 0;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
|
||||
.content { padding: 0; }
|
||||
|
||||
.comment {
|
||||
border-top: 1px solid $border-medium-grey;
|
||||
padding: 10px 0 0;
|
||||
background-color: $framed-background;
|
||||
border: 1px solid $border-medium-grey;
|
||||
border-radius: 5px;
|
||||
margin-top: 10px;
|
||||
|
||||
&:first-child { padding-top: 20px; }
|
||||
.media {
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +145,9 @@ $mobile-navbar-height: 46px;
|
||||
bottom: 0;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
li {
|
||||
font-size: 1.8rem;
|
||||
@@ -195,6 +198,21 @@ $mobile-navbar-height: 46px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.info-links {
|
||||
li {
|
||||
font-size: 1.2rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 8px 25px;
|
||||
}
|
||||
|
||||
.switch-to-touch {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#main-nav, #drawer {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.md-editor {
|
||||
border: 1px solid $light-grey;
|
||||
border: 1px solid $border-medium-grey;
|
||||
border-radius: $btn-border-radius-base;
|
||||
|
||||
&.active { border-color: $text-grey; }
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
|
||||
@import 'typography';
|
||||
|
||||
// New design, can be used adding the .modern-design class to a <div> around the page which is ported
|
||||
@import 'modern-design';
|
||||
@import 'registration';
|
||||
|
||||
a {
|
||||
color: #2489ce;
|
||||
text-decoration: none;
|
||||
@@ -516,9 +520,13 @@ h3.ltr {
|
||||
font-style: inherit;
|
||||
font-weight: inherit;
|
||||
margin: 0;
|
||||
padding: 15px 15px;
|
||||
padding: 12px;
|
||||
vertical-align: baseline;
|
||||
word-wrap: break-word;
|
||||
|
||||
p:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
form#new_conversation.new_conversation {
|
||||
@@ -632,6 +640,12 @@ h1.session {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
height: 50px;
|
||||
margin-top: 10px;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
#birth-date {
|
||||
@@ -652,40 +666,44 @@ form#update_profile_form {
|
||||
.submit_block { margin-bottom: 20px; }
|
||||
}
|
||||
|
||||
select#user_language, select#user_color_theme, #user_auto_follow_back_aspect_id, #aspect_ids_ {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.hero-unit-mobile {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: inherit;
|
||||
background-color: $background-grey;
|
||||
border-radius: 10px;
|
||||
color: inherit;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.search-mobile {
|
||||
text-align: center;
|
||||
padding-top: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input#q.search {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
select#aspect_ids_ {
|
||||
width: auto !important;
|
||||
float: right;
|
||||
margin: 0px;
|
||||
.aspect-dropdown {
|
||||
li {
|
||||
@include selectable-list;
|
||||
@include aspect-dropdown-link(128%);
|
||||
|
||||
.status-indicator {
|
||||
display: inline-block;
|
||||
height: 14px;
|
||||
width: 19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#file-upload-spinner {
|
||||
top: 0px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
#publisher_mobile {
|
||||
#publisher-mobile {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#file-upload-publisher {
|
||||
|
||||
@@ -56,4 +56,8 @@
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
66
app/assets/stylesheets/modern-design.scss
Normal file
66
app/assets/stylesheets/modern-design.scss
Normal file
@@ -0,0 +1,66 @@
|
||||
$breakpoint: 700px;
|
||||
|
||||
$subtle: $text-dark-grey;
|
||||
|
||||
$default-size: 16px;
|
||||
$smaller: .9em;
|
||||
|
||||
$default-margin: 16px; // Will be 1rem once the default-size will be set on <html>
|
||||
$double-margin: $default-margin * 2;
|
||||
$triple-margin: $default-margin * 3;
|
||||
|
||||
$radius: 4px;
|
||||
|
||||
|
||||
.modern-design {
|
||||
font-size: $default-size;
|
||||
|
||||
// Style
|
||||
h1 {
|
||||
font-size: 3em;
|
||||
margin: $double-margin 0;
|
||||
|
||||
@media screen and (max-width: $breakpoint) {
|
||||
font-size: 2em;
|
||||
margin: $default-margin 0;
|
||||
}
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: $double-margin;
|
||||
}
|
||||
|
||||
section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.advice {
|
||||
color: $subtle;
|
||||
font-size: $smaller;
|
||||
font-style: italic;
|
||||
}
|
||||
// End of style
|
||||
|
||||
// Layout
|
||||
// This cleans the famous children "margin-top" problem
|
||||
.small-container::before,
|
||||
.small-container::after {
|
||||
content: ' ';
|
||||
display: table;
|
||||
}
|
||||
|
||||
.small-container {
|
||||
margin: auto;
|
||||
max-width: 800px;
|
||||
|
||||
@media screen and (max-width: $breakpoint) {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
// End of layout
|
||||
}
|
||||
@@ -1,54 +1,79 @@
|
||||
.page-registrations {
|
||||
// For the mobile version which doesn't have the same global CSS
|
||||
background: $body-bg;
|
||||
|
||||
#main {
|
||||
padding: 0;
|
||||
padding-top: 46px;
|
||||
}
|
||||
// End specific mobile
|
||||
|
||||
// Overriding bootstrap
|
||||
.form-control {
|
||||
display: inline-block;
|
||||
width: 320px;
|
||||
}
|
||||
// End Overriding bootstrap
|
||||
|
||||
.fields {
|
||||
margin: $triple-margin 0;
|
||||
|
||||
@media screen and (max-width: $breakpoint) {
|
||||
flex-direction: column-reverse;
|
||||
margin: $double-margin 0;
|
||||
}
|
||||
|
||||
section {
|
||||
border-left: solid 3px $brand-primary;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.advice {
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
.captcha {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
.captcha-img {
|
||||
margin-left: 5px;
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.import-and-ball-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.import-instructions {
|
||||
align-self: flex-start;
|
||||
background-color: lighten($brand-primary, 40);
|
||||
border: 2px solid lighten($brand-primary, 20);
|
||||
border-radius: $radius;
|
||||
font-size: $smaller;
|
||||
margin-left: $default-margin;
|
||||
padding: $default-margin;
|
||||
width: 240px;
|
||||
|
||||
@media screen and (max-width: $breakpoint) {
|
||||
margin-bottom: $double-margin;
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.ball {
|
||||
background: image-url('branding/ball.png') no-repeat;
|
||||
background-size: contain;
|
||||
height: 633px;
|
||||
max-width: 100%;
|
||||
}
|
||||
height: 250px;
|
||||
width: 250px;
|
||||
|
||||
.v-center {
|
||||
display: table;
|
||||
height: 633px;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.v-center {
|
||||
height: auto;
|
||||
@media screen and (max-width: $breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
|
||||
h1 {
|
||||
font-size: 35px;
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.captcha-img {
|
||||
left: 10px;
|
||||
position: absolute;
|
||||
top: 169px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.form-control.captcha-input {
|
||||
border-bottom: 1px solid $input-border;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
line-height: $line-height-base;
|
||||
padding-left: 130px;
|
||||
}
|
||||
|
||||
.terms > a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// scss-lint:enable SelectorFormat
|
||||
|
||||
.enclosed-checkbox label {
|
||||
@@ -28,9 +29,31 @@
|
||||
max-width: 200px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.crop-container {
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.preview {
|
||||
border-radius: 4px;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-visibility { margin-left: 10px; }
|
||||
.settings-visibility {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.page-profiles.action-edit textarea {
|
||||
max-width: 100%;
|
||||
|
||||
@@ -116,9 +116,6 @@
|
||||
|
||||
.no-comments { text-align: center; }
|
||||
|
||||
a {
|
||||
color: $link-color;
|
||||
}
|
||||
.count {
|
||||
float: left;
|
||||
i {
|
||||
@@ -144,6 +141,7 @@
|
||||
.comment.new-comment-form-wrapper {
|
||||
padding: 10px;
|
||||
}
|
||||
.comments > .comment { padding-bottom: 0; }
|
||||
|
||||
.count,
|
||||
.interaction-avatars {
|
||||
@@ -164,4 +162,19 @@
|
||||
width: $line-height-computed;
|
||||
}
|
||||
}
|
||||
.likes {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
|
||||
.bd { display: inline-block; }
|
||||
img { display: inline; }
|
||||
}
|
||||
|
||||
.display-avatars .entypo-heart {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
line-height: 18px;
|
||||
margin-right: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.comments {
|
||||
.likes {
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
.expand-likes {
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-message-location {
|
||||
color: $text-grey;
|
||||
font-size: $font-size-small;
|
||||
|
||||
@@ -36,5 +36,19 @@
|
||||
<div class="collapsible comment-content markdown-content">
|
||||
{{{text}}}
|
||||
</div>
|
||||
|
||||
{{#if loggedIn}}
|
||||
<div class="info">
|
||||
<a href="#" class="like" rel='nofollow'>
|
||||
{{~#if userLike~}}
|
||||
{{~t "stream.unlike"~}}
|
||||
{{~else~}}
|
||||
{{~t "stream.like"~}}
|
||||
{{~/if~}}
|
||||
</a>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="likes likes-on-comment"> </div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<div class='question opened collapsible'>
|
||||
<a class='toggle' href='#'>
|
||||
<h4>{{ add_contact_roster_q }}</h4>
|
||||
</a>
|
||||
<div class='answer hideable'>{{{ add_contact_roster_a }}}</div>
|
||||
</div>
|
||||
@@ -64,12 +64,6 @@
|
||||
<a href="#" class="section-unselected faq-link" data-section="miscellaneous" data-items="back_to_top photo_albums subscribe_feed diaspora_app">{{ title_miscellaneous }}</a>
|
||||
<span class="section-selected">{{ title_miscellaneous }}</span>
|
||||
</li>
|
||||
{{#if chat_enabled }}
|
||||
<li>
|
||||
<a href="#" class="section-unselected faq-link-chat" data-section="chat" data-items="add_contact_roster i_m_a_podmin">{{ title_chat }}</a>
|
||||
<span class="section-selected">{{ title_chat }}</span>
|
||||
</li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{{#if likesCount}}
|
||||
<div class="comment">
|
||||
<div class="media">
|
||||
<div class="media{{#if displayAvatars}} display-avatars{{/if}}">
|
||||
<i class="entypo-heart"></i>
|
||||
|
||||
<div class="bd">
|
||||
@@ -8,9 +8,7 @@
|
||||
<a href="#" class="expand-likes gray">
|
||||
{{t "stream.likes" count=likesCount}}
|
||||
</a>
|
||||
|
||||
{{else}}
|
||||
|
||||
{{#each likes}}
|
||||
{{#linkToAuthor author}}
|
||||
{{{personImage this 'small' 'micro'}}}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{{/if}}
|
||||
</i>
|
||||
</td>
|
||||
<td class="pod-title" title="{{host}}">{{host}}</td>
|
||||
<td class="pod-title" title="{{host}}">{{host}}{{#if hasPort}}:{{port}}{{/if}}</td>
|
||||
<td class="added">
|
||||
<small><time datetime="{{created_at}}" title="{{localTime created_at}}"></time></small>
|
||||
</td>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
{{#unless preview}}
|
||||
<div class="feedback nsfw-hidden"> </div>
|
||||
<div class="likes nsfw-hidden"> </div>
|
||||
<div class="likes likes-on-post nsfw-hidden"> </div>
|
||||
<div class="reshares nsfw-hidden"> </div>
|
||||
<div class="comments nsfw-hidden"> </div>
|
||||
{{/unless}}
|
||||
|
||||
@@ -14,7 +14,8 @@ module Admin
|
||||
gon.unchecked_count = Pod.unchecked.count
|
||||
gon.version_failed_count = Pod.version_failed.count
|
||||
gon.error_count = Pod.check_failed.count
|
||||
|
||||
gon.active_count = Pod.active.count
|
||||
gon.total_count = Pod.count
|
||||
render "admins/pods"
|
||||
end
|
||||
format.mobile { render "admins/pods" }
|
||||
|
||||
@@ -120,6 +120,7 @@ module Api
|
||||
@scopes = endpoint.scopes
|
||||
save_request_parameters
|
||||
@app = UserApplicationPresenter.new @o_auth_application, @scopes
|
||||
override_content_security_policy_directives(form_action: %w[])
|
||||
render :new
|
||||
end
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ module Api
|
||||
class DiscoveryController < ApplicationController
|
||||
def configuration
|
||||
render json: OpenIDConnect::Discovery::Provider::Config::Response.new(
|
||||
issuer: root_url,
|
||||
issuer: AppConfig.environment.url,
|
||||
registration_endpoint: api_openid_connect_clients_url,
|
||||
authorization_endpoint: new_api_openid_connect_authorization_url,
|
||||
token_endpoint: api_openid_connect_access_tokens_url,
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V0
|
||||
class BaseController < ApplicationController
|
||||
include Api::OpenidConnect::ProtectedResourceEndpoint
|
||||
|
||||
protected
|
||||
|
||||
def current_user
|
||||
current_token ? current_token.authorization.user : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
79
app/controllers/api/v1/aspects_controller.rb
Normal file
79
app/controllers/api/v1/aspects_controller.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class AspectsController < Api::V1::BaseController
|
||||
before_action except: %i[create update destroy] do
|
||||
require_access_token %w[contacts:read]
|
||||
end
|
||||
|
||||
before_action only: %i[create update destroy] do
|
||||
require_access_token %w[contacts:modify]
|
||||
end
|
||||
|
||||
def index
|
||||
aspects_query = current_user.aspects
|
||||
aspects_page = index_pager(aspects_query).response
|
||||
aspects_page[:data] = aspects_page[:data].map {|a| aspect_as_json(a, false) }
|
||||
render_paged_api_response aspects_page
|
||||
end
|
||||
|
||||
def show
|
||||
aspect = current_user.aspects.where(id: params[:id]).first
|
||||
if aspect
|
||||
render json: aspect_as_json(aspect, true)
|
||||
else
|
||||
render_error 404, "Aspect with provided ID could not be found"
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
params.require(%i[name])
|
||||
aspect = current_user.aspects.build(name: params[:name])
|
||||
if aspect&.save
|
||||
render json: aspect_as_json(aspect, true)
|
||||
else
|
||||
render_error 422, "Failed to create the aspect"
|
||||
end
|
||||
rescue ActionController::ParameterMissing
|
||||
render_error 422, "Failed to create the aspect"
|
||||
end
|
||||
|
||||
def update
|
||||
aspect = current_user.aspects.where(id: params[:id]).first
|
||||
|
||||
if !aspect
|
||||
render_error 404, "Failed to update the aspect"
|
||||
elsif aspect.update!(aspect_params(true))
|
||||
render json: aspect_as_json(aspect, true)
|
||||
else
|
||||
render_error 422, "Failed to update the aspect"
|
||||
end
|
||||
rescue ActionController::ParameterMissing, ActiveRecord::RecordInvalid
|
||||
render_error 422, "Failed to update the aspect"
|
||||
end
|
||||
|
||||
def destroy
|
||||
aspect = current_user.aspects.where(id: params[:id]).first
|
||||
if aspect&.destroy
|
||||
head :no_content
|
||||
else
|
||||
render_error 422, "Failed to delete the aspect"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def aspect_params(allow_order=false)
|
||||
parameters = params.permit(:name)
|
||||
parameters[:order_id] = params[:order] if params.has_key?(:order) && allow_order
|
||||
|
||||
parameters
|
||||
end
|
||||
|
||||
def aspect_as_json(aspect, as_full)
|
||||
AspectPresenter.new(aspect).as_api_json(as_full)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
77
app/controllers/api/v1/base_controller.rb
Normal file
77
app/controllers/api/v1/base_controller.rb
Normal file
@@ -0,0 +1,77 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class BaseController < ApplicationController
|
||||
include Api::OpenidConnect::ProtectedResourceEndpoint
|
||||
|
||||
protect_from_forgery unless: -> { request.format.json? }
|
||||
|
||||
protected
|
||||
|
||||
rescue_from Exception do |e|
|
||||
logger.error e.message
|
||||
logger.error e.backtrace.join("\n")
|
||||
render_error 500, e.message
|
||||
end
|
||||
|
||||
rescue_from Rack::OAuth2::Server::Resource::Bearer::Unauthorized do |e|
|
||||
logger.error e.message
|
||||
render_error 403, e.message
|
||||
end
|
||||
|
||||
rescue_from Rack::OAuth2::Server::Resource::Forbidden do |e|
|
||||
logger.error e.message
|
||||
render_error 403, e.message
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do |e|
|
||||
logger.error e.message
|
||||
render_error 404, "No record found for the given id"
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid do |e|
|
||||
logger.error e.message
|
||||
render_error 422, e.message
|
||||
end
|
||||
|
||||
rescue_from ActionController::ParameterMissing do |e|
|
||||
logger.error e.message
|
||||
render_error 422, e.message.split("\n").first
|
||||
end
|
||||
|
||||
def current_user
|
||||
current_token ? current_token.authorization.user : nil
|
||||
end
|
||||
|
||||
def index_pager(query)
|
||||
Api::Paging::RestPaginatorBuilder.new(query, request).index_pager(params)
|
||||
end
|
||||
|
||||
def render_paged_api_response(page)
|
||||
link_header = []
|
||||
link_header << %(<#{page[:links][:next]}>; rel="next") if page[:links][:next]
|
||||
link_header << %(<#{page[:links][:previous]}>; rel="previous") if page[:links][:previous]
|
||||
response.set_header("Link", link_header.join(", ")) if link_header.present?
|
||||
|
||||
render json: page[:data]
|
||||
end
|
||||
|
||||
def render_error(code, message)
|
||||
render json: {code: code, message: message}, status: code
|
||||
end
|
||||
|
||||
def time_pager(query)
|
||||
Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params)
|
||||
end
|
||||
|
||||
def private_read?
|
||||
access_token? %w[private:read]
|
||||
end
|
||||
|
||||
def private_modify?
|
||||
access_token? %w[private:modify]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
118
app/controllers/api/v1/comments_controller.rb
Normal file
118
app/controllers/api/v1/comments_controller.rb
Normal file
@@ -0,0 +1,118 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class CommentsController < Api::V1::BaseController
|
||||
before_action except: %i[create destroy] do
|
||||
require_access_token %w[public:read]
|
||||
end
|
||||
|
||||
before_action only: %i[create destroy] do
|
||||
require_access_token %w[interactions public:read]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "Post with provided guid could not be found"
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid do
|
||||
render_error 422, "User is not allowed to comment"
|
||||
end
|
||||
|
||||
def create
|
||||
find_post
|
||||
comment = comment_service.create(params.require(:post_id), params.require(:body))
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_error 404, "Post with provided guid could not be found"
|
||||
else
|
||||
render json: comment_as_json(comment), status: :created
|
||||
end
|
||||
|
||||
def index
|
||||
find_post
|
||||
comments_query = comment_service.find_for_post(params.require(:post_id))
|
||||
params[:after] = Time.utc(1900).iso8601 if params.permit(:before, :after).empty?
|
||||
|
||||
comments_page = time_pager(comments_query).response
|
||||
comments_page[:data] = comments_page[:data].map {|x| comment_as_json(x) }
|
||||
render_paged_api_response comments_page
|
||||
end
|
||||
|
||||
def destroy
|
||||
find_post
|
||||
if comment_and_post_validate(params.require(:post_id), params[:id])
|
||||
comment_service.destroy!(params[:id])
|
||||
head :no_content
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render_error 403, "User not allowed to delete the comment"
|
||||
end
|
||||
|
||||
def report
|
||||
find_post
|
||||
post_guid = params.require(:post_id)
|
||||
comment_guid = params.require(:comment_id)
|
||||
return unless comment_and_post_validate(post_guid, comment_guid)
|
||||
|
||||
reason = params.require(:reason)
|
||||
comment = comment_service.find!(comment_guid)
|
||||
report = current_user.reports.new(
|
||||
item_id: comment.id,
|
||||
item_type: "Comment",
|
||||
text: reason
|
||||
)
|
||||
if report.save
|
||||
head :no_content
|
||||
else
|
||||
render_error 409, "This item already has been reported by this user"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def comment_and_post_validate(post_guid, comment_guid)
|
||||
if !comment_exists(comment_guid)
|
||||
render_error 404, "Comment not found for the given post"
|
||||
false
|
||||
elsif !comment_is_for_post(post_guid, comment_guid)
|
||||
render_error 404, "Comment not found for the given post"
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def comment_is_for_post(post_guid, comment_guid)
|
||||
comments = comment_service.find_for_post(post_guid)
|
||||
comment = comments.find {|comment| comment[:guid] == comment_guid }
|
||||
comment ? true : false
|
||||
end
|
||||
|
||||
def comment_exists(comment_guid)
|
||||
comment = comment_service.find!(comment_guid)
|
||||
comment ? true : false
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
false
|
||||
end
|
||||
|
||||
def comment_service
|
||||
@comment_service ||= CommentService.new(current_user)
|
||||
end
|
||||
|
||||
def post_service
|
||||
@post_service ||= PostService.new(current_user)
|
||||
end
|
||||
|
||||
def comment_as_json(comment)
|
||||
CommentPresenter.new(comment, current_user).as_api_response
|
||||
end
|
||||
|
||||
def find_post
|
||||
post = post_service.find!(params[:post_id])
|
||||
return post if post.public? || private_read?
|
||||
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
62
app/controllers/api/v1/contacts_controller.rb
Normal file
62
app/controllers/api/v1/contacts_controller.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "api/paging/index_paginator"
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class ContactsController < Api::V1::BaseController
|
||||
before_action except: %i[create destroy] do
|
||||
require_access_token %w[contacts:read]
|
||||
end
|
||||
|
||||
before_action only: %i[create destroy] do
|
||||
require_access_token %w[contacts:modify]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "Aspect with provided ID could not be found"
|
||||
end
|
||||
|
||||
def index
|
||||
contacts_query = aspects_membership_service.contacts_in_aspect(params.require(:aspect_id))
|
||||
contacts_page = index_pager(contacts_query).response
|
||||
contacts_page[:data] = contacts_page[:data].map do |c|
|
||||
ContactPresenter.new(c, current_user).as_api_json_without_contact
|
||||
end
|
||||
render_paged_api_response contacts_page
|
||||
end
|
||||
|
||||
def create
|
||||
aspect_id = params.require(:aspect_id)
|
||||
person = Person.find_by(guid: params.require(:person_guid))
|
||||
aspect_membership = aspects_membership_service.create(aspect_id, person.id) if person.present?
|
||||
|
||||
if aspect_membership
|
||||
head :no_content
|
||||
else
|
||||
render_error 422, "Failed to add user to aspect"
|
||||
end
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
render_error 422, "Failed to add user to aspect"
|
||||
end
|
||||
|
||||
def destroy
|
||||
aspect_id = params.require(:aspect_id)
|
||||
person = Person.find_by(guid: params[:id])
|
||||
result = aspects_membership_service.destroy_by_ids(aspect_id, person.id) if person.present?
|
||||
|
||||
if result && result[:success]
|
||||
head :no_content
|
||||
else
|
||||
render_error 422, "Failed to remove user from aspect"
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_error 404, "Aspect or contact on aspect not found"
|
||||
end
|
||||
|
||||
def aspects_membership_service
|
||||
AspectsMembershipService.new(current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
87
app/controllers/api/v1/conversations_controller.rb
Normal file
87
app/controllers/api/v1/conversations_controller.rb
Normal file
@@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class ConversationsController < Api::V1::BaseController
|
||||
include ConversationsHelper
|
||||
|
||||
BOOLEAN_TYPE = ActiveModel::Type::Boolean.new
|
||||
|
||||
before_action do
|
||||
require_access_token %w[conversations]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "Conversation with provided guid could not be found"
|
||||
end
|
||||
|
||||
def index
|
||||
mapped_params = {}
|
||||
mapped_params[:only_after] = params[:only_after] if params.has_key?(:only_after)
|
||||
|
||||
mapped_params[:unread] = BOOLEAN_TYPE.cast(params[:only_unread]) if params.has_key?(:only_unread)
|
||||
|
||||
conversations_query = conversation_service.all_for_user(mapped_params)
|
||||
conversations_page = pager(conversations_query, "conversations.created_at").response
|
||||
conversations_page[:data] = conversations_page[:data].map {|x| conversation_as_json(x) }
|
||||
render_paged_api_response conversations_page
|
||||
end
|
||||
|
||||
def show
|
||||
conversation = conversation_service.find!(params[:id])
|
||||
render json: conversation_as_json(conversation)
|
||||
end
|
||||
|
||||
def create
|
||||
params.require(%i[subject body recipients])
|
||||
recipients = recipient_ids
|
||||
conversation = conversation_service.build(params[:subject], params[:body], recipients)
|
||||
raise ActiveRecord::RecordInvalid unless conversation_valid?(conversation, recipients)
|
||||
|
||||
conversation.save!
|
||||
Diaspora::Federation::Dispatcher.defer_dispatch(current_user, conversation)
|
||||
render json: conversation_as_json(conversation), status: :created
|
||||
rescue ActiveRecord::RecordInvalid, ActionController::ParameterMissing, ActiveRecord::RecordNotFound
|
||||
render_error 422, "Couldn't accept or process the conversation"
|
||||
end
|
||||
|
||||
def update
|
||||
read = BOOLEAN_TYPE.cast(params.require(:read))
|
||||
conversation = conversation_service.find!(params[:id])
|
||||
conversation.update_read_for(current_user, read: read)
|
||||
|
||||
render json: conversation_as_json(conversation)
|
||||
rescue ActiveRecord::RecordInvalid, ActionController::ParameterMissing, ActiveRecord::RecordNotFound
|
||||
render_error 422, "Couldn't update the conversation"
|
||||
end
|
||||
|
||||
def destroy
|
||||
conversation = conversation_service.get_visibility(params[:id])
|
||||
conversation.destroy!
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conversation_service
|
||||
@conversation_service ||= ConversationService.new(current_user)
|
||||
end
|
||||
|
||||
def conversation_as_json(conversation)
|
||||
ConversationPresenter.new(conversation, current_user).as_api_json
|
||||
end
|
||||
|
||||
def pager(query, sort_field)
|
||||
Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params, sort_field)
|
||||
end
|
||||
|
||||
def recipient_ids
|
||||
params[:recipients].map {|p| Person.find_from_guid_or_username(id: p).id }
|
||||
end
|
||||
|
||||
def conversation_valid?(conversation, recipients)
|
||||
conversation.participants.length == (recipients.length + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
138
app/controllers/api/v1/likes_controller.rb
Normal file
138
app/controllers/api/v1/likes_controller.rb
Normal file
@@ -0,0 +1,138 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class LikesController < Api::V1::BaseController
|
||||
before_action do
|
||||
require_access_token %w[public:read]
|
||||
end
|
||||
|
||||
before_action only: %i[create destroy] do
|
||||
require_access_token %w[interactions]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "Post with provided guid could not be found"
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid do
|
||||
render_error 422, "User is not allowed to like"
|
||||
end
|
||||
|
||||
def show
|
||||
post = post_service.find!(params.require(:post_id))
|
||||
raise ActiveRecord::RecordInvalid unless post.public? || private_read?
|
||||
|
||||
likes_query = find_likes
|
||||
|
||||
return unless likes_query
|
||||
|
||||
likes_page = index_pager(likes_query).response
|
||||
likes_page[:data] = likes_page[:data].map {|x| like_json(x) }
|
||||
render_paged_api_response likes_page
|
||||
end
|
||||
|
||||
def create
|
||||
post = post_service.find!(params.require(:post_id))
|
||||
raise ActiveRecord::RecordInvalid unless post.public? || private_read?
|
||||
|
||||
if params[:comment_id].present?
|
||||
create_for_comment
|
||||
else
|
||||
create_for_post
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
if e.message == "Validation failed: Target has already been taken"
|
||||
return render_error 409, "Like already exists"
|
||||
end
|
||||
|
||||
raise
|
||||
end
|
||||
|
||||
def destroy
|
||||
post = post_service.find!(params.require(:post_id))
|
||||
raise ActiveRecord::RecordInvalid unless post.public? || private_read?
|
||||
|
||||
if params[:comment_id].present?
|
||||
destroy_for_comment
|
||||
else
|
||||
destroy_for_post
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_likes
|
||||
if params[:comment_id].present?
|
||||
return unless comment_and_post_validate(params[:post_id], params[:comment_id])
|
||||
|
||||
like_service.find_for_comment(params[:comment_id])
|
||||
else
|
||||
like_service.find_for_post(params[:post_id])
|
||||
end
|
||||
end
|
||||
|
||||
def like_service
|
||||
@like_service ||= LikeService.new(current_user)
|
||||
end
|
||||
|
||||
def post_service
|
||||
@post_service ||= PostService.new(current_user)
|
||||
end
|
||||
|
||||
def comment_service
|
||||
@comment_service ||= CommentService.new(current_user)
|
||||
end
|
||||
|
||||
def like_json(like)
|
||||
LikesPresenter.new(like).as_api_json
|
||||
end
|
||||
|
||||
def create_for_post
|
||||
like_service.create_for_post(params[:post_id])
|
||||
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def create_for_comment
|
||||
return unless comment_and_post_validate(params[:post_id], params[:comment_id])
|
||||
|
||||
like_service.create_for_comment(params[:comment_id])
|
||||
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def destroy_for_post
|
||||
if like_service.unlike_post(params[:post_id])
|
||||
head :no_content
|
||||
else
|
||||
render_error 410, "Like doesn’t exist"
|
||||
end
|
||||
end
|
||||
|
||||
def destroy_for_comment
|
||||
return unless comment_and_post_validate(params[:post_id], params[:comment_id])
|
||||
|
||||
if like_service.unlike_comment(params[:comment_id])
|
||||
head :no_content
|
||||
else
|
||||
render_error 410, "Like doesn’t exist"
|
||||
end
|
||||
end
|
||||
|
||||
def comment_and_post_validate(post_guid, comment_guid)
|
||||
if comment_is_for_post(post_guid, comment_guid)
|
||||
true
|
||||
else
|
||||
render_error 404, "Comment not found for the given post"
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def comment_is_for_post(post_guid, comment_guid)
|
||||
comments = comment_service.find_for_post(post_guid)
|
||||
comments.exists?(guid: comment_guid)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
app/controllers/api/v1/messages_controller.rb
Normal file
43
app/controllers/api/v1/messages_controller.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class MessagesController < Api::V1::BaseController
|
||||
before_action do
|
||||
require_access_token %w[conversations]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "Conversation with provided guid could not be found"
|
||||
end
|
||||
|
||||
def create
|
||||
conversation = conversation_service.find!(params.require(:conversation_id))
|
||||
text = params.require(:body)
|
||||
message = current_user.build_message(conversation, text: text)
|
||||
message.save!
|
||||
Diaspora::Federation::Dispatcher.defer_dispatch(current_user, message)
|
||||
render json: message_json(message), status: :created
|
||||
rescue ActionController::ParameterMissing
|
||||
render_error 422, "Couldn’t accept or process the conversation"
|
||||
end
|
||||
|
||||
def index
|
||||
conversation = conversation_service.find!(params.require(:conversation_id))
|
||||
messages_page = index_pager(conversation.messages).response
|
||||
messages_page[:data] = messages_page[:data].map {|x| message_json(x) }
|
||||
render_paged_api_response messages_page
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conversation_service
|
||||
ConversationService.new(current_user)
|
||||
end
|
||||
|
||||
def message_json(message)
|
||||
MessagePresenter.new(message).as_api_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
57
app/controllers/api/v1/notifications_controller.rb
Normal file
57
app/controllers/api/v1/notifications_controller.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class NotificationsController < Api::V1::BaseController
|
||||
BOOLEAN_TYPE = ActiveModel::Type::Boolean.new
|
||||
|
||||
before_action do
|
||||
require_access_token %w[notifications]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "Notification with provided guid could not be found"
|
||||
end
|
||||
|
||||
def show
|
||||
notification = service.get_by_guid(params[:id])
|
||||
|
||||
if notification
|
||||
render json: NotificationPresenter.new(notification).as_api_json
|
||||
else
|
||||
render_error 404, "Notification with provided guid could not be found"
|
||||
end
|
||||
end
|
||||
|
||||
def index
|
||||
after_date = Date.iso8601(params[:only_after]) if params.has_key?(:only_after)
|
||||
|
||||
notifications_query = service.index(BOOLEAN_TYPE.cast(params[:only_unread]), after_date)
|
||||
notifications_page = time_pager(notifications_query).response
|
||||
notifications_page[:data] = notifications_page[:data].map do |note|
|
||||
NotificationPresenter.new(note, default_serializer_options).as_api_json
|
||||
end
|
||||
render_paged_api_response notifications_page
|
||||
rescue ArgumentError
|
||||
render_error 422, "Could not process the notifications request"
|
||||
end
|
||||
|
||||
def update
|
||||
read = BOOLEAN_TYPE.cast(params.require(:read))
|
||||
if service.update_status_by_guid(params[:id], read)
|
||||
head :no_content
|
||||
else
|
||||
render_error 422, "Could not process the notifications request"
|
||||
end
|
||||
rescue ActionController::ParameterMissing
|
||||
render_error 422, "Could not process the notifications request"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def service
|
||||
@service ||= NotificationService.new(current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
76
app/controllers/api/v1/photos_controller.rb
Normal file
76
app/controllers/api/v1/photos_controller.rb
Normal file
@@ -0,0 +1,76 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class PhotosController < Api::V1::BaseController
|
||||
before_action except: %i[create destroy] do
|
||||
require_access_token %w[public:read]
|
||||
end
|
||||
|
||||
before_action only: %i[create destroy] do
|
||||
require_access_token %w[public:modify]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "Photo with provided guid could not be found"
|
||||
end
|
||||
|
||||
def index
|
||||
query = if private_read?
|
||||
current_user.photos
|
||||
else
|
||||
current_user.photos.where(public: true)
|
||||
end
|
||||
photos_page = time_pager(query).response
|
||||
photos_page[:data] = photos_page[:data].map {|photo| photo_json(photo) }
|
||||
render_paged_api_response photos_page
|
||||
end
|
||||
|
||||
def show
|
||||
photo = photo_service.visible_photo(params.require(:id))
|
||||
raise ActiveRecord::RecordNotFound unless photo
|
||||
|
||||
raise ActiveRecord::RecordNotFound unless photo.public? || private_read?
|
||||
|
||||
render json: photo_json(photo)
|
||||
end
|
||||
|
||||
def create
|
||||
image = params.require(:image)
|
||||
public_photo = params.has_key?(:aspect_ids)
|
||||
raise RuntimeError unless public_photo || private_modify?
|
||||
|
||||
base_params = params.permit(:aspect_ids, :pending, :set_profile_photo)
|
||||
photo = photo_service.create_from_params_and_file(base_params, image)
|
||||
raise RuntimeError unless photo
|
||||
|
||||
render json: photo_json(photo)
|
||||
rescue CarrierWave::IntegrityError, ActionController::ParameterMissing, RuntimeError
|
||||
render_error 422, "Failed to create the photo"
|
||||
end
|
||||
|
||||
def destroy
|
||||
photo = current_user.photos.where(guid: params[:id]).first
|
||||
raise ActiveRecord::RecordNotFound unless photo
|
||||
|
||||
raise ActiveRecord::RecordNotFound unless photo.public? || private_modify?
|
||||
|
||||
if current_user.retract(photo)
|
||||
head :no_content
|
||||
else
|
||||
render_error 422, "Not allowed to delete the photo"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def photo_service
|
||||
@photo_service ||= PhotoService.new(current_user)
|
||||
end
|
||||
|
||||
def photo_json(photo)
|
||||
PhotoPresenter.new(photo).as_api_json(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
101
app/controllers/api/v1/post_interactions_controller.rb
Normal file
101
app/controllers/api/v1/post_interactions_controller.rb
Normal file
@@ -0,0 +1,101 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class PostInteractionsController < Api::V1::BaseController
|
||||
include PostsHelper
|
||||
|
||||
before_action do
|
||||
require_access_token %w[public:read interactions]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "Post with provided guid could not be found"
|
||||
end
|
||||
|
||||
def subscribe
|
||||
post = find_post
|
||||
return head :conflict if current_user.participations.find_by(target_id: post.id)
|
||||
|
||||
current_user.participate!(post)
|
||||
head :no_content
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render_error 422, "Cannot subscribe to this post"
|
||||
end
|
||||
|
||||
def hide
|
||||
return render_error(422, "Missing parameter") if params[:hide].nil?
|
||||
|
||||
post = find_post
|
||||
hidden = current_user.is_shareable_hidden?(post)
|
||||
|
||||
if (params[:hide] && !hidden) || (!params[:hide] && hidden)
|
||||
current_user.toggle_hidden_shareable(post)
|
||||
head :no_content
|
||||
else
|
||||
render_error(params[:hide] ? 409 : 410, params[:hide] ? "Post already hidden" : "Post not hidden")
|
||||
end
|
||||
end
|
||||
|
||||
def mute
|
||||
post = find_post
|
||||
participation = current_user.participations.find_by(target_id: post.id)
|
||||
return head :gone unless participation
|
||||
|
||||
participation.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def report
|
||||
reason = params.require(:reason)
|
||||
post = find_post
|
||||
report = current_user.reports.new(
|
||||
item_id: post.id,
|
||||
item_type: "Post",
|
||||
text: reason
|
||||
)
|
||||
if report.save
|
||||
head :no_content
|
||||
else
|
||||
render_error 409, "Failed to create report on this post"
|
||||
end
|
||||
rescue ActionController::ParameterMissing
|
||||
render_error 422, "Failed to create report on this post"
|
||||
end
|
||||
|
||||
def vote
|
||||
post = find_post
|
||||
begin
|
||||
poll_vote = poll_service.vote(post.id, params[:poll_answer])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
# This, but not the find_post above, should return a 422,
|
||||
# we just keep poll_vote nil so it goes into the else below
|
||||
end
|
||||
if poll_vote
|
||||
head :no_content
|
||||
else
|
||||
render_error 422, "Cant vote on this post"
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render_error 422, "Cant vote on this post"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def post_service
|
||||
@post_service ||= PostService.new(current_user)
|
||||
end
|
||||
|
||||
def poll_service
|
||||
@poll_service ||= PollParticipationService.new(current_user)
|
||||
end
|
||||
|
||||
def find_post
|
||||
post = post_service.find!(params[:post_id])
|
||||
raise ActiveRecord::RecordNotFound unless post.public? || private_read?
|
||||
|
||||
post
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
115
app/controllers/api/v1/posts_controller.rb
Normal file
115
app/controllers/api/v1/posts_controller.rb
Normal file
@@ -0,0 +1,115 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class PostsController < Api::V1::BaseController
|
||||
include PostsHelper
|
||||
|
||||
before_action except: %i[create destroy] do
|
||||
require_access_token %w[public:read]
|
||||
end
|
||||
|
||||
before_action only: %i[create destroy] do
|
||||
require_access_token %w[public:modify]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "Post with provided guid could not be found"
|
||||
end
|
||||
|
||||
def show
|
||||
post = post_service.find!(params[:id])
|
||||
raise ActiveRecord::RecordNotFound unless post.public? || private_read?
|
||||
|
||||
render json: post_as_json(post)
|
||||
end
|
||||
|
||||
def create
|
||||
creation_params = normalized_create_params
|
||||
raise StandardError unless creation_params[:public] || private_modify?
|
||||
|
||||
@status_message = creation_service.create(creation_params)
|
||||
render json: PostPresenter.new(@status_message, current_user).as_api_response
|
||||
rescue StandardError
|
||||
render_error 422, "Failed to create the post"
|
||||
end
|
||||
|
||||
def destroy
|
||||
post_service.destroy(params[:id], private_modify?)
|
||||
head :no_content
|
||||
rescue Diaspora::NotMine, Diaspora::NonPublic
|
||||
render_error 403, "Not allowed to delete the post"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def normalized_create_params
|
||||
mapped_parameters = {
|
||||
status_message: {
|
||||
text: params[:body]
|
||||
},
|
||||
public: params.require(:public),
|
||||
aspect_ids: normalize_aspect_ids(params.permit(aspects: []))
|
||||
}
|
||||
add_location_params(mapped_parameters)
|
||||
add_poll_params(mapped_parameters)
|
||||
add_photo_ids(mapped_parameters)
|
||||
mapped_parameters
|
||||
end
|
||||
|
||||
def add_location_params(mapped_parameters)
|
||||
return unless params.has_key?(:location)
|
||||
|
||||
location = params.require(:location)
|
||||
mapped_parameters[:location_address] = location[:address]
|
||||
mapped_parameters[:location_coords] = "#{location[:lat]},#{location[:lng]}"
|
||||
end
|
||||
|
||||
def add_photo_ids(mapped_parameters)
|
||||
return unless params.has_key?(:photos)
|
||||
|
||||
photo_guids = params[:photos]
|
||||
return if photo_guids.empty?
|
||||
|
||||
photos = photo_guids.map {|guid| Photo.find_by!(guid: guid) }
|
||||
.select {|p| p.author_id == current_user.person.id && p.pending }
|
||||
raise InvalidArgument if photos.length != photo_guids.length
|
||||
|
||||
mapped_parameters[:photos] = photos
|
||||
end
|
||||
|
||||
def add_poll_params(mapped_parameters)
|
||||
return unless params.has_key?(:poll)
|
||||
|
||||
poll_data = params.require(:poll)
|
||||
question = poll_data[:question]
|
||||
answers = poll_data[:poll_answers]
|
||||
raise InvalidArgument if question.blank?
|
||||
|
||||
raise InvalidArgument if answers.empty?
|
||||
|
||||
answers.each do |a|
|
||||
raise InvalidArgument if a.blank?
|
||||
end
|
||||
mapped_parameters[:poll_question] = question
|
||||
mapped_parameters[:poll_answers] = answers
|
||||
end
|
||||
|
||||
def normalize_aspect_ids(aspects)
|
||||
aspects.empty? ? [] : aspects[:aspects]
|
||||
end
|
||||
|
||||
def post_service
|
||||
@post_service ||= PostService.new(current_user)
|
||||
end
|
||||
|
||||
def creation_service
|
||||
@creation_service ||= StatusMessageCreationService.new(current_user)
|
||||
end
|
||||
|
||||
def post_as_json(post)
|
||||
PostPresenter.new(post, current_user).as_api_response
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
52
app/controllers/api/v1/reshares_controller.rb
Normal file
52
app/controllers/api/v1/reshares_controller.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class ResharesController < Api::V1::BaseController
|
||||
before_action except: %i[create] do
|
||||
require_access_token %w[public:read]
|
||||
end
|
||||
|
||||
before_action only: %i[create] do
|
||||
require_access_token %w[public:modify]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "Post with provided guid could not be found"
|
||||
end
|
||||
|
||||
rescue_from Diaspora::NonPublic do
|
||||
render_error 404, "Post with provided guid could not be found"
|
||||
end
|
||||
|
||||
def show
|
||||
reshares_query = reshare_service.find_for_post(params.require(:post_id))
|
||||
reshares_page = index_pager(reshares_query).response
|
||||
reshares_page[:data] = reshares_page[:data].map do |r|
|
||||
{
|
||||
guid: r.guid,
|
||||
created_at: r.created_at,
|
||||
author: PersonPresenter.new(r.author).as_api_json
|
||||
}
|
||||
end
|
||||
render_paged_api_response reshares_page
|
||||
end
|
||||
|
||||
def create
|
||||
reshare = reshare_service.create(params.require(:post_id))
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
render_error 409, "Reshare already exists"
|
||||
rescue ActiveRecord::RecordNotFound, RuntimeError
|
||||
render_error 422, "Failed to reshare"
|
||||
else
|
||||
render json: PostPresenter.new(reshare, current_user).as_api_response
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reshare_service
|
||||
@reshare_service ||= ReshareService.new(current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
116
app/controllers/api/v1/search_controller.rb
Normal file
116
app/controllers/api/v1/search_controller.rb
Normal file
@@ -0,0 +1,116 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class SearchController < Api::V1::BaseController
|
||||
USER_FILTER_CONTACTS = "contacts"
|
||||
USER_FILTER_RECEIVING_CONTACTS = "contacts:receiving"
|
||||
USER_FILTER_SHARING_CONTACTS = "contacts:sharing"
|
||||
USER_FILTER_ASPECTS_PREFIX = "aspect:"
|
||||
USER_FILTERS_EXACT_MATCH = [USER_FILTER_CONTACTS, USER_FILTER_RECEIVING_CONTACTS,
|
||||
USER_FILTER_SHARING_CONTACTS].freeze
|
||||
USER_FILTERS_PREFIX_MATCH = [USER_FILTER_ASPECTS_PREFIX].freeze
|
||||
|
||||
before_action do
|
||||
require_access_token %w[public:read]
|
||||
end
|
||||
|
||||
rescue_from RuntimeError do |e|
|
||||
render_error 422, e.message
|
||||
end
|
||||
|
||||
def user_index
|
||||
user_page = index_pager(people_query).response
|
||||
user_page[:data] = user_page[:data].map {|p| PersonPresenter.new(p).as_api_json }
|
||||
render_paged_api_response user_page
|
||||
end
|
||||
|
||||
def post_index
|
||||
posts_page = time_pager(posts_query, "posts.created_at", "created_at").response
|
||||
posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post).as_api_response }
|
||||
render_paged_api_response posts_page
|
||||
end
|
||||
|
||||
def tag_index
|
||||
tags_page = index_pager(tags_query).response
|
||||
tags_page[:data] = tags_page[:data].pluck(:name)
|
||||
render_paged_api_response tags_page
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def time_pager(query, query_time_field, data_time_field)
|
||||
Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params, query_time_field, data_time_field)
|
||||
end
|
||||
|
||||
def people_query
|
||||
tag = params[:tag]
|
||||
name_or_handle = params[:name_or_handle]
|
||||
raise "Parameters tag and name_or_handle are exclusive" if tag.present? && name_or_handle.present?
|
||||
|
||||
query = if tag.present?
|
||||
# scope filters to only searchable people already
|
||||
Person.profile_tagged_with(tag)
|
||||
elsif name_or_handle.present?
|
||||
Person.searchable(contacts_read? && current_user) # rubocop:disable Rails/DynamicFindBy
|
||||
.find_by_substring(name_or_handle)
|
||||
else
|
||||
raise "Missing parameter tag or name_or_handle"
|
||||
end
|
||||
|
||||
query = query.where(closed_account: false)
|
||||
|
||||
user_filters.each do |filter|
|
||||
query = query.contacts_of(current_user) if filter == USER_FILTER_CONTACTS
|
||||
|
||||
if filter == USER_FILTER_RECEIVING_CONTACTS
|
||||
query = query.contacts_of(current_user).where(contacts: {receiving: true})
|
||||
end
|
||||
|
||||
if filter == USER_FILTER_SHARING_CONTACTS
|
||||
query = query.contacts_of(current_user).where(contacts: {sharing: true})
|
||||
end
|
||||
|
||||
if filter.start_with?(USER_FILTER_ASPECTS_PREFIX) # rubocop:disable Style/Next
|
||||
_, ids = filter.split(":", 2)
|
||||
ids = ids.split(",").map {|id|
|
||||
Integer(id) rescue raise("Invalid aspect filter") # rubocop:disable Style/RescueModifier
|
||||
}
|
||||
|
||||
raise "Invalid aspect filter" unless current_user.aspects.where(id: ids).count == ids.size
|
||||
|
||||
query = Person.where(id: query.all_from_aspects(ids, current_user).select(:id))
|
||||
end
|
||||
end
|
||||
|
||||
query.distinct
|
||||
end
|
||||
|
||||
def posts_query
|
||||
opts = {}
|
||||
opts[:public_only] = !private_read?
|
||||
Stream::Tag.new(current_user, params.require(:tag), opts).stream_posts
|
||||
end
|
||||
|
||||
def tags_query
|
||||
ActsAsTaggableOn::Tag.autocomplete(params.require(:query))
|
||||
end
|
||||
|
||||
def user_filters
|
||||
@user_filters ||= Array(params[:filter]).uniq.tap do |filters|
|
||||
raise "Invalid filter" unless filters.all? {|filter|
|
||||
USER_FILTERS_EXACT_MATCH.include?(filter) ||
|
||||
USER_FILTERS_PREFIX_MATCH.any? {|prefix| filter.start_with?(prefix) }
|
||||
}
|
||||
|
||||
# For now all filters require contacts:read
|
||||
require_access_token %w[contacts:read] unless filters.empty?
|
||||
end
|
||||
end
|
||||
|
||||
def contacts_read?
|
||||
access_token? %w[contacts:read]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
74
app/controllers/api/v1/streams_controller.rb
Normal file
74
app/controllers/api/v1/streams_controller.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class StreamsController < Api::V1::BaseController
|
||||
before_action do
|
||||
require_access_token %w[public:read]
|
||||
end
|
||||
|
||||
before_action only: %w[aspects] do
|
||||
require_access_token %w[contacts:read private:read]
|
||||
end
|
||||
|
||||
before_action only: %w[followed_tags] do
|
||||
require_access_token %w[tags:read]
|
||||
end
|
||||
|
||||
def aspects
|
||||
aspect_ids = params.has_key?(:aspect_ids) ? JSON.parse(params[:aspect_ids]) : []
|
||||
@stream = Stream::Aspect.new(current_user, aspect_ids, max_time: stream_max_time)
|
||||
stream_responder
|
||||
end
|
||||
|
||||
def activity
|
||||
stream_responder(Stream::Activity, "posts.interacted_at", "interacted_at")
|
||||
end
|
||||
|
||||
def multi
|
||||
stream_responder(Stream::Multi)
|
||||
end
|
||||
|
||||
def commented
|
||||
stream_responder(Stream::Comments)
|
||||
end
|
||||
|
||||
def liked
|
||||
stream_responder(Stream::Likes)
|
||||
end
|
||||
|
||||
def mentions
|
||||
stream_responder(Stream::Mention)
|
||||
end
|
||||
|
||||
def followed_tags
|
||||
stream_responder(Stream::FollowedTag)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stream_responder(stream_klass=nil, query_time_field="posts.created_at", data_time_field="created_at")
|
||||
@stream = stream_klass.present? ? stream_klass.new(current_user, max_time: stream_max_time) : @stream
|
||||
query = @stream.stream_posts
|
||||
query = query.where(public: true) unless private_read?
|
||||
posts_page = pager(query, query_time_field, data_time_field).response
|
||||
posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post, current_user).as_api_response }
|
||||
posts_page[:links].delete(:previous)
|
||||
render_paged_api_response posts_page
|
||||
end
|
||||
|
||||
def stream_max_time
|
||||
if params.has_key?("before")
|
||||
Time.iso8601(params["before"])
|
||||
else
|
||||
max_time
|
||||
end
|
||||
end
|
||||
|
||||
def pager(query, query_time_field, data_time_field)
|
||||
Api::Paging::RestPaginatorBuilder.new(query, request, true, 15)
|
||||
.time_pager(params, query_time_field, data_time_field)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
41
app/controllers/api/v1/tag_followings_controller.rb
Executable file
41
app/controllers/api/v1/tag_followings_controller.rb
Executable file
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class TagFollowingsController < Api::V1::BaseController
|
||||
before_action except: %i[create destroy] do
|
||||
require_access_token %w[tags:read]
|
||||
end
|
||||
|
||||
before_action only: %i[create destroy] do
|
||||
require_access_token %w[tags:modify]
|
||||
end
|
||||
|
||||
def index
|
||||
render json: tag_followings_service.index.pluck(:name)
|
||||
end
|
||||
|
||||
def create
|
||||
tag_followings_service.create(params.require(:name))
|
||||
head :no_content
|
||||
rescue TagFollowingService::DuplicateTag
|
||||
render_error 409, "Already following this tag"
|
||||
rescue StandardError
|
||||
render_error 422, "Failed to process the tag followings request"
|
||||
end
|
||||
|
||||
def destroy
|
||||
tag_followings_service.destroy_by_name(params.require(:id))
|
||||
head :no_content
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_error 410, "Not following this tag"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tag_followings_service
|
||||
@tag_followings_service ||= TagFollowingService.new(current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
145
app/controllers/api/v1/users_controller.rb
Normal file
145
app/controllers/api/v1/users_controller.rb
Normal file
@@ -0,0 +1,145 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class UsersController < Api::V1::BaseController
|
||||
include TagsHelper
|
||||
|
||||
before_action except: %i[contacts update show] do
|
||||
require_access_token %w[public:read]
|
||||
end
|
||||
|
||||
before_action only: %i[update] do
|
||||
require_access_token %w[profile:modify]
|
||||
end
|
||||
|
||||
before_action only: %i[contacts] do
|
||||
require_access_token %w[contacts:read]
|
||||
end
|
||||
|
||||
before_action only: %i[block] do
|
||||
require_access_token %w[contacts:modify]
|
||||
end
|
||||
|
||||
before_action only: %i[show] do
|
||||
require_access_token %w[profile]
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render_error 404, "User not found"
|
||||
end
|
||||
|
||||
def show
|
||||
person = if params.has_key?(:id)
|
||||
found_person = Person.find_by!(guid: params[:id])
|
||||
raise ActiveRecord::RecordNotFound unless found_person.searchable || access_token?("contacts:read")
|
||||
|
||||
found_person
|
||||
else
|
||||
current_user.person
|
||||
end
|
||||
render json: PersonPresenter.new(person, current_user).profile_hash_as_api_json
|
||||
end
|
||||
|
||||
def update
|
||||
params_to_update = profile_update_params
|
||||
if params_to_update && current_user.update_profile(params_to_update)
|
||||
render json: PersonPresenter.new(current_user.person, current_user).profile_hash_as_api_json
|
||||
else
|
||||
render_error 422, "Failed to update the user settings"
|
||||
end
|
||||
rescue RuntimeError
|
||||
render_error 422, "Failed to update the user settings"
|
||||
end
|
||||
|
||||
def contacts
|
||||
if params.require(:user_id) != current_user.guid
|
||||
render_error 404, "User not found"
|
||||
return
|
||||
end
|
||||
|
||||
contacts_query = aspects_service.all_contacts
|
||||
contacts_page = index_pager(contacts_query).response
|
||||
contacts_page[:data] = contacts_page[:data].map {|c| PersonPresenter.new(c.person).as_api_json }
|
||||
render_paged_api_response contacts_page
|
||||
end
|
||||
|
||||
def photos
|
||||
person = Person.find_by!(guid: params[:user_id])
|
||||
user_for_query = current_user if private_read?
|
||||
photos_query = Photo.visible(user_for_query, person, :all, Time.current)
|
||||
photos_page = time_pager(photos_query).response
|
||||
photos_page[:data] = photos_page[:data].map {|photo| PhotoPresenter.new(photo).as_api_json(true) }
|
||||
render_paged_api_response photos_page
|
||||
end
|
||||
|
||||
def posts
|
||||
person = Person.find_by!(guid: params[:user_id])
|
||||
posts_query = if private_read?
|
||||
current_user.posts_from(person, false)
|
||||
else
|
||||
Post.where(author_id: person.id, public: true)
|
||||
end
|
||||
posts_page = time_pager(posts_query).response
|
||||
posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post, current_user).as_api_response }
|
||||
render_paged_api_response posts_page
|
||||
end
|
||||
|
||||
def block
|
||||
person = Person.find_by!(guid: params[:user_id])
|
||||
service = BlockService.new(current_user)
|
||||
if request.request_method_symbol == :post
|
||||
begin
|
||||
service.block(person)
|
||||
head :created
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
render_error 409, "User is already blocked"
|
||||
end
|
||||
elsif request.request_method_symbol == :delete
|
||||
begin
|
||||
service.unblock(person)
|
||||
head :no_content
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render_error 410, "User is not blocked"
|
||||
end
|
||||
else
|
||||
raise AbstractController::ActionNotFound
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def aspects_service
|
||||
@aspects_service ||= AspectsMembershipService.new(current_user)
|
||||
end
|
||||
|
||||
def profile_update_params
|
||||
raise RuntimeError if params.has_key?(:id)
|
||||
|
||||
updates = params.permit(:bio, :birthday, :gender, :location, :name,
|
||||
:searchable, :show_profile_info, :nsfw, :tags).to_h || {}
|
||||
if updates.has_key?(:name)
|
||||
updates[:first_name] = updates[:name]
|
||||
updates[:last_name] = nil
|
||||
updates.delete(:name)
|
||||
end
|
||||
if updates.has_key?(:show_profile_info)
|
||||
updates[:public_details] = updates[:show_profile_info]
|
||||
updates.delete(:show_profile_info)
|
||||
end
|
||||
process_tags_updates(updates)
|
||||
updates
|
||||
end
|
||||
|
||||
def process_tags_updates(updates)
|
||||
return unless params.has_key?(:tags)
|
||||
|
||||
raise RuntimeError if params[:tags].length > Profile::MAX_TAGS
|
||||
|
||||
tags = params[:tags].map {|tag| "#" + normalize_tag_name(tag) }.join(" ")
|
||||
updates[:tag_string] = tags
|
||||
updates.delete(:tags)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -7,7 +7,6 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
before_action :force_tablet_html
|
||||
has_mobile_fu
|
||||
protect_from_forgery except: :receive, with: :exception, prepend: true
|
||||
|
||||
rescue_from ActionController::InvalidAuthenticityToken do
|
||||
if user_signed_in?
|
||||
@@ -22,15 +21,12 @@ class ApplicationController < ActionController::Base
|
||||
before_action :ensure_http_referer_is_set
|
||||
before_action :set_locale
|
||||
before_action :set_diaspora_header
|
||||
before_action :set_grammatical_gender
|
||||
before_action :mobile_switch
|
||||
before_action :gon_set_current_user
|
||||
before_action :gon_set_appconfig
|
||||
before_action :gon_set_preloads
|
||||
before_action :configure_permitted_parameters, if: :devise_controller?
|
||||
|
||||
inflection_method grammatical_gender: :gender
|
||||
|
||||
helper_method :all_aspects,
|
||||
:all_contacts_count,
|
||||
:my_contacts_count,
|
||||
@@ -109,28 +105,6 @@ class ApplicationController < ActionController::Base
|
||||
redirect_to stream_url, notice: "you need to be an admin or moderator to do that"
|
||||
end
|
||||
|
||||
def set_grammatical_gender
|
||||
if (user_signed_in? && I18n.inflector.inflected_locale?)
|
||||
gender = current_user.gender.to_s.tr('!()[]"\'`*=|/\#.,-:', '').downcase
|
||||
unless gender.empty?
|
||||
i_langs = I18n.inflector.inflected_locales(:gender)
|
||||
i_langs.delete I18n.locale
|
||||
i_langs.unshift I18n.locale
|
||||
i_langs.each do |lang|
|
||||
token = I18n.inflector.true_token(gender, :gender, lang)
|
||||
unless token.nil?
|
||||
@grammatical_gender = token
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def grammatical_gender
|
||||
@grammatical_gender || nil
|
||||
end
|
||||
|
||||
# use :mobile view for mobile and :html for everything else
|
||||
# (except if explicitly specified, e.g. :json, :xml)
|
||||
def mobile_switch
|
||||
@@ -162,7 +136,6 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
def gon_set_appconfig
|
||||
gon.push(appConfig: {
|
||||
chat: {enabled: AppConfig.chat.enabled?},
|
||||
settings: {podname: AppConfig.settings.pod_name},
|
||||
map: {mapbox: {
|
||||
enabled: AppConfig.map.mapbox.enabled?,
|
||||
|
||||
@@ -11,20 +11,9 @@ class AspectMembershipsController < ApplicationController
|
||||
respond_to :json
|
||||
|
||||
def destroy
|
||||
aspect = current_user.aspects.joins(:aspect_memberships).where(aspect_memberships: {id: params[:id]}).first
|
||||
contact = current_user.contacts.joins(:aspect_memberships).where(aspect_memberships: {id: params[:id]}).first
|
||||
|
||||
raise ActiveRecord::RecordNotFound unless aspect.present? && contact.present?
|
||||
|
||||
raise Diaspora::NotMine unless current_user.mine?(aspect) &&
|
||||
current_user.mine?(contact)
|
||||
|
||||
membership = contact.aspect_memberships.where(aspect_id: aspect.id).first
|
||||
|
||||
raise ActiveRecord::RecordNotFound unless membership.present?
|
||||
|
||||
# do it!
|
||||
success = membership.destroy
|
||||
delete_results = AspectsMembershipService.new(current_user).destroy_by_membership_id(params[:id])
|
||||
success = delete_results[:success]
|
||||
membership = delete_results[:membership]
|
||||
|
||||
# set the flash message
|
||||
respond_to do |format|
|
||||
@@ -39,17 +28,12 @@ class AspectMembershipsController < ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
@person = Person.find(params[:person_id])
|
||||
@aspect = current_user.aspects.where(id: params[:aspect_id]).first
|
||||
aspect_membership = AspectsMembershipService.new(current_user).create(params[:aspect_id], params[:person_id])
|
||||
|
||||
@contact = current_user.share_with(@person, @aspect)
|
||||
|
||||
if @contact.present?
|
||||
if aspect_membership
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: AspectMembershipPresenter.new(
|
||||
AspectMembership.where(contact_id: @contact.id, aspect_id: @aspect.id).first)
|
||||
.base_hash
|
||||
render json: AspectMembershipPresenter.new(aspect_membership).base_hash
|
||||
end
|
||||
end
|
||||
else
|
||||
@@ -59,6 +43,12 @@ class AspectMembershipsController < ApplicationController
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue RuntimeError
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render plain: I18n.t("aspects.add_to_aspect.failure"), status: :conflict
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::StatementInvalid do
|
||||
|
||||
@@ -72,16 +72,10 @@ class AspectsController < ApplicationController
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def toggle_chat_privilege
|
||||
aspect.chat_enabled = !aspect.chat_enabled
|
||||
aspect.save
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def aspect
|
||||
@aspect ||= current_user.aspects.where(id: (params[:id] || params[:aspect_id])).first
|
||||
@aspect ||= current_user.aspects.where(id: params[:id]).first
|
||||
end
|
||||
|
||||
def connect_person_to_aspect(aspecting_person_id)
|
||||
@@ -95,6 +89,6 @@ class AspectsController < ApplicationController
|
||||
end
|
||||
|
||||
def aspect_params
|
||||
params.require(:aspect).permit(:name, :chat_enabled, :order_id)
|
||||
params.require(:aspect).permit(:name, :order_id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,9 +4,10 @@ class BlocksController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def create
|
||||
block = current_user.blocks.new(block_params)
|
||||
|
||||
send_message(block) if block.save
|
||||
begin
|
||||
block_service.block(Person.find_by!(id: block_params[:person_id]))
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head :no_content }
|
||||
@@ -15,13 +16,13 @@ class BlocksController < ApplicationController
|
||||
end
|
||||
|
||||
def destroy
|
||||
block = current_user.blocks.find_by(id: params[:id])
|
||||
notice = if block&.delete
|
||||
ContactRetraction.for(block).defer_dispatch(current_user)
|
||||
{notice: t("blocks.destroy.success")}
|
||||
else
|
||||
{error: t("blocks.destroy.failure")}
|
||||
end
|
||||
notice = nil
|
||||
begin
|
||||
block_service.remove_block(current_user.blocks.find_by!(id: params[:id]))
|
||||
notice = {notice: t("blocks.destroy.success")}
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
notice = {error: t("blocks.destroy.failure")}
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head :no_content }
|
||||
@@ -31,17 +32,11 @@ class BlocksController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def send_message(block)
|
||||
contact = current_user.contact_for(block.person)
|
||||
|
||||
if contact
|
||||
current_user.disconnect(contact)
|
||||
elsif block.person.remote?
|
||||
Diaspora::Federation::Dispatcher.defer_dispatch(current_user, block)
|
||||
end
|
||||
end
|
||||
|
||||
def block_params
|
||||
params.require(:block).permit(:person_id)
|
||||
end
|
||||
|
||||
def block_service
|
||||
BlockService.new(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,26 +17,11 @@ class CommentsController < ApplicationController
|
||||
authenticate_user!
|
||||
end
|
||||
|
||||
def create
|
||||
begin
|
||||
comment = comment_service.create(params[:post_id], params[:text])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render plain: I18n.t("comments.create.error"), status: 404
|
||||
return
|
||||
end
|
||||
|
||||
if comment
|
||||
respond_create_success(comment)
|
||||
else
|
||||
render plain: I18n.t("comments.create.error"), status: 422
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if comment_service.destroy(params[:id])
|
||||
respond_destroy_success
|
||||
else
|
||||
respond_destroy_error
|
||||
def index
|
||||
comments = comment_service.find_for_post(params[:post_id])
|
||||
respond_with do |format|
|
||||
format.json { render json: CommentPresenter.as_collection(comments, :as_json, current_user), status: :ok }
|
||||
format.mobile { render layout: false, locals: {comments: comments} }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -46,11 +31,26 @@ class CommentsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def index
|
||||
comments = comment_service.find_for_post(params[:post_id])
|
||||
respond_with do |format|
|
||||
format.json { render json: CommentPresenter.as_collection(comments), status: 200 }
|
||||
format.mobile { render layout: false, locals: {comments: comments} }
|
||||
def create
|
||||
begin
|
||||
comment = comment_service.create(params[:post_id], params[:text])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render plain: I18n.t("comments.create.error"), status: :not_found
|
||||
return
|
||||
end
|
||||
|
||||
if comment
|
||||
respond_create_success(comment)
|
||||
else
|
||||
render plain: I18n.t("comments.create.error"), status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
if comment_service.destroy(params[:id])
|
||||
respond_destroy_success
|
||||
else
|
||||
respond_destroy_error
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class InvitationsController < ApplicationController
|
||||
session[:invalid_email_invites] = invalid_emails
|
||||
|
||||
unless valid_emails.empty?
|
||||
Workers::Mail::InviteEmail.perform_async(valid_emails.join(","), current_user.id, inviter_params)
|
||||
Workers::Mail::InviteEmail.perform_async(valid_emails.join(","), current_user.id, inviter_params.to_h)
|
||||
end
|
||||
|
||||
if emails.empty?
|
||||
|
||||
@@ -16,8 +16,23 @@ class LikesController < ApplicationController
|
||||
authenticate_user!
|
||||
end
|
||||
|
||||
def index
|
||||
like = if params[:post_id]
|
||||
like_service.find_for_post(params[:post_id])
|
||||
else
|
||||
like_service.find_for_comment(params[:comment_id])
|
||||
end
|
||||
render json: like
|
||||
.includes(author: :profile)
|
||||
.as_api_response(:backbone)
|
||||
end
|
||||
|
||||
def create
|
||||
like = like_service.create(params[:post_id])
|
||||
like = if params[:post_id]
|
||||
like_service.create_for_post(params[:post_id])
|
||||
else
|
||||
like_service.create_for_comment(params[:comment_id])
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid
|
||||
render plain: I18n.t("likes.create.error"), status: 422
|
||||
else
|
||||
@@ -36,12 +51,6 @@ class LikesController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def index
|
||||
render json: like_service.find_for_post(params[:post_id])
|
||||
.includes(author: :profile)
|
||||
.as_api_response(:backbone)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def like_service
|
||||
|
||||
@@ -20,4 +20,14 @@ class NodeInfoController < ApplicationController
|
||||
format.all { @statistics = NodeInfoPresenter.new("1.0") }
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: this is only a dummy endpoint, because old versions of the ConnectionTester (<= 0.7.17.0)
|
||||
# checked for this endpoint. Remove this endpoint again once most pods are updated to >= 0.7.18.0
|
||||
def host_meta
|
||||
render xml: <<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
|
||||
</XRD>
|
||||
XML
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user