chore: merge branch dev to main

🎉
This commit is contained in:
afn 2023-06-05 22:35:20 -04:00 committed by GitHub
commit a9835a74bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
109 changed files with 5154 additions and 2448 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
RV_API_URL="https://releases.revanced.app"

View File

@ -1,5 +1,7 @@
.DS_Store
node_modules
/public
/images
/build
/.svelte-kit
/package

View File

@ -1,13 +0,0 @@
name: refresh
on:
schedule:
- cron: '0 */2 * * *'
workflow_dispatch:
jobs:
cron:
runs-on: ubuntu-latest
environment: production
steps:
- name: Refresh the site prerender with the latest API data
run: |
curl -X POST '${{ secrets.DEPLOY_HOOK }}'

2
.gitignore vendored
View File

@ -4,8 +4,6 @@ node_modules
/public
/.svelte-kit
/package
.env
.env.*
!.env.example
/_docs_src
/static/docs

View File

@ -1,6 +1,8 @@
.DS_Store
node_modules
/build
/testing-docs
/public
/.svelte-kit
/package
.env

View File

@ -2,5 +2,8 @@
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
}

View File

@ -6,17 +6,15 @@ set -e
cd "$(dirname "$0")"
docs_git_repo="${REVANCED_DOCS_REPO:-https://github.com/revanced/revanced-documentation.git}"
export REVANCED_DOCS_FOLDER="_docs_src"
git clone "$docs_git_repo" "$REVANCED_DOCS_FOLDER"
git clone "$docs_git_repo" "_docs_src"
# TODO: transform a tag links so this works properly...
#export REVANCED_DOCS_FOLDER="_docs_src/docs"
# Do this because the docs repo doesn't have any actual docs right now
cd "$REVANCED_DOCS_FOLDER"
cp README.md index.md
cd -
#git clone --recurse-submodules "$docs_git_repo" "_docs_src"
# Copy assets from docs repo to here.
mkdir -p static/docs
cp -r "$REVANCED_DOCS_FOLDER"/assets static/docs/assets || true
# mkdir -p static/docs
# cp -r "$REVANCED_DOCS_FOLDER"/assets static/docs/assets || true
npm run build

BIN
images/manager_two.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

3
netlify.toml Normal file
View File

@ -0,0 +1,3 @@
[build]
publish = "public/"
command = "npm run build"

2496
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,33 +7,38 @@
"package": "svelte-kit package",
"preview": "sirv ./public --no-clear",
"vite:preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check --plugin-search-dir=. . && eslint .",
"format": "prettier --write --plugin-search-dir=. ."
},
"devDependencies": {
"@sveltejs/adapter-static": "next",
"@sveltejs/kit": "next",
"@sveltejs/adapter-static": "^1.0.0",
"@sveltejs/kit": "^1.0.0",
"@types/marked": "^4.0.7",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.3.0",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte3": "^4.0.0",
"prettier": "^2.6.2",
"prettier-plugin-svelte": "^2.7.0",
"imagetools-core": "^3.2.3",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1",
"sass": "^1.55.0",
"sirv-cli": "^2.0.2",
"svelte": "^3.44.0",
"svelte-check": "^2.7.1",
"svelte-preprocess": "^4.10.6",
"tslib": "^2.3.1",
"typescript": "^4.7.4",
"vite": "^3.0.0"
"svelte": "^3.54.0",
"svelte-check": "^2.9.2",
"tslib": "^2.4.1",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vite-imagetools": "^4.0.11"
},
"type": "module",
"dependencies": {
"@tanstack/query-core": "^4.24.4",
"@tanstack/query-persist-client-core": "^4.24.4",
"@tanstack/query-sync-storage-persister": "^4.24.4",
"@tanstack/svelte-query": "^4.24.4",
"asciidoctor": "^2.2.6",
"marked": "^4.1.1"
}

View File

@ -1,82 +0,0 @@
import fs from 'fs';
// This code was stolen from https://github.com/sveltejs/kit/blob/master/packages/adapter-static/platforms.js
// sveltekit does things like this instead of actually just using ts for some reason.
/** @param {import('@sveltejs/kit').Builder} builder */
function vercel_routes(builder) {
/** @type {any[]} */
const routes = [
{
src: `/${builder.config.kit.appDir}/immutable/.+`,
headers: {
'cache-control': 'public, immutable, max-age=31536000'
}
}
];
// explicit redirects
for (const [src, redirect] of builder.prerendered.redirects) {
routes.push({
src,
headers: {
Location: redirect.location
},
status: redirect.status
});
}
// prerendered pages
for (const [src, page] of builder.prerendered.pages) {
routes.push({
src,
dest: `${builder.config.kit.appDir}/prerendered/${page.file}`
});
}
// implicit redirects (trailing slashes)
for (const [src] of builder.prerendered.pages) {
if (src !== '/') {
routes.push({
src: src.endsWith('/') ? src.slice(0, -1) : src + '/',
headers: {
location: src
},
status: 308
});
}
}
routes.push({
handle: 'filesystem'
});
return routes;
}
export function wrap(adapter, opts) {
if (!process.env.VERCEL) {
// we don't have to bother :)
return adapter(opts);
}
// Not exactly what adapter-static does, but it works.
opts.pages = '.vercel/output/static';
adapter = adapter(opts);
const adapt_fn = adapter.adapt;
adapter.adapt = async (builder) => {
const result = await adapt_fn(builder);
fs.writeFileSync(
'.vercel/output/config.json',
JSON.stringify({
version: 3,
routes: vercel_routes(builder)
})
);
return result;
}
return adapter;
}

View File

@ -1,118 +0,0 @@
@import url("https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800&display=swap");
* {
box-sizing: inherit;
margin: 0;
padding: 0;
font-family: "Manrope", sans-serif;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
html {
margin: 0;
padding: 0;
font-size: 100%;
box-sizing: border-box;
overflow-y: scroll;
}
body {
margin: 0;
padding: 0;
line-height: 1.4;
background-color: var(--bg-color);
}
html,
body {
max-width: 100%;
}
.wrapper {
margin-inline: auto;
width: min(90%, 100rem);
margin-top: 7rem;
}
:root {
--white: #fff;
--accent-color: #9fd5ff;
--accent-color-two: hsl(207, 65%, 90%);
--bg-color: hsl(240, 5%, 11%);
--grey-one: #252b31;
--grey-two: #28313b;
--grey-three: #373e4d;
--grey-four: #182244;
--grey-five: hsl(208, 30%, 75%);
--grey-six: hsla(223, 14%, 15%, 0.655);
--grey-seven: #535563;
--bezier-one: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
::selection {
background: var(--grey-six);
color: var(--white);
}
/*-----headings-----*/
h1 {
color: var(--white);
font-weight: 700;
font-size: 1.5rem;
}
h2 {
color: var(--grey-five);
font-weight: 400;
font-size: 1.4rem;
}
h3 {
color: var(--white);
font-weight: 500;
font-size: 1rem;
}
h4 {
color: var(--white);
font-weight: 500;
font-size: 1.25rem;
letter-spacing: 0.02rem;
}
h5 {
color: var(--white);
font-weight: 300;
font-size: 1rem;
letter-spacing: 0.02rem;
}
h6 {
color: var(--grey-five);
font-weight: 300;
font-size: 1rem;
letter-spacing: 0.02rem;
}
::-webkit-scrollbar {
width: 11px;
background-color: transparent;
}
::-webkit-scrollbar-thumb {
background-color: var(--grey-two);
background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--grey-three);
}
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid var(--grey-three);
}

12
src/app.d.ts vendored
View File

@ -9,3 +9,15 @@ declare namespace App {
// interface Session {}
// interface Stuff {}
}
declare module "*&picture" {
/**
* actual types
* taken from https://github.com/JonasKruckenberg/imagetools/issues/160#issuecomment-1009292026
* - code https://github.com/JonasKruckenberg/imagetools/blob/main/packages/core/src/output-formats.ts
* - docs https://github.com/JonasKruckenberg/imagetools/blob/main/docs/guide/getting-started.md#metadata
*/
const out;
export default out;
}

View File

@ -3,6 +3,7 @@
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/logo.svg" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>

160
src/app.scss Normal file
View File

@ -0,0 +1,160 @@
@import url("https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800&family=Source+Code+Pro:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
* {
box-sizing: inherit;
margin: 0;
padding: 0;
font-family: var(--main-font);
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
html {
margin: 0;
padding: 0;
font-size: 100%;
box-sizing: border-box;
overflow-y: scroll;
}
body {
margin: 0;
padding: 0;
background-color: var(--bg-color);
}
html,
body {
max-width: 100%;
}
.wrapper {
margin-inline: auto;
width: min(90%, 80rem);
margin-top: 7rem;
}
:root {
/* TODO properly name these */
--main-font: "Manrope", sans-serif;
--mono-font: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
--white: hsl(206, 100%, 94%);
--accent-color: hsl(206, 100%, 81%);
--accent-color-two: hsl(208, 75%, 82%);
--accent-low-opacity: hsla(205, 91%, 69%, 0.15);
--bg-color: hsl(252, 10%, 11%);
--grey-one: hsl(210, 14%, 17%);
--grey-two: hsl(212, 19%, 19%);
--grey-three: hsl(221, 17%, 26%);
--grey-four: hsl(226, 48%, 18%);
--grey-five: hsl(208, 30%, 75%);
--grey-six: hsl(230, 9%, 13%);
--grey-seven: hsl(240, 9%, 13.5%);
--grey-eight: hsla(207, 30%, 75%, 0.577);
--grey-nine: hsla(240, 6%, 7%, 0.3);
--grey-ten: hsl(230, 9.5%, 17.5%);
--grey-eleven: hsl(208, 10%, 40%);
--bezier-one: cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
::selection {
background-color: var(--accent-low-opacity);
}
/*-----headings-----*/
h1 {
color: var(--white);
line-height: 4rem;
font-size: 3.5rem;
font-weight: 700;
letter-spacing: -0.025em;
}
h2 {
color: var(--grey-five);
font-size: 2.5rem;
letter-spacing: -0.04rem;
font-weight: 600;
}
h3 {
font-size: 1.25rem;
color: var(--accent-color-two);
font-weight: 600;
}
h4 {
color: var(--accent-color-two);
font-weight: 400;
font-size: 1rem;
letter-spacing: 0.02rem;
line-height: 2rem;
}
h5 {
color: var(--grey-five);
font-weight: 400;
font-size: 0.9rem;
letter-spacing: 0.02rem;
}
h6 {
color: var(--grey-five);
font-weight: 500;
font-size: 0.85rem;
}
p {
color: var(--grey-five);
font-weight: 400;
font-size: 1rem;
letter-spacing: 0.02rem;
line-height: 1.75rem;
}
@media screen and (max-width: 768px) {
h1 {
font-size: 2.6rem;
line-height: 3.75rem;
}
h2 {
font-size: 2rem;
}
}
/*---------------*/
::-webkit-scrollbar {
width: 5px;
background-color: transparent;
}
::-webkit-scrollbar-thumb {
background-color: var(--accent-color);
background-clip: content-box;
border-radius: 100px;
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--grey-three);
}
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid var(--grey-three);
}
input {
padding: 1rem;
border-radius: 12px;
border: 1px solid var(--grey-three);
background-color: transparent;
color: var(--accent-color-two);
}
input:focus {
outline: 1px solid var(--accent-color);
}

View File

@ -20,7 +20,7 @@ function makeStore(): Readable<RouterEvent> {
});
} else {
// On client.
let current = new URL(location);
let current = new URL(location.href);
// Return store that responds to navigation events.
// The `navigating` store immediately "pushes" `null`.

View File

@ -1,51 +0,0 @@
import { browser } from "$app/environment";
import { dev_log } from "$lib/utils";
const CACHE_KEY_PREFIX = "revanced_api_cache_l1";
const L1_CACHE_VALIDITY = 5 * 60 * 1000; // 5 minutes
function l1_key_name(endpoint: string) {
return `${CACHE_KEY_PREFIX}:${endpoint}`;
}
// Get item from the cache
export function get(endpoint: string) {
if (!browser) {
return null;
}
const key_name = l1_key_name(endpoint);
const ls_data: { valid_until: number; data: any } | null = JSON.parse(localStorage.getItem(key_name));
if (ls_data === null || ls_data.valid_until <= Date.now()) {
dev_log("Cache", `missed "${endpoint}"`);
localStorage.removeItem(key_name);
return null;
}
dev_log("Cache", `hit "${endpoint}"`);
return ls_data.data;
}
// Update the cache
export function update(endpoint: string, data: any) {
if (!browser) {
return;
}
localStorage.setItem(l1_key_name(endpoint), JSON.stringify({
data,
valid_until: Date.now() + L1_CACHE_VALIDITY
}));
}
// Clear the cache
export function clear() {
for (const key of Object.keys(localStorage)) {
if (key.startsWith(CACHE_KEY_PREFIX)) {
localStorage.removeItem(key);
}
}
}

View File

@ -1,146 +1,90 @@
import type { Readable, Subscriber, Unsubscriber, Writable } from "svelte/store";
import { writable } from "svelte/store";
import { error } from "@sveltejs/kit";
import { prerendering, browser } from "$app/environment";
import * as settings from "./settings";
import * as cache from "./cache";
export class API<T> implements Readable<T> {
private store: Writable<T>;
// Note: transform function will not be called on cache hit.
private transform: (v: any) => T;
// True if we have or are about to request data from the possibly user-specified API.
private requested_from_api = false;
// If `transform_fn_or_key` is unspecified, the data will be returned from the API as is.
// If `transform_fn_or_key` is a function, the JSON data will pass through it.
// If `transform_fn_or_key` is a string, the JSON data will be assigned to a prop on an object.
// If `load_fn_fallback` is not specified, the load function will instead cause HTTP error 500 if the API request fails (it will always throw if prerendering).
constructor(public readonly endpoint: string, transform_fn_or_key?: ((v: any) => T) | string, private load_fn_fallback?: T) {
if (transform_fn_or_key === undefined) {
this.transform = (v) => v as T;
} else if (typeof transform_fn_or_key != "string") {
// `transform_fn_or_key` is function.
this.transform = transform_fn_or_key;
} else {
// `transform_fn_or_key` is string.
this.transform = (v) => {
let data = {};
data[transform_fn_or_key] = v;
return data as T;
};
}
}
private url(): string {
let url = `${settings.api_base_url()}/${this.endpoint}`;
if (prerendering) {
url += '?cacheBypass=';
// Just add some random stuff to the string. Doesn't really matter what we add.
// This is here to make sure we bypass the cache while prerendering.
for (let i = 0; i < 6; i++) {
url += Math.floor(Math.random() * 10).toString();
}
}
return url;
}
initialized() {
return this.store !== undefined;
}
// Initialize if needed
init(data: T) {
if (this.initialized()) {
return;
}
this.store = writable(data);
}
// Request data, transform, cache and initialize if necessary.
async request(fetch_fn = fetch): Promise<T> {
if (browser) {
this.requested_from_api = true;
}
// Try to get data from the cache.
let data = cache.get(this.endpoint);
if (data === null) {
// Fetch and transform data
const response = await fetch_fn(this.url());
data = this.transform(await response.json());
// Update the cache.
cache.update(this.endpoint, data);
}
// Initialize with the data. Applicable when page load function runs on client.
this.init(data);
// store_in_cache(data)...
return data;
}
// Implements the load function found in `+page/layout.ts` files.
page_load_impl() {
return async ({ fetch }) => {
try {
return await this.request(fetch);
} catch(e) {
console.error(e);
if (this.load_fn_fallback !== undefined && !prerendering) {
return this.load_fn_fallback;
}
throw error(500, "API Request Error");
}
};
}
// Implement Svelte store.
subscribe(run: Subscriber<T>, invalidate?: any): Unsubscriber {
if (!this.initialized()) {
// Make sure you call <api>.init() with data from the load() function of the page you are working on or a layout above it.
throw Error(`API "${this.endpoint}" has not been initialized yet.`);
}
// Make sure we have up-to-date data from the API.
if (!this.requested_from_api && browser) {
this.request().then(this.store.set);
}
return this.store.subscribe(run, invalidate);
}
}
import * as settings from './settings';
// API Endpoints
import type { Patch, Repository, Tool } from '../types';
import { dev_log } from "$lib/utils";
import type { Patch, Repository, Tool } from '$lib/types';
export type ContribData = { repositories: Repository[] };
export type ReposData = Repository[];
export type PatchesData = { patches: Patch[]; packages: string[] };
export type ToolsData = { tools: Tool[] };
export type ToolsData = { [repo: string]: Tool };
export const contributors = new API<ContribData>("contributors", undefined, { repositories: [] });
export const tools = new API<ToolsData>("tools", undefined, { tools: [] } )
async function get_json(endpoint: string) {
const url = `${settings.api_base_url()}/${endpoint}`;
return await fetch(url).then((r) => r.json());
}
export const patches = new API<PatchesData>("patches", patches => {
let packages: string[] = [];
async function repositories(): Promise<ReposData> {
return await get_json('contributors').then((json) => json.repositories);
}
// gets packages
for (let i = 0; i < patches.length; i++) {
patches[i].compatiblePackages.forEach((pkg: Patch) => {
let index = packages.findIndex((x) => x == pkg.name);
if (index === -1) {
packages.push(pkg.name);
}
async function tools(): Promise<ToolsData> {
const json = await get_json('tools');
// Make the data easier to work with.
let map: Map<string, Tool> = new Map();
for (const tool of json['tools']) {
const repo: string = tool.repository;
if (!map.has(repo)) {
map.set(repo, {
version: tool.version,
repository: repo,
// Just use the timestamp of the first one we find.
timestamp: tool.timestamp,
assets: []
});
}
let value = map.get(repo)!!;
value.assets.push({
name: tool.name,
size: tool.size,
url: tool.browser_download_url,
content_type: tool.content_type
});
map.set(repo, value);
}
return Object.fromEntries(map);
}
async function manager(): Promise<Tool> {
return await tools().then((data) => data['revanced/revanced-manager']);
}
async function patches(): Promise<PatchesData> {
const json = await get_json('patches');
const packagesWithCount: { [key: string]: number } = {};
// gets packages and patch count
for (let i = 0; i < json.length; i++) {
json[i].compatiblePackages.forEach((pkg: Patch) => {
packagesWithCount[pkg.name] = (packagesWithCount[pkg.name] || 0) + 1;
});
}
return { patches, packages };
});
// sort packages by patch count to get most relevant apps on top
const packages = Object.entries(packagesWithCount)
.sort((a, b) => b[1] - a[1])
.map((pkg) => pkg[0]);
return { patches: json, packages };
}
export const staleTime = 5 * 60 * 1000;
export const queries = {
manager: {
queryKey: ['manager'],
queryFn: manager,
staleTime
},
patches: {
queryKey: ['patches'],
queryFn: patches,
staleTime
},
repositories: {
queryKey: ['repositories'],
queryFn: repositories,
staleTime
}
};

View File

@ -1,22 +1,24 @@
import { browser } from "$app/environment";
import { browser } from '$app/environment';
import { RV_API_URL } from '$env/static/public';
const URL_KEY = "revanced_api_url";
const URL_KEY = 'revanced_api_url';
export const default_base_url = RV_API_URL;
// Get base URL
export function api_base_url(): string {
const default_base_url = "https://releases.revanced.app";
if (browser) {
return localStorage.getItem(URL_KEY) || default_base_url;
}
if (browser) {
return localStorage.getItem(URL_KEY) || default_base_url;
}
return default_base_url;
return default_base_url;
}
// (re)set base URL.
export function set_api_base_url(url?: string) {
if (!url) {
localStorage.removeItem(URL_KEY);
} else {
localStorage.setItem(URL_KEY, url);
}
if (!url) {
localStorage.removeItem(URL_KEY);
} else {
localStorage.setItem(URL_KEY, url);
}
}

188
src/layout/Footer.svelte Normal file
View File

@ -0,0 +1,188 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import { queries } from '$data/api';
import { friendlyName } from '$util/friendlyName';
import { createQuery } from '@tanstack/svelte-query';
import Query from '$lib/components/Query.svelte';
const query = createQuery(['repositories'], queries.repositories);
</script>
<!-- squiggly divider line -->
<svg aria-hidden="true" width="100%" height="8" fill="none" xmlns="http://www.w3.org/2000/svg" in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<pattern id="a" width="91" height="8" patternUnits="userSpaceOnUse">
<g clip-path="url(#clip0_2426_11367)">
<path
d="M114 4c-5.067 4.667-10.133 4.667-15.2 0S88.667-.667 83.6 4 73.467 8.667 68.4 4 58.267-.667 53.2 4 43.067 8.667 38 4 27.867-.667 22.8 4 12.667 8.667 7.6 4-2.533-.667-7.6 4s-10.133 4.667-15.2 0S-32.933-.667-38 4s-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0-10.133-4.667-15.2 0-10.133 4.667-15.2 0"
stroke-linecap="square"
/>
</g>
</pattern>
<rect width="100%" height="100%" fill="url(#a)" />
</svg>
<footer in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<div class="footer-top">
<section class="main-content">
<img src="/logo.svg" class="logo-image" alt="ReVanced Logo" />
<div>
<p>
ReVanced was born out of Vanced's discontinuation and it is our goal to continue the
legacy of what Vanced left behind. Thanks to ReVanced Patcher, it's possible to create
long-lasting patches for nearly any Android app. ReVanced's patching system is designed to
allow patches to work on new versions of the apps automatically with bare minimum
maintenance.
</p>
</div>
</section>
<section class="links-container">
<ul>
<li>Pages</li>
<li><a href="/">Home</a></li>
<li><a href="/download">Download</a></li>
<li><a href="/docs">Documentation</a></li>
<li><a href="/patches">Patches</a></li>
<li><a href="/contributors">Contributors</a></li>
</ul>
<ul>
<li>Repositories</li>
<Query {query} let:data>
{#each data as { name }}
<li>
<a href="https://github.com/{name}" target="_blank" rel="noreferrer">
{friendlyName(name)}
</a>
</li>
{/each}
</Query>
</ul>
<ul>
<!-- to replace -->
<li>Socials</li>
<li><a href="https://github.com/revanced" target="_blank" rel="noreferrer">GitHub</a></li>
<li><a href="https://revanced.app/discord" target="_blank" rel="noreferrer">Discord</a></li>
<li>
<a href="https://reddit.com/r/revancedapp" target="_blank" rel="noreferrer">Reddit</a>
</li>
<li><a href="https://t.me/app_revanced" target="_blank" rel="noreferrer">Telegram</a></li>
<li>
<a href="https://twitter.com/revancedapp" target="_blank" rel="noreferrer">Twitter</a>
</li>
<li>
<a href="https://www.youtube.com/c/ReVanced" target="_blank" rel="noreferrer">YouTube</a>
</li>
</ul>
</section>
</div>
<div class="footer-bottom">
<div id="logo-name"><span>Re</span>Vanced</div>
<a href="https://liberapay.com/ReVanced/donate"><div>Donate</div></a>
<a href="mailto:contact@revanced.app"><div>Email</div></a>
</div>
</footer>
<style>
footer {
margin: 4rem 0 5rem 0;
margin-inline: auto;
padding-bottom: 1rem;
width: min(87%, 80rem);
}
.footer-top {
display: flex;
gap: 8rem;
justify-content: space-between;
margin-bottom: 4rem;
}
.footer-bottom {
display: flex;
gap: 2rem;
align-items: center;
}
#logo-name {
font-size: 1.4rem;
color: var(--white);
font-weight: 600;
}
#logo-name span {
color: var(--accent-color);
}
.footer-bottom a {
text-decoration: none;
color: var(--grey-five);
font-weight: 500;
}
li {
list-style: none;
color: var(--grey-five);
font-size: 0.9rem;
font-weight: 500;
}
li a {
color: var(--accent-color);
font-weight: 500;
font-size: 0.95rem;
}
g path {
stroke: var(--grey-three);
}
.main-content {
display: flex;
flex-direction: column;
gap: 1.5rem;
align-items: flex-start;
}
img {
height: 2.5rem;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
text-decoration-style: wavy;
text-decoration-color: var(--accent-color-two);
color: var(--white);
}
.links-container {
display: flex;
gap: 10rem;
margin-top: 1rem;
}
ul {
display: flex;
gap: 1rem;
flex-direction: column;
width: max-content;
}
@media screen and (max-width: 1050px) {
.footer-top {
flex-direction: column;
gap: 2rem;
}
.links-container {
display: grid;
gap: 3rem;
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
}
}
</style>

View File

@ -0,0 +1,37 @@
<script>
import Picture from '$lib/components/Picture.svelte';
import manager_screenshot from '$images/manager_two.png?w=1233;822;411&format=avif;webp;png&picture';
</script>
<div class="hero-img">
<Picture data={manager_screenshot} alt="Screenshot of ReVanced Manager" />
</div>
<style>
.hero-img :global(img) {
height: 100%;
border-radius: 1.75rem;
}
.hero-img {
overflow: hidden;
height: 70vh;
max-height: 70rem;
z-index: -1;
width: auto;
float: right;
padding: 0.5rem 0.5rem;
border-radius: 1.75rem;
background-color: var(--grey-six);
user-select: none;
}
@media (max-width: 1700px) {
.hero-img {
position: fixed;
height: 100vh;
top: 115px;
right: 6rem;
}
}
</style>

View File

@ -0,0 +1,61 @@
<script>
import Button from '$lib/components/Button.svelte';
</script>
<section class="hero">
<div class="hero-text">
<h1>Continuing the <br />legacy of <span>Vanced.</span></h1>
<p>
Customize your mobile experience through ReVanced <br /> by applying patches to your applications.
</p>
<div class="hero-buttons">
<Button icon="download" type="filled" href="download">Download Manager</Button>
<Button icon="docs" type="tonal" href="patches">View patches</Button>
</div>
</div>
</section>
<style>
h1 {
color: var(--white);
margin-bottom: 1.5rem;
}
p {
margin-bottom: 2rem;
}
.hero {
padding-bottom: 9rem;
}
.hero-text {
align-items: center;
}
.hero-buttons {
display: flex;
user-select: none;
gap: 1rem;
}
span {
color: var(--accent-color);
}
@media (max-width: 768px) {
.hero {
padding-bottom: 0;
}
br {
content: ' ';
}
}
@media screen and (max-width: 450px) {
.hero-buttons {
flex-direction: column;
}
}
</style>

View File

@ -1,10 +1,10 @@
<script>
import SocialButton from '../atoms/SocialButton.svelte';
import SocialButton from './SocialButton.svelte';
</script>
<div class="social-host">
<SocialButton src="github" href="https://revanced.app/github" />
<SocialButton src="discord" href="http://revanced.app/discord" />
<SocialButton src="discord" href="https://revanced.app/discord" />
<SocialButton src="reddit" href="https://revanced.app/reddit" />
<SocialButton src="telegram" href="https://revanced.app/telegram" />
</div>
@ -12,7 +12,6 @@
<style>
.social-host {
width: min(87%, 100rem);
/* same as margin-inline: auto on .wrapper */
padding: 0 max(6.5%, calc(50vw - 50rem));
align-items: center;
user-select: none;

View File

@ -0,0 +1,88 @@
<script lang="ts">
import { queries } from '$data/api';
import { dev_log } from '$util/dev';
import RouterEvents from '$data/RouterEvents';
import { useQueryClient } from '@tanstack/svelte-query';
const client = useQueryClient();
export let href: string;
export let queryKey: null | keyof typeof queries = null;
function prefetch() {
if (queryKey !== null) {
const query = queries[queryKey];
dev_log('Prefetching', query);
client.prefetchQuery(query as any);
}
}
</script>
<a data-sveltekit-preload-data on:mouseenter={prefetch} {href}>
<!-- Check if href is equal to the first path -->
<li class:selected={href === '/' + $RouterEvents.target_url.pathname.split('/')[1]}>
<span><slot /></span>
</li>
</a>
<style>
li {
border: var(--grey-six);
text-align: center;
list-style: none;
position: relative;
font-weight: 500;
font-size: 1rem;
align-items: center;
border: var(--grey-six);
transition-timing-function: var(--bezier-one);
transition-duration: 0.25s;
padding: 10px 16px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
}
a {
text-decoration: none;
font-size: 1rem;
user-select: none;
border-radius: 10px;
}
span {
font-weight: 400;
font-size: 0.9rem;
letter-spacing: 0.02rem;
color: var(--grey-five);
}
li:hover {
color: var(--white);
background-color: var(--grey-one);
}
li.selected {
background-color: var(--accent-low-opacity);
color: var(--accent-color);
}
li.selected span {
color: var(--accent-color);
}
@media (max-width: 768px) {
li {
padding: 0.75rem 1.25rem;
text-align: left;
justify-content: left;
border-radius: 100px;
}
span {
font-size: 1rem;
font-weight: 500;
}
}
</style>

View File

@ -0,0 +1,326 @@
<script lang="ts">
import { onMount } from 'svelte';
import { horizontalSlide } from '$util/horizontalSlide';
import { fade } from 'svelte/transition';
import { expoOut } from 'svelte/easing';
import Navigation from './NavButton.svelte';
import Svg from '$lib/components/Svg.svelte';
import Modal from '$lib/components/Dialogue.svelte';
import Button from '$lib/components/Button.svelte';
import * as settings from '$data/api/settings';
import RouterEvents from '$data/RouterEvents';
import { useQueryClient } from '@tanstack/svelte-query';
const client = useQueryClient();
function clear_and_reload() {
client.clear();
// `client.clear()` does technically do this for us, but it takes a while.
localStorage.clear();
location.reload();
}
let url = settings.api_base_url();
function save() {
settings.set_api_base_url(url);
clear_and_reload();
}
function reset() {
url = settings.default_base_url;
}
let menuOpen = false;
let modalOpen = false;
let y: number;
onMount(() => {
return RouterEvents.subscribe((event) => {
if (event.navigating) {
menuOpen = false;
}
});
});
</script>
<svelte:window bind:scrollY={y} />
<nav class:scrolled={y > 10}>
<button
class="menu-btn mobile-only"
on:click={() => (menuOpen = !menuOpen)}
class:open={menuOpen}
>
<span class="menu-btn__burger" />
</button>
<a href="/" id="logo"><img src="/logo.svg" alt="ReVanced Logo" /></a>
{#key menuOpen}
<div
class="nav-wrapper"
class:desktop-only={!menuOpen}
transition:horizontalSlide={{ direction: 'inline', easing: expoOut, duration: 400 }}
>
<div id="main-navigation">
<div class="nav-buttons">
<Navigation href="/">Home</Navigation>
<Navigation queryKey="manager" href="/download">Download</Navigation>
<Navigation queryKey="patches" href="/patches">Patches</Navigation>
<div hidden>
<!-- This is just temporary so the build doesn't fail -->
<Navigation href="/docs">Docs</Navigation>
</div>
<Navigation queryKey="repositories" href="/contributors">Contributors</Navigation>
</div>
</div>
<div id="secondary-navigation">
<button on:click={() => (modalOpen = !modalOpen)}>
<Svg viewBoxHeight={24} svgHeight={20}>
<path
d="M 19.1 12.9 C 19.1 12.6 19.2 12.3 19.2 12 C 19.2 11.7 19.2 11.4 19.1 11.1 L 21.1 9.5 C 21.3 9.4 21.3 9.1 21.2 8.9 L 19.3 5.6 C 19.2 5.4 18.9 5.3 18.7 5.4 L 16.3 6.4 C 15.8 6 15.3 5.7 14.7 5.5 L 14.3 3 C 14.3 2.8 14.1 2.6 13.8 2.6 L 10 2.6 C 9.8 2.6 9.6 2.8 9.5 3 L 9.2 5.3 C 8.7 5.6 8.1 5.9 7.6 6.3 L 5.2 5.3 C 5 5.2 4.8 5.3 4.6 5.5 L 2.7 8.9 C 2.6 9.1 2.7 9.3 2.9 9.5 L 4.9 11.1 C 4.9 11.4 4.8 11.7 4.8 12 C 4.8 12.3 4.8 12.6 4.9 12.9 L 2.9 14.5 C 2.7 14.6 2.7 14.9 2.8 15.1 L 4.7 18.4 C 4.8 18.6 5.1 18.7 5.3 18.6 L 7.7 17.6 C 8.2 18 8.7 18.3 9.3 18.5 L 9.7 21 C 9.8 21.2 9.9 21.4 10.2 21.4 L 14 21.4 C 14.2 21.4 14.4 21.2 14.5 21 L 14.9 18.5 C 15.5 18.3 16 17.9 16.5 17.6 L 18.9 18.6 C 19.1 18.7 19.4 18.6 19.5 18.4 L 21.4 15.1 C 21.5 14.9 21.5 14.6 21.3 14.5 L 19.1 12.9 Z M 12 15.6 C 10 15.6 8.4 14 8.4 12 C 8.4 10 10 8.4 12 8.4 C 14 8.4 15.6 10 15.6 12 C 15.6 14 14 15.6 12 15.6 Z"
/>
</Svg>
</button>
</div>
</div>
{/key}
{#if menuOpen}
<div
class="overlay mobile-only"
transition:fade={{ duration: 350 }}
on:click={() => (menuOpen = !menuOpen)}
on:keypress={() => (menuOpen = !menuOpen)}
/>
{/if}
</nav>
<!-- settings -->
<Modal bind:modalOpen>
<svelte:fragment slot="icon">
<Svg viewBoxHeight={24} viewBoxWidth={24} svgHeight={24}>
<rect fill="none" height="24" width="24" />
<path
d="M19.5,12c0-0.23-0.01-0.45-0.03-0.68l1.86-1.41c0.4-0.3,0.51-0.86,0.26-1.3l-1.87-3.23c-0.25-0.44-0.79-0.62-1.25-0.42 l-2.15,0.91c-0.37-0.26-0.76-0.49-1.17-0.68l-0.29-2.31C14.8,2.38,14.37,2,13.87,2h-3.73C9.63,2,9.2,2.38,9.14,2.88L8.85,5.19 c-0.41,0.19-0.8,0.42-1.17,0.68L5.53,4.96c-0.46-0.2-1-0.02-1.25,0.42L2.41,8.62c-0.25,0.44-0.14,0.99,0.26,1.3l1.86,1.41 C4.51,11.55,4.5,11.77,4.5,12s0.01,0.45,0.03,0.68l-1.86,1.41c-0.4,0.3-0.51,0.86-0.26,1.3l1.87,3.23c0.25,0.44,0.79,0.62,1.25,0.42 l2.15-0.91c0.37,0.26,0.76,0.49,1.17,0.68l0.29,2.31C9.2,21.62,9.63,22,10.13,22h3.73c0.5,0,0.93-0.38,0.99-0.88l0.29-2.31 c0.41-0.19,0.8-0.42,1.17-0.68l2.15,0.91c0.46,0.2,1,0.02,1.25-0.42l1.87-3.23c0.25-0.44,0.14-0.99-0.26-1.3l-1.86-1.41 C19.49,12.45,19.5,12.23,19.5,12z M12.04,15.5c-1.93,0-3.5-1.57-3.5-3.5s1.57-3.5,3.5-3.5s3.5,1.57,3.5,3.5S13.97,15.5,12.04,15.5z"
/>
</Svg>
</svelte:fragment>
<svelte:fragment slot="title">Settings</svelte:fragment>
<svelte:fragment slot="description"
>Configure the website's API. Defaults to ReVanced.</svelte:fragment
>
<div id="settings-content">
<div class="input-wrapper">
<input name="api-url" type="text" bind:value={url} />
<button id="button-reset" on:click={reset}>
<Svg viewBoxHeight={48} svgHeight={24}>
<path
d="M11.2 36.725C14.6667 40.2417 18.8833 42 23.85 42C26.35 42 28.7 41.525 30.9 40.575C33.1 39.625 35.025 38.3333 36.675 36.7C38.325 35.0667 39.625 33.15 40.575 30.95C41.525 28.75 42 26.4 42 23.9C42 21.4 41.525 19.0667 40.575 16.9C39.625 14.7333 38.325 12.8417 36.675 11.225C35.025 9.60833 33.1 8.33333 30.9 7.4C28.7 6.46667 26.35 6 23.85 6C21.1833 6 18.6583 6.58333 16.275 7.75C13.8917 8.91667 11.8333 10.5167 10.1 12.55V7.25H7.1V17.65H17.55V14.65H12.3C13.7667 12.95 15.4917 11.5833 17.475 10.55C19.4583 9.51667 21.5833 9 23.85 9C28.0167 9 31.5833 10.425 34.55 13.275C37.5167 16.125 39 19.6167 39 23.75C39 27.9833 37.5333 31.5833 34.6 34.55C31.6667 37.5167 28.0833 39 23.85 39C19.6833 39 16.1667 37.5333 13.3 34.6C10.4333 31.6667 9 28.1167 9 23.95H6C6 28.95 7.73333 33.2083 11.2 36.725Z"
/>
</Svg>
</button>
</div>
</div>
<svelte:fragment slot="buttons">
<Button type="text" on:click={clear_and_reload}>Clear cache</Button>
<Button type="text" on:click={save}>Save</Button>
</svelte:fragment>
</Modal>
<style>
path {
fill: var(--grey-five);
}
button:hover path {
fill: var(--accent-color-two);
}
button {
background-color: transparent;
border: none;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
}
.input-wrapper {
margin-bottom: 0.75rem;
display: flex;
justify-content: space-between;
position: relative;
}
input {
width: 100%;
position: relative;
padding-right: 3rem;
margin-top: 1rem;
}
#button-reset {
position: absolute;
right: 12px;
top: 30px;
}
nav {
position: fixed;
top: 0;
display: flex;
gap: 2rem;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
z-index: 666;
height: 70px;
background-color: var(--grey-seven);
width: 100%;
}
#main-navigation,
#secondary-navigation {
align-items: center;
display: flex;
gap: 2rem;
}
a {
display: flex;
}
img {
height: 22px;
}
.nav-buttons {
display: flex;
gap: 1rem;
}
.scrolled {
box-shadow: 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12),
0px 2px 4px -1px rgba(0, 0, 0, 0.2);
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 88;
}
.nav-wrapper {
display: flex;
width: 100%;
justify-content: space-between;
}
@media (min-width: 768px) {
.nav-wrapper {
align-items: center;
}
}
@media (max-width: 768px) {
.nav-wrapper {
flex-direction: column;
gap: 0.5rem;
height: 100vh;
margin: 0 auto;
position: fixed;
width: 20rem;
top: 0px;
border-radius: 0px 24px 24px 0px;
left: 0px;
background-color: var(--grey-seven);
padding: 1rem;
padding-top: 6rem;
z-index: 100;
}
.desktop-only {
display: none !important;
}
nav {
justify-content: normal;
}
.nav-buttons {
flex-direction: column;
gap: 0.5rem;
width: 100%;
}
#secondary-navigation {
z-index: 100;
padding: 16px;
}
}
@media screen and (min-width: 768px) {
.mobile-only {
display: none !important;
}
}
/* Hamburger mmm yum */
.menu-btn {
user-select: none;
position: relative;
display: flex;
height: 50px;
z-index: 999;
justify-content: center;
align-items: center;
cursor: pointer;
}
.menu-btn__burger {
display: flex;
flex-wrap: wrap;
}
.menu-btn__burger,
.menu-btn__burger::before,
.menu-btn__burger::after {
width: 24px;
height: 2px;
background: var(--grey-five);
transition: all 0.3s var(--bezier-one);
}
.menu-btn__burger::before,
.menu-btn__burger::after {
content: '';
position: absolute;
}
.menu-btn__burger::before {
transform: translateY(-6.5px);
}
.menu-btn__burger::after {
transform: translateY(6.5px);
}
/* ANIMATION */
.menu-btn.open .menu-btn__burger {
transform: translateX(-10px);
background: transparent;
box-shadow: none;
}
.menu-btn.open .menu-btn__burger::before {
transform: rotate(45deg) translate(10px, -10px);
}
.menu-btn.open .menu-btn__burger::after {
transform: rotate(-45deg) translate(10px, 10px);
}
</style>

View File

@ -0,0 +1,80 @@
<script lang="ts">
export let type: 'filled' | 'tonal' | 'text' | 'outlined';
export let icon = '';
export let href = '';
export let target = '';
</script>
<button on:click>
{#if href}
<a {href} {target} {...$$restProps} class={`button-${type}`}>
{#if icon}
<img src="../icons/{icon}.svg" alt={icon} />
{/if}
<slot />
</a>
{:else}
<div {...$$restProps} class={`button-${type}`}>
{#if icon}
<img src="../icons/{icon}.svg" alt={icon} />
{/if}
<slot />
</div>
{/if}
</button>
<style>
button {
border: none;
background-color: transparent;
padding: 0;
margin: 0;
}
a,
div {
min-width: max-content;
font-size: 0.95rem;
text-decoration: none;
color: var(--white);
font-weight: 600;
border: none;
border-radius: 12px;
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
cursor: pointer;
transition: transform 0.4s var(--bezier-one), filter 0.4s var(--bezier-one);
user-select: none;
}
.button-filled {
background-color: var(--accent-color);
color: var(--grey-four);
}
.button-tonal {
background-color: var(--grey-two);
}
.button-filled,
.button-tonal {
padding: 16px 24px;
}
.button-text {
background-color: transparent;
color: var(--accent-color);
font-weight: 500;
letter-spacing: 0.01rem;
}
div:hover,
a:hover {
filter: brightness(85%);
}
img {
height: 20px;
}
</style>

View File

@ -0,0 +1,158 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { quadInOut } from 'svelte/easing';
export let modalOpen = false;
export let fullscreen = false;
</script>
{#if modalOpen}
<div
class="overlay"
on:click={() => (modalOpen = !modalOpen)}
on:keypress={() => (modalOpen = !modalOpen)}
transition:fade={{ easing: quadInOut, duration: 150 }}
/>
<div
class="modal"
role="dialog"
class:fullscreen
aria-modal="true"
transition:fade={{ easing: quadInOut, duration: 150 }}
>
<div class="top">
<div class="title" class:hasIcon={$$slots.icon}>
{#if fullscreen}
<button on:click={() => (modalOpen = !modalOpen)}>
<img src="../icons/back.svg" id="back" alt="back" />
</button>
{/if}
{#if $$slots.icon}
<slot name="icon" />
{/if}
{#if $$slots.title}
<h4>
<slot name="title" />
</h4>
{/if}
</div>
{#if $$slots.description}
<p>
<slot name="description" />
</p>
{/if}
<div class="slot"><slot /></div>
{#if $$slots.buttons}
<div class="buttons">
<slot name="buttons" />
</div>
{/if}
</div>
</div>
{/if}
<style>
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.top {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 0.75rem;
}
.title {
position: sticky;
display: flex;
align-items: center;
gap: 1rem;
top: 0;
left: 0;
width: 100%;
background-color: var(--grey-six);
margin-bottom: 8px;
}
.buttons {
display: flex;
gap: 2rem;
margin-top: 1rem;
justify-content: flex-end;
width: 100%;
}
.hasIcon {
flex-direction: column;
}
.modal {
position: fixed;
width: min(85%, 425px);
max-height: 75%;
overflow-y: scroll;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 26px;
background-color: var(--grey-six);
display: flex;
user-select: none;
gap: 5%;
white-space: normal;
display: flex;
flex-direction: column;
gap: 2px;
z-index: 1001;
padding: 32px;
box-shadow: 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12),
0px 2px 4px -1px rgba(0, 0, 0, 0.2);
}
button {
padding: 0;
margin: 0;
border: none;
background-color: transparent;
display: flex;
align-items: center;
}
#back {
height: 24px;
}
.fullscreen {
max-height: 100%;
width: 100%;
border-radius: 0;
}
.fullscreen .title {
justify-content: flex-start;
}
.slot {
display: flex;
flex-direction: column;
align-content: center;
width: 100%;
}
.modal::-webkit-scrollbar {
display: none;
}
</style>

View File

@ -0,0 +1,50 @@
<script>
export let dropdown = false;
export let check = false;
export let selected = false;
</script>
<button class:selected on:click>
{#if check}
<img id="check" src="/icons/check.svg" alt="selected" />
{/if}
<slot />
{#if dropdown}
<img id="dropdown" src="/icons/arrow.svg" alt="dropdown" />
{/if}
</button>
<style>
button {
font-family: var(--font-two);
border: none;
border: 1.5px solid var(--grey-three);
background-color: transparent;
color: var(--grey-five);
height: 32px;
padding: 0 16px;
border-radius: 8px;
font-size: 0.85rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.selected {
background-color: var(--accent-low-opacity);
color: var(--accent-color);
}
img {
height: 18px;
}
#dropdown {
margin-right: -6px;
}
#check {
margin-left: -6px;
}
</style>

View File

@ -0,0 +1,26 @@
<script lang="ts">
let _title: string = "";
$: title = _title === "" ? "ReVanced" : `ReVanced · ${_title}`;
export { _title as title };
export let description: string = "Continuing the legacy of Vanced.";
</script>
<svelte:head>
<title>{title}</title>
<meta name="description" content={description} />
<meta name="theme-color" content="#9FD5FF" />
<!-- OpenGraph -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:image" content="/embed.png" />
<!-- Twitter -->
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" itemprop="image" content="/embed.png" />
<meta name="twitter:card" content="summary" />
</svelte:head>

View File

@ -0,0 +1,13 @@
<script lang="ts">
// See: https://github.com/JonasKruckenberg/imagetools/blob/main/docs/directives.md#picture
import type { Picture } from 'vite-imagetools';
export let data: Picture;
export let alt: string;
</script>
<picture>
{#each Object.entries(data.sources) as [format, images]}
<source srcset={images.map(img => `${img.src} ${img.w}w`).join(", ")} type="image/{format}">
{/each}
<img {alt} src={data.fallback.src} />
</picture>

View File

@ -0,0 +1,27 @@
<script lang="ts">
import type { CreateQueryResult } from '@tanstack/svelte-query';
import { isRestoring } from '../../routes/+layout.svelte';
// I might try to get this merged into tanstack query.
// So basically, this is how you do generics here.
//https://github.com/sveltejs/language-tools/issues/273#issuecomment-1003496094
type T = $$Generic;
interface $$Slots {
default: {
// slot name
data: T;
};
}
// TODO: errors
export let query: CreateQueryResult<T, any>;
</script>
{#if !$isRestoring}
{#if $query.isSuccess}
<slot data={$query.data} />
{:else if $query.isError}
<slot name="error" />
{/if}
{/if}

View File

@ -0,0 +1,90 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
export let title: string;
export let searchTerm: string | null;
export let searchTermFiltered: string | undefined;
function clear() {
searchTerm = '';
searchTermFiltered = '';
goto($page.url.pathname)
}
</script>
<div class="search-container">
<img src="../icons/search.svg" id="search" alt="Search" />
{#if searchTerm}
<img
src="../icons/close.svg"
id="clear"
alt="Clear"
on:click={clear}
on:keypress={clear}
transition:fade|local={{ easing: quintOut, duration: 250 }}
/>
{/if}
<input
type="text"
class:clear={searchTerm}
placeholder={title}
bind:value={searchTerm}
on:keyup
/>
</div>
<style>
#search {
/* umm dont ask */
position: absolute;
z-index: 1;
left: 16px;
top: 14px;
height: 24px;
}
#clear {
position: absolute;
right: 16px;
top: 14px;
z-index: 1;
height: 24px;
cursor: pointer;
}
.search-container {
position: relative;
}
input {
position: relative;
display: flex;
padding: 1rem 3.25rem;
width: 100%;
color: var(--accent-color-two);
font-weight: 500;
font-size: 0.92rem;
border-radius: 100px;
border: none;
background-color: var(--grey-ten);
}
input::placeholder {
color: var(--grey-five);
font-size: 0.9rem;
font-weight: 500;
transition: all 0.2s var(--bezier-one);
}
input:focus {
outline: none;
}
input:focus::placeholder {
outline: none;
color: var(--accent-color)
}
</style>

View File

@ -1,7 +1,8 @@
<script>
import { fade } from "svelte/transition";
import { fade } from 'svelte/transition';
</script>
<div class="spinner" transition:fade={{duration: 250}}/>
<div class="spinner" transition:fade={{ duration: 250 }} />
<style>
@keyframes spinner {
@ -13,12 +14,12 @@
.spinner:before {
content: '';
box-sizing: border-box;
position: fixed;
top: 50%;
left: 50%;
width: 50px;
height: 50px;
border-radius: 50%;
position: fixed;
top: 50%;
left: 50%;
border: 4.5px solid transparent;
border-top-color: var(--accent-color);
animation: spinner 0.6s linear infinite;

View File

@ -0,0 +1,16 @@
<script lang="ts">
export let viewBoxHeight: number;
export let viewBoxWidth = viewBoxHeight;
export let svgHeight: number;
export let svgWidth = svgHeight;
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="none"
viewBox="0 0 {viewBoxHeight} {viewBoxWidth}"
style:height={svgHeight+'px'}
style:width={svgWidth+'px'}
>
<slot />
</svg>

View File

@ -9,7 +9,7 @@
<style>
svg {
position: fixed;
z-index: -2;
z-index: -999;
bottom: 0;
height: 35vh;
width: 100%;

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,60 +0,0 @@
<script>
export let kind = 'secondary';
$: type = 'button-' + kind;
export let href = '#';
export let maxWidth = false;
export let icon = '';
</script>
<a {href} rel="noreferrer">
<div class={type} style="width: {maxWidth ? '100%' : 'max-content'}">
<img src="../icons/{icon}.svg" alt={icon} />
<slot />
</div>
</a>
<style>
a {
text-decoration: none;
border-radius: 16px;
}
div,
.button-secondary {
font-size: 1.1rem;
height: 60px;
color: var(--white);
font-weight: 600;
border: none;
border-radius: 16px;
padding: 16px 40px;
display: block;
cursor: pointer;
background-color: var(--grey-two);
transition: transform 0.4s var(--bezier-one), filter 0.4s var(--bezier-one);
user-select: none;
}
.button-primary {
background-color: var(--accent-color);
box-shadow: 0px 0px 32px 1px var(--accent-color-glow);
color: var(--grey-four);
}
div:hover {
transform: translateY(-4%);
filter: brightness(90%);
}
div,
.button-secondary {
display: flex;
justify-content: center;
align-items: center;
gap: 0.5rem;
}
img {
height: 25px;
}
</style>

View File

@ -1,29 +0,0 @@
<script lang="ts">
import type { DocumentInfo } from '$lib/documentation.shared';
export let info: DocumentInfo;
</script>
<!-- Always part of a list -->
<li>
<div class="doc-section">
<a href="/docs/{info.slug}">{info.title}</a>
</div>
</li>
<style>
a {
text-decoration: none;
background-color: inherit;
color: var(--white);
}
.doc-section {
background-color: var(--grey-one);
border-radius: 12px;
padding: 15px 20px;
}
li {
padding-bottom: 0.5rem;
}
</style>

View File

@ -1,34 +0,0 @@
<script>
</script>
<div class="hero-img">
<img src="/manager.png" alt="Screenshot of ReVanced Manager" />
</div>
<style>
img {
height: 100%;
}
.hero-img {
overflow: hidden;
height: 70vh;
max-height: 70rem;
z-index: -1;
width: auto;
float: right;
border-radius: 2rem;
transform: rotate(3.7deg);
box-shadow: 0 1rem 5rem 0 #0f111ad4;
user-select: none;
}
@media (max-width: 1700px) {
.hero-img {
border-radius: 3vh;
position: fixed;
height: 90vh;
top: 90px;
right: 7.5%;
transform: rotate(3.7deg) translateY(5%);
}
}
</style>

View File

@ -1,49 +0,0 @@
<script>
import RouterEvents from '../../../data/RouterEvents';
export let href = '/';
export let is_selected = target_url => href === target_url;
</script>
<a data-sveltekit-prefetch {href}>
<li class:selected={is_selected($RouterEvents.target_url.pathname)}>
<slot />
</li>
</a>
<style>
li {
border: var(--grey-six);
text-align: center;
list-style: none;
position: relative;
font-weight: 500;
font-size: 0.9rem;
align-items: center;
border: var(--grey-six);
transition-timing-function: var(--bezier-one);
transition-duration: 0.25s;
padding: 10px 25px;
border-radius: 200px;
display: flex;
align-items: center;
justify-content: center;
}
a {
color: var(--grey-five);
text-decoration: none;
font-size: 1rem;
user-select: none;
border-radius: 200px;
}
li:hover {
color: var(--white);
background-color: var(--grey-one);
}
li.selected {
background-color: var(--grey-two);
color: var(--accent-color);
}
</style>

View File

@ -1,67 +0,0 @@
<script lang="ts">
export let current: string | boolean;
export let name: string;
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="package"
class:selected={current === name}
on:click={() =>
(current = current === name ? false : name) && window.scrollTo({ top: 0, behavior: 'smooth' })}
>
<h3>{name}</h3>
</div>
<style>
.package {
padding: 0.6rem;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.6rem;
width: 100%;
user-select: none;
transition: all 0.4s var(--bezier-one);
}
.package::before {
content: '';
height: 5px;
inline-size: 4px;
border-radius: 200px;
background-color: var(--accent-color);
transition: all 0.2s var(--bezier-one);
opacity: 0;
}
.selected::before {
height: 20px;
transition: all 0.3s var(--bezier-one);
opacity: 1;
}
h3 {
font-size: 0.9rem;
}
.package > h3 {
color: var(--grey-five);
transition: all 0.3s var(--bezier-one);
}
.selected > h3 {
color: var(--accent-color);
transition: all 0.3s var(--bezier-one);
}
.package:hover,
.selected {
background-color: var(--grey-six);
}
.package:not(.selected):hover > h3 {
color: var(--white);
}
</style>

View File

@ -1,78 +0,0 @@
<script lang="ts">
import type { Contributor } from 'src/data/types';
import ContributorButton from '../atoms/ContributorPerson.svelte';
export let contribs: Contributor[];
export let repo: string;
let repo_name = repo
.replace(/-/g, ' ')
.replace(/revanced\/revanced/g, 'ReVanced')
.replace(/cli/g, 'CLI')
.replace(/api/g, 'API')
.replace(/(?:^|\s)\S/g, (x) => x.toUpperCase());
let usersIwantToExplodeSoBadly = ['semantic-release-bot'];
</script>
<div class="title">
<a href="https://github.com/{repo}" rel="noreferrer" target="_blank">
<h2>{repo_name}</h2>
</a>
</div>
<hr />
<div class="contrib-host">
{#each contribs as { login, avatar_url, html_url }}
{#if !usersIwantToExplodeSoBadly.includes(login)}
<ContributorButton name={login} pfp={avatar_url} url={html_url} />
{/if}
{/each}
</div>
<style>
.title {
display: flex;
align-items: center;
border-radius: 4px;
transform: translateX(-6px);
}
h2 {
text-align: center;
font-size: 1.25rem;
}
hr {
margin-top: 0.5rem;
margin-bottom: 1rem;
border-top: 1px solid var(--grey-two);
}
a {
transition: all 0.3s var(--bezier-one);
display: flex;
text-decoration: none;
width: max-content;
border-radius: 8px;
}
a > h2 {
transition: all 0.3s var(--bezier-one);
width: max-content;
padding: 0rem 0.4rem;
border-radius: 4px;
}
a:hover > h2 {
width: max-content;
background-color: var(--grey-three);
color: var(--accent-color);
}
.contrib-host {
gap: 0.5rem;
display: grid;
justify-items: center;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
</style>

View File

@ -1,39 +0,0 @@
<script lang="ts">
import { is_tree } from '$lib/documentation.shared';
import type { DocsTree } from '$lib/documentation.shared';
import DocsNavNode from '$lib/components/atoms/DocsNavNode.svelte';
export let tree: DocsTree;
// How deeply nested this is.
export let nested = 0;
</script>
{#if nested}
<!-- The index should be part of the `ul` above us. -->
<DocsNavNode info={tree.index} />
{/if}
<ul>
{#if !nested}
<!-- There is no `ul` above us, so index should go here instead. -->
<DocsNavNode info={tree.index} />
{/if}
{#each tree.nodes as node}
{#if is_tree(node)}
<!-- Recursion here is fine. We are not dealing with a tree the size of a linux root file system. -->
<svelte:self tree={node} nested={nested + 1} />
{:else}
<DocsNavNode info={node} />
{/if}
{/each}
</ul>
<style>
ul {
padding-left: 2rem;
list-style-type: "• ";
color: var(--white);
}
</style>

View File

@ -1,106 +0,0 @@
<script lang="ts">
import type { Repository } from 'src/data/types';
export let repositories: Repository[];
</script>
<hr />
<footer>
<section class="main-content">
<img src="/logo.svg" class="logo-image" alt="ReVanced Logo" />
<div>
<h1>
<span>Re</span>Vanced
</h1>
<h6>Copyright © 2022, we are very legal</h6>
</div>
</section>
<section class="links-container">
<div class="link-column">
<h5>Pages</h5>
<a href="/"><h6>Home</h6></a>
<a href="/download"><h6>Download</h6></a>
<a href="/docs"><h6>Docs</h6></a>
<a href="/patches"><h6>Patches</h6></a>
<a href="/contributors"><h6>Contributors</h6></a>
</div>
{#if repositories.length}
<div class="link-column">
<h5>Repos</h5>
{#each repositories as { name }}
<a href="https://github.com/{name}" target="_blank" rel="noreferrer">
<div>
<h6>
{name
.replace(/-/g, ' ')
.replace(/revanced\/revanced/g, '')
.replace(/cli/g, 'CLI')
.replace(/api/g, 'API')
.replace(/(?:^|\s)\S/g, (x) => x.toUpperCase())}
</h6>
</div>
</a>
{/each}
</div>
{/if}
<div class="link-column">
<!-- to replace -->
<h5>Socials</h5>
<a href="/"><h6>Github</h6></a>
<a href="/"><h6>Discord</h6></a>
<a href="/"><h6>Reddit</h6></a>
<a href="/"><h6>Twitter</h6></a>
<a href="/"><h6>Telegram</h6></a>
<a href="/"><h6>Video Site</h6></a>
</div>
</section>
</footer>
<style>
footer {
margin: 4rem 0 5rem 0;
margin-inline: auto;
display: flex;
justify-content: space-between;
width: min(85%, 90rem);
}
.main-content {
display: flex;
gap: 1rem;
}
.main-content span {
color: var(--accent-color);
}
.main-content h1 {
letter-spacing: -0.04rem;
margin-bottom: 0.5rem;
}
img {
height: 3rem;
}
a {
text-decoration: none;
}
h6 {
font-size: 0.9rem;
}
.links-container {
display: flex;
gap: 4rem;
}
.link-column {
display: flex;
gap: 0.5rem;
flex-direction: column;
}
</style>

View File

@ -1,158 +0,0 @@
<script lang="ts">
import Navigation from '../atoms/NavButton.svelte';
import { onMount } from 'svelte';
import { page } from '$app/stores';
import Button from '../atoms/Button.svelte';
let menuBtn: HTMLElement;
let menuOpen = false;
onMount(() => {
menuBtn.addEventListener('click', () => {
if (!menuOpen) {
menuBtn.classList.add('open');
menuOpen = true;
} else {
menuBtn.classList.remove('open');
menuOpen = false;
}
});
});
</script>
<nav>
<div class="left-side">
<a href="/">
<div class="logo">
<img src="/logo.svg" class="logo-image" alt="ReVanced Logo" />
</div>
</a>
<ul>
<Navigation href="/">Home</Navigation>
<Navigation href="/download">Download</Navigation>
<Navigation is_selected={target => target.startsWith("/docs")} href="/docs">Docs</Navigation>
<Navigation href="/patches">Patches</Navigation>
</ul>
</div>
<ul>
<Navigation href="/contributors/">
<img src="/icons/contrib.svg" alt="Contributors"/>
</Navigation>
<Navigation href="/api-settings/">
<img src="/icons/settings.svg" alt="Settings"/>
</Navigation>
</ul>
<div class="menu-btn" class:open={menuOpen} bind:this={menuBtn}>
<div class="menu-btn__burger" />
</div>
</nav>
<style>
nav {
padding: 0 1rem 0 2rem;
top: 0;
position: fixed;
display: flex;
align-items: center;
justify-content: space-between;
height: 70px;
width: 100%;
z-index: 999;
background-color: var(--grey-six);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
}
ul {
display: flex;
gap: 0.75rem;
align-items: center;
}
a {
text-decoration: none;
}
.logo-image {
height: 1.75rem;
width: auto;
cursor: pointer;
}
.logo {
display: flex;
align-items: center;
}
.left-side {
display: flex;
flex-direction: row;
align-items: center;
gap: 2.5rem;
}
img {
height: 20px;
}
@media screen and (max-width: 768px) {
ul {
display: none;
}
}
/* Hamburger mmm yum */
@media screen and (min-width: 768px) {
.menu-btn {
display: none !important;
}
}
.menu-btn {
user-select: none;
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 80px;
height: 60px;
cursor: pointer;
transition: all 0.5s ease-in-out;
background: var(--grey-one);
border-radius: 3rem;
}
.menu-btn__burger {
width: 30px;
height: 3px;
background: #fff;
border-radius: 5px;
transition: all 0.5s ease-in-out;
}
.menu-btn__burger::before,
.menu-btn__burger::after {
content: '';
position: absolute;
width: 30px;
height: 3px;
background: #fff;
border-radius: 5px;
transition: all 0.5s ease-in-out;
}
.menu-btn__burger::before {
transform: translateY(-8px);
}
.menu-btn__burger::after {
transform: translateY(8px);
}
/* ANIMATION */
.menu-btn.open .menu-btn__burger {
transform: translateX(-50px);
background: transparent;
box-shadow: none;
}
.menu-btn.open .menu-btn__burger::before {
transform: rotate(45deg) translate(35px, -35px);
}
.menu-btn.open .menu-btn__burger::after {
transform: rotate(-45deg) translate(35px, 35px);
}
</style>

View File

@ -1,44 +0,0 @@
<script lang="ts">
export let title: string;
</script>
<div class="menu">
<h5>{title.toUpperCase()}</h5>
<hr />
<div class="package-list">
<slot />
</div>
</div>
<style>
.menu {
height: calc(100vh - 7.5rem);
width: 100%;
padding: 0px 10px 30px 10px;
display: flex;
flex-direction: column;
position: sticky;
top: 7.5rem;
overflow-y: scroll;
}
h5 {
font-weight: 500;
}
hr {
margin-top: 0.5rem;
}
.package-list {
margin-top: 0.75rem;
display: flex;
flex-direction: column;
white-space: normal;
word-break: break-all;
}
/* .package-list:has(.loading) {
padding-top: 7.5rem;
} */
</style>

View File

@ -1,88 +0,0 @@
<script>
import Button from '$lib/components/atoms/Button.svelte';
</script>
<section class="hero">
<div class="hero-text">
<h1>
<span>Re</span>Vanced
</h1>
<h2>
An extensible framework for building <br />application mods.
</h2>
<div class="hero-buttons">
<Button icon="download" href="download" maxWidth="true" kind="primary">Download</Button>
<Button icon="docs" href="docs" maxWidth="true">Read The Docs</Button>
</div>
</div>
</section>
<style>
h2 {
margin-top: 1.75rem;
margin-bottom: 2rem;
}
.hero {
padding-bottom: 10rem;
}
.hero-text {
align-items: center;
}
.hero-buttons {
display: flex;
user-select: none;
gap: 1rem;
}
span {
color: var(--accent-color);
}
h1 {
color: var(--white);
font-weight: 700;
font-size: 5rem;
letter-spacing: -0.04em;
line-height: 0.75em;
}
@media screen and (max-width: 1919px) {
h1 {
font-size: 5rem;
}
}
@media screen and (max-width: 1052px) {
h1 {
font-size: 4.5rem;
}
}
@media screen and (max-width: 768px) {
h1 {
font-size: 4rem;
}
}
@media (max-width: 768px) {
.hero {
padding-bottom: 0;
text-align: center;
}
h2 {
font-size: 1.5rem;
}
br {
content: ' ';
}
.hero-buttons {
flex-direction: column;
}
}
</style>

View File

@ -1,45 +0,0 @@
#markup-content {
/* Defaults for text */
color: var(--white);
font-weight: 300;
font-size: 1rem;
letter-spacing: 0.02rem;
padding: 100px 30px 0px 30px;
a {
text-decoration: none;
color: var(--white);
border-bottom: 1.5px solid var(--accent-color);
padding: 0px 5px;
transition: all 0.4s var(--bezier-one);
}
a:hover {
background-color: var(--accent-color);
border-radius: 6px;
color: var(--grey-four);
}
code {
background-color: var(--grey-six);
border-radius: 4px;
padding: 2px;
}
h4 {
margin-bottom: 0.5rem;
}
h5 {
color: var(--accent-color);
}
/* Markup processors output this for bold text, but css spec is goofy aah */
strong {
font-weight: bold;
}
ul {
padding-left: 2rem;
}
}

View File

@ -1,202 +0,0 @@
import { is_tree } from './documentation.shared';
import type { Document, DocsTree, DocsTreeNode, DocumentInfo } from './documentation.shared';
import { browser, prerendering } from '$app/environment';
import fs, { existsSync as exists } from 'fs';
import path from 'path';
import { parse as parse_md } from 'marked';
import AsciiDocProcessor from 'asciidoctor'
// This file does not work in a browser.
if (browser) {
throw Error('SvelteKit has skill issues');
}
/// Constants
const supported_formats: Map<string, (markup: string) => Document> = new Map();
supported_formats.set("md", markup => {
let lines = markup.split('\n');
// Get and remove the first line.
const first_line = lines.splice(0, 1)[0];
// Remove `# `.
const title = first_line.substring(2);
// Convert the rest to html
const content = parse_md(lines.join('\n'));
return { title, content };
});
const asciidoctor = AsciiDocProcessor();
const adoc_fn = markup => {
// Get first line.
const first_line = markup.split('\n')[0];
// Remove `= `.
const title = first_line.substring(2);
// Convert it to html. Unlike markdown, we do not need to remove the first title heading.
// NOTE: Maybe consider change the safe mode value.
const content = asciidoctor.convert(markup, { doctype: "book" })
return { title, content };
}
supported_formats.set("adoc", adoc_fn)
supported_formats.set("asciidoc", adoc_fn)
const supported_filetypes = [...supported_formats.keys()];
let docs_folder = process.env.REVANCED_DOCS_FOLDER;
if (docs_folder === undefined) {
if (prerendering) { console.warn("Using testing docs in production build") }
docs_folder = "testing-docs";
}
const ignored_items = ["assets", "README.md"];
/// Utility functions
function is_directory(item: string) {
return fs.lstatSync(item).isDirectory();
}
function get_ext(fname: string) {
// Get extname and remove the first dot.
return path.extname(fname).substring(1);
}
function get_slug_of_node(node: DocsTreeNode): string {
if (is_tree(node)) {
return node.index.slug;
}
return node.slug;
}
/// Important functions
// Get a document. Returns null if it does not exist.
export function get(slug: string): Document|null {
let target = path.join(docs_folder, slug);
// Handle index file for folder.
if (exists(target) && is_directory(target)) {
target += "/index";
}
const dir = path.dirname(target);
if (!exists(dir)) {
return null;
}
let full_path, ext, found = false;
// We are looking for the file `${target}.(any_supported_extension)`. Try to find it.
for (const item of fs.readdirSync(dir)) {
full_path = path.join(dir, item);
// Get file extension
ext = get_ext(item);
// Unsupported/unrelated file.
if (!supported_formats.has(ext)) {
continue;
}
const desired_path = `${target}.${ext}`; // Don't grab some other random supported file.
if (!is_directory(full_path) && desired_path == full_path) {
// We found it.
found = true;
break;
}
}
if (!found) {
return null;
}
// Process the file and return.
return supported_formats.get(ext)(fs.readFileSync(full_path, 'utf-8'));
}
// Get file information
function process_file(fname: string): DocumentInfo {
// Remove docs folder prefix and file extension suffix, then split it.
const parts = fname
.substring(`${docs_folder}/`.length, fname.length - (get_ext(fname).length + 1))
.split("/");
// Remove `index` suffix if present.
const last_index = parts.length - 1;
if (parts[last_index] == "index") {
parts.pop();
}
const slug = parts.join("/");
const title = get(slug).title;
return { slug, title };
}
// Returns a document tree.
function process_folder(dir: string): DocsTree {
let tree: DocsTree = {
index: null,
nodes: []
};
// List everything in the directory.
const items = fs.readdirSync(dir);
for (const item of items) {
if (ignored_items.includes(item) || [".", "_"].includes(item[0])) {
continue;
}
const itemPath = path.join(dir, item);
const is_dir = is_directory(itemPath);
let is_index_file = false;
if (!is_dir) {
// Ignore files we cannot process.
if (!supported_formats.has(get_ext(item))) {
continue;
}
for (const ext of supported_filetypes) {
if (item == `index.${ext}`) {
is_index_file = true;
}
}
}
const node = is_dir ? process_folder(itemPath) : process_file(itemPath);
if (is_index_file) {
tree.index = node;
} else {
tree.nodes.push(node);
}
}
if (tree.index === null) {
throw Error(`${dir} has no index file.`);
}
// `numeric: true` because we want to be able to specify
// the order if necessary by prepending a number to the file name.
tree.nodes.sort(
(a, b) => get_slug_of_node(a).localeCompare(get_slug_of_node(b), "en", { numeric: true })
);
return tree;
}
// Returns the document tree.
export function index_content(): DocsTree {
return process_folder(docs_folder);
}

View File

@ -1,28 +0,0 @@
/// Types
export interface Document {
title: string;
// HTML
content: string;
}
export interface DocumentInfo {
title: string;
slug: string;
}
// A tree representing the `docs` folder.
export interface DocsTree {
// index.whatever
index: DocumentInfo;
// Everything except index.whatever
nodes: DocsTreeNode[];
}
export type DocsTreeNode = DocsTree | DocumentInfo;
/// Functions
export function is_tree(node: DocsTreeNode) {
return node.hasOwnProperty('nodes');
}

View File

@ -33,12 +33,17 @@ export interface PatchOption {
choices: string[];
}
export interface Asset {
name: string;
size: string|null;
url: string;
content_type: string;
};
export interface Tool {
repository: string
version: string
timestamp: string
name: string
size?: string
browser_download_url: string
content_type: string
}
repository: string;
version: string;
timestamp: string;
assets: Asset[];
};

View File

@ -1,8 +0,0 @@
import { dev } from "$app/environment";
// console.log, but only if in dev environment.
export function dev_log(part: string, ...args) {
if (dev) {
console.log(`[${part}]:`, ...args);
}
}

View File

@ -1,35 +1,34 @@
<script lang="ts">
import Navigation from '$lib/components/atoms/NavButton.svelte';
import Meta from '$lib/components/Meta.svelte';
import Navigation from '$layout/Navbar/NavButton.svelte';
import { page } from '$app/stores';
import Button from '$lib/components/Button.svelte';
$: status = $page.status;
</script>
<section class="error">
<Meta title="404" />
<section class="wrapper">
<h1>{status}</h1>
{#if status == 500}
<p>
{$page.error.message}
</p>
{:else if status == 404}
<p>That page received a cease and desist letter from a multi-billion dollar tech company.</p>
{#if status == 404}
<p>This page received a cease and desist letter from a multi-billion dollar tech company.</p>
<br />
<Navigation href="/" is_selected={() => true}>Home</Navigation>
<Button type="filled" href="/">Return Home</Button>
{:else}
<p>
{$page.error?.message}
</p>
{/if}
</section>
<style>
.error {
padding-top: 5rem;
section {
text-align: center;
margin-top: 10rem;
}
h1 {
font-size: 10rem;
color: var(--accent-color);
}
p {
font-size: 5ch;
color: var(--white);
}
</style>

View File

@ -1,46 +1,68 @@
<script lang="ts">
import { derived } from "svelte/store";
import NavHost from "$lib/components/molecules/NavHost.svelte";
import Spinner from '$lib/components/atoms/Spinner.svelte';
import RouterEvents from '../data/RouterEvents';
import '../app.css';
import type { PageData } from './$types';
import { contributors } from "../data/api";
export let data: PageData;
contributors.init(data);
// Just like the set/clearInterval example found here: https://svelte.dev/docs#run-time-svelte-store-derived
const show_loading_animation = derived(RouterEvents, ($event, set) => {
if ($event.navigating) {
// Wait 250 ms before showing the animation.
const timeout = setTimeout(() => set(true), 250);
return () => clearTimeout(timeout);
} else {
set(false)
}
}, false);
<script lang="ts" context="module">
import { writable } from 'svelte/store';
// There might be a better place to put this, but I am not entirely sure...
export const isRestoring = writable(false);
</script>
<svelte:head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="og:title" content="ReVanced" />
<meta content="/embed.png" property="og:image" />
<meta property="og:description" content="Continuing the legacy of Vanced." />
<meta name="twitter:image" itemprop="image" content="/embed.png" />
<meta name="twitter:card" content="summary" />
<meta name="theme-color" content="#9FD5FF" />
</svelte:head>
<script lang="ts">
import '../app.scss';
import { derived } from 'svelte/store';
import { onMount } from 'svelte';
import { browser } from '$app/environment';
<NavHost />
import { QueryClient } from '@tanstack/query-core';
import { persistQueryClient } from '@tanstack/query-persist-client-core';
import { QueryClientProvider } from '@tanstack/svelte-query';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
{#if $show_loading_animation}
<Spinner />
{:else}
<slot />
{/if}
import NavHost from '$layout/Navbar/NavHost.svelte';
import Spinner from '$lib/components/Spinner.svelte';
import { staleTime } from '$data/api';
import RouterEvents from '$data/RouterEvents';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
enabled: browser,
cacheTime: staleTime
}
}
});
onMount(() => {
isRestoring.set(true);
const [unsubscribe, promise] = persistQueryClient({
queryClient,
persister: createSyncStoragePersister({ storage: localStorage })
});
promise.then(() => isRestoring.set(false));
return unsubscribe;
});
// Just like the set/clearInterval example found here: https://svelte.dev/docs#run-time-svelte-store-derived
const show_loading_animation = derived(
RouterEvents,
($event, set) => {
if ($event.navigating) {
// Wait 250 ms before showing the animation.
const timeout = setTimeout(() => set(true), 250);
return () => clearTimeout(timeout);
} else {
set(false);
}
},
false
);
</script>
<QueryClientProvider client={queryClient}>
<NavHost />
{#if $show_loading_animation}
<Spinner />
{:else}
<slot />
{/if}
<!-- guhh afn -->
<!-- <Footer> -->
</QueryClientProvider>

View File

@ -1,7 +1 @@
import type { PageLoad } from './$types';
import { contributors } from '../data/api';
export const prerender = true;
export const load: PageLoad = contributors.page_load_impl();

View File

@ -1,15 +1,12 @@
<script>
import HeroImage from '$lib/components/atoms/HeroImage.svelte';
import Home from '$lib/components/organisms/Home.svelte';
import SocialHost from '$lib/components/molecules/SocialHost.svelte';
import Wave from '$lib/components/atoms/Wave.svelte';
import HeroImage from '$layout/Hero/HeroImage.svelte';
import Home from '$layout/Hero/HeroSection.svelte';
import SocialHost from '$layout/Hero/SocialHost.svelte';
import Wave from '$lib/components/Wave.svelte';
import Meta from '$lib/components/Meta.svelte';
</script>
<svelte:head>
<title>ReVanced</title>
<meta content="ReVanced" name="og:title" />
<meta content="ReVanced" name="twitter:title" />
</svelte:head>
<Meta />
<main>
<div class="wrap">

View File

@ -1,26 +0,0 @@
<script lang="ts">
import * as settings from '../../data/api/settings';
import { clear } from '../../data/api/cache';
let url = settings.api_base_url();
function handler() {
clear();
settings.set_api_base_url(url);
location.reload(true);
}
</script>
<section class="settings">
<input name="api-url" type="text" bind:value={url} />
<button on:click={handler}>Save</button>
</section>
<section class="cache">
<button on:click={clear}>Clear cache</button>
</section>
<style>
.settings {
padding-top: 5rem;
}
</style>

View File

@ -2,56 +2,61 @@
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import ContributorHost from '$lib/components/molecules/ContributorHost.svelte';
import Footer from '$lib/components/molecules/Footer.svelte';
import ContributorHost from './ContributorSection.svelte';
import Footer from '$layout/Footer.svelte';
import Meta from '$lib/components/Meta.svelte';
import Query from '$lib/components/Query.svelte';
// Handled by `+layout.ts`.
import { contributors } from '../../data/api';
import { queries } from '$data/api';
import { createQuery } from '@tanstack/svelte-query';
import type { PageData } from './$types';
export let data: PageData;
const query = createQuery(['repositories'], queries.repositories);
</script>
<svelte:head>
<title>ReVanced | Contributors</title>
<meta content="ReVanced | Contributors" name="og:title" />
<meta content="ReVanced | Contributors" name="twitter:title" />
</svelte:head>
<Meta title="Contributors" />
<main>
<div class="wrapper">
<div class="text-container" in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<h1>Made possible by the community.</h1>
<h2>Want to show up here? <span><a href="https://github.com/revanced" target="_blank" rel="noreferrer">Become a contributor</a></span></h2>
<h2>Made possible by the community.</h2>
<h4>
Want to show up here? <span
><a href="https://github.com/revanced" target="_blank" rel="noreferrer"
>Become a contributor</a
></span
>
</h4>
</div>
<div class="contrib-grid">
{#each $contributors.repositories as { contributors: contribs, name }}
<div in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<ContributorHost {contribs} repo={name} />
</div>
{/each}
<div class="repos">
<Query {query} let:data>
{#each data as { contributors, name: repo }}
<div in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<ContributorHost {contributors} {repo} />
</div>
{/each}
</Query>
</div>
</div>
</main>
<Footer {...data} />
<Footer />
<style>
.contrib-grid {
.repos {
display: flex;
flex-direction: column;
gap: 3rem;
margin-bottom: 3rem;
gap: 2rem;
margin-bottom: 4rem;
}
h1 {
font-size: 2.25rem;
font-weight: 600;
h2 {
text-align: center;
color: var(--grey-four);
margin-bottom: 0.3rem;
}
h2 {
font-size: 1rem;
h4 {
text-align: center;
color: var(--grey-four);
}
@ -59,11 +64,10 @@
display: flex;
align-items: center;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 2rem;
background-color: var(--accent-color);
padding: 2rem;
border-radius: 8px;
padding: 2.5rem 1.75rem;
border-radius: 20px;
}
a {
@ -73,12 +77,24 @@
a::after {
padding-left: 5px;
content: '->';
content: '';
position: absolute;
transition: all 0.3s var(--bezier-one);
}
a:hover {
text-decoration: underline;
text-decoration-style: wavy;
text-decoration-color: var(--grey-four);
}
a:hover::after {
transform: translateX(5px);
}
@media screen and (max-width: 768px) {
.text-container {
padding: 2rem 1.75rem;
margin-bottom: 2rem;
}
}
</style>

View File

@ -7,46 +7,60 @@
<a href={url} rel="noreferrer" target="_blank">
<img src={pfp} {alt} />
<h2>{name}</h2>
<h5>{name}</h5>
</a>
<style>
a {
color: var(--white);
text-decoration: none;
padding: 0.5rem;
cursor: pointer;
padding: 0.9rem 1rem;
width: 100%;
transition: all 0.3s var(--bezier-one);
border-radius: 4px;
transition: background-color 0.3s var(--bezier-one);
display: flex;
gap: 1rem;
align-items: center;
background-color: var(--grey-six);
border: 1px solid var(--grey-three);
border-right: 1px solid var(--grey-three);
border-bottom: 1px solid var(--grey-three);
}
a:hover {
background-color: var(--accent-color);
text-decoration: underline;
text-decoration-style: wavy;
text-decoration-color: var(--accent-color);
color: var(--white);
}
a:hover > h2 {
color: var(--grey-four);
}
h2 {
font-size: 0.95rem;
h5 {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0.25rem 0;
}
img {
border-radius: 50%;
height: 30px;
width: 30px;
height: 32px;
width: 32px;
background-color: var(--grey-two);
transition: transform 0.4s var(--bezier-one);
user-select: none;
}
@media (max-width: 768px) {
h5 {
display: none;
}
img {
height: 42px;
width: 42px;
}
a {
width: max-content;
background-color: transparent;
border: none;
}
}
</style>

View File

@ -0,0 +1,103 @@
<script lang="ts">
import { friendlyName } from '$util/friendlyName';
import { slide } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import type { Contributor } from '$lib/types';
import ContributorButton from './ContributorPerson.svelte';
export let contributors: Contributor[];
export let repo: string;
let expanded = true;
// Yes
let usersIwantToExplodeSoBadly = ['semantic-release-bot'];
let repo_name = friendlyName(repo);
</script>
<div class="section-container">
<div
class="title"
class:closed={!expanded}
on:click={() => (expanded = !expanded)}
on:keypress={() => (expanded = !expanded)}
>
<a href="https://github.com/{repo}" rel="noreferrer" target="_blank" on:click|stopPropagation>
<h4>{repo_name}</h4>
</a>
<img
id="arrow"
style:transform={expanded ? 'rotate(0deg)' : 'rotate(-180deg)'}
src="/icons/arrow.svg"
alt="dropdown"
/>
</div>
{#if expanded}
<div class="contrib-host" transition:slide|local={{ easing: quintOut, duration: 500 }}>
{#each contributors as { login, avatar_url, html_url }}
{#if !usersIwantToExplodeSoBadly.includes(login)}
<ContributorButton name={login} pfp={avatar_url} url={html_url} />
{/if}
{/each}
</div>
{/if}
</div>
<style>
.title {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
background-color: var(--grey-six);
padding: 0.75rem 1.25rem;
border-bottom: 1px solid var(--grey-three);
transition: all 0.2s var(--bezier-one);
}
.closed {
border-bottom: none;
}
#arrow {
height: 1.5rem;
transition: all 0.2s var(--bezier-one);
user-select: none;
}
.section-container {
border-radius: 20px;
overflow: hidden;
border: 1px solid var(--grey-three);
}
a {
display: flex;
text-decoration: none;
width: max-content;
border-radius: 8px;
}
a:hover {
text-decoration: underline;
text-decoration-style: wavy;
text-decoration-color: var(--accent-color);
color: var(--white);
}
.contrib-host {
margin-right: -1px;
margin-bottom: -1px;
display: grid;
justify-items: center;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
@media (max-width: 768px) {
.contrib-host {
padding: 0.75rem;
gap: 0.25rem;
grid-template-columns: repeat(auto-fill, minmax(50px, 1fr));
}
}
</style>

View File

@ -1,10 +1,10 @@
import type { PageServerLoad } from './$types';
import type { LayoutServerLoad } from './$types';
import { index_content } from '$lib/documentation.server';
import { index_content } from './documentation.server';
// The load function here used to get data from a json file created by a (prerendered) server route.
// This was because we could not prerender the documentation route before.
// If you can no longer prerender the docs, then you are going to have to move the load functions here to a prerendered server route like before and fetch them here.
export const prerender = true;
export const load: PageServerLoad = () => ({ tree: index_content() });
export const load: LayoutServerLoad = () => ({ tree: index_content() });

View File

@ -1,34 +1,37 @@
<script lang="ts">
import type { PageData } from './$types';
import type { LayoutData } from './$types';
import DocsNavTree from '$lib/components/molecules/DocsNavTree.svelte';
import DocsNavTree from './DocsNavTree.svelte';
import Footer from '$layout/Footer.svelte';
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
export let data: PageData;
export let data: LayoutData;
</script>
<section id="doc-section-main" in:fly={{ y: 10, easing: quintOut, duration: 700 }}>
<section id="doc-section-main">
<div class="menu">
<DocsNavTree tree={data.tree} />
<DocsNavTree tree={data.tree} />
</div>
<slot></slot>
<slot />
</section>
<Footer />
<style lang="scss">
.menu {
padding: 90px 15px 0px 15px;
display: flex;
flex-direction: column;
gap: 1rem;
}
#doc-section-main {
margin-inline: auto;
width: min(90%, 90rem);
margin-top: 8rem;
margin-bottom: 5rem;
}
#doc-section-main {
display: grid;
grid-template-columns: 300px 3fr;
.menu {
display: flex;
flex-direction: column;
gap: 1rem;
}
height: 100vh;
width: 100%;
}
#doc-section-main {
display: grid;
grid-template-columns: 320px 3fr;
gap: 3rem;
}
</style>

View File

@ -0,0 +1,56 @@
<script lang="ts">
import type { DocumentInfo } from './documentation.shared';
export let info: DocumentInfo;
import { page } from '$app/stores';
</script>
<!-- Always part of a list -->
<li>
<a href="/docs/{info.slug}">
<div class="doc-section item" class:selected={$page.url.pathname === `/docs/${info.slug}`}>
<h5>{info.title}</h5>
</div>
</a>
</li>
<style>
a {
text-decoration: none;
}
li {
list-style: none;
}
.item {
padding: 0.6rem 1rem;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.6rem;
width: 100%;
user-select: none;
transition: background-color 0.4s var(--bezier-one);
}
.item h5 {
color: var(--grey-five);
transition: color 0.3s var(--bezier-one);
}
.selected h5 {
color: var(--grey-four);
transition: color 0.3s var(--bezier-one);
}
.selected {
background-color: var(--accent-color);
}
.item:hover:not(.selected) {
background-color: var(--grey-six);
}
.item:not(.selected):hover h5 {
color: var(--white);
}
</style>

View File

@ -0,0 +1,37 @@
<script lang="ts">
import { is_tree, assert_is_info_node } from './documentation.shared';
import type { DocsTree } from './documentation.shared';
import DocsNavNode from './DocsNavNode.svelte';
export let tree: DocsTree;
// How deeply nested this is.
export let nested = 0;
</script>
{#if nested}
<!-- The index should be part of the `ul` above us. -->
<DocsNavNode info={tree.index} />
{/if}
<ul>
{#if !nested}
<!-- There is no `ul` above us, so index should go here instead. -->
<DocsNavNode info={tree.index} />
{/if}
{#each tree.nodes as node}
{#if is_tree(node)}
<!-- Recursion here is fine. We are not dealing with a tree the size of a linux root file system. -->
<svelte:self tree={node} nested={nested + 1} />
{:else}
<DocsNavNode info={assert_is_info_node(node)} />
{/if}
{/each}
</ul>
<style>
ul {
padding-left: 1rem;
}
</style>

View File

@ -1,17 +1,17 @@
import type { PageServerLoad } from './$types';
import { error } from '@sveltejs/kit';
import { get } from '$lib/documentation.server';
import { get } from '../documentation.server';
// See also: ../+layout.server.ts
export const prerender = true;
export const load: PageServerLoad = ({ params }) => {
const document = get(params.slug);
if (document === null) {
error
throw error(404);
}
const document = get(params.slug);
if (document === null) {
error;
throw error(404);
}
return document;
}
return document;
};

View File

@ -1,22 +1,17 @@
<script lang="ts">
import type { PageData } from './$types';
import Meta from '$lib/components/Meta.svelte';
import type { PageData } from './$types';
import '$lib/documentation.scss';
import '../documentation.scss';
// Data here comes from a trusted source.
// CSS comes from the layout.
export let data: PageData;
// Data here comes from a trusted source.
// CSS comes from the layout.
export let data: PageData;
</script>
<svelte:head>
<title>ReVanced | Docs</title>
<meta content="ReVanced | Docs" name="og:title" />
<meta content="ReVanced | Docs" name="twitter:title" />
</svelte:head>
<Meta title="Docs" />
<div id="markup-content">
<h1 class="title">{data.title}</h1>
{@html data.content}
<h1 class="title">{data.title}</h1>
{@html data.content}
</div>

View File

@ -0,0 +1,95 @@
#markup-content {
/* Defaults for text */
color: var(--accent-color-two);
font-weight: 300;
font-size: 1rem;
line-height: 1.75rem;
letter-spacing: 0.03rem !important;
a {
text-decoration: none;
color: var(--accent-color);
border-bottom: 1.5px solid var(--accent-low-opacity);
padding: 0px ;
}
code {
background-color: var(--grey-one);
border-radius: 8px;
padding: 0.2rem 0.5rem;
font-size: 0.8rem;
font-family: var(--mono-font);
font-weight: 300;
flex-wrap: wrap;
line-height: 1.25rem;
}
pre code {
font-size: 0.75rem;
background-color: var(--grey-six);
white-space: pre;
display: block;
flex-wrap: wrap;
padding: 0.5rem 1rem;
margin: 1rem 0;
}
h5 {
margin-bottom: 0.5rem;
}
h5 {
color: var(--accent-color);
}
h1, h2, h3, h4, h5 {
margin-bottom: 1rem;
}
h1 {
font-size: 2.25rem;
font-weight: 600;
letter-spacing: -0.02rem;
color: var(--accent-color-two);
border-bottom: 1px solid var(--grey-three);
padding-bottom: 1rem;
margin-bottom: 1rem;
}
h2 {
font-size: 1.5rem;
letter-spacing: -0.02rem;
font-weight: 600;
color: var(--accent-color-two);
border-bottom: 1px solid var(--grey-three);
padding-bottom: 1rem;
margin-top: 2rem;
}
h3 {
margin-top: 1rem;
margin-bottom: 0.5rem;
}
li {
margin-left: 1rem;
margin-bottom: 0.5rem;
}
/* Markup processors output this for bold text, but css spec is goofy aah */
strong {
font-weight: bold;
letter-spacing: 0.01rem;
}
ul {
padding-left: 2rem;
}
}

View File

@ -0,0 +1,207 @@
import { is_tree } from './documentation.shared';
import type { Document, DocsTree, DocsTreeNode, DocumentInfo } from './documentation.shared';
import { browser } from '$app/environment';
import fs, { existsSync as exists } from 'fs';
import path from 'path';
import { marked } from 'marked';
import AsciiDocProcessor from 'asciidoctor';
// This file does not work in a browser.
if (browser) {
throw Error('SvelteKit has skill issues');
}
/// Constants
const supported_formats: Map<string, (markup: string) => Document> = new Map();
supported_formats.set('md', (markup: string) => {
let lines = markup.split('\n');
// Get and remove the first line.
const first_line = lines.splice(0, 1)[0];
// Remove `# `.
const title = first_line.substring(2);
// Convert the rest to html
const content = marked(lines.join('\n'));
return { title, content };
});
const asciidoctor = AsciiDocProcessor();
const adoc_fn = (markup: string) => {
// Get first line.
const first_line = markup.split('\n')[0];
// Remove `= `.
const title = first_line.substring(2);
// Convert it to html. Unlike markdown, we do not need to remove the first title heading.
// NOTE: Maybe consider change the safe mode value.
const content = asciidoctor.convert(markup, { doctype: 'book' }) as string;
return { title, content };
};
supported_formats.set('adoc', adoc_fn);
supported_formats.set('asciidoc', adoc_fn);
const supported_filetypes = [...supported_formats.keys()];
let docs_folder = process.env.REVANCED_DOCS_FOLDER ?? 'testing-docs';
const ignored_items = ['assets'];
/// Utility functions
function is_directory(item: string) {
return fs.lstatSync(item).isDirectory();
}
function get_ext(fname: string) {
// Get extname and remove the first dot.
return path.extname(fname).substring(1);
}
function get_slug_of_node(node: DocsTreeNode): string {
if (is_tree(node)) {
return (node as DocsTree).index.slug;
}
return (node as DocumentInfo).slug;
}
/// Important functions
// Get a document. Returns null if it does not exist.
export function get(slug: string): Document | null {
let target = path.join(docs_folder, slug);
// Handle index (readme) file for folder.
if (exists(target) && is_directory(target)) {
target += '/README';
}
const dir = path.dirname(target);
if (!exists(dir)) {
return null;
}
let full_path: string,
ext: string,
found = false;
// We are looking for the file `${target}.(any_supported_extension)`. Try to find it.
for (const item of fs.readdirSync(dir)) {
full_path = path.join(dir, item);
// Get file extension
ext = get_ext(item);
// Unsupported/unrelated file.
if (!supported_formats.has(ext)) {
continue;
}
const desired_path = `${target}.${ext}`; // Don't grab some other random supported file.
if (!is_directory(full_path) && desired_path == full_path) {
// We found it.
found = true;
break;
}
}
if (!found) {
return null;
}
// Process the file and return.
return supported_formats.get(ext!!)!!(fs.readFileSync(full_path!!, 'utf-8'));
}
// Get file information
function process_file(fname: string): DocumentInfo {
// Remove docs folder prefix and file extension suffix, then split it.
const parts = fname
.substring(`${docs_folder}/`.length, fname.length - (get_ext(fname).length + 1))
.split('/');
// Remove `README` suffix if present.
const last_part_index = parts.length - 1;
if (parts[last_part_index] == 'README') {
parts.pop();
}
const slug = parts.join('/');
const title = get(slug)!!.title;
return { slug, title };
}
// Returns a document tree.
function process_folder(dir: string): DocsTree | null {
let tree: DocsTree = {
index: null as any,
nodes: []
};
// List everything in the directory.
const items = fs.readdirSync(dir);
for (const item of items) {
if (ignored_items.includes(item) || ['.', '_'].includes(item[0])) {
continue;
}
const itemPath = path.join(dir, item);
const is_dir = is_directory(itemPath);
let is_index_file = false;
if (!is_dir) {
// Ignore files we cannot process.
if (!supported_formats.has(get_ext(item))) {
continue;
}
for (const ext of supported_filetypes) {
if (item == `README.${ext}`) {
is_index_file = true;
}
}
}
const node = is_dir ? process_folder(itemPath) : process_file(itemPath);
if (node === null) {
console.error(`The ${itemPath} directory does not have a README/index file! ignoring...`);
continue;
}
if (is_index_file) {
tree.index = node as DocumentInfo;
} else {
tree.nodes.push(node);
}
}
if (tree.index === null) {
return null;
}
// `numeric: true` because we want to be able to specify
// the order if necessary by prepending a number to the file name.
tree.nodes.sort((a, b) =>
get_slug_of_node(a).localeCompare(get_slug_of_node(b), 'en', { numeric: true })
);
return tree;
}
// Returns the document tree.
export function index_content(): DocsTree {
const tree = process_folder(docs_folder);
if (tree === null) {
throw new Error('Root must have index (README) file.');
}
return tree;
}

View File

@ -0,0 +1,35 @@
/// Types
export interface Document {
title: string;
// HTML
content: string;
}
export interface DocumentInfo {
title: string;
slug: string;
}
// A tree representing the `docs` folder.
export interface DocsTree {
// index.whatever
index: DocumentInfo;
// Everything except index.whatever
nodes: DocsTreeNode[];
}
export type DocsTreeNode = DocsTree | DocumentInfo;
/// Functions
export function is_tree(node: DocsTreeNode) {
return Object.prototype.hasOwnProperty.call(node, 'nodes');
}
export function assert_is_info_node(v: DocsTreeNode) {
if (is_tree(v)) {
throw new Error('Value is not an info node.');
}
return v as DocumentInfo;
}

View File

@ -1,45 +1,132 @@
<script lang="ts">
import type { PageData } from './$types';
import { tools as api_tools } from '../../data/api';
import Button from '$lib/components/atoms/Button.svelte';
import Footer from '$lib/components/molecules/Footer.svelte';
import { queries } from '$data/api';
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
export let data: PageData;
api_tools.init(data);
$: tools = data.tools;
import { createQuery } from '@tanstack/svelte-query';
import manager_screenshot from '$images/manager_two.png?format=avif;webp;png&picture';
import Meta from '$lib/components/Meta.svelte';
import Query from '$lib/components/Query.svelte';
import Button from '$lib/components/Button.svelte';
import Footer from '$layout/Footer.svelte';
import Picture from '$lib/components/Picture.svelte';
import Dialogue from '$lib/components/Dialogue.svelte';
import { onMount } from 'svelte';
const query = createQuery(['manager'], queries.manager);
let warning: string;
let warningDialogue = false;
let userAgent: string;
let isAndroid: boolean;
let androidVersionMatch: RegExpExecArray | null;
let androidVersion: number;
onMount(() => {
userAgent = navigator.userAgent;
androidVersionMatch = /Android\s([\d.]+)/i.exec(userAgent);
androidVersion = androidVersionMatch ? parseInt(androidVersionMatch[1]) : 0;
isAndroid = !!androidVersion;
});
function handleClick() {
if (!isAndroid) {
warning = 'Your device is not running Android.';
warningDialogue = true;
} else if (androidVersion < 8) {
warning = `Your device is running ${androidVersion}. ReVanced only supports Android versions 8 and above.`;
warningDialogue = true;
}
}
</script>
<div class="wrapper">
<h1>ReVanced Manager</h1>
<h6>Patch your favourite apps, on-device.</h6>
<Button kind="primary" icon="download" href={tools[5].browser_download_url}>{tools[5].version}</Button>
<img src="../manager_two.png" alt="Manager Screenshot"/>
<Meta title="Download" />
<Dialogue bind:modalOpen={warningDialogue}>
<svelte:fragment slot="title">Warning</svelte:fragment>
<svelte:fragment slot="description">{warning} Do you still want to download?</svelte:fragment>
<svelte:fragment slot="buttons">
<Query {query} let:data>
<Button
type="text"
href={data.assets[0].url}
download
on:click={() => (warningDialogue = false)}>Okay</Button
>
</Query>
<Button type="text" on:click={() => (warningDialogue = false)}>Cancel</Button>
</svelte:fragment>
</Dialogue>
<div class="wrapper center" in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<h2>ReVanced <span>Manager</span></h2>
<p>Patch your favourite apps, right on your device.</p>
<div class="buttons">
<Query {query} let:data>
{#if !isAndroid || androidVersion < 8}
<Button on:click={handleClick} type="filled" icon="download">
{data.version}
</Button>
{:else}
<Button
on:click={handleClick}
type="filled"
icon="download"
href={data.assets[0].url}
download
>
{data.version}
</Button>
{/if}
</Query>
<Button type="tonal" href="https://github.com/revanced/revanced-manager" target="_blank">
View Source
</Button>
</div>
<div class="screenshot">
<Picture data={manager_screenshot} alt="Manager Screenshot" />
</div>
</div>
<Footer {...data}/>
<Footer />
<style>
div {
.center {
display: flex;
flex-direction: column;
align-items: center;
}
h1 {
font-size: 2.25rem;
font-weight: 600;
h2 {
text-align: center;
color: var(--accent-color);
color: var(--white);
}
h6 {
p {
text-align: center;
margin-bottom: 1.5rem;
}
img {
.screenshot :global(img) {
margin-top: 2.5rem;
margin-bottom: 2.5rem;
width: 25rem;
height: 50rem;
width: auto;
padding: 0.5rem 0.5rem;
border-radius: 2rem;
background-color: var(--grey-six);
user-select: none;
}
.buttons {
display: flex;
gap: 1rem;
}
span {
color: var(--accent-color);
}
</style>

View File

@ -1,5 +0,0 @@
import type { PageLoad } from './$types';
import { tools } from '../../data/api';
export const load: PageLoad = tools.page_load_impl();

View File

@ -1,80 +0,0 @@
<script lang="ts">
import type { PageData } from './$types';
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import type { CompatiblePackage } from 'src/data/types';
import { patches as api_patches } from '../../data/api';
import TreeMenu from '$lib/components/molecules/TreeMenu.svelte';
import TreeMenuButton from '$lib/components/atoms/TreeMenuButton.svelte';
import PatchCell from '$lib/components/molecules/PatchCell.svelte';
import Footer from '$lib/components/molecules/Footer.svelte';
export let data: PageData;
// Needed when someone navigates directly to the page.
api_patches.init(data);
$: ({ patches, packages } = $api_patches);
let current: boolean = false;
function search(findTerm: string | boolean, array: CompatiblePackage[]) {
for (let i = 0; i < array.length; i++) {
if (array[i].name === findTerm) {
return true;
}
}
return false;
}
</script>
<svelte:head>
<title>ReVanced | Patches</title>
<meta content="ReVanced | Patches" name="og:title" />
<meta content="ReVanced | Patches" name="twitter:title" />
</svelte:head>
<main>
<aside in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<TreeMenu title="packages">
{#each packages as pkg}
<TreeMenuButton bind:current name={pkg} />
{/each}
</TreeMenu>
</aside>
<div class="patches-container">
{#each patches as patch, i}
{#if search(current, patch.compatiblePackages) || !current}
<div in:fly={{ x: 10, easing: quintOut, duration: 750, delay: -(300 * 0.85 ** i) + 300 }}>
<PatchCell bind:current {patch} />
</div>
{/if}
{/each}
</div>
</main>
<Footer {...data} />
<style>
main {
margin-inline: auto;
width: min(95%, 100rem);
display: grid;
grid-template-columns: 300px 3fr;
gap: 1.5rem;
padding-bottom: 3rem;
}
.patches-container {
margin-top: 7.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
position: sticky;
z-index: 1;
min-height: calc(100vh - 7.5rem);
}
</style>

View File

@ -1,5 +0,0 @@
import type { PageLoad } from './$types';
import { patches } from '../../data/api';
export const load: PageLoad = patches.page_load_impl();

View File

@ -0,0 +1,84 @@
<script lang="ts">
import { goto } from '$app/navigation';
export let selectedPkg: string | undefined;
export let name: string;
export let searchTerm: string | null;
function handleClick() {
// Assign the selected package. If it's already selected, deselect it.
let path = '/patches';
if (selectedPkg !== name && name !== 'All packages') {
path += `/${name}`;
}
if (searchTerm) {
path += `?s=${searchTerm}`
};
goto(path);
window.scrollTo({ top: 0, behavior: 'smooth' });
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="package"
class:selected={selectedPkg === name || (name === 'All packages' && !selectedPkg)}
on:click={handleClick}
>
{name}
</div>
<style>
.package {
padding: 0.75rem 1rem;
font-size: 0.85rem;
font-weight: 500;
border-radius: 100px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.6rem;
width: 100%;
user-select: none;
transition: background-color 0.4s var(--bezier-one);
color: var(--grey-five);
transition: color 0.3s var(--bezier-one);
}
.selected {
color: var(--accent-color);
transition: color 0.3s var(--bezier-one);
background-color: var(--accent-low-opacity);
}
.package:hover:not(.selected) {
background-color: var(--grey-six);
}
.package:not(.selected):hover {
color: var(--white);
}
@media (max-width: 768px) {
.package {
border-radius: 0px;
font-size: 0.9rem;
padding: 1rem 1rem;
width: 100%;
background-color: transparent;
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
color: var(--grey-five);
border-bottom: 1px solid var(--grey-three);
}
.selected {
color: var(--accent-color);
background-color: var(--accent-low-opacity);
}
.package:not(.selected):hover {
color: var(--grey-five);
}
}
</style>

View File

@ -0,0 +1,53 @@
<div class="menu">
<h6>Packages</h6>
<hr/>
<div class="slot">
<slot />
</div>
</div>
<style>
.menu {
height: calc(100vh - 60px);
width: 100%;
padding: 0px 30px 30px 10px;
display: flex;
flex-direction: column;
position: sticky;
top: 60px;
padding-top: calc(6rem - 60px);
overflow-y: scroll;
}
.menu::-webkit-scrollbar-thumb {
background-color: transparent;
}
.menu:hover::-webkit-scrollbar-thumb {
background-color: var(--accent-color);
}
.slot {
margin-top: 0.75rem;
display: flex;
gap: 1rem;
flex-direction: column;
white-space: normal;
word-break: break-all;
}
h6 {
margin-bottom: 1rem;
color: var(--accent-color);
}
@media (max-width: 768px) {
.menu {
padding: 0.75rem;
height: unset;
}
h6, hr {
display: none;
}
}
</style>

View File

@ -1,10 +1,10 @@
<script lang="ts">
import { slide, fade } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import type { CompatiblePackage, Patch } from 'src/data/types';
import type { Patch } from '$lib/types';
import { friendlyName } from '$util/friendlyName';
export let patch: Patch;
export let current: boolean;
const hasPatchOptions = !!patch.options.length;
let expanded: boolean = false;
</script>
@ -18,58 +18,47 @@
>
<div class="things">
<div class="title">
<h1>
{patch.name
// im sorry
.replace(/-/g, ' ')
.replace(/(?:^|\s)\S/g, (x) => x.toUpperCase())
.replace(/Microg/g, 'MicroG')
.replace(/Hdr/g, 'HDR')
.replace(/Sponsorblock/g, 'SponsorBlock')
.replace(/Tiktok/g, 'TikTok')
.replace(/Vr/g, 'VR')}
</h1>
<h3>{friendlyName(patch.name)}</h3>
</div>
{#if hasPatchOptions}
<img id="arrow" src="/icons/arrow.svg" alt="dropdown" />
{/if}
</div>
<div class="info-container">
{#each patch.compatiblePackages as pkg, i}
{#if current === false}
<a
href="https://play.google.com/store/apps/details?id={pkg.name}"
target="_blank"
rel="noreferrer"
>
<h2>📦 {pkg.name}</h2>
</a>
{/if}
<h5>{patch.description}</h5>
<ul class="info-container">
{#each patch.compatiblePackages as pkg}
<a
href="https://play.google.com/store/apps/details?id={pkg.name}"
target="_blank"
rel="noreferrer"
>
<li class="patch-info">📦 {pkg.name}</li>
</a>
{/each}
<!-- should i hardcode this to get the version of the first package? idk you cant stop me -->
{#if patch.compatiblePackages[0].versions.length}
<h2>
{#if patch.compatiblePackages.length && patch.compatiblePackages[0].versions.length}
<li class="patch-info">
🎯 {patch.compatiblePackages[0].versions.slice(-1)}
</h2>
</li>
{/if}
<h2>🧩 {patch.version}</h2>
{#if !patch.compatiblePackages.length}
<li class="patch-info">🌎 Universal patch</li>
{/if}
{#if hasPatchOptions}
<h2>⚙️ Patch Options</h2>
<li class="patch-info">⚙️ Patch options</li>
{/if}
</div>
<h4>{patch.description}</h4>
</ul>
{#if expanded && hasPatchOptions}
<span transition:fade|local={{ easing: quintOut, duration: 1000 }}>
<div class="options" transition:slide|local={{ easing: quintOut, duration: 500 }}>
{#each patch.options as option}
<div class="option">
<h3>{option.title}</h3>
<h4>{option.description}</h4>
<h5 id="option-title">{option.title}</h5>
<h5>{option.description}</h5>
</div>
{/each}
</div>
@ -78,59 +67,55 @@
</div>
<style>
h1 {
font-weight: 600;
h3 {
margin-right: 0.5rem;
font-size: 1.25rem;
margin-bottom: 0.2rem;
color: var(--accent-color);
}
#option-title {
color: var(--accent-color-two);
}
h2 {
color: var(--accent-color);
.patch-info {
list-style: none;
font-size: 0.8rem;
font-weight: 500;
color: var(--grey-five);
padding: 0.25rem 0.5rem;
border: 1px solid var(--grey-three);
border-radius: 8px;
background-color: var(--grey-two);
padding: 0.2rem 0.4rem;
display: flex;
}
a {
text-decoration: none;
}
a .patch-info:hover {
text-decoration: underline;
color: var(--accent-color-two);
text-decoration-style: wavy;
text-decoration-color: var(--accent-color-two);
}
.info-container {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
margin-bottom: 0.5rem;
margin-left: -0.2rem;
margin-top: 0.5rem;
margin: 0.3rem 0rem;
width: 100%;
}
h3 {
color: var(--accent-color);
font-size: 0.9rem;
margin-bottom: 0.1rem;
font-weight: 500;
}
h4 {
color: var(--grey-five);
font-size: 0.9rem;
font-weight: 400;
margin-top: 0.5rem;
}
.patch-container {
transition: all 2s var(--bezier-one);
background-color: var(--grey-six);
padding: 1.5rem;
border-radius: 8px;
padding: 1.25rem;
border-radius: 12px;
}
.patch-container:active {
background-color: var(--grey-three);
filter: brightness(1.75);
}
.title {
@ -161,6 +146,7 @@
padding: 1rem;
}
/* thanks piknik */
.option + .option {
border-top: 1px solid var(--grey-three);
}

View File

@ -0,0 +1,242 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import { quintOut } from 'svelte/easing';
import { page } from '$app/stores';
import type { PageData } from './$types';
import type { Patch } from '$lib/types';
import { createQuery } from '@tanstack/svelte-query';
import { queries } from '$data/api';
import Meta from '$lib/components/Meta.svelte';
import PackageMenu from '../PackageMenu.svelte';
import Package from '../Package.svelte';
import PatchItem from '../PatchItem.svelte';
import Footer from '$layout/Footer.svelte';
import Search from '$lib/components/Search.svelte';
import FilterChip from '$lib/components/FilterChip.svelte';
import Dialogue from '$lib/components/Dialogue.svelte';
import Query from '$lib/components/Query.svelte';
const query = createQuery(['patches'], queries.patches);
export let data: PageData;
$: ({ selectedPkg } = data);
// Search whatever the s query is from the url
let searchTerm = $page.url.searchParams.get('s');
let searchTermFiltered = searchTerm
?.replace(/\./g, '')
.replace(/\s/g, '')
.replace(/-/g, '')
.toLowerCase();
let timeout: ReturnType<typeof setTimeout>;
let mobilePackages = false;
function checkCompatibility(patch: Patch, pkg: string) {
if (pkg === '') {
return false;
}
return !!patch.compatiblePackages.find((compat) => compat.name === pkg);
}
function searchString(str: string, term: string, filter: RegExp) {
return str.toLowerCase().replace(filter, '').includes(term);
}
function filter(patches: Patch[], pkg: string, search?: string): Patch[] {
if (search === undefined && pkg === '') {
return patches;
}
return patches.filter((patch) => {
// Don't show if the patch doesn't support the selected package
if (pkg && !checkCompatibility(patch, pkg)) {
return false;
}
// Filter based on the search term.
if (search !== undefined) {
return (
searchString(patch.description, search, /\s/g) ||
searchString(patch.name, search, /-/g) ||
patch.compatiblePackages.find((x) => searchString(x.name, search, /\./g))
);
}
return true;
});
}
// Make sure we don't have to filter the patches after every key press
const debounce = () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
// Filter search term for better results (i.e. " Unl O-ck" and "unlock" gives the same results)
searchTermFiltered = searchTerm
?.replace(/\./g, '')
.replace(/\s/g, '')
.replace(/-/g, '')
.toLowerCase();
// Update search URL params
// must use history.pushState instead of goto(), as goto() unselects the search bar
window.history.pushState(null, '', `${window.location.href.split('?')[0]}${searchTerm ? '?s=' + searchTerm : ''}`)
}, 500);
};
</script>
<Meta title="Patches" />
<div class="search">
<div class="search-contain">
<!-- Must bind both variables: we get searchTerm from the text input, -->
<!-- and searchTermFiltered gets cleared with the clear button -->
<Search
bind:searchTerm
bind:searchTermFiltered
title="Search for patches"
on:keyup={debounce}
/>
</div>
</div>
<main>
<div class="filter-chips" in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<FilterChip
selected={!!selectedPkg}
dropdown
on:click={() => (mobilePackages = !mobilePackages)}
>
{selectedPkg || 'Packages'}
</FilterChip>
<!-- <FilterChip check>Universal</FilterChip>
<FilterChip>Patch options</FilterChip> -->
</div>
<Query {query} let:data>
<div class="mobile-packages-Dialogue">
<Dialogue bind:modalOpen={mobilePackages} fullscreen>
<svelte:fragment slot="title">Packages</svelte:fragment>
<div class="mobile-packages">
<span
on:click={() => (mobilePackages = !mobilePackages)}
on:keypress={() => (mobilePackages = !mobilePackages)}
>
<Package {selectedPkg} name="All packages" bind:searchTerm/>
</span>
{#each data.packages as pkg}
<span
on:click={() => (mobilePackages = !mobilePackages)}
on:keypress={() => (mobilePackages = !mobilePackages)}
>
<Package {selectedPkg} name={pkg} bind:searchTerm/>
</span>
{/each}
</div>
</Dialogue>
</div>
<aside in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<PackageMenu>
<span class="packages">
<Package {selectedPkg} name="All packages" bind:searchTerm/>
{#each data.packages as pkg}
<Package {selectedPkg} name={pkg} bind:searchTerm />
{/each}
</span>
</PackageMenu>
</aside>
<div class="patches-container">
{#each filter(data.patches, selectedPkg || '', searchTermFiltered) as patch}
<!-- Trigger new animations when package or search changes (I love Svelte) -->
{#key selectedPkg || searchTermFiltered}
<div in:fly={{ y: 10, easing: quintOut, duration: 750 }}>
<PatchItem {patch} />
</div>
{/key}
{/each}
</div>
</Query>
</main>
<Footer />
<style>
main {
display: grid;
grid-template-columns: 300px 3fr;
width: min(90%, 80rem);
margin-inline: auto;
gap: 1.5rem;
}
.search {
padding-top: 5rem;
padding-bottom: 1.25rem;
background-color: var(--grey-seven);
}
.search-contain {
width: min(90%, 80rem);
margin-inline: auto;
}
.patches-container {
overflow: hidden;
border-radius: 20px;
margin-top: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
position: sticky;
z-index: 1;
min-height: calc(100vh - 6rem);
margin-bottom: 3rem;
}
.filter-chips {
display: none;
}
.mobile-packages {
margin-bottom: -1px;
overflow: hidden;
border-radius: 12px;
border: 1px solid var(--grey-three);
}
@media (min-width: 768px) {
.mobile-packages-Dialogue {
display: none;
}
}
@media (max-width: 768px) {
main {
grid-template-columns: none;
flex-direction: column;
gap: 0;
}
aside {
display: none;
}
.search {
padding-top: 4.5rem;
}
.patches-container {
margin-top: 1rem;
margin-bottom: 1.5rem;
gap: 0.75rem;
}
.filter-chips {
display: flex;
flex-wrap: wrap;
margin-top: 1rem;
gap: 0.75rem;
padding-bottom: 0rem;
}
}
</style>

View File

@ -0,0 +1,8 @@
import type { PageLoad } from './$types';
export const prerender = false;
export const load: PageLoad = async ({ params }) => {
const selectedPkg = params.package || undefined;
return { selectedPkg };
};

8
src/util/dev.ts Normal file
View File

@ -0,0 +1,8 @@
import { dev } from '$app/environment';
// console.log, but only if in dev environment.
export function dev_log(part: string, ...args: any[]) {
if (dev) {
console.log(`[${part}]:`, ...args);
}
}

15
src/util/friendlyName.ts Normal file
View File

@ -0,0 +1,15 @@
export function friendlyName(text: string): string {
return text
.replace(/-/g, ' ')
.replace(/revanced\/revanced/g, '')
.replace(/revanced/g, 'ReVanced')
.replace(/\bcli\b/g, 'CLI')
.replace(/api/g, 'API')
.replace(/microg/g, 'MicroG')
.replace(/hdr/g, 'HDR')
.replace(/sponsorblock/g, 'SponsorBlock')
.replace(/tiktok/g, 'TikTok')
.replace(/vr/g, 'VR')
.replace(/url/g, 'URL')
.replace(/(?:^|\s)\S/g, (x: string) => x.toUpperCase());
}

View File

@ -0,0 +1,38 @@
// @ts-nocheck
import { cubicOut } from 'svelte/easing';
// stolen from https://svelte.dev/repl/6d5239f09b0b4dc6aafeb70606a0fe94?version=3.46.4
// please add this svelte thanks ily <3
export function horizontalSlide(
node,
{ delay = 0, duration = 400, easing = cubicOut, direction = 'block' } = {}
) {
const style = getComputedStyle(node);
const opacity = +style.opacity;
const capitalized_logical_property = `${direction[0].toUpperCase()}${direction.slice(1)}`;
const size_value = parseFloat(style[`${direction}Size`]);
const padding_start_value = parseFloat(style[`padding${capitalized_logical_property}Start`]);
const padding_end_value = parseFloat(style[`padding${capitalized_logical_property}End`]);
const margin_start_value = parseFloat(style[`margin${capitalized_logical_property}Start`]);
const margin_end_value = parseFloat(style[`margin${capitalized_logical_property}End`]);
const border_width_start_value = parseFloat(
style[`border${capitalized_logical_property}StartWidth`]
);
const border_width_end_value = parseFloat(style[`border${capitalized_logical_property}EndWidth`]);
return {
delay,
duration,
easing,
css: (t) =>
'overflow: hidden;' +
`opacity: ${Math.min(t * 20, 1) * opacity};` +
`${direction}-size: ${t * size_value}px;` +
`padding-${direction}-start: ${t * padding_start_value}px;` +
`padding-${direction}-end: ${t * padding_end_value}px;` +
`margin-${direction}-start: ${t * margin_start_value}px;` +
`margin-${direction}-end: ${t * margin_end_value}px;` +
`border-${direction}-start-width: ${t * border_width_start_value}px;` +
`border-${direction}-start-width: ${t * border_width_end_value}px;`
};
}

12
static/_headers Normal file
View File

@ -0,0 +1,12 @@
/*
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self' *.revanced.app; img-src 'self' *.revanced.app avatars.githubusercontent.com; font-src 'self' *.revanced.app fonts.googleapis.com fonts.gstatic.com; style-src 'self' 'unsafe-inline' *.revanced.app fonts.googleapis.com fonts.gstatic.com; script-src 'self' 'unsafe-inline';
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
Cache-Control: public, max-age=604800, stale-while-revalidate=86400, stale-if-error=259200
/_app/immutable
Cache-Control: public, max-age=2419200, stale-while-revalidate=345600, stale-if-error=1036800

1
static/_redirects Normal file
View File

@ -0,0 +1 @@
/patches/* /404.html 200

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

2
static/icons/back.svg Normal file
View File

@ -0,0 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M24 40.3 7.7 24 24 7.7l2.8 2.75L15.3 22h25v4h-25l11.5 11.5Z" fill="#AFD3F4"/></svg>

After

Width:  |  Height:  |  Size: 156 B

1
static/icons/check.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M18.9 36.4 7 24.5l2.9-2.85 9 9L38.05 11.5l2.9 2.85Z" fill="#ACC1D2"/></svg>

After

Width:  |  Height:  |  Size: 147 B

1
static/icons/close.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z" fill="#ACC0D3"/></svg>

After

Width:  |  Height:  |  Size: 283 B

View File

@ -1,2 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#ACC1D2"><g><rect fill="none" height="24" width="24"/></g><g><g><g><g><path d="M16,13c3.09-2.81,6-5.44,6-7.7C22,3.45,20.55,2,18.7,2c-1.04,0-2.05,0.49-2.7,1.25C15.34,2.49,14.34,2,13.3,2 C11.45,2,10,3.45,10,5.3C10,7.56,12.91,10.19,16,13z M13.3,4c0.44,0,0.89,0.21,1.18,0.55L16,6.34l1.52-1.79 C17.81,4.21,18.26,4,18.7,4C19.44,4,20,4.56,20,5.3c0,1.12-2.04,3.17-4,4.99c-1.96-1.82-4-3.88-4-4.99C12,4.56,12.56,4,13.3,4z"/><path d="M19,16h-2c0-1.2-0.75-2.28-1.87-2.7L8.97,11H1v11h6v-1.44l7,1.94l8-2.5v-1C22,17.34,20.66,16,19,16z M3,20v-7h2v7H3z M13.97,20.41L7,18.48V13h1.61l5.82,2.17C14.77,15.3,15,15.63,15,16c0,0-1.99-0.05-2.3-0.15l-2.38-0.79l-0.63,1.9l2.38,0.79 c0.51,0.17,1.04,0.26,1.58,0.26H19c0.39,0,0.74,0.23,0.9,0.56L13.97,20.41z"/></g></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 880 B

1
static/icons/reset.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#ACC1D2"><path d="M14 38v-3h14.45q3.5 0 6.025-2.325Q37 30.35 37 26.9t-2.525-5.775Q31.95 18.8 28.45 18.8H13.7l5.7 5.7-2.1 2.1L8 17.3 17.3 8l2.1 2.1-5.7 5.7h14.7q4.75 0 8.175 3.2Q40 22.2 40 26.9t-3.425 7.9Q33.15 38 28.4 38Z"/></svg>

After

Width:  |  Height:  |  Size: 299 B

1
static/icons/search.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="m39.75 42.75-13.3-13.3Q25 30.65 23 31.35t-4.25.7q-5.65 0-9.525-3.9t-3.875-9.4q0-5.5 3.9-9.4 3.9-3.9 9.4-3.9 5.55 0 9.4 3.9 3.85 3.9 3.85 9.4 0 2.2-.65 4.15-.65 1.95-1.95 3.7l13.35 13.25ZM18.7 28.05q3.85 0 6.55-2.725 2.7-2.725 2.7-6.575t-2.7-6.575Q22.55 9.45 18.7 9.45q-3.95 0-6.675 2.725Q9.3 14.9 9.3 18.75t2.725 6.575Q14.75 28.05 18.7 28.05Z" fill="#ACC0D3"/></svg>

After

Width:  |  Height:  |  Size: 438 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#ACC1D2"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19.43 12.98c.04-.32.07-.64.07-.98 0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.09-.16-.26-.25-.44-.25-.06 0-.12.01-.17.03l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.06-.02-.12-.03-.18-.03-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.09.16.26.25.44.25.06 0 .12-.01.17-.03l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.06.02.12.03.18.03.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-1.98-1.71c.04.31.05.52.05.73 0 .21-.02.43-.05.73l-.14 1.13.89.7 1.08.84-.7 1.21-1.27-.51-1.04-.42-.9.68c-.43.32-.84.56-1.25.73l-1.06.43-.16 1.13-.2 1.35h-1.4l-.19-1.35-.16-1.13-1.06-.43c-.43-.18-.83-.41-1.23-.71l-.91-.7-1.06.43-1.27.51-.7-1.21 1.08-.84.89-.7-.14-1.13c-.03-.31-.05-.54-.05-.74s.02-.43.05-.73l.14-1.13-.89-.7-1.08-.84.7-1.21 1.27.51 1.04.42.9-.68c.43-.32.84-.56 1.25-.73l1.06-.43.16-1.13.2-1.35h1.39l.19 1.35.16 1.13 1.06.43c.43.18.83.41 1.23.71l.91.7 1.06-.43 1.27-.51.7 1.21-1.07.85-.89.7.14 1.13zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,4 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 149 165" width="149" height="165" fill="none">
<path d="M 88.61 160.69 L 147.72 8.14 C 149.06 4.67 146.5 0.93 142.78 0.93 L 125.65 0.93 C 123.41 0.93 121.41 2.34 120.66 4.45 L 74.5 134.43 L 28.34 4.45 C 27.59 2.34 25.59 0.93 23.35 0.93 L 6.22 0.93 C 2.5 0.93 -0.06 4.67 1.28 8.14 L 60.39 160.69 C 61.18 162.73 63.14 164.07 65.33 164.07 L 83.68 164.07 C 85.86 164.07 87.82 162.73 88.61 160.69 Z" fill="#ffffff"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M 39.29 0.93 C 36.07 0.93 33.98 4.31 35.42 7.19 C 46.03 28.41 59.79 55.94 70.64 77.63 C 72.24 80.82 76.78 80.82 78.38 77.63 C 89.22 55.94 102.99 28.41 113.6 7.19 C 115.04 4.31 112.94 0.93 109.73 0.93 L 39.29 0.93 Z" fill="#8bc3f5"/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(2.75,0,0,2.75,-224,-224)">
<g id="Shape">
<g id="V-Shape" serif:id="V Shape" transform="matrix(1,0,0,1,1.01146e-05,-0.0493818)">
<path d="M173.764,83.287C173.936,82.894 173.899,82.44 173.664,82.08C173.429,81.721 173.028,81.504 172.598,81.504C169.925,81.504 165.262,81.504 163.619,81.504C163.258,81.504 162.932,81.717 162.787,82.048C160.01,88.371 133.856,147.939 128.832,159.381C128.687,159.712 128.361,159.925 128,159.925C127.639,159.925 127.312,159.712 127.167,159.381C122.144,147.939 95.989,88.371 93.213,82.048C93.068,81.717 92.741,81.504 92.381,81.504C90.737,81.504 86.075,81.504 83.402,81.504C82.972,81.504 82.571,81.721 82.336,82.08C82.101,82.44 82.064,82.894 82.236,83.287C87.557,95.432 118.354,165.726 121.906,173.833C122.109,174.296 122.566,174.595 123.072,174.595C125.196,174.595 130.803,174.595 132.928,174.595C133.433,174.595 133.891,174.296 134.094,173.833C137.645,165.727 168.443,95.432 173.764,83.287Z" style="fill:white;"/>
</g>
<g id="Diamond" transform="matrix(-1.54007,-1.88604e-16,1.88604e-16,-1.54007,327.925,209.689)">
<path d="M129.203,52.157C129.329,51.938 129.563,51.803 129.816,51.803C130.069,51.803 130.303,51.938 130.429,52.157C133.25,57.043 144.956,77.318 147.777,82.203C147.903,82.422 147.903,82.692 147.777,82.912C147.65,83.131 147.416,83.266 147.163,83.266L112.469,83.266C112.216,83.266 111.982,83.131 111.856,82.912C111.729,82.692 111.729,82.422 111.856,82.203C114.676,77.318 126.382,57.043 129.203,52.157Z" style="fill: #9ED5FF;"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 761 B

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 745 KiB

View File

@ -1,21 +1,20 @@
import adapter from '@sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';
import { wrap } from './src/_vercel-moment.js';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess(),
preprocess: [vitePreprocess()],
kit: {
// adapter-static has vercel detection, but that does not let you set a custom 404 page easily.
// Instead, we have to use a wrapper that generates a vercel config if on vercel...
adapter: wrap(adapter, {
pages: "public",
fallback: "404.html"
adapter: adapter({
pages: 'public',
fallback: '404.html'
}),
env: {
publicPrefix: 'RV'
}
}
};

View File

@ -1,10 +0,0 @@
= AsciiDoc Example
= Another title
*Bold text*
== Section 1
* Item 1
* Item 2

View File

@ -1,4 +0,0 @@
# Another Page
# Hello
Content!

View File

@ -1,4 +0,0 @@
# Nesting sample
# First one
Text

View File

@ -1,4 +0,0 @@
# Nesting sample
# Third one
Text

Some files were not shown because too many files have changed in this diff Show More