210 lines
8.0 KiB
Svelte
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> |