227 lines
6.8 KiB
TypeScript
227 lines
6.8 KiB
TypeScript
import { base64ToBytes } from "@components/admin/util";
|
|
import { pageRepo } from "@components/repo/page";
|
|
import type { ListPage, PageList } from "@components/types/page";
|
|
import { get } from "svelte/store";
|
|
import yaml from "js-yaml";
|
|
|
|
export class OpenEditPage {
|
|
public content: string = "";
|
|
public frontmatter: { [key: string]: string | string[] | Date } = $state({});
|
|
public dirty: boolean = $state(false);
|
|
|
|
public readonly fileType: string;
|
|
|
|
public constructor(
|
|
private manager: PageManager,
|
|
public readonly pageId: number,
|
|
public readonly pageTitle: string,
|
|
public readonly sha: string,
|
|
public readonly originalContent: string,
|
|
public readonly path: string
|
|
) {
|
|
this.fileType = this.path.split(".").pop() || "md";
|
|
|
|
this.content = this.removeFrontmatter(originalContent);
|
|
this.frontmatter = this.parseFrontmatter(originalContent);
|
|
}
|
|
|
|
public async save(): Promise<void> {
|
|
if (!this.dirty) {
|
|
return;
|
|
}
|
|
|
|
let contentToSave = "";
|
|
if (this.frontmatter) {
|
|
contentToSave += "---\n";
|
|
contentToSave += yaml.dump(this.frontmatter);
|
|
contentToSave += "---\n\n";
|
|
}
|
|
contentToSave += this.content;
|
|
|
|
await get(pageRepo).updatePage(this.pageId, contentToSave, this.sha, prompt("Was hast du geändert?", `Updated ${this.pageTitle}`) ?? `Updated ${this.pageTitle}`, this.manager.branch);
|
|
this.dirty = false;
|
|
this.manager.reloadImages();
|
|
}
|
|
|
|
public focus(): boolean {
|
|
let index = this.manager.pages.indexOf(this);
|
|
|
|
if (index === this.manager.openPageIndex) {
|
|
return true;
|
|
}
|
|
|
|
this.manager.openPageIndex = this.manager.pages.indexOf(this);
|
|
return false;
|
|
}
|
|
|
|
private parseFrontmatter(content: string): { [key: string]: string | string[] | Date } {
|
|
const lines = content.split("\n");
|
|
let inFrontmatter = false;
|
|
const frontmatterLines: string[] = [];
|
|
|
|
for (const line of lines) {
|
|
if (line.trim() === "---") {
|
|
if (inFrontmatter) {
|
|
break; // End of frontmatter
|
|
}
|
|
inFrontmatter = true;
|
|
continue;
|
|
}
|
|
if (inFrontmatter) {
|
|
frontmatterLines.push(line);
|
|
}
|
|
}
|
|
|
|
if (frontmatterLines.length === 0) {
|
|
return {};
|
|
}
|
|
|
|
try {
|
|
// You'll need to install js-yaml: npm install js-yaml @types/js-yaml
|
|
return (yaml.load(frontmatterLines.join("\n")) || {}) as { [key: string]: string | string[] | Date };
|
|
} catch (error) {
|
|
console.error("Failed to parse YAML frontmatter:", error);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
private removeFrontmatter(content: string): string {
|
|
const lines = content.split("\n");
|
|
let inFrontmatter = false;
|
|
const result: string[] = [];
|
|
|
|
for (const line of lines) {
|
|
if (line.trim() === "---") {
|
|
inFrontmatter = !inFrontmatter;
|
|
continue;
|
|
}
|
|
if (!inFrontmatter) {
|
|
result.push(line);
|
|
}
|
|
}
|
|
|
|
return result.join("\n").trim();
|
|
}
|
|
}
|
|
|
|
export interface DirTree {
|
|
name: string;
|
|
dirs: { [key: string]: DirTree };
|
|
files: { [key: string]: ListPage };
|
|
}
|
|
|
|
export class PageManager {
|
|
public reloadImages() {
|
|
this.updater = this.updater + 1;
|
|
}
|
|
public branch: string = $state("master");
|
|
public pages: OpenEditPage[] = $state([]);
|
|
public branches: string[] = $state([]);
|
|
|
|
constructor() {
|
|
this.reloadBranches();
|
|
}
|
|
|
|
public reloadBranches() {
|
|
get(pageRepo)
|
|
.getBranches()
|
|
.then((branches) => {
|
|
this.branches = branches;
|
|
});
|
|
}
|
|
|
|
private updater = $state(0);
|
|
|
|
public openPageIndex: number = $state(-1);
|
|
public pagesLoad = $derived(get(pageRepo).listPages(this.branch).then(this.convertToTree).then(this._t(this.updater)));
|
|
public imagesLoad = $derived(get(pageRepo).listImages(this.branch).then(this._t(this.updater)));
|
|
|
|
private _t<T>(n: number): (v: T) => T {
|
|
return (v: T) => v;
|
|
}
|
|
|
|
public selectedPage = $derived(this.openPageIndex >= 0 ? this.pages[this.openPageIndex] : undefined);
|
|
|
|
private convertToTree(pages: PageList): DirTree {
|
|
const tree: DirTree = { dirs: {}, files: {}, name: "/" };
|
|
|
|
pages.forEach((page) => {
|
|
const pathParts = page.path.split("/").filter((part) => part !== "");
|
|
let current = tree;
|
|
|
|
// Navigate/create directory structure
|
|
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
const dir = pathParts[i];
|
|
if (!current.dirs[dir]) {
|
|
current.dirs[dir] = { dirs: {}, files: {}, name: dir };
|
|
}
|
|
current = current.dirs[dir];
|
|
}
|
|
|
|
// Add file to the final directory
|
|
const fileName = pathParts[pathParts.length - 1];
|
|
current.files[fileName] = page;
|
|
});
|
|
|
|
return tree;
|
|
}
|
|
|
|
public async openPage(pageId: number) {
|
|
const existingPage = this.existingPage(pageId);
|
|
if (existingPage) {
|
|
existingPage.focus();
|
|
return;
|
|
}
|
|
|
|
let r = await get(pageRepo).getPage(pageId, this.branch);
|
|
if (!r) {
|
|
return;
|
|
}
|
|
|
|
const newPage = new OpenEditPage(this, pageId, r.name, r.sha, new TextDecoder().decode(base64ToBytes(r.content)), r.path);
|
|
this.pages.push(newPage);
|
|
newPage.focus();
|
|
}
|
|
|
|
public existingPage(pageId: number): OpenEditPage | undefined {
|
|
return this.pages.find((page) => page.pageId === pageId);
|
|
}
|
|
|
|
public closePage(index: number) {
|
|
if (index < 0 || index >= this.pages.length) {
|
|
return;
|
|
}
|
|
|
|
const page = this.pages[index];
|
|
if (page.dirty) {
|
|
if (!confirm(`The page "${page.pageTitle}" has unsaved changes. Are you sure you want to close it?`)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.pages.splice(index, 1);
|
|
if (this.openPageIndex >= index) {
|
|
this.openPageIndex = Math.max(0, this.openPageIndex - 1);
|
|
}
|
|
|
|
if (this.openPageIndex < 0 && this.pages.length > 0) {
|
|
this.openPageIndex = 0;
|
|
}
|
|
|
|
if (this.pages.length === 0) {
|
|
this.openPageIndex = -1;
|
|
}
|
|
}
|
|
|
|
public async createPage(path: string, newPageName: string): Promise<void> {
|
|
await get(pageRepo).createFile(path, this.branch, newPageName, newPageName);
|
|
this.branch = this.branch;
|
|
}
|
|
|
|
public anyUnsavedChanges() {
|
|
return this.pages.some((page) => page.dirty);
|
|
}
|
|
}
|
|
|
|
export const manager = $state(new PageManager());
|