Compare commits
780 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77dbccfc7e | ||
|
|
b80f4bd053 | ||
|
|
14e1f1f935 | ||
|
|
527221462b | ||
|
|
009836647b | ||
|
|
8b25af88e0 | ||
|
|
de2cde4541 | ||
|
|
8240668a5c | ||
|
|
8a79e063e8 | ||
|
|
fdc0246c0a | ||
|
|
4cb20539fd | ||
|
|
06d02c42f1 | ||
|
|
660e4c34d9 | ||
|
|
d17d008d7e | ||
|
|
c5799fb986 | ||
|
|
39f49aa54c | ||
|
|
abd87fafd2 | ||
|
|
b462e42d33 | ||
|
|
2c4ea3ebad | ||
|
|
389ad9e5ec | ||
|
|
0d8fc89bf5 | ||
|
|
ce0781536c | ||
|
|
58a6de3a5d | ||
|
|
eb3a44a759 | ||
|
|
b0f7eb1922 | ||
|
|
1a6a5828dc | ||
|
|
76375ad02a | ||
|
|
ddca0f3038 | ||
|
|
4fe4efd95a | ||
|
|
833b323267 | ||
|
|
c1b1a9de4c | ||
|
|
72b0d4bbac | ||
|
|
91199d3460 | ||
|
|
c04c83c22a | ||
|
|
82e169150e | ||
|
|
1bd272ec8d | ||
|
|
1616fa21f2 | ||
|
|
920d9f198f | ||
|
|
f82c2de301 | ||
|
|
1e0508ac85 | ||
|
|
84f8605269 | ||
|
|
ef0f7055a0 | ||
|
|
5b578806c7 | ||
|
|
b06c338c20 | ||
|
|
9c3ffcc58e | ||
|
|
3ca47ce108 | ||
|
|
702bc64bc3 | ||
|
|
b34b24e783 | ||
|
|
5bd87033e2 | ||
|
|
927b68dab7 | ||
|
|
5aa0169e81 | ||
|
|
9ff01d1d7d | ||
|
|
16f1976e28 | ||
|
|
3d4a8f43aa | ||
|
|
5b335d2bff | ||
|
|
8736491818 | ||
|
|
037a2bf8c3 | ||
|
|
fcbf8d2e3a | ||
|
|
cc8a8bf801 | ||
|
|
bab09be364 | ||
|
|
7a0167f31b | ||
|
|
47fb761c71 | ||
|
|
18980488c6 | ||
|
|
3e844696b0 | ||
|
|
4bbe97f887 | ||
|
|
a9b94c27d4 | ||
|
|
d2b05a1b1b | ||
|
|
a03f7d111b | ||
|
|
87ce59dab6 | ||
|
|
77ec7e5a56 | ||
|
|
6de99225c4 | ||
|
|
6c0cbf2737 | ||
|
|
b0fca23a20 | ||
|
|
96a904db0d | ||
|
|
41c109416f | ||
|
|
208b37ce75 | ||
|
|
9bd33020ae | ||
|
|
e506f5e37e | ||
|
|
89179de230 | ||
|
|
d235862eb2 | ||
|
|
2226483ed3 | ||
|
|
ed7455985a | ||
|
|
42a9018e87 | ||
|
|
2e9584e39e | ||
|
|
4ab3de409e | ||
|
|
aab156560f | ||
|
|
3e09ab61a5 | ||
|
|
cad7ad2ceb | ||
|
|
af5352eba2 | ||
|
|
20cb82ec7b | ||
|
|
c9e92d92f8 | ||
|
|
9bc00baa38 | ||
|
|
0d1d6e5490 | ||
|
|
32a7cc3b7c | ||
|
|
35f212056f | ||
|
|
70bca2737c | ||
|
|
3d57ffe3c2 | ||
|
|
267432bb28 | ||
|
|
9a97b7937e | ||
|
|
0c2011f070 | ||
|
|
d662a38ac7 | ||
|
|
826d37a374 | ||
|
|
c1b9432a2b | ||
|
|
366eca019d | ||
|
|
fcf0bbb1d9 | ||
|
|
2519ed3d8c | ||
|
|
feb8f6d0f5 | ||
|
|
8a1a8abae7 | ||
|
|
105b58a5c4 | ||
|
|
382d5d0030 | ||
|
|
50a33d4f8d | ||
|
|
e0899932b1 | ||
|
|
b077371d1f | ||
|
|
037d9cf26c | ||
|
|
064a868d62 | ||
|
|
bf52d776ff | ||
|
|
fc1a07b129 | ||
|
|
19a3856768 | ||
|
|
fc414951d1 | ||
|
|
835cb237d7 | ||
|
|
af9494ecce | ||
|
|
eabf4254b1 | ||
|
|
e05f146b28 | ||
|
|
a0cd959a93 | ||
|
|
4e398bc9fd | ||
|
|
0eb1582f7d | ||
|
|
c5124db29f | ||
|
|
e2566121d9 | ||
|
|
2dac102f3e | ||
|
|
26255f8995 | ||
|
|
bc23ccc1aa | ||
|
|
b2ca3f6c0b | ||
|
|
fc2f38543c | ||
|
|
154eda0aa2 | ||
|
|
095908302f | ||
|
|
30509de370 | ||
|
|
0fae05e33a | ||
|
|
5dc222a0df | ||
|
|
202b9946e2 | ||
|
|
5d4397d38b | ||
|
|
7a0695ac9c | ||
|
|
ff7eb81e87 | ||
|
|
4d4ebf2d09 | ||
|
|
aae0bd2420 | ||
|
|
952337be6e | ||
|
|
fff4d0b286 | ||
|
|
4f1a8960f1 | ||
|
|
4c35560f6c | ||
|
|
b456823df3 | ||
|
|
4cbc02a2d3 | ||
|
|
bd39e6fba7 | ||
|
|
d9d7e79f0e | ||
|
|
b4e4252d5c | ||
|
|
ab37a135af | ||
|
|
bd28f8590b | ||
|
|
162a2fbac3 | ||
|
|
4d66649ff8 | ||
|
|
a2e67033c8 | ||
|
|
d6ff02f284 | ||
|
|
187859b1c9 | ||
|
|
4887562cc7 | ||
|
|
643fbbe90d | ||
|
|
5c45c17992 | ||
|
|
7616e93dae | ||
|
|
155051c5fe | ||
|
|
82edb6b284 | ||
|
|
c18f175b08 | ||
|
|
7d325d424a | ||
|
|
6d2ab6e0f9 | ||
|
|
71ec6f76cc | ||
|
|
a385089fe8 | ||
|
|
05051ea9f9 | ||
|
|
d495678ac2 | ||
|
|
06dbe99cae | ||
|
|
0b5bc42587 | ||
|
|
35680e719b | ||
|
|
620ad25c61 | ||
|
|
deda273eda | ||
|
|
fd8aaf543e | ||
|
|
8cb2513610 | ||
|
|
a68f0c7e42 | ||
|
|
c63662f602 | ||
|
|
6b227f241b | ||
|
|
1a9e4097fb | ||
|
|
f16ae279e2 | ||
|
|
c4d2c5b408 | ||
|
|
70bff6156a | ||
|
|
5fe93963f3 | ||
|
|
2425f07d6b | ||
|
|
66ba178ea7 | ||
|
|
5f8c941d0b | ||
|
|
f6026c6977 | ||
|
|
a10592a031 | ||
|
|
e571cc301c | ||
|
|
ce5c4e5345 | ||
|
|
3fd603667d | ||
|
|
df96ced6d9 | ||
|
|
8f1feb8cff | ||
|
|
8d726133bc | ||
|
|
10992aad3c | ||
|
|
88b24b55e6 | ||
|
|
22b0c1ebf9 | ||
|
|
ac3154e334 | ||
|
|
d313ac55d9 | ||
|
|
e96af1f6fb | ||
|
|
f0c9b7eb82 | ||
|
|
c654694603 | ||
|
|
8817a12ede | ||
|
|
b38dcc09aa | ||
|
|
dcbde420a4 | ||
|
|
c8a07a5777 | ||
|
|
9a2306a9a8 | ||
|
|
b716dc5b35 | ||
|
|
4a4a430b47 | ||
|
|
c5a73a13ea | ||
|
|
3511d7b32f | ||
|
|
6854ba70e2 | ||
|
|
b5e9bdb1d5 | ||
|
|
68b1b4c2e2 | ||
|
|
9b6caebfa6 | ||
|
|
b3947909a0 | ||
|
|
709c2f3f7b | ||
|
|
344fbdea9f | ||
|
|
26fb0486fd | ||
|
|
c9a02c2351 | ||
|
|
e0805868a6 | ||
|
|
69a86b5a14 | ||
|
|
e78eb63e4e | ||
|
|
948611f434 | ||
|
|
52d47b4247 | ||
|
|
6c6452c126 | ||
|
|
3b00a749b5 | ||
|
|
5ae0aad1d6 | ||
|
|
1979bae370 | ||
|
|
bdea8efb53 | ||
|
|
315204e5d7 | ||
|
|
053f2c1e66 | ||
|
|
088071fa38 | ||
|
|
607adcf737 | ||
|
|
d3b3f1f676 | ||
|
|
441248f737 | ||
|
|
ad195b22db | ||
|
|
99efa86c58 | ||
|
|
c8a88b837f | ||
|
|
a2b21a141a | ||
|
|
634c88d8d1 | ||
|
|
a38f1b20ec | ||
|
|
6014409d3d | ||
|
|
ee0306cda8 | ||
|
|
5c492410be | ||
|
|
e606fc9985 | ||
|
|
471dca4661 | ||
|
|
5d6b7b74c4 | ||
|
|
e91a913b62 | ||
|
|
f8a6d2bc3d | ||
|
|
0738579074 | ||
|
|
7909244e95 | ||
|
|
7f6b5638de | ||
|
|
68a8be1427 | ||
|
|
0262ee9ff0 | ||
|
|
721fa7bcda | ||
|
|
98cf07b676 | ||
|
|
2b7380ba57 | ||
|
|
6162b3e27f | ||
|
|
8d7cc2cec1 | ||
|
|
e52c45b345 | ||
|
|
ed919b2f21 | ||
|
|
781a31d3bb | ||
|
|
d82bee3150 | ||
|
|
9c01c9f65c | ||
|
|
a341b1dcbe | ||
|
|
776507703a | ||
|
|
215d24252e | ||
|
|
fd2b79a884 | ||
|
|
ab73ec684c | ||
|
|
4da0e8675f | ||
|
|
5ba3cf247a | ||
|
|
50ba1b5ecb | ||
|
|
981c5bee82 | ||
|
|
5f50b5c4ce | ||
|
|
eda3b464d8 | ||
|
|
b12c75fe21 | ||
|
|
89b93549cb | ||
|
|
4a47b5c987 | ||
|
|
426c86a451 | ||
|
|
7c88e0bc5d | ||
|
|
86793fd675 | ||
|
|
3121dd8e5d | ||
|
|
d3d4736bae | ||
|
|
65d104538a | ||
|
|
b4dba4d5bb | ||
|
|
19635704c8 | ||
|
|
c2268be997 | ||
|
|
e0282962d6 | ||
|
|
06ce2d0177 | ||
|
|
3833559cec | ||
|
|
cb9b15fccc | ||
|
|
e6736db056 | ||
|
|
0073a1121f | ||
|
|
82ca99aa5d | ||
|
|
6b5e863461 | ||
|
|
7117e67e0a | ||
|
|
217d3a40db | ||
|
|
d7bb56b815 | ||
|
|
c0272a798d | ||
|
|
30efd8c990 | ||
|
|
f7e596c628 | ||
|
|
94236a36dc | ||
|
|
bd4d20ba38 | ||
|
|
7201d7120e | ||
|
|
b152154cf0 | ||
|
|
aba3153c4f | ||
|
|
a6473f3e77 | ||
|
|
eb94911a4d | ||
|
|
142767738a | ||
|
|
5bd5e07169 | ||
|
|
e70f77819c | ||
|
|
9e4f84cf09 | ||
|
|
9abb1dfc97 | ||
|
|
a48af7d00e | ||
|
|
07f6c70dbe | ||
|
|
237c783382 | ||
|
|
12a7ac0b06 | ||
|
|
1ee0a620a7 | ||
|
|
460aa64634 | ||
|
|
0e6ee114e5 | ||
|
|
e9a88bc1cd | ||
|
|
fd8424242e | ||
|
|
0ffd145633 | ||
|
|
c5d60be069 | ||
|
|
657d597d84 | ||
|
|
c2c4116e58 | ||
|
|
2f3beca70b | ||
|
|
a46908389c | ||
|
|
218ab89113 | ||
|
|
8c895090c8 | ||
|
|
82f4bd1c63 | ||
|
|
8d63bf5e11 | ||
|
|
b47e0f4ec7 | ||
|
|
307eedaca1 | ||
|
|
c1b1f92b80 | ||
|
|
817069ae2e | ||
|
|
9b2a85bb4c | ||
|
|
4ba5a7215a | ||
|
|
17a05d4461 | ||
|
|
c7b158fd57 | ||
|
|
8b99bcc6a8 | ||
|
|
c4a3741542 | ||
|
|
445e0b0acd | ||
|
|
63ad35ce18 | ||
|
|
90e797be05 | ||
|
|
e23657144a | ||
|
|
750dac8d9d | ||
|
|
420cacd8c9 | ||
|
|
529f943646 | ||
|
|
7e44d6cdb2 | ||
|
|
5154da62ab | ||
|
|
c9ce4b5d1a | ||
|
|
9357f2ed11 | ||
|
|
3e77fdc4be | ||
|
|
6a655c48d1 | ||
|
|
10fa42830a | ||
|
|
342e8e264f | ||
|
|
f92b059604 | ||
|
|
d2dff8ce09 | ||
|
|
5692dd9909 | ||
|
|
799e8979b2 | ||
|
|
3ab4394376 | ||
|
|
bb26ff2e98 | ||
|
|
df25d7882d | ||
|
|
a3094b3923 | ||
|
|
11dce272a4 | ||
|
|
c55f67ad58 | ||
|
|
56ed5a9455 | ||
|
|
441ab183b7 | ||
|
|
b8bc307fe7 | ||
|
|
bf24bf53c0 | ||
|
|
c5e05997dd | ||
|
|
f7a49e47bf | ||
|
|
7b0d3b710a | ||
|
|
d3282c7212 | ||
|
|
35cb912e28 | ||
|
|
2b0292a614 | ||
|
|
e7fcc91133 | ||
|
|
e391ea693f | ||
|
|
6c1ce96036 | ||
|
|
8f531aff11 | ||
|
|
69823802f5 | ||
|
|
af88c5475b | ||
|
|
fd4c4f6fcb | ||
|
|
69a2cd5b61 | ||
|
|
289cd7ccec | ||
|
|
be8dc47f29 | ||
|
|
fb682c799d | ||
|
|
ba3d83e09c | ||
|
|
de6b155c55 | ||
|
|
c14c7971aa | ||
|
|
9d9432bfd5 | ||
|
|
8fc5553b52 | ||
|
|
0813d6bfd9 | ||
|
|
d36fc0fceb | ||
|
|
521c2b8cdf | ||
|
|
d213843bbf | ||
|
|
ef06e6e6a3 | ||
|
|
9593a0e46b | ||
|
|
1c54765c77 | ||
|
|
ad3d231d16 | ||
|
|
d5c720775f | ||
|
|
eeed30dd32 | ||
|
|
fbff40d7f8 | ||
|
|
2c22a6afc6 | ||
|
|
666ca92547 | ||
|
|
a62534618b | ||
|
|
b38a597b7e | ||
|
|
060d69e652 | ||
|
|
c2fe2b980f | ||
|
|
26ff6820f0 | ||
|
|
476c65a82c | ||
|
|
14d1a9fb54 | ||
|
|
682c892f53 | ||
|
|
4ea772ba18 | ||
|
|
7d483c4aab | ||
|
|
e098f1eb08 | ||
|
|
9c06cb2962 | ||
|
|
cc429fd8cb | ||
|
|
6c6c6626cf | ||
|
|
5c632642d4 | ||
|
|
79d65953a7 | ||
|
|
c821d86362 | ||
|
|
52f901c6c4 | ||
|
|
c18c2267b0 | ||
|
|
5caba447ab | ||
|
|
38b404d453 | ||
|
|
75beb0e282 | ||
|
|
400070dcb5 | ||
|
|
a08add1bfa | ||
|
|
323086c19d | ||
|
|
53d6c486ef | ||
|
|
a98fe44063 | ||
|
|
beefe7ab44 | ||
|
|
38231f4eaa | ||
|
|
9bcfb359c5 | ||
|
|
d636022898 | ||
|
|
c27ccc043c | ||
|
|
d8a975d531 | ||
|
|
270a87d03a | ||
|
|
dc32e1d142 | ||
|
|
85b933e339 | ||
|
|
61da89c1a9 | ||
|
|
59bad794e5 | ||
|
|
c920eac3fe | ||
|
|
d0ef235c24 | ||
|
|
ddc2f47f97 | ||
|
|
f45fe3e089 | ||
|
|
c82da4a4e7 | ||
|
|
cece5aea91 | ||
|
|
89229d95e5 | ||
|
|
ccb9f7e6e9 | ||
|
|
5943accc9f | ||
|
|
996bb4cdc9 | ||
|
|
bf85598b21 | ||
|
|
326e196bc3 | ||
|
|
69a478006a | ||
|
|
5088a33f14 | ||
|
|
dbe076e801 | ||
|
|
3058151d55 | ||
|
|
4b3ddcab1b | ||
|
|
3f3afd9307 | ||
|
|
a1b1dfd99a | ||
|
|
146ef7dc21 | ||
|
|
dd988979cf | ||
|
|
979afeedff | ||
|
|
8b76d26555 | ||
|
|
e4e7a69280 | ||
|
|
e9b95c8e49 | ||
|
|
cd2ef3d630 | ||
|
|
6c46bea1bd | ||
|
|
dd0dd77898 | ||
|
|
7fc56a5f71 | ||
|
|
e02c7c7732 | ||
|
|
afd295a276 | ||
|
|
1acf4544e3 | ||
|
|
1d00f21500 | ||
|
|
94cc720ee4 | ||
|
|
de2de61642 | ||
|
|
6b034d0eeb | ||
|
|
ec5aa81092 | ||
|
|
cb934dba31 | ||
|
|
c4007c8436 | ||
|
|
0c6bdf66f0 | ||
|
|
4603ee4fa5 | ||
|
|
9203e93d80 | ||
|
|
d33e0df2b4 | ||
|
|
79fca20943 | ||
|
|
962dc1b52e | ||
|
|
a934cfc4fd | ||
|
|
afbcb52286 | ||
|
|
b4c759eded | ||
|
|
53912cf730 | ||
|
|
bfab819827 | ||
|
|
869602ce00 | ||
|
|
92d234ce01 | ||
|
|
00c482aac5 | ||
|
|
aa86e2a4d1 | ||
|
|
3ba2d8ba06 | ||
|
|
ab9d7f8096 | ||
|
|
6937e7c993 | ||
|
|
125878676f | ||
|
|
1b750df0f1 | ||
|
|
ebb6a436a6 | ||
|
|
2ea3f4e0f7 | ||
|
|
5ed9819c4f | ||
|
|
89064c7e15 | ||
|
|
554dd33007 | ||
|
|
a359d5ec21 | ||
|
|
f374a77881 | ||
|
|
5377d71c1f | ||
|
|
559211ea2c | ||
|
|
c8e8fe6311 | ||
|
|
610e03a974 | ||
|
|
b81e686fc2 | ||
|
|
bc73f5d3f3 | ||
|
|
4f8f18f0ff | ||
|
|
1d360430a6 | ||
|
|
fc6c4a1c6d | ||
|
|
949e922606 | ||
|
|
4f7ed25e99 | ||
|
|
e14f84d1bb | ||
|
|
04cf73e84a | ||
|
|
5fdd61587e | ||
|
|
5c256323dc | ||
|
|
5e71f49325 | ||
|
|
24068f354d | ||
|
|
c954fab097 | ||
|
|
6db77d4785 | ||
|
|
7c295529ac | ||
|
|
1c48eb2a65 | ||
|
|
1b2f57e7d0 | ||
|
|
03130a415d | ||
|
|
4281edb7a7 | ||
|
|
2520dbfa44 | ||
|
|
99aa37675e | ||
|
|
5ba1c539ce | ||
|
|
d51b9bfc25 | ||
|
|
4d7c8449d8 | ||
|
|
78635d2671 | ||
|
|
c4debda932 | ||
|
|
76163c96d3 | ||
|
|
38a4e86eff | ||
|
|
196e19714d | ||
|
|
9a7991c982 | ||
|
|
1aa4da81cf | ||
|
|
3821c227da | ||
|
|
c771252e78 | ||
|
|
2b1e62c830 | ||
|
|
1865161d73 | ||
|
|
950c60ba62 | ||
|
|
b5b1a2848a | ||
|
|
6372debdff | ||
|
|
b0ed0359a9 | ||
|
|
198fb1ea5e | ||
|
|
f36e0c328d | ||
|
|
935f029485 | ||
|
|
ad67368a45 | ||
|
|
3a9024d1e0 | ||
|
|
6235ebfe24 | ||
|
|
e56d7d678d | ||
|
|
83e67be465 | ||
|
|
6a84dac2f8 | ||
|
|
a1a0d22524 | ||
|
|
0e995129e4 | ||
|
|
34bfa8bbfc | ||
|
|
34acb03fb6 | ||
|
|
05d56020f0 | ||
|
|
f11c377294 | ||
|
|
5f27a4bc92 | ||
|
|
e029578798 | ||
|
|
d16d842fa3 | ||
|
|
20feb65b80 | ||
|
|
da7edfe3e4 | ||
|
|
db2b7a6c2f | ||
|
|
6d99559768 | ||
|
|
0738ec8547 | ||
|
|
e74459ecb9 | ||
|
|
c28d823d7c | ||
|
|
d72028ce78 | ||
|
|
5270ad40ab | ||
|
|
ae499f760c | ||
|
|
c7dde37754 | ||
|
|
4f67411488 | ||
|
|
e1064e82ad | ||
|
|
765f27f4f3 | ||
|
|
b499f73db6 | ||
|
|
aa095c4cac | ||
|
|
4f13c42b6e | ||
|
|
af18beb134 | ||
|
|
83fb885e8b | ||
|
|
a8ed8e55c2 | ||
|
|
4e03679c70 | ||
|
|
e8dc26de91 | ||
|
|
9e2b7993a8 | ||
|
|
0f7297aa70 | ||
|
|
fe85a1c33a | ||
|
|
2358e327b4 | ||
|
|
90a858d1e3 | ||
|
|
83f1083d75 | ||
|
|
8a26062442 | ||
|
|
83d85a037e | ||
|
|
6c5a0f33ae | ||
|
|
9ddc933d7e | ||
|
|
50b1050b20 | ||
|
|
396e1f7233 | ||
|
|
07b205ee31 | ||
|
|
186d24b1b1 | ||
|
|
118c3d8949 | ||
|
|
4aae71dbdb | ||
|
|
2fe742f3f0 | ||
|
|
99b08aff08 | ||
|
|
6a76eee11f | ||
|
|
ea8bcdfc72 | ||
|
|
2f86f1c004 | ||
|
|
25c4a1e9ce | ||
|
|
226560dc52 | ||
|
|
9f89ba3c48 | ||
|
|
aa2edd9ab6 | ||
|
|
910e20575d | ||
|
|
0ff7c2c385 | ||
|
|
d8c3eb8380 | ||
|
|
0e113e57e3 | ||
|
|
cb8d6ff7c5 | ||
|
|
d98d988b7b | ||
|
|
533376a592 | ||
|
|
57c7cda58b | ||
|
|
bb68711a48 | ||
|
|
ddfb2ec900 | ||
|
|
71d65f2cda | ||
|
|
8a7b7d69a7 | ||
|
|
9256abebb1 | ||
|
|
7c4c9bcfa5 | ||
|
|
7540494cb2 | ||
|
|
a448293c61 | ||
|
|
1866eec91a | ||
|
|
526e781823 | ||
|
|
679ee2f17e | ||
|
|
b81147ed0c | ||
|
|
0e97d0bba1 | ||
|
|
47826c87f0 | ||
|
|
3c78a889cf | ||
|
|
995049f118 | ||
|
|
0b9dde7240 | ||
|
|
9d09efef33 | ||
|
|
2f99b5bdbc | ||
|
|
5cf7555b11 | ||
|
|
984eba8f13 | ||
|
|
8c7e014ad9 | ||
|
|
6594005640 | ||
|
|
4c36b8ec87 | ||
|
|
762a2891ab | ||
|
|
ec2668d2cb | ||
|
|
e566a869d1 | ||
|
|
4e6f751874 | ||
|
|
4b3b9cde50 | ||
|
|
1e33f82b51 | ||
|
|
4957cd27ec | ||
|
|
0e32994bb9 | ||
|
|
e2d90fbf5b | ||
|
|
4a3b7775ee | ||
|
|
9dd7badad5 | ||
|
|
3bc8f31a0b | ||
|
|
6c7d34b59b | ||
|
|
ac35b7a61c | ||
|
|
4836de517c | ||
|
|
b721bce67b | ||
|
|
f02a8802fc | ||
|
|
7adf76df16 | ||
|
|
ae0085106b | ||
|
|
d937871cf6 | ||
|
|
110ebfe99b | ||
|
|
3c48bc02f8 | ||
|
|
afe883a31a | ||
|
|
050c8f6899 | ||
|
|
0fcc6ad107 | ||
|
|
db118d95db | ||
|
|
0c0120d522 | ||
|
|
604bdcc75f | ||
|
|
ecc3ab8a08 | ||
|
|
1b99f0f281 | ||
|
|
3713e8d134 | ||
|
|
5dcb0e72c1 | ||
|
|
c44622d9b0 | ||
|
|
059526d961 | ||
|
|
d2a5ff1ace | ||
|
|
180fa64420 | ||
|
|
8603734c7a | ||
|
|
065f5c18e7 | ||
|
|
6f8cf69e8c | ||
|
|
f517b6838b | ||
|
|
c3557c5f8d | ||
|
|
590572bbe2 | ||
|
|
b807362823 | ||
|
|
9de8d5fa48 | ||
|
|
b6581f3c62 | ||
|
|
002c3a18cc | ||
|
|
da9527c662 | ||
|
|
62a63f42d5 | ||
|
|
20b45caba9 | ||
|
|
326d59bff2 | ||
|
|
ec2807105a | ||
|
|
611772c5fd | ||
|
|
705c60a664 | ||
|
|
7419f3bc83 | ||
|
|
08afaff9f9 | ||
|
|
512ecbf44b | ||
|
|
6376a1da6a | ||
|
|
5dfba8b29d | ||
|
|
dbd8d8ab60 | ||
|
|
24772eb6c0 | ||
|
|
4235be8e02 | ||
|
|
3288b37be7 | ||
|
|
c965b8de27 | ||
|
|
0b5a4c8bbc | ||
|
|
3953acdad0 | ||
|
|
6bbdaa921c | ||
|
|
0b89fa5ec1 | ||
|
|
e02c89527d | ||
|
|
1f8ee54615 | ||
|
|
18b708ab15 | ||
|
|
439a7b088a | ||
|
|
7f197e3316 | ||
|
|
dd3470259c | ||
|
|
8061f621f2 | ||
|
|
e6ea7b32f8 | ||
|
|
5ce75560f7 | ||
|
|
2fa8667d5f | ||
|
|
8267cc1351 | ||
|
|
562c5e503f | ||
|
|
f600ee2554 | ||
|
|
84105fe4ba | ||
|
|
619388686e | ||
|
|
669b6334c9 | ||
|
|
9ddbb1fa41 | ||
|
|
d5bc79f198 | ||
|
|
3675f1169e | ||
|
|
e7c14465af | ||
|
|
dad9f18bf5 | ||
|
|
bfa98aaac1 | ||
|
|
9657a61b0c | ||
|
|
fb3faa75f5 | ||
|
|
0fe42ca3e0 | ||
|
|
7ee142a7ed | ||
|
|
516f72a8b7 | ||
|
|
242c18e682 | ||
|
|
0409f444d0 | ||
|
|
f7e10eef5a | ||
|
|
05fc45512a | ||
|
|
767b1d7075 | ||
|
|
4bc3ff47d1 | ||
|
|
255e39e95f | ||
|
|
b1441fa647 | ||
|
|
38dde4ed38 | ||
|
|
bf0c2fb307 | ||
|
|
d19545c2c6 | ||
|
|
7bbbb1749c | ||
|
|
f813000a94 | ||
|
|
95121cf859 | ||
|
|
ea2a2b98f4 | ||
|
|
5885af8ebf | ||
|
|
ce625b6d3a | ||
|
|
f73d749bc9 | ||
|
|
107026eecc | ||
|
|
190b3c2aa6 | ||
|
|
2f1a5e6886 | ||
|
|
4589d4e6de | ||
|
|
3229601d27 | ||
|
|
95629f4546 | ||
|
|
f897346724 | ||
|
|
d34fa93322 | ||
|
|
2cab377e83 | ||
|
|
46240e45e3 | ||
|
|
353fc106b4 |
12
.changeset/config.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
||||
"changelog": ["@changesets/changelog-github", { "repo": "nebulaservices/nebula" }],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": [],
|
||||
"privatePackages": { "version": true, "tag": true }
|
||||
}
|
||||
17
.dockerignore
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
node_modules/
|
||||
.vscode
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
.github/
|
||||
.env.example
|
||||
.env
|
||||
dist/
|
||||
.git/
|
||||
.astro/
|
||||
~/
|
||||
.gitignore
|
||||
biome.json
|
||||
docker-compose.yml
|
||||
Dockerfile
|
||||
README.md
|
||||
db/
|
||||
50
.github/workflows/docker.yml
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
name: Build Docker image
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and push Docker image to registry
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository_owner == 'nebulaservices'
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login To registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/nebulaservice/nebula
|
||||
- name: Build and push
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: ./Dockerfile
|
||||
name: nebula
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
50
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
env:
|
||||
FORCE_COLOR: true
|
||||
|
||||
jobs:
|
||||
changelog:
|
||||
name: Release TAG
|
||||
if: ${{ github.repository_owner == 'nebulaservices' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v3
|
||||
with:
|
||||
version: 9.1.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --no-frozen-lockfile
|
||||
|
||||
- name: Create Release Pull Request or Publish
|
||||
id: changesets
|
||||
uses: changesets/action@v1
|
||||
with:
|
||||
version: pnpm run version
|
||||
publish: pnpm exec changeset publish
|
||||
commit: "[ci] release"
|
||||
title: "[ci] release"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
45
.gitignore
vendored
|
|
@ -1,2 +1,43 @@
|
|||
/node_modules
|
||||
.DS_Store
|
||||
# build output
|
||||
dist/
|
||||
server/*.js
|
||||
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
#external assets
|
||||
database_assets/*
|
||||
!database_assets/com.nebula.gruvbox/
|
||||
!database_assets/com.nebula.lightTheme/
|
||||
!database_assets/com.nebula.oled/
|
||||
!database_assets/com.nebula.retro/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
|
||||
# nebula catalog database
|
||||
database.sqlite
|
||||
|
||||
|
||||
# YOUR config
|
||||
config.toml
|
||||
|
||||
# Goofy PNPM problem
|
||||
~/
|
||||
|
|
|
|||
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "workerware"]
|
||||
path = workerware
|
||||
url = https://github.com/mercuryworkshop/workerware
|
||||
72
.replit
|
|
@ -1,72 +0,0 @@
|
|||
|
||||
hidden = [".config"]
|
||||
run = "npm start"
|
||||
|
||||
[[hints]]
|
||||
regex = "Error \\[ERR_REQUIRE_ESM\\]"
|
||||
message = "We see that you are using require(...) inside your code. We currently do not support this syntax. Please use 'import' instead when using external modules. (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)"
|
||||
|
||||
[nix]
|
||||
channel = "stable-21_11"
|
||||
|
||||
[env]
|
||||
XDG_CONFIG_HOME = "/home/runner/.config"
|
||||
PATH = "/home/runner/$REPL_SLUG/.config/npm/node_global/bin:/home/runner/$REPL_SLUG/node_modules/.bin"
|
||||
npm_config_prefix = "/home/runner/$REPL_SLUG/.config/npm/node_global"
|
||||
|
||||
[packager]
|
||||
language = "nodejs"
|
||||
|
||||
[packager.features]
|
||||
packageSearch = true
|
||||
guessImports = true
|
||||
enabledForHosting = false
|
||||
|
||||
[unitTest]
|
||||
language = "nodejs"
|
||||
|
||||
[languages.javascript]
|
||||
pattern = "**/{*.js,*.jsx,*.ts,*.tsx}"
|
||||
|
||||
[languages.javascript.languageServer]
|
||||
start = [ "typescript-language-server", "--stdio" ]
|
||||
|
||||
[debugger]
|
||||
support = true
|
||||
|
||||
[debugger.interactive]
|
||||
transport = "localhost:0"
|
||||
startCommand = [ "dap-node" ]
|
||||
|
||||
[debugger.interactive.initializeMessage]
|
||||
command = "initialize"
|
||||
type = "request"
|
||||
|
||||
[debugger.interactive.initializeMessage.arguments]
|
||||
clientID = "replit"
|
||||
clientName = "replit.com"
|
||||
columnsStartAt1 = true
|
||||
linesStartAt1 = true
|
||||
locale = "en-us"
|
||||
pathFormat = "path"
|
||||
supportsInvalidatedEvent = true
|
||||
supportsProgressReporting = true
|
||||
supportsRunInTerminalRequest = true
|
||||
supportsVariablePaging = true
|
||||
supportsVariableType = true
|
||||
|
||||
[debugger.interactive.launchMessage]
|
||||
command = "launch"
|
||||
type = "request"
|
||||
|
||||
[debugger.interactive.launchMessage.arguments]
|
||||
args = []
|
||||
console = "externalTerminal"
|
||||
cwd = "."
|
||||
environment = []
|
||||
pauseForSourceMap = false
|
||||
program = "./index.js"
|
||||
request = "launch"
|
||||
sourceMaps = true
|
||||
stopOnEntry = false
|
||||
type = "pwa-node"
|
||||
4
.vscode/extensions.json
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
11
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
36
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# v9.0.0
|
||||
|
||||
- The first release of Nebula V9! And with it bring a whole host of changes:
|
||||
- More stable the V8
|
||||
- Adds a Marketplace where users can create their own themes & plugins
|
||||
- Switches to Astro for speed
|
||||
- Other general bug fixes
|
||||
|
||||
# v9.0.1
|
||||
|
||||
- Bumps dependencies
|
||||
- Fixes bugs
|
||||
|
||||
# 9.0.2
|
||||
|
||||
- Adds the ability for a custom wisp server back
|
||||
- Increases upload fileSize capacity in hopes that bigger files can now be uploaded
|
||||
|
||||
# 9.0.3
|
||||
|
||||
- Bugfix: themes with caps don't uninstall - fixed
|
||||
- Rewrite: InstalledThemes and InstalledPlugins are now in Astro instead of Svelte
|
||||
|
||||
# 9.0.4
|
||||
|
||||
- General Bugfixes
|
||||
- Removes Svelte except for 2 componenents
|
||||
- Scramjet :rocket:
|
||||
|
||||
# 9.1.0
|
||||
|
||||
- Rewrites everything in the [utils/](./src/utils) folder
|
||||
- Bugfixes
|
||||
- Better logging
|
||||
- Mobile nav jank is gone
|
||||
- Component cleanup
|
||||
24
Dockerfile
|
|
@ -1,13 +1,19 @@
|
|||
FROM node:18
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json .
|
||||
COPY . .
|
||||
|
||||
RUN npm install
|
||||
|
||||
RUN npm ci
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "start"]
|
||||
RUN apk update
|
||||
RUN apk add python3 py3-pip alpine-sdk openssl-dev build-base python3-dev
|
||||
RUN python3 -m pip install setuptools --break-system-packages
|
||||
RUN cp -n config.example.toml config.toml
|
||||
RUN npm i -g pnpm
|
||||
RUN pnpm install
|
||||
RUN pnpm run build
|
||||
RUN export TERM=xterm-256color
|
||||
VOLUME /app
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["pnpm"]
|
||||
CMD ["start", "--color"]
|
||||
|
|
|
|||
4
LICENSE
|
|
@ -1,7 +1,7 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2022 Nebula Services
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
|
@ -658,4 +658,4 @@ specific requirements.
|
|||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
|
|
|||
408
README.md
|
|
@ -1,77 +1,371 @@
|
|||
<div align=center>
|
||||
<div align="center">
|
||||
|
||||
<img src="https://socialify.git.ci/nebulaservices/nebula/image?description=1&font=Inter&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Dark" alt="ruby" width="640" height="320" />
|
||||
|
||||
<img src='https://nebulaproxy.nebula.bio/images/logo.png' width="100px" height="100px">
|
||||
<h1> Nebula </h1>
|
||||
Nebula Web is an official flagship of Nebula Services. Nebula Web is a stunning and sleak webproxy with support for hundreds of popular sites, and partial support for WebRTC, used in GfN. With Nebula Web, the sky is the limit. Enjoy.
|
||||
<img alt="repo size" src="https://img.shields.io/github/repo-size/nebulaservices/nebula?style=for-the-badge"></img>
|
||||
<img alt="website status" src="https://img.shields.io/website?url=https%3A%2F%2Fnebulaproxy.io&style=for-the-badge"></img>
|
||||
<img alt="commit a week" src="https://img.shields.io/github/commit-activity/w/nebulaservices/nebula?style=for-the-badge"></img>
|
||||
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<h2>Get Started</h2>
|
||||
<a>To get started, press one of the buttons below to deploy Nebula</a>
|
||||
<br />
|
||||
<br />
|
||||
<a href="#terminal">
|
||||
<img src="https://img.shields.io/badge/terminal-%23121011.svg?style=for-the-badge&logo=gnu-bash&logoColor=white" alt="Terminal">
|
||||
</img>
|
||||
</a>
|
||||
<a href="#docker">
|
||||
<img src="https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white" alt="Docker">
|
||||
</img>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## NOTE:
|
||||
|
||||
- This will **NOT** deploy on GitHub Pages, Netlify, Vercel, Gitlab Pages, or any other _static_ host
|
||||
- This will **NOT** work on Render
|
||||
---
|
||||
|
||||
## How to get links
|
||||
|
||||
[](https://discord.gg/unblocker)
|
||||
[](https://discord.gg/unblock)
|
||||
|
||||
---
|
||||
|
||||
_this readme is 95% done_
|
||||
## Features
|
||||
|
||||
- Stunning UI with multiple themes
|
||||
- XOR/b64 Encrypts all traffic sent from Nebula
|
||||
- Hides your IP from sites
|
||||
- [List of officially supported sites](https://github.com/NebulaServices/Nebula/blob/main/docs/officially-supported-sites.md)
|
||||
- *limited* mobile support
|
||||
- StealthMode (buffed `about:blank` cloaking)
|
||||
- Multiple Proxy "Backends":
|
||||
- [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet)
|
||||
- [RammerHead](https://github.com/binary-person/rammerhead)
|
||||
---
|
||||
|
||||
## Deployment
|
||||
(Nebula's license is now GNU AGPL V3 as of v7.10)
|
||||
### Quick Deployment
|
||||
[](https://heroku.com/deploy/?template=https://github.com/NebulaServices/Nebula)
|
||||
<br>
|
||||
[](https://replit.com/github/NebulaServices/Nebula)
|
||||
<br>
|
||||
[](https://glitch.com/edit/#!/import/github/NebulaServices/Nebula)
|
||||
<br>
|
||||
[](https://cloud.ibm.com/devops/setup/deploy?repository=https://github.com/NebulaServices/Nebula)
|
||||
<br>
|
||||
[](https://console.aws.amazon.com/amplify/home#/deploy?repo=https://github.com/NebulaServices/Nebula)
|
||||
<br>
|
||||
[](https://deploy.cloud.run/?git_repo=https://github.com/NebulaServices/Nebula)
|
||||
<br>
|
||||
[](https://railway.app/new/template/pBzeiN)
|
||||
<br>
|
||||
[](https://app.koyeb.com/deploy?type=git&repository=github.com/NebulaServices/Nebula&branch=main&name=NebulaProxy)
|
||||
## Contributors
|
||||
|
||||
### Self Hosting
|
||||
```bash
|
||||
$ git clone https://github.com/NebulaServices/Nebula.git
|
||||
$ cd Nebula
|
||||
$ npm ci
|
||||
$ npm start
|
||||
```
|
||||
- [Rifting](https://github.com/rifting) - Owner & Maintainer
|
||||
- [MotorTruck1221](https://motortruck1221.com) - Maintainer
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- HTML, JS, CSS
|
||||
- Partical.JS
|
||||
- UV Backend Proxy
|
||||
- Osana Backend Proxy
|
||||
- Cyclone Backend Proxy
|
||||
- **Server:** Bare server on Node
|
||||
- [Astro](https://astro.build)
|
||||
- [Fastify](https://fastify.dev)
|
||||
- [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet)
|
||||
- [RammerHead](https://github.com/binary-person/rammerhead)
|
||||
- [Epoxy](https://github.com/mercuryworkshop/epoxy-tls)
|
||||
- [Libcurl.js](https://github.com/ading2210/libcurl.js)
|
||||
- HTML, CSS, and JavaScript (DUH)
|
||||
---
|
||||
|
||||
## Catalog/Marketplace
|
||||
|
||||
## Support
|
||||
- By default, the marketplace is enabled and uses SQLite
|
||||
- If you would like to disable the catalog, see [#config](#config)
|
||||
- For big production instances, I recommend using PostgreSQL rather than SQLite. To do this see [#config](#config)
|
||||
- To use PostgreSQL via the provided docker-compose files, see [#docker](#docker)
|
||||
|
||||
For support, email chloe@nebula.bio or join our discord: discord.nebula.bio
|
||||
### How to make a theme
|
||||
|
||||
- Themes allow you to customize Nebula's *look*.
|
||||
|
||||
## Demo
|
||||
#### Prerequisites:
|
||||
- Make sure you have our [Discord server](https://discord.gg/unblocker) so you can submit your theme
|
||||
|
||||
[Click here to see a demo of Nebula](https://tutorialread.beauty/)
|
||||
##### Making the themes:
|
||||
|
||||
1. Firstly, copy the CSS vars:
|
||||
```css
|
||||
:root {
|
||||
--background-primary: /*Your stuff here */;
|
||||
--background-lighter: ;
|
||||
--navbar-color: ;
|
||||
--navbar-text-color: ;
|
||||
--navbar-link-color: ;
|
||||
--navbar-link-hover-color: ;
|
||||
--input-text-color: ;
|
||||
--input-placeholder-color: ;
|
||||
--input-background-color: ;
|
||||
--input-border-color: ;
|
||||
--tab-color: ;
|
||||
--border-color: ;
|
||||
}
|
||||
```
|
||||
|
||||
## Acknowledgements
|
||||
> [!NOTE]
|
||||
>
|
||||
> You can add a custom font as well! To do so, add this to your `:root`
|
||||
>
|
||||
> ```css
|
||||
> --font-family: /* Font family name */;
|
||||
> ```
|
||||
>
|
||||
> And this to the bottom of your CSS file/submission:
|
||||
> ```css
|
||||
> @font-face {
|
||||
> font-family: /* Name */;
|
||||
> src: url(/* Where the font is located! Local or external work! */);
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> A good example of using a custom font is the built-in `retro` theme [here](./database_assets/com.nebula.retro)
|
||||
|
||||
- [UV (one of the back-end proxy we use)](https://github.com/titaniumnetwork-dev/Ultraviolet)
|
||||
- [Cyclone (one of the back-end proxy we use)](https://github.com/NebulaServices/Cyclone)
|
||||
- [Osana (one of the back-end proxy we use)](https://github.com/NebulaServices/Osana)
|
||||
- [Bare Server](https://github.com/tomphttp/bare-server-node)
|
||||
- [Partical.JS (v4, 5, 6.1 &< only)](https://github.com/VincentGarreau/particles.js)
|
||||
2. Add your colors and test! (Either with a self-hosted version of Nebula OR via a live preview (no clue when this will happen)
|
||||
|
||||
## License
|
||||
3. Once you're satisfied with the colors, submit your theme to the [Discord Server](https://discord.gg/unblocker)!
|
||||
|
||||
Copyright Nebula Services 2021 - Present
|
||||
<br>
|
||||
This project uses the MIT license.
|
||||
<div align=center>
|
||||
---
|
||||
### How to make a plugin
|
||||
|
||||
- Plugins extend the functionality of either the proxied page(s) or the service worker.
|
||||
- This guide provides an incredibly basic example of how to make either.
|
||||
|
||||
#### Prerequisites:
|
||||
- Make sure you have joined our [Discord server](https://discord.gg/unblocker) so you can submit your plugin.
|
||||
- Some knowledge of JS/TS
|
||||
|
||||
##### Serviceworker plugin:
|
||||
|
||||
- These plugins are handled by Workerware see [here](https://github.com/mercuryworkshop/workerware) for docs.
|
||||
|
||||
1. Create an index.js (or other file name) file:
|
||||
```bash
|
||||
touch index.js
|
||||
```
|
||||
|
||||
2. Edit that file to include either of these:
|
||||
- Code encased in a string:
|
||||
```js
|
||||
function setup() {
|
||||
// This function MUST return the following attributes:
|
||||
return {
|
||||
function: `console.log('Example code.')`,
|
||||
name: 'com.example', // Technically, it could be named anything. It is recommended to use the same name for everything (name when submitting and this)
|
||||
events: ['fetch'] // See: https://github.com/mercuryworkshop/workerware for the event types you can use. (Also typed if you are using typescript)
|
||||
}
|
||||
}
|
||||
|
||||
//This can be named anything. However, it's recommended to use `entryFunc` (with types, the naming IS enforced)
|
||||
self.entryFunc = setup; //DO NOT call the function here. Only assign the reference otherwise, it will error.
|
||||
```
|
||||
- Code in an arrow function:
|
||||
```js
|
||||
const example = () => {
|
||||
console.log('Example code')
|
||||
}
|
||||
|
||||
function setup() {
|
||||
//This function MUST return the following attributes:
|
||||
return {
|
||||
function: example, //Do not call the function, only assign the reference to the function.
|
||||
name: 'com.example', // Technicall could be name anything. Recommended to use the same name for everything (name when submitting and this)
|
||||
event: ['fetch'] // Se https://github.com/mercuryworkshop/workerware for the event types you can use. (Also typed if using typescript)
|
||||
}
|
||||
}
|
||||
|
||||
//This can be named anything. However, it's recommended to use `entryFunc` (with types, the naming IS enforced)
|
||||
self.entryFunc = setup; //DO NOT call the function here. Only assign the reference; otherwise, it will result in an error.
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> The only *allowed* way to pass code to the `function` param is either a string or an arrow function. Named functions ***WILL NOT WORK***.
|
||||
>
|
||||
> Example of a named function: `function example() {/* Some form of code */}`.
|
||||
>
|
||||
> If a named function is used where it shouldn't be, your plugin will not be approved, nor will it work properly.
|
||||
|
||||
3. Submit your plugin in the [Discord](https://discord.gg/unblocker)!
|
||||
|
||||
##### Proxied page plugins
|
||||
|
||||
- They allow modification of websites that UV proxies, (EX: you could add Vencord to Discord with this)
|
||||
|
||||
1. Create an index.js file (or another file name)
|
||||
```bash
|
||||
touch index.js
|
||||
```
|
||||
|
||||
2. Edit that file with your code and the following:
|
||||
```js
|
||||
//Name this whatever.
|
||||
function example() {
|
||||
//You MUST return the following
|
||||
return {
|
||||
host: "example.com", //The host to match (so if the user visits example.com it will inject the html below.
|
||||
html: "<script>console.log('Example')</script>", //Must return a string (and be valid HTML or your plugin will break). How you get that string is up to you
|
||||
injectTo: "head" // Can be "head" or "body"
|
||||
}
|
||||
}
|
||||
|
||||
// Technically, this could be named anything, it is recommended to call it `entryFunc`
|
||||
self.entryFunc = example; //DO NOT run the function here. That will cause errors. Only assign the reference to the function here.
|
||||
```
|
||||
|
||||
3. Submit it in our [Discord](https://discord.gg/unblocker)!
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Terminal
|
||||
|
||||
Prerequisites:
|
||||
- Node & npm
|
||||
- Git
|
||||
|
||||
1. Clone the repo:
|
||||
```bash
|
||||
git clone https://github.com/nebulaservices/nebula --recursive && cd nebula
|
||||
```
|
||||
|
||||
2. Install all of the dependencies:
|
||||
```bash
|
||||
npm i
|
||||
```
|
||||
|
||||
3. Create a `config.toml` file
|
||||
```bash
|
||||
cp config.example.toml config.toml
|
||||
```
|
||||
|
||||
4. Modify the `config.toml` file to your liking (docs [here](#environment))
|
||||
```
|
||||
nano config.toml
|
||||
```
|
||||
|
||||
5. Build the front end & server:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
6. Start the server
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> You can run `npm run bstart` to build and start together
|
||||
---
|
||||
|
||||
### Docker
|
||||
|
||||
- There are two ways to deploy with docker:
|
||||
- [Normal docker](#normal-docker)
|
||||
- [Docker Compose](#docker-compose)
|
||||
|
||||
#### Normal Docker
|
||||
|
||||
Prerequisites:
|
||||
- Git
|
||||
- Docker
|
||||
|
||||
1. Clone the repo (skip if using a prebuilt image):
|
||||
```bash
|
||||
git clone https://github.com/nebulaservices/nebula --recursive && cd nebula
|
||||
```
|
||||
|
||||
2. Create an `config.toml` file (if using prebuilt image, copy the example from the repo):
|
||||
```bash
|
||||
cp config.example.toml config.toml
|
||||
```
|
||||
|
||||
3. Modify the `config.toml` file to your liking (docs [here](#environment))
|
||||
```bash
|
||||
nano config.toml
|
||||
```
|
||||
|
||||
4. Build the docker image (skip if using prebuilt):
|
||||
```bash
|
||||
docker build nebula:latest
|
||||
```
|
||||
5. Run the docker images:
|
||||
|
||||
- Prebuilt:
|
||||
```bash
|
||||
docker run -v ./config.toml:/app/config.toml ghcr.io/nebulaservices/nebula:latest
|
||||
```
|
||||
- Image you built yourself:
|
||||
```bash
|
||||
docker run -v ./config.toml:/app/config.toml nebula:latest
|
||||
```
|
||||
|
||||
#### Docker Compose
|
||||
|
||||
Prerequisites:
|
||||
- Git
|
||||
- Docker w/compose
|
||||
|
||||
1. Clone the repo (skip if using a prebuilt image):
|
||||
```bash
|
||||
git clone https://github.com/nebulaservices/nebula --recursive
|
||||
```
|
||||
|
||||
2. Create an `config.toml` file (if using prebuilt image, copy the example from the repo):
|
||||
```bash
|
||||
cp config.example.toml config.toml
|
||||
```
|
||||
|
||||
3. Modify the `config.toml` file to your liking (docs on that [here](#environment)]
|
||||
```bash
|
||||
nano config.toml
|
||||
```
|
||||
|
||||
4. Build the docker image (skip if using prebuilt):
|
||||
```bash
|
||||
docker compose -f ./docker-compose.build.yml build
|
||||
```
|
||||
|
||||
5. Run the docker image:
|
||||
|
||||
- Prebuilt:
|
||||
```bash
|
||||
docker compose up
|
||||
```
|
||||
- Image you built yourself:
|
||||
```bash
|
||||
docker compose -f ./docker-compose.build.yml up
|
||||
```
|
||||
#### Extra (Postgres)
|
||||
|
||||
- To use Postgres over SQLite, uncomment the DB section in the `docker-compose` file (or use your own Postgres DB!). Then, modify the `config.toml` (See: [#config](#config) for knowledge on how to do this)
|
||||
- To use Postgres over SQLite in a normal docker environment (no compose), you'll have to set one up and then modify the `config.toml` to use it. (See: [#config](#config) for knowledge on how to do this)
|
||||
|
||||
---
|
||||
|
||||
## Config
|
||||
|
||||
There are a couple of configuration options for Nebula. The defaults are fine most of the time, but there are instances where you may not want certain options enabled or certain things running.
|
||||
- An example config file is located [here](./config.example.toml).
|
||||
- Config format is in TOML
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
|:----------:|:-------------:|:------:|:---------:|
|
||||
| `marketplace` | The options below are for the marketplace section | `object` | N/A |
|
||||
| `enabled` | Enable marketplace functionality | `boolean` | `true` |
|
||||
| `psk` | The password and authentication key for the marketplace. ***CHANGE FROM DEFAULT*** | `string` | `CHANGEME` |
|
||||
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
|
||||
| `db` | The below options are for the db (database) section | `object` | N/A |
|
||||
| `name` | The database name to use | `string` | `database` |
|
||||
| `username` | The username for the DB | `string` | `username` |
|
||||
| `password` | The database password. ***CHANGE FROM DEFAULT VALUE*** | `string` | `password` |
|
||||
| `postgres` | Whether to use postgres over sqlite *(recommended for large production instances)* | `boolean` | `false` |
|
||||
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
|
||||
| `postgres` | The below options are for the postgres section. (Only worry about this if you enabled postgres in the db section.) | `object` | N/A |
|
||||
| `domain` | Either the TLD or the IP address of your postgres server. | `string` | `''` |
|
||||
| `port` | The port your postgres server is listening on | `number` | `5432` |
|
||||
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
|
||||
| `server.server` | The below options are to configure the server. | `object` | N/A |
|
||||
| `port` | What port the server should listen on. *(Note: Can also be configured via environment variable `PORT`)* | `number` | `8080` |
|
||||
| `wisp` | Whether the server should use the inbuilt wisp server. (Disabled if your using an external wisp server) | `boolean` | `true` |
|
||||
| `logging` | Whether or not to enable logging. *Note: Logs are massive* | `boolean` | `true` |
|
||||
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
|
||||
|
||||
## Deploying
|
||||
### Koyeb
|
||||
- First setup the config.toml file with the docker-compose instructions!
|
||||
- Fork this repo
|
||||
- Create new koyeb service, and select webservice
|
||||
- Select import from github and import your forked repo
|
||||
- Change package to dockerfile and press deploy!
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
theme: jekyll-theme-minimal
|
||||
63
app.js
|
|
@ -1,63 +0,0 @@
|
|||
import createBareServer from '@tomphttp/bare-server-node';
|
||||
import http from 'http';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
import serveStatic from 'serve-static';
|
||||
import * as custombare from './static/customBare.mjs';
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const bareServer = createBareServer('/bare/', {
|
||||
logErrors: false,
|
||||
localAddress: undefined
|
||||
});
|
||||
|
||||
const serve = serveStatic(join(
|
||||
dirname(fileURLToPath(import.meta.url)),
|
||||
'static/'
|
||||
), {
|
||||
fallthrough: false,
|
||||
maxAge: 5 * 60 * 1000
|
||||
});
|
||||
|
||||
const server = http.createServer();
|
||||
|
||||
server.on('request', (request, response) => {
|
||||
try {
|
||||
if (custombare.route(request, response)) return true;
|
||||
|
||||
if (bareServer.shouldRoute(request)) {
|
||||
bareServer.routeRequest(request, response);
|
||||
} else {
|
||||
serve(request, response, err => {
|
||||
response.writeHead(err?.statusCode || 500, null, {
|
||||
"Content-Type": "text/plain"
|
||||
})
|
||||
response.end(err?.stack)
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
response.writeHead(500, "Internal Server Error", {
|
||||
"Content-Type": "text/plain"
|
||||
})
|
||||
response.end(e.stack)
|
||||
}
|
||||
});
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
if (bareServer.shouldRoute(req)) {
|
||||
bareServer.routeUpgrade(req, socket, head);
|
||||
} else {
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PORT);
|
||||
|
||||
if (process.env.UNSAFE_CONTINUE)
|
||||
process.on("uncaughtException", (err, origin) => {
|
||||
console.error(`Critical error (${origin}):`)
|
||||
console.error(err)
|
||||
console.error("UNSAFELY CONTINUING EXECUTION")
|
||||
console.error()
|
||||
})
|
||||
|
||||
console.log(`Server running at http://localhost:${PORT}/.`);
|
||||
7
app.json
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name": "NebulaWEB",
|
||||
"description": "Explore the web. Freely. ",
|
||||
"repository": "https://github.com/NebulaServices/Nebula",
|
||||
"logo": "https://avatars.githubusercontent.com/u/86420004?v=4",
|
||||
"keywords": ["educational", "science", "math"]
|
||||
}
|
||||
133
astro.config.ts
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
import { fileURLToPath } from "node:url";
|
||||
import node from "@astrojs/node";
|
||||
import svelte from "@astrojs/svelte";
|
||||
import tailwind from "@astrojs/tailwind";
|
||||
import { baremuxPath } from "@mercuryworkshop/bare-mux/node";
|
||||
import { epoxyPath } from "@mercuryworkshop/epoxy-transport";
|
||||
import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
|
||||
import playformCompress from "@playform/compress";
|
||||
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
|
||||
import { scramjetPath } from "@mercuryworkshop/scramjet";
|
||||
import icon from "astro-icon";
|
||||
import { defineConfig, envField } from "astro/config";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
import { version } from "./package.json";
|
||||
import { parsedDoc } from "./server/config.js";
|
||||
const workerwarePath = fileURLToPath(new URL("./workerware/src", import.meta.url));
|
||||
|
||||
export default defineConfig({
|
||||
site: parsedDoc.seo.enabled ? parsedDoc.seo.domain || process.env.SITE : 'http://localhost:4321',
|
||||
env: {
|
||||
schema: {
|
||||
VERSION: envField.string({
|
||||
context: "client",
|
||||
access: "public",
|
||||
optional: true,
|
||||
default: version
|
||||
}),
|
||||
MARKETPLACE_ENABLED: envField.boolean({
|
||||
context: "client",
|
||||
access: "public",
|
||||
optional: true,
|
||||
default: parsedDoc.marketplace.enabled
|
||||
}),
|
||||
SEO: envField.string({
|
||||
context: "client",
|
||||
access: "public",
|
||||
optional: true,
|
||||
default: JSON.stringify({
|
||||
enabled: parsedDoc.seo.enabled,
|
||||
domain: new URL(parsedDoc.seo.domain).host
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
integrations: [
|
||||
tailwind(),
|
||||
//sitemap(),
|
||||
icon(),
|
||||
svelte(),
|
||||
playformCompress({
|
||||
CSS: false,
|
||||
HTML: true,
|
||||
Image: true,
|
||||
JavaScript: true,
|
||||
SVG: true
|
||||
})
|
||||
],
|
||||
vite: {
|
||||
plugins: [
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: `${uvPath}/**/*`.replace(/\\/g, "/"),
|
||||
dest: "uv",
|
||||
overwrite: false
|
||||
},
|
||||
{
|
||||
src: `${epoxyPath}/**/*`.replace(/\\/g, "/"),
|
||||
dest: "epoxy",
|
||||
overwrite: false
|
||||
},
|
||||
{
|
||||
src: `${libcurlPath}/**/*`.replace(/\\/g, "/"),
|
||||
dest: "libcurl",
|
||||
overwrite: false
|
||||
},
|
||||
{
|
||||
src: `${scramjetPath}/**/*`.replace(/\\/g, "/"),
|
||||
dest: "scram",
|
||||
overwrite: false
|
||||
},
|
||||
{
|
||||
src: `${baremuxPath}/**/*`.replace(/\\/g, "/"),
|
||||
dest: "baremux",
|
||||
overwrite: false
|
||||
},
|
||||
{
|
||||
src: `${workerwarePath}/**/*`.replace(/\\/g, "/"),
|
||||
dest: "workerware",
|
||||
overwrite: false
|
||||
}
|
||||
]
|
||||
})
|
||||
],
|
||||
server: {
|
||||
proxy: {
|
||||
"/api/catalog-stats": {
|
||||
target: "http://localhost:8080/api/catalog-stats",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/catalog-stats/, "")
|
||||
},
|
||||
"/api/catalog-assets": {
|
||||
target: "http://localhost:8080/api/catalog-assets",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/catalog-assets/, "")
|
||||
},
|
||||
"/api/packages": {
|
||||
target: "http://localhost:8080/api/packages",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/packages/, "")
|
||||
},
|
||||
"/packages": {
|
||||
target: "http://localhost:8080",
|
||||
changeOrigin: true
|
||||
},
|
||||
"/wisp/": {
|
||||
target: "ws://localhost:8080/wisp/",
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: (path) => path.replace(/^\/wisp\//, "")
|
||||
},
|
||||
"/styles": {
|
||||
target: "http://localhost:8080",
|
||||
changeOrigin: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
output: "server",
|
||||
adapter: node({
|
||||
mode: "middleware"
|
||||
})
|
||||
});
|
||||
32
biome.json
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
|
||||
"files": {
|
||||
"ignore": ["~/", "**/dist/**", ".github/**"],
|
||||
"include": ["**/**", "server/**"]
|
||||
},
|
||||
"formatter": {
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 100,
|
||||
"ignore": ["pnpm-lock.yaml", "package.json"]
|
||||
},
|
||||
"organizeImports": { "enabled": true },
|
||||
"linter": { "enabled": false },
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"trailingCommas": "none",
|
||||
"quoteStyle": "double",
|
||||
"semicolons": "always"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"parser": {
|
||||
"allowComments": true,
|
||||
"allowTrailingCommas": true
|
||||
},
|
||||
"formatter": {
|
||||
"indentStyle": "space",
|
||||
"trailingCommas": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
23
config.example.toml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
[marketplace]
|
||||
enabled = false # Turn on or off the marketplace entirely
|
||||
psk = "CHANGEME" # Change this to something more secure.
|
||||
level = 1
|
||||
|
||||
[seo]
|
||||
enabled = false
|
||||
domain = "https://nebulaproxy.io"
|
||||
|
||||
[db]
|
||||
name = "database" # Your database name
|
||||
username = "username" # The username of your DB (SQLITE just ignores this)
|
||||
password = "password" # The password to your DB (SQLITE ignores this)
|
||||
postgres = false # Enable to use postgres over sqlite (recommended for large prod instances)
|
||||
|
||||
[postgres] # Set the "domain" to either and ip address or a actual domain
|
||||
domain = ""
|
||||
port = 5432
|
||||
|
||||
[server.server]
|
||||
port = 8080
|
||||
wisp = true
|
||||
logging = true # Disable for the tons & tons of logs to go away (useful for debugging but otherwise eh)
|
||||
22
database_assets/com.nebula.gruvbox/gruvbox.css
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
:root {
|
||||
--background-primary: #282828;
|
||||
--background-lighter: #3c3836;
|
||||
--navbar-color: #504945;
|
||||
--navbar-height: 60px;
|
||||
--navbar-text-color: #fbf1c7;
|
||||
--navbar-link-color: #ebdbb2;
|
||||
--navbar-link-hover-color: #fabd2f;
|
||||
--navbar-font: "Roboto", sans-serif;
|
||||
--input-text-color: #b8bb26;
|
||||
--input-placeholder-color: #928374;
|
||||
--input-background-color: #1d2021;
|
||||
--input-border-color: #b8bb26;
|
||||
--input-border-size: 1.3px;
|
||||
--navbar-logo-filter: none;
|
||||
--dropdown-option-hover-color: #665c54;
|
||||
--tab-color: #1d2021;
|
||||
--border-color: #b8bb26;
|
||||
--highlight-color: #fe8019;
|
||||
--accent-color: #83a598;
|
||||
--secondary-text-color: #d3869b;
|
||||
}
|
||||
BIN
database_assets/com.nebula.gruvbox/gruvbox.jpg
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
18
database_assets/com.nebula.lightTheme/light.css
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
:root {
|
||||
--background-primary: hsl(310 50% 90%);
|
||||
--background-lighter: hsl(310 50% 90%);
|
||||
--navbar-color: hsl(310 50% 100%);
|
||||
--navbar-height: 60px;
|
||||
--navbar-text-color: hsl(310 50% 15%);
|
||||
--navbar-link-color: hsl(310 50% 15%);
|
||||
--navbar-link-hover-color: hsl(310 50% 90%);
|
||||
--navbar-font: "Roboto";
|
||||
--input-text-color: hsl(310 50% 15%);
|
||||
--input-placeholder-color: white;
|
||||
--input-background-color: hsl(310 50% 100%);
|
||||
--input-border-color: hsl(310 50% 25%);
|
||||
--input-border-size: 1.3px;
|
||||
--navbar-logo-filter: none;
|
||||
--tab-color: var(--black);
|
||||
--border-color: hsl(310 50% 25%);
|
||||
}
|
||||
BIN
database_assets/com.nebula.lightTheme/light.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
19
database_assets/com.nebula.oled/oled.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
:root {
|
||||
--background-primary: #000000;
|
||||
--background-lighter: #000000;
|
||||
--navbar-color: #00000f;
|
||||
--navbar-height: 60px;
|
||||
--navbar-text-color: #4763ff;
|
||||
--navbar-link-color: #4763ff;
|
||||
--navbar-link-hover-color: gray;
|
||||
--navbar-font: "Roboto";
|
||||
--input-text-color: #4763ff;
|
||||
--input-placeholder-color: white;
|
||||
--input-background-color: #000000;
|
||||
--input-border-color: #4763ff;
|
||||
--input-border-size: 1.3px;
|
||||
--navbar-logo-filter: none;
|
||||
--dropdown-option-hover-color: #000000;
|
||||
--tab-color: #000000;
|
||||
--border-color: #4763ff;
|
||||
}
|
||||
BIN
database_assets/com.nebula.oled/oled.jpg
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
24
database_assets/com.nebula.retro/retro.css
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
:root {
|
||||
--background-primary: #000000;
|
||||
--background-lighter: #000000;
|
||||
--navbar-color: #020805;
|
||||
--navbar-height: 60px;
|
||||
--navbar-text-color: #3cb371;
|
||||
--navbar-link-color: #3cb371;
|
||||
--navbar-link-hover-color: white;
|
||||
--input-text-color: #3cb371;
|
||||
--input-placeholder-color: white;
|
||||
--input-background-color: #000000;
|
||||
--input-border-color: #3cb371;
|
||||
--input-border-size: 1.3px;
|
||||
--navbar-logo-filter: none;
|
||||
--dropdown-option-hover-color: #000000;
|
||||
--tab-color: #000000;
|
||||
--border-color: #3cb371;
|
||||
--font-family: "terminal";
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: terminal;
|
||||
src: url("./terminal.ttf");
|
||||
}
|
||||
BIN
database_assets/com.nebula.retro/retro.png
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
database_assets/com.nebula.retro/terminal.ttf
Normal file
21
docker-compose.build.yml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
services:
|
||||
nebula:
|
||||
image: ghcr.io/nebulaservices/nebula:latest
|
||||
container_name: nebula
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# HOST:CONTAINER (DO NOT CHANGE THE CONTAINER PORT, UNLESS EDITED IN THE config.toml FILE)
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./config.toml:/app/config.toml
|
||||
# Uncomment the the below stuff to use POSTGRES!
|
||||
# db:
|
||||
# image: postgres
|
||||
# restart: unless-stopped
|
||||
# environment:
|
||||
# POSTGRES_PASSWORD: password #CHANGE THIS
|
||||
# POSTGRES_USER: username
|
||||
# POSTGRES_DB: db
|
||||
# volumes:
|
||||
# - ./db:/var/lib/postgresql/data
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
version: '3'
|
||||
services:
|
||||
nebula:
|
||||
image: nebula:latest
|
||||
build: .
|
||||
container_name: nebula
|
||||
restart: unless-stopped
|
||||
20
docker-compose.yml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
services:
|
||||
nebula:
|
||||
image: ghcr.io/nebulaservices/nebula:latest
|
||||
container_name: nebula
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# HOST:CONTAINER (DO NOT CHANGE THE CONTAINER PORT, UNLESS EDITED IN THE config.toml FILE)
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./config.toml:/app/config.toml
|
||||
# Uncomment the the below stuff to use POSTGRES!
|
||||
# db:
|
||||
# image: postgres
|
||||
# restart: unless-stopped
|
||||
# environment:
|
||||
# POSTGRES_PASSWORD: password #CHANGE THIS
|
||||
# POSTGRES_USER: username
|
||||
# POSTGRES_DB: db
|
||||
# volumes:
|
||||
# - ./db:/var/lib/postgresql/data
|
||||
17
docs/docs.md
|
|
@ -1,17 +0,0 @@
|
|||
|
||||
# Nebula - Documentation
|
||||
|
||||
Hello and welcome to the Nebula Documentation!
|
||||
[](https://opensource.org/licenses/)
|
||||
[]()
|
||||
[]()
|
||||
|
||||
|
||||
## Table of contents
|
||||
- [Guide to Patreon Access](https://git.holy.how/Nebula/NebulaOfficial/docs/guides/PATREON.md)
|
||||
- [How to Deploy (tutorial/guide)](https://git.holy.how/Nebula/NebulaOfficial/docs/guides/deploy.md)
|
||||
|
||||
## Authors
|
||||
|
||||
- [@green](https://www.git.holy.how/green)
|
||||
|
||||
|
|
@ -1 +0,0 @@
|
|||
not done
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
|
||||
# How to access Patreon links!
|
||||
Welcome to this amazing guide :)
|
||||
|
||||
## Step 1
|
||||
**Become a patron...**
|
||||
|
||||
*To follow this tutorial, you must be a patron, duh.*
|
||||
|
||||
|
||||
## Step 2
|
||||
Find a supporter link in Patreon. Posts with links will have the `New links` tag, so just search for that.
|
||||
Some links are experimental and may not work as intended, but we will always tell you if it's experimental.
|
||||
## Step 3
|
||||
|
||||
Use the password from the Patreon post *Same post you got the link from, duh*
|
||||
## Step 4
|
||||
|
||||
Enter the password and Start using Nebula
|
||||
## Troubleshooting
|
||||
|
||||
If the password is incorrect:
|
||||
- Make sure you entered the correct password
|
||||
- Check the latest Patreon post to see if it changed *-- Only happens in emergencies, like password leaking*
|
||||
- If none of those work, then and only then contact chloe@nebula.bio or GreenWorld#0001 on discord.
|
||||
## Authors
|
||||
|
||||
- [@green](https://www.git.holy.how/green)
|
||||
|
||||
|
||||
## Appendix
|
||||
|
||||
This guide is 96% complete. If you are having any trouble accessing or using Patreon/supporter only links, contact chloe@nebula.bio or GreenWorld#0001 on Discord.
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# Supported Sites on Nebula
|
||||
Osana (Stable Build 83cae3b) by Nebula Services
|
||||
- Google.com
|
||||
- Youtube.com
|
||||
- Discord.com
|
||||
- Discord.com AUDIO-ONLY Voice chat
|
||||
- GitHub.com
|
||||
- Bing.com
|
||||
- search.brave.com
|
||||
- 99% of all static sites
|
||||
|
||||
|
||||
UV (Stable Build 8153927) by Titanium Network
|
||||
- Google.com
|
||||
- Youtube.com
|
||||
- GitHub.com
|
||||
- Discord.com
|
||||
- Discord.com AUDIO-ONLY Voice chat
|
||||
- search.brave.com
|
||||
- SealVM/ CollabVM
|
||||
- GeforceNow (Partial Support)
|
||||
- Now.gg (Partial Supprt)
|
||||
- All static sites
|
||||
|
||||
Cyclone (Stable Build 664608d) by Nebula Services
|
||||
- Google.com
|
||||
- Github.com (partial support)
|
||||
- 90% of static websites
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = {
|
||||
apps: [{
|
||||
name: "Site",
|
||||
script: "proxysocks node app.js"
|
||||
}]
|
||||
}
|
||||
620
package-lock.json
generated
|
|
@ -1,620 +0,0 @@
|
|||
{
|
||||
"name": "nebula-web",
|
||||
"version": "7.10",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "nebula-web",
|
||||
"version": "7.10",
|
||||
"license": "GNU AGPL V3",
|
||||
"dependencies": {
|
||||
"@tomphttp/bare-server-node": "^1.0.2-beta-readme5",
|
||||
"crypto-js": "4.1.1",
|
||||
"css-tree": "^2.1.0",
|
||||
"node-fetch": "^3.2.6",
|
||||
"serve-static": "^1.15.0",
|
||||
"ws": "^8.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tomphttp/bare-server-node": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tomphttp/bare-server-node/-/bare-server-node-1.0.3.tgz",
|
||||
"integrity": "sha512-mKMipi9qntDy3wcalWGuK3iKzM0XQT4sW3E6yRXeIU4BxFZVSLzSDejPxL6wWwniKGm0faSwZ4Pk0dQ04JEFNA==",
|
||||
"dependencies": {
|
||||
"commander": "^9.0.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"headers-polyfill": "^3.0.10",
|
||||
"http-errors": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"bare-server-node": "scripts/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "9.4.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz",
|
||||
"integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
|
||||
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
|
||||
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.28",
|
||||
"source-map-js": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
|
||||
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/debug/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz",
|
||||
"integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/headers-polyfill": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.0.10.tgz",
|
||||
"integrity": "sha512-lOhQU7iG3AMcjmb8NIWCa+KwfJw5bY44BoWPtrj5A4iDbSD3ylGf5QcYr0ZyQnhkKQ2GgWNLdF2rfrXtXlF3nQ=="
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"dependencies": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.28",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
|
||||
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.2.10",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz",
|
||||
"integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||
"dependencies": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.18.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
|
||||
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.8.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz",
|
||||
"integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@tomphttp/bare-server-node": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tomphttp/bare-server-node/-/bare-server-node-1.0.3.tgz",
|
||||
"integrity": "sha512-mKMipi9qntDy3wcalWGuK3iKzM0XQT4sW3E6yRXeIU4BxFZVSLzSDejPxL6wWwniKGm0faSwZ4Pk0dQ04JEFNA==",
|
||||
"requires": {
|
||||
"commander": "^9.0.0",
|
||||
"dotenv": "^16.0.1",
|
||||
"headers-polyfill": "^3.0.10",
|
||||
"http-errors": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "9.4.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-9.4.0.tgz",
|
||||
"integrity": "sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw=="
|
||||
},
|
||||
"crypto-js": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
|
||||
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
|
||||
},
|
||||
"css-tree": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
|
||||
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
|
||||
"requires": {
|
||||
"mdn-data": "2.0.28",
|
||||
"source-map-js": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"data-uri-to-buffer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
|
||||
"integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA=="
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "16.0.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz",
|
||||
"integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA=="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
|
||||
},
|
||||
"fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"requires": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
}
|
||||
},
|
||||
"formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"requires": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="
|
||||
},
|
||||
"headers-polyfill": {
|
||||
"version": "3.0.10",
|
||||
"resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.0.10.tgz",
|
||||
"integrity": "sha512-lOhQU7iG3AMcjmb8NIWCa+KwfJw5bY44BoWPtrj5A4iDbSD3ylGf5QcYr0ZyQnhkKQ2GgWNLdF2rfrXtXlF3nQ=="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||
"integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
|
||||
"requires": {
|
||||
"depd": "2.0.0",
|
||||
"inherits": "2.0.4",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"toidentifier": "1.0.1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"mdn-data": {
|
||||
"version": "2.0.28",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
|
||||
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "3.2.10",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz",
|
||||
"integrity": "sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==",
|
||||
"requires": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
}
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.18.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "2.0.0",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "2.0.1"
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
|
||||
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "0.18.0"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||
},
|
||||
"source-map-js": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
|
||||
},
|
||||
"toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
|
||||
},
|
||||
"web-streams-polyfill": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
|
||||
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q=="
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.8.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz",
|
||||
"integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==",
|
||||
"requires": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
85
package.json
|
|
@ -1,23 +1,66 @@
|
|||
{
|
||||
"name": "nebula-web",
|
||||
"version": "7.10",
|
||||
"description": "Explore the web. Freely.",
|
||||
"type": "module",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js"
|
||||
},
|
||||
"keywords": [
|
||||
"proxy"
|
||||
],
|
||||
"author": "Nebula Services",
|
||||
"license": "GNU AGPL V3",
|
||||
"dependencies": {
|
||||
"@tomphttp/bare-server-node": "1.0.2-beta-readme5",
|
||||
"crypto-js": "4.1.1",
|
||||
"css-tree": "^2.1.0",
|
||||
"node-fetch": "^3.2.6",
|
||||
"serve-static": "^1.15.0",
|
||||
"ws": "^8.8.1"
|
||||
}
|
||||
"name": "nebula",
|
||||
"type": "module",
|
||||
"version": "9.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev --host 0.0.0.0 & tsx --watch server/server.ts",
|
||||
"start": "node server/server.js",
|
||||
"build:server": "tsc -p server",
|
||||
"build:client": "astro check && astro build",
|
||||
"build": "npm run build:server & npm run build:client",
|
||||
"bstart": "npm run build && npm run start",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"format:code": "biome format . --write",
|
||||
"format:imports": "biome check . --write",
|
||||
"format": "npm run format:code && npm run format:imports",
|
||||
"version": "changeset version"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
"@astrojs/node": "^9.0.0",
|
||||
"@astrojs/sitemap": "^3.2.1",
|
||||
"@astrojs/svelte": "^7.0.2",
|
||||
"@astrojs/tailwind": "^5.1.4",
|
||||
"@fastify/compress": "^8.0.1",
|
||||
"@fastify/helmet": "^13.0.0",
|
||||
"@fastify/middie": "^9.0.2",
|
||||
"@fastify/multipart": "^9.0.1",
|
||||
"@fastify/static": "^8.0.3",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@mercuryworkshop/bare-mux": "^2.1.7",
|
||||
"@mercuryworkshop/epoxy-transport": "^2.1.27",
|
||||
"@mercuryworkshop/libcurl-transport": "^1.3.15",
|
||||
"@playform/compress": "^0.1.6",
|
||||
"@titaniumnetwork-dev/ultraviolet": "^3.2.10",
|
||||
"@mercuryworkshop/scramjet": "https://github.com/MercuryWorkshop/scramjet/releases/download/latest/mercuryworkshop-scramjet-1.0.2-dev.tgz",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/sequelize": "^4.28.20",
|
||||
"astro": "^5.1.1",
|
||||
"astro-icon": "^1.1.5",
|
||||
"astro-seo": "^0.8.4",
|
||||
"chalk": "^5.4.1",
|
||||
"fastify": "^5.2.0",
|
||||
"gradient-string": "^3.0.0",
|
||||
"pg": "^8.13.1",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.5",
|
||||
"smol-toml": "^1.3.1",
|
||||
"sqlite3": "^5.1.7",
|
||||
"svelte": "^5.16.0",
|
||||
"svelte-french-toast": "^1.2.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.2",
|
||||
"vite-plugin-static-copy": "^2.2.0",
|
||||
"wisp-server-node": "^1.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@changesets/cli": "^2.27.11",
|
||||
"bufferutil": "^4.0.9",
|
||||
"ora": "^8.1.1",
|
||||
"sharp": "^0.33.5",
|
||||
"tsx": "^4.19.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8870
pnpm-lock.yaml
generated
Normal file
BIN
public/classic_theme.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
public/cloaks/canvas.ico
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/cloaks/classroom.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/cloaks/google.png
Normal file
|
After Width: | Height: | Size: 615 B |
BIN
public/cloaks/ps.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/cloaks/wikipedia.ico
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
16
public/favicon.svg
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<svg
|
||||
version="1.2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 400 400"
|
||||
width="400"
|
||||
height="400"
|
||||
fill="#7967dd"
|
||||
style="width: 100%; height: 100%;"
|
||||
><title>nebula</title><g id="svgg"
|
||||
><path
|
||||
id="path0"
|
||||
fill="#7967dd"
|
||||
d="m213.6 84c1 0.3 3.4 0.7 5.1 1 1.8 0.2 4.1 0.7 5.2 1 13.2 4.1 20.3 6.8 24.5 9.1 0.6 0.3 2.3 1.2 3.8 2 2.8 1.4 13.1 8 14.4 9.2 0.5 0.3 2.3 1.9 4.2 3.5 6.7 5.5 15.5 14.9 19.2 20.4 1 1.4 2 2.7 2.2 2.8 0.3 0.1 0.5 0.5 0.5 0.8 0 0.3 1.2 2.2 2.5 4.3 2.3 3.4 7.8 14.3 9.8 19.3 0.8 2.1 0.9 2.2 10 4.9 5.6 1.6 11.1 3.4 11.7 3.8 0.3 0.2 2.4 1.1 4.7 1.9 11.1 4.1 23 12.5 27.3 19.4 5.5 8.7 3.6 20.5-4.5 28.5-3.1 3-7.5 6.4-8.4 6.4-0.3 0-0.7 0.2-0.8 0.5-1.1 2.3-23.3 11.2-35.9 14.3-3.2 0.9-3.5 1.2-5.7 6.8-7.5 19-25.5 40.6-42.3 51.1-1.6 1-3.1 2-3.3 2.2-0.1 0.2-0.9 0.7-1.7 1.2-0.8 0.4-2.2 1.2-2.9 1.6-0.8 0.4-1.6 1-1.8 1.1-0.5 0.5-4.1 2.2-8.1 3.9-1.8 0.8-3.8 1.6-4.5 2-0.7 0.3-3.1 1.1-5.2 1.8-8.3 2.6-9.8 3-21 4.9-6.4 1.1-25.3 1.3-30.5 0.3-1.9-0.3-5.8-1.1-8.6-1.6-6.8-1.3-12.7-3-20-5.7-3.3-1.2-18-8.8-19.7-10.1-0.9-0.7-4.1-3.1-7.1-5.2-5.7-4.1-17.9-15.9-20.7-20-0.9-1.2-2-2.7-2.5-3.2-3.2-3.3-13.7-21.7-13.7-24.1 0-0.6-0.2-1.2-0.6-1.4-0.3-0.2-0.8-1.2-1-2.3-0.4-1.9-1.7-2.7-6.5-3.8-23.2-5.6-43.1-17.2-48.6-28.5-7.1-14.4 4.5-31.3 27.3-39.7 1.8-0.7 4.1-1.6 5.2-2 3.7-1.5 8-2.9 19.5-6.2 1.6-0.5 2.8-1.2 2.8-1.7 0-2.4 9.8-21.6 13.1-25.7 0.2-0.4 1.4-1.8 2.5-3.2 13.8-18.1 30.2-30.6 50.6-38.8 4.3-1.7 6-2.3 14.3-4.5 5.5-1.6 11.2-2.4 18.4-2.9 7.9-0.5 24-0.2 26.8 0.6zm-29.7 17.3c-0.2 0.1-7.3 1.7-12.9 2.7-1.7 0.4-4.3 1.2-5.8 1.9-1.5 0.6-3.9 1.5-5.5 2-1.5 0.4-3.3 1.3-4 2-0.7 0.7-1.7 1.2-2.3 1.2-1.2 0-9.5 4.5-9.8 5.3-0.2 0.3-0.5 0.6-0.9 0.6-1.9 0-19.6 16-23.8 21.6-9.3 12.2-16.1 27.4-19.2 42.7-2 10.1-1.1 37.5 1.4 41.4 0 0.1 3.7 0.9 8.1 1.8 9.5 1.9 12.8 2.4 34.6 4.9 38.5 4.5 107.9 2.2 138.3-4.5 1.4-0.3 4.1-0.9 6.1-1.3 4.2-0.8 3.4 0.2 4.9-7.1 1.6-8.3 1.7-27 0.1-34.6-1.5-7.1-3.2-13.4-3.7-14.2-0.3-0.4-0.7-1.5-0.9-2.6-2.8-12.1-19.2-34.1-33-44.3-2.9-2.1-5.5-4-5.8-4.3-2.8-2.2-4.9-3.1-7.2-3.1-2.1 0-2.6-0.2-2.7-1.3-0.2-1.5-5.7-4.5-6.3-3.5-0.7 1.1-2.4 0.6-2.7-0.7-0.4-1.3-1.2-1.6-5.8-2.1-1.6-0.2-4-0.9-5.5-1.6-3.9-1.8-5.3-2.2-10.2-2.6-4.6-0.4-25.2-0.7-25.5-0.3zm74.3 42.2c7.4 9.8 4.8 23.5-4.6 24.9-6.9 1-20.9-5.8-21-10.2 0-0.2-0.3-0.8-0.8-1.3-6.4-6.8-5-20.8 2.4-24.1 6.7-2.9 17.2 1.8 24 10.7zm-176.4 36.4c-0.1-0.1-4.6 1.1-5.9 1.6-0.7 0.3-3 1.2-5.1 2-9.9 3.8-15.1 6.8-19.6 11.5-3.4 3.5-3.3 4.5 0.5 8.7 1 1 11.3 7.6 12 7.6 0.2 0 1.7 0.6 3.4 1.3 1.6 0.8 3.6 1.6 4.3 1.9 1.8 0.8 9.3 3.3 9.9 3.3 0.3 0 0.3-2 0-4.4-0.6-5.6-0.6-24.5 0.1-29.6 0.3-2.1 0.5-3.9 0.4-3.9zm229.3-0.3c-0.2 0 0 1 0.2 2.1 0.6 2.8 0.6 31.3 0 34-0.5 2.2-0.4 2.2 1.3 1.8 3.1-0.7 12.9-4.5 18.3-7 8.5-4 14.3-10.1 12.6-13.3-1.1-2.1-6.7-7.2-7.9-7.2-0.4 0-0.9-0.2-1-0.5-0.4-1.1-11.8-6.1-19.2-8.5-2.3-0.7-4.2-1.4-4.3-1.4zm-199.4 63.4l-3.1-0.4 1.8 3.2c0.9 1.8 1.9 3.4 2.2 3.5 0.3 0.1 0.5 0.6 0.5 1.1 0 0.4 0.6 1.5 1.3 2.3 0.7 0.9 1.5 1.9 1.8 2.2 0.3 0.4 0.8 1.2 1.1 1.7 6.2 10.7 35.6 33.5 43.3 33.5 0.2 0 1.3 0.4 2.5 0.9 2.5 1.2 10.6 3.4 15.3 4.2 9.5 1.8 11.6 2.1 17.4 2.1 6.6 0 16.4-1.3 22.9-3 2.2-0.5 5.2-1.3 6.8-1.7 1.6-0.3 3.2-0.9 3.5-1.2 0.4-0.3 1.1-0.6 1.6-0.6 2.3 0 22-10.6 24-12.9 0.2-0.2 2.2-1.9 4.5-3.7 5.7-4.5 11.8-11 17.1-18.4 1.6-2.3 3.2-4.5 3.6-4.9 0.4-0.4 0.7-1 0.7-1.2 0-0.2 0.8-1.9 1.9-3.6 1.1-1.7 1.9-3.2 1.9-3.4 0-0.2-3.8 0.4-11 1.6-31.7 5.4-85.1 6.7-126.9 3.1-9.6-0.8-23.1-2.3-27.8-3.2-2.2-0.4-5.3-0.9-6.9-1.2z"
|
||||
class="s0"></path></g
|
||||
></svg
|
||||
>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
19
public/nebula.css
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
:root {
|
||||
--background-primary: #191724;
|
||||
--background-lighter: #16121f;
|
||||
--navbar-color: #26233a;
|
||||
--navbar-height: 60px;
|
||||
--navbar-text-color: #7967dd;
|
||||
--navbar-link-color: #e0def4;
|
||||
--navbar-link-hover-color: gray;
|
||||
--navbar-font: "Roboto";
|
||||
--input-text-color: #e0def4;
|
||||
--input-placeholder-color: white;
|
||||
--input-background-color: #1f1d2e;
|
||||
--input-border-color: #eb6f92;
|
||||
--input-border-size: 1.3px;
|
||||
--navbar-logo-filter: none;
|
||||
--dropdown-option-hover-color: #312a49;
|
||||
--tab-color: var(--black);
|
||||
--border-color: #16121f;
|
||||
}
|
||||
75
public/sw.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
importScripts("/uv/uv.bundle.js");
|
||||
importScripts("/uv/uv.config.js");
|
||||
importScripts('/scram/scramjet.wasm.js');
|
||||
importScripts('/scram/scramjet.shared.js');
|
||||
importScripts('/scram/scramjet.worker.js');
|
||||
importScripts("/workerware/workerware.js");
|
||||
importScripts(__uv$config.sw || "/uv/uv.sw.js");
|
||||
const uv = new UVServiceWorker();
|
||||
const ww = new WorkerWare({ debug: false });
|
||||
const sj = new ScramjetServiceWorker();
|
||||
(async function () {
|
||||
await sj.loadConfig();
|
||||
})();
|
||||
//me when Firefox (thanks vk6)
|
||||
if (navigator.userAgent.includes("Firefox")) {
|
||||
Object.defineProperty(globalThis, "crossOriginIsolated", {
|
||||
value: true,
|
||||
writable: true
|
||||
});
|
||||
}
|
||||
|
||||
//where we handle our plugins!!!
|
||||
self.addEventListener("message", function (event) {
|
||||
console.log(event.data);
|
||||
uv.config.inject = [];
|
||||
//loop over the required data (we don't verify here as types will take care of us :D)
|
||||
event.data.forEach((data) => {
|
||||
if (data.remove) {
|
||||
if (data.type === "page") {
|
||||
const idx = uv.config.inject.indexOf(data.host);
|
||||
uv.config.inject.splice(idx, 1);
|
||||
} else if (data.type === "serviceWorker") {
|
||||
ww.deleteByName(data.name);
|
||||
}
|
||||
} else {
|
||||
if (data.type === "page") {
|
||||
uv.config.inject.push({
|
||||
host: data.host,
|
||||
html: data.html,
|
||||
injectTo: data.injectTo
|
||||
});
|
||||
} else if (data.type === "serviceWorker") {
|
||||
const wwFunction = eval(data.function);
|
||||
ww.use({
|
||||
function: wwFunction ? wwFunction : new Function(data.function),
|
||||
name: data.name,
|
||||
events: data.events
|
||||
});
|
||||
} else {
|
||||
console.error("NO type exists for that. Only serviceWorker & page exist.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", function (event) {
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
const wwRes = await ww.run(event)();
|
||||
if (wwRes.includes(null)) {
|
||||
return;
|
||||
}
|
||||
if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {
|
||||
return await uv.fetch(event);
|
||||
}
|
||||
else if (sj.route(event)) {
|
||||
return await sj.fetch(event);
|
||||
}
|
||||
else {
|
||||
return await fetch(event.request);
|
||||
}
|
||||
})()
|
||||
);
|
||||
});
|
||||
30
public/uv/uv.config.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
self.__uv$config = {
|
||||
prefix: "/~/uv/",
|
||||
bare: "/bare/",
|
||||
encodeUrl: function encode(str) {
|
||||
if (!str) return str;
|
||||
return encodeURIComponent(
|
||||
str
|
||||
.toString()
|
||||
.split("")
|
||||
.map((char, ind) => (ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 3) : char))
|
||||
.join("")
|
||||
);
|
||||
},
|
||||
decodeUrl: function decode(str) {
|
||||
if (!str) return str;
|
||||
let [input, ...search] = str.split("?");
|
||||
|
||||
return (
|
||||
decodeURIComponent(input)
|
||||
.split("")
|
||||
.map((char, ind) => (ind % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 3) : char))
|
||||
.join("") + (search.length ? "?" + search.join("?") : "")
|
||||
);
|
||||
},
|
||||
handler: "/uv/uv.handler.js",
|
||||
client: "/uv/uv.client.js",
|
||||
bundle: "/uv/uv.bundle.js",
|
||||
config: "/uv/uv.config.js",
|
||||
sw: "/uv/uv.sw.js"
|
||||
};
|
||||
171
public/workerware/workerware.js
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
importScripts("/workerware/WWError.js");
|
||||
const dbg = console.log.bind(console, "[WorkerWare]");
|
||||
const time = console.time.bind(console, "[WorkerWare]");
|
||||
const timeEnd = console.timeEnd.bind(console, "[WorkerWare]");
|
||||
|
||||
/*
|
||||
OPTS:
|
||||
debug - Enables debug logging.
|
||||
randomNames - Generate random names for middlewares.
|
||||
timing - Logs timing for each middleware.
|
||||
*/
|
||||
|
||||
const defaultOpt = {
|
||||
debug: false,
|
||||
randomNames: false,
|
||||
timing: false
|
||||
};
|
||||
|
||||
const validEvents = [
|
||||
"abortpayment",
|
||||
"activate",
|
||||
"backgroundfetchabort",
|
||||
"backgroundfetchclick",
|
||||
"backgroundfetchfail",
|
||||
"backgroundfetchsuccess",
|
||||
"canmakepayment",
|
||||
"contentdelete",
|
||||
"cookiechange",
|
||||
"fetch",
|
||||
"install",
|
||||
"message",
|
||||
"messageerror",
|
||||
"notificationclick",
|
||||
"notificationclose",
|
||||
"paymentrequest",
|
||||
"periodicsync",
|
||||
"push",
|
||||
"pushsubscriptionchange",
|
||||
"sync"
|
||||
];
|
||||
|
||||
class WorkerWare {
|
||||
constructor(opt) {
|
||||
this._opt = Object.assign({}, defaultOpt, opt);
|
||||
this._middlewares = [];
|
||||
}
|
||||
info() {
|
||||
return {
|
||||
version: "0.1.0",
|
||||
middlewares: this._middlewares,
|
||||
options: this._opt
|
||||
};
|
||||
}
|
||||
use(middleware) {
|
||||
let validateMW = this.validateMiddleware(middleware);
|
||||
if (validateMW.error) throw new WWError(validateMW.error);
|
||||
// This means the middleware is an anonymous function, or the user is silly and named their function "function"
|
||||
if (middleware.function.name == "function") middleware.name = crypto.randomUUID();
|
||||
if (!middleware.name) middleware.name = middleware.function.name;
|
||||
if (this._opt.randomNames) middleware.name = crypto.randomUUID();
|
||||
if (this._opt.debug) dbg("Adding middleware:", middleware.name);
|
||||
this._middlewares.push(middleware);
|
||||
}
|
||||
// Run all middlewares for the event type passed in.
|
||||
run(event) {
|
||||
const middlewares = this._middlewares;
|
||||
const returnList = [];
|
||||
let fn = async () => {
|
||||
for (let i = 0; i < middlewares.length; i++) {
|
||||
if (middlewares[i].events.includes(event.type)) {
|
||||
if (this._opt.timing) console.time(middlewares[i].name);
|
||||
// Add the configuration to the event object.
|
||||
event.workerware = {
|
||||
config: middlewares[i].configuration || {}
|
||||
};
|
||||
if (!middlewares[i].explicitCall) {
|
||||
let res = await middlewares[i].function(event);
|
||||
if (this._opt.timing) console.timeEnd(middlewares[i].name);
|
||||
returnList.push(res);
|
||||
}
|
||||
}
|
||||
}
|
||||
return returnList;
|
||||
};
|
||||
return fn;
|
||||
}
|
||||
deleteByName(middlewareID) {
|
||||
if (this._opt.debug) dbg("Deleting middleware:", middlewareID);
|
||||
this._middlewares = this._middlewares.filter((mw) => mw.name !== middlewareID);
|
||||
}
|
||||
deleteByEvent(middlewareEvent) {
|
||||
if (this._opt.debug) dbg("Deleting middleware by event:", middlewareEvent);
|
||||
this._middlewares = this._middlewares.filter((mw) => !mw.events.includes(middlewareEvent));
|
||||
}
|
||||
get() {
|
||||
return this._middlewares;
|
||||
}
|
||||
/*
|
||||
Run a single middleware by ID.
|
||||
This assumes that the user knows what they're doing, and is running the middleware on an event that it's supposed to run on.
|
||||
*/
|
||||
runMW(name, event) {
|
||||
const middlewares = this._middlewares;
|
||||
if (this._opt.debug) dbg("Running middleware:", name);
|
||||
// if (middlewares.includes(name)) {
|
||||
// return middlewares[name](event);
|
||||
// } else {
|
||||
// throw new WWError("Middleware not found!");
|
||||
// }
|
||||
let didCall = false;
|
||||
for (let i = 0; i < middlewares.length; i++) {
|
||||
if (middlewares[i].name == name) {
|
||||
didCall = true;
|
||||
event.workerware = {
|
||||
config: middlewares[i].configuration || {}
|
||||
};
|
||||
if (this._opt.timing) console.time(middlewares[i].name);
|
||||
let call = middlewares[i].function(event);
|
||||
if (this._opt.timing) console.timeEnd(middlewares[i].name);
|
||||
return call;
|
||||
}
|
||||
}
|
||||
if (!didCall) {
|
||||
throw new WWError("Middleware not found!");
|
||||
}
|
||||
}
|
||||
// type middlewareManifest = {
|
||||
// function: Function,
|
||||
// name?: string,
|
||||
// events: string[], // Should be a union of validEvents.
|
||||
// configuration?: Object // Optional configuration for the middleware.
|
||||
// }
|
||||
validateMiddleware(middleware) {
|
||||
if (!middleware.function)
|
||||
return {
|
||||
error: "middleware.function is required"
|
||||
};
|
||||
if (typeof middleware.function !== "function")
|
||||
return {
|
||||
error: "middleware.function must be typeof function"
|
||||
};
|
||||
if (
|
||||
typeof middleware.configuration !== "object" &&
|
||||
middleware.configuration !== undefined
|
||||
) {
|
||||
return {
|
||||
error: "middleware.configuration must be typeof object"
|
||||
};
|
||||
}
|
||||
if (!middleware.events)
|
||||
return {
|
||||
error: "middleware.events is required"
|
||||
};
|
||||
if (!Array.isArray(middleware.events))
|
||||
return {
|
||||
error: "middleware.events must be an array"
|
||||
};
|
||||
if (middleware.events.some((ev) => !validEvents.includes(ev)))
|
||||
return {
|
||||
error: "Invalid event type! Must be one of the following: " + validEvents.join(", ")
|
||||
};
|
||||
if (middleware.explicitCall && typeof middleware.explicitCall !== "boolean") {
|
||||
return {
|
||||
error: "middleware.explicitCall must be typeof boolean"
|
||||
};
|
||||
}
|
||||
return {
|
||||
error: undefined
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{ pkgs }: {
|
||||
deps = [
|
||||
pkgs.nodejs-16_x
|
||||
pkgs.nodePackages.typescript-language-server
|
||||
pkgs.yarn
|
||||
pkgs.replitPackages.jest
|
||||
];
|
||||
}
|
||||
95
server/config.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import chalk from "chalk";
|
||||
import { TomlPrimitive, parse } from "smol-toml";
|
||||
|
||||
interface TomlData {
|
||||
marketplace: {
|
||||
enabled: boolean;
|
||||
psk: String;
|
||||
};
|
||||
server: {
|
||||
server: {
|
||||
port: number;
|
||||
wisp: boolean;
|
||||
logging: boolean;
|
||||
};
|
||||
};
|
||||
seo: {
|
||||
enabled: boolean;
|
||||
domain: string;
|
||||
};
|
||||
db: {
|
||||
name: string;
|
||||
username: string;
|
||||
password: string;
|
||||
postgres: boolean;
|
||||
};
|
||||
postgres: {
|
||||
domain: string;
|
||||
port: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface Verify {
|
||||
name: string;
|
||||
typeOF: any;
|
||||
type: any;
|
||||
verifyExtras?: () => boolean | Error;
|
||||
}
|
||||
|
||||
let doc = readFileSync(fileURLToPath(new URL("../config.toml", import.meta.url))).toString();
|
||||
const parsedDoc = parse(doc) as unknown as TomlData;
|
||||
|
||||
function verify(t: Verify[]) {
|
||||
for (let i: number = 0; i !== t.length; i++) {
|
||||
if (typeof t[i].typeOF !== t[i].type) {
|
||||
throw new Error(`Invalid structure: "${t[i].name}" should be a(n) ${t[i].type}`);
|
||||
}
|
||||
if (t[i].verifyExtras) {
|
||||
const extra = t[i].verifyExtras();
|
||||
if (extra !== true) {
|
||||
throw extra;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
verify([
|
||||
{ name: "marketplace", typeOF: parsedDoc.marketplace, type: "object" },
|
||||
{ name: "marketplace.enabled", typeOF: parsedDoc.marketplace.enabled, type: "boolean" },
|
||||
{ name: "marketplace.psk", typeOF: parsedDoc.marketplace.psk, type: "string" },
|
||||
{ name: "server", typeOF: parsedDoc.server, type: "object" },
|
||||
{ name: "server.server", typeOF: parsedDoc.server.server, type: "object" },
|
||||
{ name: "server.server.port", typeOF: parsedDoc.server.server.port, type: "number" },
|
||||
{ name: "server.server.wisp", typeOF: parsedDoc.server.server.wisp, type: "boolean" },
|
||||
{ name: "server.server.logging", typeOF: parsedDoc.server.server.logging, type: "boolean" },
|
||||
{ name: "seo", typeOF: parsedDoc.seo, type: "object" },
|
||||
{ name: "seo.enabled", typeOF: parsedDoc.seo.enabled, type: "boolean" },
|
||||
{ name: "seo.domain", typeOF: parsedDoc.seo.domain, type: "string", verifyExtras: () => {
|
||||
try {
|
||||
new URL(parsedDoc.seo.domain);
|
||||
}
|
||||
catch (e) {
|
||||
return Error(e);
|
||||
}
|
||||
return true;
|
||||
}},
|
||||
{ name: "db", typeOF: parsedDoc.db, type: "object" },
|
||||
{ name: "db.name", typeOF: parsedDoc.db.name, type: "string" },
|
||||
{ name: "db.username", typeOF: parsedDoc.db.username, type: "string" },
|
||||
{ name: "db.password", typeOF: parsedDoc.db.password, type: "string" },
|
||||
{ name: "db.postgres", typeOF: parsedDoc.db.postgres, type: "boolean" },
|
||||
{ name: "postgres", typeOF: parsedDoc.postgres, type: "object" },
|
||||
{ name: "postgres.domain", typeOF: parsedDoc.postgres.domain, type: "string" },
|
||||
{ name: "postgres.port", typeOF: parsedDoc.postgres.port, type: "number" }
|
||||
]);
|
||||
|
||||
if (parsedDoc.marketplace.psk === "CHANGEME") {
|
||||
console.warn(chalk.yellow.bold('PSK should be changed from "CHANGEME"'));
|
||||
}
|
||||
if (parsedDoc.db.password === "password") {
|
||||
console.warn(chalk.red.bold("You should change your DB password!!"));
|
||||
}
|
||||
|
||||
export { TomlData, parsedDoc };
|
||||
98
server/dbSetup.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
import { fileURLToPath } from "node:url";
|
||||
import chalk from "chalk";
|
||||
import ora from "ora";
|
||||
import { ModelStatic } from "sequelize";
|
||||
import { Catalog, CatalogModel } from "./marketplace.js";
|
||||
|
||||
interface Items extends Omit<Catalog, "background_video" | "background_image"> {
|
||||
background_video?: string;
|
||||
background_image?: string;
|
||||
}
|
||||
|
||||
async function installItems(db: ModelStatic<CatalogModel>, items: Items[]) {
|
||||
items.forEach(async (item) => {
|
||||
await db.create({
|
||||
package_name: item.package_name,
|
||||
title: item.title,
|
||||
image: item.image,
|
||||
author: item.author,
|
||||
version: item.version,
|
||||
description: item.description,
|
||||
tags: item.tags,
|
||||
payload: item.payload,
|
||||
background_video: item.background_video,
|
||||
background_image: item.background_image,
|
||||
type: item.type
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function setupDB(db: ModelStatic<CatalogModel>) {
|
||||
//We have some packages that need to be installed if they aren't.
|
||||
const items: Items[] = [
|
||||
{
|
||||
package_name: "com.nebula.gruvbox",
|
||||
title: "Gruvbox",
|
||||
image: "gruvbox.jpg",
|
||||
author: "Nebula Services",
|
||||
version: "1.0.0",
|
||||
description: "The gruvbox theme",
|
||||
tags: ["Theme", "Simple"],
|
||||
payload: "gruvbox.css",
|
||||
type: "theme"
|
||||
},
|
||||
{
|
||||
package_name: "com.nebula.oled",
|
||||
title: "Oled theme",
|
||||
image: "oled.jpg",
|
||||
author: "Nebula Services",
|
||||
version: "1.0.0",
|
||||
description: "A sleek & simple Oled theme for Nebula",
|
||||
tags: ["Theme", "Simple", "Sleek"],
|
||||
payload: "oled.css",
|
||||
type: "theme"
|
||||
},
|
||||
{
|
||||
package_name: "com.nebula.lightTheme",
|
||||
title: "Light Theme",
|
||||
image: "light.png",
|
||||
author: "Nebula Services",
|
||||
version: "1.0.0",
|
||||
description: "A sleek light theme for Nebula",
|
||||
tags: ["Theme", "Simple", "Light"],
|
||||
payload: "light.css",
|
||||
type: "theme"
|
||||
},
|
||||
{
|
||||
package_name: "com.nebula.retro",
|
||||
title: "Retro Theme",
|
||||
image: "retro.png",
|
||||
author: "Nebula Services",
|
||||
version: "1.0.0",
|
||||
description: "Give a retro look to Nebula",
|
||||
tags: ["Theme", "Simple", "Dark", "Retro"],
|
||||
payload: "retro.css",
|
||||
type: "theme"
|
||||
},
|
||||
/* {
|
||||
package_name: 'gyatt',
|
||||
title: 'gyatt',
|
||||
image: 'gyatt',
|
||||
author: "nebuka",
|
||||
version: "2",
|
||||
description: "e",
|
||||
tags: [ "e" ],
|
||||
payload: "gyatt.js",
|
||||
type: "plugin-page"
|
||||
} */
|
||||
//To add plugins: plugin types consist of plugin-sw (workerware) & plugin-page (uv.config.inject)
|
||||
];
|
||||
const dbItems = await db.findAll();
|
||||
if (dbItems.length === 0) {
|
||||
const spinner = ora(chalk.hex("#7967dd")("Performing DB setup...")).start();
|
||||
await installItems(db, items);
|
||||
spinner.succeed(chalk.hex("#eb6f92")("DB setup complete!"));
|
||||
}
|
||||
}
|
||||
|
||||
export { setupDB };
|
||||
1
server/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
declare module "@rubynetwork/rammerhead/src/server/index.js";
|
||||
229
server/marketplace.ts
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
import { createWriteStream } from "node:fs";
|
||||
import { constants, access, mkdir } from "node:fs/promises";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { FastifyInstance, FastifyRequest } from "fastify";
|
||||
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
|
||||
import { parsedDoc } from "./config.js";
|
||||
|
||||
const db = new Sequelize(parsedDoc.db.name, parsedDoc.db.username, parsedDoc.db.password, {
|
||||
host: parsedDoc.db.postgres ? `${parsedDoc.postgres.domain}` : "localhost",
|
||||
port: parsedDoc.db.postgres ? parsedDoc.postgres.port : undefined,
|
||||
dialect: parsedDoc.db.postgres ? "postgres" : "sqlite",
|
||||
logging: parsedDoc.server.server.logging,
|
||||
storage: "database.sqlite" //this is sqlite only
|
||||
});
|
||||
|
||||
type CatalogType = "theme" | "plugin-page" | "plugin-sw";
|
||||
|
||||
interface Catalog {
|
||||
package_name: string;
|
||||
title: string;
|
||||
description: string;
|
||||
author: string;
|
||||
image: string;
|
||||
tags: object;
|
||||
version: string;
|
||||
background_image: string;
|
||||
background_video: string;
|
||||
payload: string;
|
||||
type: CatalogType;
|
||||
}
|
||||
|
||||
interface CatalogModel
|
||||
extends Catalog,
|
||||
Model<InferAttributes<CatalogModel>, InferCreationAttributes<CatalogModel>> {}
|
||||
|
||||
const catalogAssets = db.define<CatalogModel>("catalog_assets", {
|
||||
package_name: { type: DataTypes.STRING, unique: true },
|
||||
title: { type: DataTypes.TEXT },
|
||||
description: { type: DataTypes.TEXT },
|
||||
author: { type: DataTypes.TEXT },
|
||||
image: { type: DataTypes.TEXT },
|
||||
tags: { type: DataTypes.JSON, allowNull: true },
|
||||
version: { type: DataTypes.TEXT },
|
||||
background_image: { type: DataTypes.TEXT, allowNull: true },
|
||||
background_video: { type: DataTypes.TEXT, allowNull: true },
|
||||
payload: { type: DataTypes.TEXT },
|
||||
type: { type: DataTypes.TEXT }
|
||||
});
|
||||
|
||||
function marketplaceAPI(app: FastifyInstance) {
|
||||
app.get("/api/catalog-stats/", (request, reply) => {
|
||||
reply.send({
|
||||
version: "1.0.0",
|
||||
spec: "Nebula Services",
|
||||
enabled: true
|
||||
});
|
||||
});
|
||||
|
||||
// This API returns a list of the assets in the database (SW plugins and themes).
|
||||
// It also returns the number of pages in the database.
|
||||
// It can take a `?page=x` argument to display a different page, with a limit of 20 assets per page.
|
||||
type CatalogAssetsReq = FastifyRequest<{ Querystring: { page: string } }>;
|
||||
app.get("/api/catalog-assets/", async (request: CatalogAssetsReq, reply) => {
|
||||
try {
|
||||
const { page } = request.query;
|
||||
const pageNum: number = parseInt(page, 10) || 1;
|
||||
if (pageNum < 1) {
|
||||
reply.status(400).send({ error: "Page must be a positive number!" });
|
||||
}
|
||||
const offset = (pageNum - 1) * 20;
|
||||
const totalItems = await catalogAssets.count();
|
||||
const dbAssets = await catalogAssets.findAll({ offset: offset, limit: 20 });
|
||||
const assets = dbAssets.reduce((acc, asset) => {
|
||||
acc[asset.package_name] = {
|
||||
title: asset.title,
|
||||
description: asset.description,
|
||||
author: asset.author,
|
||||
image: asset.image,
|
||||
tags: asset.tags,
|
||||
version: asset.version,
|
||||
background_image: asset.background_image,
|
||||
background_video: asset.background_video,
|
||||
payload: asset.payload,
|
||||
type: asset.type
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
return reply.send({ assets, pages: Math.ceil(totalItems / 20) });
|
||||
} catch (error) {
|
||||
return reply.status(500).send({ error: "An error occured" });
|
||||
}
|
||||
});
|
||||
|
||||
type PackageReq = FastifyRequest<{ Params: { package: string } }>;
|
||||
app.get("/api/packages/:package", async (request: PackageReq, reply) => {
|
||||
try {
|
||||
const packageRow = await catalogAssets.findOne({
|
||||
where: { package_name: request.params.package }
|
||||
});
|
||||
if (!packageRow) return reply.status(404).send({ error: "Package not found!" });
|
||||
const details = {
|
||||
title: packageRow.get("title"),
|
||||
description: packageRow.get("description"),
|
||||
image: packageRow.get("image"),
|
||||
author: packageRow.get("author"),
|
||||
tags: packageRow.get("tags"),
|
||||
version: packageRow.get("version"),
|
||||
background_image: packageRow.get("background_image"),
|
||||
background_video: packageRow.get("background_video"),
|
||||
payload: packageRow.get("payload"),
|
||||
type: packageRow.get("type")
|
||||
};
|
||||
reply.send(details);
|
||||
} catch (error) {
|
||||
reply.status(500).send({ error: "An unexpected error occured" });
|
||||
}
|
||||
});
|
||||
|
||||
type UploadReq = FastifyRequest<{ Headers: { psk: string; packagename: string } }>;
|
||||
type CreateReq = FastifyRequest<{
|
||||
Headers: { psk: string };
|
||||
Body: {
|
||||
uuid: string;
|
||||
title: string;
|
||||
image: string;
|
||||
author: string;
|
||||
version: string;
|
||||
description: string;
|
||||
tags: object | any;
|
||||
payload: string;
|
||||
background_video: string;
|
||||
background_image: string;
|
||||
type: CatalogType;
|
||||
};
|
||||
}>;
|
||||
interface VerifyStatus {
|
||||
status: number;
|
||||
error?: Error;
|
||||
}
|
||||
async function verifyReq(
|
||||
request: UploadReq | CreateReq,
|
||||
upload: Boolean,
|
||||
data: any
|
||||
): Promise<VerifyStatus> {
|
||||
if (request.headers.psk !== parsedDoc.marketplace.psk) {
|
||||
return { status: 403, error: new Error("PSK isn't correct!") };
|
||||
} else if (upload && !request.headers.packagename) {
|
||||
return { status: 500, error: new Error("No packagename defined!") };
|
||||
} else if (upload && !data) {
|
||||
return { status: 400, error: new Error("No file uploaded!") };
|
||||
} else {
|
||||
return { status: 200 };
|
||||
}
|
||||
}
|
||||
|
||||
app.post("/api/upload-asset", async (request: UploadReq, reply) => {
|
||||
const data = await request.file();
|
||||
const verify: VerifyStatus = await verifyReq(request, true, data);
|
||||
if (verify.error !== undefined) {
|
||||
reply.status(verify.status).send({ status: verify.error.message });
|
||||
} else {
|
||||
try {
|
||||
await pipeline(
|
||||
data.file,
|
||||
createWriteStream(
|
||||
fileURLToPath(
|
||||
new URL(
|
||||
`../database_assets/${request.headers.packagename}/${data.filename}`,
|
||||
import.meta.url
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
return reply.status(500).send({
|
||||
status: `File couldn't be uploaded! (Package most likely doesn't exist)`
|
||||
});
|
||||
}
|
||||
return reply.status(verify.status).send({ status: "File uploaded successfully!" });
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/create-package", async (request: CreateReq, reply) => {
|
||||
const verify: VerifyStatus = await verifyReq(request, false, undefined);
|
||||
if (verify.error !== undefined) {
|
||||
reply.status(verify.status).send({ status: verify.error.message });
|
||||
} else {
|
||||
const body: Catalog = {
|
||||
package_name: request.body.uuid,
|
||||
title: request.body.title,
|
||||
image: request.body.image,
|
||||
author: request.body.author,
|
||||
version: request.body.version,
|
||||
description: request.body.description,
|
||||
tags: request.body.tags,
|
||||
payload: request.body.payload,
|
||||
background_video: request.body.background_video,
|
||||
background_image: request.body.background_image,
|
||||
type: request.body.type as CatalogType
|
||||
};
|
||||
await catalogAssets.create({
|
||||
package_name: body.package_name,
|
||||
title: body.title,
|
||||
image: body.image,
|
||||
author: body.author,
|
||||
version: body.version,
|
||||
description: body.description,
|
||||
tags: body.tags,
|
||||
payload: body.payload,
|
||||
background_video: body.background_video,
|
||||
background_image: body.background_image,
|
||||
type: body.type
|
||||
});
|
||||
const assets = fileURLToPath(new URL("../database_assets", import.meta.url));
|
||||
try {
|
||||
await access(`${assets}/${body.package_name}/`, constants.F_OK);
|
||||
return reply.status(500).send({ status: "Package already exists!" });
|
||||
} catch (err) {
|
||||
await mkdir(`${assets}/${body.package_name}/`);
|
||||
return reply
|
||||
.status(verify.status)
|
||||
.send({ status: "Package created successfully!" });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export { marketplaceAPI, db, catalogAssets, Catalog, CatalogModel };
|
||||
93
server/server.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { createWriteStream } from "node:fs";
|
||||
import { constants, access, mkdir } from "node:fs/promises";
|
||||
import { pipeline } from "node:stream/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import fastifyCompress from "@fastify/compress";
|
||||
import fastifyHelmet from "@fastify/helmet";
|
||||
import fastifyMiddie from "@fastify/middie";
|
||||
import fastifyMultipart from "@fastify/multipart";
|
||||
import fastifyStatic from "@fastify/static";
|
||||
import chalk from "chalk";
|
||||
import Fastify, { FastifyReply, FastifyRequest } from "fastify";
|
||||
import gradient from "gradient-string";
|
||||
//@ts-ignore WHY would I want this typechecked AT ALL
|
||||
import { handler as ssrHandler } from "../dist/server/entry.mjs";
|
||||
import { parsedDoc } from "./config.js";
|
||||
import { setupDB } from "./dbSetup.js";
|
||||
import { catalogAssets, marketplaceAPI } from "./marketplace.js";
|
||||
import { serverFactory } from "./serverFactory.js";
|
||||
|
||||
const app = Fastify({
|
||||
logger: parsedDoc.server.server.logging,
|
||||
ignoreDuplicateSlashes: true,
|
||||
ignoreTrailingSlash: true,
|
||||
serverFactory: serverFactory
|
||||
});
|
||||
|
||||
await app.register(fastifyCompress, {
|
||||
encodings: ["br", "gzip", "deflate"]
|
||||
});
|
||||
|
||||
await app.register(fastifyMultipart, {
|
||||
limits: {
|
||||
fileSize: 25 * 1024 * 1024,
|
||||
parts: Infinity
|
||||
},
|
||||
});
|
||||
|
||||
await app.register(fastifyHelmet, {
|
||||
xPoweredBy: false,
|
||||
crossOriginEmbedderPolicy: true,
|
||||
crossOriginOpenerPolicy: true,
|
||||
contentSecurityPolicy: false //Disabled because astro DOES NOT LIKE IT
|
||||
});
|
||||
|
||||
await app.register(fastifyStatic, {
|
||||
root: fileURLToPath(new URL("../dist/client", import.meta.url))
|
||||
});
|
||||
|
||||
//Our marketplace API. Not middleware as I don't want to deal with that LOL. Just a function that passes our app to it.
|
||||
if (parsedDoc.marketplace.enabled) {
|
||||
await app.register(fastifyStatic, {
|
||||
root: fileURLToPath(new URL("../database_assets", import.meta.url)),
|
||||
prefix: "/packages/",
|
||||
decorateReply: false
|
||||
});
|
||||
marketplaceAPI(app);
|
||||
}
|
||||
|
||||
await app.register(fastifyMiddie);
|
||||
|
||||
app.use(ssrHandler);
|
||||
|
||||
const port: number =
|
||||
parseInt(process.env.PORT as string) || parsedDoc.server.server.port || parseInt("8080");
|
||||
const titleText = `
|
||||
_ _ _ _ ____ _
|
||||
| \\ | | ___| |__ _ _| | __ _ / ___| ___ _ ____ _(_) ___ ___ ___
|
||||
| \\| |/ _ \\ '_ \\| | | | |/ _' | \\___ \\ / _ \\ '__\\ \\ / / |/ __/ _ \\/ __|
|
||||
| |\\ | __/ |_) | |_| | | (_| | ___) | __/ | \\ V /| | (_| __/\\__ \\
|
||||
|_| \\_|\\___|_.__/ \\__,_|_|\\__,_| |____/ \\___|_| \\_/ |_|\\___\\___||___/
|
||||
`;
|
||||
const titleColors = {
|
||||
purple: "#7967dd",
|
||||
pink: "#eb6f92"
|
||||
};
|
||||
|
||||
console.log(gradient(Object.values(titleColors)).multiline(titleText as string));
|
||||
app.listen({ port: port, host: "0.0.0.0" }).then(async () => {
|
||||
console.log(
|
||||
chalk.hex("#7967dd")(
|
||||
`Server listening on ${chalk.hex("#eb6f92").bold("http://localhost:" + port + "/")}`
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
chalk.hex("#7967dd")(
|
||||
`Server also listening on ${chalk.hex("#eb6f92").bold("http://0.0.0.0:" + port + "/")}`
|
||||
)
|
||||
);
|
||||
if (parsedDoc.marketplace.enabled) {
|
||||
await catalogAssets.sync();
|
||||
await setupDB(catalogAssets);
|
||||
}
|
||||
});
|
||||
29
server/serverFactory.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { createServer } from "node:http";
|
||||
import { FastifyServerFactory, FastifyServerFactoryHandler, RawServerDefault } from "fastify";
|
||||
import wisp from "wisp-server-node";
|
||||
import { LOG_LEVEL, WispOptions } from "wisp-server-node/dist/Types.js";
|
||||
import { parsedDoc } from "./config.js";
|
||||
|
||||
const wispOptions: WispOptions = {
|
||||
logLevel: parsedDoc.server.server.logging ? LOG_LEVEL.DEBUG : LOG_LEVEL.NONE,
|
||||
pingInterval: 30
|
||||
};
|
||||
|
||||
const serverFactory: FastifyServerFactory = (
|
||||
handler: FastifyServerFactoryHandler
|
||||
): RawServerDefault => {
|
||||
const httpServer = createServer();
|
||||
httpServer.on("request", (req, res) => {
|
||||
handler(req, res);
|
||||
});
|
||||
httpServer.on("upgrade", (req, socket, head) => {
|
||||
if (parsedDoc.server.server.wisp) {
|
||||
if (req.url?.endsWith("/wisp/")) {
|
||||
wisp.routeRequest(req, socket as any, head, wispOptions);
|
||||
}
|
||||
}
|
||||
});
|
||||
return httpServer;
|
||||
};
|
||||
|
||||
export { serverFactory };
|
||||
11
server/tsconfig.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noEmit": false,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
BIN
src/assets/classic_theme.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/credits/libcurl.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
src/assets/credits/mercury.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/assets/credits/motortruck1221.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
src/assets/credits/rammerhead.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
src/assets/credits/rift.jpeg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
src/assets/credits/uv.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/fortnite.jpg
Normal file
|
After Width: | Height: | Size: 13 KiB |
61
src/components/Card.astro
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
---
|
||||
interface Props {
|
||||
title: string;
|
||||
body: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
const { href, title, body } = Astro.props;
|
||||
---
|
||||
|
||||
<li class="link-card">
|
||||
<a href={href}>
|
||||
<h2>
|
||||
{title}
|
||||
<span>→</span>
|
||||
</h2>
|
||||
<p>
|
||||
{body}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
<style>
|
||||
.link-card {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
padding: 1px;
|
||||
background-color: #23262d;
|
||||
background-image: none;
|
||||
background-size: 400%;
|
||||
border-radius: 7px;
|
||||
background-position: 100%;
|
||||
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.link-card > a {
|
||||
width: 100%;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
padding: calc(1.5rem - 1px);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
background-color: #23262d;
|
||||
opacity: 0.8;
|
||||
}
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
p {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.link-card:is(:hover, :focus-within) {
|
||||
background-position: 0;
|
||||
background-image: var(--accent-gradient);
|
||||
}
|
||||
.link-card:is(:hover, :focus-within) h2 {
|
||||
color: rgb(var(--accent-light));
|
||||
}
|
||||
</style>
|
||||
71
src/components/Header.astro
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
import { MARKETPLACE_ENABLED } from "astro:env/client";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getLangFromUrl, useTranslations } from "../i18n/utils";
|
||||
import HeaderButton from "./HeaderButton.astro";
|
||||
import Logo from "./Logo.astro";
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
---
|
||||
|
||||
<div
|
||||
id="navbar"
|
||||
class="flex h-16 flex-row items-center justify-end border-b-2 border-border-color bg-navbar-color px-4 z-30 relative"
|
||||
>
|
||||
<div class="w-1/8">
|
||||
{/* Typical desktop menu */}
|
||||
<div class="relative flex-row hidden lg:flex">
|
||||
<HeaderButton text={t("header.home")} route={`/${lang}/`}>
|
||||
<Icon
|
||||
name="ph:house-bold"
|
||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||
/>
|
||||
</HeaderButton>
|
||||
<HeaderButton text={t("header.games")} route={`/${lang}/games/`}>
|
||||
<Icon
|
||||
name="ph:cube"
|
||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||
/>
|
||||
{
|
||||
/* Astro won't let us pass the icon as a prop so it's going into the outlet here. */
|
||||
}
|
||||
</HeaderButton>
|
||||
<HeaderButton
|
||||
text={t("header.settings")}
|
||||
route={`/${lang}/settings/appearance`}
|
||||
>
|
||||
<Icon
|
||||
name="ph:wrench-fill"
|
||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||
/>
|
||||
</HeaderButton>
|
||||
{MARKETPLACE_ENABLED &&
|
||||
<HeaderButton text={t("header.catalog")} route={`/${lang}/catalog/1`}>
|
||||
<Icon
|
||||
name="ph:shopping-bag-open-fill"
|
||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||
/>
|
||||
</HeaderButton>
|
||||
}
|
||||
<HeaderButton text={t("header.morelinks")}>
|
||||
<Icon
|
||||
name="ph:link-bold"
|
||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||
/>
|
||||
</HeaderButton>
|
||||
</div>
|
||||
{/* Mobile hamburger menu */}
|
||||
<div class="flex lg:hidden cursor-pointer" id="mobileNavTrigger">
|
||||
<Icon
|
||||
name="ph:text-align-justify-bold"
|
||||
class="h-9 w-9 text-text-color"
|
||||
id="hamburger_menu"
|
||||
/>
|
||||
<Icon
|
||||
name="ph:caret-right-bold"
|
||||
class="h-9 w-9 text-text-color hidden"
|
||||
id="right_caret"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
16
src/components/HeaderButton.astro
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
const { text, route } = Astro.props;
|
||||
---
|
||||
|
||||
<a
|
||||
class="group flex w-full flex-row items-center justify-center border-t-2 border-solid border-navbar-text-color p-4 lg:border-none h-1/3 lg:h-fit"
|
||||
id="header_anchor"
|
||||
href={route}
|
||||
>
|
||||
<slot />
|
||||
<span
|
||||
class="font-roboto pl-2 text-center text-3xl font-bold text-text-color roboto transition duration-500 group-hover:text-text-hover-color lg:text-xl text-nowrap"
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
</a>
|
||||
23
src/components/Loading.astro
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<div class="w-full h-full bg-primary text-navbar-text-color flex justify-center items-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 400 400" width="400" height="400" fill="#5e17eb" class="animate-pulse-brighter w-48 h-48">
|
||||
<g id="svgg">
|
||||
<path id="path0" fill="currentColor" fill-rule="evenodd" class="s0" d="m213.6 84c1 0.3 3.4 0.7 5.1 1 1.8 0.2 4.1 0.7 5.2 1 13.2 4.1 20.3 6.8 24.5 9.1 0.6 0.3 2.3 1.2 3.8 2 2.8 1.4 13.1 8 14.4 9.2 0.5 0.3 2.3 1.9 4.2 3.5 6.7 5.5 15.5 14.9 19.2 20.4 1 1.4 2 2.7 2.2 2.8 0.3 0.1 0.5 0.5 0.5 0.8 0 0.3 1.2 2.2 2.5 4.3 2.3 3.4 7.8 14.3 9.8 19.3 0.8 2.1 0.9 2.2 10 4.9 5.6 1.6 11.1 3.4 11.7 3.8 0.3 0.2 2.4 1.1 4.7 1.9 11.1 4.1 23 12.5 27.3 19.4 5.5 8.7 3.6 20.5-4.5 28.5-3.1 3-7.5 6.4-8.4 6.4-0.3 0-0.7 0.2-0.8 0.5-1.1 2.3-23.3 11.2-35.9 14.3-3.2 0.9-3.5 1.2-5.7 6.8-7.5 19-25.5 40.6-42.3 51.1-1.6 1-3.1 2-3.3 2.2-0.1 0.2-0.9 0.7-1.7 1.2-0.8 0.4-2.2 1.2-2.9 1.6-0.8 0.4-1.6 1-1.8 1.1-0.5 0.5-4.1 2.2-8.1 3.9-1.8 0.8-3.8 1.6-4.5 2-0.7 0.3-3.1 1.1-5.2 1.8-8.3 2.6-9.8 3-21 4.9-6.4 1.1-25.3 1.3-30.5 0.3-1.9-0.3-5.8-1.1-8.6-1.6-6.8-1.3-12.7-3-20-5.7-3.3-1.2-18-8.8-19.7-10.1-0.9-0.7-4.1-3.1-7.1-5.2-5.7-4.1-17.9-15.9-20.7-20-0.9-1.2-2-2.7-2.5-3.2-3.2-3.3-13.7-21.7-13.7-24.1 0-0.6-0.2-1.2-0.6-1.4-0.3-0.2-0.8-1.2-1-2.3-0.4-1.9-1.7-2.7-6.5-3.8-23.2-5.6-43.1-17.2-48.6-28.5-7.1-14.4 4.5-31.3 27.3-39.7 1.8-0.7 4.1-1.6 5.2-2 3.7-1.5 8-2.9 19.5-6.2 1.6-0.5 2.8-1.2 2.8-1.7 0-2.4 9.8-21.6 13.1-25.7 0.2-0.4 1.4-1.8 2.5-3.2 13.8-18.1 30.2-30.6 50.6-38.8 4.3-1.7 6-2.3 14.3-4.5 5.5-1.6 11.2-2.4 18.4-2.9 7.9-0.5 24-0.2 26.8 0.6zm-29.7 17.3c-0.2 0.1-7.3 1.7-12.9 2.7-1.7 0.4-4.3 1.2-5.8 1.9-1.5 0.6-3.9 1.5-5.5 2-1.5 0.4-3.3 1.3-4 2-0.7 0.7-1.7 1.2-2.3 1.2-1.2 0-9.5 4.5-9.8 5.3-0.2 0.3-0.5 0.6-0.9 0.6-1.9 0-19.6 16-23.8 21.6-9.3 12.2-16.1 27.4-19.2 42.7-2 10.1-1.1 37.5 1.4 41.4 0 0.1 3.7 0.9 8.1 1.8 9.5 1.9 12.8 2.4 34.6 4.9 38.5 4.5 107.9 2.2 138.3-4.5 1.4-0.3 4.1-0.9 6.1-1.3 4.2-0.8 3.4 0.2 4.9-7.1 1.6-8.3 1.7-27 0.1-34.6-1.5-7.1-3.2-13.4-3.7-14.2-0.3-0.4-0.7-1.5-0.9-2.6-2.8-12.1-19.2-34.1-33-44.3-2.9-2.1-5.5-4-5.8-4.3-2.8-2.2-4.9-3.1-7.2-3.1-2.1 0-2.6-0.2-2.7-1.3-0.2-1.5-5.7-4.5-6.3-3.5-0.7 1.1-2.4 0.6-2.7-0.7-0.4-1.3-1.2-1.6-5.8-2.1-1.6-0.2-4-0.9-5.5-1.6-3.9-1.8-5.3-2.2-10.2-2.6-4.6-0.4-25.2-0.7-25.5-0.3zm74.3 42.2c7.4 9.8 4.8 23.5-4.6 24.9-6.9 1-20.9-5.8-21-10.2 0-0.2-0.3-0.8-0.8-1.3-6.4-6.8-5-20.8 2.4-24.1 6.7-2.9 17.2 1.8 24 10.7zm-176.4 36.4c-0.1-0.1-4.6 1.1-5.9 1.6-0.7 0.3-3 1.2-5.1 2-9.9 3.8-15.1 6.8-19.6 11.5-3.4 3.5-3.3 4.5 0.5 8.7 1 1 11.3 7.6 12 7.6 0.2 0 1.7 0.6 3.4 1.3 1.6 0.8 3.6 1.6 4.3 1.9 1.8 0.8 9.3 3.3 9.9 3.3 0.3 0 0.3-2 0-4.4-0.6-5.6-0.6-24.5 0.1-29.6 0.3-2.1 0.5-3.9 0.4-3.9zm229.3-0.3c-0.2 0 0 1 0.2 2.1 0.6 2.8 0.6 31.3 0 34-0.5 2.2-0.4 2.2 1.3 1.8 3.1-0.7 12.9-4.5 18.3-7 8.5-4 14.3-10.1 12.6-13.3-1.1-2.1-6.7-7.2-7.9-7.2-0.4 0-0.9-0.2-1-0.5-0.4-1.1-11.8-6.1-19.2-8.5-2.3-0.7-4.2-1.4-4.3-1.4zm-199.4 63.4l-3.1-0.4 1.8 3.2c0.9 1.8 1.9 3.4 2.2 3.5 0.3 0.1 0.5 0.6 0.5 1.1 0 0.4 0.6 1.5 1.3 2.3 0.7 0.9 1.5 1.9 1.8 2.2 0.3 0.4 0.8 1.2 1.1 1.7 6.2 10.7 35.6 33.5 43.3 33.5 0.2 0 1.3 0.4 2.5 0.9 2.5 1.2 10.6 3.4 15.3 4.2 9.5 1.8 11.6 2.1 17.4 2.1 6.6 0 16.4-1.3 22.9-3 2.2-0.5 5.2-1.3 6.8-1.7 1.6-0.3 3.2-0.9 3.5-1.2 0.4-0.3 1.1-0.6 1.6-0.6 2.3 0 22-10.6 24-12.9 0.2-0.2 2.2-1.9 4.5-3.7 5.7-4.5 11.8-11 17.1-18.4 1.6-2.3 3.2-4.5 3.6-4.9 0.4-0.4 0.7-1 0.7-1.2 0-0.2 0.8-1.9 1.9-3.6 1.1-1.7 1.9-3.2 1.9-3.4 0-0.2-3.8 0.4-11 1.6-31.7 5.4-85.1 6.7-126.9 3.1-9.6-0.8-23.1-2.3-27.8-3.2-2.2-0.4-5.3-0.9-6.9-1.2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<style>
|
||||
@keyframes pulse-brighter {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.animate-pulse-brighter {
|
||||
animation: pulse-brighter 2s infinite;
|
||||
height: 11rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
15
src/components/Logo.astro
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<svg
|
||||
version="1.2"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 400 400"
|
||||
width="400"
|
||||
height="400"
|
||||
style="width: 100%; height: 100%;"
|
||||
><title>nebula</title><g id="svgg"
|
||||
><path
|
||||
id="path0"
|
||||
fill-rule="evenodd"
|
||||
d="m213.6 84c1 0.3 3.4 0.7 5.1 1 1.8 0.2 4.1 0.7 5.2 1 13.2 4.1 20.3 6.8 24.5 9.1 0.6 0.3 2.3 1.2 3.8 2 2.8 1.4 13.1 8 14.4 9.2 0.5 0.3 2.3 1.9 4.2 3.5 6.7 5.5 15.5 14.9 19.2 20.4 1 1.4 2 2.7 2.2 2.8 0.3 0.1 0.5 0.5 0.5 0.8 0 0.3 1.2 2.2 2.5 4.3 2.3 3.4 7.8 14.3 9.8 19.3 0.8 2.1 0.9 2.2 10 4.9 5.6 1.6 11.1 3.4 11.7 3.8 0.3 0.2 2.4 1.1 4.7 1.9 11.1 4.1 23 12.5 27.3 19.4 5.5 8.7 3.6 20.5-4.5 28.5-3.1 3-7.5 6.4-8.4 6.4-0.3 0-0.7 0.2-0.8 0.5-1.1 2.3-23.3 11.2-35.9 14.3-3.2 0.9-3.5 1.2-5.7 6.8-7.5 19-25.5 40.6-42.3 51.1-1.6 1-3.1 2-3.3 2.2-0.1 0.2-0.9 0.7-1.7 1.2-0.8 0.4-2.2 1.2-2.9 1.6-0.8 0.4-1.6 1-1.8 1.1-0.5 0.5-4.1 2.2-8.1 3.9-1.8 0.8-3.8 1.6-4.5 2-0.7 0.3-3.1 1.1-5.2 1.8-8.3 2.6-9.8 3-21 4.9-6.4 1.1-25.3 1.3-30.5 0.3-1.9-0.3-5.8-1.1-8.6-1.6-6.8-1.3-12.7-3-20-5.7-3.3-1.2-18-8.8-19.7-10.1-0.9-0.7-4.1-3.1-7.1-5.2-5.7-4.1-17.9-15.9-20.7-20-0.9-1.2-2-2.7-2.5-3.2-3.2-3.3-13.7-21.7-13.7-24.1 0-0.6-0.2-1.2-0.6-1.4-0.3-0.2-0.8-1.2-1-2.3-0.4-1.9-1.7-2.7-6.5-3.8-23.2-5.6-43.1-17.2-48.6-28.5-7.1-14.4 4.5-31.3 27.3-39.7 1.8-0.7 4.1-1.6 5.2-2 3.7-1.5 8-2.9 19.5-6.2 1.6-0.5 2.8-1.2 2.8-1.7 0-2.4 9.8-21.6 13.1-25.7 0.2-0.4 1.4-1.8 2.5-3.2 13.8-18.1 30.2-30.6 50.6-38.8 4.3-1.7 6-2.3 14.3-4.5 5.5-1.6 11.2-2.4 18.4-2.9 7.9-0.5 24-0.2 26.8 0.6zm-29.7 17.3c-0.2 0.1-7.3 1.7-12.9 2.7-1.7 0.4-4.3 1.2-5.8 1.9-1.5 0.6-3.9 1.5-5.5 2-1.5 0.4-3.3 1.3-4 2-0.7 0.7-1.7 1.2-2.3 1.2-1.2 0-9.5 4.5-9.8 5.3-0.2 0.3-0.5 0.6-0.9 0.6-1.9 0-19.6 16-23.8 21.6-9.3 12.2-16.1 27.4-19.2 42.7-2 10.1-1.1 37.5 1.4 41.4 0 0.1 3.7 0.9 8.1 1.8 9.5 1.9 12.8 2.4 34.6 4.9 38.5 4.5 107.9 2.2 138.3-4.5 1.4-0.3 4.1-0.9 6.1-1.3 4.2-0.8 3.4 0.2 4.9-7.1 1.6-8.3 1.7-27 0.1-34.6-1.5-7.1-3.2-13.4-3.7-14.2-0.3-0.4-0.7-1.5-0.9-2.6-2.8-12.1-19.2-34.1-33-44.3-2.9-2.1-5.5-4-5.8-4.3-2.8-2.2-4.9-3.1-7.2-3.1-2.1 0-2.6-0.2-2.7-1.3-0.2-1.5-5.7-4.5-6.3-3.5-0.7 1.1-2.4 0.6-2.7-0.7-0.4-1.3-1.2-1.6-5.8-2.1-1.6-0.2-4-0.9-5.5-1.6-3.9-1.8-5.3-2.2-10.2-2.6-4.6-0.4-25.2-0.7-25.5-0.3zm74.3 42.2c7.4 9.8 4.8 23.5-4.6 24.9-6.9 1-20.9-5.8-21-10.2 0-0.2-0.3-0.8-0.8-1.3-6.4-6.8-5-20.8 2.4-24.1 6.7-2.9 17.2 1.8 24 10.7zm-176.4 36.4c-0.1-0.1-4.6 1.1-5.9 1.6-0.7 0.3-3 1.2-5.1 2-9.9 3.8-15.1 6.8-19.6 11.5-3.4 3.5-3.3 4.5 0.5 8.7 1 1 11.3 7.6 12 7.6 0.2 0 1.7 0.6 3.4 1.3 1.6 0.8 3.6 1.6 4.3 1.9 1.8 0.8 9.3 3.3 9.9 3.3 0.3 0 0.3-2 0-4.4-0.6-5.6-0.6-24.5 0.1-29.6 0.3-2.1 0.5-3.9 0.4-3.9zm229.3-0.3c-0.2 0 0 1 0.2 2.1 0.6 2.8 0.6 31.3 0 34-0.5 2.2-0.4 2.2 1.3 1.8 3.1-0.7 12.9-4.5 18.3-7 8.5-4 14.3-10.1 12.6-13.3-1.1-2.1-6.7-7.2-7.9-7.2-0.4 0-0.9-0.2-1-0.5-0.4-1.1-11.8-6.1-19.2-8.5-2.3-0.7-4.2-1.4-4.3-1.4zm-199.4 63.4l-3.1-0.4 1.8 3.2c0.9 1.8 1.9 3.4 2.2 3.5 0.3 0.1 0.5 0.6 0.5 1.1 0 0.4 0.6 1.5 1.3 2.3 0.7 0.9 1.5 1.9 1.8 2.2 0.3 0.4 0.8 1.2 1.1 1.7 6.2 10.7 35.6 33.5 43.3 33.5 0.2 0 1.3 0.4 2.5 0.9 2.5 1.2 10.6 3.4 15.3 4.2 9.5 1.8 11.6 2.1 17.4 2.1 6.6 0 16.4-1.3 22.9-3 2.2-0.5 5.2-1.3 6.8-1.7 1.6-0.3 3.2-0.9 3.5-1.2 0.4-0.3 1.1-0.6 1.6-0.6 2.3 0 22-10.6 24-12.9 0.2-0.2 2.2-1.9 4.5-3.7 5.7-4.5 11.8-11 17.1-18.4 1.6-2.3 3.2-4.5 3.6-4.9 0.4-0.4 0.7-1 0.7-1.2 0-0.2 0.8-1.9 1.9-3.6 1.1-1.7 1.9-3.2 1.9-3.4 0-0.2-3.8 0.4-11 1.6-31.7 5.4-85.1 6.7-126.9 3.1-9.6-0.8-23.1-2.3-27.8-3.2-2.2-0.4-5.3-0.9-6.9-1.2z"
|
||||
class="s0"></path></g
|
||||
></svg
|
||||
>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
102
src/components/MobileNavigation.astro
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
---
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getLangFromUrl, useTranslations } from "../i18n/utils";
|
||||
import HeaderButton from "./HeaderButton.astro";
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
import { MARKETPLACE_ENABLED } from "astro:env/client";
|
||||
---
|
||||
|
||||
<div class="h-full mt-16 flex w-full flex-col justify-evenly bg-navbar-color m-auto" id="mobilenav">
|
||||
<HeaderButton text={t("header.home")} route={`/${lang}/`}>
|
||||
<Icon
|
||||
name="ph:house-bold"
|
||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||
/>
|
||||
</HeaderButton>
|
||||
<HeaderButton text={t("header.games")} route={`/${lang}/games/`}>
|
||||
<Icon
|
||||
name="ph:cube"
|
||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||
/>
|
||||
</HeaderButton>
|
||||
<HeaderButton
|
||||
text={t("header.settings")}
|
||||
route={`/${lang}/settings/appearance`}
|
||||
>
|
||||
<Icon
|
||||
name="ph:wrench-fill"
|
||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||
/>
|
||||
</HeaderButton>
|
||||
{MARKETPLACE_ENABLED &&
|
||||
<HeaderButton text={t("header.catalog")} route={`/${lang}/catalog/1`}>
|
||||
<Icon
|
||||
name="ph:shopping-bag-open-fill"
|
||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||
/>
|
||||
</HeaderButton>
|
||||
}
|
||||
<HeaderButton text={t("header.morelinks")}>
|
||||
<Icon
|
||||
name="ph:link-bold"
|
||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||
/>
|
||||
</HeaderButton>
|
||||
</div>
|
||||
<style is:global>
|
||||
#mobilenav {
|
||||
-webkit-transition-duration: 600ms;
|
||||
transition-duration: 600ms;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
#mobilenavwrapper {
|
||||
-webkit-transition-duration: 600ms;
|
||||
transition-duration: 600ms;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { EventHandler } from "@utils/events";
|
||||
import { Elements } from "@utils/index";
|
||||
|
||||
const init = async () => {
|
||||
const els = Elements.select([
|
||||
{ type: 'id', val: 'mobileNavTrigger' },
|
||||
{ type: 'id', val: 'right_caret' },
|
||||
{ type: 'id', val: 'hamburger_menu' },
|
||||
{ type: 'id', val: 'mobilenavwrapper' },
|
||||
{ type: 'id', val: 'mobilenav' }
|
||||
]);
|
||||
const trigger = Elements.exists<HTMLDivElement>(await els.next());
|
||||
const rightCaret = Elements.exists<SVGElement>(await els.next());
|
||||
const hamburgerMenu = Elements.exists<SVGElement>(await els.next());
|
||||
const wrapper = Elements.exists<HTMLDivElement>(await els.next());
|
||||
const mnm = Elements.exists<HTMLDivElement>(await els.next());
|
||||
let isOpen = false;
|
||||
Elements.attachEvent(trigger, "click", async () => {
|
||||
console.log(isOpen);
|
||||
if (isOpen) {
|
||||
rightCaret.style.display = "none";
|
||||
hamburgerMenu.style.display = "block";
|
||||
mnm.style.transform = "translateX(100%)";
|
||||
wrapper.style.transform = "translateX(100%)";
|
||||
isOpen = false;
|
||||
}
|
||||
else {
|
||||
hamburgerMenu.style.display = "none";
|
||||
rightCaret.style.display = "block";
|
||||
mnm.style.display = "flex";
|
||||
mnm.style.transform = "translateX(0%)";
|
||||
wrapper.style.transform = "translateX(0%)";
|
||||
isOpen = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
new EventHandler({
|
||||
events: {
|
||||
"astro:page-load": init
|
||||
},
|
||||
logging: false
|
||||
}).bind();
|
||||
</script>
|
||||
17
src/components/SidebarButton.astro
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
const { title, route } = Astro.props;
|
||||
---
|
||||
|
||||
<a
|
||||
href={route}
|
||||
class="snap-center snap-always group flex flex-col items-center md:p-0 max-sm:p-3 sm:p-3 bg-navbar-color w-full rounded-3xl md:flex-row md:bg-none md:rounded-none"
|
||||
>
|
||||
<div class="xl:p-2 max-sm:p-2 sm:p-2 md:p-0">
|
||||
<slot />
|
||||
</div>
|
||||
<div
|
||||
class="max-md:min-w-24 font-roboto text-center font-bold text-text-color roboto transition duration-500 group-hover:text-text-hover-color md:text-xl text-nowrap"
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
</a>
|
||||
58
src/components/catalog/CatalogCard.astro
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
interface Props {
|
||||
page: number | string | undefined;
|
||||
lang: string;
|
||||
}
|
||||
interface Asset {
|
||||
title: string;
|
||||
description: string;
|
||||
author: string;
|
||||
image: string;
|
||||
tags: [];
|
||||
version: string;
|
||||
background_image: string | null;
|
||||
background_video: string | null;
|
||||
payload: string;
|
||||
type: "theme" | "plugin-sw" | "plugin-page";
|
||||
}
|
||||
const { page, lang } = Astro.props;
|
||||
const getAssets = async () => {
|
||||
const res = await fetch(new URL(`/api/catalog-assets?page=${page}`, Astro.url));
|
||||
const data = await res.json();
|
||||
return data.assets;
|
||||
};
|
||||
const assets = await getAssets();
|
||||
---
|
||||
<div class="text-3xl font-roboto font-bold text-text-color p-10">
|
||||
{Object.keys(assets).length > 0 && (
|
||||
<div class="flex flex-row gap-6 flex-wrap justify-center">
|
||||
{Object.entries(assets).map((asset) => {
|
||||
const pName = asset[0];
|
||||
const a = asset[1] as unknown as Asset;
|
||||
return (
|
||||
<a href={`/${lang}/catalog/package/${pName}`}>
|
||||
<div class="bg-navbar-color w-64 rounded-3xl shadow-lg overflow-hidden transition-transform duration-300 hover:scale-105 text-text-color">
|
||||
<img src={`/packages/${pName}/${a.image}`} alt={a.title} class="w-full h-40 object-cover" />
|
||||
<div class="p-6 text-sm">
|
||||
<p class="font-semibold text-2xl mb-2"> {a.title} </p>
|
||||
<p class="mb-4"> {a.description} </p>
|
||||
<div class="flex flex-wrap gap-2 mb-4 w-full">
|
||||
{a.tags.map((tag) => (
|
||||
<p class="bg-navbar-text-color text-navbar-color font-bold px-3 py-1 rounded-md text-center"> { tag } </p>
|
||||
))}
|
||||
</div>
|
||||
<p>
|
||||
<strong>Version: </strong> { a.version }
|
||||
</p>
|
||||
<p>
|
||||
<strong>Type: </strong> { a.type === "plugin-page" || a.type === "plugin-sw" ? "plugin" : a.type }
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
107
src/components/catalog/InstalledPlugins.astro
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<div id="parent" class="flex flex-row flex-wrap gap-4 items-center font-roboto justify-center">
|
||||
</div>
|
||||
<div class="w-0 h-0 visibility-none hidden"> <asset-loader1 /> </div>
|
||||
<script>
|
||||
import { Elements } from "@utils/index";
|
||||
import { Marketplace } from "@utils/marketplace";
|
||||
import { SettingsVals } from "@utils/values";
|
||||
|
||||
type Item = {
|
||||
description: string,
|
||||
image: string,
|
||||
package_name: string,
|
||||
payload: string,
|
||||
tags: [],
|
||||
background_video: string,
|
||||
background_image: string,
|
||||
title: string,
|
||||
type: string,
|
||||
version: string
|
||||
}
|
||||
|
||||
const getItem = async (item: any) => {
|
||||
try {
|
||||
const res = await fetch(new URL(`/api/packages/${item.name}`, window.location.origin));
|
||||
const data = await res.json();
|
||||
return { ...data, package_name: item.name }
|
||||
}
|
||||
catch (err: any) {
|
||||
console.log(`Err in themes: ${err}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const constructElements = async (item: Item, parent: HTMLDivElement, marketplace: Marketplace) => {
|
||||
const main = document.createElement("div");
|
||||
main.classList.add("rounded-3xl", "bg-navbar-color", "w-64", "flex", "flex-col", "cursor-pointer", "border-text-color");
|
||||
main.dataset.name = item.package_name
|
||||
|
||||
const click = document.createElement("div");
|
||||
click.classList.add("w-full");
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.classList.add("aspect-[16/9]", "rounded-t-3xl");
|
||||
img.src = `/packages/${item.package_name}/${item.image}`;
|
||||
img.alt = `${item.type}-${item.package_name}`;
|
||||
img.loading = "lazy";
|
||||
|
||||
const info = document.createElement("div");
|
||||
info.classList.add("h-2/6", "text-center", "content-center", "p-3", "font-semibold", "items-center", "flex", "flex-col");
|
||||
|
||||
const infoTitle = document.createElement("p");
|
||||
infoTitle.classList.add("text-2xl");
|
||||
infoTitle.innerHTML = item.title;
|
||||
|
||||
const infoInner = document.createElement("div");
|
||||
infoInner.classList.add("flex", "flex-row");
|
||||
|
||||
const infoInnerDelete = document.createElement("div");
|
||||
infoInnerDelete.classList.add("h-8", "w-8", "cursor-pointer");
|
||||
// This is cursed yes. SVG's SUUUCK
|
||||
infoInnerDelete.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256"><path fill="currentColor" d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16M112 168a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm0-120H96v-8a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8Z" /></svg>`;
|
||||
|
||||
const infoInnerOpen = document.createElement("a");
|
||||
infoInnerOpen.classList.add("h-8", "w-8", "cursor-pointer");
|
||||
infoInnerOpen.href = `../catalog/package/${item.package_name}`;
|
||||
infoInnerOpen.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256"><path fill="currentColor" d="M192 136v72a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16V80a16 16 0 0 1 16-16h72a8 8 0 0 1 0 16H48v128h128v-72a8 8 0 0 1 16 0m32-96a8 8 0 0 0-8-8h-64a8 8 0 0 0-5.66 13.66L172.69 72l-42.35 42.34a8 8 0 0 0 11.32 11.32L184 83.31l26.34 26.35A8 8 0 0 0 224 104Z" /></svg>`;
|
||||
|
||||
click.appendChild(img);
|
||||
infoInner.appendChild(infoInnerDelete);
|
||||
infoInner.appendChild(infoInnerOpen);
|
||||
info.appendChild(infoTitle);
|
||||
info.appendChild(infoInner);
|
||||
main.appendChild(click);
|
||||
main.appendChild(info);
|
||||
parent.appendChild(main);
|
||||
|
||||
|
||||
Elements.attachEvent(infoInnerDelete, "click", async () => {
|
||||
await marketplace.uninstallPlugin({ name: item.package_name, type: item.type as "page" | "serviceWorker" })
|
||||
parent.removeChild(main);
|
||||
});
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
await Marketplace.ready();
|
||||
const mp = Marketplace.getInstances().next().value!;
|
||||
const els = Elements.select([
|
||||
{ type: 'id', val: 'parent' }
|
||||
]);
|
||||
const itemsJSON = JSON.parse(await mp.getValueFromStore(SettingsVals.marketPlace.plugins)) || [];
|
||||
const filteredItems = itemsJSON.filter((dat: any) => dat.remove !== true);
|
||||
const itemPromises = filteredItems.map(getItem);
|
||||
const itemsArray = await Promise.all(itemPromises);
|
||||
return {
|
||||
items: itemsArray.filter((data) => data !== null),
|
||||
elements: els,
|
||||
marketplace: mp
|
||||
}
|
||||
}
|
||||
|
||||
Elements.createCustomElement("asset-loader1", async () => {
|
||||
const { items, elements, marketplace } = await init();
|
||||
const parentElem = Elements.exists<HTMLDivElement>(await elements.next());
|
||||
const promises = items.map(item => constructElements(item, parentElem, marketplace));
|
||||
await Promise.all(promises);
|
||||
});
|
||||
</script>
|
||||
138
src/components/catalog/InstalledThemes.astro
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
<div id="parent" class="flex flex-row flex-wrap gap-4 items-center font-roboto justify-center">
|
||||
<div id="main-theme" class="rounded-3xl bg-navbar-color w-64 flex flex-col cursor-pointer">
|
||||
<div class="w-full">
|
||||
<img src="/classic_theme.png" alt="Classic nebula" class="aspect-[16/9] rounded-t-3xl" loading="eager" />
|
||||
</div>
|
||||
<div class="h-2/6 text-center content-center p-3 font-semibold items-center flex flex-col text-2xl">
|
||||
Classic Nebula
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-0 h-0 visibility-none hidden">
|
||||
<asset-loader />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { Elements } from "@utils/index";
|
||||
import { Marketplace } from "@utils/marketplace";
|
||||
import { SettingsVals } from "@utils/values";
|
||||
|
||||
type Item = {
|
||||
description: string,
|
||||
image: string,
|
||||
package_name: string,
|
||||
payload: string,
|
||||
tags: [],
|
||||
background_video: string,
|
||||
background_image: string,
|
||||
title: string,
|
||||
type: string,
|
||||
version: string
|
||||
}
|
||||
|
||||
const getItem = async (item: any) => {
|
||||
try {
|
||||
const res = await fetch(new URL(`/api/packages/${item}`, window.location.origin));
|
||||
const data = await res.json();
|
||||
return { ...data, package_name: item }
|
||||
}
|
||||
catch (err: any) {
|
||||
console.log(`Err in themes: ${err}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const constructElements = async (item: Item, mainTheme: HTMLDivElement, parent: HTMLDivElement, marketplace: Marketplace) => {
|
||||
const main = document.createElement("div");
|
||||
main.classList.add("rounded-3xl", "bg-navbar-color", "w-64", "flex", "flex-col", "cursor-pointer", "border-text-color");
|
||||
main.dataset.name = item.package_name
|
||||
|
||||
const click = document.createElement("div");
|
||||
click.classList.add("w-full");
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.classList.add("aspect-[16/9]", "rounded-t-3xl");
|
||||
img.src = `/packages/${item.package_name}/${item.image}`;
|
||||
img.alt = `${item.type}-${item.package_name}`;
|
||||
img.loading = "lazy";
|
||||
|
||||
const info = document.createElement("div");
|
||||
info.classList.add("h-2/6", "text-center", "content-center", "p-3", "font-semibold", "items-center", "flex", "flex-col");
|
||||
|
||||
const infoTitle = document.createElement("p");
|
||||
infoTitle.classList.add("text-2xl");
|
||||
infoTitle.innerHTML = item.title;
|
||||
|
||||
const infoInner = document.createElement("div");
|
||||
infoInner.classList.add("flex", "flex-row");
|
||||
|
||||
const infoInnerDelete = document.createElement("div");
|
||||
infoInnerDelete.classList.add("h-8", "w-8", "cursor-pointer");
|
||||
// This is cursed yes. SVG's SUUUCK
|
||||
infoInnerDelete.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256"><path fill="currentColor" d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16M112 168a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm0-120H96v-8a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8Z" /></svg>`;
|
||||
|
||||
const infoInnerOpen = document.createElement("a");
|
||||
infoInnerOpen.classList.add("h-8", "w-8", "cursor-pointer");
|
||||
infoInnerOpen.href = `../catalog/package/${item.package_name}`;
|
||||
infoInnerOpen.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256"><path fill="currentColor" d="M192 136v72a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16V80a16 16 0 0 1 16-16h72a8 8 0 0 1 0 16H48v128h128v-72a8 8 0 0 1 16 0m32-96a8 8 0 0 0-8-8h-64a8 8 0 0 0-5.66 13.66L172.69 72l-42.35 42.34a8 8 0 0 0 11.32 11.32L184 83.31l26.34 26.35A8 8 0 0 0 224 104Z" /></svg>`;
|
||||
|
||||
click.appendChild(img);
|
||||
infoInner.appendChild(infoInnerDelete);
|
||||
infoInner.appendChild(infoInnerOpen);
|
||||
info.appendChild(infoTitle);
|
||||
info.appendChild(infoInner);
|
||||
main.appendChild(click);
|
||||
main.appendChild(info);
|
||||
parent.appendChild(main);
|
||||
|
||||
|
||||
Elements.attachEvent(mainTheme, "click", () => {
|
||||
marketplace.theme({ type: 'remove' });
|
||||
});
|
||||
|
||||
Elements.attachEvent(click, "click", async () => {
|
||||
await marketplace.theme(
|
||||
{
|
||||
type: 'normal',
|
||||
payload: item.payload,
|
||||
sources: {
|
||||
video: item.background_video,
|
||||
bg: item.background_image
|
||||
},
|
||||
name: item.package_name
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
Elements.attachEvent(infoInnerDelete, "click", async () => {
|
||||
await marketplace.uninstallTheme({ name: item.package_name });
|
||||
parent.removeChild(main);
|
||||
await marketplace.theme({ type: 'remove' });
|
||||
});
|
||||
}
|
||||
|
||||
const init = async () => {
|
||||
await Marketplace.ready();
|
||||
const mp = Marketplace.getInstances().next().value!;
|
||||
const els = Elements.select([
|
||||
{ type: 'id', val: 'main-theme' },
|
||||
{ type: 'id', val: 'parent' }
|
||||
]);
|
||||
const itemsJSON = JSON.parse(await mp.getValueFromStore(SettingsVals.marketPlace.themes)) || [];
|
||||
const itemPromises = itemsJSON.map(getItem);
|
||||
const itemsArray = await Promise.all(itemPromises);
|
||||
return {
|
||||
items: itemsArray.filter((data) => data !== null),
|
||||
elements: els,
|
||||
marketplace: mp
|
||||
}
|
||||
}
|
||||
|
||||
Elements.createCustomElement("asset-loader", async () => {
|
||||
const { items, elements, marketplace } = await init();
|
||||
const mainTheme = Elements.exists<HTMLDivElement>(await elements.next());
|
||||
const parentElem = Elements.exists<HTMLDivElement>(await elements.next());
|
||||
const promises = items.map(item => constructElements(item, mainTheme, parentElem, marketplace));
|
||||
await Promise.all(promises);
|
||||
});
|
||||
</script>
|
||||
19
src/components/settings/CreditsCard.astro
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
import { Image } from "astro:assets";
|
||||
import { type ImageMetadata } from "astro";
|
||||
const images = import.meta.glob<{ default: ImageMetadata }>(
|
||||
"/src/assets/credits/*.{jpeg,jpg,png,gif,webp}"
|
||||
);
|
||||
|
||||
interface Props {
|
||||
image?: string;
|
||||
name: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
const { image, name, link } = Astro.props;
|
||||
---
|
||||
<a class="rounded-md bg-navbar-color h-50 w-50 p-2 flex flex-col items-center" href={link} target="_blank" rel="noopener noreferrer">
|
||||
{image && <Image loading='lazy' class='w-32 h-32 object-cover rounded-md' src={images[image]()} alt={name} />}
|
||||
<p class="h-12 w-full text-text-color flex items-center justify-center text-xl font-semibold">{name}</p>
|
||||
</a>
|
||||
70
src/components/settings/Loader.astro
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<script>
|
||||
import { EventHandler } from "@utils/events";
|
||||
import { SW, createProxyScripts, checkProxyScripts, createBareMuxConn, setTransport } from "@utils/serviceWorker";
|
||||
import { Settings } from "@utils/settings";
|
||||
import { log } from "@utils/index";
|
||||
import { Marketplace } from "@utils/marketplace";
|
||||
import { SettingsVals } from "@utils/values";
|
||||
const titleText = `
|
||||
_ _ _ _ ____ _
|
||||
| \\ | | ___| |__ _ _| | __ _ / ___| ___ _ ____ _(_) ___ ___ ___
|
||||
| \\| |/ _ \\ '_ \\| | | | |/ _' | \\___ \\ / _ \\ '__\\ \\ / / |/ __/ _ \\/ __|
|
||||
| |\\ | __/ |_) | |_| | | (_| | ___) | __/ | \\ V /| | (_| __/\\__ \\
|
||||
|_| \\_|\\___|_.__/ \\__,_|_|\\__,_| |____/ \\___|_| \\_/ |_|\\___\\___||___/
|
||||
`;
|
||||
const info = "Hello developer or curious individual & welcome to the console! \nThere isn't a whole lot here for you unless you have run into an error.";
|
||||
const sysInfo = `In which case please include the info below when opening the issue: \n\nOS: ${navigator.platform} \nBrowser: ${navigator.userAgent} \nService workers: ${"serviceWorker" in navigator ? "Yes" : "No"}`
|
||||
const init = async (): Promise<Marketplace> => {
|
||||
log({ type: 'normal', bg: false, prefix: false }, titleText);
|
||||
log({ type: 'normal', bg: true, prefix: false }, info);
|
||||
log({ type: 'normal', bg: true, prefix: false }, sysInfo);
|
||||
log({ type: 'info', bg: true, prefix: true }, "General init...");
|
||||
for (const script of createProxyScripts()) {
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
await checkProxyScripts();
|
||||
const conn = await createBareMuxConn("/baremux/worker.js");
|
||||
const sw = new SW(conn);
|
||||
const mp = new Marketplace();
|
||||
const { serviceWorker, bareMuxConn, sj } = await sw.getSWInfo();
|
||||
log({ type: 'info', bg: true, prefix: true }, `General init completed! \n\nServiceWorker: ${serviceWorker.active?.state} \nBareMuxConn: ${bareMuxConn ? 'Active': 'Not active'} \nScramjetController: ${sj ? 'Active' : 'Not active'}`);
|
||||
return mp;
|
||||
}
|
||||
|
||||
const handleTheme = async (marketplace: Marketplace) => {
|
||||
const name = await marketplace.getValueFromStore(SettingsVals.marketPlace.appearance.theme.name);
|
||||
const payload = await marketplace.getValueFromStore(SettingsVals.marketPlace.appearance.theme.payload);
|
||||
const video = await marketplace.getValueFromStore(SettingsVals.marketPlace.appearance.video);
|
||||
const image = await marketplace.getValueFromStore(SettingsVals.marketPlace.appearance.image);
|
||||
if (name !== null) {
|
||||
marketplace.theme({
|
||||
type: 'normal',
|
||||
name: name,
|
||||
payload: payload,
|
||||
sources: {
|
||||
video: video,
|
||||
bg: image
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const initSettings = async (marketplace: Marketplace) => {
|
||||
log({ type: 'info', bg: true, prefix: true }, "Initializing settings...");
|
||||
for await (const _ of Settings.initDefaults());
|
||||
await handleTheme(marketplace);
|
||||
log({ type: 'info', bg: true, prefix: true }, "Initialized Settings!");
|
||||
}
|
||||
|
||||
const eventHandler = new EventHandler({
|
||||
events: {
|
||||
"DOMContentLoaded": async () => {
|
||||
const mp = await init();
|
||||
await initSettings(mp);
|
||||
}
|
||||
},
|
||||
logging: true
|
||||
});
|
||||
//bind the events
|
||||
eventHandler.bind();
|
||||
</script>
|
||||
67
src/components/settings/SettingsCard.astro
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
interface Inputs {
|
||||
input: boolean;
|
||||
required?: boolean;
|
||||
placeholder?: string;
|
||||
validate?: boolean;
|
||||
validateString?: string;
|
||||
}
|
||||
interface SelectOptions {
|
||||
value: string;
|
||||
name: string;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
interface Both {
|
||||
enabled: boolean;
|
||||
showOnSelect?: {
|
||||
value: string;
|
||||
};
|
||||
showOnInput?: boolean;
|
||||
}
|
||||
interface Selects {
|
||||
select: boolean;
|
||||
name?: string;
|
||||
multiple?: boolean;
|
||||
options?: SelectOptions[];
|
||||
}
|
||||
interface Buttons {
|
||||
name: string;
|
||||
id: string;
|
||||
}
|
||||
interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
input: Inputs;
|
||||
select: Selects;
|
||||
both: Both;
|
||||
button: Buttons;
|
||||
}
|
||||
|
||||
const { title, description, input, select, both, button } = Astro.props;
|
||||
---
|
||||
|
||||
<div class={`${both.enabled ? "h-72" : "h-64"} w-64 rounded-3xl bg-navbar-color flex flex-col items-center p-4`}>
|
||||
<h1 class="text-3xl font-bold mb-2"> { title } </h1>
|
||||
<p class="text-md w-full text-ellipsis text-center"> { description } </p>
|
||||
<div class="w-full h-full flex-grow flex justify-center items-center flex-col gap-4">
|
||||
<!-- We only want to render an input if it's enabled -->
|
||||
{(input.input && !both.enabled) &&
|
||||
<input class="text-md w-full h-10 p-2 bg-input border border-input-border-color rounded-md text-input-text" required={input.required} placeholder={input.placeholder}></input>
|
||||
}
|
||||
<!-- Same with dropdown selections -->
|
||||
{select.select &&
|
||||
<select id={select.name?.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()} class="text-md w-full h-10 p-2 bg-input border border-input-border-color rounded-md text-input-text" multiple={select.multiple} name={select.name}>
|
||||
{select.options!.map((option) => (
|
||||
<option id={option.value} disabled={option.disabled} value={option.value}>{option.name}</option>
|
||||
))}
|
||||
</select>
|
||||
}
|
||||
{(both.enabled && both.showOnSelect?.value) &&
|
||||
<input id={'inputOnSelectValue' + both.showOnSelect?.value} class="hidden text-md w-full h-10 p-2 bg-input border border-input-border-color rounded-md text-input-text" required={input.required} placeholder={input.placeholder}></input>
|
||||
}
|
||||
<button id={button.id.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()} class="w-36 h-10 rounded-md border border-input-border-color text-input-text bg-input hover:border-input hover:bg-input-border-color hover:text-input hover:font-bold active:bg-input active:text-input-text transition-all duration-200">
|
||||
{button.name}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
13
src/components/settings/ThemeCard.astro
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
const { image, title } = Astro.props;
|
||||
import { Image } from "astro:assets";
|
||||
---
|
||||
|
||||
<div class="rounded-3xl bg-navbar-color w-64 flex flex-col">
|
||||
<div class="w-full">
|
||||
<Image src={image} alt="Theme" class="aspect-[16/9] rounded-t-3xl" />
|
||||
</div>
|
||||
<div class="h-2/6 text-center content-center p-3 font-semibold">
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
41
src/components/toasts/Toast.svelte
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts">
|
||||
import { type ToastPosition, type Props, type TType } from "@utils/index";
|
||||
import toast from "svelte-french-toast";
|
||||
export let toastProp: Props;
|
||||
function handleToast(toastProp: Props) {
|
||||
switch (toastProp.toastType) {
|
||||
case "success":
|
||||
toast.success(toastProp.text, {
|
||||
style: "background: var(--navbar-color); color: var(--input-text-color);",
|
||||
icon: toastProp.emoji,
|
||||
position: toastProp.position ?? "bottom-right",
|
||||
duration: toastProp.duration
|
||||
});
|
||||
break;
|
||||
case "error":
|
||||
toast.error(toastProp.text, {
|
||||
style: "background: var(--navbar-color); color: var(--input-text-color);",
|
||||
icon: toastProp.emoji,
|
||||
position: toastProp.position ?? "bottom-right",
|
||||
duration: toastProp.duration
|
||||
});
|
||||
break;
|
||||
case "promise":
|
||||
throw new Error("Due to the way astro renders promise toasts are not available (ish)");
|
||||
break;
|
||||
case "multiline":
|
||||
toast(toastProp.text, {
|
||||
style: "background: var(--navbar-color); color: var(--input-text-color);",
|
||||
icon: toastProp.emoji,
|
||||
position: toastProp.position ?? "bottom-right",
|
||||
duration: toastProp.duration
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error("Something isn't right...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<!-- A hacky way to get this to be called. Just click this button (preferably via an EVENT) (see ../../utils/toast.ts) -->
|
||||
<button id={toastProp.id} class:invisible={'invisible'} class:hidden={'hidden'} class={toastProp.class} on:click={() => {return handleToast(toastProp)}}>Auto clicked for toast notifs</button>
|
||||
8
src/components/toasts/ToastWrapper.svelte
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { Toaster } from "svelte-french-toast";
|
||||
</script>
|
||||
|
||||
<div class="hidden" id="toastwrapper">
|
||||
<Toaster />
|
||||
<slot />
|
||||
</div>
|
||||
45
src/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/// <reference path="../.astro/types.d.ts" />
|
||||
/// <reference types="astro/client" />
|
||||
/// <reference types="@titaniumnetwork-dev/ultraviolet/client" />
|
||||
interface SJOptions {
|
||||
prefix: string;
|
||||
globals?: {
|
||||
wrapfn: string;
|
||||
wrapthisfn: string;
|
||||
trysetfn: string;
|
||||
importfn: string;
|
||||
rewritefn: string;
|
||||
metafn: string;
|
||||
setrealmfn: string;
|
||||
pushsourcemapfn: string;
|
||||
};
|
||||
files: {
|
||||
wasm: string;
|
||||
shared: string;
|
||||
worker: string;
|
||||
client: string;
|
||||
sync: string;
|
||||
};
|
||||
flags?: {
|
||||
serviceworkers?: boolean;
|
||||
syncxhr?: boolean;
|
||||
naiiveRewriter?: boolean;
|
||||
strictRewrites?: boolean;
|
||||
rewriterLogs?: boolean;
|
||||
captureErrors?: boolean;
|
||||
cleanErrors?: boolean;
|
||||
scramitize?: boolean;
|
||||
sourcemaps?: boolean;
|
||||
};
|
||||
siteFlags?: {};
|
||||
codec?: {
|
||||
encode: string;
|
||||
decode: string;
|
||||
};
|
||||
}
|
||||
|
||||
declare class ScramjetController {
|
||||
constructor(opts: SJOptions);
|
||||
init(): Promise<void>;
|
||||
encodeUrl(term: string): string;
|
||||
}
|
||||
12
src/i18n/en_US.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"header.home": "Home",
|
||||
"header.games": "Games",
|
||||
"header.settings": "Settings",
|
||||
"header.morelinks": "Want more links?",
|
||||
"header.catalog": "Nebula Catalog",
|
||||
"home.placeholder": "Search the web freely",
|
||||
"settings.settings": "Settings",
|
||||
"settings.appearance": "Appearance",
|
||||
"settings.proxy": "Proxy",
|
||||
"settings.tab": "Tab"
|
||||
}
|
||||
12
src/i18n/jp.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"header.home": "ホーム",
|
||||
"header.games": "ゲーム",
|
||||
"header.settings": "設定",
|
||||
"header.morelinks": "リンク一覧",
|
||||
"header.catalog": "Nebula Catalog",
|
||||
"home.placeholder": "検索欄",
|
||||
"settings.settings": "Settings",
|
||||
"settings.appearance": "Appearance",
|
||||
"settings.proxy": "Proxy",
|
||||
"settings.tab": "Tab"
|
||||
}
|
||||
9
src/i18n/ui.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import en_US from "./en_US.json";
|
||||
import jp from "./jp.json";
|
||||
|
||||
export const defaultLang = "en_US";
|
||||
|
||||
export const ui = {
|
||||
en_US,
|
||||
jp
|
||||
};
|
||||
15
src/i18n/utils.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { defaultLang, ui } from "./ui";
|
||||
|
||||
export const STATIC_PATHS = [{ params: { lang: "en_US" } }, { params: { lang: "jp" } }];
|
||||
|
||||
export function getLangFromUrl(url: URL) {
|
||||
const [, lang] = url.pathname.split("/");
|
||||
if (lang in ui) return lang as keyof typeof ui;
|
||||
return defaultLang;
|
||||
}
|
||||
|
||||
export function useTranslations(lang: keyof typeof ui) {
|
||||
return function t(key: keyof (typeof ui)[typeof defaultLang]) {
|
||||
return ui[lang][key] || ui[defaultLang][key];
|
||||
};
|
||||
}
|
||||
167
src/layouts/Layout.astro
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
---
|
||||
import { ClientRouter } from "astro:transitions";
|
||||
import Header from "@components/Header.astro";
|
||||
import MobileNavigation from "@components/MobileNavigation.astro";
|
||||
import SettingsLoader from "@components/settings/Loader.astro";
|
||||
import { SEO } from "astro-seo";
|
||||
import { SEO as SEOC } from "astro:env/client";
|
||||
interface Props {
|
||||
title: string;
|
||||
noHeader?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
}
|
||||
|
||||
const { title, noHeader, description, image } = Astro.props;
|
||||
const SEOConfig = JSON.parse(SEOC);
|
||||
const Host = Astro.url.host;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<SettingsLoader transition:persist />
|
||||
<meta charset="UTF-8" />
|
||||
<SEO
|
||||
title={title},
|
||||
titleTemplate={SEOConfig.enabled && Host === SEOConfig.domain ? 'Nebula | %s': '%s'},
|
||||
titleDefault={SEOConfig.enabled && Host === SEOConfig.domain ? 'Nebula': ''},
|
||||
description={SEOConfig.enabled && Host === SEOConfig.domain ? description ? description : 'A stunning and sleek web proxy with support for hundreds of popular sites.' : ''},
|
||||
charset="UTF-8",
|
||||
openGraph={{
|
||||
basic: {
|
||||
title: SEOConfig.enabled && Host === SEOConfig.domain ? 'Nebula' :'',
|
||||
type: 'website',
|
||||
url: SEOConfig.enabled && Host === SEOConfig.domain ? 'https://nebulaproxy.io' : '',
|
||||
image: SEOConfig.enabled && Host === SEOConfig.domain ? image ? image : '/logo.png': ''
|
||||
},
|
||||
optional: {
|
||||
description: SEOConfig.enabled && Host === SEOConfig.domain ? description ? description : 'A stunning and sleek web proxy with support for hundreds of popular sites.' : ''
|
||||
}
|
||||
}},
|
||||
twitter={{
|
||||
card: "summary_large_image",
|
||||
title: SEOConfig.enabled && Host === SEOConfig.domain ? 'Nebula' : '',
|
||||
description: SEOConfig.enabled && Host === SEOConfig.domain ? description ? description : 'A stunning and sleek web proxy with support for hundreds of popular sites.' : '',
|
||||
image: SEOConfig.enabled && Host === SEOConfig.domain ? image ? image : '/logo.png': '',
|
||||
imageAlt: image ? 'Catalog image' : `${SEOConfig.enabled && Host === SEOConfig.domain && "Nebula services's"} logo`
|
||||
}},
|
||||
extend={{
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg', id: 'favicon' },
|
||||
SEOConfig.enabled && Host === SEOConfig.domain && { rel: 'sitemap', href: '/sitemap-index.xml' },
|
||||
{
|
||||
rel: 'preload',
|
||||
href: "https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap",
|
||||
as: 'style',
|
||||
crossorigin: 'anonymous'
|
||||
},
|
||||
{
|
||||
rel: 'preload',
|
||||
href: "https://fonts.gstatic.com/s/roboto/v32/KFOmCnqEu92Fr1Mu4mxK.woff2",
|
||||
as: 'font',
|
||||
type: 'font/woff2',
|
||||
crossorigin: 'anonymous'
|
||||
}
|
||||
],
|
||||
meta: [
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' },
|
||||
{ name: 'generator', content: `${Astro.generator}` }
|
||||
]
|
||||
}}
|
||||
/>
|
||||
{/* I left this out of the SEO component as we need it to persist properly : D */}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/nebula.css"
|
||||
id="stylesheet"
|
||||
transition:persist
|
||||
/>
|
||||
<ClientRouter fallback="animate" />
|
||||
</head>
|
||||
<body class="h-full bg-primary">
|
||||
{!noHeader && <Header />}
|
||||
<div class="h-full z-10 w-full fixed">
|
||||
<slot />
|
||||
</div>
|
||||
<div id="mobilenavwrapper" class="w-full fixed inset-0 h-[calc(100%-4rem)] z-20">
|
||||
<MobileNavigation />
|
||||
</div>
|
||||
<video
|
||||
autoplay
|
||||
muted
|
||||
loop
|
||||
id="nebulaVideo"
|
||||
class="absolute z-0 h-[calc(100%-4rem)] w-full object-cover"
|
||||
transition:persist
|
||||
>
|
||||
<source type="video/mp4" id="videosource" transition:persist />
|
||||
</video>
|
||||
<img
|
||||
src=""
|
||||
class="absolute z-0 h-[calc(100%-4rem)] w-full object-cover hidden"
|
||||
id="nebulaImage"
|
||||
transition:persist
|
||||
/>
|
||||
<style is:global>
|
||||
.roboto {
|
||||
font-family: var(--font-family), Roboto, sans-serif;
|
||||
font-weight: 200;
|
||||
font-style: normal;
|
||||
}
|
||||
/* Custom scrollbar because the default ones look like ASS */
|
||||
::-webkit-scrollbar {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--navbar-color);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--navbar-text-color);
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--navbar-link-color);
|
||||
}
|
||||
::-moz-scrollbar {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
}
|
||||
::-moz-scrollbar-track {
|
||||
background: var(--navbar-color);
|
||||
}
|
||||
::-moz-scrollbar-thumb {
|
||||
background: var(--navbar-text-color);
|
||||
}
|
||||
::-moz-scrollbar-thumb:hover {
|
||||
background: var(--navbar-link-color);
|
||||
}
|
||||
::-ms-scrollbar {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
}
|
||||
::-ms-scrollbar-track {
|
||||
background: var(--navbar-color);
|
||||
}
|
||||
::-ms-scrollbar-thumb {
|
||||
background: var(--navbar-text-color);
|
||||
}
|
||||
::-ms-scrollbar-thumb:hover {
|
||||
background: var(--navbar-link-color);
|
||||
}
|
||||
::-o-scrollbar {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
}
|
||||
::-o-scrollbar-track {
|
||||
background: var(--navbar-color);
|
||||
}
|
||||
::-o-scrollbar-thumb {
|
||||
background: var(--navbar-text-color);
|
||||
}
|
||||
::-o-scrollbar-thumb:hover {
|
||||
background: var(--navbar-link-color);
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
35
src/layouts/SettingsLayout.astro
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
import { getLangFromUrl, useTranslations } from "../i18n/utils";
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
const { title } = Astro.props;
|
||||
import SidebarButton from "@components/SidebarButton.astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
---
|
||||
|
||||
<div class="flex flex-row font-roboto">
|
||||
<div class="text-text-color mt-16 fixed inset-0 h-[calc(100%-4rem)] z-0 bg-primary flex-col flex md:flex-row">
|
||||
<div class="items-center md:p-3 sm:p-3 flex flex-row border-border-color md:gap-10 xl:gap-10 max-sm:gap-5 sm:gap-5 md:border-r-2 lg:w-2/12 md:w-3/12 md:flex-col md:bg-navbar-color md:gap-0 overflow-x-auto md:overflow-x-hidden overflow-y-hidden max-md:ml-1 max-md:mr-1 max-md:max-h-24 max-md:min-h-24 max-md:justify-left max-md:scroll-ml-3 max-md:scroll-mr-3 max-md:snap-x max-md:snap-mandatory">
|
||||
<SidebarButton title={t("settings.appearance")} route={`/${lang}/settings/appearance`}>
|
||||
<Icon name="ph:palette" class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6" />
|
||||
</SidebarButton>
|
||||
<SidebarButton title={t("settings.proxy")} route={`/${lang}/settings/pr`}>
|
||||
<Icon name="ph:globe-hemisphere-east-fill" class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6" />
|
||||
</SidebarButton>
|
||||
<SidebarButton title={t("settings.tab")} route={`/${lang}/settings/tab/`}>
|
||||
<Icon name="ph:laptop-fill" class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6" />
|
||||
</SidebarButton>
|
||||
<SidebarButton title="Misc" route={`/${lang}/settings/misc`}>
|
||||
<Icon name="ph:gear" class="h-6 w-6 text-text-color transistion duration-500 group-hover:text-text-hover-color md:h-6 md:w=6" />
|
||||
</SidebarButton>
|
||||
<SidebarButton title="Credits" route={`/${lang}/settings/credits`}>
|
||||
<Icon name="ph:user" class="h-6 w-6 text-text-color transistion duration-500 group-hover:text-text-hover-color md:h-6 md:w=6" />
|
||||
</SidebarButton>
|
||||
</div>
|
||||
<div class="p-8 md:pb-2 w-full flex-grow overflow-y-auto">
|
||||
<p class="text-5xl font-semibold mb-5">{t("settings.settings")}</p>
|
||||
<p class="text-2xl">{title}</p>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
12
src/layouts/SettingsSection.astro
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
const { title, subtitle } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="mt-5 font-roboto">
|
||||
<hr />
|
||||
<div class="mt-3">
|
||||
<div class="text-2xl font-semibold">{title}</div>
|
||||
<div class="text-lg mb-5">{subtitle}</div>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
53
src/pages/[lang]/catalog/[...page].astro
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
import CatalogCard from "@components/catalog/CatalogCard.astro";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
|
||||
import Pagination from "./pagination.astro";
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
|
||||
const { page } = Astro.params;
|
||||
|
||||
const response = await fetch(new URL("/api/catalog-assets/", Astro.url));
|
||||
const assetsJson = await response.json();
|
||||
|
||||
const nextPage = parseInt(page!) + 1;
|
||||
const previousPage = parseInt(page!) - 1;
|
||||
const lastPage = assetsJson.pages;
|
||||
---
|
||||
|
||||
<Layout title="Catalog">
|
||||
<div class="flex mt-16 w-full fixed inset-0 h-[calc(100%-4rem)] z-0 bg-primary flex-col items-center overflow-auto font-roboto">
|
||||
<div class="w-full flex flex-col gap-4 jusitfy-center items-center text-text-color mt-9">
|
||||
<h1 class="text-3xl md:text-5xl font-bold"> Nebula Catalog </h1>
|
||||
<p class="text-l md:text-xl max-md:text-center max-md:p-2"> The Nebula Catalog is a place for you to find user-created themes and plugins. </p>
|
||||
</div>
|
||||
<CatalogCard page={page} lang={lang} />
|
||||
<div class="flex flex-row pb-8 gap-4 font-roboto">
|
||||
{/* The first page. If the user is on this page, or the one after it, don't show it. */}
|
||||
{parseInt(page!) > 2 && (
|
||||
<a href={`/${lang}/catalog/${1}`} class="w-8 h-8 bg-navbar-color items-center text-center content-center text-text-color rounded-md"> 1 </a>
|
||||
)
|
||||
}
|
||||
{previousPage > 0 && (
|
||||
<a href={`/${lang}/catalog/${previousPage}`} class="w-8 h-8 bg-navbar-color items-center text-center content-center text-text-color rounded-md">{previousPage}</a>
|
||||
)
|
||||
}
|
||||
{/* The greyed out page the user is currently on */}
|
||||
<a class="w-8 h-8 bg-lighter items-center text-center content-center text-text-color rounded-md">{page}</a>
|
||||
{nextPage < lastPage && (
|
||||
<a href={`/${lang}/catalog/${nextPage}`} class="w-8 h-8 bg-navbar-color items-center text-center content-center text-text-color rounded-md">{nextPage}</a>
|
||||
)
|
||||
}
|
||||
{/* Pagination input */}
|
||||
<Pagination />
|
||||
{/* The last page. If the user is on this page, don't show it. */}
|
||||
{page != lastPage && (
|
||||
<a href={`/${lang}/catalog/${assetsJson.pages}`} class="w-8 h-8 bg-navbar-color items-center text-center content-center text-text-color rounded-md">
|
||||
{assetsJson.pages}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
185
src/pages/[lang]/catalog/package/[...packageName].astro
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
---
|
||||
const { packageName } = Astro.params;
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
const response = await fetch(
|
||||
new URL("/api/packages/" + packageName, Astro.url)
|
||||
);
|
||||
const assetsJson = await response.json();
|
||||
---
|
||||
|
||||
<Layout
|
||||
title={`Package: ${assetsJson.title}`}
|
||||
description=`${assetsJson.title} is a package on Nebula. Start using this package on Nebula today!`
|
||||
image={`/packages/${packageName}/${assetsJson.image}`}
|
||||
>
|
||||
<div
|
||||
class="flex flex-wrap min-[1032px]:mt-16 mt-8 w-full fixed inset-0 h-full md:h-[calc(100%-4rem)] z-0 bg-primary flex-col items-center content-center justify-center lg:pb-64 font-roboto max-md:p-4"
|
||||
>
|
||||
{
|
||||
assetsJson.error && (
|
||||
<h1 class="text-text-color text-3xl font-bold">
|
||||
{" "}
|
||||
Unexpected error. Is the name right?{" "}
|
||||
</h1>
|
||||
)
|
||||
}
|
||||
{
|
||||
!assetsJson.error && (
|
||||
<div class="flex flex-col max-md:min-[1032px]:justify-center md:min-[1032px]:flex-row items-center text-text-color bg-navbar-color rounded-2xl">
|
||||
{assetsJson.background_video && (
|
||||
<video
|
||||
src={`/packages/${packageName}/${assetsJson.background_video}`}
|
||||
controls
|
||||
class="md:w-[44rem] md:h-[25rem] h-[12rem] w-full object-cover rounded-xl"
|
||||
>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
)}
|
||||
{assetsJson.backgroundImage && (
|
||||
<div
|
||||
style={{
|
||||
backgroundImage: `url(/packages/${packageName}/${assetsJson.backgroundImage})`,
|
||||
}}
|
||||
class="md:w-[44rem] md:h-[25rem] h-[12rem] w-full object-cover bg-center rounded-xl"
|
||||
/>
|
||||
)}
|
||||
{!assetsJson.background_video && !assetsJson.backgroundImage && (
|
||||
<img
|
||||
loading="lazy"
|
||||
src={`/packages/${packageName}/${assetsJson.image}`}
|
||||
alt={assetsJson.title}
|
||||
class="md:w-[44rem] md:h-[25rem] h-[12rem] w-full object-cover rounded-xl"
|
||||
/>
|
||||
)}
|
||||
<div class="flex flex-col ml-7 md:p-16 p-10">
|
||||
<p class="text-xl">
|
||||
{assetsJson.type === "plugin-page" ||
|
||||
assetsJson.type === "plugin-sw"
|
||||
? "plugin"
|
||||
: assetsJson.type}
|
||||
</p>
|
||||
<h1 class="text-4xl font-roboto font-semibold">
|
||||
{assetsJson.title}
|
||||
</h1>
|
||||
<p class="text-xl">
|
||||
{" "}
|
||||
By: <strong>{assetsJson.author}</strong>
|
||||
</p>
|
||||
<p class="text-xl">{assetsJson.description}</p>
|
||||
<button
|
||||
class="bg-primary text-text-color border border-transparent rounded-lg px-6 py-3 hover:bg-navbar-color transition-colors duration-300 mt-9"
|
||||
id="install"
|
||||
>
|
||||
Install
|
||||
</button>
|
||||
<button
|
||||
class="hidden bg-primary text-text-color border border-transparent rounded-lg px-6 py-3 hover:bg-navbar-color transition-colors duration-300 mt-9"
|
||||
id="uninstall"
|
||||
>
|
||||
Uninstall
|
||||
</button>
|
||||
<a
|
||||
href="../1"
|
||||
class="w-full bg-background text-text-color border-none rounded-lg text-l max-md:mt-7 px-4 py-4 h-full flex-grow md:text-right text-center font-bold underline underline-offset-4 decoration-2 hover:decoration-border-color transition-colors duration-300"
|
||||
>
|
||||
Go Back
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<package-vars data-assets={JSON.stringify(assetsJson)} data-name={packageName} data-type={assetsJson.type}></package-vars>
|
||||
</Layout>
|
||||
<script>
|
||||
import { Elements } from "@utils/index";
|
||||
import { Marketplace } from "@utils/marketplace";
|
||||
|
||||
const init = async () => {
|
||||
await Marketplace.ready();
|
||||
const marketplace = Marketplace.getInstances().next().value!;
|
||||
const elements = Elements.select([
|
||||
{ type: 'id', val: 'install' },
|
||||
{ type: 'id', val: 'uninstall' }
|
||||
]);
|
||||
return { marketplace, elements };
|
||||
};
|
||||
|
||||
const handleThemes = async (marketplace: Marketplace, name: string, assets: any) => {
|
||||
const { exists } = await marketplace.getThemes(name);
|
||||
if (!exists) {
|
||||
await marketplace.installTheme({ name: name });
|
||||
await marketplace.theme({
|
||||
type: 'normal',
|
||||
payload: assets.payload,
|
||||
name: name,
|
||||
sources: {
|
||||
video: assets.background_video,
|
||||
bg: assets.background_image
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
await marketplace.theme({ type: 'remove', name: name });
|
||||
await marketplace.uninstallTheme({ name: name });
|
||||
}
|
||||
}
|
||||
|
||||
const handlePlugins = async (marketplace: Marketplace, name: string, assets: any) => {
|
||||
const { plug } = await marketplace.getPlugins(name);
|
||||
console.log(plug);
|
||||
console.log(assets);
|
||||
if (!plug || plug.remove === true) {
|
||||
await marketplace.installPlugin({
|
||||
name: name,
|
||||
src: assets.payload,
|
||||
type: assets.type === "plugin-page" ? "page": "serviceWorker"
|
||||
});
|
||||
return;
|
||||
}
|
||||
await marketplace.uninstallPlugin({
|
||||
name: name,
|
||||
type: assets.type === "plugin-page" ? "page": "serviceWorker"
|
||||
});
|
||||
}
|
||||
|
||||
Elements.createCustomElement("package-vars",
|
||||
async(datasets) => {
|
||||
const { marketplace, elements } = await init();
|
||||
const { dataAssets, dataName, dataType } = { dataAssets: datasets![0].val, dataName: datasets![1].val, dataType: datasets![2].val };
|
||||
const { exists: themeExists } = await marketplace.getThemes(dataName!);
|
||||
const { plug } = await marketplace.getPlugins(dataName!);
|
||||
console.log(dataName);
|
||||
const installButton = Elements.exists<HTMLButtonElement>(await elements.next());
|
||||
const uninstallButton = Elements.exists<HTMLButtonElement>(await elements.next());
|
||||
|
||||
Elements.attachEvent(installButton, "click", async () => {
|
||||
dataType?.startsWith("plugin")
|
||||
? await handlePlugins(marketplace, dataName!, JSON.parse(dataAssets!) || [])
|
||||
: await handleThemes(marketplace, dataName!, JSON.parse(dataAssets!) || []);
|
||||
installButton.classList.add("hidden");
|
||||
uninstallButton.classList.remove("hidden");
|
||||
});
|
||||
|
||||
Elements.attachEvent(uninstallButton, "click", async () => {
|
||||
dataType?.startsWith("plugin")
|
||||
? await handlePlugins(marketplace, dataName!, JSON.parse(dataAssets!) || [])
|
||||
: await handleThemes(marketplace, dataName!, JSON.parse(dataAssets!) || []);
|
||||
uninstallButton.classList.add("hidden");
|
||||
installButton.classList.remove("hidden");
|
||||
});
|
||||
|
||||
if (themeExists || (plug && plug.remove === false || plug.remove === undefined)) {
|
||||
installButton.classList.add("hidden");
|
||||
uninstallButton.classList.remove("hidden");
|
||||
}
|
||||
},
|
||||
//Pull in the data elements.
|
||||
[
|
||||
{ name: 'assets' },
|
||||
{ name: 'name' },
|
||||
{ name: 'type' }
|
||||
]
|
||||
);
|
||||
|
||||
</script>
|
||||
27
src/pages/[lang]/catalog/pagination.astro
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<input
|
||||
class="w-10 h-8 bg-navbar-color items-center text-center content-center text-text-color rounded-md [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
||||
type="number"
|
||||
id="pageinationInput"
|
||||
placeholder="..."
|
||||
/>
|
||||
<script>
|
||||
import { EventHandler } from "@utils/events";
|
||||
import { Elements } from "@utils/index";
|
||||
import { navigate } from "astro:transitions/client";
|
||||
new EventHandler({
|
||||
events: {
|
||||
"astro:page-load": async () => {
|
||||
const el = Elements.select([
|
||||
{ type: 'id', val: 'pageinationInput' }
|
||||
]);
|
||||
const input = Elements.exists<HTMLInputElement>(await el.next());
|
||||
Elements.attachEvent(input, "keyup", (event?: Event) => {
|
||||
const e = event as KeyboardEvent | undefined;
|
||||
if (e?.key) return navigate(`${input.value}`);
|
||||
});
|
||||
}
|
||||
},
|
||||
logging: false
|
||||
})
|
||||
.bind();
|
||||
</script>
|
||||
27
src/pages/[lang]/games.astro
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
---
|
||||
|
||||
<Layout title="Chango Games">
|
||||
<iframe id="chango" class="w-full h-full" src="/loading"></iframe>
|
||||
</Layout>
|
||||
<script>
|
||||
import { EventHandler } from "@utils/events";
|
||||
import { Elements } from "@utils/index";
|
||||
|
||||
const init = async () => {
|
||||
const el = Elements.select([
|
||||
{ type: 'id', val: 'chango' }
|
||||
]);
|
||||
const chango = Elements.exists<HTMLIFrameElement>(await el.next());
|
||||
chango.src = `${__uv$config.prefix}${__uv$config.encodeUrl!("https://radon.games/games/")}`
|
||||
};
|
||||
|
||||
new EventHandler({
|
||||
events: {
|
||||
"astro:page-load": init
|
||||
},
|
||||
logging: false
|
||||
})
|
||||
.bind();
|
||||
</script>
|
||||
217
src/pages/[lang]/index.astro
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
---
|
||||
import Logo from "@components/Logo.astro";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import { getLangFromUrl, useTranslations } from "../../i18n/utils";
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
import { VERSION } from "astro:env/client";
|
||||
---
|
||||
|
||||
<Layout title="Home">
|
||||
<div class="flex flex-wrap mt-16 justify-center content-center w-full bg-primary fixed inset-0 h-[calc(100%-4rem)] z-0 flex-col items-center">
|
||||
<div class="w-full flex flex-col justify-center items-center content-center h-2/4">
|
||||
<div class="flex flex-row items-center mb-8">
|
||||
<div class="h-32 w-32 fill-navbar-text-color">
|
||||
<Logo />
|
||||
</div>
|
||||
<h1 class="font-roboto whitespace-nowrap text-navbar-text-color sm:visible text-5xl sm:text-7xl roboto">
|
||||
nebula.
|
||||
</h1>
|
||||
</div>
|
||||
<input
|
||||
id="nebula-input"
|
||||
class="transition-all duration-300 font-roboto h-14 rounded-t-2xl w-10/12 rounded-b-2xl border border-input-border-color bg-input p-2 text-center text-xl text-input-text placeholder:text-input-text roboto focus:outline-none md:w-3/12"
|
||||
placeholder={t("home.placeholder")}
|
||||
/>
|
||||
<div
|
||||
id="omnibox"
|
||||
class="hidden p-1 transition-all duration-300 flex flex-col w-10/12 md:w-3/12 h-full flex-grow bg-input text-center items-center rounded-b-2xl border-input-border-color border-b border-r border-l"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<iframe
|
||||
id="neb-iframe"
|
||||
class="hidden z-100 w-full h-full absolute top-0 bottom-0 bg-primary"
|
||||
src="/loading"></iframe>
|
||||
<div
|
||||
id="version"
|
||||
class="flex flex-row w-full absolute bottom-4 pr-4 pl-4 text-text-color h-6 justify-between items-center font-roboto"
|
||||
>
|
||||
<p>Version: {VERSION}</p>
|
||||
<div class="hidden md:flex gap-2 flex-grow ml-16 justify-center items-center">
|
||||
<p> Having problems? </p>
|
||||
<button id="reset" class="underline underline-offset-4 hover:decoration-input-border-color active:decoration-input-text">
|
||||
Click here to reset your instance
|
||||
</button>
|
||||
</div>
|
||||
<p>© Nebula Services 2024</p>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
<script>
|
||||
import { EventHandler } from "@utils/events";
|
||||
import { SupportedSites, SearchEngines, SettingsVals } from "@utils/values";
|
||||
import { search, Elements } from "@utils/index";
|
||||
import { BareClient } from "@mercuryworkshop/bare-mux";
|
||||
import { defaultStore } from "@utils/storage";
|
||||
import { SW } from "@utils/serviceWorker";
|
||||
import { Marketplace } from "@utils/marketplace";
|
||||
|
||||
type Suggestion = {
|
||||
phrase: string;
|
||||
};
|
||||
|
||||
const init = async (): Promise<void> => {
|
||||
const bc = new BareClient();
|
||||
const se = Elements.select([
|
||||
{ type: 'id', val: 'reset' },
|
||||
{ type: 'id', val: 'nebula-input' },
|
||||
{ type: 'id', val: 'omnibox' },
|
||||
{ type: 'id', val: 'version' },
|
||||
{ type: 'id', val: 'neb-iframe' }
|
||||
]);
|
||||
|
||||
const reset = Elements.exists<HTMLButtonElement>(await se.next());
|
||||
Elements.attachEvent(reset, "click", async () => {
|
||||
const t = await navigator.serviceWorker.getRegistrations();
|
||||
t.map(async (reg) => {
|
||||
await reg.unregister();
|
||||
});
|
||||
localStorage.clear();
|
||||
window.location.href = "/";
|
||||
});
|
||||
|
||||
const input = Elements.exists<HTMLInputElement>(await se.next());
|
||||
const omnibox = Elements.exists<HTMLDivElement>(await se.next());
|
||||
const copyright = Elements.exists<HTMLDivElement>(await se.next());
|
||||
const iframe = Elements.exists<HTMLIFrameElement>(await se.next());
|
||||
const prox = async (input: string, prox: "uv" | "sj") => {
|
||||
await Marketplace.ready();
|
||||
const mp = Marketplace.getInstances().next().value!;
|
||||
const sw = SW.getInstances().next().value as SW;
|
||||
iframe.classList.remove("hidden");
|
||||
const val = search(
|
||||
input,
|
||||
defaultStore.getVal(SettingsVals.proxy.searchEngine)
|
||||
? SearchEngines[defaultStore.getVal(SettingsVals.proxy.searchEngine)]
|
||||
: SearchEngines.ddg
|
||||
);
|
||||
const { serviceWorker } = await sw.getSWInfo();
|
||||
mp.handlePlugins(serviceWorker);
|
||||
switch(prox) {
|
||||
case "uv": {
|
||||
iframe.src = `${__uv$config.prefix}${__uv$config.encodeUrl!(val)}`;
|
||||
break;
|
||||
}
|
||||
case "sj": {
|
||||
const { sj } = await sw.getSWInfo();
|
||||
iframe.src = sj.encodeUrl(val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
input.addEventListener("keypress", async (event: any) => {
|
||||
if (event.key === "Enter") {
|
||||
copyright.classList.add("hidden");
|
||||
if (defaultStore.getVal(SettingsVals.proxy.proxy.key) === SettingsVals.proxy.proxy.available.automatic) {
|
||||
switch(SupportedSites[input.value]) {
|
||||
case "uv": {
|
||||
prox(input.value, "uv");
|
||||
break;
|
||||
}
|
||||
case "sj": {
|
||||
prox(input.value, "sj");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
prox(input.value, "uv");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch(defaultStore.getVal("proxy") as "uv" | "sj") {
|
||||
case "uv": {
|
||||
prox(input.value, "uv");
|
||||
break;
|
||||
}
|
||||
case "sj": {
|
||||
prox(input.value, "sj");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
input.addEventListener("input", async () => {
|
||||
if (input.value.length < 3) {
|
||||
omnibox.innerHTML = "";
|
||||
omnibox.classList.add("hidden");
|
||||
input.classList.add("rounded-b-2xl");
|
||||
return;
|
||||
}
|
||||
|
||||
omnibox.classList.remove("hidden");
|
||||
input.classList.remove("rounded-b-2xl");
|
||||
const data = await bc.fetch(`https://api.duckduckgo.com/ac?q=${encodeURIComponent(input.value)}&format=json`);
|
||||
const res = await data.json();
|
||||
const fData = res.slice(0, 6); // Trim to only the first 6 results.
|
||||
if (fData.length <= 0) {
|
||||
omnibox.classList.add("hidden");
|
||||
input.classList.add("rounded-b-2xl");
|
||||
return;
|
||||
}
|
||||
omnibox.innerHTML = "";
|
||||
fData.map((res: Suggestion) => {
|
||||
const span = document.createElement("span") as HTMLSpanElement;
|
||||
const p = document.createElement("p") as HTMLParagraphElement;
|
||||
p.classList.add(
|
||||
"cursor-pointer",
|
||||
"text-ellipsis",
|
||||
"text-center",
|
||||
"w-full",
|
||||
"overflow-hidden",
|
||||
"whitespace-nowrap"
|
||||
);
|
||||
p.innerText = res.phrase;
|
||||
|
||||
span.classList.add(
|
||||
"cursor-pointer",
|
||||
"font-roboto",
|
||||
"border-b",
|
||||
"border-input-border-color",
|
||||
"last:rounded-b-xl",
|
||||
"last:border-none",
|
||||
"text-ellipsis",
|
||||
"whitespace-nowrap",
|
||||
"w-full",
|
||||
"text-input-text",
|
||||
"h-9",
|
||||
"text-xl",
|
||||
"text-align-center",
|
||||
"overflow-hidden",
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-center",
|
||||
"hover:bg-lighter",
|
||||
"active:bg-primary"
|
||||
);
|
||||
span.addEventListener("click", () => {
|
||||
input.value = res.phrase;
|
||||
input.dispatchEvent(
|
||||
new KeyboardEvent("keypress", { key: "Enter", code: "Enter" })
|
||||
);
|
||||
});
|
||||
span.appendChild(p);
|
||||
omnibox.appendChild(span);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
new EventHandler({
|
||||
events: {
|
||||
"astro:page-load": init
|
||||
},
|
||||
logging: false
|
||||
})
|
||||
.bind();
|
||||
</script>
|
||||
36
src/pages/[lang]/settings/appearance.astro
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
import InstalledThemes from "@components/catalog/InstalledThemes.astro";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import SettingsLayout from "@layouts/SettingsLayout.astro";
|
||||
import SettingsSection from "@layouts/SettingsSection.astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
import { MARKETPLACE_ENABLED } from "astro:env/client";
|
||||
---
|
||||
|
||||
<Layout title="Settings">
|
||||
<SettingsLayout title={t("settings.appearance")}>
|
||||
<SettingsSection
|
||||
title="Theme"
|
||||
subtitle="Choose a theme so your eyes don't hate us.">
|
||||
<div class="flex flex-row flex-wrap gap-4 items-center font-roboto">
|
||||
<div class="justify-center flex flex-row gap-6 flex-wrap md:justify-normal">
|
||||
<InstalledThemes />
|
||||
{MARKETPLACE_ENABLED &&
|
||||
<a href={`/${lang}/catalog/1`} class="rounded-3xl bg-navbar-color w-64 flex flex-col">
|
||||
<div class="w-full items-center justify-center flex aspect-[16/9]">
|
||||
<Icon name="ph:plus-bold" class="h-16 w-16" />
|
||||
</div>
|
||||
<div class="h-2/6 text-center content-center p-3 font-semibold">
|
||||
Get more themes in the <strong>Nebula Catalog!</strong>
|
||||
</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
<div class="text-3xl font-roboto font-bold text-text-color"></div>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
</SettingsLayout>
|
||||
</Layout>
|
||||
32
src/pages/[lang]/settings/credits.astro
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import SettingsLayout from "@layouts/SettingsLayout.astro";
|
||||
import SettingsSection from "@layouts/SettingsSection.astro";
|
||||
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
import CreditsCard from "@components/settings/CreditsCard.astro";
|
||||
---
|
||||
|
||||
<Layout title="Settings">
|
||||
<SettingsLayout title="Credits">
|
||||
<SettingsSection title="Site Developers & Maintainers" subtitle="Thank you to all of the devs and maintainers!">
|
||||
<div class="flex flex-row flex-wrap gap-4 items-center font-roboto">
|
||||
<div class="justify-center flex flex-row gap-6 flex-wrap md:justify-normal">
|
||||
<CreditsCard image="/src/assets/credits/rift.jpeg" name="Rifting" link="https://github.com/rifting" />
|
||||
<CreditsCard image="/src/assets/credits/motortruck1221.png" name="MotorTruck1221" link="https://motortruck1221.com" />
|
||||
</div>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
<SettingsSection title="Tools & Projects" subtitle="Nebula wouldn't be possible without these tools & projects">
|
||||
<div class="flex flex-row flex-wrap gap-6 items-center font-roboto">
|
||||
<CreditsCard image="/src/assets/credits/uv.png" name="Ultraviolet" link="https://github.com/titaniumnetwork-dev/ultraviolet" />
|
||||
<CreditsCard image="/src/assets/credits/rammerhead.png" name="Rammerhead" link="https://github.com/binary-person/rammerhead" />
|
||||
<CreditsCard image="/src/assets/credits/libcurl.png" name="Libcurl.js" link="https://github.com/ading2210/libcurl.js" />
|
||||
<CreditsCard image="/src/assets/credits/mercury.png" name="Epoxy TLS" link="https://github.com/mercuryworkshop/epoxy-tls" />
|
||||
</div>
|
||||
</SettingsSection>
|
||||
<SettingsSection title="Translators" subtitle="Translations are made possible by these lovely individuals">
|
||||
</SettingsSection>
|
||||
</SettingsLayout>
|
||||
</Layout>
|
||||
84
src/pages/[lang]/settings/misc.astro
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
import SettingsCard from "@components/settings/SettingsCard.astro";
|
||||
import Toast from "@components/toasts/Toast.svelte";
|
||||
import ToastWrapper from "@components/toasts/ToastWrapper.svelte";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import SettingsLayout from "@layouts/SettingsLayout.astro";
|
||||
import SettingsSection from "@layouts/SettingsSection.astro";
|
||||
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
|
||||
|
||||
const origin = Astro.url.origin;
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
---
|
||||
|
||||
<Layout title="Settings">
|
||||
<SettingsLayout title="Miscellaneous">
|
||||
<SettingsSection title="Misc" subtitle="All of our miscellaneous settings">
|
||||
<div class="w-full h-full flex flex-col items-center justify-center flex-wrap md:flex-row md:items-start md:justify-start gap-4">
|
||||
<SettingsCard
|
||||
title="Language"
|
||||
description="Choose your language"
|
||||
input={{input:false}}
|
||||
button={{name: 'Change Language', id: 'setlang'}}
|
||||
select={{
|
||||
select: true,
|
||||
name: 'lang',
|
||||
options: [
|
||||
{name: 'English', value: 'en_US', disabled: false},
|
||||
{name: 'Japanese', value: 'jp', disabled: false},
|
||||
]
|
||||
}}
|
||||
both={{enabled: false}}
|
||||
/>
|
||||
<SettingsCard
|
||||
title="Reset instance"
|
||||
description="Reset your instance if your having troubles or problems."
|
||||
input={{ input: false }}
|
||||
button={{ name: 'Reset', id: 'resetinstance' }}
|
||||
select={{ select: false }}
|
||||
both={{ enabled: false }}
|
||||
/>
|
||||
</SettingsSection>
|
||||
</SettingsLayout>
|
||||
<script>
|
||||
import { EventHandler } from "@utils/events";
|
||||
import { navigate } from "astro:transitions/client";
|
||||
import { defaultStore } from "@utils/storage";
|
||||
import { SettingsVals } from "@utils/values";
|
||||
import { Elements } from "@utils/index";
|
||||
|
||||
const handler = async () => {
|
||||
const els = Elements.select([
|
||||
{ type: 'id', val: 'lang' },
|
||||
{ type: 'id', val: 'setlang' },
|
||||
{ type: 'id', val: 'resetinstance' }
|
||||
]);
|
||||
const lang = defaultStore.getVal(SettingsVals.i18n.lang);
|
||||
const val = Elements.exists<HTMLSelectElement>(await els.next());
|
||||
const button = Elements.exists<HTMLButtonElement>(await els.next());
|
||||
val.value = lang || "en_US";
|
||||
Elements.attachEvent(button, "click", () => {
|
||||
defaultStore.setVal(SettingsVals.i18n.lang, val.value);
|
||||
navigate(`${window.location.origin}/${val.value}/settings/misc`);
|
||||
});
|
||||
|
||||
const resetButton = Elements.exists<HTMLButtonElement>(await els.next());
|
||||
Elements.attachEvent(resetButton, "click", async () => {
|
||||
const t = await navigator.serviceWorker.getRegistrations();
|
||||
t.map(async (reg) => {
|
||||
await reg.unregister();
|
||||
});
|
||||
localStorage.clear();
|
||||
window.location.href = "/";
|
||||
});
|
||||
}
|
||||
|
||||
new EventHandler({
|
||||
events: {
|
||||
"astro:page-load": handler
|
||||
},
|
||||
logging: false
|
||||
})
|
||||
.bind();
|
||||
</script>
|
||||
267
src/pages/[lang]/settings/pr.astro
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
---
|
||||
import InstalledPlugins from "@components/catalog/InstalledPlugins.astro";
|
||||
import SettingsCard from "@components/settings/SettingsCard.astro";
|
||||
import Toast from "@components/toasts/Toast.svelte";
|
||||
import ToastWrapper from "@components/toasts/ToastWrapper.svelte";
|
||||
import Layout from "@layouts/Layout.astro";
|
||||
import SettingsLayout from "@layouts/SettingsLayout.astro";
|
||||
import SettingsSection from "@layouts/SettingsSection.astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
|
||||
|
||||
const lang = getLangFromUrl(Astro.url);
|
||||
const t = useTranslations(lang);
|
||||
import { MARKETPLACE_ENABLED } from "astro:env/client";
|
||||
---
|
||||
|
||||
<Layout title="Settings">
|
||||
<SettingsLayout title={t("settings.proxy")}>
|
||||
<SettingsSection
|
||||
title="Proxy"
|
||||
subtitle="A wide variety of settings for the proxy/rewriter itself."
|
||||
>
|
||||
<div
|
||||
class="w-full h-full flex flex-col items-center justify-center flex-wrap md:flex-row md:items-start md:justify-start gap-4"
|
||||
>
|
||||
<SettingsCard
|
||||
title="Proxy"
|
||||
description="Choose the proxy/rewriter that fits your needs"
|
||||
input={{ input: false }}
|
||||
button={{ name: "Change", id: "setproxy" }}
|
||||
select={{
|
||||
select: true,
|
||||
name: "proxy",
|
||||
options: [
|
||||
{ name: "Automatic", value: "automatic", disabled: false },
|
||||
{ name: "Ultraviolet", value: "uv", disabled: false },
|
||||
{ name: "Scramjet (BETA)", value: "sj", disabled: false },
|
||||
],
|
||||
}}
|
||||
both={{enabled: false}}
|
||||
/>
|
||||
<!-- SettingsCard
|
||||
title="Open in"
|
||||
description="Choose how to open your sites"
|
||||
input={{ input: false }}
|
||||
button={{ name: "Set", id: "setopenin" }}
|
||||
select={{
|
||||
select: true,
|
||||
name: "openin",
|
||||
options: [
|
||||
{ name: "Embed", value: "embed", disabled: false },
|
||||
{ name: "Direct", value: "direct", disabled: false },
|
||||
{ name: "About:Blank", value: "a:b", disabled: false },
|
||||
{ name: "Blob", value: "blob", disabled: false },
|
||||
],
|
||||
}}
|
||||
both={{enabled: false}}
|
||||
/ -->
|
||||
<SettingsCard
|
||||
title="Search Engine"
|
||||
description="Choose your search engine"
|
||||
input={{ input: false }}
|
||||
button={{ name: "Set Search Engine", id: "setsearchengine" }}
|
||||
select={{
|
||||
select: true,
|
||||
name: "searchengine",
|
||||
options: [
|
||||
{ name: "DuckDuckGo", value: "ddg", disabled: false },
|
||||
{ name: "Google", value: "google", disabled: false },
|
||||
{ name: "Bing", value: "bing", disabled: false },
|
||||
],
|
||||
}}
|
||||
both={{enabled: false}}
|
||||
/>
|
||||
<SettingsCard
|
||||
title="Wisp Server"
|
||||
description="Choose the wisp server you feel is the fastest"
|
||||
input={{ input: true, required: false, placeholder: 'wss://nebulaproxy.io/wisp' }}
|
||||
button={{ name: "Select", id: "setwispurl" }}
|
||||
select={{
|
||||
select: true,
|
||||
name: "wispurl",
|
||||
options: [
|
||||
{ name: "Default", value: "default", disabled: false },
|
||||
{ name: "Ruby Network (US)", value: "ruby", disabled: false },
|
||||
{ name: "Custom", value: "custom", disabled: false }
|
||||
],
|
||||
}}
|
||||
both={{
|
||||
enabled: true,
|
||||
showOnSelect: {value: 'custom'}
|
||||
}}
|
||||
/>
|
||||
<SettingsCard
|
||||
title="Transport"
|
||||
description="Select the transport to use"
|
||||
input={{ input: false }}
|
||||
button={{ name: "Set transport", id: "settransport" }}
|
||||
select={{
|
||||
select: true,
|
||||
name: "transport",
|
||||
options: [
|
||||
{ name: "Libcurl", value: "libcurl", disabled: false },
|
||||
{ name: "Epoxy", value: "epoxy", disabled: false },
|
||||
],
|
||||
}}
|
||||
both={{enabled: false}}
|
||||
/>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
{
|
||||
MARKETPLACE_ENABLED && (
|
||||
<SettingsSection
|
||||
title="Plugins"
|
||||
subtitle="Plugins allow you to modify the way the proxy works (UV only, plugins are auto applied)"
|
||||
>
|
||||
<div class="flex flex-row gap-6 justify-center md:justify-normal">
|
||||
<InstalledPlugins />
|
||||
<a
|
||||
href={`/${lang}/catalog/1`}
|
||||
class="rounded-3xl bg-navbar-color w-64 flex flex-col"
|
||||
>
|
||||
<div class="w-full items-center justify-center flex aspect-[16/9]">
|
||||
<Icon name="ph:plus-bold" class="h-16 w-16" />
|
||||
</div>
|
||||
<div class="h-2/6 text-center content-center p-3 font-semibold">
|
||||
Get more plugins in the <strong>Nebula Catalog!</strong>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
)
|
||||
}
|
||||
</SettingsLayout>
|
||||
<ToastWrapper client:load>
|
||||
<Toast
|
||||
toastProp={{
|
||||
toastType: "success",
|
||||
text: "Successfully changed proxy!",
|
||||
class: "proxyMessage",
|
||||
}}
|
||||
client:load
|
||||
/>
|
||||
<!-- Toast
|
||||
toastProp={{
|
||||
toastType: "success",
|
||||
text: "Saved selection!",
|
||||
class: "openInMessage",
|
||||
}}
|
||||
client:load
|
||||
/-->
|
||||
<Toast
|
||||
toastProp={{
|
||||
toastType: "success",
|
||||
text: "Saved Search Engine Selection!",
|
||||
class: "searchEngineMessage",
|
||||
}}
|
||||
client:load
|
||||
/>
|
||||
<Toast
|
||||
toastProp={{
|
||||
toastType: "success",
|
||||
text: "Wisp server selected!",
|
||||
class: "wispUrlMessage",
|
||||
}}
|
||||
client:load
|
||||
/>
|
||||
<Toast
|
||||
toastProp={{
|
||||
toastType: "success",
|
||||
text: "Transport set!",
|
||||
class: "transportMessage",
|
||||
}}
|
||||
client:load
|
||||
/>
|
||||
</ToastWrapper>
|
||||
</Layout>
|
||||
<script>
|
||||
import { EventHandler } from "@utils/events";
|
||||
import { Elements, toast } from "@utils/index";
|
||||
import { defaultStore } from "@utils/storage";
|
||||
import { SettingsVals } from "@utils/values";
|
||||
import { Settings } from "@utils/settings";
|
||||
import { setTransport, SW } from "@utils/serviceWorker";
|
||||
|
||||
const init = async (): Promise<AsyncGenerator> => {
|
||||
return Elements.select([
|
||||
{ type: 'id', val: 'setproxy' },
|
||||
{ type: 'id', val: 'proxy' },
|
||||
{ type: 'id', val: 'setsearchengine' },
|
||||
{ type: 'id', val: 'searchengine' },
|
||||
{ type: 'id', val: 'setwispurl' },
|
||||
{ type: 'id', val: 'wispurl' },
|
||||
{ type: 'id', val: 'inputOnSelectValuecustom' },
|
||||
{ type: 'id', val: 'settransport' },
|
||||
{ type: 'id', val: 'transport' },
|
||||
]);
|
||||
}
|
||||
|
||||
const handleProxy = async (vals: AsyncGenerator) => {
|
||||
const button = Elements.exists<HTMLButtonElement>(await vals.next());
|
||||
const selectEl = Elements.exists<HTMLSelectElement>(await vals.next());
|
||||
selectEl.value = defaultStore.getVal(SettingsVals.proxy.proxy.key);
|
||||
Elements.attachEvent(button, "click", () => {
|
||||
Settings.proxy.change(selectEl.value as "uv" | "sj" | "automatic");
|
||||
toast(".proxyMessage");
|
||||
});
|
||||
}
|
||||
|
||||
const handleSearchEngine = async (vals: AsyncGenerator) => {
|
||||
const button = Elements.exists<HTMLButtonElement>(await vals.next());
|
||||
const selectEl = Elements.exists<HTMLSelectElement>(await vals.next());
|
||||
selectEl.value = defaultStore.getVal(SettingsVals.proxy.searchEngine);
|
||||
Elements.attachEvent(button, "click", () => {
|
||||
Settings.proxy.searchEngine(selectEl.value);
|
||||
toast(".searchEngineMessage");
|
||||
});
|
||||
}
|
||||
|
||||
const handleWispServers = async (vals: AsyncGenerator) => {
|
||||
|
||||
const handleCustom = async (c: HTMLInputElement, removeClass: boolean) => {
|
||||
if (!removeClass) return c.classList.add("hidden");
|
||||
const sw = SW.getInstances().next().value as SW;
|
||||
const { bareMuxConn } = await sw.getSWInfo();
|
||||
c.classList.remove("hidden");
|
||||
if (c.value === "") return c.value = defaultStore.getVal("customWispUrl");
|
||||
defaultStore.setVal("customWispUrl", c.value);
|
||||
await setTransport(bareMuxConn);
|
||||
toast(".wispUrlMessage");
|
||||
}
|
||||
|
||||
const button = Elements.exists<HTMLButtonElement>(await vals.next());
|
||||
const selectEl = Elements.exists<HTMLSelectElement>(await vals.next());
|
||||
const custom = Elements.exists<HTMLInputElement>(await vals.next());
|
||||
selectEl.value = defaultStore.getVal(SettingsVals.proxy.wispServer);
|
||||
Elements.attachEvent(button, "click", () => {
|
||||
Settings.proxy.wisp(selectEl.value);
|
||||
selectEl.value === "custom" ? handleCustom(custom, true) : handleCustom(custom, false)
|
||||
toast(".wispUrlMessage");
|
||||
});
|
||||
};
|
||||
|
||||
const handleTransports = async (vals: AsyncGenerator) => {
|
||||
const button = Elements.exists<HTMLButtonElement>(await vals.next());
|
||||
const selectEl = Elements.exists<HTMLSelectElement>(await vals.next());
|
||||
selectEl.value = defaultStore.getVal(SettingsVals.proxy.transport.key);
|
||||
Elements.attachEvent(button, "click", () => {
|
||||
Settings.proxy.transport(selectEl.value as "libcurl" | "epoxy");
|
||||
toast(".transportMessage");
|
||||
});
|
||||
};
|
||||
|
||||
new EventHandler({
|
||||
events: {
|
||||
"astro:page-load": async () => {
|
||||
const v = await init();
|
||||
await handleProxy(v);
|
||||
await handleSearchEngine(v);
|
||||
await handleWispServers(v);
|
||||
await handleTransports(v);
|
||||
}
|
||||
},
|
||||
logging: false
|
||||
})
|
||||
.bind();
|
||||
</script>
|
||||