Compare commits
676 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 |
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 }
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
FROM node:18
|
||||
|
||||
# Install basic development tools
|
||||
RUN apt update && apt install -y less man-db sudo
|
||||
|
||||
# Ensure default `node` user has access to `sudo`
|
||||
ARG USERNAME=node
|
||||
RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
|
||||
&& chmod 0440 /etc/sudoers.d/$USERNAME
|
||||
|
||||
# Set `DEVCONTAINER` environment variable to help with orientation
|
||||
ENV DEVCONTAINER=true
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
// See https://containers.dev/implementors/json_reference/ for configuration reference
|
||||
{
|
||||
"name": "Nebula",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"remoteUser": "node",
|
||||
"postCreateCommand": "npm install"
|
||||
}
|
||||
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/
|
||||
4
.github/FUNDING.yml
vendored
|
|
@ -1,4 +0,0 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
patreon: nebuladevs
|
||||
ko_fi: nebulaa
|
||||
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,10 +1,43 @@
|
|||
# build output
|
||||
dist/
|
||||
server/*.js
|
||||
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
# System Files
|
||||
#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
|
||||
Thumbs.db
|
||||
.breakpoints
|
||||
|
||||
memory.txt
|
||||
old.app.js
|
||||
# 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
|
||||
|
|
@ -1 +0,0 @@
|
|||
node_modules
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "none"
|
||||
}
|
||||
86
.replit
|
|
@ -1,86 +0,0 @@
|
|||
entrypoint = "app.js"
|
||||
|
||||
hidden = [".config", "package-lock.json"]
|
||||
|
||||
[interpreter]
|
||||
command = [
|
||||
"prybar-nodejs",
|
||||
"-q",
|
||||
"--ps1",
|
||||
"\u0001\u001b[33m\u0002\u0001\u001b[00m\u0002 ",
|
||||
"-i"
|
||||
]
|
||||
|
||||
[[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-22_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"
|
||||
|
||||
[gitHubImport]
|
||||
requiredFiles = [".replit", "replit.nix", ".config", "package.json", "package-lock.json"]
|
||||
|
||||
[packager]
|
||||
language = "nodejs"
|
||||
|
||||
[packager.features]
|
||||
packageSearch = true
|
||||
guessImports = true
|
||||
enabledForHosting = false
|
||||
|
||||
[unitTest]
|
||||
language = "nodejs"
|
||||
|
||||
[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"
|
||||
|
||||
[languages]
|
||||
|
||||
[languages.javascript]
|
||||
pattern = "**/{*.js,*.jsx,*.ts,*.tsx}"
|
||||
|
||||
[languages.javascript.languageServer]
|
||||
start = "typescript-language-server --stdio"
|
||||
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
.vscode/settings.json
vendored
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
}
|
||||
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,7 +1,19 @@
|
|||
FROM node:alpine
|
||||
WORKDIR /usr/src/app
|
||||
FROM node:22-alpine
|
||||
|
||||
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"]
|
||||
|
|
|
|||
2
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.
|
||||
|
||||
|
|
|
|||
542
README.md
|
|
@ -1,213 +1,371 @@
|
|||
# Nebula
|
||||
<div align="center">
|
||||
|
||||
NebulaWeb is an official flagship of Nebula Services and Nebula Developer Labs. NebulaWeb is a stunning, sleek, and functional web-proxy with support for thousands of popular sites. With NebulaWeb, the sky is the limit.
|
||||
<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 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)
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- Stunning and highly functional UI with multiple themes
|
||||
- XOR/b64 encoding all traffic
|
||||
- Hides your IP from sites
|
||||
- [List of officially supported sites](https://github.com/NebulaServices/Nebula/blob/dev/docs/officially-supported-sites.md)
|
||||
- _limited_ mobile support
|
||||
- Stealth Mode (buffed `about:blank` cloaking)
|
||||
- **NEW** Clickoff cloaking
|
||||
- **NEW** Email OTP verification
|
||||
|
||||
# Deployment
|
||||
|
||||
Table of contents
|
||||
|
||||
- Quick & easy deployment
|
||||
- Deployment configuration explaination
|
||||
- how to use email OTP Verification mode
|
||||
- Advanced Deployment
|
||||
- Filesystem
|
||||
|
||||
## Quick & Easy Deployment Options
|
||||
|
||||
[](https://heroku.com/deploy/?template=https://github.com/NebulaServices/Nebula)
|
||||
<br>
|
||||
[](https://replit.com/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)
|
||||
<br>
|
||||
[](https://render.com/deploy?repo=https://github.com/NebulaServices/Nebula)
|
||||
|
||||
- Multiple Proxy "Backends":
|
||||
- [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet)
|
||||
- [RammerHead](https://github.com/binary-person/rammerhead)
|
||||
---
|
||||
|
||||
## Deployment Configuration Guide
|
||||
|
||||
(Example configuration with none-json notes)
|
||||
|
||||
```json
|
||||
{
|
||||
"sendgrid_verification": false,
|
||||
"sendgrid_options": {
|
||||
"api_key": "YOUR_SENDGRID_API_KEY",
|
||||
"sendFromEmail": "THE EMAIL THE CODES WILL BE SENT FROM (MUST BE VERIFIED IN SENDGRID)",
|
||||
"to_email": "THE EMAIL YOU WANT THE CODES SENT TO"
|
||||
},
|
||||
|
||||
"discord_verification": false,
|
||||
"webhook_url": "YOUR DISCORD WEBHOOK URL",
|
||||
|
||||
"smtp_verification": false,
|
||||
"smtp_options": {
|
||||
"to_email": "THE EMAIL YOU WANT THE CODES SENT TO",
|
||||
"sendFromEmail": "THE EMAIL THE CODES ARE SENT FROM",
|
||||
"host": "YOUR SMTP HOST",
|
||||
"port": 465,
|
||||
"auth": {
|
||||
"user": "SMTP USER",
|
||||
"pass": "YOUR PASSWORD"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Email Verification OTP
|
||||
|
||||
### What is this?
|
||||
|
||||
Email verification is a new and unique feature that we've implemented in the event that someone wants to keep their deployment of Nebula private and secure.
|
||||
|
||||
### What does it do
|
||||
|
||||
When a user tries to access the website, before allowed access they will be asked for a One time password sent to an email set in the deployment configuration. Once verified, they will have 15 day access to the site.
|
||||
|
||||
#### SendGrid Setup Instructions
|
||||
|
||||
- Firstly, We need to enable verification within the deployment configuration
|
||||
|
||||
- change `"sendgrid_verification":false,` to `"sendgrid_verification":true,` above the SendGrid Section
|
||||
|
||||
- _Note: You have to reboot the node app for any changes to take place._
|
||||
|
||||
- Now, we need to use an api to send a message
|
||||
- Make an account at Sendgrid (https://app.sendgrid.com/)
|
||||
- _Note: It is likely that other versions of Nebula will use a different package to send emails._
|
||||
- Verify the email you want to recieve emails from (Create a sender identity)
|
||||
- Go to settings -> Sender authentication and click Verify a Single Sender
|
||||
- Now, We need to get the API key to connect to the API
|
||||
- Go to settings -> API Keys -> and make an API key.
|
||||
- Complete the information in the deployment config `deployment.config.json` under the `sendgrid_options` section such as: sendFromEmail, to_email and api_key
|
||||
|
||||
#### Discord Webhook Setup Instructions
|
||||
|
||||
- Set discord_verification to true in the deployment configuration.
|
||||
- Create a channel in a discord server you have admin in.
|
||||
- Click the Edit Channel button.
|
||||
- Click Integrations
|
||||
- Click create web hook and copy the URL.
|
||||
- Paste it under the `webhook_url` section in the deployment configuration.
|
||||
|
||||
#### SMTP Setup Instructions
|
||||
|
||||
- Set `smtp_verification` to true.
|
||||
- Change `to_email` to the email address you want the codes to be sent to.
|
||||
- Change `sendFromEmail to the email address that is going to send the codes.
|
||||
- Get the host and port from your email provider's documentation.
|
||||
- Fill in your username and password under the `user` and `pass` section under auth.
|
||||
|
||||
## Advanced Deployment
|
||||
|
||||
### Initial configuration
|
||||
|
||||
credits to @ProgrammerIn-wonderland for writing this wonderful tutorial (which can also be found in the docs :)
|
||||
|
||||
- Create an account at https://www.cloudflare.com/
|
||||
- Create an account at https://www.freenom.com/ (or any registrars)
|
||||
- Find a free domain name at Freenom
|
||||
- Click checkout
|
||||
- Select (12 Months @ FREE)
|
||||
- Select "Use DNS"
|
||||
- Select Use your own DNS
|
||||
- Go to cloudflare, click add new site, and enter the free domain name
|
||||
- Select "Free Plan"
|
||||
- Click continue, ignore DNS
|
||||
- Copy the name servers cloudflare gives you
|
||||
- Go back to your Freenom tab, enter in the name servers which cloudflare gave you
|
||||
- You can keep IP blank
|
||||
- Click continue
|
||||
- Click complete order
|
||||
- Go back to cloudflare tab, click "Check Nameservers"
|
||||
- Select DNS on your right bar
|
||||
- Enter in the IP of the server which will be hosting Nebula
|
||||
- Target will be `@`
|
||||
- Click Enable proxy (little gray cloud icon, if active its orange)
|
||||
- Select SSL/TLS in your right bar
|
||||
- Click "Flexible"
|
||||
## Contributors
|
||||
|
||||
- [Rifting](https://github.com/rifting) - Owner & Maintainer
|
||||
- [MotorTruck1221](https://motortruck1221.com) - Maintainer
|
||||
---
|
||||
|
||||
### Server configuration
|
||||
|
||||
- SSH into the server you'll be using, I'll assume its running Ubuntu 22.04 (though the commands are the same for debian 10+ versions, and Ubuntu versions 20.04+)
|
||||
- run
|
||||
|
||||
```
|
||||
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - \ &&
|
||||
sudo apt-get install -y nodejs npm
|
||||
git clone https://github.com/NebulaServices/Nebula.git
|
||||
cd Nebula
|
||||
npm i
|
||||
npm ci
|
||||
sudo nohup PORT=80 node . &
|
||||
```
|
||||
|
||||
**Make sure your firewall is configured to let through port 80 traffic!** \
|
||||
_Note: Server will need to run` cd Nebula && sudo nohup PORT=80 node . &` on reboot_
|
||||
|
||||
## File Structure
|
||||
|
||||
| **File** | Purpose | |
|
||||
| -------------------------------- | -------------------------------------------------------------------------------------------------------- | --- |
|
||||
| `src/index.html` | The main frontend visuals for NebulaWEB. | |
|
||||
| `src/unv.html` | The verification-required frontend/visuals. | |
|
||||
| `src/options.html` | The frontend for Nebula's options, settings, and preferences. | |
|
||||
| `public/resources/v.js` | Client verification system for the OTP system. | |
|
||||
| `public/resources/nebulamain.js` | All of the DOM/client code for NebulaWEB. Includes options, themeSystem, cloak, stealthengine, and more. | |
|
||||
| `app.js` | The backend server for Nebula. Contains Nodestatic, Bare, HTTP, and more. | |
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- HTML, JS, CSS
|
||||
- Partical.JS (Specifically v4, 5, 6.1 &< only)
|
||||
- Ultraviolet (proxy)
|
||||
- Osana (proxy)
|
||||
- TompHTTP Bare Server Node
|
||||
- ExpressJS
|
||||
- [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)
|
||||
---
|
||||
|
||||
## Support
|
||||
## Catalog/Marketplace
|
||||
|
||||
For support, email chloe@nebula.bio or join our discord: discord.gg/unblocker
|
||||
- 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)
|
||||
|
||||
## Demo
|
||||
### How to make a theme
|
||||
|
||||
[Click here to see a demo of Nebula](https://nebulaproxy.io/)
|
||||
- Themes allow you to customize Nebula's *look*.
|
||||
|
||||
## Acknowledgements
|
||||
#### Prerequisites:
|
||||
- Make sure you have our [Discord server](https://discord.gg/unblocker) so you can submit your theme
|
||||
|
||||
- [UV (one of the proxies use)](https://github.com/titaniumnetwork-dev/Ultraviolet)
|
||||
- [Osana (one of the proxies we use)](https://github.com/NebulaServices/Osana)
|
||||
- [Bare Server Node](https://github.com/tomphttp/bare-server-node)
|
||||
- [Partical.JS (v4, 5, 6.1 &< only)](https://github.com/VincentGarreau/particles.js)
|
||||
##### Making the themes:
|
||||
|
||||
## License
|
||||
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: ;
|
||||
}
|
||||
```
|
||||
|
||||
(Nebula's license is now GNU AGPL V3 as of v7.10)
|
||||
Copyright Nebula Services 2021 - Present
|
||||
<br>
|
||||
This project uses the AGLP GNU V3 license.
|
||||
> [!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)
|
||||
|
||||
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)
|
||||
|
||||
3. Once you're satisfied with the colors, submit your theme to the [Discord Server](https://discord.gg/unblocker)!
|
||||
|
||||
---
|
||||
### 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!
|
||||
|
|
|
|||
249
app.js
|
|
@ -1,249 +0,0 @@
|
|||
import express from "express";
|
||||
import cookieParser from "cookie-parser";
|
||||
import http from "node:http";
|
||||
import createBareServer from "@tomphttp/bare-server-node";
|
||||
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
|
||||
import path from "node:path";
|
||||
import config from "./deployment.config.json" assert { type: "json" };
|
||||
import sgMail from "@sendgrid/mail";
|
||||
import nodemailer from "nodemailer";
|
||||
import * as uuid from "uuid";
|
||||
import fs from "node:fs";
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
const __dirname = process.cwd();
|
||||
const ACTIVE_CODES = new Set();
|
||||
if (!fs.existsSync("./memory.txt")) {
|
||||
fs.writeFileSync("./memory.txt", "", "utf-8");
|
||||
}
|
||||
let TOKENS = fs
|
||||
.readFileSync("./memory.txt", "utf-8")
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((token) => {
|
||||
const parts = token.split(":");
|
||||
return {
|
||||
id: parts[0],
|
||||
token: parts[1],
|
||||
expiration: parts[2]
|
||||
};
|
||||
});
|
||||
|
||||
const server = http.createServer();
|
||||
const app = express(server);
|
||||
const bareServer = createBareServer("/bare/");
|
||||
|
||||
// Middleware
|
||||
app.use(cookieParser());
|
||||
app.use(express.json());
|
||||
app.use(
|
||||
express.urlencoded({
|
||||
extended: true
|
||||
})
|
||||
);
|
||||
|
||||
// Verification
|
||||
app.patch("/generate-otp", async (req, res) => {
|
||||
if (
|
||||
config.sendgrid_verification ||
|
||||
config.discord_verification ||
|
||||
config.smtp_verificaton
|
||||
) {
|
||||
const OTP = generateCode();
|
||||
ACTIVE_CODES.add(OTP);
|
||||
|
||||
setTimeout(() => {
|
||||
ACTIVE_CODES.delete(OTP);
|
||||
}, 1000 * 60 * 5);
|
||||
|
||||
let email = {
|
||||
to: "",
|
||||
from: "",
|
||||
subject: `NebulaWEB personal access code ${OTP}`,
|
||||
text: `
|
||||
####### ACCESS CODE (OTP) ${OTP} #######
|
||||
####### DO NOT SHARE THIS CODE! #######
|
||||
(this message is automated)`
|
||||
};
|
||||
|
||||
if (config.sendgrid_verification) {
|
||||
sgMail.setApiKey(config.sendgrid_options.api_key);
|
||||
|
||||
email.to = config.sendgrid_options.to_email;
|
||||
email.from = config.sendgrid_options.sendFromEmail;
|
||||
try {
|
||||
await sgMail.send(msg);
|
||||
} catch {
|
||||
return res.status(504).end();
|
||||
}
|
||||
}
|
||||
|
||||
if (config.smtp_verification) {
|
||||
const smtpMailerAgent = nodemailer.createTransport(config.smtp_options);
|
||||
|
||||
email.to = config.smtp_options.to_email;
|
||||
email.from = config.smtp_options.sendFromEmail;
|
||||
try {
|
||||
smtpMailerAgent.sendMail(email);
|
||||
} catch {
|
||||
return res.status(504).end();
|
||||
}
|
||||
}
|
||||
|
||||
if (config.discord_verification) {
|
||||
try {
|
||||
await fetch(config.webhook_url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
content: `Your NebulaWEB access code is \`${OTP}\``
|
||||
})
|
||||
});
|
||||
} catch {
|
||||
return res.status(500).end();
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).end();
|
||||
} else {
|
||||
res.status(404).end();
|
||||
}
|
||||
});
|
||||
|
||||
function generateCode() {
|
||||
const code = Math.floor(Math.random() * 1000000);
|
||||
return code.toString().padStart(6, "0");
|
||||
}
|
||||
|
||||
app.post("/validate-otp", (req, res) => {
|
||||
if (
|
||||
config.sendgrid_verification ||
|
||||
config.discord_verification ||
|
||||
config.smtp_verificaton
|
||||
) {
|
||||
const OTP = req.body.otp;
|
||||
|
||||
if (ACTIVE_CODES.has(OTP)) {
|
||||
ACTIVE_CODES.delete(OTP);
|
||||
|
||||
const token = uuid.v4();
|
||||
|
||||
TOKENS.push({
|
||||
id: OTP,
|
||||
token: hash(token),
|
||||
expiration: Date.now() + 1000 * 60 * 60 * 24 * 30
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
"./memory.txt",
|
||||
TOKENS.map((token) => {
|
||||
return `${token.id}:${token.token}:${token.expiration}`;
|
||||
}).join("\n"),
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
validation: `${OTP}:${token}`
|
||||
});
|
||||
} else {
|
||||
res.status(401).json({
|
||||
success: false
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(404).end();
|
||||
}
|
||||
});
|
||||
|
||||
// Static files
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
app.use("/uv/", express.static(uvPath));
|
||||
|
||||
// Login route
|
||||
app.get("/login", (req, res) => {
|
||||
if (
|
||||
config.sendgrid_verification ||
|
||||
config.discord_verification ||
|
||||
config.smtp_verificaton
|
||||
) {
|
||||
res.sendFile(path.join(__dirname, "src", "unv.html"));
|
||||
} else {
|
||||
res.redirect("/");
|
||||
}
|
||||
});
|
||||
|
||||
// General Routes
|
||||
app.use((req, res, next) => {
|
||||
if (
|
||||
config.sendgrid_verification ||
|
||||
config.discord_verification ||
|
||||
config.smtp_verificaton
|
||||
) {
|
||||
const verification = req.cookies["validation"];
|
||||
if (!verification || !validateToken(verification)) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.sendFile(path.join(__dirname, "src", "index.html"));
|
||||
});
|
||||
|
||||
app.get("/options", (req, res) => {
|
||||
res.sendFile(path.join(__dirname, "src", "options.html"));
|
||||
});
|
||||
|
||||
app.get("/privacy", (req, res) => {
|
||||
res.sendFile(path.join(__dirname, "src", "privacy.html"));
|
||||
});
|
||||
|
||||
// Bare Server
|
||||
server.on("request", (req, res) => {
|
||||
if (bareServer.shouldRoute(req)) {
|
||||
bareServer.routeRequest(req, res);
|
||||
} else {
|
||||
app(req, res);
|
||||
}
|
||||
});
|
||||
|
||||
server.on("upgrade", (req, socket, head) => {
|
||||
if (bareServer.shouldRoute(req)) {
|
||||
bareServer.routeUpgrade(req, socket, head);
|
||||
} else {
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
|
||||
server.on("listening", () => {
|
||||
console.log(`Server running at http://localhost:${PORT}/.`);
|
||||
});
|
||||
|
||||
server.listen({
|
||||
port: PORT
|
||||
});
|
||||
|
||||
function hash(token) {
|
||||
const salt = bcrypt.genSaltSync(10);
|
||||
return bcrypt.hashSync(token, salt);
|
||||
}
|
||||
|
||||
function validateToken(verification) {
|
||||
const [id, token] = verification.split(":");
|
||||
const tokenData = TOKENS.find((token) => token.id == id);
|
||||
|
||||
if (!tokenData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tokenData.expiration < Date.now()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return bcrypt.compareSync(token, tokenData.token);
|
||||
}
|
||||
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
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"sendgrid_verification": false,
|
||||
"sendgrid_options": {
|
||||
"api_key": "YOUR_SENDGRID_API_KEY",
|
||||
"sendFromEmail": "THE EMAIL THE CODES WILL BE SENT FROM (MUST BE VERIFIED IN SENDGRID)",
|
||||
"to_email": "THE EMAIL YOU WANT THE CODES SENT TO"
|
||||
},
|
||||
|
||||
"discord_verification": false,
|
||||
"webhook_url": "YOUR DISCORD WEBHOOK URL",
|
||||
|
||||
"smtp_verification": false,
|
||||
"smtp_options": {
|
||||
"to_email": "THE EMAIL YOU WANT THE CODES SENT TO",
|
||||
"sendFromEmail": "THE EMAIL THE CODES ARE SENT FROM",
|
||||
"host": "YOUR SMTP HOST",
|
||||
"port": 465,
|
||||
"auth": {
|
||||
"user": "SMTP USER",
|
||||
"pass": "YOUR PASSWORD"
|
||||
}
|
||||
}
|
||||
}
|
||||
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,10 +1,20 @@
|
|||
version: "3"
|
||||
services:
|
||||
nebula:
|
||||
image: nebula:latest
|
||||
build: .
|
||||
image: ghcr.io/nebulaservices/nebula:latest
|
||||
container_name: nebula
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# DO NOT CHANGE 3000!
|
||||
- your port here:3000
|
||||
# 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,8 +0,0 @@
|
|||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "Site",
|
||||
script: "proxysocks node app.js"
|
||||
}
|
||||
]
|
||||
};
|
||||
2551
package-lock.json
generated
78
package.json
|
|
@ -1,28 +1,66 @@
|
|||
{
|
||||
"name": "nebula-web",
|
||||
"version": "7.11.6",
|
||||
"description": "Explore the web. Freely.",
|
||||
"name": "nebula",
|
||||
"type": "module",
|
||||
"main": "app.js",
|
||||
"version": "9.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node app.js"
|
||||
"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"
|
||||
},
|
||||
"keywords": [
|
||||
"proxy"
|
||||
],
|
||||
"author": "Nebula Services",
|
||||
"license": "AGPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"@titaniumnetwork-dev/ultraviolet": "^1.0.8-beta",
|
||||
"@tomphttp/bare-server-node": "^1.2.3",
|
||||
"bcrypt": "^5.1.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"express": "^4.18.2",
|
||||
"nodemailer": "^6.9.1",
|
||||
"uuid": "^9.0.0"
|
||||
"@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"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"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;
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
(()=>{"use strict";self.__osana$config={bare:`${location.origin}/bare/`,prefix:"/service/~osana/",codec:self.__osana$bundle.codecs.none,files:{config:"/osana/osana.config.js",client:"/osana/osana.client.js",bundle:"/osana/osana.bundle.js",worker:"/osana/osana.worker.js"},blacklist:[/^(www\.)?netflix\.com/,/^accounts\.google\.com/]}})();
|
||||
//# sourceMappingURL=osana.config.js.map
|
||||
|
|
@ -1 +0,0 @@
|
|||
{"version":3,"file":"osana.config.js","mappings":"mBAAAA,KAAKC,eAAiB,CAClBC,KAAM,GAAGC,SAASC,eAClBC,OAAQ,WACRC,MAAON,KAAKO,eAAeC,OAAOC,KAClCC,MAAO,CACHC,OAAQ,mBACRC,OAAQ,mBACRC,OAAQ,mBACRC,OAAQ,oBAEZC,UAAW,CACP,wBACA,0B","sources":["webpack://osana/./src/config.ts"],"sourcesContent":["self.__osana$config = {\n bare: `${location.origin}/bare/`,\n prefix: \"/~osana/\",\n codec: self.__osana$bundle.codecs.none,\n files: {\n config: \"/osana.config.js\",\n client: \"/osana.client.js\",\n bundle: \"/osana.bundle.js\",\n worker: \"/osana.worker.js\"\n },\n blacklist: [\n /^(www\\.)?netflix\\.com/,\n /^accounts\\.google\\.com/,\n ]\n};\nexport {};\n"],"names":["self","__osana$config","bare","location","origin","prefix","codec","__osana$bundle","codecs","none","files","config","client","bundle","worker","blacklist"],"sourceRoot":""}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
(()=>{"use strict";importScripts("/osana/osana.bundle.js?1"),importScripts("/osana/osana.config.js?1"),self.OsanaServiceWorker=class{constructor(){this.config=self.__osana$config,this.bundle=self.__osana$bundle,this.bareClient=new this.bundle.BareClient(this.config.bare)}fetch(t){return s=this,n=void 0,r=function*(){const s=this.config.codec.decode(new URL(t.request.url).pathname.replace(this.config.prefix,""))+new URL(t.request.url).search;if(!/^https?:\/\//.test(s))return fetch(t.request.url);const n=new URL(s),i=this.bundle.rewrite.headers.request(Object.fromEntries(t.request.headers.entries()),n);if(this.config.blacklist&&this.config.blacklist.some((e=>e.test(n.host))))return new e;const r={method:t.request.method,headers:i};["GET","HEAD"].includes(t.request.method)||(r.body=yield t.request.blob());const o=yield this.bareClient.fetch(n,r);let a=o.rawResponse.status;const c=this.bundle.rewrite.headers.response(o.rawHeaders,n);let l="";return/text\/html/.test(c["Content-Type"])?(l=`<head><script src="${this.config.files.bundle}?1"><\/script><script src="${this.config.files.config}?1"><\/script><script src="${this.config.files.client}?1"><\/script><link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYGBkZAAAAAoAAx9k7/gAAAAASUVORK5CYIIA"><link rel="icon" href="${n.origin}/favicon.ico">`+(301===a&&c.location?`<meta http-equiv="refresh" content="0; url=${this.bundle.rewrite.url(c.location)}">`:"")+"</head>",l+=this.bundle.rewrite.html(yield o.text(),n.origin+n.pathname)):l=/text\/css/.test(c["Content-Type"])||"style"===t.request.destination?this.bundle.rewrite.css(yield o.text(),n.origin+n.pathname):/application\/javascript/.test(c["Content-Type"])||"script"===t.request.destination?this.bundle.rewrite.js(yield o.text()):yield o.arrayBuffer(),new Response(l,{status:o.rawResponse.status,statusText:o.rawResponse.statusText,headers:c})},new((i=void 0)||(i=Promise))((function(e,t){function o(e){try{c(r.next(e))}catch(e){t(e)}}function a(e){try{c(r.throw(e))}catch(e){t(e)}}function c(t){var s;t.done?e(t.value):(s=t.value,s instanceof i?s:new i((function(e){e(s)}))).then(o,a)}c((r=r.apply(s,n||[])).next())}));var s,n,i,r}};class e extends Response{constructor(){super("Forbidden",{status:403,statusText:"Forbidden",headers:{"Content-Type":"text/plain"}})}}})();
|
||||
//# sourceMappingURL=osana.worker.js.map
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
{
|
||||
"adjectives": [
|
||||
"admiring",
|
||||
"adoring",
|
||||
"affectionate",
|
||||
"agitated",
|
||||
"amazing",
|
||||
"angry",
|
||||
"awesome",
|
||||
"beautiful",
|
||||
"blissful",
|
||||
"bold",
|
||||
"boring",
|
||||
"brave",
|
||||
"busy",
|
||||
"charming",
|
||||
"clever",
|
||||
"cool",
|
||||
"compassionate",
|
||||
"competent",
|
||||
"condescending",
|
||||
"confident",
|
||||
"cranky",
|
||||
"crazy",
|
||||
"dazzling",
|
||||
"determined",
|
||||
"distracted",
|
||||
"dreamy",
|
||||
"eager",
|
||||
"ecstatic",
|
||||
"elastic",
|
||||
"elated",
|
||||
"elegant",
|
||||
"eloquent",
|
||||
"epic",
|
||||
"exciting",
|
||||
"fervent",
|
||||
"festive",
|
||||
"flamboyant",
|
||||
"focused",
|
||||
"friendly",
|
||||
"frosty",
|
||||
"funny",
|
||||
"gallant",
|
||||
"gifted",
|
||||
"goofy",
|
||||
"gracious",
|
||||
"great",
|
||||
"happy",
|
||||
"hardcore",
|
||||
"heuristic",
|
||||
"hopeful",
|
||||
"hungry",
|
||||
"infallible",
|
||||
"inspiring",
|
||||
"interesting",
|
||||
"intelligent",
|
||||
"jolly",
|
||||
"jovial",
|
||||
"keen",
|
||||
"kind",
|
||||
"laughing",
|
||||
"loving",
|
||||
"lucid",
|
||||
"magical",
|
||||
"mystifying",
|
||||
"modest",
|
||||
"musing",
|
||||
"naughty",
|
||||
"nervous",
|
||||
"nice",
|
||||
"nifty",
|
||||
"nostalgic",
|
||||
"objective",
|
||||
"optimistic",
|
||||
"peaceful",
|
||||
"pedantic",
|
||||
"pensive",
|
||||
"practical",
|
||||
"priceless",
|
||||
"quirky",
|
||||
"quizzical",
|
||||
"recursing",
|
||||
"relaxed",
|
||||
"reverent",
|
||||
"romantic",
|
||||
"sad",
|
||||
"serene",
|
||||
"sharp",
|
||||
"silly",
|
||||
"sleepy",
|
||||
"stoic",
|
||||
"strange",
|
||||
"stupefied",
|
||||
"suspicious",
|
||||
"sweet",
|
||||
"tender",
|
||||
"thirsty",
|
||||
"trusting",
|
||||
"unruffled",
|
||||
"upbeat",
|
||||
"vibrant",
|
||||
"vigilant",
|
||||
"vigorous",
|
||||
"wizardly",
|
||||
"wonderful",
|
||||
"xenodochial",
|
||||
"youthful",
|
||||
"zealous",
|
||||
"zen"
|
||||
],
|
||||
"surnames": [
|
||||
"albattani",
|
||||
"allen",
|
||||
"almeida",
|
||||
"antonelli",
|
||||
"agnesi",
|
||||
"archimedes",
|
||||
"ardinghelli",
|
||||
"aryabhata",
|
||||
"austin",
|
||||
"babbage",
|
||||
"banach",
|
||||
"banzai",
|
||||
"bardeen",
|
||||
"bartik",
|
||||
"bassi",
|
||||
"beaver",
|
||||
"bell",
|
||||
"benz",
|
||||
"bhabha",
|
||||
"bhaskara",
|
||||
"black",
|
||||
"blackburn",
|
||||
"blackwell",
|
||||
"bohr",
|
||||
"booth",
|
||||
"borg",
|
||||
"bose",
|
||||
"bouman",
|
||||
"boyd",
|
||||
"brahmagupta",
|
||||
"brattain",
|
||||
"brown",
|
||||
"buck",
|
||||
"burnell",
|
||||
"cannon",
|
||||
"carson",
|
||||
"cartwright",
|
||||
"carver",
|
||||
"cerf",
|
||||
"chandrasekhar"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
// Get's the current day using the Date function built in.
|
||||
// A dependency for displaying time - displayTime(void)
|
||||
function getDayName(dateStr, locale) {
|
||||
var date = new Date(dateStr);
|
||||
return date.toLocaleDateString(locale, { weekday: "long" });
|
||||
}
|
||||
|
||||
// The main function to show the time on the main page
|
||||
// needs to be initialized by a call (only one)
|
||||
// Dependent on getDayName function
|
||||
function displayTime() {
|
||||
var date = new Date();
|
||||
var h = date.getHours(); // 0 - 23
|
||||
var m = date.getMinutes(); // 0 - 59
|
||||
var s = date.getSeconds(); // 0 - 59
|
||||
var session = "AM";
|
||||
h = h == 12 ? 24 : h;
|
||||
|
||||
if (h == 0) {
|
||||
h = 12;
|
||||
} else if (h >= 12) {
|
||||
h = h - 12;
|
||||
session = "PM";
|
||||
}
|
||||
h = h < 10 ? "0" + h : h;
|
||||
m = m < 10 ? "0" + m : m;
|
||||
s = s < 10 ? "0" + s : s;
|
||||
// Repeat itself every second
|
||||
setTimeout(displayTime, 1000);
|
||||
// Get today's date
|
||||
var today = new Date();
|
||||
var dd = String(today.getDate()).padStart(2, "0");
|
||||
var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0!
|
||||
var yyyy = today.getFullYear();
|
||||
today = mm + "/" + dd + "/" + yyyy;
|
||||
var time = h + "<span style='opacity:100%;' class='clockColon'>:</span>" + m;
|
||||
try {
|
||||
document.getElementById("digitalClock").innerHTML =
|
||||
getDayName(today, "us-US") + ", " + time + " " + session + ".";
|
||||
} catch {}
|
||||
|
||||
return time;
|
||||
}
|
||||
// initialize the time function
|
||||
|
||||
displayTime();
|
||||
|
|
@ -1,356 +0,0 @@
|
|||
// Welcome to the main Nebula script
|
||||
// This script handles all the tasks neccesary for a proxy.
|
||||
// What this doesn't include is the actual proxies, just the neccesary tasks in order for the proxies to be able to preform, such as registering the service worker required by Interception proxies.
|
||||
|
||||
// Documentation Writers/Contributors:
|
||||
// GreenWorld#0001 (Discord) / GreenyDev (Github)
|
||||
// If you would like to contribute, feel free to open a pull request.
|
||||
// These docs are not finished
|
||||
|
||||
// Navigation controls for smaller devices
|
||||
// Executed in the inline HTML
|
||||
function openNav() {
|
||||
document.getElementById("sidenav").style.width = "260px";
|
||||
}
|
||||
function closeNav() {
|
||||
document.getElementById("sidenav").style.width = "0px";
|
||||
}
|
||||
|
||||
function setLoaderText() {
|
||||
document.getElementById("connectorText").textContent =
|
||||
"connecting to service";
|
||||
const loader = document.getElementById("lpoader");
|
||||
|
||||
const loadConstructer = loader.style;
|
||||
loadConstructer.display = "flex";
|
||||
loadConstructer.justifyContent = "center";
|
||||
// Changing the text over multiple periods of time creates character, and aliveness (is that even a word?)
|
||||
setTimeout(() => {
|
||||
document.getElementById("connectorText").style.fontSize = "12px";
|
||||
document.getElementById("connectorText").textContent =
|
||||
"Due to high server load, this may take a while.";
|
||||
}, 3200);
|
||||
setTimeout(() => {
|
||||
document.getElementById("connectorText").style.fontSize = "14px";
|
||||
document.getElementById("connectorText").textContent =
|
||||
"Hmmm.. Something isn't right..";
|
||||
}, 17000);
|
||||
}
|
||||
|
||||
window.stealthEngineLoaded = false;
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
// Register the service workers for Osana and Ultraviolet proxy protocols
|
||||
// This is a better method than registering onsubmit because this allows the ability to use proxied links on the main page.
|
||||
|
||||
navigator.serviceWorker.register("./sw.js", {
|
||||
scope: "/service/"
|
||||
});
|
||||
|
||||
// Link evaluation
|
||||
// This functions' purpose is to check a string of text (the argument)
|
||||
// it recognizes whether a string is a URL or not, and it returns a true or false value
|
||||
function isUrl(val = "") {
|
||||
if (
|
||||
/^http(s?):\/\//.test(val) ||
|
||||
(val.includes(".") && val.substr(0, 1) !== " ")
|
||||
)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
const proxy = localStorage.getItem("proxy") || "uv";
|
||||
const inpbox = document.querySelector("form");
|
||||
// Display the "loading" indicators on the main page, looks much better than a static/still screen.
|
||||
|
||||
const hasLoadedElement = document.createElement("div");
|
||||
hasLoadedElement.id = "hasLoaded";
|
||||
hasLoadedElement.style.display = "none";
|
||||
document.body.appendChild(hasLoadedElement);
|
||||
|
||||
inpbox.addEventListener("submit", (event) => {
|
||||
// Prevents the default event tasks
|
||||
event.preventDefault();
|
||||
console.log("Connecting to service -> loading");
|
||||
setLoaderText();
|
||||
});
|
||||
|
||||
// Form submission
|
||||
const form = document.querySelector("form");
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
// Check if the service worker (commonly called SW) is registered
|
||||
if (typeof navigator.serviceWorker === "undefined")
|
||||
alert(
|
||||
"An error occured registering your service worker. Please contact support - discord.gg/unblocker"
|
||||
);
|
||||
//
|
||||
if (proxy === "uv" || proxy === "osana") {
|
||||
// Re-register the service worker incase it failed to onload
|
||||
navigator.serviceWorker
|
||||
.register("./sw.js", {
|
||||
scope: "/service/"
|
||||
})
|
||||
.then(() => {
|
||||
const value = event.target.firstElementChild.value;
|
||||
let url = value.trim();
|
||||
if (!isUrl(url)) url = "https://www.google.com/search?q=" + url;
|
||||
if (!(url.startsWith("https://") || url.startsWith("http://")))
|
||||
url = "http://" + url;
|
||||
// encode the URL for UltraViolet
|
||||
let redirectTo =
|
||||
proxy === "uv"
|
||||
? __uv$config.prefix + __uv$config.encodeUrl(url)
|
||||
: __osana$config.prefix + __osana$config.codec.encode(url);
|
||||
const option = localStorage.getItem("nogg");
|
||||
if (option === "on") {
|
||||
if (window.stealthEngineLoaded !== false) {
|
||||
stealthEngine(redirectTo);
|
||||
} else {
|
||||
console.error(
|
||||
"Stealth Engine failed to load! Please contact support - discord.gg/unblocker"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
// If StealthMode is off, this is the enabled option.
|
||||
const _popout = window.open("/blob", "_self");
|
||||
const blob = _popout.document;
|
||||
// Write all of the neccesary page elements, and the Options including the cloak (if enabled)
|
||||
// The blob writing is just the background elements, like the "Nebula is loading your content, please wait" screen. It does not carry proxied content, or even the iframe.
|
||||
blob.write(`
|
||||
<script>
|
||||
function handleTabLeave(activeInfo) {
|
||||
var link = document.querySelector("link[rel~='icon']");
|
||||
if (localStorage.getItem('ADVcloak') == "on") {
|
||||
if (document.title == "Nebula") {
|
||||
document.title = "Google"
|
||||
if (!link) {
|
||||
link = document.createElement('link');
|
||||
link.rel = 'icon';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
}
|
||||
link.href = 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQo7AE3IF34XPGyseQjkXIOsWXpkZiLlMjSAwySjcJSPAwlv3hnGKi1&usqp=CAU';
|
||||
document.title = "Google"
|
||||
} else if (document.title == "Google") {
|
||||
document.title = "Nebula"
|
||||
if (!link) {
|
||||
link = document.createElement('link');
|
||||
link.rel = 'icon';
|
||||
document.getElementsByTagName('head')[0].appendChild(link);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
document.addEventListener("visibilitychange", handleTabLeave)
|
||||
</script>
|
||||
|
||||
<style>@import "https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap"; body{background:#191724;color:#fff}div{margin-top:30px;font-size:100px;text-align:center;font-family:"Roboto";font-weight:700}.loader .b1{left:42%}.loader .b2{left:50%;animation-delay:100ms}.loader .b3{left:58%;animation-delay:200ms;color:#eb6f92}.loader .b1,.loader .b2,.loader .b3{width:10px;height:30px;position:absolute;top:50%;transform:rotate(0);animation-name:spinify;animation-duration:1600ms;animation-iteration-count:infinite;color:#eb6f92;background-color:#eb6f92}@keyframes spinify{0%{transform:translate(0px,0px)}33%{transform:translate(0px,24px);border-radius:100%;width:10px;height:10px}66%{transform:translate(0px,-16px)}88%{transform:translate(0px,4px)}100%{transform:translate(0px,0px)}}</style>
|
||||
<div class="loader">
|
||||
<div>Nebula is loading your content!</div>
|
||||
<div style='font-size:35px;'>Please wait</div>
|
||||
<div class="b1"></div>
|
||||
<div class="b2"></div>
|
||||
<div class="b3"></div>
|
||||
</div>
|
||||
`);
|
||||
// inside of the blob, create and append the Iframe element which WILL carry the proxied content.
|
||||
const iframe = blob.createElement("iframe");
|
||||
const style = iframe.style;
|
||||
const img = blob.createElement("link");
|
||||
const link = location.href;
|
||||
// We attach ARC because it supports us, keeping our arc link there would be greatly appreciated :)
|
||||
const arcSrc = blob.createElement("script");
|
||||
arcSrc.setAttribute(
|
||||
"src",
|
||||
"https://arc.io/widget.min.js#BgaWcYfi"
|
||||
);
|
||||
// Arc requires the Async attribute
|
||||
// Async means not running parallel to other tasks, so it loads seperately to everything else (in a sense)
|
||||
// Aysnchronous and Synchronous are somewhat difficult topics, so we recommend you
|
||||
arcSrc.setAttribute("async", "");
|
||||
blob.head.appendChild(arcSrc);
|
||||
img.rel = "icon";
|
||||
img.href =
|
||||
"https://static.nebulacdn.xyz/content/images/nebula_logo_619x619.png";
|
||||
blob.title = getRandomName();
|
||||
// slice the link like some nice fruit :)
|
||||
// Removing the '/' from 'whateverthislinkis.gay/'
|
||||
// ^
|
||||
var currentLink = link.slice(0, link.length - 1);
|
||||
// To attribute the iframe to a source, we need to + the current link (post-slice) to the requested website, which is passed through the functions argument
|
||||
iframe.src = currentLink + redirectTo;
|
||||
|
||||
// Style the Iframe to fill the entire screen and remove the bessels.
|
||||
style.position = "fixed";
|
||||
style.top = style.bottom = style.left = style.right = 0;
|
||||
style.border = style.outline = "none";
|
||||
style.width = style.height = "100%";
|
||||
// finally, append the iframe to the blob's (window) body
|
||||
blob.body.appendChild(iframe);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Set the option
|
||||
var option = localStorage.getItem("nogg");
|
||||
if (localStorage.getItem("theme") == null) {
|
||||
localStorage.setItem("theme", "dark");
|
||||
}
|
||||
|
||||
// Extra logging for support
|
||||
function log() {
|
||||
setTimeout(
|
||||
console.log.bind(
|
||||
console,
|
||||
"%cWelcome To Nebula",
|
||||
"background: #3F51B5;color:#FFF;padding:5px;border-radius: 5px;line-height: 26px; font-size:30px;"
|
||||
)
|
||||
);
|
||||
setTimeout(
|
||||
console.log.bind(
|
||||
console,
|
||||
"%c If you are seeing this, Nebula's main script has succesfully loaded!",
|
||||
"background: green;color:#FFF;padding:5px;border-radius: 5px;line-height: 26px; font-size:12px;"
|
||||
)
|
||||
);
|
||||
setTimeout(
|
||||
console.log.bind(
|
||||
console,
|
||||
"%cIf you encounter an error, contact our support team on discord. Copy and paste the information below and send it in the ticket",
|
||||
"background: red;color:#FFF;padding:5px;border-radius: 5px;line-height: 26px; font-size:12px;"
|
||||
)
|
||||
);
|
||||
let online = navigator.onLine;
|
||||
let userAgent = navigator.userAgent;
|
||||
let browserName;
|
||||
let diagnosticDomain = window.location.href;
|
||||
if (userAgent.match(/chrome|chromium|crios/i)) {
|
||||
browserName = "chrome";
|
||||
} else if (userAgent.match(/firefox|fxios/i)) {
|
||||
browserName = "firefox";
|
||||
} else if (userAgent.match(/safari/i)) {
|
||||
browserName = "safari";
|
||||
} else if (userAgent.match(/opr\//i)) {
|
||||
browserName = "opera";
|
||||
} else if (userAgent.match(/edg/i)) {
|
||||
browserName = "edge";
|
||||
} else {
|
||||
browserName = "No browser detection";
|
||||
}
|
||||
console.log.bind(
|
||||
console,
|
||||
`%cInformation: \n URL: ${diagnosticDomain} \n BrowserName: ${browserName} \n IsOnline: ${online} \n UA: ${userAgent}, `,
|
||||
"background: gray;color:#FFF;padding:3px;border-radius: 0px; font-size:12px;"
|
||||
);
|
||||
}
|
||||
log();
|
||||
|
||||
// Adjectives and surnames for a more advanced stealth engine.
|
||||
// Used together to generate random names for the tab name
|
||||
let adjectives;
|
||||
let surnames;
|
||||
|
||||
async function surnameAdjectivesData() {
|
||||
await fetch("/resources/adjectives_surnames.json")
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
adjectives = data.adjectives;
|
||||
surnames = data.surnames;
|
||||
});
|
||||
}
|
||||
surnameAdjectivesData();
|
||||
|
||||
// Random number generator
|
||||
// Dependency of getRandomName function
|
||||
function getRandomNumber(min, max) {
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
}
|
||||
|
||||
// Random name generator
|
||||
function getRandomName() {
|
||||
const random1 = getRandomNumber(0, adjectives.length);
|
||||
const random2 = getRandomNumber(0, surnames.length);
|
||||
const adjective = adjectives[random1];
|
||||
const surname = surnames[random2];
|
||||
// Connect the adjective and surname together to create a random name
|
||||
const randomName = adjective + "-" + surname;
|
||||
return randomName;
|
||||
}
|
||||
|
||||
// Clickoff cloaking
|
||||
// This is used to cloak the tab when it is not active
|
||||
function handleTabLeave() {
|
||||
var link = document.querySelector("link[rel~='icon']");
|
||||
if (localStorage.getItem("ADVcloak") == "on") {
|
||||
if (document.title == "Nebula") {
|
||||
if (!link) {
|
||||
link = document.createElement("link");
|
||||
link.rel = "icon";
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
}
|
||||
link.href = "https://www.google.com/favicon.ico";
|
||||
document.title = "Google";
|
||||
} else if (document.title == "Google") {
|
||||
document.title = "Nebula";
|
||||
if (!link) {
|
||||
link = document.createElement("link");
|
||||
link.rel = "icon";
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
}
|
||||
link.href =
|
||||
"https://camo.githubusercontent.com/b565ae2e136e0ac6023e7099288a62382de7c2b8cdce86a8b90449b86649434c/68747470733a2f2f6e6562756c6170726f78792e6e6562756c612e62696f2f696d616765732f6c6f676f2e706e67";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create and Add the event listener
|
||||
document.addEventListener("visibilitychange", handleTabLeave);
|
||||
|
||||
const stealthStored = localStorage.getItem("nogg");
|
||||
function link(_link) {
|
||||
if (stealthStored == "on") {
|
||||
let inFrame;
|
||||
try {
|
||||
inFrame = window !== top;
|
||||
} catch (e) {
|
||||
inFrame = true;
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (!inFrame && !navigator.userAgent.includes("Firefox")) {
|
||||
const popup = open("about:blank", "_blank");
|
||||
if (!popup || popup.closed) {
|
||||
alert("Popups are disabled!");
|
||||
} else {
|
||||
const doc = popup.document;
|
||||
const iframe = doc.createElement("iframe");
|
||||
const style = iframe.style;
|
||||
const img = doc.createElement("link");
|
||||
img.rel = "icon";
|
||||
img.href =
|
||||
"https://ssl.gstatic.com/images/branding/product/1x/drive_2020q4_32dp.png";
|
||||
doc.title = getRandomName();
|
||||
var currentLink = _link.slice(0, _link.length - 1);
|
||||
iframe.src =
|
||||
location.origin +
|
||||
"/service/go/" +
|
||||
__uv$config.encodeUrl(currentLink);
|
||||
style.position = "fixed";
|
||||
style.top = style.bottom = style.left = style.right = 0;
|
||||
style.border = style.outline = "none";
|
||||
style.width = style.height = "100%";
|
||||
doc.body.appendChild(iframe);
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
} else {
|
||||
location.href =
|
||||
"service/go/" + __uv$config.encodeUrl("https://radon.games/");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
// OPTIONS
|
||||
|
||||
const storedSetTheme = localStorage.getItem("theme");
|
||||
|
||||
function switchProxy() {
|
||||
var selecter = document.getElementById("proxySwitcher");
|
||||
var selectedOption = selecter.value;
|
||||
|
||||
localStorage.setItem("proxy", selectedOption);
|
||||
var storedChoice = localStorage.getItem("proxy");
|
||||
console.log(selectedOption);
|
||||
}
|
||||
|
||||
function resetViews() {
|
||||
changeCSS("--background-primary", "#191724", true);
|
||||
changeCSS("--navbar-color", "#26233a", true);
|
||||
changeCSS("--navbar-height", "60px", true);
|
||||
changeCSS("--navbar-text-color", "rgb(121 103 221)", true);
|
||||
changeCSS("--navbar-link-color", "#e0def4", true);
|
||||
changeCSS("--navbar-font", '"Roboto"', true);
|
||||
changeCSS("--input-text-color", "#e0def4", true);
|
||||
changeCSS("--input-placeholder-color", "#6e6a86", true);
|
||||
changeCSS("--input-background-color", "#1f1d2e", true);
|
||||
changeCSS("--input-placeholder-color", "white", true);
|
||||
changeCSS("--input-border-color", "#eb6f92", true);
|
||||
changeCSS("--input-border-size", "1.3px", true);
|
||||
return "All views reset";
|
||||
}
|
||||
function saveIc() {
|
||||
console.log("Checked");
|
||||
var notification = `
|
||||
<div class="notification-container" id="notification-container">
|
||||
<div class="notification notification-success">
|
||||
<strong>Success!</strong> Your settings have been saved!
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById("notifhere").innerHTML = notification;
|
||||
setTimeout(() => {
|
||||
var NotificationOBJ = document.getElementById("notifhere");
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function unsavedChanges() {
|
||||
var notification = `
|
||||
<div class="notification-container" id="notification-container">
|
||||
<div class="notification notification-danger" id="notification-container">
|
||||
<strong>Danger!</strong> You have unsaved changes!
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById("notifhere").innerHTML = notification;
|
||||
setTimeout(() => {
|
||||
var NotificationOBJ = document.getElementById("notifhere");
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
var option = localStorage.getItem("nogg");
|
||||
|
||||
function toggleNoGG() {
|
||||
if (option === "on") {
|
||||
option = "off";
|
||||
localStorage.setItem("nogg", "off");
|
||||
} else {
|
||||
option = "on";
|
||||
localStorage.setItem("nogg", "on");
|
||||
}
|
||||
}
|
||||
var option2 = localStorage.getItem("ADVcloak");
|
||||
function toggleClickoff() {
|
||||
if (option2 === "on") {
|
||||
option2 = "off";
|
||||
localStorage.setItem("ADVcloak", "off");
|
||||
} else {
|
||||
option2 = "on";
|
||||
localStorage.setItem("ADVcloak", "on");
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function () {
|
||||
if (localStorage.getItem("ABfaviconURL") === null) {
|
||||
} else if (localStorage.getItem("ABfaviconURL") == "") {
|
||||
} else {
|
||||
document.querySelector("#faviconInput").value =
|
||||
localStorage.getItem("ABfaviconURL");
|
||||
}
|
||||
if (localStorage.getItem("ABtitle") === null) {
|
||||
} else if (localStorage.getItem("ABtitle") == "") {
|
||||
} else {
|
||||
document.querySelector("#titleInput").value =
|
||||
localStorage.getItem("ABtitle");
|
||||
}
|
||||
};
|
||||
|
||||
function saveAbInfo() {
|
||||
var faviconURL = document.getElementById("faviconInput").value;
|
||||
var title = document.getElementById("titleInput").value;
|
||||
localStorage.setItem("ABfaviconURL", faviconURL);
|
||||
localStorage.setItem("ABtitle", title);
|
||||
var notification = `
|
||||
<div class="notification-container" id="notification-container">
|
||||
<div class="notification notification-success">
|
||||
<strong>Success!</strong> Your settings have been saved!
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById("notifhere").innerHTML = notification;
|
||||
setTimeout(() => {
|
||||
var NotificationOBJ = document.getElementById("notifhere");
|
||||
}, 2000);
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
window.onload = function () {
|
||||
changeCSS(
|
||||
"--background-primary",
|
||||
localStorage.getItem("--background-primary")
|
||||
);
|
||||
changeCSS("--navbar-color", localStorage.getItem("--navbar-color"));
|
||||
changeCSS("--navbar-height", localStorage.getItem("--navbar-height"));
|
||||
changeCSS("--navbar-text-color", localStorage.getItem("--navbar-text-color"));
|
||||
changeCSS("--input-text-color", localStorage.getItem("--input-text-color"));
|
||||
changeCSS(
|
||||
"--input-placeholder-color",
|
||||
localStorage.getItem("--input-placeholder-color")
|
||||
);
|
||||
changeCSS(
|
||||
"--input-background-color",
|
||||
localStorage.getItem("--input-background-color")
|
||||
);
|
||||
changeCSS(
|
||||
"--input-border-color",
|
||||
localStorage.getItem("--input-border-color")
|
||||
);
|
||||
changeCSS("--input-border-size", localStorage.getItem("--input-border-size"));
|
||||
changeCSS("--navbar-link-color", localStorage.getItem("--navbar-link-color"));
|
||||
changeCSS("--navbar-font", localStorage.getItem("--navbar-font"));
|
||||
changeCSS(
|
||||
"--navbar-logo-filter",
|
||||
localStorage.getItem("--navbar-logo-filter")
|
||||
);
|
||||
changeCSS(
|
||||
"--text-color-primary",
|
||||
localStorage.getItem("--text-color-primary")
|
||||
);
|
||||
};
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
// Stealth engine, a dependency for everything above.
|
||||
|
||||
// ensures that the js file is loaded
|
||||
window.stealthEngineLoaded = true;
|
||||
function stealthEngine(encodedURL) {
|
||||
// Remember that the EncodedURL argument must be pre-encoded, or encoded before the function is called.
|
||||
// This function does not encode the argument at all!
|
||||
|
||||
// Initialize the variable
|
||||
let inFrame;
|
||||
// make sure there isn't a window open already
|
||||
try {
|
||||
inFrame = window !== top;
|
||||
} catch (e) {
|
||||
inFrame = true;
|
||||
}
|
||||
setTimeout(() => {
|
||||
// Basically, a checklist to make sure that an error won't occur.
|
||||
// In this if statement, we're checking if an iframe is already being opened, if popups are disabled, and if the user agent IS NOT firefox (firefox sucks, sorry Moz)
|
||||
if (!inFrame && !navigator.userAgent.includes("Firefox")) {
|
||||
const popup = open("about:blank", "_blank");
|
||||
if (!popup || popup.closed) {
|
||||
alert(
|
||||
"StealthEngine was unable to open a popup. (do you have popups disabled?)"
|
||||
);
|
||||
} else {
|
||||
const doc = popup.document;
|
||||
const iframe = doc.createElement("iframe");
|
||||
const style = iframe.style;
|
||||
popup.onload = () => {
|
||||
document.getElementById("lpoader").style.display = "none";
|
||||
document.getElementById("connectorText").textContent =
|
||||
"connecting to service";
|
||||
setTimeout(() => {
|
||||
document.getElementById("connectorText").textContent =
|
||||
"connecting to service";
|
||||
}, 17500);
|
||||
};
|
||||
var isClosed = setInterval(function () {
|
||||
if (popup.closed) {
|
||||
clearInterval(isClosed);
|
||||
document.getElementById("lpoader").style.display = "none";
|
||||
document.getElementById("connectorText").textContent =
|
||||
"connecting to service";
|
||||
}
|
||||
}, 1000);
|
||||
// Favicon attachment
|
||||
const img = doc.createElement("link");
|
||||
const arcSrc = doc.createElement("script");
|
||||
// We attach ARC because it supports us, keeping our arc link there would be greatly appreciated :)
|
||||
arcSrc.setAttribute("src", "https://arc.io/widget.min.js#BgaWcYfi");
|
||||
arcSrc.setAttribute("async", "");
|
||||
doc.head.appendChild(arcSrc);
|
||||
const link = location.href;
|
||||
img.rel = "icon";
|
||||
img.href =
|
||||
ABFavicon ||
|
||||
"https://ssl.gstatic.com/images/branding/product/1x/drive_2020q4_32dp.png";
|
||||
if (localStorage.nogg == "on") {
|
||||
doc.title = ABTitle || getRandomName();
|
||||
} else {
|
||||
doc.title = ABTitle || "Nebula";
|
||||
}
|
||||
|
||||
var currentLink = link.slice(0, link.length - 1);
|
||||
|
||||
iframe.src = currentLink + encodedURL;
|
||||
|
||||
style.position = "fixed";
|
||||
style.top = style.bottom = style.left = style.right = 0;
|
||||
style.border = style.outline = "none";
|
||||
style.width = style.height = "100%";
|
||||
|
||||
doc.body.appendChild(iframe);
|
||||
doc.head.appendChild(img);
|
||||
}
|
||||
}
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
let tryAbFavi = localStorage.getItem("ABfaviconURL");
|
||||
let ABFavicon = "";
|
||||
if (tryAbFavi === null) {
|
||||
console.warn("ABfaviconURL is null, Defaulting");
|
||||
ABFavicon = "";
|
||||
} else if (tryAbFavi == "") {
|
||||
console.warn("ABfaviconURL is empty, Defaulting");
|
||||
ABFavicon = "";
|
||||
} else {
|
||||
ABFavicon = tryAbFavi;
|
||||
}
|
||||
|
||||
let tryAbTitle = localStorage.getItem("ABtitle");
|
||||
let ABTitle = "";
|
||||
if (tryAbTitle === null) {
|
||||
console.warn("ABtitle is null, Defaulting");
|
||||
ABTitle = "";
|
||||
} else if (tryAbTitle == "") {
|
||||
console.warn("ABtitle is empty, Defaulting");
|
||||
ABTitle = "";
|
||||
} else {
|
||||
ABTitle = tryAbTitle;
|
||||
}
|
||||
|
|
@ -1,189 +0,0 @@
|
|||
const THEME_OPTIONS = {
|
||||
dark: {
|
||||
"--background-primary": "#191724",
|
||||
"--navbar-color": "#26233a",
|
||||
"--navbar-height": "60px",
|
||||
"--navbar-text-color": "#7967dd",
|
||||
"--input-text-color": "#e0def4",
|
||||
"--input-placeholder-color": "#6e6a86",
|
||||
"--input-background-color": "#1f1d2e",
|
||||
"--input-placeholder-color": "white",
|
||||
"--input-border-color": "#eb6f92",
|
||||
"--input-border-size": "1.3px",
|
||||
"--navbar-link-color": "#e0def4",
|
||||
"--navbar-font": '"Roboto"',
|
||||
"--navbar-logo-filter": "invert(0%)",
|
||||
"--text-color-primary": "#e0def4"
|
||||
},
|
||||
light: {
|
||||
"--background-primary": "#d8d8d8",
|
||||
"--navbar-color": "#a2a2a2",
|
||||
"--navbar-height": "4em",
|
||||
"--navbar-text-color": "#000000",
|
||||
"--input-text-color": "#e0def4",
|
||||
"--input-placeholder-color": "white",
|
||||
"--input-background-color": "black",
|
||||
"--input-border-color": "#eb6f92",
|
||||
"--input-border-size": "1.3px",
|
||||
"--navbar-link-color": "#000000",
|
||||
"--navbar-font": '"Roboto"',
|
||||
"--navbar-logo-filter": "invert(30%)",
|
||||
"--text-color-primary": "#303030"
|
||||
},
|
||||
suit: {
|
||||
"--background-primary": "#0c0c0c",
|
||||
"--navbar-color": "#ff2e4e",
|
||||
"--navbar-height": "4em",
|
||||
"--navbar-text-color": "#000000",
|
||||
"--input-text-color": "#e0def4",
|
||||
"--input-placeholder-color": "white",
|
||||
"--input-background-color": "#00000000",
|
||||
"--input-border-color": "#ff346e",
|
||||
"--input-border-size": "1.3px",
|
||||
"--navbar-link-color": "#000000",
|
||||
"--navbar-font": '"Roboto"',
|
||||
"--navbar-logo-filter": "brightness(30)",
|
||||
"--text-color-primary": "#00000"
|
||||
},
|
||||
metallic: {
|
||||
"--background-primary": "#171717",
|
||||
"--navbar-color": "#004953",
|
||||
"--navbar-height": "4em",
|
||||
"--navbar-text-color": "#ffffff",
|
||||
"--input-text-color": "#e0def4",
|
||||
"--input-placeholder-color": "white",
|
||||
"--input-background-color": "#004953",
|
||||
"--input-border-color": "#000000",
|
||||
"--input-border-size": "1.3px",
|
||||
"--navbar-link-color": "#e0def4",
|
||||
"--navbar-font": '"Roboto"',
|
||||
"--navbar-logo-filter": "invert(50%)",
|
||||
"--text-color-primary": "#e0def4"
|
||||
},
|
||||
dante: {
|
||||
"--background-primary": "#131313",
|
||||
"--navbar-color": "#e4ff8b",
|
||||
"--navbar-height": "3.5em",
|
||||
"--navbar-text-color": "#000000",
|
||||
"--input-text-color": "#000000",
|
||||
"--input-placeholder-color": "000000",
|
||||
"--input-background-color": "#e4ff8b",
|
||||
"--input-border-color": "#00000000",
|
||||
"--input-border-size": "1.3px",
|
||||
"--navbar-link-color": "#000000",
|
||||
"--navbar-font": '"Roboto"',
|
||||
"--navbar-logo-filter": "brightness(0%)",
|
||||
"--text-color-primary": "#000"
|
||||
}
|
||||
};
|
||||
|
||||
function changeCSS(property, value, isRoot = false) {
|
||||
const root = document.documentElement;
|
||||
isRoot
|
||||
? root.style.setProperty(property, value)
|
||||
: root.style.setProperty(property, value, "important");
|
||||
}
|
||||
|
||||
function saveCSS(variable, value) {
|
||||
localStorage.setItem(variable, value);
|
||||
}
|
||||
function applyTheme(theme) {
|
||||
Object.entries(theme).forEach(([property, value]) => {
|
||||
changeCSS(property, value);
|
||||
localStorage.setItem(property, value);
|
||||
});
|
||||
}
|
||||
|
||||
function switchTheme() {
|
||||
const selecter = document.getElementById("themeSwitcher");
|
||||
const selectedOption = selecter.value;
|
||||
console.log(selectedOption);
|
||||
const theme = THEME_OPTIONS[selectedOption];
|
||||
|
||||
if (!theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyTheme(theme);
|
||||
localStorage.setItem("theme", selectedOption);
|
||||
|
||||
if (selectedOption == "custom") {
|
||||
let startCustom = prompt(
|
||||
"Would you like to have an interactive setup? Y/N",
|
||||
""
|
||||
);
|
||||
if (startCustom == "Y" || startCustom == "y") {
|
||||
alert(
|
||||
"Welcome to the interactive setup. Please enter the following values. If you don't know what to enter, just press enter. They will default to Dark Mode."
|
||||
);
|
||||
let background = prompt(
|
||||
"Background color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #191724",
|
||||
""
|
||||
);
|
||||
let navbar = prompt(
|
||||
"Navbar color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #26233a",
|
||||
""
|
||||
);
|
||||
let navbarHeight = prompt(
|
||||
"Navbar height || Possible Types: ABSOLUTE: cm, mm, Q, in, pc, pt, px RELATIVE: em, ex, ch, rem, lh, rlh, vw, vh, vb, vi || Default Value: 60px",
|
||||
""
|
||||
);
|
||||
let navbarText = prompt(
|
||||
"Navbar text color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #7967dd",
|
||||
""
|
||||
);
|
||||
let inputText = prompt(
|
||||
"Input text color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #e0def4",
|
||||
""
|
||||
);
|
||||
let inputPlaceholder = prompt(
|
||||
"Input placeholder color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #6e6a86",
|
||||
""
|
||||
);
|
||||
let inputBackground = prompt(
|
||||
"Input background color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #1f1d2e",
|
||||
""
|
||||
);
|
||||
let inputBorder = prompt(
|
||||
"Input border color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #eb6f92",
|
||||
""
|
||||
);
|
||||
let inputBorderSize = prompt(
|
||||
"Input border size || Possible Types: ABSOLUTE: cm, mm, Q, in, pc, pt, px RELATIVE: em, ex, ch, rem, lh, rlh, vw, vh, vb, vi || Default Value: 1.3px",
|
||||
""
|
||||
);
|
||||
let navbarFont = prompt(
|
||||
'Navbar font || Enter a default font name, custom ones will likely not work. || Default Value: "Roboto"',
|
||||
""
|
||||
);
|
||||
let navbarLink = prompt(
|
||||
"Navbar link color || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #e0def4",
|
||||
""
|
||||
);
|
||||
let navbarLogoFilter = prompt(
|
||||
"Navbar logo filter || Adjust the NavBar-Logo-Filter. || Default Value: invert(0%)",
|
||||
""
|
||||
);
|
||||
let textColorPrimary = prompt(
|
||||
"Text color primary || Possible Types: Keywords, RGB, RBBA, HSL, HSLA, Hexadecimal. || Default Value: #e0def4",
|
||||
""
|
||||
);
|
||||
localStorage.setItem("theme", "custom");
|
||||
changeCSS("--background-primary", background, true);
|
||||
changeCSS("--navbar-color", navbar, true);
|
||||
changeCSS("--navbar-height", navbarHeight, true);
|
||||
changeCSS("--navbar-text-color", navbarText, true);
|
||||
changeCSS("--input-text-color", inputText, true);
|
||||
changeCSS("--input-placeholder-color", inputPlaceholder, true);
|
||||
changeCSS("--input-background-color", inputBackground, true);
|
||||
changeCSS("--input-border-color", inputBorder, true);
|
||||
changeCSS("--input-border-size", inputBorderSize, true);
|
||||
changeCSS("--navbar-link-color", navbarLink, true);
|
||||
changeCSS("--navbar-font", navbarFont, true);
|
||||
changeCSS("--navbar-logo-filter", navbarLogoFilter, true);
|
||||
changeCSS("--text-color-primary", textColorPrimary, true);
|
||||
} else {
|
||||
alert("Non-Interactive Setup Not supported yet.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
const generateOTP = document.getElementById("generate-otp");
|
||||
|
||||
generateOTP.onclick = () => {
|
||||
fetch("/generate-otp", { method: "PATCH" });
|
||||
};
|
||||
|
||||
const validateOTP = document.getElementById("validate-otp");
|
||||
|
||||
validateOTP.onclick = () => {
|
||||
fetch("/validate-otp", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
otp: document.getElementById("otp").value
|
||||
})
|
||||
})
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
setCookie("validation", data.validation, 30);
|
||||
location.href = "/";
|
||||
} else {
|
||||
alert("Invalid OTP.");
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
alert("An error occurred while validating your OTP.");
|
||||
});
|
||||
};
|
||||
|
||||
function setCookie(name, value, days) {
|
||||
var expires = "";
|
||||
if (days) {
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
|
||||
expires = "; expires=" + date.toUTCString();
|
||||
}
|
||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
||||
}
|
||||
|
|
@ -1,500 +0,0 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=Dongle&family=Roboto:wght@100&display=swap");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@1,100&display=swap");
|
||||
|
||||
:root {
|
||||
--background-primary: #191724;
|
||||
--navbar-color: #26233a;
|
||||
--navbar-height: 60px;
|
||||
--navbar-text-color: #7967dd;
|
||||
--navbar-link-color: #e0def4;
|
||||
--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;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
text-align: center;
|
||||
font-family: "Roboto";
|
||||
}
|
||||
|
||||
:-moz-placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#navbar {
|
||||
height: var(--navbar-height);
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--navbar-color);
|
||||
}
|
||||
|
||||
.sidenav-btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidenav {
|
||||
height: 100%;
|
||||
width: 0;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #232133;
|
||||
padding-top: 60px;
|
||||
-webkit-transition: 0.5s;
|
||||
transition: 0.5s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidenav a {
|
||||
padding: 8px 16px 16px 32px;
|
||||
text-decoration: none;
|
||||
width: 100vw;
|
||||
font-size: 24px;
|
||||
color: #cfcfcf;
|
||||
display: block;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.sidenav .closebtn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 25px;
|
||||
font-size: 36px;
|
||||
margin-left: 50px;
|
||||
z-index: 2;
|
||||
left: 140px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 690px) {
|
||||
/* funny number */
|
||||
|
||||
#navbar > ul > li {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidenav-btn {
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 90vw;
|
||||
width: var(--navbar-height);
|
||||
height: var(--navbar-height);
|
||||
}
|
||||
|
||||
.sidenav-btn > svg {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
#digitalCLOContainerLI {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#navbar > ul > li > a > svg {
|
||||
min-width: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--navbar-link-color);
|
||||
text-decoration: none !important;
|
||||
font-family: "Roboto";
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: grey;
|
||||
transition: 0.5s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.down {
|
||||
background-color: rgb(90, 24, 154);
|
||||
left: inherit !important;
|
||||
font-family: "Helvetica";
|
||||
background-color: var(--navbar-color);
|
||||
/* box-shadow: 2px 2px rgb(0 0 0 / 20%); */
|
||||
color: white;
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
list-style-type: none;
|
||||
position: fixed;
|
||||
border-bottom-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#navbar ul:not(.down) {
|
||||
font-family: "Helvetica";
|
||||
/* box-shadow: 2px 2px rgb(0 0 0 / 20%); */
|
||||
color: white;
|
||||
margin-left: auto;
|
||||
list-style-type: none;
|
||||
float: right !important;
|
||||
margin-right: 2.3%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#navbar ul li {
|
||||
float: left;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
padding-right: 1em;
|
||||
padding-left: 1em;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
::placeholder,
|
||||
input[type="text"] {
|
||||
color: var(--input-placeholder-color);
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
font-family: "Roboto";
|
||||
}
|
||||
|
||||
#navbar ul p,
|
||||
ul a {
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
*/
|
||||
/*
|
||||
#navbar ul li p, ul li a {
|
||||
font-weight: normal;
|
||||
}
|
||||
*/
|
||||
|
||||
#navbar ul li ul {
|
||||
border-bottom-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
margin-top: 1rem;
|
||||
left: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#navbar ul li:hover ul,
|
||||
ul li ul:hover {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#navbar ul li p {
|
||||
color: white;
|
||||
font-family: "Calibri";
|
||||
z-index: 3 !important;
|
||||
}
|
||||
|
||||
#navbar ul li ul li {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 98%;
|
||||
color: white;
|
||||
flex-direction: column;
|
||||
font-family: "Roboto";
|
||||
}
|
||||
|
||||
#content h1 {
|
||||
padding-bottom: 0.5em;
|
||||
font-weight: 100;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#content img {
|
||||
padding-left: 0.5em;
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
#content input {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
font-family: "Calibri";
|
||||
border-style: solid !important;
|
||||
border: var(--input-border-size) solid var(--input-border-color);
|
||||
border-width: 1px;
|
||||
border-radius: 15px;
|
||||
background-color: var(--input-background-color);
|
||||
color: var(--input-text-color);
|
||||
width: 300px;
|
||||
height: 50px;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
font-family: "Roboto";
|
||||
}
|
||||
|
||||
#content input:focus {
|
||||
outline: none;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
@keyframes inputwide {
|
||||
0% {
|
||||
width: 0px;
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
|
||||
100% {
|
||||
width: 300px;
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
width: 283px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
transition: 0.1s;
|
||||
}
|
||||
|
||||
svg path,
|
||||
svg rect {
|
||||
fill: #eb6f92;
|
||||
}
|
||||
|
||||
.connector {
|
||||
color: white;
|
||||
margin-left: 10px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@keyframes popout {
|
||||
0% {
|
||||
height: -20px;
|
||||
}
|
||||
|
||||
100% {
|
||||
height: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.nebHeader {
|
||||
font-family: var(--navbar-font);
|
||||
color: var(--navbar-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
flex-wrap: nowrap;
|
||||
flex-direction: row;
|
||||
margin-left: 0.5%;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background-primary);
|
||||
color: var(--text-color-primary);
|
||||
animation: fadeInAnimation ease 1s;
|
||||
animation-iteration-count: 1;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeInAnimation {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
input:focus::placeholder {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
#navbar #thumbImg {
|
||||
transition: width 2s, height 2s, transform 2s;
|
||||
filter: var(--navbar-logo-filter);
|
||||
}
|
||||
|
||||
#navbar #thumbImg:hover {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
|
||||
.stamp {
|
||||
text-align: right;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
font-family: "Montserrat", sans-serif;
|
||||
font-style: italic;
|
||||
font-weight: lighter;
|
||||
color: whitesmoke;
|
||||
opacity: 90%;
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
padding-left: 5px;
|
||||
padding-bottom: 1px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.stamp:hover {
|
||||
text-align: right;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
font-family: "Montserrat", sans-serif;
|
||||
font-style: italic;
|
||||
font-weight: lighter;
|
||||
color: whitesmoke;
|
||||
opacity: 38%;
|
||||
user-select: none;
|
||||
font-size: 13px;
|
||||
padding-left: 5px;
|
||||
padding-bottom: 1px;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.github {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.tos {
|
||||
position: fixed;
|
||||
right: 67px;
|
||||
bottom: 0;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.privacy {
|
||||
position: fixed;
|
||||
right: 114px;
|
||||
bottom: 0;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.modal-window {
|
||||
position: fixed;
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: all 0.3s;
|
||||
font-family: "Montserrat", sans-serif;
|
||||
}
|
||||
|
||||
.modal-window:target {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.modal-window > div {
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 2em;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.modal-window header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-window h1 {
|
||||
font-size: 150%;
|
||||
margin: 0 0 15px;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
color: #aaa;
|
||||
line-height: 50px;
|
||||
font-size: 80%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
width: 70px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.modal-window > div {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.modal-window div:not(:last-of-type) {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 150px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
small {
|
||||
color: lightgray;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: white;
|
||||
padding: 1em 1.5em;
|
||||
border-radius: 0.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn i {
|
||||
padding-right: 0.3em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1190px) {
|
||||
#digitalClock {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.clockColon {
|
||||
opacity: 100%;
|
||||
|
||||
animation: blink 0.5s step-end infinite alternate;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% {
|
||||
opacity: 0%;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,556 +0,0 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=Work+Sans:wght@300&display=swap");
|
||||
|
||||
:root {
|
||||
--background-primary: #191724;
|
||||
--sidebar-color: #191724;
|
||||
--sidebar-text-color: #e0def4;
|
||||
--text-color-primary: #e0def4;
|
||||
--text-color-secondary: #6e6a86;
|
||||
--focus-color: #eb6f92;
|
||||
--header-height: 10vh;
|
||||
--section-font-size: 20pt;
|
||||
--section-font: 'Calibri';
|
||||
--section-padding: 0.5em;
|
||||
--setting-distance-from-sidebar: 1em;
|
||||
--setting-distance-from-right: 1em;
|
||||
--setting-name-font: 'Calibri';
|
||||
--setting-desc-font: 'Calibri';
|
||||
}
|
||||
|
||||
* {
|
||||
user-select: none;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
body {
|
||||
background-color: var(--background-primary);
|
||||
}*/
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-content: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-evenly;
|
||||
align-items: flex-end;
|
||||
margin-top: 75px;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
animation: fadeIn 700ms ease-in 30ms forwards;
|
||||
-webkit-animation: fadeIn 700ms ease-in 300ms forwards;
|
||||
position: absolute;
|
||||
top: var(--header-height);
|
||||
left: 0;
|
||||
background-color: var(--sidebar-color);
|
||||
transition: width 0.5s;
|
||||
width: var(--sidebar-width);
|
||||
}
|
||||
|
||||
.setting li a {
|
||||
background-color: #2e2828;
|
||||
padding: 10px 16px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.settings-div li a ul {
|
||||
border-bottom-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
margin-top: 10px;
|
||||
left: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.settings-div li a:hover ul,
|
||||
ul li:hover {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.settings-div ul li p{
|
||||
color: white;
|
||||
font-family: 'Calibri';
|
||||
z-index: 3 !important;
|
||||
|
||||
}*/
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.settings-div li a ul li {
|
||||
clear: both;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.section {
|
||||
background-color: transparent;
|
||||
color: var(--sidebar-text-color);
|
||||
font-size: var(--section-font-size);
|
||||
font-family: var(--section-font);
|
||||
width: 100%;
|
||||
transition: background-color 0.5s;
|
||||
padding-top: var(--section-padding);
|
||||
padding-bottom: var(--section-padding);
|
||||
}
|
||||
|
||||
.section:hover {
|
||||
background-color: #ffffff20;
|
||||
}
|
||||
|
||||
.settings-div {
|
||||
position: absolute;
|
||||
left: calc(var(--sidebar-width) + var(--setting-distance-from-sidebar));
|
||||
top: 0;
|
||||
width: calc(100vw - var(--sidebar-width) - var(--setting-distance-from-sidebar));
|
||||
padding-top: 5%;
|
||||
}
|
||||
|
||||
.setting-input {
|
||||
/* left: 100%; */
|
||||
color: black !important;
|
||||
position: relative;
|
||||
/* right: 0; */
|
||||
transform: translateY(-1.5em);
|
||||
float: right;
|
||||
margin-right: 5%;
|
||||
}
|
||||
|
||||
.setting-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Mulish:wght@300&display=swap');
|
||||
|
||||
.toogle-button {
|
||||
font-weight: bold;
|
||||
font-size: 10PX;
|
||||
display: inline-block;
|
||||
width: 75px;
|
||||
height: 35px;
|
||||
background-color: #dfddf3;
|
||||
border-radius: 30px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toogle-button::after {
|
||||
content: 'Off';
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
color: #E7E2CD;
|
||||
background-color: #e14343;
|
||||
border: 2px solid #E7E2CD;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 5px rgb(0 0 0 / 25%);
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: 0;
|
||||
line-height: 0;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
transition: all .5s;
|
||||
transform: 1s ease-in;
|
||||
font-family: 'Mulish', sans-serif;
|
||||
}
|
||||
|
||||
.setting-input:checked+.toogle-button::after {
|
||||
content: 'On';
|
||||
background-color: #53b357;
|
||||
transform: translateX(35px) rotate(360deg);
|
||||
}
|
||||
|
||||
ul li {
|
||||
float: left;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
padding-right: 1em;
|
||||
padding-left: 1em;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
transform: translateY(-30px);
|
||||
font-family: var(--setting-desc-font);
|
||||
color: white;
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0.01;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0.01;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css?family=Roboto:400,500&display=swap');
|
||||
|
||||
.notification-container {
|
||||
position: fixed;
|
||||
top: 4px;
|
||||
right: 5px;
|
||||
width: 500px;
|
||||
max-width: calc(100% - 30px);
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.notification {
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
padding: 15px 20px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 15px;
|
||||
animation: grow 0.5s ease-in forwards;
|
||||
}
|
||||
|
||||
@keyframes grow {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.notification.hide {
|
||||
animation: shrink 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes shrink {
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
|
||||
.notification strong {
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.notification-info {
|
||||
background-color: #00cae3;
|
||||
}
|
||||
|
||||
.notification-success {
|
||||
background-color: #3d3571;
|
||||
}
|
||||
|
||||
.notification-warning {
|
||||
background-color: #ff9e0f;
|
||||
}
|
||||
|
||||
.notification-danger {
|
||||
background-color: #f55145;
|
||||
}
|
||||
|
||||
.stamp {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.bk-btn {
|
||||
height: 52px;
|
||||
width: 52px;
|
||||
background-color: black;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.bk-btn .bk-btn-triangle {
|
||||
position: relative;
|
||||
top: 13px;
|
||||
left: 10.4px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 13px solid transparent;
|
||||
border-bottom: 13px solid transparent;
|
||||
border-right: 13px solid white;
|
||||
}
|
||||
|
||||
.bk-btn .bk-btn-bar {
|
||||
position: relative;
|
||||
background-color: white;
|
||||
height: 7.8px;
|
||||
width: 13px;
|
||||
top: -3.64px;
|
||||
left: 22.88px;
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@300&display=swap');
|
||||
|
||||
.settings-cont {
|
||||
background: #45454521;
|
||||
box-sizing: border-box;
|
||||
width: 300px;
|
||||
height: 246px;
|
||||
padding: 30px;
|
||||
border: 2px solid rgb(0 0 0 / 64%);
|
||||
border-radius: 9px;
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: var(--text-color-primary);
|
||||
font-size: 27px;
|
||||
margin: 0;
|
||||
font-family: 'Ubuntu', sans-serif;
|
||||
font-weight: 700;
|
||||
font-style: bold;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--text-color-secondary);
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
font-family: 'Ubuntu', sans-serif;
|
||||
font-weight: 300;
|
||||
/* font-style: Italic; */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.secondary-desc {
|
||||
color: var(--text-color-primary);
|
||||
margin: 0;
|
||||
margin-top: 0.3em;
|
||||
font-size: 18px;
|
||||
font-family: 'Ubuntu', sans-serif;
|
||||
font-weight: 300;
|
||||
/* font-style: Italic; */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@700&display=swap');
|
||||
|
||||
.new-tag {
|
||||
font-size: 16px;
|
||||
color: rgb(226, 68, 68);
|
||||
font-family: 'Oxygen', sans-serif;
|
||||
}
|
||||
|
||||
.square {
|
||||
width: .7em;
|
||||
height: .7em;
|
||||
margin: .5em;
|
||||
}
|
||||
|
||||
|
||||
/* Custom dropdown */
|
||||
|
||||
.custom-dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 10px;
|
||||
/* demo only */
|
||||
}
|
||||
|
||||
.custom-dropdown select {
|
||||
background-color: rgb(121 103 221);
|
||||
color: var(--text-color-primary);
|
||||
font-size: inherit;
|
||||
padding: .5em;
|
||||
padding-right: 2.5em;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
border-radius: 3px;
|
||||
text-indent: 0.01px;
|
||||
text-overflow: '';
|
||||
-webkit-appearance: button;
|
||||
/* hide default arrow in chrome OSX */
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.custom-dropdown::before,
|
||||
.custom-dropdown::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.custom-dropdown::after {
|
||||
/* Custom dropdown arrow */
|
||||
content: "\25BC";
|
||||
height: 1em;
|
||||
font-size: .625em;
|
||||
line-height: 1;
|
||||
right: 1.2em;
|
||||
top: 50%;
|
||||
margin-top: -.5em;
|
||||
}
|
||||
|
||||
.custom-dropdown::before {
|
||||
/* Custom dropdown arrow cover */
|
||||
width: 2em;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
.custom-dropdown select[disabled] {
|
||||
color: rgba(0, 0, 0, .3);
|
||||
}
|
||||
|
||||
.custom-dropdown select[disabled]::after {
|
||||
color: rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
.custom-dropdown::before {
|
||||
background-color: rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.custom-dropdown::after {
|
||||
color: rgba(0, 0, 0, .4);
|
||||
}
|
||||
|
||||
.button-save {
|
||||
background-color: #5e18eb;
|
||||
color: #fff;
|
||||
font-size: inherit;
|
||||
padding: 0.5em;
|
||||
padding-right: -0.5em;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
border-radius: 3px;
|
||||
text-indent: 0.01px;
|
||||
text-overflow: '';
|
||||
-webkit-appearance: button;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button-save-alt {
|
||||
background-color: #5e18eb;
|
||||
color: #fff;
|
||||
font-size: inherit;
|
||||
padding: 0.3em;
|
||||
padding-right: -0.5em;
|
||||
border: 0;
|
||||
margin: 0.2vh;
|
||||
border-radius: 3px;
|
||||
text-indent: 0.01px;
|
||||
text-overflow: '';
|
||||
-webkit-appearance: button;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap');
|
||||
|
||||
.expiramental {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 10px;
|
||||
color: rgb(184, 0, 0);
|
||||
font-weight: 400;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
height: 0;
|
||||
width: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
/* text-indent: -10049px; */
|
||||
width: 85px;
|
||||
height: 37px;
|
||||
background: #504e58;
|
||||
display: block;
|
||||
border-radius: 100px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
label:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: 35px;
|
||||
height: 28px;
|
||||
background: #3d3571;
|
||||
border-radius: 90px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
input:checked+label {
|
||||
background: #7967dd;
|
||||
|
||||
}
|
||||
|
||||
input:checked+label:after {
|
||||
left: calc(100% - 5px);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
label:active:after {
|
||||
width: 38px;
|
||||
}
|
||||
|
||||
.bareLocationInput {
|
||||
border-style: solid !important;
|
||||
border: var(--input-border-size) solid #5e18eb;
|
||||
border-width: 0px;
|
||||
border-radius: 6px;
|
||||
background-color: #191724;
|
||||
color: var(--input-text-color);
|
||||
width: auto;
|
||||
height: auto;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
text-align: center;
|
||||
width: 220px;
|
||||
font-size: 16px;
|
||||
font-family: 'Roboto';
|
||||
height: 29px;
|
||||
transition: 0.06s;
|
||||
}
|
||||
|
||||
.bareLocationInput:hover {
|
||||
border: var(--input-border-size) solid #5e18eb;
|
||||
border-width: 1px;
|
||||
border-radius: 6px;
|
||||
|
||||
}
|
||||
|
||||
.bareValidSignal {
|
||||
margin-top: -13px;
|
||||
color: #f45145bd;
|
||||
font-family: 'Ubuntu', sans-serif;
|
||||
font-weight: lighter;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
87
public/sw.js
|
|
@ -1,14 +1,75 @@
|
|||
importScripts("./uv/uv.bundle.js");
|
||||
importScripts("./uv/uv.config.js");
|
||||
importScripts("./uv/uv.sw.js");
|
||||
importScripts("./osana/osana.worker.js");
|
||||
|
||||
const UV = new UVServiceWorker();
|
||||
const Osana = new OsanaServiceWorker();
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (event.request.url.startsWith(location.origin + "/service/go/"))
|
||||
event.respondWith(UV.fetch(event));
|
||||
if (event.request.url.startsWith(location.origin + "/service/~osana/"))
|
||||
event.respondWith(Osana.fetch(event));
|
||||
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);
|
||||
}
|
||||
})()
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,9 +1,29 @@
|
|||
self.__uv$config = {
|
||||
prefix: "/service/go/",
|
||||
prefix: "/~/uv/",
|
||||
bare: "/bare/",
|
||||
encodeUrl: Ultraviolet.codec.xor.encode,
|
||||
decodeUrl: Ultraviolet.codec.xor.decode,
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
10
render.yaml
|
|
@ -1,10 +0,0 @@
|
|||
services:
|
||||
- type: web
|
||||
name: Nebula
|
||||
env: docker
|
||||
repo: https://github.com/NebulaServices/Nebula.git
|
||||
region: oregon
|
||||
plan: free
|
||||
branch: main
|
||||
numInstances: 1
|
||||
healthCheckPath: /
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
{ pkgs }: {
|
||||
deps = [
|
||||
pkgs.nodejs-18_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>
|
||||