Compare commits
1,669 commits
develop
...
shigusegub
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dae4e5b2ac | ||
|
|
209637ee8a | ||
|
|
9d657395ff | ||
|
|
6d6c627c3e | ||
|
|
20162e5358 | ||
|
|
2e53707324 | ||
|
|
6124d9c04c | ||
|
|
f62e0c5718 | ||
|
|
3fbf21a757 | ||
|
|
a4845bf275 | ||
|
|
f7c67130f5 | ||
|
|
63bffe73db | ||
|
|
2ce11e56d4 | ||
|
|
dbdf81d8b3 | ||
|
|
496099bb00 | ||
|
|
6967151275 | ||
|
|
ea020cefdb | ||
|
|
6f3f75d9b4 | ||
|
|
2c4cb8a67a | ||
|
|
0e84cffa41 | ||
|
|
d2a870ac96 | ||
|
|
76d3ec1b39 | ||
|
|
85976a61b8 | ||
|
|
682ad334c1 | ||
|
|
2c673f439f | ||
|
|
71172ec93a | ||
|
|
d3d4b899d2 | ||
|
|
b08df84282 | ||
|
|
dbc9bd9c46 | ||
|
|
c9dede920e | ||
|
|
8fabbe9525 | ||
|
|
0688cdab86 | ||
|
|
5dc6ca6d58 | ||
|
|
dc96e5ac53 | ||
|
|
fc66830138 | ||
|
|
cd66dabf94 | ||
|
|
69656e0181 | ||
|
|
29e71c8a26 | ||
|
|
2881b31ff2 | ||
|
|
0f5c9ace4f | ||
|
|
9ae56174c9 | ||
|
|
db2f48a1f9 | ||
|
|
4cd6c6e9f0 | ||
|
|
fd2986ea85 | ||
|
|
b2011429c6 | ||
|
|
8dce2d75aa | ||
|
|
fbf01fce09 | ||
|
|
c87abc9eb7 | ||
|
|
9060977790 | ||
|
|
6750397ef7 | ||
|
|
58f06dea94 | ||
|
|
8985094644 | ||
|
|
13cd50886b | ||
|
|
c5aee1c012 | ||
|
|
92d0916f40 | ||
|
|
1fcc7c6ae8 | ||
|
|
c2f5840a02 | ||
|
|
1e93e0a9c3 | ||
|
|
848d48e404 | ||
|
|
20e781c71d | ||
|
|
e3a441310f | ||
|
|
3cdcfe19f4 | ||
|
|
74d83a996c | ||
|
|
3e71833cab | ||
|
|
d028a86013 | ||
|
|
18d8ea6b63 | ||
|
|
e554eeeef6 | ||
|
|
5e21134d9b | ||
|
|
573a980512 | ||
|
|
912aa228d1 | ||
|
|
095abb2914 | ||
|
|
617613dfb4 | ||
|
|
4156b1597a | ||
|
|
24ce2dc0a5 | ||
|
|
7e67b5274d | ||
|
|
cf77127335 |
||
|
|
02f952047d | ||
|
|
b9f1c33b50 | ||
|
|
98110c0596 | ||
|
|
127396d2af | ||
|
|
320899c9a2 | ||
|
|
69edded5b0 | ||
|
|
1e08616b1f | ||
|
|
dcb7ed1b8c | ||
|
|
aa25cd04b1 | ||
|
|
42930252b1 | ||
|
|
6f5eb6c442 | ||
|
|
e13e84e26d | ||
|
|
3d78a34daa | ||
|
|
ca10ffca8d | ||
|
|
4b87fa083e | ||
|
|
851c100a24 | ||
|
|
9e980dcd87 | ||
|
|
6fc8cc32ca | ||
|
|
b8bfd6b1a9 | ||
|
|
0dc8305e95 | ||
|
|
949aa90faa | ||
|
|
03b6178d17 | ||
|
|
d2f528bb15 | ||
|
|
0492a8d6a0 | ||
|
|
33c7876a8a | ||
|
|
7f54706821 | ||
|
|
99d2efac6c | ||
|
|
7ffec2c324 | ||
|
|
ae600da287 | ||
|
|
2197c030de | ||
|
|
85c2947714 | ||
|
|
f30537f25e | ||
|
|
d15642d93d | ||
|
|
b2964612ae | ||
|
|
e956d55219 | ||
|
|
04797f8bd2 | ||
|
|
7f54d11834 | ||
|
|
1c53ac84cc | ||
|
|
1654234e32 | ||
|
|
b05a501236 | ||
|
|
9262e803ec | ||
|
|
8372348148 | ||
|
|
b9a77cc61d | ||
|
|
1fe112c7f3 | ||
|
|
59b7da77c9 | ||
|
|
b33ded3a45 | ||
|
|
46cbef8253 | ||
|
|
308c5cd222 | ||
|
|
bbee089d64 | ||
|
|
fd03a5ade3 | ||
|
|
d7b1aaa616 | ||
|
|
971161a441 | ||
|
|
29fe616c1c | ||
|
|
50105a8f92 | ||
|
|
fb77d084d4 | ||
|
|
6ae05ab956 | ||
|
|
963c55cf71 | ||
|
|
fd086febfa | ||
|
|
202d1618c2 | ||
|
|
657de70153 | ||
|
|
c2732e3f40 | ||
|
|
c9b47a0ca9 | ||
|
|
3aae2f33d2 | ||
|
|
aa426b3d14 | ||
|
|
c71a36de30 | ||
|
|
7d88140bb4 | ||
|
|
db7e4a3434 | ||
|
|
4edf6b03ce | ||
|
|
25a9033b6b | ||
|
|
d26dca92e2 | ||
|
|
f97658b6d6 | ||
|
|
b9e6b9b1f0 | ||
|
|
d2d8b0167c | ||
|
|
3355f1d797 | ||
|
|
06608adec4 | ||
|
|
68aadcdd08 | ||
|
|
0222d493f8 | ||
|
|
bb802ed756 | ||
|
|
db3bfb6fc3 | ||
|
|
3ec21cb442 | ||
|
|
5b143b2aea | ||
|
|
96fc30a1b7 | ||
|
|
edbf5f3276 | ||
|
|
189b092d2c | ||
|
|
bf75c7af85 | ||
|
|
952800410e | ||
|
|
c085acd2dd | ||
|
|
e4a33bf6d7 | ||
|
|
625954721e | ||
|
|
76dd3540e4 | ||
|
|
494f6b471e | ||
|
|
1642d62b82 | ||
|
|
5a6f4fb466 | ||
|
|
13ace1d24e | ||
|
|
57dfbd8a53 | ||
|
|
576774540f | ||
|
|
caa0213ac6 | ||
|
|
a7fa7558b3 | ||
|
|
bdb992a8e5 | ||
|
|
bc47bef80d | ||
|
|
c4f83808b0 | ||
|
|
5aed9a20b8 | ||
|
|
e961b6e14c | ||
|
|
df8df5a0bf | ||
|
|
7d0f03fdba | ||
|
|
0bc5442eb9 | ||
|
|
4329502422 | ||
|
|
2fe5efc69d | ||
|
|
ace8295c03 | ||
|
|
d5c75915e6 | ||
|
|
d7453c09b2 | ||
|
|
9c043533f2 | ||
|
|
b0bce1bf18 | ||
|
|
48ba3892c3 | ||
|
|
ac751320f4 | ||
|
|
3fca18e248 | ||
|
|
56a6a25112 | ||
|
|
cdbf3f42b8 | ||
|
|
0a9a3648d6 | ||
|
|
5042db43ab | ||
|
|
cae19d8a9a | ||
|
|
311e9d255d | ||
|
|
3a321ca756 | ||
|
|
42a5da93ea | ||
|
|
b7a97b8603 | ||
|
|
c93f55e8f7 | ||
|
|
4a98ec9611 | ||
|
|
388ecd9a5e | ||
|
|
0252d39c75 | ||
|
|
235e6bd233 | ||
|
|
672bedaf6d | ||
|
|
55b5d2c5d7 | ||
|
|
b73c9ae4e8 | ||
|
|
5718483558 | ||
|
|
114d49b6d6 | ||
|
|
bc92f535de | ||
|
|
db535ae057 | ||
|
|
eae09226b5 | ||
|
|
16f456eaea | ||
|
|
489fb17070 | ||
|
|
0d04b1c8ce | ||
|
|
b38343705c | ||
|
|
3bc8800c35 | ||
|
|
e4c5a88913 | ||
|
|
ad13f2417f | ||
|
|
951dc87c09 | ||
|
|
7b02072133 | ||
|
|
2af0c83389 | ||
|
|
04a21e4698 | ||
|
|
41b6e80171 | ||
|
|
b519c0f3aa | ||
|
|
355a5955b3 | ||
|
|
dba63e6825 | ||
|
|
f24f164995 | ||
|
|
e6bda9638b | ||
|
|
e01753830d | ||
|
|
1e73c7e8cb | ||
|
|
94864276c1 | ||
|
|
2a3999bf42 | ||
|
|
171bb3b7ca | ||
|
|
23c5a6fab7 | ||
|
|
f7fc678c23 | ||
|
|
38a99b2f39 | ||
|
|
9fae1d086b | ||
|
|
6f9cd347df | ||
|
|
402cbf6593 | ||
|
|
452a522fa4 | ||
|
|
1fd6584374 | ||
|
|
ce04595e36 | ||
|
|
2d0bd043cb | ||
|
|
db73631459 | ||
|
|
b3bf4fca75 | ||
|
|
3716797e04 | ||
|
|
7c57be22e4 | ||
|
|
50ede338e7 | ||
|
|
b0f725671a | ||
|
|
fba7d15a2c | ||
|
|
9572b9704c | ||
|
|
63535b1494 | ||
|
|
8b8af2889b | ||
|
|
3f4ad34377 | ||
|
|
7d1799e929 | ||
|
|
8e6800fd1e | ||
|
|
e6f025bf6e | ||
|
|
5958c32acf | ||
|
|
a96f533777 | ||
|
|
a3a35e76a8 | ||
|
|
1683d98699 | ||
|
|
3601012cc2 | ||
|
|
848f2c7ddb | ||
|
|
043c02ff40 | ||
|
|
661ab34889 | ||
|
|
debd3a3e7b | ||
|
|
2f8ea4f3b3 | ||
|
|
30f9b84f08 | ||
|
|
dd910ff8a8 |
||
|
|
90f7dee343 | ||
|
|
f616c583f2 | ||
|
|
f0cf1da920 | ||
|
|
004bdd6b79 | ||
|
|
3286725510 | ||
|
|
ce048667f5 | ||
|
|
bc23d46615 | ||
|
|
aa7911665b | ||
|
|
7ba0b1d622 | ||
|
|
be0bf5e119 | ||
|
|
e9f1b29e1c | ||
|
|
b4cd8d8fab | ||
|
|
d5723bbf34 | ||
|
|
d67967dc78 | ||
|
|
59b65891af | ||
|
|
ff9127973e | ||
|
|
8cd50f6df3 | ||
|
|
b3b71fcf18 | ||
|
|
ce0921e208 | ||
|
|
e40e56a988 | ||
|
|
41bcc4c93e | ||
|
|
d2266303d1 | ||
|
|
6c965789d8 | ||
|
|
3196eb79de | ||
|
|
f3e897588b | ||
|
|
abe1b9c565 | ||
|
|
e0b76eeda6 | ||
|
|
811eb3d361 | ||
|
|
320a53835a | ||
|
|
00ba6b7c5d | ||
|
|
7a919e7c76 | ||
|
|
326d4976a3 | ||
|
|
df05a7a8a7 | ||
|
|
fb828b07f9 | ||
|
|
ed10af15e2 | ||
|
|
6341a48d05 | ||
|
|
53afb86da1 | ||
|
|
aa7d5c0efd | ||
|
|
8353db33ad | ||
|
|
40eb565f2a | ||
|
|
0ecbae9675 | ||
|
|
6e1c65a574 | ||
|
|
b7c75dcba2 | ||
|
|
205e03ea15 | ||
|
|
f9848daf48 | ||
|
|
30917ff017 | ||
|
|
f9570b132f | ||
|
|
473133aa02 | ||
|
|
a62ffbfbab | ||
|
|
2551947ee6 | ||
|
|
e3bfbcf0d2 | ||
|
|
683b2d7fea | ||
|
|
0fe823aeec | ||
|
|
b46c8358ae | ||
|
|
65b40f8f72 | ||
|
|
6b4057c6ab | ||
|
|
2a69abf374 | ||
|
|
0560110868 | ||
|
|
3df779f02f | ||
|
|
0b78c64928 | ||
|
|
9b8bccd27d | ||
|
|
2854b012c9 | ||
|
|
82b560ccf2 | ||
|
|
288ad9dd06 | ||
|
|
fdaaf333c5 | ||
|
|
2601c09660 | ||
|
|
38fc7f784e | ||
|
|
4c4144f1eb | ||
|
|
088be118c3 | ||
|
|
58ce121010 | ||
|
|
7e04269b7e | ||
|
|
3bacacd214 | ||
|
|
b3f5edb913 | ||
|
|
785c8d518a | ||
|
|
a5af76603f | ||
|
|
589737dced | ||
|
|
542db846a6 | ||
|
|
798178a86f | ||
|
|
8f320faa2d | ||
|
|
528cc9f388 | ||
|
|
17c04d4d83 | ||
|
|
0b9547b289 | ||
|
|
0ad18d3e5e | ||
|
|
0424992a35 | ||
|
|
4ce9a011da | ||
|
|
91ab21b37b | ||
|
|
b3ba505e51 | ||
|
|
7678b7f597 | ||
|
|
d148f36474 | ||
|
|
f252585b7f | ||
|
|
4aa27c5be0 | ||
|
|
bebb3fcfa6 | ||
|
|
8780e0191e | ||
|
|
26a2232e18 | ||
|
|
0d6453baec | ||
|
|
80b0117ba2 | ||
|
|
7b3ef62b96 | ||
|
|
15239cf92d | ||
|
|
3ba9c01e15 | ||
|
|
7ff72e5ae2 | ||
|
|
48651518df | ||
|
|
202f5d87b3 | ||
|
|
7b2f3b648a | ||
|
|
16ada7ec6c | ||
|
|
028556f8ab | ||
|
|
9ed2fd3c4b | ||
|
|
c47bfe53ff | ||
|
|
35fea49bd0 | ||
|
|
866b416dbe | ||
|
|
1c0aa025a3 | ||
|
|
bfd1809a34 | ||
|
|
96d886d213 | ||
|
|
1a0f4c8c34 | ||
|
|
9b58b8c290 | ||
|
|
9f481052d0 | ||
|
|
69726895b1 | ||
|
|
e560b1d6c7 | ||
|
|
c65c0afce3 | ||
|
|
c7ad009496 | ||
|
|
f0250ffc52 | ||
|
|
267692f56a | ||
|
|
49a98a6fe2 | ||
|
|
4c17458e58 | ||
|
|
6399de114c | ||
|
|
bfb9f6c5e2 | ||
|
|
e076403fcb | ||
|
|
97adff267f | ||
|
|
5bb0424397 | ||
|
|
00c3719618 | ||
|
|
aa5688a43b | ||
|
|
f35633c855 | ||
|
|
6e479d246b | ||
|
|
0b9b7a51a6 | ||
|
|
a6c844e522 | ||
|
|
2869ab787d | ||
|
|
22a14d8985 | ||
|
|
ce5638d223 | ||
|
|
2a2ffff992 | ||
|
|
0a4c867519 | ||
|
|
4886b34c2f | ||
|
|
f6cdf06ba1 | ||
|
|
adbeb9d0dd | ||
|
|
8958f70a48 | ||
|
|
f7fca3a1ba | ||
|
|
918fbecf3e | ||
|
|
25d54cbeea | ||
|
|
be3170b50b | ||
|
|
6f29f9e735 | ||
|
|
1e4544b2ca | ||
|
|
286bb6aa97 | ||
|
|
9d16aa5ace | ||
|
|
72cd6b40ea | ||
|
|
e3a7d3dca8 | ||
|
|
4ba1e55f05 | ||
|
|
24c0e0e497 | ||
|
|
bf4dbaf077 | ||
|
|
a6a2dbd2f7 | ||
|
|
c3872147c0 | ||
|
|
e04d7d2c97 | ||
|
|
0ccff2019f | ||
|
|
04c180e0d9 | ||
|
|
ec635426c3 | ||
|
|
a0159f1e18 | ||
|
|
d62393bf6b | ||
|
|
1d3b271e7c | ||
|
|
6e5da62233 | ||
|
|
b80035cbb0 | ||
|
|
047dda5525 | ||
|
|
e82de98892 | ||
|
|
cf4aa692e3 | ||
|
|
8f16da2f6f | ||
|
|
1f53c8bb07 | ||
|
|
2830b55d41 | ||
|
|
f86cc5d8b5 | ||
|
|
8b8975adb2 | ||
|
|
2aabaeb5c6 | ||
|
|
67f606a3b0 | ||
|
|
9440d35266 | ||
|
|
6341747ec9 | ||
|
|
9ec2ff409d | ||
|
|
4ff257be57 | ||
|
|
5e77a0a23d | ||
|
|
370a7f8291 | ||
|
|
0f51550802 | ||
|
|
700e096dd4 | ||
|
|
d879b6f6eb | ||
|
|
8d141cbeab | ||
|
|
81bb4f133b | ||
|
|
a4de299c58 | ||
|
|
11e6349e8d | ||
|
|
409816748e | ||
|
|
cfb4868c55 | ||
|
|
f2783260f1 | ||
|
|
7fd46f2d6c | ||
|
|
f27099a4b3 | ||
|
|
18110d6821 | ||
|
|
202bfbad02 | ||
|
|
7b643b5486 | ||
|
|
1c4f19e56f | ||
|
|
d66dd17f7b | ||
|
|
76e67a08c6 | ||
|
|
ebe727b378 | ||
|
|
99886ac28c | ||
|
|
d4c0ccf659 | ||
|
|
6e44a3afa9 | ||
|
|
ee01395071 | ||
|
|
fa67b2330f | ||
|
|
7e9fd4d1dd | ||
|
|
bc2964c327 | ||
|
|
3311c676ad | ||
|
|
184f1cdc24 | ||
|
|
8268d0d349 | ||
|
|
a4e6a72ca2 | ||
|
|
49422705a8 | ||
|
|
d34ce95d17 | ||
|
|
fc90a92ecf | ||
|
|
ea3e054c21 | ||
|
|
d89f564b5e | ||
|
|
a4802030be | ||
|
|
86f8f46b95 | ||
|
|
41267a5d43 | ||
|
|
c8fa72c791 | ||
|
|
800ab90cf9 | ||
|
|
59de80639f | ||
|
|
f79c61c4e7 | ||
|
|
b305748a92 | ||
|
|
7d985bd475 | ||
|
|
60363e66fb | ||
|
|
50314fe253 | ||
|
|
20beb30fc3 | ||
|
|
2df895ab02 | ||
|
|
51eb61180d | ||
|
|
38b9b04385 | ||
|
|
28422adc8c | ||
|
|
3c2c572661 | ||
|
|
39602f36bb | ||
|
|
68c88677a0 | ||
|
|
d82ab81e4e | ||
|
|
66b83558bb | ||
|
|
9a85c3f8fb | ||
|
|
34c29e4fdc | ||
|
|
6e7ad5d554 | ||
|
|
07a6d58660 | ||
|
|
ddcd9007dc | ||
|
|
f8a5918fc8 | ||
|
|
bb044315cd | ||
|
|
426fb90522 | ||
|
|
2008fcce22 | ||
|
|
ba5efe4a67 | ||
|
|
83ad4078bb | ||
|
|
41d794d3ab | ||
|
|
eb68857293 | ||
|
|
add9535b1a | ||
|
|
1fdb676eca | ||
|
|
9e77071e77 | ||
|
|
f5d167950d | ||
|
|
4ffd8499a7 | ||
|
|
b05ebeaed5 | ||
|
|
518144f308 | ||
|
|
731e9eed6e | ||
|
|
0b1e7dbcea | ||
|
|
ef0a4f4023 | ||
|
|
2c1bea0e0b | ||
|
|
d299e864bf | ||
|
|
79868aeeb9 | ||
|
|
38130fce90 | ||
|
|
3294df0325 | ||
|
|
0e56f8f103 | ||
|
|
e747ee896e | ||
|
|
e1cbb1ccd2 | ||
|
|
04ee1a892c | ||
|
|
23975b506e | ||
|
|
c12e1a69cf | ||
|
|
6aae8a8705 | ||
|
|
6ae52e192b | ||
|
|
eb77c5dbb9 | ||
|
|
b014489295 | ||
|
|
35c2e87131 | ||
|
|
9f0b65654e | ||
|
|
2441e6508d | ||
|
|
71d1baffcc | ||
|
|
8436f39eff | ||
|
|
385f921c41 | ||
|
|
74b410da2b | ||
|
|
d9639c543c | ||
|
|
c433aa38fb | ||
|
|
a626b37354 | ||
|
|
2e9f23542c | ||
|
|
1d729cb0c5 | ||
|
|
0f573637a7 | ||
|
|
b2f7309e1e | ||
|
|
c179daaf80 | ||
|
|
558251ce74 | ||
|
|
7bc5dd440b | ||
|
|
a635f025be | ||
|
|
1bc53262d6 | ||
|
|
d6ebc5049e | ||
|
|
3081504c64 | ||
|
|
dc531d4ef3 | ||
|
|
5a6a77bd75 | ||
|
|
bc96d16e11 | ||
|
|
82d67a634e | ||
|
|
3822aaf137 | ||
|
|
0d32a7ddac | ||
|
|
d72438f0a7 | ||
|
|
e66b1edcf4 | ||
|
|
389ca8e151 | ||
|
|
114257359c | ||
|
|
ea519c0cf3 | ||
|
|
4b5e6804a9 | ||
|
|
5dc0048eac | ||
|
|
c5c27a2167 | ||
|
|
917d0b5b23 | ||
|
|
cf9c91dd02 | ||
|
|
79f4e44028 | ||
|
|
90fba141d7 | ||
|
|
466ff72067 | ||
|
|
ba48c875db | ||
|
|
8dc45514ef | ||
|
|
9fbf426388 | ||
|
|
ee0c73446e | ||
|
|
5cafdca855 | ||
|
|
ec367ad761 | ||
|
|
1a577cfcfc | ||
|
|
71fdae1d8f | ||
|
|
61fcc48854 | ||
|
|
d25b909883 | ||
|
|
1dfa99c74f | ||
|
|
d7fb073908 | ||
|
|
f304016a45 | ||
|
|
100049c94d | ||
|
|
714ed1040b | ||
|
|
40a17862a5 | ||
|
|
8483268cb3 | ||
|
|
230e61235d | ||
|
|
e4b44f8c7b | ||
|
|
046b959bd7 | ||
|
|
4146c071ce | ||
|
|
b3abc5f9c3 | ||
|
|
c5f2c3ac9e |
||
|
|
0a8d7ea659 | ||
|
|
0851ec894b | ||
|
|
7702c209d5 | ||
|
|
587be9afc0 | ||
|
|
da1830f9b4 | ||
|
|
37225ae8db | ||
|
|
8e9f0ae4a5 | ||
|
|
ec747c0818 | ||
|
|
917fc1def0 | ||
|
|
a1f43234cd | ||
|
|
128a8ab9f7 | ||
|
|
8aee7b7958 | ||
|
|
3d848e093d | ||
|
|
24f9ce021c | ||
|
|
a2801e45a2 | ||
|
|
17bdc6e5b0 |
||
|
|
192db80db4 | ||
|
|
2ef96ecc19 | ||
|
|
168430fff2 | ||
|
|
acd43a8593 | ||
|
|
187d43a4e5 | ||
|
|
97497b5945 | ||
|
|
9d31bdf2d9 | ||
|
|
f4db0dbdd4 | ||
|
|
537031f0cb | ||
|
|
598bdb6132 | ||
|
|
f0b1255a3c | ||
|
|
ad2689a5eb | ||
|
|
645585b033 | ||
|
|
a171f5cbe7 | ||
|
|
2bf584b89f | ||
|
|
feb630ef1f | ||
|
|
4c3626574d | ||
|
|
70c89b0cb9 | ||
|
|
96c57a8ada | ||
|
|
26b6f78f0b | ||
|
|
f965d874ee | ||
|
|
15a79c2a24 | ||
|
|
78c92e15d6 | ||
|
|
21978806fb | ||
|
|
30152e3780 | ||
|
|
77a2457f09 | ||
|
|
b5fd56c790 | ||
|
|
ac8519c166 | ||
|
|
f36f11045e | ||
|
|
c9a4aee954 | ||
|
|
b9161ef697 | ||
|
|
afce8c715d | ||
|
|
8259526be6 | ||
|
|
3390377a26 | ||
|
|
e228080160 |
||
|
|
4ee26c6041 |
||
|
|
62c5939df6 | ||
|
|
60e5c3b042 | ||
|
|
2f7f0cdcef | ||
|
|
84f77f2948 | ||
|
|
7f30b3291a | ||
|
|
e8ee3d474c | ||
|
|
8fa2781745 | ||
|
|
fc8fdf7bc3 | ||
|
|
921c6fd202 | ||
|
|
3feccb7ebc | ||
|
|
81daf2d011 | ||
|
|
c53745b367 | ||
|
|
8ebf4a9b7b | ||
|
|
66531deac8 | ||
|
|
98917a4fb9 | ||
|
|
d8fdef42ce | ||
|
|
61459a348d | ||
|
|
ac6c3d0848 | ||
|
|
45a2ad419e | ||
|
|
4978404f4d | ||
|
|
484c4aed4c | ||
|
|
27f753e8de |
||
|
|
a63e7f1b6f | ||
|
|
6414b3605a | ||
|
|
44a0ad2f82 | ||
|
|
84cafd2fe1 | ||
|
|
15fe8d8c15 | ||
|
|
6adfd81e71 | ||
|
|
14f0ec50e7 | ||
|
|
d0aa1ec85f | ||
|
|
e9d661a0b8 | ||
|
|
cd9555cd87 | ||
|
|
3efc41b312 | ||
|
|
1fa2888e2a | ||
|
|
3c4749e709 | ||
|
|
13f590cd35 | ||
|
|
4b72b77839 | ||
|
|
92e4a53a60 | ||
|
|
459d452d4d | ||
|
|
f30fbc04c1 | ||
|
|
d847a3fdc2 | ||
|
|
f1ffde68d7 | ||
|
|
b1bfffc006 | ||
|
|
393aebb21c | ||
|
|
4bdbdb3920 | ||
|
|
8c28133c72 | ||
|
|
7981473b23 | ||
|
|
dc65bbc051 | ||
|
|
2d8f99f86b | ||
|
|
d0f894b653 | ||
|
|
d701595173 | ||
|
|
35d51a9aac | ||
|
|
a0cfbd3e78 | ||
|
|
8f7cca96a5 | ||
|
|
f2e1fc1570 | ||
|
|
0e43b3c970 | ||
|
|
a78f0de459 | ||
|
|
bc84ec7efa | ||
|
|
bfe29987db | ||
|
|
88c79c2536 | ||
|
|
22cdf2ded5 | ||
|
|
f4c56be826 | ||
|
|
d70254a8bf | ||
|
|
f112724a5a | ||
|
|
c31e9466b7 | ||
|
|
8b298c800f | ||
|
|
67d51b2602 | ||
|
|
2fbea80d50 | ||
|
|
9a88746547 | ||
|
|
56da80125e | ||
|
|
dc16e2c1cc | ||
|
|
cf41556217 | ||
|
|
bfd271a69f | ||
|
|
45e6e03a03 | ||
|
|
1dc22e2678 | ||
|
|
540ea2f3c0 | ||
|
|
4d5333102e | ||
|
|
2be826c763 | ||
|
|
c0edf265ca | ||
|
|
d9e4a3a4a8 | ||
|
|
f16e61035b | ||
|
|
7ced58d89e | ||
|
|
9f5fd5799d | ||
|
|
ed27be1efd | ||
|
|
f3446051e8 | ||
|
|
b59aafe900 | ||
|
|
75927f66b1 | ||
|
|
56bce80674 | ||
|
|
41dd15a965 | ||
|
|
707428bdc7 | ||
|
|
a3c760912a | ||
|
|
5fa922a7f6 | ||
|
|
71c277296b | ||
|
|
47d4cb8f17 | ||
|
|
2d49ff4fe5 | ||
|
|
9b41315511 | ||
|
|
cf9421846d | ||
|
|
da788eff7a | ||
|
|
706975e41d | ||
|
|
5de788238c | ||
|
|
887d14b46e | ||
|
|
49e164f9ea | ||
|
|
05aacaa332 | ||
|
|
42cd7babdc | ||
|
|
5ec6f8663a | ||
|
|
dd580d4830 | ||
|
|
fd58f3b320 | ||
|
|
7afdf3e70c | ||
|
|
6184229cda | ||
|
|
1b62640325 | ||
|
|
4597d8a967 | ||
|
|
a16a5cc538 | ||
|
|
5c5a350447 | ||
|
|
36ce62efab | ||
|
|
a1ed45593c | ||
|
|
ca75891668 | ||
|
|
76616461e9 | ||
|
|
c5d4545698 | ||
|
|
4fd8895252 | ||
|
|
ec07e2282b | ||
|
|
5e9fb8347b | ||
|
|
356a45857f | ||
|
|
a201ac9239 | ||
|
|
07615e117f | ||
|
|
33226d3f66 | ||
|
|
20d016ed74 | ||
|
|
8533793c47 | ||
|
|
9fa41c7208 | ||
|
|
380f4a624e | ||
|
|
bf40b7b762 | ||
|
|
96f31a0b62 | ||
|
|
d9931bd8e4 | ||
|
|
3b2f1fbbc1 | ||
|
|
f6a2d1bdd6 | ||
|
|
673d8490f5 | ||
|
|
29fb47a767 | ||
|
|
ece5831de3 | ||
|
|
0ab4ce288b | ||
|
|
b0e6ea096a | ||
|
|
a81b477350 | ||
|
|
1c9f15e285 | ||
|
|
bf905abce5 | ||
|
|
75f09fb6fe | ||
|
|
101cb3206e | ||
|
|
4fe5a97813 | ||
|
|
3ba45efc2e | ||
|
|
0653636b27 | ||
|
|
24427de6e7 | ||
|
|
cc8480e8ea | ||
|
|
eda51ae486 | ||
|
|
52a2745a63 | ||
|
|
202c982bc2 | ||
|
|
390e0c5491 | ||
|
|
0db70f787a | ||
|
|
661913e063 | ||
|
|
1274665911 | ||
|
|
14960cf6de | ||
|
|
9d0706d760 | ||
|
|
f90b06cea4 | ||
|
|
f4168fea42 | ||
|
|
246160d966 | ||
|
|
6debf2e2a1 | ||
|
|
cc6930e465 | ||
|
|
64a9b375e5 | ||
|
|
5e9714015c | ||
|
|
ebd4b0ee95 | ||
|
|
dbbd5f067b | ||
|
|
dd50bc8d16 | ||
|
|
845d091c3e | ||
|
|
dfbd17ea37 | ||
|
|
15604bcd7b | ||
|
|
a08f0e7128 | ||
|
|
fcf612fbe1 | ||
|
|
6fdf192816 | ||
|
|
7e0bc054d0 | ||
|
|
a2d9c4db6a | ||
|
|
926f45df85 | ||
|
|
00eabd2447 | ||
|
|
6160f01c99 | ||
|
|
d1cc6ecfb9 | ||
|
|
05d86255fd | ||
|
|
24b6ad45a8 | ||
|
|
1272c3e905 | ||
|
|
55be9965db | ||
|
|
8318f2c39e | ||
|
|
11adf48f4a | ||
|
|
aa51c42b7f | ||
|
|
dcc2acfe5d |
||
|
|
d49a4196c7 |
||
|
|
e36f002a7e | ||
|
|
d6c8024614 | ||
|
|
671e975364 | ||
|
|
c087e91a15 | ||
|
|
984164d206 | ||
|
|
72ebbd93f6 | ||
|
|
7cda66b513 | ||
|
|
10b334b033 | ||
|
|
34bef1074e | ||
|
|
8d640d0b79 | ||
|
|
57aa8818a9 | ||
|
|
5d47ac04b0 | ||
|
|
f1910b37d3 | ||
|
|
46f904e0e4 | ||
|
|
7fbff6e899 | ||
|
|
ae68a571c2 | ||
|
|
addc460aff | ||
|
|
13a5746978 | ||
|
|
d5e7edaa3d | ||
|
|
7115b0e22c | ||
|
|
f347897b29 | ||
|
|
eaf7efdcff | ||
|
|
d712e7ddc5 | ||
|
|
b8c84094b8 | ||
|
|
b4458cb45b | ||
|
|
882a80b04f |
||
|
|
4840177c8c |
||
|
|
59c090ae2d |
||
|
|
5e880ed54f | ||
|
|
9d04e34507 | ||
|
|
5461289cb7 | ||
|
|
3a25a69296 | ||
|
|
c8e3392ae4 | ||
|
|
e566c98670 | ||
|
|
94640c3df1 | ||
|
|
804279bca2 | ||
|
|
f91f377ce0 | ||
|
|
280fbef3b9 | ||
|
|
4b51432ff1 | ||
|
|
3f062fb5b6 | ||
|
|
76c1ab13ec | ||
|
|
1bb68b43f6 | ||
|
|
290056e818 | ||
|
|
3cc635407c | ||
|
|
15d20fa256 | ||
|
|
390aad6135 | ||
|
|
ebdd8c281d | ||
|
|
29729e63c7 | ||
|
|
851b39bdd7 | ||
|
|
d37c9afc01 | ||
|
|
598c569c93 | ||
|
|
ccce538823 | ||
|
|
eab2c35b15 | ||
|
|
c3309d9367 | ||
|
|
b1dda75d0d | ||
|
|
8e94212151 | ||
|
|
8525966d90 | ||
|
|
9d9549532b | ||
|
|
cd8bab0cdb | ||
|
|
a9fa92e9bb | ||
|
|
fef9121fce | ||
|
|
225352e090 | ||
|
|
8e8f03767f | ||
|
|
73ce22f600 | ||
|
|
8331761329 | ||
|
|
099e493298 |
||
|
|
50e51437c7 |
||
|
|
216d318bb5 |
||
|
|
400829fdde | ||
|
|
bec7fbf7b2 | ||
|
|
f64a48190b | ||
|
|
7b9d192d51 | ||
|
|
42a88fbb13 | ||
|
|
5ac676bd21 | ||
|
|
9f1f37ec7e | ||
|
|
1adb8236d9 | ||
|
|
c4affccf6d | ||
|
|
d8e43175f4 | ||
|
|
a5cc7351ec | ||
|
|
68357fb890 | ||
|
|
7bd54815c4 | ||
|
|
ce24132c33 |
||
|
|
589d2ef254 | ||
|
|
7f28011caa | ||
|
|
ea13874b50 | ||
|
|
752554183f | ||
|
|
f0ba4acd15 | ||
|
|
7ba5e6d31c | ||
|
|
0a1a8f0bdd | ||
|
|
d19d28afe2 | ||
|
|
7661bb69ff |
||
|
|
3a4cdbef15 |
||
|
|
1bdf67c08d | ||
|
|
081890be7c | ||
|
|
5d7aabe7ad | ||
|
|
d8802ad20d | ||
|
|
4b6b420f70 | ||
|
|
42782f2dde | ||
|
|
f7b5f8d27d | ||
|
|
4f14edaa9e | ||
|
|
ce29e7918b | ||
|
|
94d8c4b812 | ||
|
|
d0827dbdec | ||
|
|
236ebbffff | ||
|
|
a3862ba0bc | ||
|
|
8a2a2554d2 | ||
|
|
9e2ee1e66a | ||
|
|
81f7a38ecc | ||
|
|
10d33f2530 | ||
|
|
f5f0f8ce6e | ||
|
|
36860b7893 | ||
|
|
5ccaca24ce | ||
|
|
1f89e122a6 | ||
|
|
32839357f5 | ||
|
|
7f880dea0e |
||
|
|
0190a698db | ||
|
|
0cb47652b8 |
||
|
|
652781fcc8 |
||
|
|
8b8f6cbca2 |
||
|
|
bbeafab1ef |
||
|
|
e21bac3d70 |
||
|
|
67724ad2a4 |
||
|
|
3315901b07 |
||
|
|
cc4d4ccbeb |
||
|
|
ed04c2ac71 |
||
|
|
6ab98db7ad |
||
|
|
f1f7f5de51 |
||
|
|
63490ad2da |
||
|
|
2b16dd55c4 |
||
|
|
a959104333 |
||
|
|
aa6b390a6f | ||
|
|
927f68418e | ||
|
|
49ed5d7da0 | ||
|
|
6fc42b123a | ||
|
|
fe436e33f2 | ||
|
|
5eb9c030fd | ||
|
|
4a573125e2 | ||
|
|
157e227f6f | ||
|
|
44bee26551 |
||
|
|
0ea6cd008a |
||
|
|
b51f870cf0 |
||
|
|
a3b4076f63 |
||
|
|
eede2f5aff |
||
|
|
1ede2a19f4 |
||
|
|
80cf7733b5 |
||
|
|
baad129b49 |
||
|
|
eb6d9cdd4b |
||
|
|
cca5e31f56 |
||
|
|
b46de85926 |
||
|
|
25de264abb |
||
|
|
9dcdd421ac | ||
|
|
bb70c50c6b |
||
|
|
0822940594 | ||
|
|
1602d35f97 | ||
|
|
c2d2ec7e18 | ||
|
|
f2c1ad9eee | ||
|
|
95b75fc1d9 | ||
|
|
fffda79ee2 | ||
|
|
843a1d0859 | ||
|
|
bf28482493 | ||
|
|
dd927227d9 | ||
|
|
690812f27c |
||
|
|
be929cab9b |
||
|
|
bc0b13ac0d |
||
|
|
1c0d0f8d9a | ||
|
|
41d156806e | ||
|
|
9e2086edaf | ||
|
|
c06780ed18 | ||
|
|
91bb53c04d | ||
|
|
a72a7b2920 | ||
|
|
751c24a312 | ||
|
|
c2f577e9fa | ||
|
|
b0af90a629 | ||
|
|
c9cba41728 | ||
|
|
0509a38730 | ||
|
|
f28fc7a2fd | ||
|
|
7bab1e4259 | ||
|
|
8912664ab3 | ||
|
|
bcc9d9b851 | ||
|
|
97c9b6e25d | ||
|
|
f7f09f0dbc | ||
|
|
bcea217705 | ||
|
|
4e130375a7 | ||
|
|
74fe330e4a | ||
|
|
29a45c6415 | ||
|
|
606e3c012d | ||
|
|
b81692017d | ||
|
|
89ba855e45 | ||
|
|
3b2baa45d0 | ||
|
|
babe3697db | ||
|
|
c14f88e290 | ||
|
|
17d562b979 | ||
|
|
2097192469 | ||
|
|
7def989ec2 | ||
|
|
96a236ed28 | ||
|
|
afb9c0aa99 | ||
|
|
6a308138f9 | ||
|
|
6db7942d88 | ||
|
|
bf2101124d | ||
|
|
7ade2bf1b5 | ||
|
|
aadf86dee6 | ||
|
|
2a1a4d63a1 | ||
|
|
8fe85e75d1 | ||
|
|
78a5377e77 | ||
|
|
e7c8e77a7f | ||
|
|
7bfee03530 | ||
|
|
7d2fd609ef | ||
|
|
3627aadead | ||
|
|
da73226cb1 | ||
|
|
2e78975e4c | ||
|
|
f8fa540af7 | ||
|
|
3c465e9a52 | ||
|
|
0db0850402 | ||
|
|
46921c9fc8 | ||
|
|
a583432b4b | ||
|
|
04b3d3bedf | ||
|
|
54cb7d2bd3 | ||
|
|
55c181c49d | ||
|
|
2af00f7c7e |
||
|
|
6279edc135 | ||
|
|
24f8255d2d | ||
|
|
3eff845028 | ||
|
|
bb882afc76 | ||
|
|
471df530b1 | ||
|
|
7aee12bcd4 | ||
|
|
e0b9940535 | ||
|
|
081b2f66f2 | ||
|
|
78907571cb | ||
|
|
143a670858 | ||
|
|
21af30f71b | ||
|
|
9187c1d31c | ||
|
|
f659c9c123 | ||
|
|
37f90e3702 | ||
|
|
d1ea589531 | ||
|
|
d9aca590bb | ||
|
|
bb2de0f567 | ||
|
|
da48e1ecbf | ||
|
|
9ebd7a78ac | ||
|
|
1f56401a8e | ||
|
|
6dfb213dfe | ||
|
|
a5c4853987 | ||
|
|
acd4453380 | ||
|
|
66f35f15af | ||
|
|
292e0dab08 | ||
|
|
31609da231 | ||
|
|
d5b554f5af | ||
|
|
391f7e0711 | ||
|
|
ac44c1bc11 | ||
|
|
66963427bd | ||
|
|
78cbbf651d | ||
|
|
57fd447038 | ||
|
|
64cf4c7bb8 | ||
|
|
fe4867474b | ||
|
|
5ef527d747 | ||
|
|
b94d389b7f | ||
|
|
a2a0559c8f | ||
|
|
5b3484dd7d | ||
|
|
7d19cc2d53 | ||
|
|
dbbae642ef | ||
|
|
0c4fd2d50d | ||
|
|
af5729844e | ||
|
|
85a063f056 | ||
|
|
8bf12a5359 | ||
|
|
c926ed7ac1 | ||
|
|
58e18d48df | ||
|
|
cd46f324d2 | ||
|
|
ec11bdfc2b | ||
|
|
cb19ffb95e | ||
|
|
06b0ccde99 | ||
|
|
88067c607b | ||
|
|
02c5ee9c49 | ||
|
|
28a38d413d | ||
|
|
77a84c3009 | ||
|
|
e89e505753 | ||
|
|
f4685c59b8 | ||
|
|
c4d3d9def4 | ||
|
|
cdc541e81d | ||
|
|
02e1e5cca4 | ||
|
|
0d2295e065 | ||
|
|
ee155f15b7 | ||
|
|
b7b0d8c6af | ||
|
|
753f5fa65d | ||
|
|
d7f76f2f91 | ||
|
|
131c763706 | ||
|
|
628167c5b9 | ||
|
|
f449b52813 | ||
|
|
55579bea55 | ||
|
|
02cc040cd6 | ||
|
|
7f9fe6b660 | ||
|
|
c16d71eb94 | ||
|
|
06d82896a1 | ||
|
|
c9aed38127 | ||
|
|
a7bdd3e548 | ||
|
|
625629847b | ||
|
|
0ab8fb98a5 | ||
|
|
65a05e5374 | ||
|
|
4ea53f3776 | ||
|
|
e690ce193b | ||
|
|
d01e069bf2 | ||
|
|
8be36ae07b | ||
|
|
031f8e65e6 | ||
|
|
92c82a0a34 | ||
|
|
8c5fab4f3c | ||
|
|
8880843824 | ||
|
|
33525eeaef | ||
|
|
6a1da89297 | ||
|
|
3490716365 | ||
|
|
24a57a03a3 | ||
|
|
cafa8da357 | ||
|
|
edad925e64 | ||
|
|
50202255b8 | ||
|
|
d9154224db | ||
|
|
567931bc88 | ||
|
|
786ee58de5 | ||
|
|
446acb5590 | ||
|
|
2acbde639e | ||
|
|
09240a0502 | ||
|
|
8fe5e74e9d | ||
|
|
aaf77a659c | ||
|
|
8701766e98 | ||
|
|
b814da845f | ||
|
|
00dda09021 | ||
|
|
7c0f8fe002 | ||
|
|
ae6c3078f6 | ||
|
|
4a74e83860 | ||
|
|
4a41fdd606 | ||
|
|
e08b74952e | ||
|
|
549528a774 | ||
|
|
8e4f05bee1 | ||
|
|
291a7f2ed5 | ||
|
|
64c3807881 | ||
|
|
e67ae13f4e | ||
|
|
ebd3b7d9f5 | ||
|
|
d6e0867d66 | ||
|
|
eea173cf7e | ||
|
|
bd30ef5f81 | ||
|
|
5c92e91f98 | ||
|
|
4c732a8ca5 | ||
|
|
a3966ce225 | ||
|
|
7a4efdb5a8 | ||
|
|
085788f637 | ||
|
|
7b6bef1b1a | ||
|
|
772a73a4e3 | ||
|
|
9f33a68154 | ||
|
|
1584e087f2 | ||
|
|
b0a0560dbd | ||
|
|
e1c6149941 | ||
|
|
0a5de96f36 | ||
|
|
5509cb31a8 | ||
|
|
8add8da279 | ||
|
|
8c24ebeddf | ||
|
|
3bdcb2be59 | ||
|
|
47034d09f1 | ||
|
|
deddc7683d | ||
|
|
d22027275c | ||
|
|
e7cf90bbfe | ||
|
|
d451ecebf4 | ||
|
|
0885f5cac2 | ||
|
|
797259cc9d | ||
|
|
b8288d7cc4 | ||
|
|
60897ebbda | ||
|
|
84b2a55424 | ||
|
|
97aaa71bd6 | ||
|
|
97a944c690 | ||
|
|
fc10e7cc1a | ||
|
|
df100989ed | ||
|
|
626066944e | ||
|
|
bd45a0d17b | ||
|
|
e4e6dc30b2 | ||
|
|
868187479b | ||
|
|
7586b75e1c | ||
|
|
4f108057a2 | ||
|
|
24699536c6 | ||
|
|
74b167f8db | ||
|
|
a1ddd01291 | ||
|
|
518ca4a26c | ||
|
|
ca8e3cea8d | ||
|
|
f944a08f25 | ||
|
|
d25c2f04c9 | ||
|
|
9ddf70ce46 | ||
|
|
3786402963 | ||
|
|
ee8008ef93 | ||
|
|
1229bd2f48 | ||
|
|
5cd7d3cd03 | ||
|
|
8739bc7002 | ||
|
|
a2dca331e2 | ||
|
|
8a40afa174 | ||
|
|
bc79c1c901 | ||
|
|
1b14b0cca7 | ||
|
|
68093b6276 | ||
|
|
41f54b687b | ||
|
|
6eaebedebe | ||
|
|
3e401417df | ||
|
|
9c571d6d17 | ||
|
|
7c3d11d9df | ||
|
|
a7e484255d | ||
|
|
9deb8aaff6 | ||
|
|
cfa1a48bfb | ||
|
|
edb704339f | ||
|
|
35087351e7 | ||
|
|
2562e66ff4 | ||
|
|
6acf0e2f10 | ||
|
|
b3fa273f09 | ||
|
|
6f9c7f1bbd | ||
|
|
eafa378eb9 | ||
|
|
692ee06477 | ||
|
|
2c9547f5ff | ||
|
|
6939405173 | ||
|
|
7259817a84 | ||
|
|
25c9fa9eb3 | ||
|
|
c36c133162 | ||
|
|
25200b7cca | ||
|
|
bd99d3e9d5 | ||
|
|
582ec616b8 | ||
|
|
45d1a94153 | ||
|
|
4150ded11f | ||
|
|
f40dacaa22 | ||
|
|
b1bd4da197 | ||
|
|
7887867f9a | ||
|
|
e72012ef35 | ||
|
|
774b018dc6 | ||
|
|
f450979f46 | ||
|
|
3db1713616 | ||
|
|
fb5f873061 | ||
|
|
0352dc9a11 | ||
|
|
b831f34c06 | ||
|
|
5a085d8e36 | ||
|
|
4887d37110 | ||
|
|
1bc0adb535 | ||
|
|
76a948c66f | ||
|
|
7a3a4e81a1 | ||
|
|
b3ce454203 | ||
|
|
af3c2bc6fc | ||
|
|
5222da7748 | ||
|
|
17917932a0 | ||
|
|
f38904ac8c | ||
|
|
c516614bd4 | ||
|
|
a89a21c3ef | ||
|
|
1697b97e9d | ||
|
|
4e85003220 | ||
|
|
e78f82d674 | ||
|
|
96fd7f91c4 | ||
|
|
eb7406c663 | ||
|
|
08f8b975b6 | ||
|
|
ad0667ed3b | ||
|
|
8da89574fa | ||
|
|
7129c5a0c6 | ||
|
|
e21fbeaa62 | ||
|
|
e4085fb457 | ||
|
|
1dcb641314 | ||
|
|
fe84a52dcc | ||
|
|
6908ddeec1 | ||
|
|
804bacb7ba | ||
|
|
35409ad9eb | ||
|
|
1eb96182bb | ||
|
|
dad461f407 | ||
|
|
c0875ee34e | ||
|
|
ad8579af99 | ||
|
|
fe35147649 | ||
|
|
758fc48156 | ||
|
|
bb954482ee | ||
|
|
5bfe3e61a9 | ||
|
|
f6ec13b64d | ||
|
|
2ad5c3d3fe | ||
|
|
0da1926802 | ||
|
|
b461fc1c4a | ||
|
|
1603193436 | ||
|
|
fa76be2f9a | ||
|
|
28955d8444 | ||
|
|
3cab1faaf4 | ||
|
|
d0228728f4 | ||
|
|
a18854a30d | ||
|
|
c04570b1e4 | ||
|
|
f61f224bb1 | ||
|
|
4ed8847b9d | ||
|
|
338bdd93de | ||
|
|
ada054189d | ||
|
|
41a1a75f74 | ||
|
|
32d07606ba | ||
|
|
2f7b9fb4ef | ||
|
|
1d7c9e17fe | ||
|
|
2c520bdc4f | ||
|
|
f6a1c4f2c2 | ||
|
|
faa48e405f | ||
|
|
8b015d2f7f | ||
|
|
73ec9a25cf | ||
|
|
989198acab | ||
|
|
76b5ba6e61 | ||
|
|
78f7d80456 | ||
|
|
c102b4f240 | ||
|
|
ab25457a5f | ||
|
|
ace6068948 | ||
|
|
afa3410f46 | ||
|
|
09956cad2f | ||
|
|
18aba1ebbc | ||
|
|
a00792a775 | ||
|
|
5c6d29193e | ||
|
|
acbb53d6a4 | ||
|
|
f653ac4a62 | ||
|
|
bc2da26dda | ||
|
|
dcc2ea548b | ||
|
|
74117a5520 | ||
|
|
f23470c9ab | ||
|
|
3548075abb | ||
|
|
5e098c1231 | ||
|
|
a4560d562d | ||
|
|
7f89efa4f9 | ||
|
|
fd8478df1e | ||
|
|
d84c30480b | ||
|
|
a84424408d | ||
|
|
5bb9ceaf94 | ||
|
|
fd0b9099bc | ||
|
|
34f2a30688 | ||
|
|
68e8a24f18 | ||
|
|
8657c820bd | ||
|
|
cc922029fb | ||
|
|
843f9ff519 | ||
|
|
caa2fd44ff | ||
|
|
fbb1dad55f | ||
|
|
b593cadff8 | ||
|
|
d5d7582741 | ||
|
|
d3474e171e | ||
|
|
53f3c24081 | ||
|
|
86e1e6becf | ||
|
|
984dfd06c2 | ||
|
|
1ff3be579c | ||
|
|
38c0856c20 | ||
|
|
9c10370e19 | ||
|
|
cc184858cd | ||
|
|
14e30dabd6 | ||
|
|
7f74ed9753 | ||
|
|
aff95ded52 | ||
|
|
e7cdf00d11 | ||
|
|
3cda070507 | ||
|
|
d31a7594e7 | ||
|
|
6335a937c9 | ||
|
|
0f20d0b0b0 | ||
|
|
1c5cfea174 | ||
|
|
062eb8491f | ||
|
|
5d29e718ee | ||
|
|
3caa45a8c5 | ||
|
|
202a0e8dd7 | ||
|
|
2869d7ef50 | ||
|
|
6a1a3ebf13 | ||
|
|
9068d90d4a | ||
|
|
6bf6d102ef | ||
|
|
9b7d30151f | ||
|
|
761ca72136 | ||
|
|
1f7fa777b2 | ||
|
|
0bbdea0e90 | ||
|
|
e2bcbd51bb | ||
|
|
1cc9ff70e0 | ||
|
|
b9c0a477ee | ||
|
|
0a98acce96 | ||
|
|
300f73331a | ||
|
|
33c9236e45 | ||
|
|
34449a7c61 | ||
|
|
b8b02cf144 | ||
|
|
375cbca9eb | ||
|
|
7eff56ca2e | ||
|
|
50b1c5b640 | ||
|
|
6e50df6ad3 | ||
|
|
42aa58bc68 | ||
|
|
9e17be813d | ||
|
|
b7dcb6e97b | ||
|
|
69940b7561 | ||
|
|
211a45b688 | ||
|
|
b172f99835 | ||
|
|
76f340ce2d | ||
|
|
619423eacb | ||
|
|
c698dec89d | ||
|
|
5a8eae4510 | ||
|
|
8cc2584571 | ||
|
|
3d68dda5a2 | ||
|
|
9b2f54c571 | ||
|
|
0631acb8ad | ||
|
|
155e7aa117 | ||
|
|
b0e687692a | ||
|
|
f3a79cdb97 | ||
|
|
331b7dbaa3 | ||
|
|
cbe9427123 | ||
|
|
452859fb0f | ||
|
|
8c25620c6b | ||
|
|
3cc9afc15f | ||
|
|
179fb9e527 | ||
|
|
79ae297121 | ||
|
|
e4a1a56dbd | ||
|
|
7e92c4c1a3 | ||
|
|
dca5fbb8f5 | ||
|
|
1117f358cc | ||
|
|
b348d22a2b | ||
|
|
3f331b53db | ||
|
|
1ffbd02c87 | ||
|
|
9a9d89229f | ||
|
|
3c92941664 | ||
|
|
7f9ab03447 | ||
|
|
51b62be34d | ||
|
|
13c482ea2a | ||
|
|
098087a722 | ||
|
|
90b2efc6f7 | ||
|
|
7be1ab53ec | ||
|
|
7b0f72601b | ||
|
|
bd644a7d52 | ||
|
|
0e1c1b04a0 | ||
|
|
406b973654 | ||
|
|
89b916da28 | ||
|
|
a7e1ce20bf | ||
|
|
47edd34742 | ||
|
|
462d8efde8 | ||
|
|
4af8a307ee | ||
|
|
fdc9d8dc72 | ||
|
|
7ae0d6b95d | ||
|
|
c81948620f | ||
|
|
7b56b3f411 | ||
|
|
00438e852e | ||
|
|
02cf4fd43f | ||
|
|
877f8bec45 | ||
|
|
98a4b07ec1 | ||
|
|
d8a951dc52 | ||
|
|
86900061c2 | ||
|
|
10aa5755a4 | ||
|
|
b32909a9e6 | ||
|
|
691a79e3be | ||
|
|
fd1e3f65a8 | ||
|
|
507824224f | ||
|
|
313eb8f4cd | ||
|
|
ca5c24452e | ||
|
|
973e8697bc | ||
|
|
098879be3e | ||
|
|
41f5ae30f1 | ||
|
|
b6684d90b5 | ||
|
|
f4483e1ee4 | ||
|
|
454aa695ab | ||
|
|
4c68009ae0 | ||
|
|
27d2ac6f66 | ||
|
|
b21133ee12 | ||
|
|
fc652523aa | ||
|
|
ae55374841 | ||
|
|
8901552112 | ||
|
|
4fcd511225 | ||
|
|
eef151e7a6 | ||
|
|
1857d0fadd | ||
|
|
898881bac1 | ||
|
|
86585cc644 | ||
|
|
d787fb1a60 | ||
|
|
a2a58dc082 | ||
|
|
4aaf6bcc59 | ||
|
|
7c947115e2 | ||
|
|
311d935943 | ||
|
|
eb472e2d76 | ||
|
|
ec1f069515 | ||
|
|
30206d20a7 | ||
|
|
73f49466a1 | ||
|
|
1873c75a7f | ||
|
|
369ac726c0 | ||
|
|
3d78a7377e | ||
|
|
7314092d19 | ||
|
|
3ca882f883 | ||
|
|
e7307d1e19 | ||
|
|
d6600bfa5a | ||
|
|
82504a1fcf | ||
|
|
28b8620656 | ||
|
|
7419616f61 | ||
|
|
92f724de36 | ||
|
|
b55aeb54f6 | ||
|
|
f46adb6724 | ||
|
|
f6f2e14c19 | ||
|
|
4e2cb8c132 | ||
|
|
f1d0a6b0a2 | ||
|
|
f85972310c | ||
|
|
030a2127ee | ||
|
|
cec2ae2fbd | ||
|
|
2bd16bedce | ||
|
|
7e4fe93c7f | ||
|
|
dfbc2704d8 |
||
|
|
21cb168dfd | ||
|
|
d772f6cfeb | ||
|
|
f4d29b5d5e | ||
|
|
21b17f333d | ||
|
|
098c6af7ef | ||
|
|
1a38e0273e | ||
|
|
14fdd34964 | ||
|
|
1ec1ba8d3e | ||
|
|
eeb2dbcb60 | ||
|
|
0afe69ce28 | ||
|
|
202d77e0cc | ||
|
|
78e4f0ecd8 | ||
|
|
5a2080570f | ||
|
|
54c740c252 | ||
|
|
707ed633e4 | ||
|
|
57d84a3cc6 | ||
|
|
69f684cb64 | ||
|
|
4d472e1d4b | ||
|
|
4f66731723 | ||
|
|
f0e5b0be1e | ||
|
|
f75ea738ca | ||
|
|
97c058ebda | ||
|
|
cfe52185f7 | ||
|
|
756ea63b67 | ||
|
|
8725de3e91 | ||
|
|
a6863248bb | ||
|
|
da2c016ab4 | ||
|
|
7e684ea3ff | ||
|
|
0f2bd39db8 | ||
|
|
c58ed1036f | ||
|
|
d31da2c300 | ||
|
|
bae4836349 | ||
|
|
3f55c08693 | ||
|
|
191609c662 | ||
|
|
2df431ea17 | ||
|
|
bb41c13620 | ||
|
|
b48b4493f9 | ||
|
|
48cc7ccc78 | ||
|
|
541affd459 | ||
|
|
a21f25ed8e | ||
|
|
3d77860e57 | ||
|
|
c937736fea | ||
|
|
24663b2f04 | ||
|
|
9e3e4ed429 | ||
|
|
81d9537f9d | ||
|
|
13838a75a9 | ||
|
|
20e6382df6 | ||
|
|
dee3cf7883 | ||
|
|
ab295c588d | ||
|
|
02ecd8bb6c | ||
|
|
424da4c311 | ||
|
|
e876c98d5e | ||
|
|
2eb8e1e095 | ||
|
|
e8d0e45b5b | ||
|
|
ba4be2cb22 | ||
|
|
f0957bdb4f | ||
|
|
07a48315a1 | ||
|
|
89b05cfc57 | ||
|
|
d2cce99086 | ||
|
|
d5571216fe | ||
|
|
596a1e4961 | ||
|
|
b599407b67 | ||
|
|
9753db1c67 | ||
|
|
2a98ea6ddc | ||
|
|
6230edcbec | ||
|
|
58ac749755 | ||
|
|
4ddc191928 | ||
|
|
e541e2c682 | ||
|
|
45f6e003c4 | ||
|
|
487d9c447d | ||
|
|
aa922faf62 | ||
|
|
415180e8fa | ||
|
|
fb40694e8e | ||
|
|
ef795becf6 | ||
|
|
b4a1bcd070 | ||
|
|
f1468a3f5d | ||
|
|
e7eb1059c3 | ||
|
|
e1d3ebc943 | ||
|
|
d5549ac1ee | ||
|
|
9db5552f30 | ||
|
|
22d3c13135 | ||
|
|
c73965bee0 | ||
|
|
144d426864 | ||
|
|
71622e2932 |
||
|
|
edf5e36bf8 |
||
|
|
087a7c6434 |
||
|
|
89346369e0 |
||
|
|
40c7236653 |
||
|
|
91500ee4b0 |
||
|
|
f41f7c77d9 |
||
|
|
55598d1cad |
||
|
|
18d8bb2c04 |
||
|
|
5bc4ea2904 |
||
|
|
86f6fbeb46 |
||
|
|
8a58de5c9f |
||
|
|
fb87477298 |
||
|
|
b27f696d77 |
||
|
|
5206609812 |
||
|
|
edcd6982a5 |
||
|
|
95b06cacea |
||
|
|
dee38f7fe4 |
||
|
|
2edf5fc9f6 |
||
|
|
b6f1178ea3 |
||
|
|
98bbb4eeef |
||
|
|
71f16ec39c |
||
|
|
1bffcc5fe7 |
||
|
|
2a8c7e8e7d |
||
|
|
747bad79fc |
||
|
|
e67e5ff899 |
||
|
|
ba5cad90d8 |
||
|
|
8a7f17ac9e |
||
|
|
a245379f43 |
||
|
|
02e2e6b1bf |
||
|
|
1edada7e9d |
||
|
|
1aa0901471 |
||
|
|
d67d24757f | ||
|
|
139f8dd91e | ||
|
|
84cab03a1d | ||
|
|
c7303598df | ||
|
|
5515f53794 |
||
|
|
963e163858 |
||
|
|
8eff081468 |
||
|
|
ad7d47f440 |
||
|
|
f9254e5fb7 |
||
|
|
e3ca5b0a32 |
||
|
|
22ab848f6b |
||
|
|
b1dcea0199 |
||
|
|
872569ae8e |
||
|
|
c25cfe540b |
||
|
|
3430604dda |
||
|
|
27e36dbc2e |
||
|
|
aa6c13f9e6 |
||
|
|
aa98e83ff0 |
||
|
|
edfaf5e80c |
||
|
|
f7daaead6f |
||
|
|
1506d2421d |
||
|
|
da58282e90 |
||
|
|
7d90c594fe |
||
|
|
f8a0cd2dd3 |
||
|
|
1bf256b34b |
||
|
|
93a87b5646 |
12
.dockerignore
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
logs/
|
||||||
|
.DS_Store
|
||||||
|
.git/
|
||||||
|
config/local.json
|
||||||
|
pleroma-backend/
|
||||||
|
test/e2e/reports/
|
||||||
|
test/e2e-playwright/test-results/
|
||||||
|
test/e2e-playwright/playwright-report/
|
||||||
|
__screenshots__/
|
||||||
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
build/*.js
|
|
||||||
config/*.js
|
|
||||||
27
.eslintrc.js
|
|
@ -1,27 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
parserOptions: {
|
|
||||||
parser: '@babel/eslint-parser',
|
|
||||||
sourceType: 'module'
|
|
||||||
},
|
|
||||||
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
|
|
||||||
extends: [
|
|
||||||
'standard',
|
|
||||||
'plugin:vue/recommended'
|
|
||||||
],
|
|
||||||
// required to lint *.vue files
|
|
||||||
plugins: [
|
|
||||||
'vue'
|
|
||||||
],
|
|
||||||
// add your custom rules here
|
|
||||||
rules: {
|
|
||||||
// allow paren-less arrow functions
|
|
||||||
'arrow-parens': 0,
|
|
||||||
// allow async-await
|
|
||||||
'generator-star-spacing': 0,
|
|
||||||
// allow debugger during development
|
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
|
||||||
'vue/require-prop-types': 0,
|
|
||||||
'vue/multi-word-component-names': 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2
.gitattributes
vendored
|
|
@ -1 +1 @@
|
||||||
/build/webpack.prod.conf.js export-subst
|
/build/commit_hash.js export-subst
|
||||||
|
|
|
||||||
6
.gitignore
vendored
|
|
@ -4,8 +4,12 @@ dist/
|
||||||
npm-debug.log
|
npm-debug.log
|
||||||
test/unit/coverage
|
test/unit/coverage
|
||||||
test/e2e/reports
|
test/e2e/reports
|
||||||
|
test/e2e-playwright/test-results
|
||||||
|
test/e2e-playwright/playwright-report
|
||||||
selenium-debug.log
|
selenium-debug.log
|
||||||
.idea/
|
.idea/
|
||||||
|
.gitlab-ci-local/
|
||||||
config/local.json
|
config/local.json
|
||||||
static/emoji.json
|
src/assets/emoji.json
|
||||||
logs/
|
logs/
|
||||||
|
__screenshots__/
|
||||||
|
|
|
||||||
161
.gitlab-ci.yml
|
|
@ -1,7 +1,7 @@
|
||||||
# This file is a template, and might need editing before it works on your project.
|
# This file is a template, and might need editing before it works on your project.
|
||||||
# Official framework image. Look for the different tagged releases at:
|
# Official framework image. Look for the different tagged releases at:
|
||||||
# https://hub.docker.com/r/library/node/tags/
|
# https://hub.docker.com/r/library/node/tags/
|
||||||
image: node:16
|
image: node:18
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- check-changelog
|
- check-changelog
|
||||||
|
|
@ -34,12 +34,23 @@ check-changelog:
|
||||||
- apk add git
|
- apk add git
|
||||||
- sh ./tools/check-changelog
|
- sh ./tools/check-changelog
|
||||||
|
|
||||||
lint:
|
lint-eslint:
|
||||||
stage: lint
|
stage: lint
|
||||||
script:
|
script:
|
||||||
- yarn
|
- yarn
|
||||||
- npm run lint
|
- yarn ci-eslint
|
||||||
- npm run stylelint
|
|
||||||
|
lint-biome:
|
||||||
|
stage: lint
|
||||||
|
script:
|
||||||
|
- yarn
|
||||||
|
- yarn ci-biome
|
||||||
|
|
||||||
|
lint-stylelint:
|
||||||
|
stage: lint
|
||||||
|
script:
|
||||||
|
- yarn
|
||||||
|
- yarn ci-stylelint
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
|
|
@ -50,10 +61,144 @@ test:
|
||||||
APT_CACHE_DIR: apt-cache
|
APT_CACHE_DIR: apt-cache
|
||||||
script:
|
script:
|
||||||
- mkdir -pv $APT_CACHE_DIR && apt-get -qq update
|
- mkdir -pv $APT_CACHE_DIR && apt-get -qq update
|
||||||
- apt install firefox-esr -y --no-install-recommends
|
|
||||||
- firefox --version
|
|
||||||
- yarn
|
- yarn
|
||||||
- yarn unit
|
- yarn playwright install firefox
|
||||||
|
- yarn playwright install-deps
|
||||||
|
- yarn unit-ci
|
||||||
|
artifacts:
|
||||||
|
# When the test fails, upload screenshots for better context on why it fails
|
||||||
|
paths:
|
||||||
|
- test/**/__screenshots__
|
||||||
|
when: on_failure
|
||||||
|
|
||||||
|
e2e-pleroma:
|
||||||
|
stage: test
|
||||||
|
image: mcr.microsoft.com/playwright:v1.57.0-jammy
|
||||||
|
services:
|
||||||
|
- name: postgres:15-alpine
|
||||||
|
alias: db
|
||||||
|
- name: $PLEROMA_IMAGE
|
||||||
|
alias: pleroma
|
||||||
|
entrypoint: ["/bin/ash", "-c"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SEED_SENTINEL_PATH=/var/lib/pleroma/.e2e_seeded
|
||||||
|
CONFIG_OVERRIDE_PATH=/var/lib/pleroma/config.exs
|
||||||
|
|
||||||
|
echo '-- Waiting for database...'
|
||||||
|
while ! pg_isready -U ${DB_USER:-pleroma} -d postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma} -t 1; do
|
||||||
|
sleep 1s
|
||||||
|
done
|
||||||
|
|
||||||
|
echo '-- Writing E2E config overrides...'
|
||||||
|
cat > $CONFIG_OVERRIDE_PATH <<EOF
|
||||||
|
import Config
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Captcha,
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
config :pleroma, :instance,
|
||||||
|
registrations_open: true,
|
||||||
|
account_activation_required: false,
|
||||||
|
approval_required: false
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo '-- Running migrations...'
|
||||||
|
/opt/pleroma/bin/pleroma_ctl migrate
|
||||||
|
|
||||||
|
echo '-- Starting!'
|
||||||
|
/opt/pleroma/bin/pleroma start &
|
||||||
|
PLEROMA_PID=$!
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if kill -0 $PLEROMA_PID 2>/dev/null; then
|
||||||
|
kill -TERM $PLEROMA_PID
|
||||||
|
wait $PLEROMA_PID || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup INT TERM
|
||||||
|
|
||||||
|
echo '-- Waiting for API...'
|
||||||
|
api_ok=false
|
||||||
|
for _i in $(seq 1 120); do
|
||||||
|
if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
|
||||||
|
api_ok=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1s
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ $api_ok != true ]; then
|
||||||
|
echo 'Timed out waiting for Pleroma API to become available'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f $SEED_SENTINEL_PATH ]; then
|
||||||
|
if [ -n ${E2E_ADMIN_USERNAME:-} ] && [ -n ${E2E_ADMIN_PASSWORD:-} ] && [ -n ${E2E_ADMIN_EMAIL:-} ]; then
|
||||||
|
echo '-- Seeding admin user' $E2E_ADMIN_USERNAME '...'
|
||||||
|
if ! /opt/pleroma/bin/pleroma_ctl user new $E2E_ADMIN_USERNAME $E2E_ADMIN_EMAIL --admin --password $E2E_ADMIN_PASSWORD -y; then
|
||||||
|
echo '-- User already exists or creation failed, ensuring admin + confirmed...'
|
||||||
|
/opt/pleroma/bin/pleroma_ctl user set $E2E_ADMIN_USERNAME --admin --confirmed
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo '-- Skipping admin seeding (missing E2E_ADMIN_* env)'
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch $SEED_SENTINEL_PATH
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait $PLEROMA_PID
|
||||||
|
tags:
|
||||||
|
- amd64
|
||||||
|
- himem
|
||||||
|
variables:
|
||||||
|
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
|
||||||
|
FF_NETWORK_PER_BUILD: "true"
|
||||||
|
PLEROMA_IMAGE: git.pleroma.social:5050/pleroma/pleroma:stable
|
||||||
|
POSTGRES_USER: pleroma
|
||||||
|
POSTGRES_PASSWORD: pleroma
|
||||||
|
POSTGRES_DB: pleroma
|
||||||
|
DB_USER: pleroma
|
||||||
|
DB_PASS: pleroma
|
||||||
|
DB_NAME: pleroma
|
||||||
|
DB_HOST: db
|
||||||
|
DB_PORT: 5432
|
||||||
|
DOMAIN: localhost
|
||||||
|
INSTANCE_NAME: Pleroma E2E
|
||||||
|
E2E_ADMIN_USERNAME: admin
|
||||||
|
E2E_ADMIN_PASSWORD: adminadmin
|
||||||
|
E2E_ADMIN_EMAIL: admin@example.com
|
||||||
|
ADMIN_EMAIL: $E2E_ADMIN_EMAIL
|
||||||
|
NOTIFY_EMAIL: $E2E_ADMIN_EMAIL
|
||||||
|
VITE_PROXY_TARGET: http://pleroma:4000
|
||||||
|
VITE_PROXY_ORIGIN: http://localhost:4000
|
||||||
|
E2E_BASE_URL: http://localhost:8080
|
||||||
|
script:
|
||||||
|
- npm install -g yarn@1.22.22
|
||||||
|
- yarn --frozen-lockfile
|
||||||
|
- |
|
||||||
|
echo "-- Waiting for Pleroma API..."
|
||||||
|
api_ok="false"
|
||||||
|
for _i in $(seq 1 120); do
|
||||||
|
if wget -qO- http://pleroma:4000/api/v1/instance >/dev/null 2>&1; then
|
||||||
|
api_ok="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1s
|
||||||
|
done
|
||||||
|
if [ "$api_ok" != "true" ]; then
|
||||||
|
echo "Timed out waiting for Pleroma API to become available"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- yarn e2e:pw
|
||||||
|
artifacts:
|
||||||
|
when: on_failure
|
||||||
|
paths:
|
||||||
|
- test/e2e-playwright/test-results
|
||||||
|
- test/e2e-playwright/playwright-report
|
||||||
|
|
||||||
build:
|
build:
|
||||||
stage: build
|
stage: build
|
||||||
|
|
@ -62,7 +207,7 @@ build:
|
||||||
- himem
|
- himem
|
||||||
script:
|
script:
|
||||||
- yarn
|
- yarn
|
||||||
- npm run build
|
- yarn build
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- dist/
|
- dist/
|
||||||
|
|
|
||||||
8
.gitlab/merge_request_templates/Release.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
### Release checklist
|
||||||
|
* [ ] Bump version in `package.json`
|
||||||
|
* [ ] Compile a changelog with the `tools/collect-changelog` script
|
||||||
|
* [ ] Create an MR with an announcement to pleroma.social
|
||||||
|
#### post-merge
|
||||||
|
* [ ] Tag the release on the merge commit
|
||||||
|
* [ ] Make the tag into a Gitlab Release™
|
||||||
|
* [ ] Merge `master` into `develop` (in case the fixes are already in develop, use `git merge -s ours --no-commit` and manually merge the changelogs)
|
||||||
|
|
@ -1 +1 @@
|
||||||
16.18.1
|
18.20.8
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": [
|
"extends": [
|
||||||
"stylelint-rscss/config",
|
|
||||||
"stylelint-config-standard",
|
"stylelint-config-standard",
|
||||||
"stylelint-config-recommended-scss",
|
"stylelint-config-recommended-scss",
|
||||||
"stylelint-config-html",
|
"stylelint-config-html",
|
||||||
|
|
@ -8,20 +7,13 @@
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"declaration-no-important": true,
|
"declaration-no-important": true,
|
||||||
"rscss/no-descendant-combinator": false,
|
|
||||||
"rscss/class-format": [
|
|
||||||
false,
|
|
||||||
{
|
|
||||||
"component": "pascal-case",
|
|
||||||
"variant": "^-[a-z]\\w+",
|
|
||||||
"element": "^[a-z]\\w+"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"selector-class-pattern": null,
|
"selector-class-pattern": null,
|
||||||
"import-notation": null,
|
"import-notation": null,
|
||||||
"custom-property-pattern": null,
|
"custom-property-pattern": null,
|
||||||
"keyframes-name-pattern": null,
|
"keyframes-name-pattern": null,
|
||||||
"scss/operator-no-newline-after": null,
|
"scss/operator-no-newline-after": null,
|
||||||
|
"declaration-property-value-no-unknown": true,
|
||||||
|
"scss/declaration-property-value-no-unknown": true,
|
||||||
"declaration-block-no-redundant-longhand-properties": [
|
"declaration-block-no-redundant-longhand-properties": [
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
138
CHANGELOG.md
|
|
@ -3,6 +3,144 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
|
## 2.10.1
|
||||||
|
### Fixed
|
||||||
|
- fixed being unable to set actor type from profile page
|
||||||
|
- fixed error when clicking mute menu itself (instead of submenu items)
|
||||||
|
- fixed mute -> domain status submenu not working
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
- Add playwright E2E-tests with an optional docker-based backend
|
||||||
|
|
||||||
|
## 2.10.0
|
||||||
|
### Changed
|
||||||
|
- Temporary changes modal now shows actual countdown instead of fixed timeout
|
||||||
|
- Disabled elements are more disabled now
|
||||||
|
- Rearranged and split settings to make more sense and be less of a wall of text
|
||||||
|
- On mobile settings now take up full width and presented in navigation style
|
||||||
|
improved styles for settings
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Most of the remaining AdminFE tabs were added into Admin Dashboard
|
||||||
|
- It's now possible to customize PWA Manfiest from PleromaFE
|
||||||
|
- Make every configuration option default-overridable by instance admins
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed settings not appearing if user never touched "show advanced" toggle
|
||||||
|
- Fix display of the broken/deleted/banned users
|
||||||
|
- Fixed incorrect emoji display in post interaction lists
|
||||||
|
- Fixed list title not being saved when editing
|
||||||
|
- Fixed poll notifications not being expandable
|
||||||
|
|
||||||
|
|
||||||
|
## 2.9.3
|
||||||
|
### Fixed
|
||||||
|
- Being unable to update profile
|
||||||
|
|
||||||
|
## 2.9.2
|
||||||
|
### Changed
|
||||||
|
- BREAKING: due to some internal technical changes logging into AdminFE through PleromaFE is no longer possible
|
||||||
|
- User card/profile got an overhaul
|
||||||
|
- Profile editing overhaul
|
||||||
|
- Visually combined subject and content fields in post form
|
||||||
|
- Moved post form's emoji button into input field
|
||||||
|
- Minor visual changes and fixes
|
||||||
|
- Clicking on fav/rt/emoji notifications' contents expands/collapses it
|
||||||
|
- Reduced time taken processing theme by half
|
||||||
|
- Splash screen only appears if loading takes more than 2 seconds
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Mutes received an update, adding support for regex, muting based on username and expiration time.
|
||||||
|
- Mutes are now synchronized across sessions
|
||||||
|
- Support for expiring mutes and blocks (if available)
|
||||||
|
- Clicking on emoji shows bigger version of it alongside with its shortcode
|
||||||
|
- Admins also are able to copy it into a local pack
|
||||||
|
- Added support for Akkoma and IceShrimp.NET backends
|
||||||
|
- Compatibility with stricter CSP (Akkoma backend)
|
||||||
|
- Added a way to upload new packs from a URL or ZIP file via the Admin Dashboard
|
||||||
|
- Unify show/hide content buttons
|
||||||
|
- Add support for detachable scrollTop button
|
||||||
|
- Option to left-align user bio
|
||||||
|
- Cache assets and emojis with service worker
|
||||||
|
- Indicate currently active V3 theme as a body element class
|
||||||
|
- Add arithmetic blend ISS function
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Display counter for status action buttons when they are in the menu
|
||||||
|
- Fix bookmark button alignment in the extra actions menu
|
||||||
|
- Instance favicons are no longer stretched
|
||||||
|
- A lot more scalable UI fixes
|
||||||
|
- Emoji picker now should work fine when emoji size is increased
|
||||||
|
|
||||||
|
## 2.8.0
|
||||||
|
### Changed
|
||||||
|
- BREAKING: static/img/nsfw.2958239.png is now static/img/nsfw.DepQPhG0.png, which may affect people who specify exactly this path as the cover image
|
||||||
|
- BREAKING: static/emoji.json is replaced with a properly hashed path under static/js in the production build, meaning server admins cannot provide their own set of unicode emojis by overriding this file (custom (image-based) emojis not affected)
|
||||||
|
- Speed up initial boot.
|
||||||
|
- Updated our build system to support browsers:
|
||||||
|
Safari >= 15
|
||||||
|
Firefox >= 115
|
||||||
|
Android > 4
|
||||||
|
no Opera Mini support
|
||||||
|
no IE support
|
||||||
|
no "dead" (unmaintained) browsers support
|
||||||
|
|
||||||
|
This does not guarantee that browsers will or will not work.
|
||||||
|
|
||||||
|
- Use /api/v1/accounts/:id/follow for account subscriptions instead of the deprecated routes
|
||||||
|
- Modal layout for mobile has new layout to make it easy to use
|
||||||
|
- Better display of mute reason on posts
|
||||||
|
- Simplify the OAuth client_name to 'PleromaFE'
|
||||||
|
- Partially migrated from vuex to pinia
|
||||||
|
- Authenticate and subscribe to streaming after connection
|
||||||
|
- Tabs now have indentation for better visibility of which tab is currently active
|
||||||
|
- Upgraded Vue to version 3.5
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Support bookmark folders
|
||||||
|
- Some new default color schemes
|
||||||
|
- Added support for fetching /{resource}.custom.ext to allow adding instance-specific themes without altering sourcetree
|
||||||
|
- Post actions customization
|
||||||
|
- Support displaying time in absolute format
|
||||||
|
- Add draft management system
|
||||||
|
- Compress most kinds of images on upload.
|
||||||
|
- Added option to always convert images to JPEG format instead of using WebP when compressing images.
|
||||||
|
- Added configurable image compression option in general settings, allowing users to control whether images are compressed before upload.
|
||||||
|
- Inform users that Smithereen public polls are public
|
||||||
|
- Splash screen + loading indicator to make process of identifying initialization issues and load performance
|
||||||
|
- UI for making v3 themes and palettes, support for bundling v3 themes
|
||||||
|
- Make UserLink wrappable
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed occasional overflows in emoji picker and made header scrollable
|
||||||
|
- Updated shadow editor, hopefully fixed long-standing bugs, added ability to specify shadow's name.
|
||||||
|
- Checkbox vertical alignment
|
||||||
|
- Check for canvas extract permission when initializing favicon service
|
||||||
|
- Fix some of the color manipulation functions
|
||||||
|
- Fix draft saving when auto-save is off
|
||||||
|
- Switch from class hack to normalButton attribute for emoji count popover
|
||||||
|
- Fix emoji inconsistencies in notifications,
|
||||||
|
- Fix some emoji not scaling with interface
|
||||||
|
- Make sure hover style is also applied to :focus-visible
|
||||||
|
- Improved ToS and registration
|
||||||
|
- Fix small markup inconsistencies
|
||||||
|
- Fixed modals buttons overflow
|
||||||
|
- Fix whitespaces for multiple status mute reasons, display bot status reason
|
||||||
|
- Create an OAuth app only when needed
|
||||||
|
- Fix CSS compatibility issues in style_setter.js for older browsers like Palemoon
|
||||||
|
- Proper sticky header for conversations on user page
|
||||||
|
- Add text label for more actions button in post status form
|
||||||
|
- Reply-or-quote buttons now take less space
|
||||||
|
- Allow repeats of own posts with private scopes
|
||||||
|
- Bookmarks visible again on mobile
|
||||||
|
- Remove focusability on hidden popover in subject input
|
||||||
|
- Show only month and day instead of weird "day, hour" format.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- BREAKING: drop support for browsers that do not support `<script type="module">`
|
||||||
|
- BREAKING: css source map does not work in production (see https://github.com/vitejs/vite/issues/2830 )
|
||||||
|
- Remove emoji annotations code for unused languages from final build
|
||||||
|
|
||||||
## 2.7.1
|
## 2.7.1
|
||||||
Bugfix release. Added small optimizations to emoji picker that should make it a bit more responsive, however it needs rather large change to make it more performant which might come in a major release.
|
Bugfix release. Added small optimizations to emoji picker that should make it a bit more responsive, however it needs rather large change to make it more performant which might come in a major release.
|
||||||
|
|
||||||
|
|
|
||||||
149
biome.json
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
|
||||||
|
"vcs": {
|
||||||
|
"enabled": true,
|
||||||
|
"clientKind": "git",
|
||||||
|
"useIgnoreFile": true
|
||||||
|
},
|
||||||
|
"files": {
|
||||||
|
"includes": ["**", "!!**/dist", "!!tools/emojis.json"]
|
||||||
|
},
|
||||||
|
"formatter": {
|
||||||
|
"enabled": true,
|
||||||
|
"indentStyle": "space"
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
|
"domains": {
|
||||||
|
"vue": "recommended"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"recommended": false,
|
||||||
|
"complexity": {
|
||||||
|
"noAdjacentSpacesInRegex": "error",
|
||||||
|
"noExtraBooleanCast": "error",
|
||||||
|
"noUselessCatch": "error",
|
||||||
|
"noUselessEscapeInRegex": "error"
|
||||||
|
},
|
||||||
|
"correctness": {
|
||||||
|
"noConstAssign": "error",
|
||||||
|
"noConstantCondition": "error",
|
||||||
|
"noEmptyCharacterClassInRegex": "error",
|
||||||
|
"noEmptyPattern": "error",
|
||||||
|
"noGlobalObjectCalls": "error",
|
||||||
|
"noInvalidBuiltinInstantiation": "error",
|
||||||
|
"noInvalidConstructorSuper": "error",
|
||||||
|
"noNonoctalDecimalEscape": "error",
|
||||||
|
"noPrecisionLoss": "error",
|
||||||
|
"noSelfAssign": "error",
|
||||||
|
"noSetterReturn": "error",
|
||||||
|
"noSwitchDeclarations": "error",
|
||||||
|
"noUndeclaredVariables": "error",
|
||||||
|
"noUnreachable": "error",
|
||||||
|
"noUnreachableSuper": "error",
|
||||||
|
"noUnsafeFinally": "error",
|
||||||
|
"noUnsafeOptionalChaining": "error",
|
||||||
|
"noUnusedLabels": "error",
|
||||||
|
"noUnusedPrivateClassMembers": "error",
|
||||||
|
"noUnusedVariables": "error",
|
||||||
|
"useIsNan": "error",
|
||||||
|
"useValidForDirection": "error",
|
||||||
|
"useValidTypeof": "error",
|
||||||
|
"useYield": "error"
|
||||||
|
},
|
||||||
|
"suspicious": {
|
||||||
|
"noAsyncPromiseExecutor": "error",
|
||||||
|
"noCatchAssign": "error",
|
||||||
|
"noClassAssign": "error",
|
||||||
|
"noCompareNegZero": "error",
|
||||||
|
"noConstantBinaryExpressions": "error",
|
||||||
|
"noControlCharactersInRegex": "error",
|
||||||
|
"noDebugger": "error",
|
||||||
|
"noDuplicateCase": "error",
|
||||||
|
"noDuplicateClassMembers": "error",
|
||||||
|
"noDuplicateElseIf": "error",
|
||||||
|
"noDuplicateObjectKeys": "error",
|
||||||
|
"noDuplicateParameters": "error",
|
||||||
|
"noEmptyBlockStatements": "error",
|
||||||
|
"noFallthroughSwitchClause": "error",
|
||||||
|
"noFunctionAssign": "error",
|
||||||
|
"noGlobalAssign": "error",
|
||||||
|
"noImportAssign": "error",
|
||||||
|
"noIrregularWhitespace": "error",
|
||||||
|
"noMisleadingCharacterClass": "error",
|
||||||
|
"noPrototypeBuiltins": "error",
|
||||||
|
"noRedeclare": "error",
|
||||||
|
"noShadowRestrictedNames": "error",
|
||||||
|
"noSparseArray": "error",
|
||||||
|
"noUnsafeNegation": "error",
|
||||||
|
"noUselessRegexBackrefs": "error",
|
||||||
|
"noWith": "error",
|
||||||
|
"useGetterReturn": "error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"javascript": {
|
||||||
|
"formatter": {
|
||||||
|
"quoteStyle": "single",
|
||||||
|
"semicolons": "asNeeded"
|
||||||
|
},
|
||||||
|
"globals": []
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"includes": ["**/*.spec.js", "test/fixtures/*.js"],
|
||||||
|
"javascript": {
|
||||||
|
"globals": [
|
||||||
|
"vi",
|
||||||
|
"describe",
|
||||||
|
"it",
|
||||||
|
"test",
|
||||||
|
"expect",
|
||||||
|
"before",
|
||||||
|
"beforeEach",
|
||||||
|
"after",
|
||||||
|
"afterEach"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"includes": ["**/*.vue"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"style": {
|
||||||
|
"useConst": "off",
|
||||||
|
"useImportType": "off"
|
||||||
|
},
|
||||||
|
"correctness": {
|
||||||
|
"noUnusedVariables": "off",
|
||||||
|
"noUnusedImports": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"assist": {
|
||||||
|
"enabled": true,
|
||||||
|
"actions": {
|
||||||
|
"source": {
|
||||||
|
"organizeImports": {
|
||||||
|
"level": "on",
|
||||||
|
"options": {
|
||||||
|
"groups": [
|
||||||
|
[":NODE:", ":PACKAGE:", "!src/**", "!@fortawesome/**"],
|
||||||
|
":BLANK_LINE:",
|
||||||
|
[":PATH:", "src/components/**"],
|
||||||
|
":BLANK_LINE:",
|
||||||
|
[":PATH:", "src/stores/**"],
|
||||||
|
":BLANK_LINE:",
|
||||||
|
[":PATH:", "src/**", "src/stores/**", "src/components/**"],
|
||||||
|
":BLANK_LINE:",
|
||||||
|
"@fortawesome/fontawesome-svg-core",
|
||||||
|
"@fortawesome/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
// https://github.com/shelljs/shelljs
|
|
||||||
require('./check-versions')()
|
|
||||||
require('shelljs/global')
|
|
||||||
env.NODE_ENV = 'production'
|
|
||||||
|
|
||||||
var path = require('path')
|
|
||||||
var config = require('../config')
|
|
||||||
var ora = require('ora')
|
|
||||||
var webpack = require('webpack')
|
|
||||||
var webpackConfig = require('./webpack.prod.conf')
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
' Tip:\n' +
|
|
||||||
' Built files are meant to be served over an HTTP server.\n' +
|
|
||||||
' Opening index.html over file:// won\'t work.\n'
|
|
||||||
)
|
|
||||||
|
|
||||||
var spinner = ora('building for production...')
|
|
||||||
spinner.start()
|
|
||||||
|
|
||||||
var updateEmoji = require('./update-emoji').updateEmoji
|
|
||||||
updateEmoji()
|
|
||||||
|
|
||||||
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
|
|
||||||
rm('-rf', assetsPath)
|
|
||||||
mkdir('-p', assetsPath)
|
|
||||||
cp('-R', 'static/*', assetsPath)
|
|
||||||
|
|
||||||
webpack(webpackConfig, function (err, stats) {
|
|
||||||
spinner.stop()
|
|
||||||
if (err) throw err
|
|
||||||
process.stdout.write(stats.toString({
|
|
||||||
colors: true,
|
|
||||||
modules: false,
|
|
||||||
children: false,
|
|
||||||
chunks: false,
|
|
||||||
chunkModules: false
|
|
||||||
}) + '\n')
|
|
||||||
if (stats.hasErrors()) {
|
|
||||||
console.error('See above for errors.')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
var semver = require('semver')
|
|
||||||
var chalk = require('chalk')
|
|
||||||
var packageConfig = require('../package.json')
|
|
||||||
var exec = function (cmd) {
|
|
||||||
return require('child_process')
|
|
||||||
.execSync(cmd).toString().trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
var versionRequirements = [
|
|
||||||
{
|
|
||||||
name: 'node',
|
|
||||||
currentVersion: semver.clean(process.version),
|
|
||||||
versionRequirement: packageConfig.engines.node
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'npm',
|
|
||||||
currentVersion: exec('npm --version'),
|
|
||||||
versionRequirement: packageConfig.engines.npm
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
module.exports = function () {
|
|
||||||
var warnings = []
|
|
||||||
for (var i = 0; i < versionRequirements.length; i++) {
|
|
||||||
var mod = versionRequirements[i]
|
|
||||||
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
|
|
||||||
warnings.push(mod.name + ': ' +
|
|
||||||
chalk.red(mod.currentVersion) + ' should be ' +
|
|
||||||
chalk.green(mod.versionRequirement)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (warnings.length) {
|
|
||||||
console.log('')
|
|
||||||
console.log(chalk.yellow('To use this template, you must update following to modules:'))
|
|
||||||
console.log()
|
|
||||||
for (var i = 0; i < warnings.length; i++) {
|
|
||||||
var warning = warnings[i]
|
|
||||||
console.log(' ' + warning)
|
|
||||||
}
|
|
||||||
console.log()
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
42
build/check-versions.mjs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import chalk from 'chalk'
|
||||||
|
import semver from 'semver'
|
||||||
|
|
||||||
|
import packageConfig from '../package.json' with { type: 'json' }
|
||||||
|
|
||||||
|
var versionRequirements = [
|
||||||
|
{
|
||||||
|
name: 'node',
|
||||||
|
currentVersion: semver.clean(process.version),
|
||||||
|
versionRequirement: packageConfig.engines.node,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const warnings = []
|
||||||
|
for (let i = 0; i < versionRequirements.length; i++) {
|
||||||
|
const mod = versionRequirements[i]
|
||||||
|
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
|
||||||
|
warnings.push(
|
||||||
|
mod.name +
|
||||||
|
': ' +
|
||||||
|
chalk.red(mod.currentVersion) +
|
||||||
|
' should be ' +
|
||||||
|
chalk.green(mod.versionRequirement),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (warnings.length) {
|
||||||
|
console.warn(
|
||||||
|
chalk.yellow(
|
||||||
|
'\nTo use this template, you must update following to modules:\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for (let i = 0; i < warnings.length; i++) {
|
||||||
|
const warning = warnings[i]
|
||||||
|
console.warn(' ' + warning)
|
||||||
|
}
|
||||||
|
console.warn()
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
build/commit_hash.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import childProcess from 'child_process'
|
||||||
|
|
||||||
|
export const getCommitHash = () => {
|
||||||
|
const subst = '$Format:%h$'
|
||||||
|
if (!subst.match(/Format:/)) {
|
||||||
|
return subst
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return childProcess
|
||||||
|
.execSync('git rev-parse --short HEAD')
|
||||||
|
.toString()
|
||||||
|
.trim()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed run git:', e)
|
||||||
|
return 'UNKNOWN'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
build/copy_plugin.js
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { cp } from 'node:fs/promises'
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
import serveStatic from 'serve-static'
|
||||||
|
|
||||||
|
const getPrefix = (s) => {
|
||||||
|
const padEnd = s.endsWith('/') ? s : s + '/'
|
||||||
|
return padEnd.startsWith('/') ? padEnd : '/' + padEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyPlugin = ({ inUrl, inFs }) => {
|
||||||
|
const prefix = getPrefix(inUrl)
|
||||||
|
const subdir = prefix.slice(1)
|
||||||
|
let copyTarget
|
||||||
|
const handler = serveStatic(inFs)
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'copy-plugin-serve',
|
||||||
|
apply: 'serve',
|
||||||
|
configureServer(server) {
|
||||||
|
server.middlewares.use(prefix, handler)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'copy-plugin-build',
|
||||||
|
apply: 'build',
|
||||||
|
configResolved(config) {
|
||||||
|
copyTarget = resolve(config.root, config.build.outDir, subdir)
|
||||||
|
},
|
||||||
|
closeBundle: {
|
||||||
|
order: 'post',
|
||||||
|
sequential: true,
|
||||||
|
async handler() {
|
||||||
|
console.info(`Copying '${inFs}' to ${copyTarget}...`)
|
||||||
|
await cp(inFs, copyTarget, { recursive: true })
|
||||||
|
console.info('Done.')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default copyPlugin
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
/* eslint-disable */
|
|
||||||
require('eventsource-polyfill')
|
|
||||||
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
|
|
||||||
|
|
||||||
hotClient.subscribe(function (event) {
|
|
||||||
if (event.action === 'reload') {
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
require('./check-versions')()
|
|
||||||
var config = require('../config')
|
|
||||||
if (!process.env.NODE_ENV) process.env.NODE_ENV = config.dev.env
|
|
||||||
var path = require('path')
|
|
||||||
var express = require('express')
|
|
||||||
var webpack = require('webpack')
|
|
||||||
var opn = require('opn')
|
|
||||||
var proxyMiddleware = require('http-proxy-middleware')
|
|
||||||
var webpackConfig = process.env.NODE_ENV === 'testing'
|
|
||||||
? require('./webpack.prod.conf')
|
|
||||||
: require('./webpack.dev.conf')
|
|
||||||
|
|
||||||
var updateEmoji = require('./update-emoji').updateEmoji
|
|
||||||
updateEmoji()
|
|
||||||
|
|
||||||
// default port where dev server listens for incoming traffic
|
|
||||||
var port = process.env.PORT || config.dev.port
|
|
||||||
// Define HTTP proxies to your custom API backend
|
|
||||||
// https://github.com/chimurai/http-proxy-middleware
|
|
||||||
var proxyTable = config.dev.proxyTable
|
|
||||||
|
|
||||||
var app = express()
|
|
||||||
var compiler = webpack(webpackConfig)
|
|
||||||
|
|
||||||
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
|
||||||
publicPath: webpackConfig.output.publicPath,
|
|
||||||
writeToDisk: true,
|
|
||||||
stats: {
|
|
||||||
colors: true,
|
|
||||||
chunks: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var hotMiddleware = require('webpack-hot-middleware')(compiler)
|
|
||||||
|
|
||||||
// FIXME: The statement below gives error about hooks being required in webpack 5.
|
|
||||||
// force page reload when html-webpack-plugin template changes
|
|
||||||
// compiler.plugin('compilation', function (compilation) {
|
|
||||||
// compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
|
|
||||||
// // FIXME: This supposed to reload whole page when index.html is changed,
|
|
||||||
// // however now it reloads entire page on every breath, i suppose the order
|
|
||||||
// // of plugins changed or something. It's a minor thing and douesn't hurt
|
|
||||||
// // disabling it, constant reloads hurt much more
|
|
||||||
|
|
||||||
// // hotMiddleware.publish({ action: 'reload' })
|
|
||||||
// // cb()
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
|
|
||||||
// proxy api requests
|
|
||||||
Object.keys(proxyTable).forEach(function (context) {
|
|
||||||
var options = proxyTable[context]
|
|
||||||
if (typeof options === 'string') {
|
|
||||||
options = { target: options }
|
|
||||||
}
|
|
||||||
app.use(proxyMiddleware.createProxyMiddleware(context, options))
|
|
||||||
})
|
|
||||||
|
|
||||||
// handle fallback for HTML5 history API
|
|
||||||
app.use(require('connect-history-api-fallback')())
|
|
||||||
|
|
||||||
// serve webpack bundle output
|
|
||||||
app.use(devMiddleware)
|
|
||||||
|
|
||||||
// enable hot-reload and state-preserving
|
|
||||||
// compilation error display
|
|
||||||
app.use(hotMiddleware)
|
|
||||||
|
|
||||||
// serve pure static assets
|
|
||||||
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
|
|
||||||
app.use(staticPath, express.static('./static'))
|
|
||||||
|
|
||||||
module.exports = app.listen(port, function (err) {
|
|
||||||
if (err) {
|
|
||||||
console.log(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var uri = 'http://localhost:' + port
|
|
||||||
console.log('Listening at ' + uri + '\n')
|
|
||||||
// opn(uri)
|
|
||||||
})
|
|
||||||
73
build/emojis_plugin.js
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { access } from 'node:fs/promises'
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
|
||||||
|
import { languages } from '../src/i18n/languages.js'
|
||||||
|
|
||||||
|
const annotationsImportPrefix = '@kazvmoe-infra/unicode-emoji-json/annotations/'
|
||||||
|
const specialAnnotationsLocale = {
|
||||||
|
ja_easy: 'ja',
|
||||||
|
}
|
||||||
|
|
||||||
|
const internalToAnnotationsLocale = (internal) =>
|
||||||
|
specialAnnotationsLocale[internal] || internal
|
||||||
|
|
||||||
|
// This gets all the annotations that are accessible (whose language
|
||||||
|
// can be chosen in the settings). Data for other languages are
|
||||||
|
// discarded because there is no way for it to be fetched.
|
||||||
|
const getAllAccessibleAnnotations = async (projectRoot) => {
|
||||||
|
const imports = (
|
||||||
|
await Promise.all(
|
||||||
|
languages.map(async (lang) => {
|
||||||
|
const destLang = internalToAnnotationsLocale(lang)
|
||||||
|
const importModule = `${annotationsImportPrefix}${destLang}.json`
|
||||||
|
const importFile = resolve(projectRoot, 'node_modules', importModule)
|
||||||
|
try {
|
||||||
|
await access(importFile)
|
||||||
|
return `'${lang}': () => import('${importModule}')`
|
||||||
|
} catch (e) {
|
||||||
|
if (e.message.match(/ENOENT/)) {
|
||||||
|
console.warn(`Missing emoji annotations locale: ${destLang}`)
|
||||||
|
} else {
|
||||||
|
console.error('test', e.message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.filter((k) => k)
|
||||||
|
.join(',\n')
|
||||||
|
|
||||||
|
return `
|
||||||
|
export const annotationsLoader = {
|
||||||
|
${imports}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojiAnnotationsId = 'virtual:pleroma-fe/emoji-annotations'
|
||||||
|
const emojiAnnotationsIdResolved = '\0' + emojiAnnotationsId
|
||||||
|
|
||||||
|
const emojisPlugin = () => {
|
||||||
|
let projectRoot
|
||||||
|
return {
|
||||||
|
name: 'emojis-plugin',
|
||||||
|
configResolved(conf) {
|
||||||
|
projectRoot = conf.root
|
||||||
|
},
|
||||||
|
resolveId(id) {
|
||||||
|
if (id === emojiAnnotationsId) {
|
||||||
|
return emojiAnnotationsIdResolved
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
async load(id) {
|
||||||
|
if (id === emojiAnnotationsIdResolved) {
|
||||||
|
return await getAllAccessibleAnnotations(projectRoot)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default emojisPlugin
|
||||||
28
build/msw_plugin.js
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
import { resolve } from 'node:path'
|
||||||
|
|
||||||
|
const target = 'node_modules/msw/lib/mockServiceWorker.js'
|
||||||
|
|
||||||
|
const mswPlugin = () => {
|
||||||
|
let projectRoot
|
||||||
|
return {
|
||||||
|
name: 'msw-plugin',
|
||||||
|
apply: 'serve',
|
||||||
|
configResolved(conf) {
|
||||||
|
projectRoot = conf.root
|
||||||
|
},
|
||||||
|
configureServer(server) {
|
||||||
|
server.middlewares.use(async (req, res, next) => {
|
||||||
|
if (req.path === '/mockServiceWorker.js') {
|
||||||
|
const file = await readFile(resolve(projectRoot, target))
|
||||||
|
res.set('Content-Type', 'text/javascript')
|
||||||
|
res.send(file)
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default mswPlugin
|
||||||
33
build/service_worker_messages.js
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
import { dirname, resolve } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
import { langCodeToJsonName, languages } from '../src/i18n/languages.js'
|
||||||
|
|
||||||
|
const i18nDir = resolve(
|
||||||
|
dirname(dirname(fileURLToPath(import.meta.url))),
|
||||||
|
'src/i18n',
|
||||||
|
)
|
||||||
|
|
||||||
|
export const i18nFiles = languages.reduce((acc, lang) => {
|
||||||
|
const name = langCodeToJsonName(lang)
|
||||||
|
const file = resolve(i18nDir, name + '.json')
|
||||||
|
acc[lang] = file
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
export const generateServiceWorkerMessages = async () => {
|
||||||
|
const msgArray = await Promise.all(
|
||||||
|
Object.entries(i18nFiles).map(async ([lang, file]) => {
|
||||||
|
const fileContent = await readFile(file, 'utf-8')
|
||||||
|
const msg = {
|
||||||
|
notifications: JSON.parse(fileContent).notifications || {},
|
||||||
|
}
|
||||||
|
return [lang, msg]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return msgArray.reduce((acc, [lang, msg]) => {
|
||||||
|
acc[lang] = msg
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
211
build/sw_plugin.js
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
import { dirname, resolve } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import * as esbuild from 'esbuild'
|
||||||
|
import { build } from 'vite'
|
||||||
|
|
||||||
|
import {
|
||||||
|
generateServiceWorkerMessages,
|
||||||
|
i18nFiles,
|
||||||
|
} from './service_worker_messages.js'
|
||||||
|
|
||||||
|
const getSWMessagesAsText = async () => {
|
||||||
|
const messages = await generateServiceWorkerMessages()
|
||||||
|
return `export default ${JSON.stringify(messages, undefined, 2)}`
|
||||||
|
}
|
||||||
|
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)))
|
||||||
|
|
||||||
|
const swEnvName = 'virtual:pleroma-fe/service_worker_env'
|
||||||
|
const swEnvNameResolved = '\0' + swEnvName
|
||||||
|
const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };`
|
||||||
|
const getProdSwEnv = ({ assets }) =>
|
||||||
|
`self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
|
||||||
|
|
||||||
|
export const devSwPlugin = ({ swSrc, swDest, transformSW, alias }) => {
|
||||||
|
const swFullSrc = resolve(projectRoot, swSrc)
|
||||||
|
const esbuildAlias = {}
|
||||||
|
Object.entries(alias).forEach(([source, dest]) => {
|
||||||
|
esbuildAlias[source] = dest.startsWith('/') ? projectRoot + dest : dest
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'dev-sw-plugin',
|
||||||
|
apply: 'serve',
|
||||||
|
configResolved() {
|
||||||
|
/* no-op */
|
||||||
|
},
|
||||||
|
resolveId(id) {
|
||||||
|
const name = id.startsWith('/') ? id.slice(1) : id
|
||||||
|
if (name === swDest) {
|
||||||
|
return swFullSrc
|
||||||
|
} else if (name === swEnvName) {
|
||||||
|
return swEnvNameResolved
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
async load(id) {
|
||||||
|
if (id === swFullSrc) {
|
||||||
|
return readFile(swFullSrc, 'utf-8')
|
||||||
|
} else if (id === swEnvNameResolved) {
|
||||||
|
return getDevSwEnv()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* vite does not bundle the service worker
|
||||||
|
* during dev, and firefox does not support ESM as service worker
|
||||||
|
* https://bugzilla.mozilla.org/show_bug.cgi?id=1360870
|
||||||
|
*/
|
||||||
|
async transform(code, id) {
|
||||||
|
if (id === swFullSrc && transformSW) {
|
||||||
|
const res = await esbuild.build({
|
||||||
|
entryPoints: [swSrc],
|
||||||
|
bundle: true,
|
||||||
|
write: false,
|
||||||
|
outfile: 'sw-pleroma.js',
|
||||||
|
alias: esbuildAlias,
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
name: 'vite-like-root-resolve',
|
||||||
|
setup(b) {
|
||||||
|
b.onResolve({ filter: new RegExp(/^\//) }, (args) => ({
|
||||||
|
path: resolve(projectRoot, args.path.slice(1)),
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sw-messages',
|
||||||
|
setup(b) {
|
||||||
|
b.onResolve(
|
||||||
|
{ filter: new RegExp('^' + swMessagesName + '$') },
|
||||||
|
(args) => ({
|
||||||
|
path: args.path,
|
||||||
|
namespace: 'sw-messages',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
b.onLoad(
|
||||||
|
{ filter: /.*/, namespace: 'sw-messages' },
|
||||||
|
async () => ({
|
||||||
|
contents: await getSWMessagesAsText(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sw-env',
|
||||||
|
setup(b) {
|
||||||
|
b.onResolve(
|
||||||
|
{ filter: new RegExp('^' + swEnvName + '$') },
|
||||||
|
(args) => ({
|
||||||
|
path: args.path,
|
||||||
|
namespace: 'sw-env',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
b.onLoad({ filter: /.*/, namespace: 'sw-env' }, () => ({
|
||||||
|
contents: getDevSwEnv(),
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
const text = res.outputFiles[0].text
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Idea taken from
|
||||||
|
// https://github.com/vite-pwa/vite-plugin-pwa/blob/main/src/plugins/build.ts
|
||||||
|
// rollup does not support compiling to iife if we want to code-split;
|
||||||
|
// however, we must compile the service worker to iife because of browser support.
|
||||||
|
// Run another vite build just for the service worker targeting iife at
|
||||||
|
// the end of the build.
|
||||||
|
export const buildSwPlugin = ({ swSrc, swDest }) => {
|
||||||
|
let config
|
||||||
|
return {
|
||||||
|
name: 'build-sw-plugin',
|
||||||
|
enforce: 'post',
|
||||||
|
apply: 'build',
|
||||||
|
configResolved(resolvedConfig) {
|
||||||
|
config = {
|
||||||
|
define: resolvedConfig.define,
|
||||||
|
resolve: resolvedConfig.resolve,
|
||||||
|
plugins: [swMessagesPlugin()],
|
||||||
|
publicDir: false,
|
||||||
|
build: {
|
||||||
|
...resolvedConfig.build,
|
||||||
|
lib: {
|
||||||
|
entry: swSrc,
|
||||||
|
formats: ['iife'],
|
||||||
|
name: 'sw_pleroma',
|
||||||
|
},
|
||||||
|
emptyOutDir: false,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
entryFileNames: swDest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
configFile: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
generateBundle: {
|
||||||
|
order: 'post',
|
||||||
|
sequential: true,
|
||||||
|
async handler(_, bundle) {
|
||||||
|
const assets = Object.keys(bundle)
|
||||||
|
.filter((name) => !/\.map$/.test(name))
|
||||||
|
.map((name) => '/' + name)
|
||||||
|
config.plugins.push({
|
||||||
|
name: 'build-sw-env-plugin',
|
||||||
|
resolveId(id) {
|
||||||
|
if (id === swEnvName) {
|
||||||
|
return swEnvNameResolved
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
load(id) {
|
||||||
|
if (id === swEnvNameResolved) {
|
||||||
|
return getProdSwEnv({ assets })
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
closeBundle: {
|
||||||
|
order: 'post',
|
||||||
|
sequential: true,
|
||||||
|
async handler() {
|
||||||
|
console.info('Building service worker for production')
|
||||||
|
await build(config)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const swMessagesName = 'virtual:pleroma-fe/service_worker_messages'
|
||||||
|
const swMessagesNameResolved = '\0' + swMessagesName
|
||||||
|
|
||||||
|
export const swMessagesPlugin = () => {
|
||||||
|
return {
|
||||||
|
name: 'sw-messages-plugin',
|
||||||
|
resolveId(id) {
|
||||||
|
if (id === swMessagesName) {
|
||||||
|
Object.values(i18nFiles).forEach((f) => {
|
||||||
|
this.addWatchFile(f)
|
||||||
|
})
|
||||||
|
return swMessagesNameResolved
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async load(id) {
|
||||||
|
if (id === swMessagesNameResolved) {
|
||||||
|
return await getSWMessagesAsText()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,22 @@
|
||||||
|
import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with {
|
||||||
module.exports = {
|
type: 'json',
|
||||||
updateEmoji () {
|
|
||||||
const emojis = require('@kazvmoe-infra/unicode-emoji-json/data-by-group')
|
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
Object.keys(emojis)
|
|
||||||
.map(k => {
|
|
||||||
emojis[k].map(e => {
|
|
||||||
delete e.unicode_version
|
|
||||||
delete e.emoji_version
|
|
||||||
delete e.skin_tone_support_unicode_version
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const res = {}
|
|
||||||
Object.keys(emojis)
|
|
||||||
.map(k => {
|
|
||||||
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
|
|
||||||
res[groupId] = emojis[k]
|
|
||||||
})
|
|
||||||
|
|
||||||
console.info('Updating emojis...')
|
|
||||||
fs.writeFileSync('static/emoji.json', JSON.stringify(res))
|
|
||||||
console.info('Done.')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
Object.keys(emojis).map((k) => {
|
||||||
|
emojis[k].map((e) => {
|
||||||
|
delete e.unicode_version
|
||||||
|
delete e.emoji_version
|
||||||
|
delete e.skin_tone_support_unicode_version
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = {}
|
||||||
|
Object.keys(emojis).map((k) => {
|
||||||
|
const groupId = k.replace('&', 'and').replace(/ /g, '-').toLowerCase()
|
||||||
|
res[groupId] = emojis[k]
|
||||||
|
})
|
||||||
|
|
||||||
|
console.info('Updating emojis...')
|
||||||
|
fs.writeFileSync('src/assets/emoji.json', JSON.stringify(res))
|
||||||
|
console.info('Done.')
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
var path = require('path')
|
|
||||||
var config = require('../config')
|
|
||||||
var sass = require('sass')
|
|
||||||
var MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|
||||||
|
|
||||||
exports.assetsPath = function (_path) {
|
|
||||||
var assetsSubDirectory = process.env.NODE_ENV === 'production'
|
|
||||||
? config.build.assetsSubDirectory
|
|
||||||
: config.dev.assetsSubDirectory
|
|
||||||
return path.posix.join(assetsSubDirectory, _path)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.cssLoaders = function (options) {
|
|
||||||
options = options || {}
|
|
||||||
|
|
||||||
function generateLoaders (loaders) {
|
|
||||||
// Extract CSS when that option is specified
|
|
||||||
// (which is the case during production build)
|
|
||||||
if (options.extract) {
|
|
||||||
return [MiniCssExtractPlugin.loader].concat(loaders)
|
|
||||||
} else {
|
|
||||||
return ['vue-style-loader'].concat(loaders)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// http://vuejs.github.io/vue-loader/configurations/extract-css.html
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
test: /\.(post)?css$/,
|
|
||||||
use: generateLoaders(['css-loader', 'postcss-loader']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.less$/,
|
|
||||||
use: generateLoaders(['css-loader', 'postcss-loader', 'less-loader']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.sass$/,
|
|
||||||
use: generateLoaders([
|
|
||||||
'css-loader',
|
|
||||||
'postcss-loader',
|
|
||||||
{
|
|
||||||
loader: 'sass-loader',
|
|
||||||
options: {
|
|
||||||
indentedSyntax: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: generateLoaders(['css-loader', 'postcss-loader', 'sass-loader'])
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.styl(us)?$/,
|
|
||||||
use: generateLoaders(['css-loader', 'postcss-loader', 'stylus-loader']),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate loaders for standalone style files (outside of .vue)
|
|
||||||
exports.styleLoaders = function (options) {
|
|
||||||
return exports.cssLoaders(options)
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
var path = require('path')
|
|
||||||
var config = require('../config')
|
|
||||||
var utils = require('./utils')
|
|
||||||
var projectRoot = path.resolve(__dirname, '../')
|
|
||||||
var ServiceWorkerWebpackPlugin = require('serviceworker-webpack5-plugin')
|
|
||||||
var CopyPlugin = require('copy-webpack-plugin');
|
|
||||||
var { VueLoaderPlugin } = require('vue-loader')
|
|
||||||
var ESLintPlugin = require('eslint-webpack-plugin');
|
|
||||||
var StylelintPlugin = require('stylelint-webpack-plugin');
|
|
||||||
|
|
||||||
var env = process.env.NODE_ENV
|
|
||||||
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
|
|
||||||
// various preprocessor loaders added to vue-loader at the end of this file
|
|
||||||
var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
|
|
||||||
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
|
|
||||||
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
|
|
||||||
|
|
||||||
var now = Date.now()
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
entry: {
|
|
||||||
app: './src/main.js'
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: config.build.assetsRoot,
|
|
||||||
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
|
|
||||||
filename: '[name].js',
|
|
||||||
chunkFilename: '[name].js'
|
|
||||||
},
|
|
||||||
optimization: {
|
|
||||||
splitChunks: {
|
|
||||||
chunks: 'all'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.mjs', '.js', '.jsx', '.vue'],
|
|
||||||
modules: [
|
|
||||||
path.join(__dirname, '../node_modules')
|
|
||||||
],
|
|
||||||
alias: {
|
|
||||||
'static': path.resolve(__dirname, '../static'),
|
|
||||||
'src': path.resolve(__dirname, '../src'),
|
|
||||||
'assets': path.resolve(__dirname, '../src/assets'),
|
|
||||||
'components': path.resolve(__dirname, '../src/components'),
|
|
||||||
'vue-i18n': 'vue-i18n/dist/vue-i18n.runtime.esm-bundler.js'
|
|
||||||
},
|
|
||||||
fallback: {
|
|
||||||
'querystring': require.resolve('querystring-es3'),
|
|
||||||
'url': require.resolve('url/')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
noParse: /node_modules\/localforage\/dist\/localforage.js/,
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
enforce: 'post',
|
|
||||||
test: /\.(json5?|ya?ml)$/, // target json, json5, yaml and yml files
|
|
||||||
type: 'javascript/auto',
|
|
||||||
loader: '@intlify/vue-i18n-loader',
|
|
||||||
include: [ // Use `Rule.include` to specify the files of locale messages to be pre-compiled
|
|
||||||
path.resolve(__dirname, '../src/i18n')
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.vue$/,
|
|
||||||
loader: 'vue-loader',
|
|
||||||
options: {
|
|
||||||
compilerOptions: {
|
|
||||||
isCustomElement(tag) {
|
|
||||||
if (tag === 'pinch-zoom') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.jsx?$/,
|
|
||||||
include: projectRoot,
|
|
||||||
exclude: /node_modules\/(?!tributejs)/,
|
|
||||||
use: 'babel-loader'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
|
||||||
type: 'asset',
|
|
||||||
generator: {
|
|
||||||
filename: utils.assetsPath('img/[name].[hash:7][ext]')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
|
||||||
type: 'asset',
|
|
||||||
generator: {
|
|
||||||
filename: utils.assetsPath('fonts/[name].[hash:7][ext]')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.mjs$/,
|
|
||||||
include: /node_modules/,
|
|
||||||
type: 'javascript/auto'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new ServiceWorkerWebpackPlugin({
|
|
||||||
entry: path.join(__dirname, '..', 'src/sw.js'),
|
|
||||||
filename: 'sw-pleroma.js'
|
|
||||||
}),
|
|
||||||
new ESLintPlugin({
|
|
||||||
extensions: ['js', 'vue'],
|
|
||||||
formatter: require('eslint-formatter-friendly')
|
|
||||||
}),
|
|
||||||
new StylelintPlugin({}),
|
|
||||||
new VueLoaderPlugin(),
|
|
||||||
// This copies Ruffle's WASM to a directory so that JS side can access it
|
|
||||||
new CopyPlugin({
|
|
||||||
patterns: [
|
|
||||||
{
|
|
||||||
from: "node_modules/@ruffle-rs/ruffle/**/*",
|
|
||||||
to: "static/ruffle/[name][ext]"
|
|
||||||
},
|
|
||||||
],
|
|
||||||
options: {
|
|
||||||
concurrency: 100,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
var config = require('../config')
|
|
||||||
var webpack = require('webpack')
|
|
||||||
var merge = require('webpack-merge')
|
|
||||||
var utils = require('./utils')
|
|
||||||
var baseWebpackConfig = require('./webpack.base.conf')
|
|
||||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
|
||||||
|
|
||||||
// add hot-reload related code to entry chunks
|
|
||||||
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
|
|
||||||
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = merge(baseWebpackConfig, {
|
|
||||||
module: {
|
|
||||||
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
|
|
||||||
},
|
|
||||||
mode: 'development',
|
|
||||||
// eval-source-map is faster for development
|
|
||||||
devtool: 'eval-source-map',
|
|
||||||
plugins: [
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env': config.dev.env,
|
|
||||||
'COMMIT_HASH': JSON.stringify('DEV'),
|
|
||||||
'DEV_OVERRIDES': JSON.stringify(config.dev.settings),
|
|
||||||
'__VUE_OPTIONS_API__': true,
|
|
||||||
'__VUE_PROD_DEVTOOLS__': false
|
|
||||||
}),
|
|
||||||
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
|
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
|
||||||
// https://github.com/ampedandwired/html-webpack-plugin
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
filename: 'index.html',
|
|
||||||
template: 'index.html',
|
|
||||||
inject: true
|
|
||||||
})
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
var path = require('path')
|
|
||||||
var config = require('../config')
|
|
||||||
var utils = require('./utils')
|
|
||||||
var webpack = require('webpack')
|
|
||||||
var merge = require('webpack-merge')
|
|
||||||
var baseWebpackConfig = require('./webpack.base.conf')
|
|
||||||
var MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
|
||||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
|
|
||||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
|
||||||
var env = process.env.NODE_ENV === 'testing'
|
|
||||||
? require('../config/test.env')
|
|
||||||
: config.build.env
|
|
||||||
|
|
||||||
let commitHash = (() => {
|
|
||||||
const subst = "$Format:%h$";
|
|
||||||
if(!subst.match(/Format:/)) {
|
|
||||||
return subst;
|
|
||||||
} else {
|
|
||||||
return require('child_process')
|
|
||||||
.execSync('git rev-parse --short HEAD')
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
var webpackConfig = merge(baseWebpackConfig, {
|
|
||||||
mode: 'production',
|
|
||||||
module: {
|
|
||||||
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, extract: true })
|
|
||||||
},
|
|
||||||
devtool: config.build.productionSourceMap ? 'source-map' : false,
|
|
||||||
optimization: {
|
|
||||||
minimize: true,
|
|
||||||
splitChunks: {
|
|
||||||
chunks: 'all'
|
|
||||||
},
|
|
||||||
minimizer: [
|
|
||||||
`...`,
|
|
||||||
new CssMinimizerPlugin()
|
|
||||||
]
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: config.build.assetsRoot,
|
|
||||||
filename: utils.assetsPath('js/[name].[chunkhash].js'),
|
|
||||||
chunkFilename: utils.assetsPath('js/[name].[chunkhash].js')
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
// http://vuejs.github.io/vue-loader/workflow/production.html
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
'process.env': env,
|
|
||||||
'COMMIT_HASH': JSON.stringify(commitHash),
|
|
||||||
'DEV_OVERRIDES': JSON.stringify(undefined),
|
|
||||||
'__VUE_OPTIONS_API__': true,
|
|
||||||
'__VUE_PROD_DEVTOOLS__': false
|
|
||||||
}),
|
|
||||||
// extract css into its own file
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: utils.assetsPath('css/[name].[contenthash].css')
|
|
||||||
}),
|
|
||||||
// generate dist index.html with correct asset hash for caching.
|
|
||||||
// you can customize output by editing /index.html
|
|
||||||
// see https://github.com/ampedandwired/html-webpack-plugin
|
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
filename: process.env.NODE_ENV === 'testing'
|
|
||||||
? 'index.html'
|
|
||||||
: config.build.index,
|
|
||||||
template: 'index.html',
|
|
||||||
inject: true,
|
|
||||||
minify: {
|
|
||||||
removeComments: true,
|
|
||||||
collapseWhitespace: true,
|
|
||||||
removeAttributeQuotes: true,
|
|
||||||
ignoreCustomComments: [/server-generated-meta/]
|
|
||||||
// more options:
|
|
||||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
// split vendor js into its own file
|
|
||||||
// extract webpack runtime and module manifest to its own file in order to
|
|
||||||
// prevent vendor hash from being updated whenever app bundle is updated
|
|
||||||
// new webpack.optimize.SplitChunksPlugin({
|
|
||||||
// name: ['app', 'vendor']
|
|
||||||
// }),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
if (config.build.productionGzip) {
|
|
||||||
var CompressionWebpackPlugin = require('compression-webpack-plugin')
|
|
||||||
|
|
||||||
webpackConfig.plugins.push(
|
|
||||||
new CompressionWebpackPlugin({
|
|
||||||
asset: '[path].gz[query]',
|
|
||||||
algorithm: 'gzip',
|
|
||||||
test: new RegExp(
|
|
||||||
'\\.(' +
|
|
||||||
config.build.productionGzipExtensions.join('|') +
|
|
||||||
')$'
|
|
||||||
),
|
|
||||||
threshold: 10240,
|
|
||||||
minRatio: 0.8
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = webpackConfig
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Updated shadow editor, hopefully fixed long-standing bugs, added ability to specify shadow's name.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Support bookmark folders
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
Updated our build system to support browsers:
|
|
||||||
Safari >= 15
|
|
||||||
Firefox >= 115
|
|
||||||
Android > 4
|
|
||||||
no Opera Mini support
|
|
||||||
no IE support
|
|
||||||
no "dead" (unmaintained) browsers support
|
|
||||||
|
|
||||||
This does not guarantee that browsers will or will not work.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Support displaying time in absolute format
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Fix whitespaces for multiple status mute reasons, display bot status reason
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Inform users that Smithereen public polls are public
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Simplify the OAuth client_name to 'PleromaFE'
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Splash screen + loading indicator to make process of identifying initialization issues and load performance
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
var merge = require('webpack-merge')
|
|
||||||
var prodEnv = require('./prod.env')
|
|
||||||
|
|
||||||
module.exports = merge(prodEnv, {
|
|
||||||
NODE_ENV: '"development"'
|
|
||||||
})
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
// see http://vuejs-templates.github.io/webpack for documentation.
|
|
||||||
const path = require('path')
|
|
||||||
let settings = {}
|
|
||||||
try {
|
|
||||||
settings = require('./local.json')
|
|
||||||
if (settings.target && settings.target.endsWith('/')) {
|
|
||||||
// replacing trailing slash since it can conflict with some apis
|
|
||||||
// and that's how actual BE reports its url
|
|
||||||
settings.target = settings.target.replace(/\/$/, '')
|
|
||||||
}
|
|
||||||
console.log('Using local dev server settings (/config/local.json):')
|
|
||||||
console.log(JSON.stringify(settings, null, 2))
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Local dev server settings not found (/config/local.json)')
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = settings.target || 'http://localhost:4000/'
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
build: {
|
|
||||||
env: require('./prod.env'),
|
|
||||||
index: path.resolve(__dirname, '../dist/index.html'),
|
|
||||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
|
||||||
assetsSubDirectory: 'static',
|
|
||||||
assetsPublicPath: '/',
|
|
||||||
productionSourceMap: true,
|
|
||||||
// Gzip off by default as many popular static hosts such as
|
|
||||||
// Surge or Netlify already gzip all static assets for you.
|
|
||||||
// Before setting to `true`, make sure to:
|
|
||||||
// npm install --save-dev compression-webpack-plugin
|
|
||||||
productionGzip: false,
|
|
||||||
productionGzipExtensions: ['js', 'css']
|
|
||||||
},
|
|
||||||
dev: {
|
|
||||||
env: require('./dev.env'),
|
|
||||||
port: 8080,
|
|
||||||
settings,
|
|
||||||
assetsSubDirectory: 'static',
|
|
||||||
assetsPublicPath: '/',
|
|
||||||
proxyTable: {
|
|
||||||
'/api': {
|
|
||||||
target,
|
|
||||||
changeOrigin: true,
|
|
||||||
cookieDomainRewrite: 'localhost'
|
|
||||||
},
|
|
||||||
'/nodeinfo': {
|
|
||||||
target,
|
|
||||||
changeOrigin: true,
|
|
||||||
cookieDomainRewrite: 'localhost'
|
|
||||||
},
|
|
||||||
'/socket': {
|
|
||||||
target,
|
|
||||||
changeOrigin: true,
|
|
||||||
cookieDomainRewrite: 'localhost',
|
|
||||||
ws: true,
|
|
||||||
headers: {
|
|
||||||
'Origin': target
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'/oauth/revoke': {
|
|
||||||
target,
|
|
||||||
changeOrigin: true,
|
|
||||||
cookieDomainRewrite: 'localhost'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// CSS Sourcemaps off by default because relative paths are "buggy"
|
|
||||||
// with this option, according to the CSS-Loader README
|
|
||||||
// (https://github.com/webpack/css-loader#sourcemaps)
|
|
||||||
// In our experience, they generally work as expected,
|
|
||||||
// just be aware of this issue when enabling this option.
|
|
||||||
cssSourceMap: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
NODE_ENV: '"production"'
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
var merge = require('webpack-merge')
|
|
||||||
var devEnv = require('./dev.env')
|
|
||||||
|
|
||||||
module.exports = merge(devEnv, {
|
|
||||||
NODE_ENV: '"testing"'
|
|
||||||
})
|
|
||||||
57
docker-compose.e2e.yml
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: pleroma
|
||||||
|
POSTGRES_PASSWORD: pleroma
|
||||||
|
POSTGRES_DB: pleroma
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U pleroma -d pleroma"]
|
||||||
|
interval: 2s
|
||||||
|
timeout: 2s
|
||||||
|
retries: 30
|
||||||
|
|
||||||
|
pleroma:
|
||||||
|
image: ${PLEROMA_IMAGE:-git.pleroma.social:5050/pleroma/pleroma:stable}
|
||||||
|
environment:
|
||||||
|
DB_USER: pleroma
|
||||||
|
DB_PASS: pleroma
|
||||||
|
DB_NAME: pleroma
|
||||||
|
DB_HOST: db
|
||||||
|
DB_PORT: 5432
|
||||||
|
DOMAIN: localhost
|
||||||
|
INSTANCE_NAME: Pleroma E2E
|
||||||
|
ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
|
||||||
|
NOTIFY_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
|
||||||
|
E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
|
||||||
|
E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
|
||||||
|
E2E_ADMIN_EMAIL: ${E2E_ADMIN_EMAIL:-admin@example.com}
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
volumes:
|
||||||
|
- ./docker/pleroma/entrypoint.e2e.sh:/opt/pleroma/entrypoint.e2e.sh:ro
|
||||||
|
entrypoint: ["/bin/ash", "/opt/pleroma/entrypoint.e2e.sh"]
|
||||||
|
healthcheck:
|
||||||
|
# NOTE: "localhost" may resolve to ::1 in some images (IPv6) while Pleroma only
|
||||||
|
# listens on IPv4 in this container. Use 127.0.0.1 to avoid false negatives.
|
||||||
|
test: ["CMD-SHELL", "test -f /var/lib/pleroma/.e2e_seeded && wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null || exit 1"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 60
|
||||||
|
|
||||||
|
e2e:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/e2e/Dockerfile.e2e
|
||||||
|
depends_on:
|
||||||
|
pleroma:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
CI: "1"
|
||||||
|
VITE_PROXY_TARGET: http://pleroma:4000
|
||||||
|
VITE_PROXY_ORIGIN: http://localhost:4000
|
||||||
|
E2E_BASE_URL: http://localhost:8080
|
||||||
|
E2E_ADMIN_USERNAME: ${E2E_ADMIN_USERNAME:-admin}
|
||||||
|
E2E_ADMIN_PASSWORD: ${E2E_ADMIN_PASSWORD:-adminadmin}
|
||||||
|
command: ["yarn", "e2e:pw"]
|
||||||
16
docker/e2e/Dockerfile.e2e
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
FROM mcr.microsoft.com/playwright:v1.57.0-jammy
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
|
||||||
|
|
||||||
|
RUN npm install -g yarn@1.22.22
|
||||||
|
|
||||||
|
COPY package.json yarn.lock ./
|
||||||
|
RUN yarn --frozen-lockfile
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV CI=1
|
||||||
|
|
||||||
|
CMD ["yarn", "e2e:pw"]
|
||||||
71
docker/pleroma/entrypoint.e2e.sh
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
#!/bin/ash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SEED_SENTINEL_PATH="/var/lib/pleroma/.e2e_seeded"
|
||||||
|
CONFIG_OVERRIDE_PATH="/var/lib/pleroma/config.exs"
|
||||||
|
|
||||||
|
echo "-- Waiting for database..."
|
||||||
|
while ! pg_isready -U "${DB_USER:-pleroma}" -d "postgres://${DB_HOST:-db}:${DB_PORT:-5432}/${DB_NAME:-pleroma}" -t 1; do
|
||||||
|
sleep 1s
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "-- Writing E2E config overrides..."
|
||||||
|
cat > "$CONFIG_OVERRIDE_PATH" <<'EOF'
|
||||||
|
import Config
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Captcha,
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
config :pleroma, :instance,
|
||||||
|
registrations_open: true,
|
||||||
|
account_activation_required: false,
|
||||||
|
approval_required: false
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "-- Running migrations..."
|
||||||
|
/opt/pleroma/bin/pleroma_ctl migrate
|
||||||
|
|
||||||
|
echo "-- Starting!"
|
||||||
|
/opt/pleroma/bin/pleroma start &
|
||||||
|
PLEROMA_PID="$!"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [ -n "${PLEROMA_PID:-}" ] && kill -0 "$PLEROMA_PID" 2>/dev/null; then
|
||||||
|
kill -TERM "$PLEROMA_PID"
|
||||||
|
wait "$PLEROMA_PID" || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup INT TERM
|
||||||
|
|
||||||
|
echo "-- Waiting for API..."
|
||||||
|
api_ok="false"
|
||||||
|
for _i in $(seq 1 120); do
|
||||||
|
if wget -qO- http://127.0.0.1:4000/api/v1/instance >/dev/null 2>&1; then
|
||||||
|
api_ok="true"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1s
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$api_ok" != "true" ]; then
|
||||||
|
echo "Timed out waiting for Pleroma API to become available"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$SEED_SENTINEL_PATH" ]; then
|
||||||
|
if [ -n "${E2E_ADMIN_USERNAME:-}" ] && [ -n "${E2E_ADMIN_PASSWORD:-}" ] && [ -n "${E2E_ADMIN_EMAIL:-}" ]; then
|
||||||
|
echo "-- Seeding admin user (${E2E_ADMIN_USERNAME})..."
|
||||||
|
if ! /opt/pleroma/bin/pleroma_ctl user new "$E2E_ADMIN_USERNAME" "$E2E_ADMIN_EMAIL" --admin --password "$E2E_ADMIN_PASSWORD" -y; then
|
||||||
|
echo "-- User already exists (or creation failed), ensuring admin + confirmed..."
|
||||||
|
/opt/pleroma/bin/pleroma_ctl user set "$E2E_ADMIN_USERNAME" --admin --confirmed
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "-- Skipping admin seeding (missing E2E_ADMIN_* env)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "$SEED_SENTINEL_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait "$PLEROMA_PID"
|
||||||
34
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import js from '@eslint/js'
|
||||||
|
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||||
|
import vue from 'eslint-plugin-vue'
|
||||||
|
import globals from 'globals'
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
...vue.configs['flat/recommended'],
|
||||||
|
globalIgnores(['**/*.js', 'build/', 'dist/', 'config/']),
|
||||||
|
{
|
||||||
|
files: ['src/**/*.vue'],
|
||||||
|
plugins: { js },
|
||||||
|
extends: ['js/recommended'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2024,
|
||||||
|
sourceType: 'module',
|
||||||
|
|
||||||
|
parserOptions: {
|
||||||
|
parser: '@babel/eslint-parser',
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.vitest,
|
||||||
|
...globals.chai,
|
||||||
|
...globals.commonjs,
|
||||||
|
...globals.serviceworker,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
'vue/require-prop-types': 0,
|
||||||
|
'vue/multi-word-component-names': 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
122
index.html
|
|
@ -3,115 +3,20 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
|
||||||
<link rel="icon" type="image/png" href="/favicon.png">
|
<link rel="preload" href="/static/config.json" as="fetch" crossorigin />
|
||||||
|
<link rel="preload" href="/api/pleroma/frontend_configurations" as="fetch" crossorigin />
|
||||||
|
<link rel="preload" href="/nodeinfo/2.0.json" as="fetch" crossorigin />
|
||||||
|
<link rel="preload" href="/nodeinfo/2.1.json" as="fetch" crossorigin />
|
||||||
|
<link rel="preload" href="/api/v1/instance" as="fetch" crossorigin />
|
||||||
|
<link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" />
|
||||||
<!-- putting styles here to avoid having to wait for styles to load up -->
|
<!-- putting styles here to avoid having to wait for styles to load up -->
|
||||||
<style id="splashscreen">
|
<link rel="stylesheet" id="splashscreen" href="/static/splash.css" />
|
||||||
#splash {
|
<link rel="stylesheet" id="custom-styles-holder" type="text/css" href="/static/empty.css" />
|
||||||
--scale: 1;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: auto;
|
|
||||||
grid-template-columns: auto;
|
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
justify-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
background: #0f161e;
|
|
||||||
font-family: sans-serif;
|
|
||||||
color: #b9b9ba;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 9999;
|
|
||||||
font-size: calc(1vw + 1vh + 1vmin);
|
|
||||||
}
|
|
||||||
|
|
||||||
#splash-credit {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 14px;
|
|
||||||
bottom: 16px;
|
|
||||||
right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#splash-container {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mascot-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
justify-content: center;
|
|
||||||
perspective: 60em;
|
|
||||||
perspective-origin: 0 -15em;
|
|
||||||
transform-style: preserve-3d;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mascot {
|
|
||||||
width: calc(10em * var(--scale));
|
|
||||||
height: calc(10em * var(--scale));
|
|
||||||
object-fit: contain;
|
|
||||||
object-position: bottom;
|
|
||||||
transform: translateZ(-2em);
|
|
||||||
}
|
|
||||||
|
|
||||||
#throbber {
|
|
||||||
display: grid;
|
|
||||||
width: calc(5em * 0.5 * var(--scale));
|
|
||||||
height: calc(8em * 0.5 * var(--scale));
|
|
||||||
margin-left: 4.1em;
|
|
||||||
z-index: 2;
|
|
||||||
grid-template-rows: repeat(8, 1fr);
|
|
||||||
grid-template-columns: repeat(5, 1fr);
|
|
||||||
grid-template-areas: "P P . L L"
|
|
||||||
"P P . L L"
|
|
||||||
"P P . L L"
|
|
||||||
"P P . L L"
|
|
||||||
"P P . . ."
|
|
||||||
"P P . . ."
|
|
||||||
"P P . E E"
|
|
||||||
"P P . E E";
|
|
||||||
}
|
|
||||||
|
|
||||||
.chunk {
|
|
||||||
background-color: #e2b188;
|
|
||||||
box-shadow: 0.01em 0.01em 0.1em 0 #e2b188;
|
|
||||||
}
|
|
||||||
|
|
||||||
#chunk-P {
|
|
||||||
grid-area: P;
|
|
||||||
border-top-left-radius: calc(var(--logoChunkSize) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#chunk-L {
|
|
||||||
grid-area: L;
|
|
||||||
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#chunk-E {
|
|
||||||
grid-area: E;
|
|
||||||
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#status {
|
|
||||||
margin-top: 1em;
|
|
||||||
line-height: 2;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion) {
|
|
||||||
#throbber {
|
|
||||||
animation: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<style id="pleroma-eager-styles" type="text/css"></style>
|
|
||||||
<style id="pleroma-lazy-styles" type="text/css"></style>
|
|
||||||
<!--server-generated-meta-->
|
<!--server-generated-meta-->
|
||||||
</head>
|
</head>
|
||||||
<body style="margin: 0; padding: 0">
|
<body>
|
||||||
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
<noscript>To use Pleroma, please enable JavaScript.</noscript>
|
||||||
<div id="splash">
|
<div id="splash" class="initial-hidden">
|
||||||
<!-- we are hiding entire graphic so no point showing credit -->
|
<!-- we are hiding entire graphic so no point showing credit -->
|
||||||
<div aria-hidden="true" id="splash-credit">
|
<div aria-hidden="true" id="splash-credit">
|
||||||
Art by pipivovott
|
Art by pipivovott
|
||||||
|
|
@ -126,18 +31,21 @@
|
||||||
<div class="chunk" id="chunk-E">
|
<div class="chunk" id="chunk-E">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img id="mascot" src="/static/pleromatan_apology.png">
|
<img id="mascot" src="/static/pleromatan_apology_small.webp">
|
||||||
</div>
|
</div>
|
||||||
<div id="status" class="css-ok">
|
<div id="status" class="css-ok">
|
||||||
<!-- (。>﹏<) -->
|
<!-- (。>﹏<) -->
|
||||||
<!-- it's a pseudographic, don't want screenreader read out nonsense -->
|
<!-- it's a pseudographic, don't want screenreader read out nonsense -->
|
||||||
<span aria-hidden="true" class="initial-text">(。>﹏<)</span>
|
<span aria-hidden="true" class="initial-text">(。>﹏<)</span>
|
||||||
</div>
|
</div>
|
||||||
|
<code id="statusError"></code>
|
||||||
|
<pre id="statusStack"></pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="app" class="hidden"></div>
|
<div id="app" class="hidden"></div>
|
||||||
<div id="modal"></div>
|
<div id="modal"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
<div id="popovers" />
|
<div id="popovers"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
180
package.json
|
|
@ -1,137 +1,127 @@
|
||||||
{
|
{
|
||||||
"name": "pleroma_fe",
|
"name": "pleroma_fe",
|
||||||
"version": "2.7.1",
|
"version": "2.10.1",
|
||||||
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
|
"description": "Pleroma frontend, the default frontend of Pleroma social network server",
|
||||||
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
|
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node build/dev-server.js",
|
"dev": "node build/update-emoji.js && vite dev",
|
||||||
"build": "node build/build.js",
|
"build": "node build/update-emoji.js && vite build",
|
||||||
"unit": "karma start test/unit/karma.conf.js --single-run",
|
"unit": "node build/update-emoji.js && vitest --run",
|
||||||
"unit:watch": "karma start test/unit/karma.conf.js --single-run=false",
|
"unit-ci": "node build/update-emoji.js && vitest --run --browser.headless",
|
||||||
"e2e": "node test/e2e/runner.js",
|
"unit:watch": "node build/update-emoji.js && vitest",
|
||||||
"test": "npm run unit && npm run e2e",
|
"e2e:pw": "playwright test --config test/e2e-playwright/playwright.config.mjs",
|
||||||
"stylelint": "npx stylelint '**/*.scss' '**/*.vue'",
|
"e2e": "sh ./tools/e2e/run.sh",
|
||||||
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs",
|
"test": "yarn run unit && yarn run e2e",
|
||||||
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
|
"ci-biome": "yarn exec biome check",
|
||||||
|
"ci-eslint": "yarn exec eslint",
|
||||||
|
"ci-stylelint": "yarn exec stylelint '**/*.scss' '**/*.vue'",
|
||||||
|
"lint": "yarn ci-biome; yarn ci-eslint; yarn ci-stylelint",
|
||||||
|
"lint-fix": "yarn exec eslint -- --fix; yarn exec stylelint '**/*.scss' '**/*.vue' --fix; biome check --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.21.5",
|
"@babel/runtime": "7.28.4",
|
||||||
"@chenfengyuan/vue-qrcode": "2.0.0",
|
"@chenfengyuan/vue-qrcode": "2.0.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "6.4.0",
|
"@fortawesome/fontawesome-svg-core": "7.1.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "6.4.0",
|
"@fortawesome/free-regular-svg-icons": "7.1.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "6.4.0",
|
"@fortawesome/free-solid-svg-icons": "7.1.0",
|
||||||
"@fortawesome/vue-fontawesome": "3.0.3",
|
"@fortawesome/vue-fontawesome": "3.1.2",
|
||||||
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
|
"@kazvmoe-infra/pinch-zoom-element": "1.3.0",
|
||||||
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
|
||||||
"@ruffle-rs/ruffle": "0.1.0-nightly.2024.8.21",
|
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.6.22",
|
||||||
"@vuelidate/core": "2.0.3",
|
"@vuelidate/core": "2.0.3",
|
||||||
"@vuelidate/validators": "2.0.4",
|
"@vuelidate/validators": "2.0.4",
|
||||||
|
"@web3-storage/parse-link-header": "^3.1.0",
|
||||||
"body-scroll-lock": "3.1.5",
|
"body-scroll-lock": "3.1.5",
|
||||||
"chromatism": "3.0.0",
|
"chromatism": "3.0.0",
|
||||||
"click-outside-vue3": "4.0.1",
|
"click-outside-vue3": "4.0.1",
|
||||||
"cropperjs": "1.5.13",
|
"cropperjs": "2.0.1",
|
||||||
"escape-html": "1.0.3",
|
"escape-html": "1.0.3",
|
||||||
|
"globals": "^16.0.0",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"js-cookie": "3.0.5",
|
"js-cookie": "3.0.5",
|
||||||
"localforage": "1.10.0",
|
"localforage": "1.10.0",
|
||||||
"parse-link-header": "2.0.0",
|
"parse-link-header": "2.0.0",
|
||||||
"phoenix": "1.7.7",
|
"phoenix": "1.8.1",
|
||||||
"punycode.js": "2.3.0",
|
"pinia": "^3.0.0",
|
||||||
"qrcode": "1.5.3",
|
"punycode.js": "2.3.1",
|
||||||
|
"qrcode": "1.5.4",
|
||||||
"querystring-es3": "0.2.1",
|
"querystring-es3": "0.2.1",
|
||||||
"url": "0.11.0",
|
"url": "0.11.4",
|
||||||
"utf8": "3.0.0",
|
"utf8": "3.0.0",
|
||||||
"vue": "3.2.45",
|
"uuid": "11.1.0",
|
||||||
"vue-i18n": "9.2.2",
|
"vue": "3.5.22",
|
||||||
"vue-router": "4.1.6",
|
"vue-i18n": "11",
|
||||||
"vue-template-compiler": "2.7.14",
|
"vue-router": "4.6.4",
|
||||||
"vue-virtual-scroller": "^2.0.0-beta.7",
|
"vue-virtual-scroller": "^2.0.0-beta.7",
|
||||||
"vuex": "4.1.0"
|
"vuex": "4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.21.8",
|
"@babel/core": "7.28.5",
|
||||||
"@babel/eslint-parser": "7.21.8",
|
"@babel/eslint-parser": "7.28.5",
|
||||||
"@babel/plugin-transform-runtime": "7.21.4",
|
"@babel/plugin-transform-runtime": "7.28.5",
|
||||||
"@babel/preset-env": "7.21.5",
|
"@babel/preset-env": "7.28.5",
|
||||||
"@babel/register": "7.21.0",
|
"@babel/register": "7.28.3",
|
||||||
"@intlify/vue-i18n-loader": "5.0.1",
|
"@biomejs/biome": "2.3.11",
|
||||||
|
"@pinia/testing": "1.0.3",
|
||||||
"@ungap/event-target": "0.2.4",
|
"@ungap/event-target": "0.2.4",
|
||||||
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
|
"@vitejs/plugin-vue-jsx": "^4.1.1",
|
||||||
|
"@vitest/browser": "^3.0.7",
|
||||||
|
"@vitest/ui": "^3.0.7",
|
||||||
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
|
||||||
"@vue/babel-plugin-jsx": "1.2.2",
|
"@vue/babel-plugin-jsx": "1.5.0",
|
||||||
"@vue/compiler-sfc": "3.2.45",
|
"@vue/compiler-sfc": "3.5.22",
|
||||||
"@vue/test-utils": "2.2.8",
|
"@vue/test-utils": "2.4.6",
|
||||||
"autoprefixer": "10.4.19",
|
"autoprefixer": "10.4.21",
|
||||||
"babel-loader": "9.1.3",
|
|
||||||
"babel-plugin-lodash": "3.3.4",
|
"babel-plugin-lodash": "3.3.4",
|
||||||
"chai": "4.3.7",
|
"chai": "5.3.3",
|
||||||
"chalk": "1.1.3",
|
"chalk": "5.6.2",
|
||||||
"chromedriver": "108.0.0",
|
"chromedriver": "135.0.4",
|
||||||
"connect-history-api-fallback": "2.0.0",
|
"connect-history-api-fallback": "2.0.0",
|
||||||
"copy-webpack-plugin": "11.0.0",
|
"cross-spawn": "7.0.6",
|
||||||
"cross-spawn": "7.0.3",
|
|
||||||
"css-loader": "6.10.0",
|
|
||||||
"css-minimizer-webpack-plugin": "4.2.2",
|
|
||||||
"custom-event-polyfill": "1.0.7",
|
"custom-event-polyfill": "1.0.7",
|
||||||
"eslint": "8.33.0",
|
"eslint": "9.39.2",
|
||||||
"eslint-config-standard": "17.0.0",
|
"eslint-config-standard": "17.1.0",
|
||||||
"eslint-formatter-friendly": "7.0.0",
|
"eslint-formatter-friendly": "7.0.0",
|
||||||
"eslint-plugin-import": "2.27.5",
|
"eslint-plugin-import": "2.32.0",
|
||||||
"eslint-plugin-n": "15.6.1",
|
"eslint-plugin-n": "17.23.1",
|
||||||
"eslint-plugin-promise": "6.1.1",
|
"eslint-plugin-promise": "7.2.1",
|
||||||
"eslint-plugin-vue": "9.9.0",
|
"eslint-plugin-vue": "10.6.2",
|
||||||
"eslint-webpack-plugin": "3.2.0",
|
|
||||||
"eventsource-polyfill": "0.9.6",
|
"eventsource-polyfill": "0.9.6",
|
||||||
"express": "4.18.2",
|
"express": "5.1.0",
|
||||||
"function-bind": "1.1.1",
|
"function-bind": "1.1.2",
|
||||||
"html-webpack-plugin": "5.5.1",
|
"http-proxy-middleware": "3.0.5",
|
||||||
"http-proxy-middleware": "2.0.6",
|
"iso-639-1": "3.1.5",
|
||||||
"iso-639-1": "2.1.15",
|
|
||||||
"json-loader": "0.5.7",
|
|
||||||
"karma": "6.4.4",
|
|
||||||
"karma-coverage": "2.2.0",
|
|
||||||
"karma-firefox-launcher": "2.1.3",
|
|
||||||
"karma-mocha": "2.0.1",
|
|
||||||
"karma-mocha-reporter": "2.2.5",
|
|
||||||
"karma-sinon-chai": "2.0.2",
|
|
||||||
"karma-sourcemap-loader": "0.3.8",
|
|
||||||
"karma-spec-reporter": "0.0.36",
|
|
||||||
"karma-webpack": "5.0.0",
|
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"mini-css-extract-plugin": "2.7.6",
|
"msw": "2.10.5",
|
||||||
"mocha": "10.2.0",
|
"nightwatch": "3.12.2",
|
||||||
"nightwatch": "2.6.25",
|
"playwright": "1.57.0",
|
||||||
"opn": "5.5.0",
|
"postcss": "8.5.6",
|
||||||
"ora": "0.4.1",
|
|
||||||
"postcss": "8.4.23",
|
|
||||||
"postcss-html": "^1.5.0",
|
"postcss-html": "^1.5.0",
|
||||||
"postcss-loader": "7.0.2",
|
|
||||||
"postcss-scss": "^4.0.6",
|
"postcss-scss": "^4.0.6",
|
||||||
"sass": "1.60.0",
|
"sass": "1.93.2",
|
||||||
"sass-loader": "13.2.2",
|
"selenium-server": "3.141.59",
|
||||||
"selenium-server": "2.53.1",
|
"semver": "7.7.3",
|
||||||
"semver": "7.3.8",
|
"serve-static": "2.2.0",
|
||||||
"serviceworker-webpack5-plugin": "2.0.0",
|
"shelljs": "0.10.0",
|
||||||
"shelljs": "0.8.5",
|
"sinon": "20.0.0",
|
||||||
"sinon": "15.0.4",
|
"sinon-chai": "4.0.1",
|
||||||
"sinon-chai": "3.7.0",
|
"stylelint": "16.25.0",
|
||||||
"stylelint": "14.16.1",
|
|
||||||
"stylelint-config-html": "^1.1.0",
|
"stylelint-config-html": "^1.1.0",
|
||||||
"stylelint-config-recommended-scss": "^8.0.0",
|
"stylelint-config-recommended": "^16.0.0",
|
||||||
"stylelint-config-recommended-vue": "^1.4.0",
|
"stylelint-config-recommended-scss": "^14.0.0",
|
||||||
"stylelint-config-standard": "29.0.0",
|
"stylelint-config-recommended-vue": "^1.6.0",
|
||||||
"stylelint-rscss": "0.4.0",
|
"stylelint-config-standard": "38.0.0",
|
||||||
"stylelint-webpack-plugin": "^3.3.0",
|
"vite": "^6.1.0",
|
||||||
"vue-loader": "17.0.1",
|
"vite-plugin-eslint2": "^5.0.3",
|
||||||
"vue-style-loader": "4.1.3",
|
"vite-plugin-stylelint": "^6.0.0",
|
||||||
"webpack": "5.75.0",
|
"vitest": "^3.0.7",
|
||||||
"webpack-dev-middleware": "3.7.3",
|
"vue-eslint-parser": "10.2.0"
|
||||||
"webpack-hot-middleware": "2.25.3",
|
|
||||||
"webpack-merge": "0.20.0"
|
|
||||||
},
|
},
|
||||||
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 16.0.0",
|
"node": ">= 16.0.0"
|
||||||
"npm": ">= 3.0.0"
|
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
module.exports = {
|
import autoprefixer from 'autoprefixer'
|
||||||
plugins: [
|
|
||||||
require('autoprefixer')
|
export default {
|
||||||
]
|
plugins: [autoprefixer],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
public/static/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
*.custom.*
|
||||||
|
Before Width: | Height: | Size: 628 KiB After Width: | Height: | Size: 628 KiB |
|
|
@ -24,6 +24,8 @@
|
||||||
"showInstanceSpecificPanel": false,
|
"showInstanceSpecificPanel": false,
|
||||||
"sidebarRight": false,
|
"sidebarRight": false,
|
||||||
"subjectLineBehavior": "email",
|
"subjectLineBehavior": "email",
|
||||||
"theme": "pleroma-dark",
|
"theme": null,
|
||||||
|
"style": null,
|
||||||
|
"palette": null,
|
||||||
"webPushNotifications": false
|
"webPushNotifications": false
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
135
public/static/palettes/index.json
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
{
|
||||||
|
"pleroma-dark": [
|
||||||
|
"Pleroma Dark",
|
||||||
|
"#121a24",
|
||||||
|
"#182230",
|
||||||
|
"#b9b9ba",
|
||||||
|
"#d8a070",
|
||||||
|
"#d31014",
|
||||||
|
"#0fa00f",
|
||||||
|
"#0095ff",
|
||||||
|
"#ffa500"
|
||||||
|
],
|
||||||
|
"pleroma-light": [
|
||||||
|
"Pleroma Light",
|
||||||
|
"#f2f4f6",
|
||||||
|
"#dbe0e8",
|
||||||
|
"#304055",
|
||||||
|
"#f86f0f",
|
||||||
|
"#d31014",
|
||||||
|
"#0fa00f",
|
||||||
|
"#0095ff",
|
||||||
|
"#ffa500"
|
||||||
|
],
|
||||||
|
"classic-dark": {
|
||||||
|
"name": "Classic Dark",
|
||||||
|
"bg": "#161c20",
|
||||||
|
"fg": "#282e32",
|
||||||
|
"text": "#b9b9b9",
|
||||||
|
"link": "#baaa9c",
|
||||||
|
"cRed": "#d31014",
|
||||||
|
"cGreen": "#0fa00f",
|
||||||
|
"cBlue": "#0095ff",
|
||||||
|
"cOrange": "#ffa500"
|
||||||
|
},
|
||||||
|
"bird": [
|
||||||
|
"Bird",
|
||||||
|
"#f8fafd",
|
||||||
|
"#e6ecf0",
|
||||||
|
"#14171a",
|
||||||
|
"#0084b8",
|
||||||
|
"#e0245e",
|
||||||
|
"#17bf63",
|
||||||
|
"#1b95e0",
|
||||||
|
"#fab81e"
|
||||||
|
],
|
||||||
|
"pleroma-amoled": [
|
||||||
|
"Pleroma Dark AMOLED",
|
||||||
|
"#000000",
|
||||||
|
"#111111",
|
||||||
|
"#b0b0b1",
|
||||||
|
"#d8a070",
|
||||||
|
"#aa0000",
|
||||||
|
"#0fa00f",
|
||||||
|
"#0095ff",
|
||||||
|
"#d59500"
|
||||||
|
],
|
||||||
|
"tomorrow-night": {
|
||||||
|
"name": "Tomorrow Night",
|
||||||
|
"bg": "#1d1f21",
|
||||||
|
"fg": "#373b41",
|
||||||
|
"link": "#81a2be",
|
||||||
|
"text": "#c5c8c6",
|
||||||
|
"cRed": "#cc6666",
|
||||||
|
"cBlue": "#8abeb7",
|
||||||
|
"cGreen": "#b5bd68",
|
||||||
|
"cOrange": "#de935f"
|
||||||
|
},
|
||||||
|
"dracula": {
|
||||||
|
"name": "Dracula",
|
||||||
|
"bg": "#282A36",
|
||||||
|
"fg": "#44475A",
|
||||||
|
"link": "#BC92F9",
|
||||||
|
"text": "#f8f8f2",
|
||||||
|
"cRed": "#FF5555",
|
||||||
|
"cBlue": "#8BE9FD",
|
||||||
|
"cGreen": "#50FA7B",
|
||||||
|
"cOrange": "#FFB86C"
|
||||||
|
},
|
||||||
|
"ir-black": [
|
||||||
|
"Ir Black",
|
||||||
|
"#000000",
|
||||||
|
"#242422",
|
||||||
|
"#b5b3aa",
|
||||||
|
"#ff6c60",
|
||||||
|
"#FF6C60",
|
||||||
|
"#A8FF60",
|
||||||
|
"#96CBFE",
|
||||||
|
"#FFFFB6"
|
||||||
|
],
|
||||||
|
"monokai": [
|
||||||
|
"Monokai",
|
||||||
|
"#272822",
|
||||||
|
"#383830",
|
||||||
|
"#f8f8f2",
|
||||||
|
"#f92672",
|
||||||
|
"#F92672",
|
||||||
|
"#a6e22e",
|
||||||
|
"#66d9ef",
|
||||||
|
"#f4bf75"
|
||||||
|
],
|
||||||
|
"purple-stream": {
|
||||||
|
"name": "Purple stream",
|
||||||
|
"bg": "#17171A",
|
||||||
|
"fg": "#450F92",
|
||||||
|
"link": "#8769B4",
|
||||||
|
"text": "#C0C0C5",
|
||||||
|
"cRed": "#EB0300",
|
||||||
|
"cBlue": "#4656FF",
|
||||||
|
"cGreen": "#B0E020",
|
||||||
|
"cOrange": "#FF9046"
|
||||||
|
},
|
||||||
|
"feud": {
|
||||||
|
"name": "Feud",
|
||||||
|
"bg": "#323337",
|
||||||
|
"fg": "#1D1E21",
|
||||||
|
"link": "#18A0E3",
|
||||||
|
"accent": "#6671E2",
|
||||||
|
"text": "#DBDDE0",
|
||||||
|
"cRed": "#E05053",
|
||||||
|
"cBlue": "#6671E2",
|
||||||
|
"cGreen": "#3A8D5D",
|
||||||
|
"cOrange": "#DCAA45"
|
||||||
|
},
|
||||||
|
"constabulary": {
|
||||||
|
"name": "Constabulary",
|
||||||
|
"bg": "#FFFFFF",
|
||||||
|
"fg": "#3B5897",
|
||||||
|
"link": "#28487C",
|
||||||
|
"text": "#333333",
|
||||||
|
"cRed": "#FA3C4C",
|
||||||
|
"cBlue": "#0083FF",
|
||||||
|
"cGreen": "#44BDC6",
|
||||||
|
"cOrange": "#FFC200"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 396 KiB After Width: | Height: | Size: 396 KiB |
|
Before Width: | Height: | Size: 521 KiB After Width: | Height: | Size: 521 KiB |
BIN
public/static/pleromatan_apology_fox_small.webp
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/static/pleromatan_apology_small.webp
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.2 MiB After Width: | Height: | Size: 1.2 MiB |
133
public/static/splash.css
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash {
|
||||||
|
--scale: 1;
|
||||||
|
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
grid-template-columns: auto;
|
||||||
|
align-content: center;
|
||||||
|
place-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #0f161e;
|
||||||
|
font-family: sans-serif;
|
||||||
|
color: #b9b9ba;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9999;
|
||||||
|
font-size: calc(1vw + 1vh + 1vmin);
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 500ms ease-out 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash.hidden,
|
||||||
|
#splash.initial-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-credit {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 1em;
|
||||||
|
bottom: 1em;
|
||||||
|
right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#splash-container {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mascot-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: center;
|
||||||
|
perspective: 60em;
|
||||||
|
perspective-origin: 0 -15em;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mascot {
|
||||||
|
width: calc(10em * var(--scale));
|
||||||
|
height: calc(10em * var(--scale));
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: bottom;
|
||||||
|
transform: translateZ(-2em);
|
||||||
|
}
|
||||||
|
|
||||||
|
#throbber {
|
||||||
|
display: grid;
|
||||||
|
width: calc(5em * 0.5 * var(--scale));
|
||||||
|
height: calc(8em * 0.5 * var(--scale));
|
||||||
|
margin-left: 4.1em;
|
||||||
|
z-index: 2;
|
||||||
|
grid-template-rows: repeat(8, 1fr);
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
grid-template-areas:
|
||||||
|
"P P . L L"
|
||||||
|
"P P . L L"
|
||||||
|
"P P . L L"
|
||||||
|
"P P . L L"
|
||||||
|
"P P . . ."
|
||||||
|
"P P . . ."
|
||||||
|
"P P . E E"
|
||||||
|
"P P . E E";
|
||||||
|
|
||||||
|
--logoChunkSize: calc(2em * 0.5 * var(--scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
.chunk {
|
||||||
|
background-color: #e2b188;
|
||||||
|
box-shadow: 0.01em 0.01em 0.1em 0 #e2b188;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chunk-P {
|
||||||
|
grid-area: P;
|
||||||
|
border-top-left-radius: calc(var(--logoChunkSize) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#chunk-L {
|
||||||
|
grid-area: L;
|
||||||
|
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#chunk-E {
|
||||||
|
grid-area: E;
|
||||||
|
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
margin-top: 1em;
|
||||||
|
line-height: 2;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#statusError {
|
||||||
|
display: none;
|
||||||
|
margin-top: 1em;
|
||||||
|
font-size: calc(1vw + 1vh + 1vmin);
|
||||||
|
line-height: 2;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#statusStack {
|
||||||
|
display: none;
|
||||||
|
margin-top: 1em;
|
||||||
|
font-size: calc((1vw + 1vh + 1vmin) / 2.5);
|
||||||
|
width: calc(100vw - 5em);
|
||||||
|
padding: 1em;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow-x: hidden;
|
||||||
|
text-align: left;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
#throbber {
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
public/static/styles.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"pleroma-dark": "/static/themes/pleroma-dark.json",
|
||||||
|
"pleroma-light": "/static/themes/pleroma-light.json",
|
||||||
|
"redmond-xx": "/static/themes/redmond-xx.json",
|
||||||
|
"redmond-xx-se": "/static/themes/redmond-xx-se.json",
|
||||||
|
"redmond-xxi": "/static/themes/redmond-xxi.json",
|
||||||
|
"breezy-dark": "/static/themes/breezy-dark.json",
|
||||||
|
"breezy-light": "/static/themes/breezy-light.json",
|
||||||
|
"mammal": "/static/themes/mammal.json",
|
||||||
|
"paper": "/static/themes/paper.json"
|
||||||
|
}
|
||||||
102
public/static/styles/Breezy DX.iss
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
@meta {
|
||||||
|
name: Breezy DX;
|
||||||
|
author: HJ;
|
||||||
|
license: WTFPL;
|
||||||
|
website: ebin.club;
|
||||||
|
}
|
||||||
|
|
||||||
|
@palette.Dark {
|
||||||
|
bg: #292C32;
|
||||||
|
fg: #292C32;
|
||||||
|
text: #ffffff;
|
||||||
|
link: #1CA4F3;
|
||||||
|
accent: #1CA4F3;
|
||||||
|
cRed: #f41a51;
|
||||||
|
cBlue: #1CA4F3;
|
||||||
|
cGreen: #1af46e;
|
||||||
|
cOrange: #f4af1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
@palette.Light {
|
||||||
|
bg: #EFF0F2;
|
||||||
|
fg: #EFF0F2;
|
||||||
|
text: #1B1F22;
|
||||||
|
underlay: #5d6086;
|
||||||
|
accent: #1CA4F3;
|
||||||
|
cBlue: #1CA4F3;
|
||||||
|
cRed: #f41a51;
|
||||||
|
cGreen: #0b6a30;
|
||||||
|
cOrange: #f4af1a;
|
||||||
|
border: #d8e6f9;
|
||||||
|
link: #1CA4F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@palette.Panda {
|
||||||
|
bg: #EFF0F2;
|
||||||
|
fg: #292C32;
|
||||||
|
text: #1B1F22;
|
||||||
|
link: #1CA4F3;
|
||||||
|
accent: #1CA4F3;
|
||||||
|
cRed: #f41a51;
|
||||||
|
cBlue: #1CA4F3;
|
||||||
|
cGreen: #0b6a30;
|
||||||
|
cOrange: #f4af1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Root {
|
||||||
|
--badgeNotification: color | --cRed;
|
||||||
|
--buttonDefaultHoverGlow: shadow | inset 0 0 0 1 --accent / 1;
|
||||||
|
--buttonDefaultFocusGlow: shadow | inset 0 0 0 1 --accent / 1;
|
||||||
|
--buttonDefaultShadow: shadow | inset 0 0 0 1 --text / 0.35, 0 5 5 -5 #000000 / 0.35;
|
||||||
|
--buttonDefaultBevel: shadow | inset 0 14 14 -14 #FFFFFF / 0.1;
|
||||||
|
--buttonPressedBevel: shadow | inset 0 -20 20 -20 #000000 / 0.05;
|
||||||
|
--defaultInputBevel: shadow | inset 0 0 0 1 --text / 0.35;
|
||||||
|
--defaultInputHoverGlow: shadow | 0 0 0 1 --accent / 1;
|
||||||
|
--defaultInputFocusGlow: shadow | 0 0 0 1 --link / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
background: --parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:disabled {
|
||||||
|
shadow: --buttonDefaultBevel, --buttonDefaultShadow
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:hover {
|
||||||
|
background: --inheritedBackground;
|
||||||
|
shadow: --buttonDefaultHoverGlow, --buttonDefaultBevel, --buttonDefaultShadow
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:toggled {
|
||||||
|
background: $blend(--inheritedBackground 0.3 --accent)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:pressed {
|
||||||
|
background: $blend(--inheritedBackground 0.8 --accent)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:pressed:toggled {
|
||||||
|
background: $blend(--inheritedBackground 0.2 --accent)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:toggled:hover {
|
||||||
|
background: $blend(--inheritedBackground 0.3 --accent)
|
||||||
|
}
|
||||||
|
|
||||||
|
Input {
|
||||||
|
shadow: --defaultInputBevel;
|
||||||
|
background: $mod(--bg -10);
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelHeader {
|
||||||
|
shadow: inset 0 30 30 -30 #ffffff / 0.25
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab:hover {
|
||||||
|
shadow: --buttonDefaultHoverGlow, --buttonDefaultBevel, --buttonDefaultShadow
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab {
|
||||||
|
background: --bg;
|
||||||
|
}
|
||||||
201
public/static/styles/Redmond DX.iss
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
@meta {
|
||||||
|
name: Redmond DX;
|
||||||
|
author: HJ;
|
||||||
|
license: WTFPL;
|
||||||
|
website: ebin.club;
|
||||||
|
}
|
||||||
|
|
||||||
|
@palette.Modern {
|
||||||
|
bg: #D3CFC7;
|
||||||
|
fg: #092369;
|
||||||
|
text: #000000;
|
||||||
|
link: #0000FF;
|
||||||
|
accent: #A5C9F0;
|
||||||
|
cRed: #FF3000;
|
||||||
|
cBlue: #009EFF;
|
||||||
|
cGreen: #309E00;
|
||||||
|
cOrange: #FFCE00;
|
||||||
|
}
|
||||||
|
|
||||||
|
@palette.Classic {
|
||||||
|
bg: #BFBFBF;
|
||||||
|
fg: #000180;
|
||||||
|
text: #000000;
|
||||||
|
link: #0000FF;
|
||||||
|
accent: #A5C9F0;
|
||||||
|
cRed: #FF0000;
|
||||||
|
cBlue: #2E2ECE;
|
||||||
|
cGreen: #007E00;
|
||||||
|
cOrange: #CE8F5F;
|
||||||
|
}
|
||||||
|
|
||||||
|
@palette.Vapor {
|
||||||
|
bg: #F0ADCD;
|
||||||
|
fg: #bca4ee;
|
||||||
|
text: #602040;
|
||||||
|
link: #064745;
|
||||||
|
accent: #9DF7C8;
|
||||||
|
cRed: #86004a;
|
||||||
|
cBlue: #0e5663;
|
||||||
|
cGreen: #0a8b51;
|
||||||
|
cOrange: #787424;
|
||||||
|
}
|
||||||
|
|
||||||
|
Root {
|
||||||
|
--gradientColor: color | --accent;
|
||||||
|
--inputColor: color | #FFFFFF;
|
||||||
|
--bevelLight: color | $brightness(--bg 50);
|
||||||
|
--bevelDark: color | $brightness(--bg -20);
|
||||||
|
--bevelExtraDark: color | #404040;
|
||||||
|
--buttonDefaultBevel: shadow | $borderSide(--bevelExtraDark bottom-right 1 1), $borderSide(--bevelLight top-left 1 1), $borderSide(--bevelDark bottom-right 1 2);
|
||||||
|
--buttonPressedFocusedBevel: shadow | inset 0 0 0 1 #000000 / 1 #Outer , inset 0 0 0 2 --bevelExtraDark / 1 #inner;
|
||||||
|
--buttonPressedBevel: shadow | $borderSide(--bevelDark top-left 1 1), $borderSide(--bevelLight bottom-right 1 1), $borderSide(--bevelExtraDark top-left 1 2);
|
||||||
|
--defaultInputBevel: shadow | $borderSide(--bevelDark top-left 1 1), $borderSide(--bevelLight bottom-right 1 1), $borderSide(--bevelExtraDark top-left 1 2), $borderSide(--bg bottom-right 1 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:toggled {
|
||||||
|
background: --bg;
|
||||||
|
shadow: --buttonPressedBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:focused {
|
||||||
|
shadow: --buttonDefaultBevel, 0 0 0 1 #000000 / 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:pressed {
|
||||||
|
shadow: --buttonPressedBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:hover {
|
||||||
|
shadow: --buttonDefaultBevel;
|
||||||
|
background: --bg
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
shadow: --buttonDefaultBevel;
|
||||||
|
background: --bg;
|
||||||
|
roundness: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:pressed:hover {
|
||||||
|
shadow: --buttonPressedBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:hover:pressed:focused {
|
||||||
|
shadow: --buttonPressedFocusedBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:pressed:focused {
|
||||||
|
shadow: --buttonPressedFocusedBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Button:toggled:pressed {
|
||||||
|
shadow: --buttonPressedFocusedBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Input {
|
||||||
|
background: $boost(--bg 20);
|
||||||
|
shadow: --defaultInputBevel;
|
||||||
|
roundness: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Input:focused {
|
||||||
|
shadow: inset 0 0 0 1 #000000 / 1, --defaultInputBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Input:focused:hover {
|
||||||
|
shadow: --defaultInputBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Input:focused:hover:disabled {
|
||||||
|
shadow: --defaultInputBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Input:hover {
|
||||||
|
shadow: --defaultInputBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Input:disabled {
|
||||||
|
shadow: --defaultInputBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Panel {
|
||||||
|
shadow: --buttonDefaultBevel;
|
||||||
|
roundness: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelHeader {
|
||||||
|
shadow: inset -1100 0 1000 -1000 --gradientColor / 1 #Gradient ;
|
||||||
|
background: --fg
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelHeader ButtonUnstyled Icon {
|
||||||
|
textColor: --text;
|
||||||
|
textAuto: 'no-preserve'
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelHeader Button Icon {
|
||||||
|
textColor: --text;
|
||||||
|
textAuto: 'no-preserve'
|
||||||
|
}
|
||||||
|
|
||||||
|
PanelHeader Button Text {
|
||||||
|
textColor: --text;
|
||||||
|
textAuto: 'no-preserve'
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab:hover {
|
||||||
|
background: --bg;
|
||||||
|
shadow: --buttonDefaultBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab:active {
|
||||||
|
background: --bg
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab:active:hover {
|
||||||
|
background: --bg;
|
||||||
|
shadow: --defaultButtonBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab:active:hover:disabled {
|
||||||
|
background: --bg
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab:hover:disabled {
|
||||||
|
background: --bg
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab:disabled {
|
||||||
|
background: --bg
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab {
|
||||||
|
background: --bg;
|
||||||
|
shadow: --buttonDefaultBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
Tab:hover:active {
|
||||||
|
shadow: --buttonDefaultBevel
|
||||||
|
}
|
||||||
|
|
||||||
|
TopBar Link {
|
||||||
|
textColor: #ffffff
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem:hover {
|
||||||
|
background: --fg
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem:active {
|
||||||
|
background: --fg
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem:active:hover {
|
||||||
|
background: --fg
|
||||||
|
}
|
||||||
|
|
||||||
|
Popover {
|
||||||
|
shadow: --buttonDefaultBevel, 5 5 0 0 #000000 / 0.2;
|
||||||
|
roundness: 0
|
||||||
|
}
|
||||||
4
public/static/styles/index.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"RedmondDX": "/static/styles/Redmond DX.iss",
|
||||||
|
"BreezyDX": "/static/styles/Breezy DX.iss"
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": ["config:base"]
|
||||||
"config:base"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
279
src/App.js
|
|
@ -1,29 +1,45 @@
|
||||||
import UserPanel from './components/user_panel/user_panel.vue'
|
import { throttle } from 'lodash'
|
||||||
import NavPanel from './components/nav_panel/nav_panel.vue'
|
import { mapState } from 'pinia'
|
||||||
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
|
|
||||||
import FeaturesPanel from './components/features_panel/features_panel.vue'
|
|
||||||
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
|
|
||||||
import ShoutPanel from './components/shout_panel/shout_panel.vue'
|
|
||||||
import MediaModal from './components/media_modal/media_modal.vue'
|
|
||||||
import SideDrawer from './components/side_drawer/side_drawer.vue'
|
|
||||||
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
|
|
||||||
import MobileNav from './components/mobile_nav/mobile_nav.vue'
|
|
||||||
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
|
|
||||||
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
|
|
||||||
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
|
|
||||||
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
|
|
||||||
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
|
|
||||||
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
|
|
||||||
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
|
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
import { defineAsyncComponent } from 'vue'
|
import { defineAsyncComponent } from 'vue'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import messages from 'src/i18n/messages'
|
||||||
|
import localeService from 'src/services/locale/locale.service.js'
|
||||||
|
|
||||||
|
import DesktopNav from './components/desktop_nav/desktop_nav.vue'
|
||||||
|
import EditStatusModal from './components/edit_status_modal/edit_status_modal.vue'
|
||||||
|
import FeaturesPanel from './components/features_panel/features_panel.vue'
|
||||||
|
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
|
||||||
|
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
|
||||||
|
import MediaModal from './components/media_modal/media_modal.vue'
|
||||||
|
import MobileNav from './components/mobile_nav/mobile_nav.vue'
|
||||||
|
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue'
|
||||||
|
import NavPanel from './components/nav_panel/nav_panel.vue'
|
||||||
|
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
|
||||||
|
import ShoutPanel from './components/shout_panel/shout_panel.vue'
|
||||||
|
import SideDrawer from './components/side_drawer/side_drawer.vue'
|
||||||
|
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
|
||||||
|
import UserPanel from './components/user_panel/user_panel.vue'
|
||||||
|
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
|
||||||
|
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
|
||||||
|
import { getOrCreateServiceWorker } from './services/sw/sw'
|
||||||
|
import { windowHeight, windowWidth } from './services/window_utils/window_utils'
|
||||||
|
|
||||||
|
import { useI18nStore } from 'src/stores/i18n.js'
|
||||||
|
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||||
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
|
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||||
|
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||||
|
import { useShoutStore } from 'src/stores/shout.js'
|
||||||
|
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {
|
components: {
|
||||||
UserPanel,
|
UserPanel,
|
||||||
NavPanel,
|
NavPanel,
|
||||||
Notifications: defineAsyncComponent(() => import('./components/notifications/notifications.vue')),
|
Notifications: defineAsyncComponent(
|
||||||
|
() => import('./components/notifications/notifications.vue'),
|
||||||
|
),
|
||||||
InstanceSpecificPanel,
|
InstanceSpecificPanel,
|
||||||
FeaturesPanel,
|
FeaturesPanel,
|
||||||
WhoToFollowPanel,
|
WhoToFollowPanel,
|
||||||
|
|
@ -33,125 +49,228 @@ export default {
|
||||||
MobilePostStatusButton,
|
MobilePostStatusButton,
|
||||||
MobileNav,
|
MobileNav,
|
||||||
DesktopNav,
|
DesktopNav,
|
||||||
SettingsModal: defineAsyncComponent(() => import('./components/settings_modal/settings_modal.vue')),
|
SettingsModal: defineAsyncComponent(
|
||||||
UpdateNotification: defineAsyncComponent(() => import('./components/update_notification/update_notification.vue')),
|
() => import('./components/settings_modal/settings_modal.vue'),
|
||||||
|
),
|
||||||
|
UpdateNotification: defineAsyncComponent(
|
||||||
|
() => import('./components/update_notification/update_notification.vue'),
|
||||||
|
),
|
||||||
UserReportingModal,
|
UserReportingModal,
|
||||||
PostStatusModal,
|
PostStatusModal,
|
||||||
EditStatusModal,
|
EditStatusModal,
|
||||||
StatusHistoryModal,
|
StatusHistoryModal,
|
||||||
GlobalNoticeList
|
GlobalNoticeList,
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
mobileActivePanel: 'timeline'
|
mobileActivePanel: 'timeline',
|
||||||
}),
|
}),
|
||||||
watch: {
|
watch: {
|
||||||
themeApplied (value) {
|
themeApplied() {
|
||||||
this.removeSplash()
|
this.removeSplash()
|
||||||
}
|
},
|
||||||
|
currentTheme() {
|
||||||
|
this.setThemeBodyClass()
|
||||||
|
},
|
||||||
|
layoutType() {
|
||||||
|
document.getElementById('modal').classList = ['-' + this.layoutType]
|
||||||
|
},
|
||||||
},
|
},
|
||||||
created () {
|
created() {
|
||||||
// Load the locale from the storage
|
// Load the locale from the storage
|
||||||
const val = this.$store.getters.mergedConfig.interfaceLanguage
|
const value = useSyncConfigStore().mergedConfig.interfaceLanguage
|
||||||
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
|
useI18nStore().setLanguage(value)
|
||||||
window.addEventListener('resize', this.updateMobileState)
|
useEmojiStore().loadUnicodeEmojiData(value)
|
||||||
|
|
||||||
|
document.getElementById('modal').classList = ['-' + this.layoutType]
|
||||||
|
|
||||||
|
// Create bound handlers
|
||||||
|
this.updateScrollState = throttle(this.scrollHandler, 200)
|
||||||
|
this.updateMobileState = throttle(this.resizeHandler, 200)
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
if (this.$store.state.interface.themeApplied) {
|
window.addEventListener('resize', this.updateMobileState)
|
||||||
|
this.scrollParent.addEventListener('scroll', this.updateScrollState)
|
||||||
|
|
||||||
|
if (this.themeApplied) {
|
||||||
|
this.setThemeBodyClass()
|
||||||
this.removeSplash()
|
this.removeSplash()
|
||||||
}
|
}
|
||||||
|
getOrCreateServiceWorker()
|
||||||
},
|
},
|
||||||
unmounted () {
|
unmounted() {
|
||||||
window.removeEventListener('resize', this.updateMobileState)
|
window.removeEventListener('resize', this.updateMobileState)
|
||||||
|
this.scrollParent.removeEventListener('scroll', this.updateScrollState)
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
themeApplied () {
|
currentTheme() {
|
||||||
return this.$store.state.interface.themeApplied
|
if (this.styleDataUsed) {
|
||||||
|
const styleMeta = this.styleDataUsed.find(
|
||||||
|
(x) => x.component === '@meta',
|
||||||
|
)
|
||||||
|
|
||||||
|
if (styleMeta !== undefined) {
|
||||||
|
return styleMeta.directives.name.replaceAll(' ', '-').toLowerCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'stock'
|
||||||
},
|
},
|
||||||
classes () {
|
layoutModalClass() {
|
||||||
|
return '-' + this.layoutType
|
||||||
|
},
|
||||||
|
classes() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'-reverse': this.reverseLayout,
|
'-reverse': this.reverseLayout,
|
||||||
'-no-sticky-headers': this.noSticky,
|
'-no-sticky-headers': this.noSticky,
|
||||||
'-has-new-post-button': this.newPostButtonShown
|
'-has-new-post-button': this.newPostButtonShown,
|
||||||
},
|
},
|
||||||
'-' + this.layoutType
|
'-' + this.layoutType,
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
navClasses () {
|
navClasses() {
|
||||||
const { navbarColumnStretch } = this.$store.getters.mergedConfig
|
const { navbarColumnStretch } = useSyncConfigStore().mergedConfig
|
||||||
return [
|
return [
|
||||||
'-' + this.layoutType,
|
'-' + this.layoutType,
|
||||||
...(navbarColumnStretch ? ['-column-stretch'] : [])
|
...(navbarColumnStretch ? ['-column-stretch'] : []),
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
currentUser () { return this.$store.state.users.currentUser },
|
currentUser() {
|
||||||
userBackground () { return this.currentUser.background_image },
|
return this.$store.state.users.currentUser
|
||||||
instanceBackground () {
|
|
||||||
return this.mergedConfig.hideInstanceWallpaper
|
|
||||||
? null
|
|
||||||
: this.$store.state.instance.background
|
|
||||||
},
|
},
|
||||||
background () { return this.userBackground || this.instanceBackground },
|
userBackground() {
|
||||||
bgStyle () {
|
return this.currentUser.background_image
|
||||||
|
},
|
||||||
|
instanceBackground() {
|
||||||
|
return useSyncConfigStore().mergedConfig.hideInstanceWallpaper ? null : this.instanceBackgroundUrl
|
||||||
|
},
|
||||||
|
background() {
|
||||||
|
return this.userBackground || this.instanceBackground
|
||||||
|
},
|
||||||
|
bgStyle() {
|
||||||
if (this.background) {
|
if (this.background) {
|
||||||
return {
|
return {
|
||||||
'--body-background-image': `url(${this.background})`
|
'--body-background-image': `url(${this.background})`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
shout () { return this.$store.state.shout.joined },
|
shout() {
|
||||||
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
|
return useShoutStore().joined
|
||||||
showInstanceSpecificPanel () {
|
|
||||||
return this.$store.state.instance.showInstanceSpecificPanel &&
|
|
||||||
!this.$store.getters.mergedConfig.hideISP &&
|
|
||||||
this.$store.state.instance.instanceSpecificPanelContent
|
|
||||||
},
|
},
|
||||||
isChats () {
|
isChats() {
|
||||||
return this.$route.name === 'chat' || this.$route.name === 'chats'
|
return this.$route.name === 'chat' || this.$route.name === 'chats'
|
||||||
},
|
},
|
||||||
isListEdit () {
|
isListEdit() {
|
||||||
return this.$route.name === 'lists-edit'
|
return this.$route.name === 'lists-edit'
|
||||||
},
|
},
|
||||||
newPostButtonShown () {
|
newPostButtonShown() {
|
||||||
if (this.isChats) return false
|
if (this.isChats) return false
|
||||||
if (this.isListEdit) return false
|
if (this.isListEdit) return false
|
||||||
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
|
return (
|
||||||
|
useSyncConfigStore().mergedConfig.alwaysShowNewPostButton ||
|
||||||
|
this.layoutType === 'mobile'
|
||||||
|
)
|
||||||
},
|
},
|
||||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
shoutboxPosition() {
|
||||||
editingAvailable () { return this.$store.state.instance.editingAvailable },
|
return useSyncConfigStore().mergedConfig.alwaysShowNewPostButton || false
|
||||||
shoutboxPosition () {
|
|
||||||
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || false
|
|
||||||
},
|
},
|
||||||
hideShoutbox () {
|
hideShoutbox() {
|
||||||
return this.$store.getters.mergedConfig.hideShoutbox
|
return useSyncConfigStore().mergedConfig.hideShoutbox
|
||||||
},
|
},
|
||||||
layoutType () { return this.$store.state.interface.layoutType },
|
reverseLayout() {
|
||||||
privateMode () { return this.$store.state.instance.private },
|
const { thirdColumnMode, sidebarRight: reverseSetting } =
|
||||||
reverseLayout () {
|
useSyncConfigStore().mergedConfig
|
||||||
const { thirdColumnMode, sidebarRight: reverseSetting } = this.$store.getters.mergedConfig
|
|
||||||
if (this.layoutType !== 'wide') {
|
if (this.layoutType !== 'wide') {
|
||||||
return reverseSetting
|
return reverseSetting
|
||||||
} else {
|
} else {
|
||||||
return thirdColumnMode === 'notifications' ? reverseSetting : !reverseSetting
|
return thirdColumnMode === 'notifications'
|
||||||
|
? reverseSetting
|
||||||
|
: !reverseSetting
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
|
noSticky() {
|
||||||
showScrollbars () { return this.$store.getters.mergedConfig.showScrollbars },
|
return useSyncConfigStore().mergedConfig.disableStickyHeaders
|
||||||
...mapGetters(['mergedConfig'])
|
},
|
||||||
|
showScrollbars() {
|
||||||
|
return useSyncConfigStore().mergedConfig.showScrollbars
|
||||||
|
},
|
||||||
|
scrollParent() {
|
||||||
|
return window /* this.$refs.appContentRef */
|
||||||
|
},
|
||||||
|
showInstanceSpecificPanel() {
|
||||||
|
return (
|
||||||
|
this.instanceSpecificPanelPresent &&
|
||||||
|
!useSyncConfigStore().mergedConfig.hideISP
|
||||||
|
)
|
||||||
|
},
|
||||||
|
...mapGetters(['mergedConfig']),
|
||||||
|
...mapState(useInterfaceStore, [
|
||||||
|
'themeApplied',
|
||||||
|
'styleDataUsed',
|
||||||
|
'layoutType',
|
||||||
|
]),
|
||||||
|
...mapState(useInstanceStore, ['styleDataUsed']),
|
||||||
|
...mapState(useInstanceCapabilitiesStore, [
|
||||||
|
'suggestionsEnabled',
|
||||||
|
'editingAvailable',
|
||||||
|
]),
|
||||||
|
...mapState(useInstanceStore, {
|
||||||
|
instanceBackgroundUrl: (store) => store.instanceIdentity.background,
|
||||||
|
showFeaturesPanel: (store) => store.instanceIdentity.showFeaturesPanel,
|
||||||
|
instanceSpecificPanelPresent: (store) =>
|
||||||
|
store.instanceIdentity.showInstanceSpecificPanel &&
|
||||||
|
store.instanceIdentity.instanceSpecificPanelContent,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateMobileState () {
|
resizeHandler() {
|
||||||
this.$store.dispatch('setLayoutWidth', windowWidth())
|
useInterfaceStore().setLayoutWidth(windowWidth())
|
||||||
this.$store.dispatch('setLayoutHeight', windowHeight())
|
useInterfaceStore().setLayoutHeight(windowHeight())
|
||||||
},
|
},
|
||||||
removeSplash () {
|
scrollHandler() {
|
||||||
document.querySelector('#status').textContent = this.$t('splash.fun_' + Math.ceil(Math.random() * 4))
|
const scrollPosition =
|
||||||
|
this.scrollParent === window
|
||||||
|
? window.scrollY
|
||||||
|
: this.scrollParent.scrollTop
|
||||||
|
|
||||||
|
if (scrollPosition != 0) {
|
||||||
|
this.$refs.appContentRef.classList.add(['-scrolled'])
|
||||||
|
} else {
|
||||||
|
this.$refs.appContentRef.classList.remove(['-scrolled'])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setThemeBodyClass() {
|
||||||
|
const themeName = this.currentTheme
|
||||||
|
const classList = Array.from(document.body.classList)
|
||||||
|
const oldTheme = classList.filter((c) => c.startsWith('theme-'))
|
||||||
|
|
||||||
|
if (themeName !== null && themeName !== '') {
|
||||||
|
const newTheme = `theme-${themeName.toLowerCase()}`
|
||||||
|
|
||||||
|
// remove old theme reference if there are any
|
||||||
|
if (oldTheme.length) {
|
||||||
|
document.body.classList.replace(oldTheme[0], newTheme)
|
||||||
|
} else {
|
||||||
|
document.body.classList.add(newTheme)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// remove theme reference if non-V3 theme is used
|
||||||
|
document.body.classList.remove(...oldTheme)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeSplash() {
|
||||||
|
document.querySelector('#status').textContent = this.$t(
|
||||||
|
'splash.fun_' + Math.ceil(Math.random() * 4),
|
||||||
|
)
|
||||||
const splashscreenRoot = document.querySelector('#splash')
|
const splashscreenRoot = document.querySelector('#splash')
|
||||||
splashscreenRoot.addEventListener('transitionend', () => {
|
splashscreenRoot.addEventListener('transitionend', () => {
|
||||||
splashscreenRoot.remove()
|
splashscreenRoot.remove()
|
||||||
})
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
splashscreenRoot.remove() // forcibly remove it, should fix my plasma browser widget t. HJ
|
||||||
|
}, 600)
|
||||||
splashscreenRoot.classList.add('hidden')
|
splashscreenRoot.classList.add('hidden')
|
||||||
document.querySelector('#app').classList.remove('hidden')
|
document.querySelector('#app').classList.remove('hidden')
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
199
src/App.scss
|
|
@ -1,6 +1,9 @@
|
||||||
// stylelint-disable rscss/class-format
|
// stylelint-disable rscss/class-format
|
||||||
/* stylelint-disable no-descending-specificity */
|
/* stylelint-disable no-descending-specificity */
|
||||||
@import "./panel";
|
@use "panel";
|
||||||
|
|
||||||
|
@import '@fortawesome/fontawesome-svg-core/styles.css';
|
||||||
|
@import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--status-margin: 0.75em;
|
--status-margin: 0.75em;
|
||||||
|
|
@ -18,7 +21,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-size: var(--textSize, 14px);
|
font-size: var(--textSize, 1rem);
|
||||||
|
|
||||||
--navbar-height: var(--navbarSize, 3.5rem);
|
--navbar-height: var(--navbarSize, 3.5rem);
|
||||||
--emoji-size: var(--emojiSize, 32px);
|
--emoji-size: var(--emojiSize, 32px);
|
||||||
|
|
@ -30,12 +33,12 @@ body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-family: var(--font);
|
font-family: var(--font);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
overscroll-behavior-y: none;
|
overscroll-behavior-y: none;
|
||||||
overflow-x: clip;
|
overflow: clip scroll;
|
||||||
overflow-y: scroll;
|
|
||||||
|
|
||||||
&.hidden {
|
&.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
@ -224,9 +227,8 @@ nav {
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
align-content: flex-start;
|
place-content: flex-start center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow-x: clip;
|
overflow-x: clip;
|
||||||
|
|
||||||
|
|
@ -262,8 +264,7 @@ nav {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: var(--navbar-height);
|
top: var(--navbar-height);
|
||||||
max-height: calc(100vh - var(--navbar-height));
|
max-height: calc(100vh - var(--navbar-height));
|
||||||
overflow-y: auto;
|
overflow: hidden auto;
|
||||||
overflow-x: hidden;
|
|
||||||
margin-left: calc(var(--___paddingIncrease) * -1);
|
margin-left: calc(var(--___paddingIncrease) * -1);
|
||||||
padding-left: calc(var(--___paddingIncrease) + var(--___columnMargin) / 2);
|
padding-left: calc(var(--___paddingIncrease) + var(--___columnMargin) / 2);
|
||||||
|
|
||||||
|
|
@ -381,6 +382,10 @@ nav {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-family: var(--font);
|
font-family: var(--font);
|
||||||
|
|
||||||
|
&.-transparent {
|
||||||
|
backdrop-filter: blur(0.125em) contrast(60%);
|
||||||
|
}
|
||||||
|
|
||||||
&::-moz-focus-inner {
|
&::-moz-focus-inner {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
@ -388,39 +393,35 @@ nav {
|
||||||
&:disabled {
|
&:disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translate(1px, 1px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item,
|
.menu-item {
|
||||||
.list-item {
|
line-height: var(--__line-height);
|
||||||
display: block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
text-align: initial;
|
|
||||||
font-size: inherit;
|
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
font-size: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: inherit;
|
|
||||||
clear: both;
|
a,
|
||||||
position: relative;
|
button:not(.button-default) {
|
||||||
white-space: nowrap;
|
color: var(--text);
|
||||||
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
border-color: var(--border);
|
border-color: var(--border);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0;
|
border-width: 0;
|
||||||
border-top-width: 1px;
|
border-top-width: 1px;
|
||||||
width: 100%;
|
|
||||||
line-height: var(--__line-height);
|
|
||||||
padding: var(--__vertical-gap) var(--__horizontal-gap);
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
--__line-height: 1.5em;
|
|
||||||
--__horizontal-gap: 0.75em;
|
|
||||||
--__vertical-gap: 0.5em;
|
|
||||||
|
|
||||||
&.-non-interactive {
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.-active,
|
&.-active,
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
@ -442,20 +443,6 @@ nav {
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a,
|
|
||||||
button:not(.button-default) {
|
|
||||||
text-align: initial;
|
|
||||||
padding: 0;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
display: inline;
|
|
||||||
font-size: 100%;
|
|
||||||
font-family: inherit;
|
|
||||||
line-height: unset;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
border-top-right-radius: var(--roundness);
|
border-top-right-radius: var(--roundness);
|
||||||
border-top-left-radius: var(--roundness);
|
border-top-left-radius: var(--roundness);
|
||||||
|
|
@ -469,6 +456,42 @@ nav {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-item,
|
||||||
|
.list-item {
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
text-align: initial;
|
||||||
|
color: inherit;
|
||||||
|
clear: both;
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--__vertical-gap) var(--__horizontal-gap);
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
--__line-height: 1.5em;
|
||||||
|
--__horizontal-gap: 0.75em;
|
||||||
|
--__vertical-gap: 0.5em;
|
||||||
|
|
||||||
|
&.-non-interactive {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
button:not(.button-default) {
|
||||||
|
text-align: initial;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
display: inline;
|
||||||
|
font-family: inherit;
|
||||||
|
line-height: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.button-unstyled {
|
.button-unstyled {
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
@ -490,6 +513,12 @@ nav {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
&.-disabled {
|
||||||
|
color: var(--textFaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
input,
|
input,
|
||||||
textarea {
|
textarea {
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -506,6 +535,10 @@ textarea {
|
||||||
height: unset;
|
height: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: var(--textFaint)
|
||||||
|
}
|
||||||
|
|
||||||
--_padding: 0.5em;
|
--_padding: 0.5em;
|
||||||
|
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -526,6 +559,10 @@ textarea {
|
||||||
&[disabled="disabled"],
|
&[disabled="disabled"],
|
||||||
&.disabled {
|
&.disabled {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
color: var(--textFaint);
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line declaration-no-important */
|
||||||
|
background-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[type="range"] {
|
&[type="range"] {
|
||||||
|
|
@ -551,6 +588,8 @@ textarea {
|
||||||
& + label::before {
|
& + label::before {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
background-color: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
+ label::before {
|
+ label::before {
|
||||||
|
|
@ -650,7 +689,8 @@ option {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-auto-flow: row dense;
|
grid-auto-flow: row dense;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: repeat(auto-fit, minmax(20em, 1fr));
|
||||||
|
grid-gap: 0.5em;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|
@ -660,11 +700,6 @@ option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-block {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-group {
|
.btn-group {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
@ -676,7 +711,6 @@ option {
|
||||||
--_roundness-right: 0;
|
--_roundness-right: 0;
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> *:first-child,
|
> *:first-child,
|
||||||
|
|
@ -723,17 +757,15 @@ option {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-dot {
|
&.-dot {
|
||||||
min-height: 8px;
|
min-height: 0.6em;
|
||||||
max-height: 8px;
|
max-height: 0.6em;
|
||||||
min-width: 8px;
|
min-width: 0.6em;
|
||||||
max-width: 8px;
|
max-width: 0.6em;
|
||||||
padding: 0;
|
left: calc(50% + 0.5em);
|
||||||
|
top: calc(50% - 1em);
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
font-size: 0;
|
padding: 0;
|
||||||
left: calc(50% - 4px);
|
margin: 0;
|
||||||
top: calc(50% - 4px);
|
|
||||||
margin-left: 6px;
|
|
||||||
margin-top: -6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.-counter {
|
&.-counter {
|
||||||
|
|
@ -764,21 +796,18 @@ option {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.visibility-notice {
|
|
||||||
padding: 0.5em;
|
|
||||||
border: 1px solid var(--textFaint);
|
|
||||||
border-radius: var(--roundness);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notice-dismissible {
|
.notice-dismissible {
|
||||||
padding-right: 4rem;
|
display: flex;
|
||||||
position: relative;
|
padding: 0.75em 1em;
|
||||||
|
align-items: baseline;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: block;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.dismiss {
|
.dismiss {
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 0.5em;
|
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -814,7 +843,7 @@ option {
|
||||||
.login-hint {
|
.login-hint {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
@media all and (min-width: 801px) {
|
@media all and (width >= 801px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -836,7 +865,7 @@ option {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: 800px) {
|
@media all and (width <= 800px) {
|
||||||
.mobile-hidden {
|
.mobile-hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
@ -917,12 +946,7 @@ option {
|
||||||
|
|
||||||
#splash {
|
#splash {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 2s;
|
// transition: opacity 0.5s;
|
||||||
opacity: 1;
|
|
||||||
|
|
||||||
&.hidden {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#status {
|
#status {
|
||||||
&.css-ok {
|
&.css-ok {
|
||||||
|
|
@ -946,7 +970,7 @@ option {
|
||||||
|
|
||||||
&.dead {
|
&.dead {
|
||||||
animation-name: dead;
|
animation-name: dead;
|
||||||
animation-duration: 2s;
|
animation-duration: 0.5s;
|
||||||
animation-iteration-count: 1;
|
animation-iteration-count: 1;
|
||||||
transform: rotateX(90deg) rotateY(0) rotateZ(-45deg);
|
transform: rotateX(90deg) rotateY(0) rotateZ(-45deg);
|
||||||
}
|
}
|
||||||
|
|
@ -1061,7 +1085,7 @@ option {
|
||||||
scale: 1.0063 0.9938;
|
scale: 1.0063 0.9938;
|
||||||
translate: 0 -10%;
|
translate: 0 -10%;
|
||||||
transform: rotateZ(var(--defaultZ));
|
transform: rotateZ(var(--defaultZ));
|
||||||
animation-timing-function: ease-in-ou;
|
animation-timing-function: ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
90% {
|
90% {
|
||||||
|
|
@ -1080,3 +1104,8 @@ option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property --shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-show="$store.state.interface.themeApplied"
|
v-show="themeApplied"
|
||||||
id="app-loaded"
|
id="app-loaded"
|
||||||
:style="bgStyle"
|
:style="bgStyle"
|
||||||
>
|
>
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<Notifications v-if="currentUser" />
|
<Notifications v-if="currentUser" />
|
||||||
<div
|
<div
|
||||||
id="content"
|
id="content"
|
||||||
|
ref="appContentRef"
|
||||||
class="app-layout container"
|
class="app-layout container"
|
||||||
:class="classes"
|
:class="classes"
|
||||||
>
|
>
|
||||||
|
|
@ -70,7 +71,7 @@
|
||||||
<PostStatusModal />
|
<PostStatusModal />
|
||||||
<EditStatusModal v-if="editingAvailable" />
|
<EditStatusModal v-if="editingAvailable" />
|
||||||
<StatusHistoryModal v-if="editingAvailable" />
|
<StatusHistoryModal v-if="editingAvailable" />
|
||||||
<SettingsModal />
|
<SettingsModal :class="layoutModalClass" />
|
||||||
<UpdateNotification />
|
<UpdateNotification />
|
||||||
<GlobalNoticeList />
|
<GlobalNoticeList />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
../../static/pleromatan_apology.png
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
../../static/pleromatan_apology_fox.png
|
|
||||||
|
|
@ -1,21 +1,45 @@
|
||||||
|
/* global process */
|
||||||
|
|
||||||
|
import vClickOutside from 'click-outside-vue3'
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import vClickOutside from 'click-outside-vue3'
|
|
||||||
import VueVirtualScroller from 'vue-virtual-scroller'
|
import VueVirtualScroller from 'vue-virtual-scroller'
|
||||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||||
|
|
||||||
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
|
import { config } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import {
|
||||||
|
FontAwesomeIcon,
|
||||||
|
FontAwesomeLayers,
|
||||||
|
} from '@fortawesome/vue-fontawesome'
|
||||||
|
|
||||||
|
config.autoAddCss = false
|
||||||
|
|
||||||
import App from '../App.vue'
|
import App from '../App.vue'
|
||||||
import routes from './routes'
|
|
||||||
import VBodyScrollLock from 'src/directives/body_scroll_lock'
|
|
||||||
|
|
||||||
import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
|
|
||||||
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
|
|
||||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import { applyConfig } from '../services/style_setter/style_setter.js'
|
|
||||||
import FaviconService from '../services/favicon_service/favicon_service.js'
|
import FaviconService from '../services/favicon_service/favicon_service.js'
|
||||||
|
import { applyStyleConfig } from '../services/style_setter/style_setter.js'
|
||||||
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
|
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
|
||||||
|
import {
|
||||||
|
windowHeight,
|
||||||
|
windowWidth,
|
||||||
|
} from '../services/window_utils/window_utils'
|
||||||
|
import routes from './routes'
|
||||||
|
|
||||||
|
import { useAnnouncementsStore } from 'src/stores/announcements'
|
||||||
|
import { useAuthFlowStore } from 'src/stores/auth_flow'
|
||||||
|
import { useEmojiStore } from 'src/stores/emoji.js'
|
||||||
|
import { useI18nStore } from 'src/stores/i18n'
|
||||||
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
|
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||||
|
import { useInterfaceStore } from 'src/stores/interface.js'
|
||||||
|
import { useOAuthStore } from 'src/stores/oauth'
|
||||||
|
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||||
|
|
||||||
|
import VBodyScrollLock from 'src/directives/body_scroll_lock'
|
||||||
|
import {
|
||||||
|
instanceDefaultConfig,
|
||||||
|
staticOrApiConfigDefault,
|
||||||
|
} from 'src/modules/default_config_state.js'
|
||||||
|
|
||||||
let staticInitialResults = null
|
let staticInitialResults = null
|
||||||
|
|
||||||
|
|
@ -24,7 +48,9 @@ const parsedInitialResults = () => {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (!staticInitialResults) {
|
if (!staticInitialResults) {
|
||||||
staticInitialResults = JSON.parse(document.getElementById('initial-results').textContent)
|
staticInitialResults = JSON.parse(
|
||||||
|
document.getElementById('initial-results').textContent,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return staticInitialResults
|
return staticInitialResults
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +72,7 @@ const preloadFetch = async (request) => {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
json: () => requestData,
|
json: () => requestData,
|
||||||
text: () => requestData
|
text: () => requestData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,34 +84,57 @@ const getInstanceConfig = async ({ store }) => {
|
||||||
const textlimit = data.max_toot_chars
|
const textlimit = data.max_toot_chars
|
||||||
const vapidPublicKey = data.pleroma.vapid_public_key
|
const vapidPublicKey = data.pleroma.vapid_public_key
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
|
useInstanceCapabilitiesStore().set(
|
||||||
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
|
'pleromaExtensionsAvailable',
|
||||||
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required })
|
data.pleroma,
|
||||||
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 })
|
)
|
||||||
|
useInstanceStore().set({
|
||||||
|
path: 'textlimit',
|
||||||
|
value: textlimit,
|
||||||
|
})
|
||||||
|
useInstanceStore().set({
|
||||||
|
path: 'accountApprovalRequired',
|
||||||
|
value: data.approval_required,
|
||||||
|
})
|
||||||
|
useInstanceStore().set({
|
||||||
|
path: 'birthdayRequired',
|
||||||
|
value: !!data.pleroma?.metadata.birthday_required,
|
||||||
|
})
|
||||||
|
useInstanceStore().set({
|
||||||
|
path: 'birthdayMinAge',
|
||||||
|
value: data.pleroma?.metadata.birthday_min_age || 0,
|
||||||
|
})
|
||||||
|
|
||||||
if (vapidPublicKey) {
|
if (vapidPublicKey) {
|
||||||
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
|
useInstanceStore().set({
|
||||||
|
path: 'vapidPublicKey',
|
||||||
|
value: vapidPublicKey,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw (res)
|
throw res
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Could not load instance config, potentially fatal')
|
console.error('Could not load instance config, potentially fatal')
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
|
// We should check for scrobbles support here but it requires userId
|
||||||
|
// so instead we check for it where it's fetched (statuses.js)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBackendProvidedConfig = async ({ store }) => {
|
const getBackendProvidedConfig = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await window.fetch('/api/pleroma/frontend_configurations')
|
const res = await window.fetch('/api/pleroma/frontend_configurations')
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
return data.pleroma_fe
|
return data.pleroma_fe
|
||||||
} else {
|
} else {
|
||||||
throw (res)
|
throw res
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Could not load backend-provided frontend config, potentially fatal')
|
console.error(
|
||||||
|
'Could not load backend-provided frontend config, potentially fatal',
|
||||||
|
)
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +145,7 @@ const getStaticConfig = async () => {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
return res.json()
|
return res.json()
|
||||||
} else {
|
} else {
|
||||||
throw (res)
|
throw res
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to load static/config.json, continuing without it.')
|
console.warn('Failed to load static/config.json, continuing without it.')
|
||||||
|
|
@ -118,48 +167,20 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
||||||
config = Object.assign({}, staticConfig, apiConfig)
|
config = Object.assign({}, staticConfig, apiConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyInstanceOption = (name) => {
|
const copyInstanceOption = ({ source, destination }) => {
|
||||||
store.dispatch('setInstanceOption', { name, value: config[name] })
|
if (typeof config[source] !== 'undefined') {
|
||||||
|
useInstanceStore().set({ path: destination, value: config[source] })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
copyInstanceOption('theme')
|
Object.keys(staticOrApiConfigDefault)
|
||||||
copyInstanceOption('nsfwCensorImage')
|
.map((k) => ({ source: k, destination: `instanceIdentity.${k}` }))
|
||||||
copyInstanceOption('background')
|
.forEach(copyInstanceOption)
|
||||||
copyInstanceOption('hidePostStats')
|
Object.keys(instanceDefaultConfig)
|
||||||
copyInstanceOption('hideBotIndication')
|
.map((k) => ({ source: k, destination: `prefsStorage.${k}` }))
|
||||||
copyInstanceOption('hideUserStats')
|
.forEach(copyInstanceOption)
|
||||||
copyInstanceOption('hideFilteredStatuses')
|
|
||||||
copyInstanceOption('logo')
|
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', {
|
useAuthFlowStore().setInitialStrategy(config.loginMethod)
|
||||||
name: 'logoMask',
|
|
||||||
value: typeof config.logoMask === 'undefined'
|
|
||||||
? true
|
|
||||||
: config.logoMask
|
|
||||||
})
|
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', {
|
|
||||||
name: 'logoMargin',
|
|
||||||
value: typeof config.logoMargin === 'undefined'
|
|
||||||
? 0
|
|
||||||
: config.logoMargin
|
|
||||||
})
|
|
||||||
copyInstanceOption('logoLeft')
|
|
||||||
store.commit('authFlow/setInitialStrategy', config.loginMethod)
|
|
||||||
|
|
||||||
copyInstanceOption('redirectRootNoLogin')
|
|
||||||
copyInstanceOption('redirectRootLogin')
|
|
||||||
copyInstanceOption('showInstanceSpecificPanel')
|
|
||||||
copyInstanceOption('minimalScopesMode')
|
|
||||||
copyInstanceOption('hideMutedPosts')
|
|
||||||
copyInstanceOption('collapseMessageWithSubject')
|
|
||||||
copyInstanceOption('scopeCopy')
|
|
||||||
copyInstanceOption('subjectLineBehavior')
|
|
||||||
copyInstanceOption('postContentType')
|
|
||||||
copyInstanceOption('alwaysShowSubjectInput')
|
|
||||||
copyInstanceOption('showFeaturesPanel')
|
|
||||||
copyInstanceOption('hideSitename')
|
|
||||||
copyInstanceOption('sidebarRight')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTOS = async ({ store }) => {
|
const getTOS = async ({ store }) => {
|
||||||
|
|
@ -167,13 +188,12 @@ const getTOS = async ({ store }) => {
|
||||||
const res = await window.fetch('/static/terms-of-service.html')
|
const res = await window.fetch('/static/terms-of-service.html')
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const html = await res.text()
|
const html = await res.text()
|
||||||
store.dispatch('setInstanceOption', { name: 'tos', value: html })
|
useInstanceStore().set({ name: 'instanceIdentity.tos', value: html })
|
||||||
} else {
|
} else {
|
||||||
throw (res)
|
throw res
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Can't load TOS")
|
console.warn("Can't load TOS\n", e)
|
||||||
console.warn(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -182,13 +202,15 @@ const getInstancePanel = async ({ store }) => {
|
||||||
const res = await preloadFetch('/instance/panel.html')
|
const res = await preloadFetch('/instance/panel.html')
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const html = await res.text()
|
const html = await res.text()
|
||||||
store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
|
useInstanceStore().set({
|
||||||
|
path: 'instanceIdentity.instanceSpecificPanelContent',
|
||||||
|
value: html,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
throw (res)
|
throw res
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Can't load instance panel")
|
console.warn("Can't load instance panel\n", e)
|
||||||
console.warn(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,119 +219,218 @@ const getStickers = async ({ store }) => {
|
||||||
const res = await window.fetch('/static/stickers.json')
|
const res = await window.fetch('/static/stickers.json')
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const values = await res.json()
|
const values = await res.json()
|
||||||
const stickers = (await Promise.all(
|
const stickers = (
|
||||||
Object.entries(values).map(async ([name, path]) => {
|
await Promise.all(
|
||||||
const resPack = await window.fetch(path + 'pack.json')
|
Object.entries(values).map(async ([name, path]) => {
|
||||||
let meta = {}
|
const resPack = await window.fetch(path + 'pack.json')
|
||||||
if (resPack.ok) {
|
let meta = {}
|
||||||
meta = await resPack.json()
|
if (resPack.ok) {
|
||||||
}
|
meta = await resPack.json()
|
||||||
return {
|
}
|
||||||
pack: name,
|
return {
|
||||||
path,
|
pack: name,
|
||||||
meta
|
path,
|
||||||
}
|
meta,
|
||||||
})
|
}
|
||||||
)).sort((a, b) => {
|
}),
|
||||||
|
)
|
||||||
|
).sort((a, b) => {
|
||||||
return a.meta.title.localeCompare(b.meta.title)
|
return a.meta.title.localeCompare(b.meta.title)
|
||||||
})
|
})
|
||||||
store.dispatch('setInstanceOption', { name: 'stickers', value: stickers })
|
useEmojiStore().setStickers(stickers)
|
||||||
} else {
|
} else {
|
||||||
throw (res)
|
throw res
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Can't load stickers")
|
console.warn("Can't load stickers\n", e)
|
||||||
console.warn(e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAppSecret = async ({ store }) => {
|
const getAppSecret = async ({ store }) => {
|
||||||
const { state, commit } = store
|
const oauth = useOAuthStore()
|
||||||
const { oauth, instance } = state
|
if (oauth.userToken) {
|
||||||
return getOrCreateApp({ ...oauth, instance: instance.server, commit })
|
store.commit(
|
||||||
.then((app) => getClientToken({ ...app, instance: instance.server }))
|
'setBackendInteractor',
|
||||||
.then((token) => {
|
backendInteractorService(oauth.getToken),
|
||||||
commit('setAppToken', token.access_token)
|
)
|
||||||
commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolveStaffAccounts = ({ store, accounts }) => {
|
const resolveStaffAccounts = ({ store, accounts }) => {
|
||||||
const nicknames = accounts.map(uri => uri.split('/').pop())
|
const nicknames = accounts.map((uri) => uri.split('/').pop())
|
||||||
store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
|
useInstanceStore().set({
|
||||||
|
path: 'staffAccounts',
|
||||||
|
value: nicknames,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNodeInfo = async ({ store }) => {
|
const getNodeInfo = async ({ store }) => {
|
||||||
try {
|
try {
|
||||||
const res = await preloadFetch('/nodeinfo/2.0.json')
|
let res = await preloadFetch('/nodeinfo/2.1.json')
|
||||||
|
if (!res.ok) res = await preloadFetch('/nodeinfo/2.0.json')
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
const metadata = data.metadata
|
const metadata = data.metadata
|
||||||
const features = metadata.features
|
const features = metadata.features
|
||||||
store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName })
|
useInstanceStore().set({
|
||||||
store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
|
path: 'name',
|
||||||
store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
|
value: metadata.nodeName,
|
||||||
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
|
})
|
||||||
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
|
useInstanceStore().set({
|
||||||
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
|
path: 'registrationOpen',
|
||||||
store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') })
|
value: data.openRegistrations,
|
||||||
store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') })
|
})
|
||||||
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
useInstanceCapabilitiesStore().set(
|
||||||
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
'mediaProxyAvailable',
|
||||||
store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
|
features.includes('media_proxy'),
|
||||||
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
|
)
|
||||||
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
|
useInstanceCapabilitiesStore().set(
|
||||||
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
|
'safeDM',
|
||||||
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
|
features.includes('safe_dm_mentions'),
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'shoutAvailable',
|
||||||
|
features.includes('chat'),
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'pleromaChatMessagesAvailable',
|
||||||
|
features.includes('pleroma_chat_messages'),
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'pleromaCustomEmojiReactionsAvailable',
|
||||||
|
|
||||||
|
features.includes('pleroma_custom_emoji_reactions') ||
|
||||||
|
features.includes('custom_emoji_reactions'),
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'pleromaBookmarkFoldersAvailable',
|
||||||
|
features.includes('pleroma:bookmark_folders'),
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'gopherAvailable',
|
||||||
|
features.includes('gopher'),
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'pollsAvailable',
|
||||||
|
features.includes('polls'),
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'editingAvailable',
|
||||||
|
features.includes('editing'),
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'mailerEnabled',
|
||||||
|
metadata.mailerEnabled,
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'quotingAvailable',
|
||||||
|
features.includes('quote_posting'),
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'groupActorAvailable',
|
||||||
|
features.includes('pleroma:group_actors'),
|
||||||
|
)
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'blockExpiration',
|
||||||
|
features.includes('pleroma:block_expiration'),
|
||||||
|
)
|
||||||
|
useInstanceStore().set({
|
||||||
|
path: 'localBubbleInstances',
|
||||||
|
value: metadata.localBubbleInstances ?? [],
|
||||||
|
})
|
||||||
|
useInstanceCapabilitiesStore().set(
|
||||||
|
'localBubble',
|
||||||
|
(metadata.localBubbleInstances ?? []).length > 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
useInstanceStore().set({
|
||||||
|
path: 'limits.pollLimits',
|
||||||
|
value: metadata.pollLimits,
|
||||||
|
})
|
||||||
const uploadLimits = metadata.uploadLimits
|
const uploadLimits = metadata.uploadLimits
|
||||||
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
|
useInstanceStore().set({
|
||||||
store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) })
|
path: 'limits.uploadlimit',
|
||||||
store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) })
|
value: parseInt(uploadLimits.general),
|
||||||
store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) })
|
})
|
||||||
store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits })
|
useInstanceStore().set({
|
||||||
|
path: 'limits.avatarlimit',
|
||||||
|
value: parseInt(uploadLimits.avatar),
|
||||||
|
})
|
||||||
|
useInstanceStore().set({
|
||||||
|
path: 'limits.backgroundlimit',
|
||||||
|
value: parseInt(uploadLimits.background),
|
||||||
|
})
|
||||||
|
useInstanceStore().set({
|
||||||
|
path: 'limits.bannerlimit',
|
||||||
|
value: parseInt(uploadLimits.banner),
|
||||||
|
})
|
||||||
|
useInstanceStore().set({
|
||||||
|
path: 'limits.fieldsLimits',
|
||||||
|
value: metadata.fieldsLimits,
|
||||||
|
})
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
|
useInstanceStore().set({
|
||||||
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
|
path: 'restrictedNicknames',
|
||||||
|
value: metadata.restrictedNicknames,
|
||||||
|
})
|
||||||
|
useInstanceCapabilitiesStore().set('postFormats', metadata.postFormats)
|
||||||
|
|
||||||
const suggestions = metadata.suggestions
|
const suggestions = metadata.suggestions
|
||||||
store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
|
useInstanceCapabilitiesStore().set(
|
||||||
store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web })
|
'suggestionsEnabled',
|
||||||
|
suggestions.enabled,
|
||||||
|
)
|
||||||
|
// this is unused, why?
|
||||||
|
useInstanceCapabilitiesStore().set('suggestionsWeb', suggestions.web)
|
||||||
|
|
||||||
const software = data.software
|
const software = data.software
|
||||||
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
|
useInstanceStore().set({
|
||||||
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
|
path: 'backendVersion',
|
||||||
|
value: software.version,
|
||||||
|
})
|
||||||
|
useInstanceStore().set({
|
||||||
|
path: 'backendRepository',
|
||||||
|
value: software.repository,
|
||||||
|
})
|
||||||
|
|
||||||
const priv = metadata.private
|
const priv = metadata.private
|
||||||
store.dispatch('setInstanceOption', { name: 'private', value: priv })
|
useInstanceStore().set({ path: 'privateMode', value: priv })
|
||||||
|
|
||||||
const frontendVersion = window.___pleromafe_commit_hash
|
const frontendVersion = window.___pleromafe_commit_hash
|
||||||
store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
|
useInstanceStore().set({
|
||||||
|
path: 'frontendVersion',
|
||||||
|
value: frontendVersion,
|
||||||
|
})
|
||||||
|
|
||||||
const federation = metadata.federation
|
const federation = metadata.federation
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', {
|
useInstanceCapabilitiesStore().set(
|
||||||
name: 'tagPolicyAvailable',
|
'tagPolicyAvailable',
|
||||||
value: typeof federation.mrf_policies === 'undefined'
|
typeof federation.mrf_policies === 'undefined'
|
||||||
? false
|
? false
|
||||||
: metadata.federation.mrf_policies.includes('TagPolicy')
|
: metadata.federation.mrf_policies.includes('TagPolicy'),
|
||||||
})
|
)
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
|
useInstanceStore().set({
|
||||||
store.dispatch('setInstanceOption', {
|
path: 'federationPolicy',
|
||||||
name: 'federating',
|
value: federation,
|
||||||
value: typeof federation.enabled === 'undefined'
|
})
|
||||||
? true
|
useInstanceStore().set({
|
||||||
: federation.enabled
|
path: 'federating',
|
||||||
|
value:
|
||||||
|
typeof federation.enabled === 'undefined' ? true : federation.enabled,
|
||||||
})
|
})
|
||||||
|
|
||||||
const accountActivationRequired = metadata.accountActivationRequired
|
const accountActivationRequired = metadata.accountActivationRequired
|
||||||
store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
|
useInstanceStore().set({
|
||||||
|
path: 'accountActivationRequired',
|
||||||
|
value: accountActivationRequired,
|
||||||
|
})
|
||||||
|
|
||||||
const accounts = metadata.staffAccounts
|
const accounts = metadata.staffAccounts
|
||||||
resolveStaffAccounts({ store, accounts })
|
resolveStaffAccounts({ store, accounts })
|
||||||
} else {
|
} else {
|
||||||
throw (res)
|
throw res
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Could not load nodeinfo')
|
console.warn('Could not load nodeinfo')
|
||||||
|
|
@ -319,23 +440,89 @@ const getNodeInfo = async ({ store }) => {
|
||||||
|
|
||||||
const setConfig = async ({ store }) => {
|
const setConfig = async ({ store }) => {
|
||||||
// apiConfig, staticConfig
|
// apiConfig, staticConfig
|
||||||
const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()])
|
const configInfos = await Promise.all([
|
||||||
|
getBackendProvidedConfig({ store }),
|
||||||
|
getStaticConfig(),
|
||||||
|
])
|
||||||
const apiConfig = configInfos[0]
|
const apiConfig = configInfos[0]
|
||||||
const staticConfig = configInfos[1]
|
const staticConfig = configInfos[1]
|
||||||
|
|
||||||
await setSettings({ store, apiConfig, staticConfig }).then(getAppSecret({ store }))
|
getAppSecret({ store })
|
||||||
|
await setSettings({ store, apiConfig, staticConfig })
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkOAuthToken = async ({ store }) => {
|
const checkOAuthToken = async ({ store }) => {
|
||||||
if (store.getters.getUserToken()) {
|
const oauth = useOAuthStore()
|
||||||
return store.dispatch('loginUser', store.getters.getUserToken())
|
if (oauth.getUserToken) {
|
||||||
|
return store.dispatch('loginUser', oauth.getUserToken)
|
||||||
}
|
}
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
const afterStoreSetup = async ({ store, i18n }) => {
|
const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => {
|
||||||
store.dispatch('setLayoutWidth', windowWidth())
|
const app = createApp(App)
|
||||||
store.dispatch('setLayoutHeight', windowHeight())
|
// Must have app use pinia before we do anything that touches the store
|
||||||
|
// https://pinia.vuejs.org/core-concepts/plugins.html#Introduction
|
||||||
|
// "Plugins are only applied to stores created after the plugins themselves, and after pinia is passed to the app, otherwise they won't be applied."
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
|
const waitForAllStoresToLoad = async () => {
|
||||||
|
// the stores that do not persist technically do not need to be awaited here,
|
||||||
|
// but that involves either hard-coding the stores in some place (prone to errors)
|
||||||
|
// or writing another vite plugin to analyze which stores needs persisting (++load time)
|
||||||
|
const allStores = import.meta.glob('../stores/*.js', { eager: true })
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
// do some checks to avoid common errors
|
||||||
|
if (!Object.keys(allStores).length) {
|
||||||
|
throw new Error(
|
||||||
|
'No stores are available. Check the code in src/boot/after_store.js',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(
|
||||||
|
Object.entries(allStores).map(async ([name, mod]) => {
|
||||||
|
const isStoreName = (name) => name.startsWith('use')
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
if (Object.keys(mod).filter(isStoreName).length !== 1) {
|
||||||
|
throw new Error(
|
||||||
|
'Each store file must export exactly one store as a named export. Check your code in src/stores/',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const storeFuncName = Object.keys(mod).find(isStoreName)
|
||||||
|
if (storeFuncName && typeof mod[storeFuncName] === 'function') {
|
||||||
|
const p = mod[storeFuncName]().$persistLoaded
|
||||||
|
if (!(p instanceof Promise)) {
|
||||||
|
throw new Error(
|
||||||
|
`${name} store's $persistLoaded is not a Promise. The persist plugin is not applied.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
await p
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Store module ${name} does not export a 'use...' function`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await waitForAllStoresToLoad()
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Cannot load stores:', e)
|
||||||
|
storageError = e
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storageError) {
|
||||||
|
useInterfaceStore().pushGlobalNotice({
|
||||||
|
messageKey: 'errors.storage_unavailable',
|
||||||
|
level: 'error',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
useInterfaceStore().setLayoutWidth(windowWidth())
|
||||||
|
useInterfaceStore().setLayoutHeight(windowHeight())
|
||||||
|
|
||||||
FaviconService.initFaviconService()
|
FaviconService.initFaviconService()
|
||||||
initServiceWorker(store)
|
initServiceWorker(store)
|
||||||
|
|
@ -343,33 +530,39 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
window.addEventListener('focus', () => updateFocus())
|
window.addEventListener('focus', () => updateFocus())
|
||||||
|
|
||||||
const overrides = window.___pleromafe_dev_overrides || {}
|
const overrides = window.___pleromafe_dev_overrides || {}
|
||||||
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
|
const server =
|
||||||
store.dispatch('setInstanceOption', { name: 'server', value: server })
|
typeof overrides.target !== 'undefined'
|
||||||
|
? overrides.target
|
||||||
|
: window.location.origin
|
||||||
|
useInstanceStore().set({ name: 'server', value: server })
|
||||||
|
|
||||||
document.querySelector('#status').textContent = i18n.global.t('splash.settings')
|
|
||||||
await setConfig({ store })
|
await setConfig({ store })
|
||||||
document.querySelector('#status').textContent = i18n.global.t('splash.theme')
|
|
||||||
try {
|
try {
|
||||||
await store.dispatch('setTheme').catch((e) => { console.error('Error setting theme', e) })
|
await useInterfaceStore()
|
||||||
|
.applyTheme()
|
||||||
|
.catch((e) => {
|
||||||
|
console.error('Error setting theme', e)
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
window.splashError(e)
|
||||||
return Promise.reject(e)
|
return Promise.reject(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
applyConfig(store.state.config, i18n.global)
|
applyStyleConfig(useSyncConfigStore().mergedConfig, i18n.global)
|
||||||
|
|
||||||
// Now we can try getting the server settings and logging in
|
// Now we can try getting the server settings and logging in
|
||||||
// Most of these are preloaded into the index.html so blocking is minimized
|
// Most of these are preloaded into the index.html so blocking is minimized
|
||||||
document.querySelector('#status').textContent = i18n.global.t('splash.instance')
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
checkOAuthToken({ store }),
|
checkOAuthToken({ store }),
|
||||||
getInstancePanel({ store }),
|
getInstancePanel({ store }),
|
||||||
getNodeInfo({ store }),
|
getNodeInfo({ store }),
|
||||||
getInstanceConfig({ store })
|
getInstanceConfig({ store }),
|
||||||
]).catch(e => Promise.reject(e))
|
]).catch((e) => Promise.reject(e))
|
||||||
|
|
||||||
// Start fetching things that don't need to block the UI
|
// Start fetching things that don't need to block the UI
|
||||||
store.dispatch('fetchMutes')
|
store.dispatch('fetchMutes')
|
||||||
store.dispatch('startFetchingAnnouncements')
|
store.dispatch('loadDrafts')
|
||||||
|
useAnnouncementsStore().startFetchingAnnouncements()
|
||||||
getTOS({ store })
|
getTOS({ store })
|
||||||
getStickers({ store })
|
getStickers({ store })
|
||||||
|
|
||||||
|
|
@ -377,19 +570,26 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: routes(store),
|
routes: routes(store),
|
||||||
scrollBehavior: (to, _from, savedPosition) => {
|
scrollBehavior: (to, _from, savedPosition) => {
|
||||||
if (to.matched.some(m => m.meta.dontScroll)) {
|
if (to.matched.some((m) => m.meta.dontScroll)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return savedPosition || { left: 0, top: 0 }
|
return savedPosition || { left: 0, top: 0 }
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const app = createApp(App)
|
useI18nStore().setI18n(i18n)
|
||||||
|
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(store)
|
app.use(store)
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
|
|
||||||
|
// Little thing to get out of invalid theme state
|
||||||
|
window.resetThemes = () => {
|
||||||
|
useInterfaceStore().resetThemeV3()
|
||||||
|
useInterfaceStore().resetThemeV3Palette()
|
||||||
|
useInterfaceStore().resetThemeV2()
|
||||||
|
}
|
||||||
|
|
||||||
app.use(vClickOutside)
|
app.use(vClickOutside)
|
||||||
app.use(VBodyScrollLock)
|
app.use(VBodyScrollLock)
|
||||||
app.use(VueVirtualScroller)
|
app.use(VueVirtualScroller)
|
||||||
|
|
@ -399,7 +599,6 @@ const afterStoreSetup = async ({ store, i18n }) => {
|
||||||
|
|
||||||
// remove after vue 3.3
|
// remove after vue 3.3
|
||||||
app.config.unwrapInjectedRef = true
|
app.config.unwrapInjectedRef = true
|
||||||
document.querySelector('#status').textContent = i18n.global.t('splash.almost')
|
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
return app
|
return app
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,48 @@
|
||||||
import PublicTimeline from 'components/public_timeline/public_timeline.vue'
|
|
||||||
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
|
|
||||||
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
|
|
||||||
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
|
|
||||||
import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
|
|
||||||
import ConversationPage from 'components/conversation-page/conversation-page.vue'
|
|
||||||
import Interactions from 'components/interactions/interactions.vue'
|
|
||||||
import DMs from 'components/dm_timeline/dm_timeline.vue'
|
|
||||||
import ChatList from 'components/chat_list/chat_list.vue'
|
|
||||||
import Chat from 'components/chat/chat.vue'
|
|
||||||
import UserProfile from 'components/user_profile/user_profile.vue'
|
|
||||||
import Search from 'components/search/search.vue'
|
|
||||||
import Registration from 'components/registration/registration.vue'
|
|
||||||
import PasswordReset from 'components/password_reset/password_reset.vue'
|
|
||||||
import FollowRequests from 'components/follow_requests/follow_requests.vue'
|
|
||||||
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
|
|
||||||
import Notifications from 'components/notifications/notifications.vue'
|
|
||||||
import AuthForm from 'components/auth_form/auth_form.js'
|
|
||||||
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
|
|
||||||
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
|
|
||||||
import About from 'components/about/about.vue'
|
import About from 'components/about/about.vue'
|
||||||
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
|
|
||||||
import Lists from 'components/lists/lists.vue'
|
|
||||||
import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
|
|
||||||
import ListsEdit from 'components/lists_edit/lists_edit.vue'
|
|
||||||
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
|
|
||||||
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
|
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
|
||||||
import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
|
import AuthForm from 'components/auth_form/auth_form.js'
|
||||||
import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue'
|
import BookmarkTimeline from 'components/bookmark_timeline/bookmark_timeline.vue'
|
||||||
|
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
|
||||||
|
import Chat from 'components/chat/chat.vue'
|
||||||
|
import ChatList from 'components/chat_list/chat_list.vue'
|
||||||
|
import ConversationPage from 'components/conversation-page/conversation-page.vue'
|
||||||
|
import DMs from 'components/dm_timeline/dm_timeline.vue'
|
||||||
|
import Drafts from 'components/drafts/drafts.vue'
|
||||||
|
import FollowRequests from 'components/follow_requests/follow_requests.vue'
|
||||||
|
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
|
||||||
|
import Interactions from 'components/interactions/interactions.vue'
|
||||||
|
import Lists from 'components/lists/lists.vue'
|
||||||
|
import ListsEdit from 'components/lists_edit/lists_edit.vue'
|
||||||
|
import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
|
||||||
|
import Notifications from 'components/notifications/notifications.vue'
|
||||||
|
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
|
||||||
|
import PasswordReset from 'components/password_reset/password_reset.vue'
|
||||||
|
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
|
||||||
|
import PublicTimeline from 'components/public_timeline/public_timeline.vue'
|
||||||
|
import Registration from 'components/registration/registration.vue'
|
||||||
|
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
|
||||||
|
import Search from 'components/search/search.vue'
|
||||||
|
import ShoutPanel from 'components/shout_panel/shout_panel.vue'
|
||||||
|
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
|
||||||
|
import UserProfile from 'components/user_profile/user_profile.vue'
|
||||||
|
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
|
||||||
|
|
||||||
|
import NavPanel from 'src/components/nav_panel/nav_panel.vue'
|
||||||
import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue'
|
import BookmarkFolderEdit from '../components/bookmark_folder_edit/bookmark_folder_edit.vue'
|
||||||
|
import BookmarkFolders from '../components/bookmark_folders/bookmark_folders.vue'
|
||||||
|
import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
|
||||||
|
|
||||||
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
|
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||||
|
|
||||||
export default (store) => {
|
export default (store) => {
|
||||||
const validateAuthenticatedRoute = (to, from, next) => {
|
const validateAuthenticatedRoute = (to, from, next) => {
|
||||||
if (store.state.users.currentUser) {
|
if (store.state.users.currentUser) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
next(store.state.instance.redirectRootNoLogin || '/main/all')
|
next(
|
||||||
|
useInstanceStore().instanceIdentity.redirectRootNoLogin || '/main/all',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,63 +50,178 @@ export default (store) => {
|
||||||
{
|
{
|
||||||
name: 'root',
|
name: 'root',
|
||||||
path: '/',
|
path: '/',
|
||||||
redirect: _to => {
|
redirect: () => {
|
||||||
return (store.state.users.currentUser
|
return (
|
||||||
? store.state.instance.redirectRootLogin
|
(store.state.users.currentUser
|
||||||
: store.state.instance.redirectRootNoLogin) || '/main/all'
|
? useInstanceStore().instanceIdentity.redirectRootLogin
|
||||||
}
|
: useInstanceStore().instanceIdentity.redirectRootNoLogin) ||
|
||||||
|
'/main/all'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'public-external-timeline',
|
||||||
|
path: '/main/all',
|
||||||
|
component: PublicAndExternalTimeline,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'public-timeline',
|
||||||
|
path: '/main/public',
|
||||||
|
component: PublicTimeline,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'friends',
|
||||||
|
path: '/main/friends',
|
||||||
|
component: FriendsTimeline,
|
||||||
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
},
|
},
|
||||||
{ name: 'public-external-timeline', path: '/main/all', component: PublicAndExternalTimeline },
|
|
||||||
{ name: 'public-timeline', path: '/main/public', component: PublicTimeline },
|
|
||||||
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
|
|
||||||
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
|
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
|
||||||
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
|
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
|
||||||
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
|
{ name: 'bubble', path: '/bubble', component: BubbleTimeline },
|
||||||
|
{
|
||||||
|
name: 'conversation',
|
||||||
|
path: '/notice/:id',
|
||||||
|
component: ConversationPage,
|
||||||
|
meta: { dontScroll: true },
|
||||||
|
},
|
||||||
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
|
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
|
||||||
{
|
{
|
||||||
name: 'remote-user-profile-acct',
|
name: 'remote-user-profile-acct',
|
||||||
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
|
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',
|
||||||
component: RemoteUserResolver,
|
component: RemoteUserResolver,
|
||||||
beforeEnter: validateAuthenticatedRoute
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'remote-user-profile',
|
name: 'remote-user-profile',
|
||||||
path: '/remote-users/:hostname/:username',
|
path: '/remote-users/:hostname/:username',
|
||||||
component: RemoteUserResolver,
|
component: RemoteUserResolver,
|
||||||
beforeEnter: validateAuthenticatedRoute
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'external-user-profile',
|
||||||
|
path: '/users/$:id',
|
||||||
|
component: UserProfile,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'interactions',
|
||||||
|
path: '/users/:username/interactions',
|
||||||
|
component: Interactions,
|
||||||
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dms',
|
||||||
|
path: '/users/:username/dms',
|
||||||
|
component: DMs,
|
||||||
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
},
|
},
|
||||||
{ name: 'external-user-profile', path: '/users/$:id', component: UserProfile },
|
|
||||||
{ name: 'interactions', path: '/users/:username/interactions', component: Interactions, beforeEnter: validateAuthenticatedRoute },
|
|
||||||
{ name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
|
|
||||||
{ name: 'registration', path: '/registration', component: Registration },
|
{ name: 'registration', path: '/registration', component: Registration },
|
||||||
{ name: 'password-reset', path: '/password-reset', component: PasswordReset, props: true },
|
{
|
||||||
{ name: 'registration-token', path: '/registration/:token', component: Registration },
|
name: 'password-reset',
|
||||||
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
|
path: '/password-reset',
|
||||||
{ name: 'notifications', path: '/:username/notifications', component: Notifications, props: () => ({ disableTeleport: true }), beforeEnter: validateAuthenticatedRoute },
|
component: PasswordReset,
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'registration-token',
|
||||||
|
path: '/registration/:token',
|
||||||
|
component: Registration,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'friend-requests',
|
||||||
|
path: '/friend-requests',
|
||||||
|
component: FollowRequests,
|
||||||
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'notifications',
|
||||||
|
path: '/:username/notifications',
|
||||||
|
component: Notifications,
|
||||||
|
props: () => ({ disableTeleport: true }),
|
||||||
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
|
},
|
||||||
{ name: 'login', path: '/login', component: AuthForm },
|
{ name: 'login', path: '/login', component: AuthForm },
|
||||||
{ name: 'shout-panel', path: '/shout-panel', component: ShoutPanel, props: () => ({ floating: false }) },
|
{
|
||||||
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
|
name: 'shout-panel',
|
||||||
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
|
path: '/shout-panel',
|
||||||
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
|
component: ShoutPanel,
|
||||||
|
props: () => ({ floating: false }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'oauth-callback',
|
||||||
|
path: '/oauth-callback',
|
||||||
|
component: OAuthCallback,
|
||||||
|
props: (route) => ({ code: route.query.code }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'search',
|
||||||
|
path: '/search',
|
||||||
|
component: Search,
|
||||||
|
props: (route) => ({ query: route.query.query }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'who-to-follow',
|
||||||
|
path: '/who-to-follow',
|
||||||
|
component: WhoToFollow,
|
||||||
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
|
},
|
||||||
{ name: 'about', path: '/about', component: About },
|
{ name: 'about', path: '/about', component: About },
|
||||||
{ name: 'announcements', path: '/announcements', component: AnnouncementsPage },
|
{
|
||||||
|
name: 'announcements',
|
||||||
|
path: '/announcements',
|
||||||
|
component: AnnouncementsPage,
|
||||||
|
},
|
||||||
|
{ name: 'drafts', path: '/drafts', component: Drafts },
|
||||||
{ name: 'user-profile', path: '/users/:name', component: UserProfile },
|
{ name: 'user-profile', path: '/users/:name', component: UserProfile },
|
||||||
{ name: 'legacy-user-profile', path: '/:name', component: UserProfile },
|
{ name: 'legacy-user-profile', path: '/:name', component: UserProfile },
|
||||||
{ name: 'lists', path: '/lists', component: Lists },
|
{ name: 'lists', path: '/lists', component: Lists },
|
||||||
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
|
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
|
||||||
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
|
{ name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
|
||||||
{ name: 'lists-new', path: '/lists/new', component: ListsEdit },
|
{ name: 'lists-new', path: '/lists/new', component: ListsEdit },
|
||||||
{ name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute },
|
{
|
||||||
{ name: 'bookmark-folders', path: '/bookmark_folders', component: BookmarkFolders },
|
name: 'edit-navigation',
|
||||||
{ name: 'bookmark-folder-new', path: '/bookmarks/new-folder', component: BookmarkFolderEdit },
|
path: '/nav-edit',
|
||||||
{ name: 'bookmark-folder', path: '/bookmarks/:id', component: BookmarkTimeline },
|
component: NavPanel,
|
||||||
{ name: 'bookmark-folder-edit', path: '/bookmarks/:id/edit', component: BookmarkFolderEdit }
|
props: () => ({ forceExpand: true, forceEditMode: true }),
|
||||||
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bookmark-folders',
|
||||||
|
path: '/bookmark_folders',
|
||||||
|
component: BookmarkFolders,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bookmark-folder-new',
|
||||||
|
path: '/bookmarks/new-folder',
|
||||||
|
component: BookmarkFolderEdit,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bookmark-folder',
|
||||||
|
path: '/bookmarks/:id',
|
||||||
|
component: BookmarkTimeline,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bookmark-folder-edit',
|
||||||
|
path: '/bookmarks/:id/edit',
|
||||||
|
component: BookmarkFolderEdit,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
if (store.state.instance.pleromaChatMessagesAvailable) {
|
if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) {
|
||||||
routes = routes.concat([
|
routes = routes.concat([
|
||||||
{ name: 'chat', path: '/users/:username/chats/:recipient_id', component: Chat, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute },
|
{
|
||||||
{ name: 'chats', path: '/users/:username/chats', component: ChatList, meta: { dontScroll: false }, beforeEnter: validateAuthenticatedRoute }
|
name: 'chat',
|
||||||
|
path: '/users/:username/chats/:recipient_id',
|
||||||
|
component: Chat,
|
||||||
|
meta: { dontScroll: false },
|
||||||
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'chats',
|
||||||
|
path: '/users/:username/chats',
|
||||||
|
component: ChatList,
|
||||||
|
meta: { dontScroll: false },
|
||||||
|
beforeEnter: validateAuthenticatedRoute,
|
||||||
|
},
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue'
|
|
||||||
import FeaturesPanel from '../features_panel/features_panel.vue'
|
import FeaturesPanel from '../features_panel/features_panel.vue'
|
||||||
import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
|
import InstanceSpecificPanel from '../instance_specific_panel/instance_specific_panel.vue'
|
||||||
import StaffPanel from '../staff_panel/staff_panel.vue'
|
|
||||||
import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue'
|
import MRFTransparencyPanel from '../mrf_transparency_panel/mrf_transparency_panel.vue'
|
||||||
|
import StaffPanel from '../staff_panel/staff_panel.vue'
|
||||||
|
import TermsOfServicePanel from '../terms_of_service_panel/terms_of_service_panel.vue'
|
||||||
|
|
||||||
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
|
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||||
|
|
||||||
const About = {
|
const About = {
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -10,16 +13,20 @@ const About = {
|
||||||
FeaturesPanel,
|
FeaturesPanel,
|
||||||
TermsOfServicePanel,
|
TermsOfServicePanel,
|
||||||
StaffPanel,
|
StaffPanel,
|
||||||
MRFTransparencyPanel
|
MRFTransparencyPanel,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
|
showFeaturesPanel() {
|
||||||
showInstanceSpecificPanel () {
|
return useInstanceStore().instanceIdentity.showFeaturesPanel
|
||||||
return this.$store.state.instance.showInstanceSpecificPanel &&
|
},
|
||||||
!this.$store.getters.mergedConfig.hideISP &&
|
showInstanceSpecificPanel() {
|
||||||
this.$store.state.instance.instanceSpecificPanelContent
|
return (
|
||||||
}
|
useInstanceStore().instanceIdentity.showInstanceSpecificPanel &&
|
||||||
}
|
!useSyncConfigStore().mergedConfig.hideISP &&
|
||||||
|
useInstanceStore().instanceIdentity.instanceSpecificPanelContent
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default About
|
export default About
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,105 @@
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'pinia'
|
||||||
import ProgressButton from '../progress_button/progress_button.vue'
|
|
||||||
import Popover from '../popover/popover.vue'
|
|
||||||
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
|
|
||||||
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
|
||||||
import {
|
|
||||||
faEllipsisV
|
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
|
||||||
|
|
||||||
library.add(
|
import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
|
||||||
faEllipsisV
|
import UserTimedFilterModal from 'src/components/user_timed_filter_modal/user_timed_filter_modal.vue'
|
||||||
)
|
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
|
||||||
|
import Popover from '../popover/popover.vue'
|
||||||
|
import ProgressButton from '../progress_button/progress_button.vue'
|
||||||
|
|
||||||
|
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||||
|
import { useReportsStore } from 'src/stores/reports'
|
||||||
|
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||||
|
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
|
import { faEllipsisV } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
library.add(faEllipsisV)
|
||||||
|
|
||||||
const AccountActions = {
|
const AccountActions = {
|
||||||
props: [
|
props: ['user', 'relationship'],
|
||||||
'user', 'relationship'
|
data() {
|
||||||
],
|
|
||||||
data () {
|
|
||||||
return {
|
return {
|
||||||
showingConfirmBlock: false,
|
showingConfirmBlock: false,
|
||||||
showingConfirmRemoveFollower: false
|
showingConfirmRemoveFollower: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
ProgressButton,
|
ProgressButton,
|
||||||
Popover,
|
Popover,
|
||||||
UserListMenu,
|
UserListMenu,
|
||||||
ConfirmModal
|
ConfirmModal,
|
||||||
|
UserTimedFilterModal,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showConfirmBlock () {
|
showConfirmRemoveUserFromFollowers() {
|
||||||
this.showingConfirmBlock = true
|
|
||||||
},
|
|
||||||
hideConfirmBlock () {
|
|
||||||
this.showingConfirmBlock = false
|
|
||||||
},
|
|
||||||
showConfirmRemoveUserFromFollowers () {
|
|
||||||
this.showingConfirmRemoveFollower = true
|
this.showingConfirmRemoveFollower = true
|
||||||
},
|
},
|
||||||
hideConfirmRemoveUserFromFollowers () {
|
hideConfirmRemoveUserFromFollowers() {
|
||||||
this.showingConfirmRemoveFollower = false
|
this.showingConfirmRemoveFollower = false
|
||||||
},
|
},
|
||||||
showRepeats () {
|
hideConfirmBlock() {
|
||||||
|
this.showingConfirmBlock = false
|
||||||
|
},
|
||||||
|
showRepeats() {
|
||||||
this.$store.dispatch('showReblogs', this.user.id)
|
this.$store.dispatch('showReblogs', this.user.id)
|
||||||
},
|
},
|
||||||
hideRepeats () {
|
hideRepeats() {
|
||||||
this.$store.dispatch('hideReblogs', this.user.id)
|
this.$store.dispatch('hideReblogs', this.user.id)
|
||||||
},
|
},
|
||||||
blockUser () {
|
blockUser() {
|
||||||
if (!this.shouldConfirmBlock) {
|
if (this.$refs.timedBlockDialog) {
|
||||||
this.doBlockUser()
|
this.$refs.timedBlockDialog.optionallyPrompt()
|
||||||
} else {
|
} else {
|
||||||
this.showConfirmBlock()
|
if (!this.shouldConfirmBlock) {
|
||||||
|
this.doBlockUser()
|
||||||
|
} else {
|
||||||
|
this.showingConfirmBlock = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
doBlockUser () {
|
doBlockUser() {
|
||||||
this.$store.dispatch('blockUser', this.user.id)
|
this.$store.dispatch('blockUser', { id: this.user.id })
|
||||||
this.hideConfirmBlock()
|
this.hideConfirmBlock()
|
||||||
},
|
},
|
||||||
unblockUser () {
|
unblockUser() {
|
||||||
this.$store.dispatch('unblockUser', this.user.id)
|
this.$store.dispatch('unblockUser', this.user.id)
|
||||||
},
|
},
|
||||||
removeUserFromFollowers () {
|
removeUserFromFollowers() {
|
||||||
if (!this.shouldConfirmRemoveUserFromFollowers) {
|
if (!this.shouldConfirmRemoveUserFromFollowers) {
|
||||||
this.doRemoveUserFromFollowers()
|
this.doRemoveUserFromFollowers()
|
||||||
} else {
|
} else {
|
||||||
this.showConfirmRemoveUserFromFollowers()
|
this.showConfirmRemoveUserFromFollowers()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
doRemoveUserFromFollowers () {
|
doRemoveUserFromFollowers() {
|
||||||
this.$store.dispatch('removeUserFromFollowers', this.user.id)
|
this.$store.dispatch('removeUserFromFollowers', this.user.id)
|
||||||
this.hideConfirmRemoveUserFromFollowers()
|
this.hideConfirmRemoveUserFromFollowers()
|
||||||
},
|
},
|
||||||
reportUser () {
|
reportUser() {
|
||||||
this.$store.dispatch('openUserReportingModal', { userId: this.user.id })
|
useReportsStore().openUserReportingModal({ userId: this.user.id })
|
||||||
},
|
},
|
||||||
openChat () {
|
openChat() {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'chat',
|
name: 'chat',
|
||||||
params: { username: this.$store.state.users.currentUser.screen_name, recipient_id: this.user.id }
|
params: {
|
||||||
|
username: this.$store.state.users.currentUser.screen_name,
|
||||||
|
recipient_id: this.user.id,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
shouldConfirmBlock () {
|
shouldConfirmBlock() {
|
||||||
return this.$store.getters.mergedConfig.modalOnBlock
|
return useSyncConfigStore().mergedConfig.modalOnBlock
|
||||||
},
|
},
|
||||||
shouldConfirmRemoveUserFromFollowers () {
|
shouldConfirmRemoveUserFromFollowers() {
|
||||||
return this.$store.getters.mergedConfig.modalOnRemoveUserFromFollowers
|
return useSyncConfigStore().mergedConfig.modalOnRemoveUserFromFollowers
|
||||||
},
|
},
|
||||||
...mapState({
|
...mapState(useInstanceCapabilitiesStore, [
|
||||||
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
|
'blockExpiration',
|
||||||
})
|
'pleromaChatMessagesAvailable',
|
||||||
}
|
]),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AccountActions
|
export default AccountActions
|
||||||
|
|
|
||||||
|
|
@ -3,66 +3,85 @@
|
||||||
<Popover
|
<Popover
|
||||||
trigger="click"
|
trigger="click"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
:bound-to="{ x: 'container' }"
|
|
||||||
remove-padding
|
remove-padding
|
||||||
>
|
>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu">
|
||||||
<template v-if="relationship.following">
|
<template v-if="relationship.following">
|
||||||
<button
|
<div
|
||||||
v-if="relationship.showing_reblogs"
|
v-if="relationship.showing_reblogs"
|
||||||
class="dropdown-item menu-item"
|
class="menu-item dropdown-item"
|
||||||
@click="hideRepeats"
|
|
||||||
>
|
>
|
||||||
{{ $t('user_card.hide_repeats') }}
|
<button
|
||||||
</button>
|
class="main-button"
|
||||||
<button
|
@click="hideRepeats"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.hide_repeats') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
v-if="!relationship.showing_reblogs"
|
v-if="!relationship.showing_reblogs"
|
||||||
class="dropdown-item menu-item"
|
class="menu-item dropdown-item"
|
||||||
@click="showRepeats"
|
|
||||||
>
|
>
|
||||||
{{ $t('user_card.show_repeats') }}
|
<button
|
||||||
</button>
|
class="main-button"
|
||||||
|
@click="showRepeats"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.show_repeats') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
role="separator"
|
role="separator"
|
||||||
class="dropdown-divider"
|
class="dropdown-divider"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<UserListMenu :user="user" />
|
<UserListMenu :user="user" />
|
||||||
<button
|
<div
|
||||||
v-if="relationship.followed_by"
|
v-if="relationship.followed_by"
|
||||||
class="dropdown-item menu-item"
|
class="menu-item dropdown-item"
|
||||||
@click="removeUserFromFollowers"
|
|
||||||
>
|
>
|
||||||
{{ $t('user_card.remove_follower') }}
|
<button
|
||||||
</button>
|
class="main-button"
|
||||||
<button
|
@click="removeUserFromFollowers"
|
||||||
v-if="relationship.blocking"
|
>
|
||||||
class="dropdown-item menu-item"
|
{{ $t('user_card.remove_follower') }}
|
||||||
@click="unblockUser"
|
</button>
|
||||||
>
|
</div>
|
||||||
{{ $t('user_card.unblock') }}
|
<div class="menu-item dropdown-item">
|
||||||
</button>
|
<button
|
||||||
<button
|
v-if="relationship.blocking"
|
||||||
v-else
|
class="main-button"
|
||||||
class="dropdown-item menu-item"
|
@click="unblockUser"
|
||||||
@click="blockUser"
|
>
|
||||||
>
|
{{ $t('user_card.unblock') }}
|
||||||
{{ $t('user_card.block') }}
|
</button>
|
||||||
</button>
|
<button
|
||||||
<button
|
v-else
|
||||||
class="dropdown-item menu-item"
|
class="main-button"
|
||||||
@click="reportUser"
|
@click="blockUser"
|
||||||
>
|
>
|
||||||
{{ $t('user_card.report') }}
|
{{ $t('user_card.block') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
</div>
|
||||||
|
<div class="menu-item dropdown-item">
|
||||||
|
<button
|
||||||
|
class="main-button"
|
||||||
|
@click="reportUser"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.report') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
v-if="pleromaChatMessagesAvailable"
|
v-if="pleromaChatMessagesAvailable"
|
||||||
class="dropdown-item menu-item"
|
class="menu-item dropdown-item"
|
||||||
@click="openChat"
|
|
||||||
>
|
>
|
||||||
{{ $t('user_card.message') }}
|
<button
|
||||||
</button>
|
class="main-button"
|
||||||
|
@click="openChat"
|
||||||
|
>
|
||||||
|
{{ $t('user_card.message') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
|
|
@ -76,7 +95,8 @@
|
||||||
</Popover>
|
</Popover>
|
||||||
<teleport to="#modal">
|
<teleport to="#modal">
|
||||||
<confirm-modal
|
<confirm-modal
|
||||||
v-if="showingConfirmBlock"
|
v-if="showingConfirmBlock && !blockExpiration"
|
||||||
|
ref="blockDialog"
|
||||||
:title="$t('user_card.block_confirm_title')"
|
:title="$t('user_card.block_confirm_title')"
|
||||||
:confirm-text="$t('user_card.block_confirm_accept_button')"
|
:confirm-text="$t('user_card.block_confirm_accept_button')"
|
||||||
:cancel-text="$t('user_card.block_confirm_cancel_button')"
|
:cancel-text="$t('user_card.block_confirm_cancel_button')"
|
||||||
|
|
@ -86,6 +106,7 @@
|
||||||
<i18n-t
|
<i18n-t
|
||||||
keypath="user_card.block_confirm"
|
keypath="user_card.block_confirm"
|
||||||
tag="span"
|
tag="span"
|
||||||
|
scope="global"
|
||||||
>
|
>
|
||||||
<template #user>
|
<template #user>
|
||||||
<span
|
<span
|
||||||
|
|
@ -107,6 +128,7 @@
|
||||||
<i18n-t
|
<i18n-t
|
||||||
keypath="user_card.remove_follower_confirm"
|
keypath="user_card.remove_follower_confirm"
|
||||||
tag="span"
|
tag="span"
|
||||||
|
scope="global"
|
||||||
>
|
>
|
||||||
<template #user>
|
<template #user>
|
||||||
<span
|
<span
|
||||||
|
|
@ -115,6 +137,12 @@
|
||||||
</template>
|
</template>
|
||||||
</i18n-t>
|
</i18n-t>
|
||||||
</confirm-modal>
|
</confirm-modal>
|
||||||
|
<UserTimedFilterModal
|
||||||
|
v-if="blockExpiration"
|
||||||
|
ref="timedBlockDialog"
|
||||||
|
:is-mute="false"
|
||||||
|
:user="user"
|
||||||
|
/>
|
||||||
</teleport>
|
</teleport>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,51 @@
|
||||||
export default {
|
export default {
|
||||||
name: 'Alert',
|
name: 'Alert',
|
||||||
selector: '.alert',
|
selector: '.alert',
|
||||||
validInnerComponents: [
|
validInnerComponents: ['Text', 'Icon', 'Link', 'Border', 'ButtonUnstyled'],
|
||||||
'Text',
|
|
||||||
'Icon',
|
|
||||||
'Link',
|
|
||||||
'Border',
|
|
||||||
'ButtonUnstyled'
|
|
||||||
],
|
|
||||||
variants: {
|
variants: {
|
||||||
normal: '.neutral',
|
normal: '.neutral',
|
||||||
error: '.error',
|
error: '.error',
|
||||||
warning: '.warning',
|
warning: '.warning',
|
||||||
success: '.success'
|
success: '.success',
|
||||||
|
},
|
||||||
|
editor: {
|
||||||
|
border: 1,
|
||||||
|
aspect: '3 / 1',
|
||||||
},
|
},
|
||||||
defaultRules: [
|
defaultRules: [
|
||||||
{
|
{
|
||||||
directives: {
|
directives: {
|
||||||
background: '--text',
|
background: '--text',
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
blur: '9px'
|
blur: '9px',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
parent: {
|
parent: {
|
||||||
component: 'Alert'
|
component: 'Alert',
|
||||||
},
|
},
|
||||||
component: 'Border',
|
component: 'Border',
|
||||||
directives: {
|
directives: {
|
||||||
textColor: '--parent'
|
textColor: '--parent',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
variant: 'error',
|
variant: 'error',
|
||||||
directives: {
|
directives: {
|
||||||
background: '--cRed'
|
background: '--cRed',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
variant: 'warning',
|
variant: 'warning',
|
||||||
directives: {
|
directives: {
|
||||||
background: '--cOrange'
|
background: '--cOrange',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
directives: {
|
directives: {
|
||||||
background: '--cGreen'
|
background: '--cGreen',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,108 +1,130 @@
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
|
import localeService from '../../services/locale/locale.service.js'
|
||||||
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
|
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
|
||||||
import RichContent from '../rich_content/rich_content.jsx'
|
import RichContent from '../rich_content/rich_content.jsx'
|
||||||
import localeService from '../../services/locale/locale.service.js'
|
|
||||||
|
import { useAnnouncementsStore } from 'src/stores/announcements.js'
|
||||||
|
|
||||||
const Announcement = {
|
const Announcement = {
|
||||||
components: {
|
components: {
|
||||||
AnnouncementEditor,
|
AnnouncementEditor,
|
||||||
RichContent
|
RichContent,
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
editing: false,
|
editing: false,
|
||||||
editedAnnouncement: {
|
editedAnnouncement: {
|
||||||
content: '',
|
content: '',
|
||||||
startsAt: undefined,
|
startsAt: undefined,
|
||||||
endsAt: undefined,
|
endsAt: undefined,
|
||||||
allDay: undefined
|
allDay: undefined,
|
||||||
},
|
},
|
||||||
editError: ''
|
editError: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
announcement: Object
|
announcement: Object,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
currentUser: state => state.users.currentUser
|
currentUser: (state) => state.users.currentUser,
|
||||||
}),
|
}),
|
||||||
canEditAnnouncement () {
|
canEditAnnouncement() {
|
||||||
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
|
return (
|
||||||
|
this.currentUser &&
|
||||||
|
this.currentUser.privileges.includes(
|
||||||
|
'announcements_manage_announcements',
|
||||||
|
)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
content () {
|
content() {
|
||||||
return this.announcement.content
|
return this.announcement.content
|
||||||
},
|
},
|
||||||
isRead () {
|
isRead() {
|
||||||
return this.announcement.read
|
return this.announcement.read
|
||||||
},
|
},
|
||||||
publishedAt () {
|
publishedAt() {
|
||||||
const time = this.announcement.published_at
|
const time = this.announcement.published_at
|
||||||
if (!time) {
|
if (!time) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
|
return this.formatTimeOrDate(
|
||||||
|
time,
|
||||||
|
localeService.internalToBrowserLocale(this.$i18n.locale),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
startsAt () {
|
startsAt() {
|
||||||
const time = this.announcement.starts_at
|
const time = this.announcement.starts_at
|
||||||
if (!time) {
|
if (!time) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
|
return this.formatTimeOrDate(
|
||||||
|
time,
|
||||||
|
localeService.internalToBrowserLocale(this.$i18n.locale),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
endsAt () {
|
endsAt() {
|
||||||
const time = this.announcement.ends_at
|
const time = this.announcement.ends_at
|
||||||
if (!time) {
|
if (!time) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.formatTimeOrDate(time, localeService.internalToBrowserLocale(this.$i18n.locale))
|
return this.formatTimeOrDate(
|
||||||
|
time,
|
||||||
|
localeService.internalToBrowserLocale(this.$i18n.locale),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
inactive () {
|
inactive() {
|
||||||
return this.announcement.inactive
|
return this.announcement.inactive
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
markAsRead () {
|
markAsRead() {
|
||||||
if (!this.isRead) {
|
if (!this.isRead) {
|
||||||
return this.$store.dispatch('markAnnouncementAsRead', this.announcement.id)
|
return useAnnouncementsStore().markAnnouncementAsRead(
|
||||||
|
this.announcement.id,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deleteAnnouncement () {
|
deleteAnnouncement() {
|
||||||
return this.$store.dispatch('deleteAnnouncement', this.announcement.id)
|
return useAnnouncementsStore().deleteAnnouncement(this.announcement.id)
|
||||||
},
|
},
|
||||||
formatTimeOrDate (time, locale) {
|
formatTimeOrDate(time, locale) {
|
||||||
const d = new Date(time)
|
const d = new Date(time)
|
||||||
return this.announcement.all_day ? d.toLocaleDateString(locale) : d.toLocaleString(locale)
|
return this.announcement.all_day
|
||||||
|
? d.toLocaleDateString(locale)
|
||||||
|
: d.toLocaleString(locale)
|
||||||
},
|
},
|
||||||
enterEditMode () {
|
enterEditMode() {
|
||||||
this.editedAnnouncement.content = this.announcement.pleroma.raw_content
|
this.editedAnnouncement.content = this.announcement.pleroma.raw_content
|
||||||
this.editedAnnouncement.startsAt = this.announcement.starts_at
|
this.editedAnnouncement.startsAt = this.announcement.starts_at
|
||||||
this.editedAnnouncement.endsAt = this.announcement.ends_at
|
this.editedAnnouncement.endsAt = this.announcement.ends_at
|
||||||
this.editedAnnouncement.allDay = this.announcement.all_day
|
this.editedAnnouncement.allDay = this.announcement.all_day
|
||||||
this.editing = true
|
this.editing = true
|
||||||
},
|
},
|
||||||
submitEdit () {
|
submitEdit() {
|
||||||
this.$store.dispatch('editAnnouncement', {
|
useAnnouncementsStore()
|
||||||
id: this.announcement.id,
|
.editAnnouncement({
|
||||||
...this.editedAnnouncement
|
id: this.announcement.id,
|
||||||
})
|
...this.editedAnnouncement,
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.editing = false
|
this.editing = false
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
this.editError = error.error
|
this.editError = error.error
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
cancelEdit () {
|
cancelEdit() {
|
||||||
this.editing = false
|
this.editing = false
|
||||||
},
|
},
|
||||||
clearError () {
|
clearError() {
|
||||||
this.editError = undefined
|
this.editError = undefined
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Announcement
|
export default Announcement
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ import Checkbox from '../checkbox/checkbox.vue'
|
||||||
|
|
||||||
const AnnouncementEditor = {
|
const AnnouncementEditor = {
|
||||||
components: {
|
components: {
|
||||||
Checkbox
|
Checkbox,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
announcement: Object,
|
announcement: Object,
|
||||||
disabled: Boolean
|
disabled: Boolean,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AnnouncementEditor
|
export default AnnouncementEditor
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,9 @@
|
||||||
id="announcement-all-day"
|
id="announcement-all-day"
|
||||||
v-model="announcement.allDay"
|
v-model="announcement.allDay"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
/>
|
>
|
||||||
<label for="announcement-all-day">{{ $t('announcements.all_day_prompt') }}</label>
|
{{ $t('announcements.all_day_prompt') }}
|
||||||
|
</Checkbox>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -55,7 +56,7 @@
|
||||||
.post-textarea {
|
.post-textarea {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
height: 10em;
|
height: 10em;
|
||||||
overflow: none;
|
overflow: visible;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,67 @@
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
import Announcement from '../announcement/announcement.vue'
|
import Announcement from '../announcement/announcement.vue'
|
||||||
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
|
import AnnouncementEditor from '../announcement_editor/announcement_editor.vue'
|
||||||
|
|
||||||
|
import { useAnnouncementsStore } from 'src/stores/announcements.js'
|
||||||
|
|
||||||
const AnnouncementsPage = {
|
const AnnouncementsPage = {
|
||||||
components: {
|
components: {
|
||||||
Announcement,
|
Announcement,
|
||||||
AnnouncementEditor
|
AnnouncementEditor,
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
newAnnouncement: {
|
newAnnouncement: {
|
||||||
content: '',
|
content: '',
|
||||||
startsAt: undefined,
|
startsAt: undefined,
|
||||||
endsAt: undefined,
|
endsAt: undefined,
|
||||||
allDay: false
|
allDay: false,
|
||||||
},
|
},
|
||||||
posting: false,
|
posting: false,
|
||||||
error: undefined
|
error: undefined,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted() {
|
||||||
this.$store.dispatch('fetchAnnouncements')
|
useAnnouncementsStore().fetchAnnouncements()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
currentUser: state => state.users.currentUser
|
currentUser: (state) => state.users.currentUser,
|
||||||
}),
|
}),
|
||||||
announcements () {
|
announcements() {
|
||||||
return this.$store.state.announcements.announcements
|
return useAnnouncementsStore().announcements
|
||||||
|
},
|
||||||
|
canPostAnnouncement() {
|
||||||
|
return (
|
||||||
|
this.currentUser &&
|
||||||
|
this.currentUser.privileges.includes(
|
||||||
|
'announcements_manage_announcements',
|
||||||
|
)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
canPostAnnouncement () {
|
|
||||||
return this.currentUser && this.currentUser.privileges.includes('announcements_manage_announcements')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
postAnnouncement () {
|
postAnnouncement() {
|
||||||
this.posting = true
|
this.posting = true
|
||||||
this.$store.dispatch('postAnnouncement', this.newAnnouncement)
|
useAnnouncementsStore()
|
||||||
|
.postAnnouncement(this.newAnnouncement)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.newAnnouncement.content = ''
|
this.newAnnouncement.content = ''
|
||||||
this.startsAt = undefined
|
this.startsAt = undefined
|
||||||
this.endsAt = undefined
|
this.endsAt = undefined
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
this.error = error.error
|
this.error = error.error
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.posting = false
|
this.posting = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
clearError () {
|
clearError() {
|
||||||
this.error = undefined
|
this.error = undefined
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AnnouncementsPage
|
export default AnnouncementsPage
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="panel panel-default announcements-page">
|
<div class="panel panel-default announcements-page">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<span>
|
<h1 class="title">
|
||||||
{{ $t('announcements.page_header') }}
|
{{ $t('announcements.page_header') }}
|
||||||
</span>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<section
|
<section
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@
|
||||||
export default {
|
export default {
|
||||||
emits: ['resetAsyncComponent'],
|
emits: ['resetAsyncComponent'],
|
||||||
methods: {
|
methods: {
|
||||||
retry () {
|
retry() {
|
||||||
this.$emit('resetAsyncComponent')
|
this.$emit('resetAsyncComponent')
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,29 @@
|
||||||
import StillImage from '../still-image/still-image.vue'
|
import { mapGetters } from 'vuex'
|
||||||
import Flash from '../flash/flash.vue'
|
|
||||||
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
|
||||||
import nsfwImage from '../../assets/nsfw.png'
|
import nsfwImage from '../../assets/nsfw.png'
|
||||||
import fileTypeService from '../../services/file_type/file_type.service.js'
|
import fileTypeService from '../../services/file_type/file_type.service.js'
|
||||||
import { mapGetters } from 'vuex'
|
import Flash from '../flash/flash.vue'
|
||||||
|
import StillImage from '../still-image/still-image.vue'
|
||||||
|
import VideoAttachment from '../video_attachment/video_attachment.vue'
|
||||||
|
|
||||||
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
|
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
|
||||||
|
import { useMediaViewerStore } from 'src/stores/media_viewer'
|
||||||
|
import { useSyncConfigStore } from 'src/stores/sync_config.js'
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
|
faAlignRight,
|
||||||
faFile,
|
faFile,
|
||||||
faMusic,
|
|
||||||
faImage,
|
faImage,
|
||||||
faVideo,
|
faMusic,
|
||||||
faPlayCircle,
|
|
||||||
faTimes,
|
|
||||||
faStop,
|
|
||||||
faSearchPlus,
|
|
||||||
faTrashAlt,
|
|
||||||
faPencilAlt,
|
faPencilAlt,
|
||||||
faAlignRight
|
faPlayCircle,
|
||||||
|
faSearchPlus,
|
||||||
|
faStop,
|
||||||
|
faTimes,
|
||||||
|
faTrashAlt,
|
||||||
|
faVideo,
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
|
|
@ -30,7 +37,7 @@ library.add(
|
||||||
faSearchPlus,
|
faSearchPlus,
|
||||||
faTrashAlt,
|
faTrashAlt,
|
||||||
faPencilAlt,
|
faPencilAlt,
|
||||||
faAlignRight
|
faAlignRight,
|
||||||
)
|
)
|
||||||
|
|
||||||
const Attachment = {
|
const Attachment = {
|
||||||
|
|
@ -45,72 +52,77 @@ const Attachment = {
|
||||||
'remove',
|
'remove',
|
||||||
'shiftUp',
|
'shiftUp',
|
||||||
'shiftDn',
|
'shiftDn',
|
||||||
'edit'
|
'edit',
|
||||||
],
|
],
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
localDescription: this.description || this.attachment.description,
|
localDescription: this.description || this.attachment.description,
|
||||||
nsfwImage: this.$store.state.instance.nsfwCensorImage || nsfwImage,
|
nsfwImage:
|
||||||
hideNsfwLocal: this.$store.getters.mergedConfig.hideNsfw,
|
useInstanceStore().instanceIdentity.nsfwCensorImage || nsfwImage,
|
||||||
preloadImage: this.$store.getters.mergedConfig.preloadImage,
|
hideNsfwLocal: useSyncConfigStore().mergedConfig.hideNsfw,
|
||||||
|
preloadImage: useSyncConfigStore().mergedConfig.preloadImage,
|
||||||
loading: false,
|
loading: false,
|
||||||
img: fileTypeService.fileType(this.attachment.mimetype) === 'image' && document.createElement('img'),
|
img:
|
||||||
|
fileTypeService.fileType(this.attachment.mimetype) === 'image' &&
|
||||||
|
document.createElement('img'),
|
||||||
modalOpen: false,
|
modalOpen: false,
|
||||||
showHidden: false,
|
showHidden: false,
|
||||||
flashLoaded: false,
|
flashLoaded: false,
|
||||||
showDescription: false
|
showDescription: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Flash,
|
Flash,
|
||||||
StillImage,
|
StillImage,
|
||||||
VideoAttachment
|
VideoAttachment,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
classNames () {
|
classNames() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'-loading': this.loading,
|
'-loading': this.loading,
|
||||||
'-nsfw-placeholder': this.hidden,
|
'-nsfw-placeholder': this.hidden,
|
||||||
'-editable': this.edit !== undefined,
|
'-editable': this.edit !== undefined,
|
||||||
'-compact': this.compact
|
'-compact': this.compact,
|
||||||
},
|
},
|
||||||
'-type-' + this.type,
|
'-type-' + this.type,
|
||||||
this.size && '-size-' + this.size,
|
this.size && '-size-' + this.size,
|
||||||
`-${this.useContainFit ? 'contain' : 'cover'}-fit`
|
`-${this.useContainFit ? 'contain' : 'cover'}-fit`,
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
usePlaceholder () {
|
usePlaceholder() {
|
||||||
return this.size === 'hide'
|
return this.size === 'hide'
|
||||||
},
|
},
|
||||||
useContainFit () {
|
useContainFit() {
|
||||||
return this.$store.getters.mergedConfig.useContainFit
|
return useSyncConfigStore().mergedConfig.useContainFit
|
||||||
},
|
},
|
||||||
placeholderName () {
|
placeholderName() {
|
||||||
if (this.attachment.description === '' || !this.attachment.description) {
|
if (this.attachment.description === '' || !this.attachment.description) {
|
||||||
return this.type.toUpperCase()
|
return this.type.toUpperCase()
|
||||||
}
|
}
|
||||||
return this.attachment.description
|
return this.attachment.description
|
||||||
},
|
},
|
||||||
placeholderIconClass () {
|
placeholderIconClass() {
|
||||||
if (this.type === 'image') return 'image'
|
if (this.type === 'image') return 'image'
|
||||||
if (this.type === 'video') return 'video'
|
if (this.type === 'video') return 'video'
|
||||||
if (this.type === 'audio') return 'music'
|
if (this.type === 'audio') return 'music'
|
||||||
return 'file'
|
return 'file'
|
||||||
},
|
},
|
||||||
referrerpolicy () {
|
referrerpolicy() {
|
||||||
return this.$store.state.instance.mediaProxyAvailable ? '' : 'no-referrer'
|
return useInstanceCapabilitiesStore().mediaProxyAvailable
|
||||||
|
? ''
|
||||||
|
: 'no-referrer'
|
||||||
},
|
},
|
||||||
type () {
|
type() {
|
||||||
return fileTypeService.fileType(this.attachment.mimetype)
|
return fileTypeService.fileType(this.attachment.mimetype)
|
||||||
},
|
},
|
||||||
hidden () {
|
hidden() {
|
||||||
return this.nsfw && this.hideNsfwLocal && !this.showHidden
|
return this.nsfw && this.hideNsfwLocal && !this.showHidden
|
||||||
},
|
},
|
||||||
isEmpty () {
|
isEmpty() {
|
||||||
return (this.type === 'html' && !this.attachment.oembed)
|
return this.type === 'html' && !this.attachment.oembed
|
||||||
},
|
},
|
||||||
useModal () {
|
useModal() {
|
||||||
let modalTypes = []
|
let modalTypes = []
|
||||||
switch (this.size) {
|
switch (this.size) {
|
||||||
case 'hide':
|
case 'hide':
|
||||||
|
|
@ -125,61 +137,62 @@ const Attachment = {
|
||||||
}
|
}
|
||||||
return modalTypes.includes(this.type)
|
return modalTypes.includes(this.type)
|
||||||
},
|
},
|
||||||
videoTag () {
|
videoTag() {
|
||||||
return this.useModal ? 'button' : 'span'
|
return this.useModal ? 'button' : 'span'
|
||||||
},
|
},
|
||||||
...mapGetters(['mergedConfig'])
|
...mapGetters(['mergedConfig']),
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'attachment.description' (newVal) {
|
'attachment.description'(newVal) {
|
||||||
this.localDescription = newVal
|
this.localDescription = newVal
|
||||||
},
|
},
|
||||||
localDescription (newVal) {
|
localDescription(newVal) {
|
||||||
this.onEdit(newVal)
|
this.onEdit(newVal)
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
linkClicked ({ target }) {
|
linkClicked({ target }) {
|
||||||
if (target.tagName === 'A') {
|
if (target.tagName === 'A') {
|
||||||
window.open(target.href, '_blank')
|
window.open(target.href, '_blank')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openModal (event) {
|
openModal() {
|
||||||
if (this.useModal) {
|
if (this.useModal) {
|
||||||
this.$emit('setMedia')
|
this.$emit('setMedia')
|
||||||
this.$store.dispatch('setCurrentMedia', this.attachment)
|
useMediaViewerStore().setCurrentMedia(this.attachment)
|
||||||
} else if (this.type === 'unknown') {
|
} else if (this.type === 'unknown') {
|
||||||
window.open(this.attachment.url)
|
window.open(this.attachment.url)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openModalForce (event) {
|
openModalForce() {
|
||||||
this.$emit('setMedia')
|
this.$emit('setMedia')
|
||||||
this.$store.dispatch('setCurrentMedia', this.attachment)
|
useMediaViewerStore().setCurrentMedia(this.attachment)
|
||||||
},
|
},
|
||||||
onEdit (event) {
|
onEdit(event) {
|
||||||
this.edit && this.edit(this.attachment, event)
|
this.edit && this.edit(this.attachment, event)
|
||||||
},
|
},
|
||||||
onRemove () {
|
onRemove() {
|
||||||
this.remove && this.remove(this.attachment)
|
this.remove && this.remove(this.attachment)
|
||||||
},
|
},
|
||||||
onShiftUp () {
|
onShiftUp() {
|
||||||
this.shiftUp && this.shiftUp(this.attachment)
|
this.shiftUp && this.shiftUp(this.attachment)
|
||||||
},
|
},
|
||||||
onShiftDn () {
|
onShiftDn() {
|
||||||
this.shiftDn && this.shiftDn(this.attachment)
|
this.shiftDn && this.shiftDn(this.attachment)
|
||||||
},
|
},
|
||||||
stopFlash () {
|
stopFlash() {
|
||||||
this.$refs.flash.closePlayer()
|
this.$refs.flash.closePlayer()
|
||||||
},
|
},
|
||||||
setFlashLoaded (event) {
|
setFlashLoaded(event) {
|
||||||
this.flashLoaded = event
|
this.flashLoaded = event
|
||||||
},
|
},
|
||||||
toggleDescription () {
|
toggleDescription() {
|
||||||
this.showDescription = !this.showDescription
|
this.showDescription = !this.showDescription
|
||||||
},
|
},
|
||||||
toggleHidden (event) {
|
toggleHidden(event) {
|
||||||
if (
|
if (
|
||||||
(this.mergedConfig.useOneClickNsfw && !this.showHidden) &&
|
this.mergedConfig.useOneClickNsfw &&
|
||||||
|
!this.showHidden &&
|
||||||
(this.type !== 'video' || this.mergedConfig.playVideosInModal)
|
(this.type !== 'video' || this.mergedConfig.playVideosInModal)
|
||||||
) {
|
) {
|
||||||
this.openModal(event)
|
this.openModal(event)
|
||||||
|
|
@ -200,12 +213,12 @@ const Attachment = {
|
||||||
this.showHidden = !this.showHidden
|
this.showHidden = !this.showHidden
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onImageLoad (image) {
|
onImageLoad(image) {
|
||||||
const width = image.naturalWidth
|
const width = image.naturalWidth
|
||||||
const height = image.naturalHeight
|
const height = image.naturalHeight
|
||||||
this.$emit('naturalSizeLoad', { id: this.attachment.id, width, height })
|
this.$emit('naturalSizeLoad', { id: this.attachment.id, width, height })
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Attachment
|
export default Attachment
|
||||||
|
|
|
||||||
|
|
@ -107,9 +107,9 @@
|
||||||
|
|
||||||
.play-icon {
|
.play-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-size: 64px;
|
font-size: 4.5em;
|
||||||
top: calc(50% - 32px);
|
top: calc(50% - 2.25rem);
|
||||||
left: calc(50% - 32px);
|
left: calc(50% - 2.25rem);
|
||||||
color: rgb(255 255 255 / 75%);
|
color: rgb(255 255 255 / 75%);
|
||||||
text-shadow: 0 0 2px rgb(0 0 0 / 40%);
|
text-shadow: 0 0 2px rgb(0 0 0 / 40%);
|
||||||
|
|
||||||
|
|
@ -177,7 +177,8 @@
|
||||||
.text {
|
.text {
|
||||||
flex: 2;
|
flex: 2;
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
word-break: break-all;
|
overflow-wrap: break-word;
|
||||||
|
text-wrap: pretty;
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
export default {
|
|
||||||
name: 'Attachment',
|
|
||||||
selector: '.Attachment',
|
|
||||||
validInnerComponents: [
|
|
||||||
'Border',
|
|
||||||
'ButtonUnstyled',
|
|
||||||
'Input'
|
|
||||||
],
|
|
||||||
defaultRules: [
|
|
||||||
{
|
|
||||||
directives: {
|
|
||||||
roundness: 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: 'ButtonUnstyled',
|
|
||||||
parent: { component: 'Attachment' },
|
|
||||||
directives: {
|
|
||||||
background: '#FFFFFF',
|
|
||||||
opacity: 0.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
v-if="usePlaceholder"
|
v-if="usePlaceholder"
|
||||||
class="Attachment -placeholder button-unstyled"
|
class="Attachment -placeholder button-default"
|
||||||
:class="classNames"
|
:class="classNames"
|
||||||
@click="openModal"
|
@click="openModal"
|
||||||
>
|
>
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="remove"
|
v-if="remove"
|
||||||
class="button-unstyled attachment-button"
|
class="button-default attachment-button -transparent"
|
||||||
@click.prevent="onRemove"
|
@click.prevent="onRemove"
|
||||||
>
|
>
|
||||||
<FAIcon icon="trash-alt" />
|
<FAIcon icon="trash-alt" />
|
||||||
|
|
@ -81,7 +81,7 @@
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="type === 'flash' && flashLoaded"
|
v-if="type === 'flash' && flashLoaded"
|
||||||
class="button-unstyled attachment-button"
|
class="button-default attachment-button -transparent"
|
||||||
:title="$t('status.attachment_stop_flash')"
|
:title="$t('status.attachment_stop_flash')"
|
||||||
@click.prevent="stopFlash"
|
@click.prevent="stopFlash"
|
||||||
>
|
>
|
||||||
|
|
@ -89,7 +89,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="attachment.description && size !== 'small' && !edit && type !== 'unknown'"
|
v-if="attachment.description && size !== 'small' && !edit && type !== 'unknown'"
|
||||||
class="button-unstyled attachment-button"
|
class="button-default attachment-button -transparent"
|
||||||
:title="$t('status.show_attachment_description')"
|
:title="$t('status.show_attachment_description')"
|
||||||
@click.prevent="toggleDescription"
|
@click.prevent="toggleDescription"
|
||||||
>
|
>
|
||||||
|
|
@ -97,7 +97,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="!useModal && type !== 'unknown'"
|
v-if="!useModal && type !== 'unknown'"
|
||||||
class="button-unstyled attachment-button"
|
class="button-default attachment-button -transparent"
|
||||||
:title="$t('status.show_attachment_in_modal')"
|
:title="$t('status.show_attachment_in_modal')"
|
||||||
@click.prevent="openModalForce"
|
@click.prevent="openModalForce"
|
||||||
>
|
>
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="nsfw && hideNsfwLocal"
|
v-if="nsfw && hideNsfwLocal"
|
||||||
class="button-unstyled attachment-button"
|
class="button-default attachment-button -transparent"
|
||||||
:title="$t('status.hide_attachment')"
|
:title="$t('status.hide_attachment')"
|
||||||
@click.prevent="toggleHidden"
|
@click.prevent="toggleHidden"
|
||||||
>
|
>
|
||||||
|
|
@ -113,7 +113,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="shiftUp"
|
v-if="shiftUp"
|
||||||
class="button-unstyled attachment-button"
|
class="button-default attachment-button -transparent"
|
||||||
:title="$t('status.move_up')"
|
:title="$t('status.move_up')"
|
||||||
@click.prevent="onShiftUp"
|
@click.prevent="onShiftUp"
|
||||||
>
|
>
|
||||||
|
|
@ -121,7 +121,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="shiftDn"
|
v-if="shiftDn"
|
||||||
class="button-unstyled attachment-button"
|
class="button-default attachment-button -transparent"
|
||||||
:title="$t('status.move_down')"
|
:title="$t('status.move_down')"
|
||||||
@click.prevent="onShiftDn"
|
@click.prevent="onShiftDn"
|
||||||
>
|
>
|
||||||
|
|
@ -129,7 +129,7 @@
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="remove"
|
v-if="remove"
|
||||||
class="button-unstyled attachment-button"
|
class="button-default attachment-button -transparent"
|
||||||
:title="$t('status.remove_attachment')"
|
:title="$t('status.remove_attachment')"
|
||||||
@click.prevent="onRemove"
|
@click.prevent="onRemove"
|
||||||
>
|
>
|
||||||
|
|
@ -238,8 +238,8 @@
|
||||||
ref="flash"
|
ref="flash"
|
||||||
class="flash"
|
class="flash"
|
||||||
:src="attachment.large_thumb_url || attachment.url"
|
:src="attachment.large_thumb_url || attachment.url"
|
||||||
@playerOpened="setFlashLoaded(true)"
|
@player-opened="setFlashLoaded(true)"
|
||||||
@playerClosed="setFlashLoaded(false)"
|
@player-closed="setFlashLoaded(false)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,34 @@
|
||||||
|
import { mapState } from 'pinia'
|
||||||
import { h, resolveComponent } from 'vue'
|
import { h, resolveComponent } from 'vue'
|
||||||
|
|
||||||
import LoginForm from '../login_form/login_form.vue'
|
import LoginForm from '../login_form/login_form.vue'
|
||||||
import MFARecoveryForm from '../mfa_form/recovery_form.vue'
|
import MFARecoveryForm from '../mfa_form/recovery_form.vue'
|
||||||
import MFATOTPForm from '../mfa_form/totp_form.vue'
|
import MFATOTPForm from '../mfa_form/totp_form.vue'
|
||||||
import { mapGetters } from 'vuex'
|
|
||||||
|
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
|
||||||
|
|
||||||
const AuthForm = {
|
const AuthForm = {
|
||||||
name: 'AuthForm',
|
name: 'AuthForm',
|
||||||
render () {
|
render() {
|
||||||
return h(resolveComponent(this.authForm))
|
return h(resolveComponent(this.authForm))
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
authForm () {
|
authForm() {
|
||||||
if (this.requiredTOTP) { return 'MFATOTPForm' }
|
if (this.requiredTOTP) {
|
||||||
if (this.requiredRecovery) { return 'MFARecoveryForm' }
|
return 'MFATOTPForm'
|
||||||
|
}
|
||||||
|
if (this.requiredRecovery) {
|
||||||
|
return 'MFARecoveryForm'
|
||||||
|
}
|
||||||
return 'LoginForm'
|
return 'LoginForm'
|
||||||
},
|
},
|
||||||
...mapGetters('authFlow', ['requiredTOTP', 'requiredRecovery'])
|
...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery']),
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
MFARecoveryForm,
|
MFARecoveryForm,
|
||||||
MFATOTPForm,
|
MFATOTPForm,
|
||||||
LoginForm
|
LoginForm,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AuthForm
|
export default AuthForm
|
||||||
|
|
|
||||||
|
|
@ -2,51 +2,55 @@ const debounceMilliseconds = 500
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
query: { // function to query results and return a promise
|
query: {
|
||||||
|
// function to query results and return a promise
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
filter: { // function to filter results in real time
|
filter: {
|
||||||
type: Function
|
// function to filter results in real time
|
||||||
|
type: Function,
|
||||||
},
|
},
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'Search...'
|
default: 'Search...',
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
data () {
|
data() {
|
||||||
return {
|
return {
|
||||||
term: '',
|
term: '',
|
||||||
timeout: null,
|
timeout: null,
|
||||||
results: [],
|
results: [],
|
||||||
resultsVisible: false
|
resultsVisible: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filtered () {
|
filtered() {
|
||||||
return this.filter ? this.filter(this.results) : this.results
|
return this.filter ? this.filter(this.results) : this.results
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
term (val) {
|
term(val) {
|
||||||
this.fetchResults(val)
|
this.fetchResults(val)
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchResults (term) {
|
fetchResults(term) {
|
||||||
clearTimeout(this.timeout)
|
clearTimeout(this.timeout)
|
||||||
this.timeout = setTimeout(() => {
|
this.timeout = setTimeout(() => {
|
||||||
this.results = []
|
this.results = []
|
||||||
if (term) {
|
if (term) {
|
||||||
this.query(term).then((results) => { this.results = results })
|
this.query(term).then((results) => {
|
||||||
|
this.results = results
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}, debounceMilliseconds)
|
}, debounceMilliseconds)
|
||||||
},
|
},
|
||||||
onInputClick () {
|
onInputClick() {
|
||||||
this.resultsVisible = true
|
this.resultsVisible = true
|
||||||
},
|
},
|
||||||
onClickOutside () {
|
onClickOutside() {
|
||||||
this.resultsVisible = false
|
this.resultsVisible = false
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,28 @@
|
||||||
import UserAvatar from '../user_avatar/user_avatar.vue'
|
import UserAvatar from '../user_avatar/user_avatar.vue'
|
||||||
|
|
||||||
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
|
|
||||||
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
|
||||||
|
|
||||||
const AvatarList = {
|
const AvatarList = {
|
||||||
props: ['users'],
|
props: ['users'],
|
||||||
computed: {
|
computed: {
|
||||||
slicedUsers () {
|
slicedUsers() {
|
||||||
return this.users ? this.users.slice(0, 15) : []
|
return this.users ? this.users.slice(0, 15) : []
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
UserAvatar
|
UserAvatar,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
userProfileLink (user) {
|
userProfileLink(user) {
|
||||||
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
|
return generateProfileLink(
|
||||||
}
|
user.id,
|
||||||
}
|
user.screen_name,
|
||||||
|
useInstanceStore().restrictedNicknames,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AvatarList
|
export default AvatarList
|
||||||
|
|
|
||||||