Prettier Format

This commit is contained in:
MotorTruck1221 2023-12-26 03:51:37 -07:00
parent 3a9024d1e0
commit ad67368a45
42 changed files with 1650 additions and 1609 deletions

8
.prettierignore Normal file
View file

@ -0,0 +1,8 @@
# Build artifacts
dist/
# Other things that don't need to be reformatted
public/uv/
public/dynamic/
pnpm-lock.yaml
package-lock.json

View file

@ -1,8 +1,8 @@
{ {
"singleQuote": false, "singleQuote": false,
"endOfLine": "crlf", "endOfLine": "crlf",
"tabWidth": 2, "tabWidth": 2,
"useTabs": false, "useTabs": false,
"trailingComma": "none", "trailingComma": "none",
"plugins": ["prettier-plugin-tailwindcss"] "plugins": ["prettier-plugin-tailwindcss"]
} }

24
.vscode/settings.json vendored
View file

@ -1,12 +1,12 @@
{ {
"editor.tabSize": 2, "editor.tabSize": 2,
"editor.insertSpaces": true, "editor.insertSpaces": true,
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.formatOnPaste": true, "editor.formatOnPaste": true,
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"files.autoSave": "afterDelay", "files.autoSave": "afterDelay",
"files.autoSaveDelay": 0, "files.autoSaveDelay": 0,
"css.lint.unknownAtRules": "ignore", "css.lint.unknownAtRules": "ignore",
"editor.linkedEditing": true, "editor.linkedEditing": true,
"css.lint.unknownProperties": "ignore" "css.lint.unknownProperties": "ignore"
} }

View file

@ -1,7 +1,8 @@
# Nebula rewrite # Nebula rewrite
`npm i -g pnpm tsx`
`git clone https://github.com/NebulaServices/Nebula.git` `npm i -g pnpm tsx`
`git switch rewrite` `git clone https://github.com/NebulaServices/Nebula.git`
`pnpm i` `git switch rewrite`
`pnpm i`
readme meant for devs, will be changed before being merged into main
readme meant for devs, will be changed before being merged into main

14
framer-motion.d.ts vendored
View file

@ -1,7 +1,7 @@
import * as React from "preact/compat"; import * as React from "preact/compat";
declare module "framer-motion" { declare module "framer-motion" {
export interface AnimatePresenceProps { export interface AnimatePresenceProps {
children?: React.ReactNode; children?: React.ReactNode;
} }
} }

View file

@ -1,29 +1,29 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo.png" /> <link rel="icon" type="image/svg+xml" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="color-scheme" content="light dark" /> <meta name="color-scheme" content="light dark" />
<title>Nebula</title> <title>Nebula</title>
<script src="/uv/uv.bundle.js"></script> <script src="/uv/uv.bundle.js"></script>
<script src="/uv/uv.config.js"></script> <script src="/uv/uv.config.js"></script>
<script src="/dynamic/dynamic.config.js"></script> <script src="/dynamic/dynamic.config.js"></script>
<script> <script>
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
window.addEventListener("load", () => { window.addEventListener("load", () => {
navigator.serviceWorker.register("/uvsw.js", { navigator.serviceWorker.register("/uvsw.js", {
scope: __uv$config.prefix scope: __uv$config.prefix
}); });
navigator.serviceWorker.register("/dysw.js", { navigator.serviceWorker.register("/dysw.js", {
scope: __dynamic$config.prefix scope: __dynamic$config.prefix
}); });
}); });
} }
</script> </script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/index.tsx"></script> <script type="module" src="/src/index.tsx"></script>
</body> </body>
</html> </html>

View file

@ -1,46 +1,46 @@
{ {
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "concurrently \"vite\" \"bare-server-node --port 8080\"", "dev": "concurrently \"vite\" \"bare-server-node --port 8080\"",
"build": "vite build", "build": "vite build",
"bstart": "npm run build && tsx server.ts", "bstart": "npm run build && tsx server.ts",
"preview": "vite preview", "preview": "vite preview",
"format": "prettier --write ." "format": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@fastify/compress": "^6.5.0", "@fastify/compress": "^6.5.0",
"@fastify/static": "^6.12.0", "@fastify/static": "^6.12.0",
"@titaniumnetwork-dev/ultraviolet": "^2.0.0", "@titaniumnetwork-dev/ultraviolet": "^2.0.0",
"@tomphttp/bare-server-node": "^2.0.1", "@tomphttp/bare-server-node": "^2.0.1",
"classnames": "^2.3.2", "classnames": "^2.3.2",
"fastify": "^4.25.1", "fastify": "^4.25.1",
"framer-motion": "^10.16.16", "framer-motion": "^10.16.16",
"i18next": "^23.7.9", "i18next": "^23.7.9",
"i18next-browser-languagedetector": "^7.2.0", "i18next-browser-languagedetector": "^7.2.0",
"million": "^2.6.4", "million": "^2.6.4",
"preact": "^10.13.1", "preact": "^10.13.1",
"preact-iso": "^2.3.2", "preact-iso": "^2.3.2",
"preact-render-to-string": "^6.3.1", "preact-render-to-string": "^6.3.1",
"preact-router": "^4.1.2", "preact-router": "^4.1.2",
"rammerhead": "https://github.com/holy-unblocker/rammerhead/releases/download/v1.2.41-holy.5/rammerhead-1.2.41-holy.5.tgz", "rammerhead": "https://github.com/holy-unblocker/rammerhead/releases/download/v1.2.41-holy.5/rammerhead-1.2.41-holy.5.tgz",
"react-helmet": "^6.1.0", "react-helmet": "^6.1.0",
"react-i18next": "^13.5.0", "react-i18next": "^13.5.0",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"tsx": "^4.7.0" "tsx": "^4.7.0"
}, },
"devDependencies": { "devDependencies": {
"@preact/preset-vite": "^2.5.0", "@preact/preset-vite": "^2.5.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "^8.55.0", "eslint": "^8.55.0",
"eslint-config-preact": "^1.3.0", "eslint-config-preact": "^1.3.0",
"postcss": "^8.4.32", "postcss": "^8.4.32",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"prettier-plugin-tailwindcss": "^0.5.9", "prettier-plugin-tailwindcss": "^0.5.9",
"tailwindcss": "^3.3.6", "tailwindcss": "^3.3.6",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.0.9", "vite": "^5.0.9",
"vite-plugin-static-copy": "^1.0.0" "vite-plugin-static-copy": "^1.0.0"
} }
} }

View file

@ -1,6 +1,6 @@
export default { export default {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
autoprefixer: {} autoprefixer: {}
} }
}; };

View file

@ -1,21 +1,21 @@
importScripts("/dynamic/dynamic.config.js"); importScripts("/dynamic/dynamic.config.js");
importScripts("/dynamic/dynamic.worker.js"); importScripts("/dynamic/dynamic.worker.js");
const dynamic = new Dynamic(); const dynamic = new Dynamic();
self.dynamic = dynamic; self.dynamic = dynamic;
self.addEventListener("fetch", (event) => { self.addEventListener("fetch", (event) => {
if ( if (
event.request.url.startsWith(location.origin + self.__dynamic$config.prefix) event.request.url.startsWith(location.origin + self.__dynamic$config.prefix)
) )
event.respondWith( event.respondWith(
(async function () { (async function () {
if (await dynamic.route(event)) { if (await dynamic.route(event)) {
return await dynamic.fetch(event); return await dynamic.fetch(event);
} }
return await fetch(event.request); return await fetch(event.request);
})() })()
); );
}); });

View file

@ -1,10 +1,10 @@
importScripts("/uv/uv.bundle.js"); importScripts("/uv/uv.bundle.js");
importScripts("/uv/uv.config.js"); importScripts("/uv/uv.config.js");
importScripts(__uv$config.sw || "/uv/uv.sw.js"); importScripts(__uv$config.sw || "/uv/uv.sw.js");
const sw = new UVServiceWorker(); const sw = new UVServiceWorker();
self.addEventListener("fetch", (event) => { self.addEventListener("fetch", (event) => {
if (event.request.url.startsWith(location.origin + __uv$config.prefix)) if (event.request.url.startsWith(location.origin + __uv$config.prefix))
return event.respondWith(sw.fetch(event)); return event.respondWith(sw.fetch(event));
}); });

172
server.ts
View file

@ -1,86 +1,86 @@
import fastify from 'fastify'; import fastify from "fastify";
import fastifyStatic from '@fastify/static'; import fastifyStatic from "@fastify/static";
import { fileURLToPath } from 'url'; import { fileURLToPath } from "url";
import path from 'path'; import path from "path";
import createRammerhead from "rammerhead/src/server/index.js"; import createRammerhead from "rammerhead/src/server/index.js";
import { createBareServer } from "@tomphttp/bare-server-node"; import { createBareServer } from "@tomphttp/bare-server-node";
import { createServer } from "http"; import { createServer } from "http";
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
const bare = createBareServer("/bare/"); const bare = createBareServer("/bare/");
const rh = createRammerhead(); const rh = createRammerhead();
const rammerheadScopes = [ const rammerheadScopes = [
"/rammerhead.js", "/rammerhead.js",
"/hammerhead.js", "/hammerhead.js",
"/transport-worker.js", "/transport-worker.js",
"/task.js", "/task.js",
"/iframe-task.js", "/iframe-task.js",
"/worker-hammerhead.js", "/worker-hammerhead.js",
"/messaging", "/messaging",
"/sessionexists", "/sessionexists",
"/deletesession", "/deletesession",
"/newsession", "/newsession",
"/editsession", "/editsession",
"/needpassword", "/needpassword",
"/syncLocalStorage", "/syncLocalStorage",
"/api/shuffleDict", "/api/shuffleDict",
"/mainport" "/mainport"
]; ];
const rammerheadSession = /^\/[a-z0-9]{32}/; const rammerheadSession = /^\/[a-z0-9]{32}/;
function shouldRouteRh(req) { function shouldRouteRh(req) {
const url = new URL(req.url, "http://0.0.0.0"); const url = new URL(req.url, "http://0.0.0.0");
return ( return (
rammerheadScopes.includes(url.pathname) || rammerheadScopes.includes(url.pathname) ||
rammerheadSession.test(url.pathname) rammerheadSession.test(url.pathname)
); );
} }
function routeRhRequest(req, res) { function routeRhRequest(req, res) {
rh.emit("request", req, res); rh.emit("request", req, res);
} }
function routeRhUpgrade(req, socket, head) { function routeRhUpgrade(req, socket, head) {
rh.emit("upgrade", req, socket, head); rh.emit("upgrade", req, socket, head);
} }
const serverFactory = (handler, opts) => { const serverFactory = (handler, opts) => {
return createServer() return createServer()
.on("request", (req, res) => { .on("request", (req, res) => {
if (bare.shouldRoute(req)) { if (bare.shouldRoute(req)) {
bare.routeRequest(req, res); bare.routeRequest(req, res);
} else if (shouldRouteRh(req)) { } else if (shouldRouteRh(req)) {
routeRhRequest(req, res); routeRhRequest(req, res);
} else { } else {
handler(req, res); handler(req, res);
} }
}) })
.on("upgrade", (req, socket, head) => { .on("upgrade", (req, socket, head) => {
if (bare.shouldRoute(req)) { if (bare.shouldRoute(req)) {
bare.routeUpgrade(req, socket, head); bare.routeUpgrade(req, socket, head);
} else if (shouldRouteRh(req)) { } else if (shouldRouteRh(req)) {
routeRhUpgrade(req, socket, head); routeRhUpgrade(req, socket, head);
} }
}); });
}; };
const app = fastify({ logger: true, serverFactory }); const app = fastify({ logger: true, serverFactory });
app.register(fastifyStatic, { app.register(fastifyStatic, {
root: path.join(__dirname, 'dist'), root: path.join(__dirname, "dist"),
prefix: '/', prefix: "/",
serve: true, serve: true,
wildcard: false, wildcard: false
}); });
app.setNotFoundHandler((req, res) => { app.setNotFoundHandler((req, res) => {
res.sendFile('index.html') // SPA catch-all res.sendFile("index.html"); // SPA catch-all
}) });
app.listen({ app.listen({
port: 8080 port: 8080
}); });

View file

@ -1,12 +1,12 @@
import { Header } from "./Header"; import { Header } from "./Header";
export function HeaderRoute(props: { children: any }) { export function HeaderRoute(props: { children: any }) {
return ( return (
<div class="flex h-screen flex-col"> <div class="flex h-screen flex-col">
<Header /> <Header />
<div class="flex-1 bg-primary"> <div class="flex-1 bg-primary">
<main class="h-full">{props.children}</main> <main class="h-full">{props.children}</main>
</div> </div>
</div> </div>
); );
} }

View file

@ -1,13 +1,23 @@
import { motion } from "framer-motion" import { motion } from "framer-motion";
import { IframeHeader } from "./IframeHeader" import { IframeHeader } from "./IframeHeader";
export function Iframe(props: { url: string }) { export function Iframe(props: { url: string }) {
return ( return (
<> <>
<IframeHeader url={ props.url } /> <IframeHeader url={props.url} />
<motion.div className="w-full h-[calc(100%_-_4rem)] bg-primary" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.5 }} > <motion.div
<iframe id="iframe" src={ props.url } className="w-full h-full border-none bg-primary" /> className="h-[calc(100%_-_4rem)] w-full bg-primary"
</motion.div> initial={{ opacity: 0 }}
</> animate={{ opacity: 1 }}
) exit={{ opacity: 0 }}
} transition={{ duration: 0.5 }}
>
<iframe
id="iframe"
src={props.url}
className="h-full w-full border-none bg-primary"
/>
</motion.div>
</>
);
}

View file

@ -1,33 +1,48 @@
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "preact-router"; import { Link } from "preact-router";
import { RiPictureInPictureExitFill, RiFullscreenFill } from "react-icons/ri" import { RiPictureInPictureExitFill, RiFullscreenFill } from "react-icons/ri";
export function IframeHeader(props: { url: string }) { export function IframeHeader(props: { url: string }) {
const { t } = useTranslation() const { t } = useTranslation();
const [showPopout, setShowPopout] = useState(false); const [showPopout, setShowPopout] = useState(false);
const [showFullScreen, setFullScreen] = useState(false); const [showFullScreen, setFullScreen] = useState(false);
if (showPopout) { if (showPopout) {
window.location.replace(props.url); window.location.replace(props.url);
} }
if (showFullScreen) { if (showFullScreen) {
document.getElementById("iframe").requestFullscreen(); document.getElementById("iframe").requestFullscreen();
setFullScreen(false); setFullScreen(false);
} }
return ( return (
<div id="iframeNav" className="flex h-16 flex-row items-center justify-between bg-navbar-color px-4"> <div
<Link href="/" class="w-1/2"> id="iframeNav"
<div className="flex flex-row items-center"> className="flex h-16 flex-row items-center justify-between bg-navbar-color px-4"
<img src="/logo.png" className="h-16 w-16 transition-all duration-1000 hover:rotate-[360deg]"></img> >
<h1 className="font-roboto text-2xl font-bold text-navbar-text-color md:text-4xl"> {t("header.title")} </h1> <Link href="/" class="w-1/2">
</div> <div className="flex flex-row items-center">
</Link> <img
<div id="navItems" class="w-1/2"> src="/logo.png"
<div className="flex flex-row items-center justify-end gap-3 mr-4"> className="h-16 w-16 transition-all duration-1000 hover:rotate-[360deg]"
<RiPictureInPictureExitFill className="h-6 w-6 cursor-pointer transition-all duration-0500 text-navbar-text-color hover:scale-110 hover:brightness-125" onClick={() => setShowPopout(true)} /> ></img>
<RiFullscreenFill className="h-6 w-6 cursor-pointer transition-all duration-0500 text-navbar-text-color hover:scale-110 hover:brightness-125 active:rotate-90" onClick={() => setFullScreen(true)} /> <h1 className="font-roboto text-2xl font-bold text-navbar-text-color md:text-4xl">
</div> {" "}
</div> {t("header.title")}{" "}
</div> </h1>
) </div>
}; </Link>
<div id="navItems" class="w-1/2">
<div className="mr-4 flex flex-row items-center justify-end gap-3">
<RiPictureInPictureExitFill
className="duration-0500 h-6 w-6 cursor-pointer text-navbar-text-color transition-all hover:scale-110 hover:brightness-125"
onClick={() => setShowPopout(true)}
/>
<RiFullscreenFill
className="duration-0500 h-6 w-6 cursor-pointer text-navbar-text-color transition-all hover:scale-110 hover:brightness-125 active:rotate-90"
onClick={() => setFullScreen(true)}
/>
</div>
</div>
</div>
);
}

View file

@ -1,32 +1,32 @@
import i18n from "i18next"; import i18n from "i18next";
import { initReactI18next } from "react-i18next"; import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector"; import LanguageDetector from "i18next-browser-languagedetector";
import translationEN from "./locales/en.json"; import translationEN from "./locales/en.json";
import translationES from "./locales/es.json"; import translationES from "./locales/es.json";
import translationJA from "./locales/ja.json"; import translationJA from "./locales/ja.json";
const resources = { const resources = {
en: { en: {
translation: translationEN translation: translationEN
}, },
es: { es: {
translation: translationES translation: translationES
}, },
ja: { ja: {
translation: translationJA translation: translationJA
} }
}; };
i18n i18n
.use(initReactI18next) .use(initReactI18next)
.use(LanguageDetector) .use(LanguageDetector)
.init({ .init({
resources, resources,
fallbackLng: "en", fallbackLng: "en",
interpolation: { interpolation: {
escapeValue: false escapeValue: false
} }
}); });
export default i18n; export default i18n;

View file

@ -1,28 +1,27 @@
import { render } from "preact"; import { render } from "preact";
import { LocationProvider, Router, Route } from "preact-iso"; import { LocationProvider, Router, Route } from "preact-iso";
import { Home } from "./pages/Home"; import { Home } from "./pages/Home";
import { NotFound } from "./pages/_404.jsx"; import { NotFound } from "./pages/_404.jsx";
import { DiscordPage } from "./pages/discord.jsx"; import { DiscordPage } from "./pages/discord.jsx";
import { ProxyFrame } from "./pages/ProxyFrame.js"; import { ProxyFrame } from "./pages/ProxyFrame.js";
import { Settings } from "./pages/Settings/index.js"; import { Settings } from "./pages/Settings/index.js";
import "./style.css"; import "./style.css";
import "./themes/main.css"; import "./themes/main.css";
import "./i18n"; import "./i18n";
export function App() { export function App() {
return ( return (
<LocationProvider> <LocationProvider>
<Router> <Router>
<Route path="/" component={Home} /> <Route path="/" component={Home} />
<Route path="/discord" component={DiscordPage} /> <Route path="/discord" component={DiscordPage} />
<Route path="/go/:url" component={ProxyFrame} /> <Route path="/go/:url" component={ProxyFrame} />
<Route path="/settings" component={Settings} /> <Route path="/settings" component={Settings} />
<Route default component={NotFound} /> <Route default component={NotFound} />
</Router> </Router>
</LocationProvider> </LocationProvider>
); );
} }
render(<App />, document.getElementById("app")); render(<App />, document.getElementById("app"));

View file

@ -1,52 +1,52 @@
{ {
"header": { "header": {
"title": "Nebel", "title": "Nebel",
"games": "Spiele", "games": "Spiele",
"settings": "einstellungen", "settings": "einstellungen",
"discord": "Sie wollen mehr URL-Adresse?" "discord": "Sie wollen mehr URL-Adresse?"
}, },
"404": { "404": {
"text": "Dieser Nebel-Dienst wurde deaktiviert.", "text": "Dieser Nebel-Dienst wurde deaktiviert.",
"return": "Zurück nach Hause." "return": "Zurück nach Hause."
}, },
"home": { "home": {
"placeholder": "Suchen Sie frei im Web." "placeholder": "Suchen Sie frei im Web."
}, },
"discord": { "discord": {
"title": "Nebel's Discord Server", "title": "Nebel's Discord Server",
"sub": "Möchten Sie diesen über einen Proxy öffnen?", "sub": "Möchten Sie diesen über einen Proxy öffnen?",
"button1": "Normal öffnen", "button1": "Normal öffnen",
"button2": "Proxy verwenden" "button2": "Proxy verwenden"
}, },
"setting": { "setting": {
"tabs": { "tabs": {
"proxy": "Proxy", "proxy": "Proxy",
"tab": "tab", "tab": "tab",
"custom": "Anpassung", "custom": "Anpassung",
"misc": "Misc" "misc": "Misc"
}, },
"proxy": { "proxy": {
"title": "Proxy", "title": "Proxy",
"subtitle": "Wählen Sie den Proxy, der Ihren Anforderungen entspricht", "subtitle": "Wählen Sie den Proxy, der Ihren Anforderungen entspricht",
"automatic": "Automatisch", "automatic": "Automatisch",
"buggyWarning": "(PROBLEME)" "buggyWarning": "(PROBLEME)"
}, },
"languages": { "languages": {
"title": "Sprache", "title": "Sprache",
"subtitle": "Wählen Sie Ihre bevorzugte Sprache", "subtitle": "Wählen Sie Ihre bevorzugte Sprache",
"japanese": "Japanisch", "japanese": "Japanisch",
"english": "Englisch", "english": "Englisch",
"spanish": "Spanisch", "spanish": "Spanisch",
"german": "Deutsch", "german": "Deutsch",
"greek": "Griechisch", "greek": "Griechisch",
"dutch": "Niederländisch" "dutch": "Niederländisch"
}, },
"proxymodes": { "proxymodes": {
"title": "Öffnen in", "title": "Öffnen in",
"subtitle": "Wählen Sie, wie Ihre Seiten geöffnet werden sollen", "subtitle": "Wählen Sie, wie Ihre Seiten geöffnet werden sollen",
"embed": "Einbetten", "embed": "Einbetten",
"direct": "Direkt", "direct": "Direkt",
"aboutblank": "About:Blank" "aboutblank": "About:Blank"
} }
} }
} }

View file

@ -1,52 +1,52 @@
{ {
"header": { "header": {
"title": "nebula.", "title": "nebula.",
"games": "Games", "games": "Games",
"settings": "Settings", "settings": "Settings",
"discord": "Want more links?" "discord": "Want more links?"
}, },
"404": { "404": {
"text": "This Nebula Service has been disabled.", "text": "This Nebula Service has been disabled.",
"return": "Back to home" "return": "Back to home"
}, },
"home": { "home": {
"placeholder": "Search the web freely" "placeholder": "Search the web freely"
}, },
"discord": { "discord": {
"title": "Nebula's Discord Server", "title": "Nebula's Discord Server",
"sub": "Would you like to open this via proxy?", "sub": "Would you like to open this via proxy?",
"button1": "Open Normally", "button1": "Open Normally",
"button2": "Use Proxy" "button2": "Use Proxy"
}, },
"settings": { "settings": {
"tabs": { "tabs": {
"proxy": "Proxy", "proxy": "Proxy",
"tab": "Tab", "tab": "Tab",
"custom": "Customization", "custom": "Customization",
"misc": "Misc" "misc": "Misc"
}, },
"proxy": { "proxy": {
"title": "Proxy", "title": "Proxy",
"subtitle": "Choose the proxy that fits your needs", "subtitle": "Choose the proxy that fits your needs",
"automatic": "Automatic", "automatic": "Automatic",
"buggyWarning": "(BUGGY)" "buggyWarning": "(BUGGY)"
}, },
"languages": { "languages": {
"title": "Language", "title": "Language",
"subtitle": "Choose your preferred language", "subtitle": "Choose your preferred language",
"japanese": "Japanese", "japanese": "Japanese",
"english": "English", "english": "English",
"spanish": "Spanish", "spanish": "Spanish",
"german": "German", "german": "German",
"greek": "Greek", "greek": "Greek",
"dutch": "Dutch" "dutch": "Dutch"
}, },
"proxymodes": { "proxymodes": {
"title": "Open in", "title": "Open in",
"subtitle": "Choose how to open your sites", "subtitle": "Choose how to open your sites",
"embed": "Embed", "embed": "Embed",
"direct": "Direct", "direct": "Direct",
"aboutblank": "About:Blank" "aboutblank": "About:Blank"
} }
} }
} }

View file

@ -1,52 +1,52 @@
{ {
"header": { "header": {
"title": "nebula.", "title": "nebula.",
"games": "Juegos", "games": "Juegos",
"settings": "Ajustes", "settings": "Ajustes",
"discord": "¿Quieres más enlaces?" "discord": "¿Quieres más enlaces?"
}, },
"404": { "404": {
"text": "Este servicio Nebula ha sido deshabilitado.", "text": "Este servicio Nebula ha sido deshabilitado.",
"return": "De vuelta a casa" "return": "De vuelta a casa"
}, },
"home": { "home": {
"placeholder": "Busca en la web libremente" "placeholder": "Busca en la web libremente"
}, },
"discord": { "discord": {
"title": "Nebula's Discord Servidor", "title": "Nebula's Discord Servidor",
"sub": "¿Le gustaría abrir esto a través de proxy?", "sub": "¿Le gustaría abrir esto a través de proxy?",
"button1": "Abierto normalmente", "button1": "Abierto normalmente",
"button2": "Usa proxy" "button2": "Usa proxy"
}, },
"settings": { "settings": {
"tabs": { "tabs": {
"proxy": "Proxy", "proxy": "Proxy",
"tab": "Pestaña", "tab": "Pestaña",
"custom": "Personalización", "custom": "Personalización",
"misc": "Misc" "misc": "Misc"
}, },
"proxy": { "proxy": {
"title": "Proxy", "title": "Proxy",
"subtitle": "Elige el proxy que se ajuste a tus necesidades", "subtitle": "Elige el proxy que se ajuste a tus necesidades",
"automatic": "Automática", "automatic": "Automática",
"buggyWarning": "(CALESA)" "buggyWarning": "(CALESA)"
}, },
"languages": { "languages": {
"title": "Idioma", "title": "Idioma",
"subtitle": "Elige tu idioma preferido", "subtitle": "Elige tu idioma preferido",
"japanese": "Japonés", "japanese": "Japonés",
"english": "Inglés", "english": "Inglés",
"spanish": "Español", "spanish": "Español",
"german": "", "german": "",
"greek": "", "greek": "",
"dutch": "" "dutch": ""
}, },
"proxymodes": { "proxymodes": {
"title": "Abrir en", "title": "Abrir en",
"subtitle": "Elige cómo abrir tus sitios", "subtitle": "Elige cómo abrir tus sitios",
"embed": "Incrustar", "embed": "Incrustar",
"direct": "Directo", "direct": "Directo",
"aboutblank": "About:Blank" "aboutblank": "About:Blank"
} }
} }
} }

View file

@ -1,52 +1,52 @@
{ {
"header": { "header": {
"title": "νεφέλωμα", "title": "νεφέλωμα",
"games": "Παιχνίδια", "games": "Παιχνίδια",
"settings": "Ρυθμίσεις", "settings": "Ρυθμίσεις",
"discord": "θέλετε περισσότερους συνδέσμους?" "discord": "θέλετε περισσότερους συνδέσμους?"
}, },
"404": { "404": {
"text": "Αυτή η υπηρεσία Νεφέλωμα έχει απενεργοποιηθεί.", "text": "Αυτή η υπηρεσία Νεφέλωμα έχει απενεργοποιηθεί.",
"return": "Επιστροφή στο σπίτι." "return": "Επιστροφή στο σπίτι."
}, },
"home": { "home": {
"placeholder": "Ελεύθερη αναζήτηση στο διαδίκτυο." "placeholder": "Ελεύθερη αναζήτηση στο διαδίκτυο."
}, },
"discord": { "discord": {
"title": "Discord Διακομιστής του Νεφελώματος", "title": "Discord Διακομιστής του Νεφελώματος",
"sub": "Άνοιγμα μέσω διακομιστή μεσολάβησης;", "sub": "Άνοιγμα μέσω διακομιστή μεσολάβησης;",
"button1": "Ανοιχτό κανονικά", "button1": "Ανοιχτό κανονικά",
"button2": "Χρήση μεσολάβησης" "button2": "Χρήση μεσολάβησης"
}, },
"setting": { "setting": {
"tabs": { "tabs": {
"proxy": "Mεσολάβησης", "proxy": "Mεσολάβησης",
"tab": "Καρτέλα", "tab": "Καρτέλα",
"custom": "Προσαρμογή", "custom": "Προσαρμογή",
"misc": "Διάφορα" "misc": "Διάφορα"
}, },
"proxy": { "proxy": {
"title": "Μεσολάβηση", "title": "Μεσολάβηση",
"subtitle": "Επιλέξτε τη μεσολάβηση που ταιριάζει στις ανάγκες σας", "subtitle": "Επιλέξτε τη μεσολάβηση που ταιριάζει στις ανάγκες σας",
"automatic": "Αυτόματο", "automatic": "Αυτόματο",
"buggyWarning": "(ΠΡΟΒΛΗΜΑ)" "buggyWarning": "(ΠΡΟΒΛΗΜΑ)"
}, },
"languages": { "languages": {
"title": "Γλώσσες", "title": "Γλώσσες",
"subtitle": "Επιλέξτε τη γλώσσα που προτιμάτε", "subtitle": "Επιλέξτε τη γλώσσα που προτιμάτε",
"japanese": "Ιαπωνικά", "japanese": "Ιαπωνικά",
"english": "Αγγλικά", "english": "Αγγλικά",
"spanish": "Ησπανικά", "spanish": "Ησπανικά",
"german": "Γερμανικό", "german": "Γερμανικό",
"greek": "Ελληνικά", "greek": "Ελληνικά",
"dutch": "Ολλανδικά" "dutch": "Ολλανδικά"
}, },
"proxymodes": { "proxymodes": {
"title": "Άνοιγμα σε", "title": "Άνοιγμα σε",
"subtitle": "Επιλέξτε τον τρόπο ανοίγματος των ιστοσελίδων σας", "subtitle": "Επιλέξτε τον τρόπο ανοίγματος των ιστοσελίδων σας",
"embed": "εΕνσωμάτωση", "embed": "εΕνσωμάτωση",
"direct": "¨Αμεσα", "direct": "¨Αμεσα",
"aboutblank": "About:Blank" "aboutblank": "About:Blank"
} }
} }
} }

View file

@ -1,52 +1,52 @@
{ {
"header": { "header": {
"title": "ネブラ。", "title": "ネブラ。",
"games": "ゲーム", "games": "ゲーム",
"settings": "セッティング", "settings": "セッティング",
"discord": "もっとリンクが欲しいですか?" "discord": "もっとリンクが欲しいですか?"
}, },
"404": { "404": {
"text": "このネブラサービスは無効になっています", "text": "このネブラサービスは無効になっています",
"return": "帰宅" "return": "帰宅"
}, },
"home": { "home": {
"placeholder": "由にウェブを検索" "placeholder": "由にウェブを検索"
}, },
"discord": { "discord": {
"title": "ネブラDiscordサーバー", "title": "ネブラDiscordサーバー",
"sub": "プロキシ経由で開きますか?", "sub": "プロキシ経由で開きますか?",
"button1": "通常通り開く", "button1": "通常通り開く",
"button2": "プロキシを使用する" "button2": "プロキシを使用する"
}, },
"settings": { "settings": {
"tabs": { "tabs": {
"proxy": "プロキシ", "proxy": "プロキシ",
"tab": "タブ", "tab": "タブ",
"custom": "カスタマイズ", "custom": "カスタマイズ",
"misc": "その他" "misc": "その他"
}, },
"proxy": { "proxy": {
"title": "プロキシ", "title": "プロキシ",
"subtitle": "ニーズに合ったプロキシを選んでください", "subtitle": "ニーズに合ったプロキシを選んでください",
"automatic": "自動", "automatic": "自動",
"buggyWarning": "(バグ)" "buggyWarning": "(バグ)"
}, },
"languages": { "languages": {
"title": "言語", "title": "言語",
"subtitle": "好きな言語を選んでください", "subtitle": "好きな言語を選んでください",
"japanese": "日本語", "japanese": "日本語",
"english": "英語", "english": "英語",
"spanish": "スペイン語", "spanish": "スペイン語",
"german": "", "german": "",
"greek": "", "greek": "",
"dutch": "" "dutch": ""
}, },
"proxymodes": { "proxymodes": {
"title": "開く", "title": "開く",
"subtitle": "サイトを開く方法を選択する", "subtitle": "サイトを開く方法を選択する",
"embed": "埋め込む", "embed": "埋め込む",
"direct": "直接", "direct": "直接",
"aboutblank": "About:Blank" "aboutblank": "About:Blank"
} }
} }
} }

View file

@ -1,55 +1,55 @@
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { HeaderRoute } from "../components/HeaderRoute"; import { HeaderRoute } from "../components/HeaderRoute";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
export function Home() { export function Home() {
const [isFocused, setIsFocused] = useState(false); const [isFocused, setIsFocused] = useState(false);
const [inputValue, setInputValue] = useState(""); const [inputValue, setInputValue] = useState("");
const { t } = useTranslation(); const { t } = useTranslation();
const handleSubmit = (event) => { const handleSubmit = (event) => {
event.preventDefault(); event.preventDefault();
window.location.href = "/go/" + encodeURIComponent(inputValue); window.location.href = "/go/" + encodeURIComponent(inputValue);
}; };
return ( return (
<HeaderRoute> <HeaderRoute>
<Helmet> <Helmet>
<title>Nebula</title> <title>Nebula</title>
</Helmet> </Helmet>
<div class="flex h-full flex-col items-center justify-center"> <div class="flex h-full flex-col items-center justify-center">
<div class="font-inter absolute bottom-0 left-0 p-1 text-sm italic text-input-text"> <div class="font-inter absolute bottom-0 left-0 p-1 text-sm italic text-input-text">
Nebula &copy; Nebula Services {new Date().getUTCFullYear()} Nebula &copy; Nebula Services {new Date().getUTCFullYear()}
</div> </div>
<a href="https://github.com/NebulaServices/Nebula"> <a href="https://github.com/NebulaServices/Nebula">
<div class="font-inter absolute bottom-0 right-0 p-1 text-sm text-input-text"> <div class="font-inter absolute bottom-0 right-0 p-1 text-sm text-input-text">
GitHub GitHub
</div> </div>
</a> </a>
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
class="flex h-full w-full items-center justify-center" class="flex h-full w-full items-center justify-center"
> >
<input <input
onFocus={() => { onFocus={() => {
setIsFocused(true); setIsFocused(true);
}} }}
onBlur={() => { onBlur={() => {
setIsFocused(false); setIsFocused(false);
}} }}
type="text" type="text"
value={inputValue} value={inputValue}
onChange={(e) => onChange={(e) =>
setInputValue((e.target as HTMLInputElement).value) setInputValue((e.target as HTMLInputElement).value)
} }
className={`font-roboto h-14 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none ${ className={`font-roboto h-14 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none ${
isFocused ? "w-full md:w-3/12" : "w-full md:w-80" isFocused ? "w-full md:w-3/12" : "w-full md:w-80"
} transition-all duration-300`} } transition-all duration-300`}
placeholder={isFocused ? "" : t("home.placeholder")} placeholder={isFocused ? "" : t("home.placeholder")}
/> />
</form> </form>
</div> </div>
</HeaderRoute> </HeaderRoute>
); );
} }

View file

@ -1,84 +1,85 @@
import { RammerheadEncode } from "../util/RammerheadEncode"; import { RammerheadEncode } from "../util/RammerheadEncode";
import { searchUtil } from "../util/searchUtil"; import { searchUtil } from "../util/searchUtil";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
//import our Iframe component //import our Iframe component
import { Iframe } from "../components/iframe/Iframe"; import { Iframe } from "../components/iframe/Iframe";
declare global { declare global {
interface Window { interface Window {
__uv$config: any; __uv$config: any;
__dynamic$config: any; __dynamic$config: any;
} }
} }
export function ProxyFrame(props: { url: string }) { export function ProxyFrame(props: { url: string }) {
// pass the URL encoded with encodeURIcomponent // pass the URL encoded with encodeURIcomponent
const localProxy = localStorage.getItem("proxy") || "automatic"; const localProxy = localStorage.getItem("proxy") || "automatic";
const proxyMode = localStorage.getItem("proxyMode") || "direct"; const proxyMode = localStorage.getItem("proxyMode") || "direct";
const [ProxiedUrl, setProxiedUrl] = useState<string | undefined>(undefined); const [ProxiedUrl, setProxiedUrl] = useState<string | undefined>(undefined);
let decodedUrl = decodeURIComponent(props.url); let decodedUrl = decodeURIComponent(props.url);
//attempt to convert to a valid url //attempt to convert to a valid url
decodedUrl = searchUtil(decodedUrl, "https://google.com/search?q=%s"); decodedUrl = searchUtil(decodedUrl, "https://google.com/search?q=%s");
let proxyRef; let proxyRef;
useEffect(() => { useEffect(() => {
// For now we can redirect to the results. In the future we will add an if statement looking for the users proxy display choice // For now we can redirect to the results. In the future we will add an if statement looking for the users proxy display choice
if (localProxy === "rammerhead") { if (localProxy === "rammerhead") {
RammerheadEncode(decodedUrl).then((result: string) => { RammerheadEncode(decodedUrl).then((result: string) => {
setProxiedUrl(result); setProxiedUrl(result);
}); });
} else if (localProxy === "ultraviolet") { } else if (localProxy === "ultraviolet") {
setProxiedUrl( setProxiedUrl(
window.__uv$config.prefix + window.__uv$config.encodeUrl(decodedUrl) window.__uv$config.prefix + window.__uv$config.encodeUrl(decodedUrl)
); );
} else if (localProxy === "dynamic") { } else if (localProxy === "dynamic") {
setProxiedUrl( setProxiedUrl(
window.__dynamic$config.prefix + encodeURIComponent(decodedUrl) window.__dynamic$config.prefix + encodeURIComponent(decodedUrl)
); );
} else { } else {
// use UV for automatic // use UV for automatic
setProxiedUrl( setProxiedUrl(
window.__uv$config.prefix + window.__uv$config.encodeUrl(decodedUrl) window.__uv$config.prefix + window.__uv$config.encodeUrl(decodedUrl)
); );
} }
}, [localProxy]); }, [localProxy]);
if (proxyMode == "direct") { if (proxyMode == "direct") {
window.location.href = ProxiedUrl; window.location.href = ProxiedUrl;
} } else if (proxyMode == "aboutblank") {
else if (proxyMode == "aboutblank") { const newWindow = window.open("about:blank", "_blank");
const newWindow = window.open("about:blank", "_blank"); const newDocument = newWindow.document.open();
const newDocument = newWindow.document.open(); newDocument.write(`
newDocument.write(` <!DOCTYPE html>
<!DOCTYPE html> <html>
<html> <head>
<head> <style type="text/css">
<style type="text/css"> body, html
body, html {
{ margin: 0; padding: 0; height: 100%; overflow: hidden;
margin: 0; padding: 0; height: 100%; overflow: hidden; }
} </style>
</style> </head>
</head> <body>
<body> <iframe style="border: none; width: 100%; height: 100vh;" src="${
<iframe style="border: none; width: 100%; height: 100vh;" src="${ window.location.origin + ProxiedUrl
window.location.origin + ProxiedUrl }"></iframe>
}"></iframe> </body>
</body> </html>
</html> `);
`); newDocument.close();
newDocument.close() window.location.replace("/");
window.location.replace("/"); }
}
return (
return ( <div class="h-screen w-screen bg-primary">
<div class="h-screen w-screen bg-primary"> {proxyMode === "direct" && <h1>Loading {localProxy}...</h1>}
{proxyMode === "direct" && <h1>Loading {localProxy}...</h1>} {proxyMode === "aboutblank" && <h1>Loading {localProxy}...</h1>}
{proxyMode === "aboutblank" && <h1>Loading {localProxy}...</h1>} {proxyMode === "embed" && (
{proxyMode === "embed" && <Iframe url={ProxiedUrl} normalUrl={decodedUrl} />} <Iframe url={ProxiedUrl} normalUrl={decodedUrl} />
</div> )}
); </div>
} );
}

View file

@ -1,19 +1,19 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { tabContentVariant, settingsPageVariant } from "./Variants"; import { tabContentVariant, settingsPageVariant } from "./Variants";
const Customization = ({ id, active }) => ( const Customization = ({ id, active }) => (
<motion.div <motion.div
role="tabpanel" role="tabpanel"
id={id} id={id}
className="tab-content" className="tab-content"
variants={tabContentVariant} variants={tabContentVariant}
animate={active ? "active" : "inactive"} animate={active ? "active" : "inactive"}
initial="inactive" initial="inactive"
> >
<motion.div variants={settingsPageVariant} className="content-card"> <motion.div variants={settingsPageVariant} className="content-card">
<h1>Customization</h1> <h1>Customization</h1>
</motion.div> </motion.div>
</motion.div> </motion.div>
); );
export default Customization; export default Customization;

View file

@ -1,73 +1,73 @@
import { useState, useEffect } from "preact/hooks"; import { useState, useEffect } from "preact/hooks";
import { FaAngleDown } from "react-icons/fa"; import { FaAngleDown } from "react-icons/fa";
interface Option { interface Option {
id: string; id: string;
label: string; // Translations CAN be passed label: string; // Translations CAN be passed
} }
const Dropdown = ({ const Dropdown = ({
storageKey, storageKey,
options, options,
refresh refresh
}: { }: {
storageKey: string; storageKey: string;
options: Option[]; options: Option[];
refresh: boolean; refresh: boolean;
}) => { }) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [choice, setChoice] = useState(() => { const [choice, setChoice] = useState(() => {
return localStorage.getItem(storageKey) || options[0]?.id || ""; return localStorage.getItem(storageKey) || options[0]?.id || "";
}); });
// update on localstorage change // update on localstorage change
useEffect(() => { useEffect(() => {
setChoice(localStorage.getItem(storageKey) || options[0]?.id || ""); setChoice(localStorage.getItem(storageKey) || options[0]?.id || "");
}, [storageKey, options]); }, [storageKey, options]);
return ( return (
<div className="relative text-center"> <div className="relative text-center">
<div <div
className={`font-roboto flex h-14 w-56 cursor-pointer flex-col items-center justify-center border border-input-border-color bg-input text-center text-xl ${ className={`font-roboto flex h-14 w-56 cursor-pointer flex-col items-center justify-center border border-input-border-color bg-input text-center text-xl ${
isOpen ? "rounded-t-2xl" : "rounded-2xl" isOpen ? "rounded-t-2xl" : "rounded-2xl"
}`} }`}
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
> >
<div className="flex h-full w-full select-none flex-row items-center"> <div className="flex h-full w-full select-none flex-row items-center">
<div class="h-full w-1/4"></div> <div class="h-full w-1/4"></div>
<div class="flex w-2/4 flex-col items-center"> <div class="flex w-2/4 flex-col items-center">
{options.find((o) => o.id === choice)?.label} {options.find((o) => o.id === choice)?.label}
</div> </div>
<div class="flex w-1/4 flex-col items-center"> <div class="flex w-1/4 flex-col items-center">
<FaAngleDown /> <FaAngleDown />
</div> </div>
</div> </div>
{isOpen && ( {isOpen && (
<div className="absolute top-full w-full"> <div className="absolute top-full w-full">
{options.map((option, index) => ( {options.map((option, index) => (
<div <div
key={option.id} key={option.id}
className={`border border-input-border-color bg-input p-2 hover:bg-dropdown-option-hover-color ${ className={`border border-input-border-color bg-input p-2 hover:bg-dropdown-option-hover-color ${
index === options.length - 1 ? "rounded-b-2xl" : "" index === options.length - 1 ? "rounded-b-2xl" : ""
}`} }`}
onClick={() => { onClick={() => {
setIsOpen(false); setIsOpen(false);
setChoice(option.id); setChoice(option.id);
localStorage.setItem(storageKey, option.id); localStorage.setItem(storageKey, option.id);
if (refresh === true) { if (refresh === true) {
window.location.reload(); window.location.reload();
} }
}} }}
> >
{option.label} {option.label}
</div> </div>
))} ))}
</div> </div>
)} )}
</div> </div>
</div> </div>
); );
}; };
export default Dropdown; export default Dropdown;

View file

@ -1,41 +1,41 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { tabContentVariant, settingsPageVariant } from "./Variants"; import { tabContentVariant, settingsPageVariant } from "./Variants";
import Dropdown from "./Dropdown"; import Dropdown from "./Dropdown";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const Misc = ({ id, active }) => { const Misc = ({ id, active }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const languages = [ const languages = [
{ id: "en-US", label: t("settings.languages.english") }, { id: "en-US", label: t("settings.languages.english") },
{ id: "es", label: t("settings.languages.spanish") }, { id: "es", label: t("settings.languages.spanish") },
{ id: "ja", label: t("settings.languages.japanese") } { id: "ja", label: t("settings.languages.japanese") }
]; ];
return ( return (
<motion.div <motion.div
role="tabpanel" role="tabpanel"
id={id} id={id}
className="tab-content" className="tab-content"
variants={tabContentVariant} variants={tabContentVariant}
animate={active ? "active" : "inactive"} animate={active ? "active" : "inactive"}
initial="inactive" initial="inactive"
> >
<motion.div <motion.div
variants={settingsPageVariant} variants={settingsPageVariant}
className="content-card flex flex-row flex-wrap justify-around" className="content-card flex flex-row flex-wrap justify-around"
> >
<div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center"> <div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center">
<div class="text-3xl">{t("settings.languages.title")}</div> <div class="text-3xl">{t("settings.languages.title")}</div>
<div class="text-md">{t("settings.languages.subtitle")}</div> <div class="text-md">{t("settings.languages.subtitle")}</div>
<Dropdown <Dropdown
storageKey="i18nextLng" storageKey="i18nextLng"
options={languages} options={languages}
refresh={true} refresh={true}
/> />
</div> </div>
</motion.div> </motion.div>
</motion.div> </motion.div>
); );
}; };
export default Misc; export default Misc;

View file

@ -1,49 +1,53 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { tabContentVariant, settingsPageVariant } from "./Variants"; import { tabContentVariant, settingsPageVariant } from "./Variants";
import Dropdown from "./Dropdown"; import Dropdown from "./Dropdown";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const Proxy = ({ id, active }) => { const Proxy = ({ id, active }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const engines = [ const engines = [
{ id: "automatic", label: t("settings.proxy.automatic") }, { id: "automatic", label: t("settings.proxy.automatic") },
{ id: "ultraviolet", label: "Ultraviolet" }, { id: "ultraviolet", label: "Ultraviolet" },
{ id: "rammerhead", label: "Rammerhead" }, { id: "rammerhead", label: "Rammerhead" },
{ id: "dynamic", label: "Dynamic " + t("settings.proxy.buggyWarning") } { id: "dynamic", label: "Dynamic " + t("settings.proxy.buggyWarning") }
]; ];
const proxyModes = [ const proxyModes = [
{ id: "direct", label: t("settings.proxymodes.direct") }, { id: "direct", label: t("settings.proxymodes.direct") },
{ id: "embed", label: t("settings.proxymodes.embed") }, { id: "embed", label: t("settings.proxymodes.embed") },
{ id: "aboutblank", label: t("settings.proxymodes.aboutblank") } { id: "aboutblank", label: t("settings.proxymodes.aboutblank") }
]; ];
return ( return (
<motion.div <motion.div
role="tabpanel" role="tabpanel"
id={id} id={id}
className="tab-content" className="tab-content"
variants={tabContentVariant} variants={tabContentVariant}
animate={active ? "active" : "inactive"} animate={active ? "active" : "inactive"}
initial="inactive" initial="inactive"
> >
<motion.div <motion.div
variants={settingsPageVariant} variants={settingsPageVariant}
className="content-card flex flex-row flex-wrap justify-left w-full gap-4" className="content-card justify-left flex w-full flex-row flex-wrap gap-4"
> >
<div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center"> <div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center">
<div class="p-2 text-3xl">{t("settings.proxy.title")}</div> <div class="p-2 text-3xl">{t("settings.proxy.title")}</div>
<div class="text-md p-4">{t("settings.proxy.subtitle")}</div> <div class="text-md p-4">{t("settings.proxy.subtitle")}</div>
<Dropdown storageKey="proxy" options={engines} refresh={false} /> <Dropdown storageKey="proxy" options={engines} refresh={false} />
</div> </div>
<div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center"> <div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center">
<div class="p-2 text-3xl">{t("settings.proxymodes.title")}</div> <div class="p-2 text-3xl">{t("settings.proxymodes.title")}</div>
<div class="text-md p-4">{t("settings.proxymodes.subtitle")}</div> <div class="text-md p-4">{t("settings.proxymodes.subtitle")}</div>
<Dropdown storageKey="proxyMode" options={proxyModes} refresh={false} /> <Dropdown
</div> storageKey="proxyMode"
</motion.div> options={proxyModes}
</motion.div> refresh={false}
); />
}; </div>
export default Proxy; </motion.div>
</motion.div>
);
};
export default Proxy;

View file

@ -1,106 +1,106 @@
import { useState, useEffect } from "preact/hooks"; import { useState, useEffect } from "preact/hooks";
import cn from "classnames"; import cn from "classnames";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import "./styles.css"; import "./styles.css";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const tabVariant = { const tabVariant = {
active: { active: {
width: "55%", width: "55%",
transition: { transition: {
type: "tween", type: "tween",
duration: 0.4 duration: 0.4
} }
}, },
inactive: { inactive: {
width: "15%", width: "15%",
transition: { transition: {
type: "tween", type: "tween",
duration: 0.4 duration: 0.4
} }
} }
}; };
const tabTextVariant = { const tabTextVariant = {
active: { active: {
opacity: 1, opacity: 1,
x: 0, x: 0,
display: "block", display: "block",
transition: { transition: {
type: "tween", type: "tween",
duration: 0.3, duration: 0.3,
delay: 0.3 delay: 0.3
} }
}, },
inactive: { inactive: {
opacity: 0, opacity: 0,
x: -30, x: -30,
transition: { transition: {
type: "tween", type: "tween",
duration: 0.3, duration: 0.3,
delay: 0.1 delay: 0.1
}, },
transitionEnd: { display: "none" } transitionEnd: { display: "none" }
} }
}; };
const TabComponent = ({ tabs, defaultIndex = 0 }) => { const TabComponent = ({ tabs, defaultIndex = 0 }) => {
const [activeTabIndex, setActiveTabIndex] = useState(defaultIndex); const [activeTabIndex, setActiveTabIndex] = useState(defaultIndex);
useEffect(() => { useEffect(() => {
document.documentElement.style.setProperty( document.documentElement.style.setProperty(
"--active-color", "--active-color",
tabs[activeTabIndex].color tabs[activeTabIndex].color
); );
}, [activeTabIndex, tabs]); }, [activeTabIndex, tabs]);
// Default to a tab based on the URL hash value // Default to a tab based on the URL hash value
useEffect(() => { useEffect(() => {
const tabFromHash = tabs.findIndex( const tabFromHash = tabs.findIndex(
(tab) => `#${tab.id}` === window.location.hash (tab) => `#${tab.id}` === window.location.hash
); );
setActiveTabIndex(tabFromHash !== -1 ? tabFromHash : defaultIndex); setActiveTabIndex(tabFromHash !== -1 ? tabFromHash : defaultIndex);
}, [tabs, defaultIndex]); }, [tabs, defaultIndex]);
const onTabClick = (index) => { const onTabClick = (index) => {
setActiveTabIndex(index); setActiveTabIndex(index);
}; };
const { t } = useTranslation() const { t } = useTranslation();
return ( return (
<div class="flex flex-col items-center w-full h-full"> <div class="flex h-full w-full flex-col items-center">
<div className="container h-full w-full"> <div className="container h-full w-full">
<div className="tabs-component"> <div className="tabs-component">
<ul className="tab-links" role="tablist"> <ul className="tab-links" role="tablist">
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<motion.li <motion.li
key={tab.id} key={tab.id}
className={cn("tab", { active: activeTabIndex === index })} className={cn("tab", { active: activeTabIndex === index })}
role="presentation" role="presentation"
variants={tabVariant} variants={tabVariant}
animate={activeTabIndex === index ? "active" : "inactive"} animate={activeTabIndex === index ? "active" : "inactive"}
> >
<a href={`#${tab.id}`} onClick={() => onTabClick(index)}> <a href={`#${tab.id}`} onClick={() => onTabClick(index)}>
{tab.icon} {tab.icon}
<motion.span variants={tabTextVariant}> <motion.span variants={tabTextVariant}>
{t(tab.title)} {t(tab.title)}
</motion.span> </motion.span>
</a> </a>
</motion.li> </motion.li>
))} ))}
</ul> </ul>
{tabs.map((tab, index) => ( {tabs.map((tab, index) => (
<tab.content <tab.content
key={tab.id} key={tab.id}
id={`${tab.id}-content`} id={`${tab.id}-content`}
active={activeTabIndex === index} active={activeTabIndex === index}
/> />
))} ))}
</div> </div>
</div> </div>
</div> </div>
); );
}; };
export default TabComponent; export default TabComponent;

View file

@ -1,19 +1,19 @@
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import { tabContentVariant, settingsPageVariant } from "./Variants"; import { tabContentVariant, settingsPageVariant } from "./Variants";
const TabSettings = ({ id, active }) => ( const TabSettings = ({ id, active }) => (
<motion.div <motion.div
role="tabpanel" role="tabpanel"
id={id} id={id}
className="tab-content" className="tab-content"
variants={tabContentVariant} variants={tabContentVariant}
animate={active ? "active" : "inactive"} animate={active ? "active" : "inactive"}
initial="inactive" initial="inactive"
> >
<motion.div variants={settingsPageVariant} className="content-card"> <motion.div variants={settingsPageVariant} className="content-card">
<h1>Tab settings</h1> <h1>Tab settings</h1>
</motion.div> </motion.div>
</motion.div> </motion.div>
); );
export default TabSettings; export default TabSettings;

View file

@ -1,30 +1,30 @@
const tabContentVariant = { const tabContentVariant = {
active: { active: {
display: "block", display: "block",
transition: { transition: {
staggerChildren: 0.2 staggerChildren: 0.2
} }
}, },
inactive: { inactive: {
display: "none" display: "none"
} }
}; };
const settingsPageVariant = { const settingsPageVariant = {
active: { active: {
opacity: 1, opacity: 1,
y: 0, y: 0,
transition: { transition: {
duration: 0.5 duration: 0.5
} }
}, },
inactive: { inactive: {
opacity: 0, opacity: 0,
y: 10, y: 10,
transition: { transition: {
duration: 0.5 duration: 0.5
} }
} }
}; };
export { settingsPageVariant, tabContentVariant }; export { settingsPageVariant, tabContentVariant };

View file

@ -1,15 +1,15 @@
import TabComponent from "./TabComponent"; import TabComponent from "./TabComponent";
import { HeaderRoute } from "../../components/HeaderRoute"; import { HeaderRoute } from "../../components/HeaderRoute";
import tabs from "./tabs"; import tabs from "./tabs";
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet";
export function Settings() { export function Settings() {
return ( return (
<HeaderRoute> <HeaderRoute>
<Helmet> <Helmet>
<title>Settings</title> <title>Settings</title>
</Helmet> </Helmet>
<TabComponent tabs={tabs} /> <TabComponent tabs={tabs} />
</HeaderRoute> </HeaderRoute>
); );
} }

View file

@ -1,128 +1,128 @@
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
:root { :root {
--white: #fff; --white: #fff;
--black: #333; --black: #333;
--active-color: #f1f1f1; --active-color: #f1f1f1;
--border-radius: 40px; --border-radius: 40px;
} }
body { body {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
font-family: Arial, Helvetica, sans-serif; font-family: Arial, Helvetica, sans-serif;
background: var(--active-color); background: var(--active-color);
transition: background 1.5s ease; transition: background 1.5s ease;
} }
img { img {
max-width: 100%; max-width: 100%;
vertical-align: middle; vertical-align: middle;
} }
.tabs-component { .tabs-component {
width: 100%; width: 100%;
height: 100%; height: 100%;
margin: auto; margin: auto;
padding: 40px; padding: 40px;
border-radius: var(--border-radius); border-radius: var(--border-radius);
} }
.tab-links { .tab-links {
padding: 0; padding: 0;
margin: 0 auto 20px; margin: 0 auto 20px;
list-style: none; list-style: none;
max-width: 400px; max-width: 400px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.tab { .tab {
position: relative; position: relative;
} }
.tab a { .tab a {
text-decoration: none; text-decoration: none;
color: var(--black); color: var(--black);
} }
.tab::before { .tab::before {
content: ""; content: "";
width: 100%; width: 100%;
height: 100%; height: 100%;
opacity: 0.2; opacity: 0.2;
position: absolute; position: absolute;
border-radius: var(--border-radius); border-radius: var(--border-radius);
background: none; background: none;
transition: background 0.5s ease; transition: background 0.5s ease;
} }
.tab svg { .tab svg {
height: 30px; height: 30px;
width: 30px; width: 30px;
min-width: 30px; min-width: 30px;
fill: var(--black); fill: var(--black);
transition: fill 0.5s ease; transition: fill 0.5s ease;
} }
.tab.active::before { .tab.active::before {
background: var(--active-color); background: var(--active-color);
} }
.tab span { .tab span {
font-weight: 700; font-weight: 700;
margin-left: 10px; margin-left: 10px;
transition: color 0.5s ease; transition: color 0.5s ease;
} }
.tab.active span { .tab.active span {
color: var(--active-color); color: var(--active-color);
} }
.tab.active svg { .tab.active svg {
fill: var(--active-color); fill: var(--active-color);
} }
.tab a { .tab a {
padding: 16px; padding: 16px;
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 20px; font-size: 20px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
} }
.cards { .cards {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
margin-top: 40px; margin-top: 40px;
} }
.content-card { .content-card {
width: 48%; width: 48%;
margin-bottom: 26px; margin-bottom: 26px;
} }
.content-card .info::after { .content-card .info::after {
content: ""; content: "";
display: block; display: block;
width: 100%; width: 100%;
height: 3px; height: 3px;
bottom: -5px; bottom: -5px;
background: var(--active-color); background: var(--active-color);
opacity: 0.5; opacity: 0.5;
} }
.content-card img { .content-card img {
border-radius: 6px; border-radius: 6px;
} }
.content-card h3 { .content-card h3 {
margin: 0 0 5px; margin: 0 0 5px;
} }
.content-card .info { .content-card .info {
padding: 10px 0; padding: 10px 0;
} }

View file

@ -1,42 +1,42 @@
import Proxy from "./Proxy"; import Proxy from "./Proxy";
import TabSettings from "./TabSettings"; import TabSettings from "./TabSettings";
import Misc from "./Misc"; import Misc from "./Misc";
import Customization from "./Customization"; import Customization from "./Customization";
import { GoBrowser } from "react-icons/go"; import { GoBrowser } from "react-icons/go";
import { AiOutlineLaptop } from "react-icons/ai"; import { AiOutlineLaptop } from "react-icons/ai";
import { FaPalette } from "react-icons/fa"; import { FaPalette } from "react-icons/fa";
import { FaGear } from "react-icons/fa6"; import { FaGear } from "react-icons/fa6";
const tabs = [ const tabs = [
{ {
title: "settings.tabs.proxy", title: "settings.tabs.proxy",
id: "proxy", id: "proxy",
icon: <AiOutlineLaptop />, icon: <AiOutlineLaptop />,
color: "#5d5dff", color: "#5d5dff",
content: Proxy content: Proxy
}, },
{ {
title: "settings.tabs.tab", title: "settings.tabs.tab",
id: "tab", id: "tab",
icon: <GoBrowser />, icon: <GoBrowser />,
color: "#67bb67", color: "#67bb67",
content: TabSettings content: TabSettings
}, },
{ {
title: "settings.tabs.custom", title: "settings.tabs.custom",
id: "custom", id: "custom",
icon: <FaPalette />, icon: <FaPalette />,
color: "#63a7c7", color: "#63a7c7",
content: Customization content: Customization
}, },
{ {
title: "settings.tabs.misc", title: "settings.tabs.misc",
id: "misc", id: "misc",
icon: <FaGear />, icon: <FaGear />,
color: "#f56868", color: "#f56868",
content: Misc content: Misc
} }
]; ];
export default tabs; export default tabs;

View file

@ -1,26 +1,26 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "preact-router"; import { Link } from "preact-router";
import { HeaderRoute } from "../components/HeaderRoute"; import { HeaderRoute } from "../components/HeaderRoute";
export function NotFound() { export function NotFound() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<HeaderRoute> <HeaderRoute>
<section class="h-full"> <section class="h-full">
<div class="flex h-full flex-col items-center justify-center"> <div class="flex h-full flex-col items-center justify-center">
<img src="/404.png" class="h-72"></img> <img src="/404.png" class="h-72"></img>
<div class="flex flex-col items-center p-6"> <div class="flex flex-col items-center p-6">
<p class="font-roboto text-4xl font-bold">{t("404.text")}</p> <p class="font-roboto text-4xl font-bold">{t("404.text")}</p>
<span class="font-roboto text-3xl">404</span> <span class="font-roboto text-3xl">404</span>
</div> </div>
<Link href="/"> <Link href="/">
<button class="font-roboto h-14 w-44 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none"> <button class="font-roboto h-14 w-44 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none">
{t("404.return")} {t("404.return")}
</button> </button>
</Link> </Link>
</div> </div>
</section> </section>
</HeaderRoute> </HeaderRoute>
); );
} }

View file

@ -1,29 +1,32 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { HeaderRoute } from "../components/HeaderRoute"; import { HeaderRoute } from "../components/HeaderRoute";
export function DiscordPage() { export function DiscordPage() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<HeaderRoute> <HeaderRoute>
<section class="h-full"> <section class="h-full">
<div class="flex h-full flex-col items-center justify-center"> <div class="flex h-full flex-col items-center justify-center">
<div class="flex flex-col items-center p-6"> <div class="flex flex-col items-center p-6">
<p class="font-roboto text-4xl font-bold">{t("discord.title")}</p> <p class="font-roboto text-4xl font-bold">{t("discord.title")}</p>
<span class="font-roboto text-3xl">{t("discord.sub")}</span> <span class="font-roboto text-3xl">{t("discord.sub")}</span>
</div> </div>
<a href="https://discord.gg/unblocker" class="p-6"> <a href="https://discord.gg/unblocker" class="p-6">
<button class="font-roboto h-14 w-56 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none"> <button class="font-roboto h-14 w-56 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none">
{t("discord.button1")} {t("discord.button1")}
</button> </button>
</a> </a>
<a href="${window.location.href = '/go/' + encodeURIComponent(https://discord.gg/unblocker)}" class="p-6"> <a
<button class="font-roboto h-14 w-56 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none"> href="${window.location.href = '/go/' + encodeURIComponent(https://discord.gg/unblocker)}"
{t("discord.button2")} class="p-6"
</button> >
</a> <button class="font-roboto h-14 w-56 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none">
</div> {t("discord.button2")}
</section> </button>
</HeaderRoute> </a>
); </div>
} </section>
</HeaderRoute>
);
}

View file

@ -1,5 +1,5 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;700;900&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;700;900&display=swap");
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;

View file

@ -1,28 +1,28 @@
@import url("https://fonts.googleapis.com/css2?family=Dongle&family=Roboto:wght@100&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Dongle&family=Roboto:wght@100&display=swap");
:root { :root {
--background-primary: #191724; --background-primary: #191724;
--background-lighter: #16121f; --background-lighter: #16121f;
--navbar-color: #26233a; --navbar-color: #26233a;
--navbar-height: 60px; --navbar-height: 60px;
--navbar-text-color: #7967dd; --navbar-text-color: #7967dd;
--navbar-link-color: #e0def4; --navbar-link-color: #e0def4;
--navbar-link-hover-color: gray; --navbar-link-hover-color: gray;
--navbar-font: "Roboto"; --navbar-font: "Roboto";
--input-text-color: #e0def4; --input-text-color: #e0def4;
--input-placeholder-color: white; --input-placeholder-color: white;
--input-background-color: #1f1d2e; --input-background-color: #1f1d2e;
--input-border-color: #eb6f92; --input-border-color: #eb6f92;
--input-border-size: 1.3px; --input-border-size: 1.3px;
--navbar-logo-filter: none; --navbar-logo-filter: none;
--dropdown-option-hover-color: #312a49; --dropdown-option-hover-color: #312a49;
} }
.font-inter { .font-inter {
font-family: "Inter", sans-serif; font-family: "Inter", sans-serif;
font-weight: 300; font-weight: 300;
} }
.font-roboto { .font-roboto {
font-family: "Roboto"; font-family: "Roboto";
} }

View file

@ -1,176 +1,177 @@
export function RammerheadEncode(baseUrl) { // Hellhead export function RammerheadEncode(baseUrl) {
const mod = (n, m) => ((n % m) + m) % m; // Hellhead
const baseDictionary = const mod = (n, m) => ((n % m) + m) % m;
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-"; const baseDictionary =
const shuffledIndicator = "_rhs"; "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-";
const generateDictionary = function () { const shuffledIndicator = "_rhs";
let str = ""; const generateDictionary = function () {
const split = baseDictionary.split(""); let str = "";
while (split.length > 0) { const split = baseDictionary.split("");
str += split.splice(Math.floor(Math.random() * split.length), 1)[0]; while (split.length > 0) {
} str += split.splice(Math.floor(Math.random() * split.length), 1)[0];
return str; }
}; return str;
interface StrShuffler { };
dictionary: any; interface StrShuffler {
} dictionary: any;
class StrShuffler { }
constructor(dictionary = generateDictionary()) { class StrShuffler {
this.dictionary = dictionary; constructor(dictionary = generateDictionary()) {
} this.dictionary = dictionary;
shuffle(str) { }
if (str.startsWith(shuffledIndicator)) { shuffle(str) {
return str; if (str.startsWith(shuffledIndicator)) {
} return str;
let shuffledStr = ""; }
for (let i = 0; i < str.length; i++) { let shuffledStr = "";
const char = str.charAt(i); for (let i = 0; i < str.length; i++) {
const idx = baseDictionary.indexOf(char); const char = str.charAt(i);
if (char === "%" && str.length - i >= 3) { const idx = baseDictionary.indexOf(char);
shuffledStr += char; if (char === "%" && str.length - i >= 3) {
shuffledStr += str.charAt(++i); shuffledStr += char;
shuffledStr += str.charAt(++i); shuffledStr += str.charAt(++i);
} else if (idx === -1) { shuffledStr += str.charAt(++i);
shuffledStr += char; } else if (idx === -1) {
} else { shuffledStr += char;
shuffledStr += this.dictionary.charAt( } else {
mod(idx + i, baseDictionary.length) shuffledStr += this.dictionary.charAt(
); mod(idx + i, baseDictionary.length)
} );
} }
return shuffledIndicator + shuffledStr; }
} return shuffledIndicator + shuffledStr;
unshuffle(str) { }
if (!str.startsWith(shuffledIndicator)) { unshuffle(str) {
return str; if (!str.startsWith(shuffledIndicator)) {
} return str;
}
str = str.slice(shuffledIndicator.length);
str = str.slice(shuffledIndicator.length);
let unshuffledStr = "";
for (let i = 0; i < str.length; i++) { let unshuffledStr = "";
const char = str.charAt(i); for (let i = 0; i < str.length; i++) {
const idx = this.dictionary.indexOf(char); const char = str.charAt(i);
if (char === "%" && str.length - i >= 3) { const idx = this.dictionary.indexOf(char);
unshuffledStr += char; if (char === "%" && str.length - i >= 3) {
unshuffledStr += str.charAt(++i); unshuffledStr += char;
unshuffledStr += str.charAt(++i); unshuffledStr += str.charAt(++i);
} else if (idx === -1) { unshuffledStr += str.charAt(++i);
unshuffledStr += char; } else if (idx === -1) {
} else { unshuffledStr += char;
unshuffledStr += baseDictionary.charAt( } else {
mod(idx - i, baseDictionary.length) unshuffledStr += baseDictionary.charAt(
); mod(idx - i, baseDictionary.length)
} );
} }
return unshuffledStr; }
} return unshuffledStr;
} }
function get(url, callback, shush = false) { }
var request = new XMLHttpRequest(); function get(url, callback, shush = false) {
request.open("GET", url, true); var request = new XMLHttpRequest();
request.send(); request.open("GET", url, true);
request.send();
request.onerror = function () {
if (!shush) console.log("Cannot communicate with the server"); request.onerror = function () {
}; if (!shush) console.log("Cannot communicate with the server");
request.onload = function () { };
if (request.status === 200) { request.onload = function () {
callback(request.responseText); if (request.status === 200) {
} else { callback(request.responseText);
if (!shush) } else {
console.log( if (!shush)
'unexpected server response to not match "200". Server says "' + console.log(
request.responseText + 'unexpected server response to not match "200". Server says "' +
'"' request.responseText +
); '"'
} );
}; }
} };
var api = { }
newsession(callback) { var api = {
get("/newsession", callback); newsession(callback) {
}, get("/newsession", callback);
sessionexists(id, callback) { },
get("/sessionexists?id=" + encodeURIComponent(id), function (res) { sessionexists(id, callback) {
if (res === "exists") return callback(true); get("/sessionexists?id=" + encodeURIComponent(id), function (res) {
if (res === "not found") return callback(false); if (res === "exists") return callback(true);
console.log("unexpected response from server. received" + res); if (res === "not found") return callback(false);
}); console.log("unexpected response from server. received" + res);
}, });
shuffleDict(id, callback) { },
console.log("Shuffling", id); shuffleDict(id, callback) {
get("/api/shuffleDict?id=" + encodeURIComponent(id), function (res) { console.log("Shuffling", id);
callback(JSON.parse(res)); get("/api/shuffleDict?id=" + encodeURIComponent(id), function (res) {
}); callback(JSON.parse(res));
} });
}; }
var localStorageKey = "rammerhead_sessionids"; };
var localStorageKeyDefault = "rammerhead_default_sessionid"; var localStorageKey = "rammerhead_sessionids";
var sessionIdsStore = { var localStorageKeyDefault = "rammerhead_default_sessionid";
get() { var sessionIdsStore = {
var rawData = localStorage.getItem(localStorageKey); get() {
if (!rawData) return []; var rawData = localStorage.getItem(localStorageKey);
try { if (!rawData) return [];
var data = JSON.parse(rawData); try {
if (!Array.isArray(data)) throw "getout"; var data = JSON.parse(rawData);
return data; if (!Array.isArray(data)) throw "getout";
} catch (e) { return data;
return []; } catch (e) {
} return [];
}, }
set(data) { },
if (!data || !Array.isArray(data)) throw new TypeError("must be array"); set(data) {
localStorage.setItem(localStorageKey, JSON.stringify(data)); if (!data || !Array.isArray(data)) throw new TypeError("must be array");
}, localStorage.setItem(localStorageKey, JSON.stringify(data));
getDefault() { },
var sessionId = localStorage.getItem(localStorageKeyDefault); getDefault() {
if (sessionId) { var sessionId = localStorage.getItem(localStorageKeyDefault);
var data = sessionIdsStore.get(); if (sessionId) {
data.filter(function (e) { var data = sessionIdsStore.get();
return e.id === sessionId; data.filter(function (e) {
}); return e.id === sessionId;
if (data.length) return data[0]; });
} if (data.length) return data[0];
return null; }
}, return null;
setDefault(id) { },
localStorage.setItem(localStorageKeyDefault, id); setDefault(id) {
} localStorage.setItem(localStorageKeyDefault, id);
}; }
function addSession(id) { };
var data = sessionIdsStore.get(); function addSession(id) {
data.unshift({ id: id, createdOn: new Date().toLocaleString() }); var data = sessionIdsStore.get();
sessionIdsStore.set(data); data.unshift({ id: id, createdOn: new Date().toLocaleString() });
} sessionIdsStore.set(data);
function getSessionId() { }
return new Promise((resolve) => { function getSessionId() {
var id = localStorage.getItem("session-string"); return new Promise((resolve) => {
api.sessionexists(id, function (value) { var id = localStorage.getItem("session-string");
if (!value) { api.sessionexists(id, function (value) {
console.log("Session validation failed"); if (!value) {
api.newsession(function (id) { console.log("Session validation failed");
addSession(id); api.newsession(function (id) {
localStorage.setItem("session-string", id); addSession(id);
console.log(id); localStorage.setItem("session-string", id);
console.log("^ new id"); console.log(id);
resolve(id); console.log("^ new id");
}); resolve(id);
} else { });
resolve(id); } else {
} resolve(id);
}); }
}); });
} });
var ProxyHref; }
var ProxyHref;
return getSessionId().then((id) => {
return new Promise((resolve, reject) => { return getSessionId().then((id) => {
api.shuffleDict(id, function (shuffleDict) { return new Promise((resolve, reject) => {
var shuffler = new StrShuffler(shuffleDict); api.shuffleDict(id, function (shuffleDict) {
ProxyHref = "/" + id + "/" + shuffler.shuffle(baseUrl); var shuffler = new StrShuffler(shuffleDict);
resolve(ProxyHref); ProxyHref = "/" + id + "/" + shuffler.shuffle(baseUrl);
}); resolve(ProxyHref);
}); });
}); });
} });
}

View file

@ -1,10 +1,10 @@
export function searchUtil(input: string, template: string) { export function searchUtil(input: string, template: string) {
try { try {
return new URL(input).toString(); return new URL(input).toString();
} catch (error) {} } catch (error) {}
try { try {
const url = new URL(`http://${input}`); const url = new URL(`http://${input}`);
if (url.hostname.includes(".")) return url.toString(); if (url.hostname.includes(".")) return url.toString();
} catch (error) {} } catch (error) {}
return template.replace("%s", encodeURIComponent(input)); return template.replace("%s", encodeURIComponent(input));
} }

View file

@ -1,21 +1,20 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: { theme: {
colors: { colors: {
primary: "var(--background-primary)", primary: "var(--background-primary)",
lighter: "var(--background-lighter)", lighter: "var(--background-lighter)",
"navbar-text-color": "var(--navbar-text-color)", "navbar-text-color": "var(--navbar-text-color)",
"navbar-color": "var(--navbar-color)", "navbar-color": "var(--navbar-color)",
"text-color": "var(--navbar-link-color)", "text-color": "var(--navbar-link-color)",
"text-hover-color": "var(--navbar-link-hover-color)", "text-hover-color": "var(--navbar-link-hover-color)",
input: "var(--input-background-color)", input: "var(--input-background-color)",
"input-text": "var(--input-text-color)", "input-text": "var(--input-text-color)",
"input-border-color": "var(--input-border-color)", "input-border-color": "var(--input-border-color)",
"dropdown-option-hover-color": "var(--dropdown-option-hover-color)", "dropdown-option-hover-color": "var(--dropdown-option-hover-color)"
}, },
extend: {} extend: {}
}, },
plugins: [] plugins: []
}; };

View file

@ -1,12 +1,12 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2020", "target": "ES2020",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "preact", "jsxImportSource": "preact",
"skipLibCheck": true, "skipLibCheck": true,
"paths": {} "paths": {}
} }
} }

View file

@ -1,31 +1,31 @@
import million from "million/compiler"; import million from "million/compiler";
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import preact from "@preact/preset-vite"; import preact from "@preact/preset-vite";
import { viteStaticCopy } from "vite-plugin-static-copy"; import { viteStaticCopy } from "vite-plugin-static-copy";
import { uvPath } from "@titaniumnetwork-dev/ultraviolet"; import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
viteStaticCopy({ viteStaticCopy({
targets: [ targets: [
{ {
// .replace fixes weird paths on Windows // .replace fixes weird paths on Windows
src: `${uvPath}/uv.*.js`.replace(/\\/g, "/"), src: `${uvPath}/uv.*.js`.replace(/\\/g, "/"),
dest: "uv", dest: "uv",
overwrite: false overwrite: false
} }
] ]
}), }),
million.vite({ auto: true }), million.vite({ auto: true }),
preact() preact()
], ],
server: { server: {
proxy: { proxy: {
"/bare": { "/bare": {
target: "http://localhost:8080/", target: "http://localhost:8080/",
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/bare/, "") rewrite: (path) => path.replace(/^\/bare/, "")
} }
} }
} }
}); });