diff --git a/package.json b/package.json
index 18c8d45..8b96e23 100644
--- a/package.json
+++ b/package.json
@@ -28,16 +28,17 @@
"tailwindcss": "^3.3.2"
},
"dependencies": {
+ "@codemirror/lang-markdown": "^6.2.2",
+ "@ddietr/codemirror-themes": "^1.4.2",
"astro": "^3.1.1",
"color": "^4.2.3",
- "crypto-js": "^4.1.1",
"flowbite": "^1.7.0",
"flowbite-svelte": "^0.39.2",
"flowbite-svelte-icons": "^0.2.5",
- "gitea-js": "^1.20.1",
"moment": "^2.29.4",
"sharp": "^0.32.6",
"svelte-awesome": "^3.2.0",
+ "svelte-codemirror-editor": "^1.1.0",
"svelte-spa-router": "^3.3.0",
"zod": "^3.21.4"
}
diff --git a/public/favicon.svg b/public/favicon.svg
deleted file mode 100644
index f157bd1..0000000
--- a/public/favicon.svg
+++ /dev/null
@@ -1,9 +0,0 @@
-
diff --git a/src/components/admin/App.svelte b/src/components/admin/App.svelte
index e54ea7d..f1887f0 100644
--- a/src/components/admin/App.svelte
+++ b/src/components/admin/App.svelte
@@ -11,6 +11,7 @@
'/login': wrap({asyncComponent: () => import('./pages/Login.svelte'), conditions: detail => get(tokenStore) == ""}),
'/event/:id': wrap({asyncComponent: () => import('./pages/Event.svelte'), conditions: detail => get(tokenStore) != ""}),
'/event/:id/generate': wrap({asyncComponent: () => import('./pages/Generate.svelte'), conditions: detail => get(tokenStore) != ""}),
+ '/edit': wrap({asyncComponent: () => import('./pages/Edit.svelte'), conditions: detail => get(tokenStore) != ""}),
'*': wrap({asyncComponent: () => import('./pages/NotFound.svelte')})
}
diff --git a/src/components/admin/components/TypeAheadSearch.svelte b/src/components/admin/components/TypeAheadSearch.svelte
index c08a615..31b5fea 100644
--- a/src/components/admin/components/TypeAheadSearch.svelte
+++ b/src/components/admin/components/TypeAheadSearch.svelte
@@ -1,14 +1,13 @@
+
\ No newline at end of file
diff --git a/src/components/admin/pages/Home.svelte b/src/components/admin/pages/Home.svelte
index fbe8ab8..27576f9 100644
--- a/src/components/admin/pages/Home.svelte
+++ b/src/components/admin/pages/Home.svelte
@@ -21,11 +21,12 @@
- Eventplanner
+ Admin-Tool
+ Edit Pages
Permissions
showLogoutModal = true} class="cursor-pointer select-none">Logout
diff --git a/src/components/admin/pages/Login.svelte b/src/components/admin/pages/Login.svelte
index 34db74b..3b3104e 100644
--- a/src/components/admin/pages/Login.svelte
+++ b/src/components/admin/pages/Login.svelte
@@ -3,7 +3,7 @@
import {fly} from "svelte/transition";
import {replace} from "svelte-spa-router";
import {EyeOutline, EyeSlashOutline, EyeSolid} from "flowbite-svelte-icons";
- import {tokenStore} from "../repo/repo.js";
+ import {fetchWithToken, tokenStore} from "../repo/repo.js";
let show = false;
let loading = false;
@@ -12,7 +12,7 @@
async function handleSubmit() {
loading = true;
- let res = await fetch("https://steamwar.de/eventplanner-api/data", {headers: {"X-SW-Auth": value}})
+ let res = await fetchWithToken(value, "/data")
loading = false;
if(res.ok) {
$tokenStore = value;
diff --git a/src/components/admin/pages/edit/Editor.svelte b/src/components/admin/pages/edit/Editor.svelte
new file mode 100644
index 0000000..142ea35
--- /dev/null
+++ b/src/components/admin/pages/edit/Editor.svelte
@@ -0,0 +1,50 @@
+
+
+{#await pageFuture}
+
+{:then p}
+
+
+
+
+
+ Save
+
+
+
+
+
+
+{:catch error}
+ {error.message}
+{/await}
\ No newline at end of file
diff --git a/src/components/admin/repo/page.ts b/src/components/admin/repo/page.ts
new file mode 100644
index 0000000..a4c5580
--- /dev/null
+++ b/src/components/admin/repo/page.ts
@@ -0,0 +1,44 @@
+import type {Page, PageList} from "../types/page.ts";
+import {fetchWithToken} from "./repo.ts";
+import {PageListSchema, PageSchema} from "../types/page.ts";
+import {bytesToBase64} from "../util.ts";
+import {branches} from "../stores/stores.ts";
+
+export class PageRepo {
+ constructor(private token: string) {}
+
+ public async listPages(branch: string = "master"): Promise {
+ return await fetchWithToken(this.token, `/page?branch=${branch}`)
+ .then(value => value.json())
+ .then(value => PageListSchema.parse(value))
+ }
+
+ public async getPage(id: number, branch: string = "master"): Promise {
+ return await fetchWithToken(this.token, `/page/${id}?branch=${branch}`)
+ .then(value => value.json())
+ .then(value => PageSchema.parse(value))
+ }
+
+ public async updatePage(id: number, content: string, sha: string, message: string, branch: string = "master"): Promise {
+ await fetchWithToken(this.token, `/page/${id}?branch=${branch}`, {
+ method: "PUT",
+ body: JSON.stringify({
+ content: bytesToBase64(new TextEncoder().encode(content)),
+ sha, message
+ })
+ })
+ }
+
+ public async getBranches(): Promise {
+ return await fetchWithToken(this.token, "/page/branch")
+ .then(value => value.json())
+ }
+
+ public async createBranch(branch: string): Promise {
+ await fetchWithToken(this.token, `/page/branch`, {method: "POST", body: JSON.stringify({branch})})
+ }
+
+ public async deleteBranch(branch: string): Promise {
+ await fetchWithToken(this.token, `/page/branch`, {method: "DELETE", body: JSON.stringify({branch})})
+ }
+}
\ No newline at end of file
diff --git a/src/components/admin/repo/repo.ts b/src/components/admin/repo/repo.ts
index 2dbf045..f497f3c 100644
--- a/src/components/admin/repo/repo.ts
+++ b/src/components/admin/repo/repo.ts
@@ -2,14 +2,17 @@ import {derived, writable} from "svelte/store";
import {EventRepo} from "./event.js";
import {FightRepo} from "./fight.js";
import {PermsRepo} from "./perms.js";
+import {PageRepo} from "./page.ts";
export { EventRepo } from "./event.js"
-export const fetchWithToken = (token: string, url: string, params: RequestInit = {}) => fetch(`https://steamwar.de/eventplanner-api${url}`, {...params, headers: {"X-SW-Auth": token, "Content-Type": "application/json", ...params.headers}});
+export const apiUrl = import.meta.env.PUBLIC_API_SERVER;
+export const fetchWithToken = (token: string, url: string, params: RequestInit = {}) => fetch(`${apiUrl}${url}`, {...params, headers: {"Authorization": "Bearer " + (token), "Content-Type": "application/json", ...params.headers}});
export const tokenStore = writable(localStorage.getItem("sw-api-token") ?? "")
tokenStore.subscribe((value) => localStorage.setItem("sw-api-token", value))
export const eventRepo = derived(tokenStore, ($token) => new EventRepo($token))
export const fightRepo = derived(tokenStore, ($token) => new FightRepo($token))
export const permsRepo = derived(tokenStore, ($token) => new PermsRepo($token))
+export const pageRepo = derived(tokenStore, ($token) => new PageRepo($token))
diff --git a/src/components/admin/stores/cached.ts b/src/components/admin/stores/cached.ts
index ba88b26..4eca3c9 100644
--- a/src/components/admin/stores/cached.ts
+++ b/src/components/admin/stores/cached.ts
@@ -29,7 +29,7 @@ export function cached(normal: T, init: () => Promise): Cached {
};
}
-export function cachedFamily(normal: K, init: (T) => Promise): (T) => Cached {
+export function cachedFamily(normal: K, init: (arg0: T) => Promise): (arg: T) => Cached {
const stores: Map> = new Map();
return (arg: T) => {
if(stores.has(arg)) {
diff --git a/src/components/admin/stores/stores.ts b/src/components/admin/stores/stores.ts
index 100a837..eb6a862 100644
--- a/src/components/admin/stores/stores.ts
+++ b/src/components/admin/stores/stores.ts
@@ -4,28 +4,27 @@ import {cached, cachedFamily} from "./cached.js";
import type {Team} from "../types/team.js";
import {TeamSchema} from "../types/team.js";
import {get, writable} from "svelte/store";
-import {permsRepo, tokenStore} from "../repo/repo.js";
+import {apiUrl, fetchWithToken, pageRepo, permsRepo, tokenStore} from "../repo/repo.js";
import {z} from "zod";
-export const schemTypes = cached([], () => {
- return fetch("https://steamwar.de/eventplanner-api/data/schematicTypes", {headers: {"X-SW-Auth": get(tokenStore)}})
- .then(res => res.json())
-})
+export const schemTypes = cached([], () =>
+ fetchWithToken(get(tokenStore), `/data/schematicTypes`)
+ .then(res => res.json()))
export const players = cached([], async () => {
- const res = await fetch("https://steamwar.de/eventplanner-api/data/users", {headers: {"X-SW-Auth": get(tokenStore)}});
+ const res = await fetchWithToken(get(tokenStore), `/data/users`);
return z.array(PlayerSchema).parse(await res.json());
})
export const gamemodes = cached([], async () => {
- const res = await fetch("https://steamwar.de/eventplanner-api/data/gamemodes", {headers: {"X-SW-Auth": get(tokenStore)}});
+ const res = await fetchWithToken(get(tokenStore), `/data/gamemodes`);
return z.array(z.string()).parse(await res.json());
})
export const maps = cachedFamily([], async (gamemode) => {
if (get(gamemodes).every(value => value !== gamemode)) return [];
- const res = await fetch(`https://steamwar.de/eventplanner-api/data/gamemodes/${gamemode}/maps`, {headers: {"X-SW-Auth": get(tokenStore)}});
+ const res = await fetchWithToken(get(tokenStore), `/data/gamemodes/${gamemode}/maps`);
if (!res.ok) {
return [];
} else {
@@ -34,14 +33,19 @@ export const maps = cachedFamily([], async (gamemode) => {
})
export const groups = cached([], async () => {
- const res = await fetch("https://steamwar.de/eventplanner-api/data/groups", {headers: {"X-SW-Auth": get(tokenStore)}});
+ const res = await fetchWithToken(get(tokenStore), `/data/groups`);
return z.array(z.string()).parse(await res.json());
})
export const teams = cached([], async () => {
- const res = await fetch("https://steamwar.de/eventplanner-api/team", {headers: {"X-SW-Auth": get(tokenStore)}});
+ const res = await fetchWithToken(get(tokenStore), `/team`);
return z.array(TeamSchema).parse(await res.json());
})
+export const branches = cached([], async () => {
+ const res = await get(pageRepo).getBranches();
+ return z.array(z.string()).parse(res);
+})
+
export const isWide = writable(window.innerWidth >= 640);
window.addEventListener("resize", () => isWide.set(window.innerWidth >= 640));
diff --git a/src/components/admin/types/page.ts b/src/components/admin/types/page.ts
new file mode 100644
index 0000000..4c7992a
--- /dev/null
+++ b/src/components/admin/types/page.ts
@@ -0,0 +1,27 @@
+import {z} from "zod";
+
+export const ListPageSchema = z.object({
+ path: z.string(),
+ name: z.string(),
+ sha: z.string(),
+ downloadUrl: z.string().url(),
+ id: z.number().positive()
+});
+
+export type ListPage = z.infer;
+
+export const PageListSchema = z.array(ListPageSchema)
+
+export type PageList = z.infer;
+
+export const PageSchema = z.object({
+ path: z.string(),
+ name: z.string(),
+ sha: z.string(),
+ downloadUrl: z.string().url(),
+ content: z.string(),
+ size: z.number().gte(0),
+ id: z.number().positive()
+})
+
+export type Page = z.infer;
\ No newline at end of file
diff --git a/src/components/admin/util.ts b/src/components/admin/util.ts
index d5cfc1e..31056e1 100644
--- a/src/components/admin/util.ts
+++ b/src/components/admin/util.ts
@@ -1,8 +1,23 @@
import Color from "color";
import type {Team} from "./types/team.js";
+import type {ListPage, PageList} from "./types/page.ts";
export const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
+export const nameRegex = new RegExp("(?!.*\/).+(?=\\.md)");
+
+export function mapToMap(pages: PageList): Map {
+ const map = new Map();
+ for (const page of pages) {
+ let folder = page.path.substring(0, page.path.indexOf(nameRegex.exec(page.path)[0]));
+ if (!map.has(folder)) {
+ map.set(folder, []);
+ }
+ map.get(folder).push(page);
+ }
+ return map;
+}
+
export function colorFromTeam(team: Team): string {
switch (team.color) {
case "1":
@@ -47,3 +62,14 @@ export function lighten(color: string) {
export function brightness(color: string) {
return Color(color).isLight()
}
+
+export function base64ToBytes(base64: string) {
+ const binString = atob(base64);
+ // @ts-ignore
+ return Uint8Array.from(binString, (m) => m.codePointAt(0));
+}
+
+export function bytesToBase64(bytes: Uint8Array) {
+ const binString = String.fromCodePoint(...bytes);
+ return btoa(binString);
+}
diff --git a/src/layouts/Basic.astro b/src/layouts/Basic.astro
index 5d4a7b6..6f77abe 100644
--- a/src/layouts/Basic.astro
+++ b/src/layouts/Basic.astro
@@ -1,7 +1,11 @@
---
import {astroI18n} from "astro-i18n";
+import icon from '../images/logo.png';
+import {getImage} from "astro:assets";
const { title, description } = Astro.props.frontmatter || Astro.props;
+
+const iconImage = await getImage({src: icon, height: 32, width: 32, format: 'png', quality: 100});
---
@@ -11,6 +15,7 @@ const { title, description } = Astro.props.frontmatter || Astro.props;
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+
{title}
diff --git a/src/layouts/NavbarLayout.astro b/src/layouts/NavbarLayout.astro
index 77f6c5b..1b0912c 100644
--- a/src/layouts/NavbarLayout.astro
+++ b/src/layouts/NavbarLayout.astro
@@ -4,6 +4,7 @@ import { Image } from 'astro:assets';
import '../styles/button.css';
import localLogo from "../images/logo.png"
import {l, t} from "astro-i18n";
+import {YoutubeSolid} from "flowbite-svelte-icons"
const { title } = Astro.props;
---
@@ -13,7 +14,7 @@ const { title } = Astro.props;
+ before:bg-black before:absolute before:top-0 before:left-0 before:bottom-0 before:right-0 before:-z-10 before:scale-y-0 before:transition-transform before:origin-top">
- © SteamWar.de
+ © SteamWar.de - {new Date().getFullYear()}
diff --git a/src/pages/index.astro b/src/pages/index.astro
index db163ce..5123da5 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -2,7 +2,7 @@
import NavbarLayout from "../layouts/NavbarLayout.astro";
import { Image } from "astro:assets";
-import localBau from "../images/2022-03-28_13.18.25.png";
+import localBau from "../images/bau.jpg";
import {l, t} from "astro-i18n";
import {CaretRight, Archive, Rocket, Bell} from "@astropub/icons"
---
@@ -121,8 +121,9 @@ import {CaretRight, Archive, Rocket, Bell} from "@astropub/icons"
}
.card {
- @apply w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg
- dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100;
+ @apply w-72 border-2 bg-zinc-50 border-gray-100 flex flex-col items-center p-8 m-4 rounded-xl shadow-lg transition-transform duration-300 ease-in-out
+ dark:bg-zinc-900 dark:border-gray-800 dark:text-gray-100
+ hover:scale-105;
>h1 {
@apply text-xl font-bold underline mt-4;
}