chore: merge branch dev
to main
🎉
@ -1,5 +1,7 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/public
|
||||
/images
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
|
13
.github/workflows/refresh.yaml
vendored
@ -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
@ -4,8 +4,6 @@ node_modules
|
||||
/public
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
/_docs_src
|
||||
/static/docs
|
||||
|
@ -1,6 +1,8 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/testing-docs
|
||||
/public
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
|
@ -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" } }]
|
||||
}
|
||||
|
14
ci-build.sh
@ -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
After Width: | Height: | Size: 81 KiB |
3
netlify.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[build]
|
||||
publish = "public/"
|
||||
command = "npm run build"
|
2496
package-lock.json
generated
33
package.json
@ -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"
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
118
src/app.css
@ -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
@ -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;
|
||||
}
|
@ -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
@ -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);
|
||||
}
|
@ -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`.
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
};
|
||||
|
@ -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
@ -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>
|
37
src/layout/Hero/HeroImage.svelte
Normal 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>
|
61
src/layout/Hero/HeroSection.svelte
Normal 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>
|
@ -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;
|
88
src/layout/Navbar/NavButton.svelte
Normal 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>
|
326
src/layout/Navbar/NavHost.svelte
Normal 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>
|
80
src/lib/components/Button.svelte
Normal 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>
|
158
src/lib/components/Dialogue.svelte
Normal 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>
|
||||
|
||||
|
50
src/lib/components/FilterChip.svelte
Normal 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>
|
26
src/lib/components/Meta.svelte
Normal 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>
|
13
src/lib/components/Picture.svelte
Normal 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>
|
27
src/lib/components/Query.svelte
Normal 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}
|
90
src/lib/components/Search.svelte
Normal 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>
|
@ -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;
|
16
src/lib/components/Svg.svelte
Normal 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>
|
@ -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 |
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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');
|
||||
}
|
@ -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[];
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
|
@ -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">
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
103
src/routes/contributors/ContributorSection.svelte
Normal 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>
|
@ -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() });
|
||||
|
@ -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>
|
||||
|
56
src/routes/docs/DocsNavNode.svelte
Normal 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>
|
37
src/routes/docs/DocsNavTree.svelte
Normal 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>
|
@ -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;
|
||||
};
|
||||
|
@ -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>
|
||||
|
95
src/routes/docs/documentation.scss
Normal 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;
|
||||
}
|
||||
}
|
207
src/routes/docs/documentation.server.ts
Normal 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;
|
||||
}
|
35
src/routes/docs/documentation.shared.ts
Normal 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;
|
||||
}
|
@ -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>
|
||||
|
@ -1,5 +0,0 @@
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
import { tools } from '../../data/api';
|
||||
|
||||
export const load: PageLoad = tools.page_load_impl();
|
@ -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>
|
@ -1,5 +0,0 @@
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
import { patches } from '../../data/api';
|
||||
|
||||
export const load: PageLoad = patches.page_load_impl();
|
84
src/routes/patches/Package.svelte
Normal 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>
|
53
src/routes/patches/PackageMenu.svelte
Normal 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>
|
@ -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);
|
||||
}
|
242
src/routes/patches/[[package]]/+page.svelte
Normal 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>
|
8
src/routes/patches/[[package]]/+page.ts
Normal 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
@ -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
@ -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());
|
||||
}
|
38
src/util/horizontalSlide.js
Normal 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
@ -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
@ -0,0 +1 @@
|
||||
/patches/* /404.html 200
|
BIN
static/embed.png
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.9 KiB |
2
static/icons/back.svg
Normal 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
@ -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
@ -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 |
@ -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
@ -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
@ -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 |
@ -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 |
@ -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 |
Before Width: | Height: | Size: 745 KiB |
@ -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'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,10 +0,0 @@
|
||||
= AsciiDoc Example
|
||||
|
||||
= Another title
|
||||
|
||||
*Bold text*
|
||||
|
||||
== Section 1
|
||||
|
||||
* Item 1
|
||||
* Item 2
|
@ -1,4 +0,0 @@
|
||||
# Another Page
|
||||
|
||||
# Hello
|
||||
Content!
|
@ -1,4 +0,0 @@
|
||||
# Nesting sample
|
||||
|
||||
# First one
|
||||
Text
|
@ -1,4 +0,0 @@
|
||||
# Nesting sample
|
||||
|
||||
# Third one
|
||||
Text
|