Files
Website/src/components/admin/pages/Edit.svelte
Chaoscaot a75b5b7c09
All checks were successful
SteamWarCI Build successful
Fix Branch Creation
2025-01-26 10:24:20 +01:00

210 lines
8.0 KiB
Svelte

<!--
- This file is a part of the SteamWar software.
-
- Copyright (C) 2023 SteamWar.de-Serverteam
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script lang="ts">
import { preventDefault } from 'svelte/legacy';
import {ArrowLeftOutline} from "flowbite-svelte-icons";
import {Button, Card, Navbar, NavBrand, Spinner} from "flowbite-svelte";
import {mapToMap, nameRegex} from "../util.ts";
import TypeAheadSearch from "../components/TypeAheadSearch.svelte";
import {branches} from "@stores/stores.ts";
import Editor from "./edit/Editor.svelte";
import {pageRepo} from "@repo/page.ts";
let selected: number | null = $state(null);
let selectedBranch: string = $state("master");
let searchValue: string = $state("");
let dirty = $state(false);
let selectedPath: string | null = $state(null);
let pathSearchValue: string = $state("");
async function createBranch(name: string | null = null): Promise<string> {
return new Promise(async (resolve) => {
if (!name) {
name = prompt("Branch name:");
if (!name) {
resolve("");
return;
}
}
if (name) {
selected = null;
await $pageRepo.createBranch(name);
let inter = setInterval(() => {
branches.reload();
if ($branches.includes(name!)) {
selectedBranch = name!;
searchValue = "";
clearInterval(inter);
resolve(name!);
}
}, 1000);
}
})
}
function changePage(id: number) {
if (dirty) {
if (confirm("You have unsaved changes. Are you sure you want to change the page?")) {
selected = id;
dirty = false;
}
} else {
selected = id;
}
}
async function deleteBranch(con: boolean) {
if (selectedBranch !== "master") {
let conf = con || confirm("Are you sure you want to delete this branch?");
if (conf) {
await $pageRepo.deleteBranch(selectedBranch);
let inter = setInterval(() => {
branches.reload();
if (!$branches.includes(selectedBranch)) {
selectedBranch = "master";
searchValue = "";
clearInterval(inter);
}
}, 1000);
}
} else {
alert("You can't delete the master branch");
}
}
async function createFile() {
let name = prompt("File name:", "[Name].md");
if (name) {
await $pageRepo.createFile(`${selectedPath}${name}`, selectedBranch);
reload();
}
}
function reload() {
const w = selectedBranch;
selectedBranch = "###!";
selectedBranch = w;
}
async function newAnnouncement() {
const title = prompt("Title: ");
if (!title) {
return;
}
const slug = title.toLowerCase().replace(/ /g, "-");
const branch = await createBranch(slug)
selectedPath = "announcements/de/"
await $pageRepo.createFile(`${selectedPath}${slug}.md`, branch, slug, title);
reload();
const pages = await $pageRepo.listPages(branch);
const page = pages.find(page => page.path === `${selectedPath}${slug}.md`);
if (page) {
changePage(page.id);
} else {
alert("Error creating page");
}
}
let pagesFuture = $derived($pageRepo.listPages(selectedBranch));
let availableBranches = $derived($branches.map((branch) => ({
name: branch,
value: branch
})));
</script>
<div class="flex flex-col h-screen overflow-scroll">
<Navbar>
<NavBrand href="#">
<ArrowLeftOutline></ArrowLeftOutline>
<span class="ml-4 self-center whitespace-nowrap text-xl font-semibold dark:text-white">
Edit Pages
</span>
</NavBrand>
</Navbar>
<div class="p-4 flex-1">
<div class="grid md:grid-cols-3 grid-cols-1 h-full gap-8">
<Card class="h-full flex flex-col !max-w-full">
{#await pagesFuture}
<Spinner/>
{:then pages}
{@const pagesMap = mapToMap(pages)}
<div class="border-b border-b-gray-600 pb-2 flex justify-between">
<div>
<TypeAheadSearch items={availableBranches} bind:selected={selectedBranch} bind:searchValue/>
<TypeAheadSearch items={Array.from(pagesMap.keys()).map(value => ({value, name: value}))}
bind:selected={selectedPath} bind:searchValue={pathSearchValue}
maxItems={Number.MAX_VALUE} leftText={true}/>
</div>
<div>
{#if selectedBranch !== "master"}
<Button onclick={() => createFile()} color="alternative" disabled={!selectedPath}>Create File
</Button>
<Button onclick={() => deleteBranch(false)} color="none">Delete Branch</Button>
{:else}
<Button onclick={() => createBranch()}>Create Branch</Button>
{/if}
</div>
</div>
<ul>
{#if (selectedPath)}
{@const value = pagesMap.get(selectedPath) || []}
{#each value as page}
{@const nameRegexExec = nameRegex.exec(page.path)}
{@const match = nameRegexExec ? nameRegexExec[0] : ""}
{@const startIndex = page.path.indexOf(match)}
{@const endIndex = startIndex + match.length}
<li class="p-4 transition-colors hover:bg-gray-700 cursor-pointer"
onclick={preventDefault(() => changePage(page.id))}>
<span class:text-orange-600={selected === page.id}>{page.path.substring(0, startIndex)}</span><span
class="text-white"
class:!text-orange-500={selected === page.id}>{match}</span><span
class:text-orange-600={selected === page.id}>{page.path.substring(endIndex, page.path.length)}</span>
</li>
{/each}
{:else}
<Button onclick={newAnnouncement}>Neue Ankündigung</Button>
{/if}
</ul>
{:catch error}
<p>{error.message}</p>
{/await}
</Card>
<Card class="!max-w-full" style="grid-column: 2/4">
{#if selected}
<Editor pageId={selected} bind:branch={selectedBranch} on:reload={reload} bind:dirty/>
{/if}
</Card>
</div>
</div>
</div>