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,
"endOfLine": "crlf",
"tabWidth": 2,
"useTabs": false,
"trailingComma": "none",
"plugins": ["prettier-plugin-tailwindcss"]
}
{
"singleQuote": false,
"endOfLine": "crlf",
"tabWidth": 2,
"useTabs": false,
"trailingComma": "none",
"plugins": ["prettier-plugin-tailwindcss"]
}

24
.vscode/settings.json vendored
View file

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

View file

@ -1,7 +1,8 @@
# Nebula rewrite
`npm i -g pnpm tsx`
`git clone https://github.com/NebulaServices/Nebula.git`
`git switch rewrite`
`pnpm i`
readme meant for devs, will be changed before being merged into main
# Nebula rewrite
`npm i -g pnpm tsx`
`git clone https://github.com/NebulaServices/Nebula.git`
`git switch rewrite`
`pnpm i`
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";
declare module "framer-motion" {
export interface AnimatePresenceProps {
children?: React.ReactNode;
}
}
import * as React from "preact/compat";
declare module "framer-motion" {
export interface AnimatePresenceProps {
children?: React.ReactNode;
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

172
server.ts
View file

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

View file

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

View file

@ -1,13 +1,23 @@
import { motion } from "framer-motion"
import { IframeHeader } from "./IframeHeader"
export function Iframe(props: { url: string }) {
return (
<>
<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 }} >
<iframe id="iframe" src={ props.url } className="w-full h-full border-none bg-primary" />
</motion.div>
</>
)
}
import { motion } from "framer-motion";
import { IframeHeader } from "./IframeHeader";
export function Iframe(props: { url: string }) {
return (
<>
<IframeHeader url={props.url} />
<motion.div
className="h-[calc(100%_-_4rem)] w-full bg-primary"
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 { useTranslation } from "react-i18next";
import { Link } from "preact-router";
import { RiPictureInPictureExitFill, RiFullscreenFill } from "react-icons/ri"
export function IframeHeader(props: { url: string }) {
const { t } = useTranslation()
const [showPopout, setShowPopout] = useState(false);
const [showFullScreen, setFullScreen] = useState(false);
if (showPopout) {
window.location.replace(props.url);
}
if (showFullScreen) {
document.getElementById("iframe").requestFullscreen();
setFullScreen(false);
}
return (
<div id="iframeNav" className="flex h-16 flex-row items-center justify-between bg-navbar-color px-4">
<Link href="/" class="w-1/2">
<div className="flex flex-row items-center">
<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>
</div>
</Link>
<div id="navItems" class="w-1/2">
<div className="flex flex-row items-center justify-end gap-3 mr-4">
<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)} />
<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)} />
</div>
</div>
</div>
)
};
import { useState } from "preact/hooks";
import { useTranslation } from "react-i18next";
import { Link } from "preact-router";
import { RiPictureInPictureExitFill, RiFullscreenFill } from "react-icons/ri";
export function IframeHeader(props: { url: string }) {
const { t } = useTranslation();
const [showPopout, setShowPopout] = useState(false);
const [showFullScreen, setFullScreen] = useState(false);
if (showPopout) {
window.location.replace(props.url);
}
if (showFullScreen) {
document.getElementById("iframe").requestFullscreen();
setFullScreen(false);
}
return (
<div
id="iframeNav"
className="flex h-16 flex-row items-center justify-between bg-navbar-color px-4"
>
<Link href="/" class="w-1/2">
<div className="flex flex-row items-center">
<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>
</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 { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import translationEN from "./locales/en.json";
import translationES from "./locales/es.json";
import translationJA from "./locales/ja.json";
const resources = {
en: {
translation: translationEN
},
es: {
translation: translationES
},
ja: {
translation: translationJA
}
};
i18n
.use(initReactI18next)
.use(LanguageDetector)
.init({
resources,
fallbackLng: "en",
interpolation: {
escapeValue: false
}
});
export default i18n;
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import translationEN from "./locales/en.json";
import translationES from "./locales/es.json";
import translationJA from "./locales/ja.json";
const resources = {
en: {
translation: translationEN
},
es: {
translation: translationES
},
ja: {
translation: translationJA
}
};
i18n
.use(initReactI18next)
.use(LanguageDetector)
.init({
resources,
fallbackLng: "en",
interpolation: {
escapeValue: false
}
});
export default i18n;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,41 +1,41 @@
import { motion } from "framer-motion";
import { tabContentVariant, settingsPageVariant } from "./Variants";
import Dropdown from "./Dropdown";
import { useTranslation } from "react-i18next";
const Misc = ({ id, active }) => {
const { t } = useTranslation();
const languages = [
{ id: "en-US", label: t("settings.languages.english") },
{ id: "es", label: t("settings.languages.spanish") },
{ id: "ja", label: t("settings.languages.japanese") }
];
return (
<motion.div
role="tabpanel"
id={id}
className="tab-content"
variants={tabContentVariant}
animate={active ? "active" : "inactive"}
initial="inactive"
>
<motion.div
variants={settingsPageVariant}
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="text-3xl">{t("settings.languages.title")}</div>
<div class="text-md">{t("settings.languages.subtitle")}</div>
<Dropdown
storageKey="i18nextLng"
options={languages}
refresh={true}
/>
</div>
</motion.div>
</motion.div>
);
};
export default Misc;
import { motion } from "framer-motion";
import { tabContentVariant, settingsPageVariant } from "./Variants";
import Dropdown from "./Dropdown";
import { useTranslation } from "react-i18next";
const Misc = ({ id, active }) => {
const { t } = useTranslation();
const languages = [
{ id: "en-US", label: t("settings.languages.english") },
{ id: "es", label: t("settings.languages.spanish") },
{ id: "ja", label: t("settings.languages.japanese") }
];
return (
<motion.div
role="tabpanel"
id={id}
className="tab-content"
variants={tabContentVariant}
animate={active ? "active" : "inactive"}
initial="inactive"
>
<motion.div
variants={settingsPageVariant}
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="text-3xl">{t("settings.languages.title")}</div>
<div class="text-md">{t("settings.languages.subtitle")}</div>
<Dropdown
storageKey="i18nextLng"
options={languages}
refresh={true}
/>
</div>
</motion.div>
</motion.div>
);
};
export default Misc;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,26 +1,26 @@
import { useTranslation } from "react-i18next";
import { Link } from "preact-router";
import { HeaderRoute } from "../components/HeaderRoute";
export function NotFound() {
const { t } = useTranslation();
return (
<HeaderRoute>
<section class="h-full">
<div class="flex h-full flex-col items-center justify-center">
<img src="/404.png" class="h-72"></img>
<div class="flex flex-col items-center p-6">
<p class="font-roboto text-4xl font-bold">{t("404.text")}</p>
<span class="font-roboto text-3xl">404</span>
</div>
<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">
{t("404.return")}
</button>
</Link>
</div>
</section>
</HeaderRoute>
);
}
import { useTranslation } from "react-i18next";
import { Link } from "preact-router";
import { HeaderRoute } from "../components/HeaderRoute";
export function NotFound() {
const { t } = useTranslation();
return (
<HeaderRoute>
<section class="h-full">
<div class="flex h-full flex-col items-center justify-center">
<img src="/404.png" class="h-72"></img>
<div class="flex flex-col items-center p-6">
<p class="font-roboto text-4xl font-bold">{t("404.text")}</p>
<span class="font-roboto text-3xl">404</span>
</div>
<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">
{t("404.return")}
</button>
</Link>
</div>
</section>
</HeaderRoute>
);
}

View file

@ -1,29 +1,32 @@
import { useTranslation } from "react-i18next";
import { HeaderRoute } from "../components/HeaderRoute";
export function DiscordPage() {
const { t } = useTranslation();
return (
<HeaderRoute>
<section class="h-full">
<div class="flex h-full flex-col items-center justify-center">
<div class="flex flex-col items-center p-6">
<p class="font-roboto text-4xl font-bold">{t("discord.title")}</p>
<span class="font-roboto text-3xl">{t("discord.sub")}</span>
</div>
<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">
{t("discord.button1")}
</button>
</a>
<a href="${window.location.href = '/go/' + encodeURIComponent(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">
{t("discord.button2")}
</button>
</a>
</div>
</section>
</HeaderRoute>
);
}
import { useTranslation } from "react-i18next";
import { HeaderRoute } from "../components/HeaderRoute";
export function DiscordPage() {
const { t } = useTranslation();
return (
<HeaderRoute>
<section class="h-full">
<div class="flex h-full flex-col items-center justify-center">
<div class="flex flex-col items-center p-6">
<p class="font-roboto text-4xl font-bold">{t("discord.title")}</p>
<span class="font-roboto text-3xl">{t("discord.sub")}</span>
</div>
<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">
{t("discord.button1")}
</button>
</a>
<a
href="${window.location.href = '/go/' + encodeURIComponent(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">
{t("discord.button2")}
</button>
</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");
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;700;900&display=swap");
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -1,28 +1,28 @@
@import url("https://fonts.googleapis.com/css2?family=Dongle&family=Roboto:wght@100&display=swap");
: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;
}
.font-inter {
font-family: "Inter", sans-serif;
font-weight: 300;
}
.font-roboto {
font-family: "Roboto";
}
@import url("https://fonts.googleapis.com/css2?family=Dongle&family=Roboto:wght@100&display=swap");
: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;
}
.font-inter {
font-family: "Inter", sans-serif;
font-weight: 300;
}
.font-roboto {
font-family: "Roboto";
}

View file

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

View file

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

View file

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

View file

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

View file

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