Files
memorium/islands/KMenu/commands/create_book.ts
Max Richter c232794cc0 feat: add Book type to schema and sidebar
- Add BookContentSchema with headline, subtitle, bookBody, reviewBody fields
- Add BookResource type and update GenericResourceSchema
- Add books to sidebar navigation with Bookmark Tabs icon
2026-02-10 18:17:32 +01:00

135 lines
3.7 KiB
TypeScript

import { MenuEntry } from "@islands/KMenu/types.ts";
import { debounce } from "@lib/helpers.ts";
import { getCookie } from "@lib/string.ts";
import { BookResource } from "@lib/marka/schema.ts";
interface HardcoverBook {
id: string;
slug: string;
title: string;
subtitle?: string;
author_names: string[];
series_names?: string[];
release_year?: string;
image?: string;
}
export const createNewBook: MenuEntry = {
title: "Create new book",
meta: "",
icon: "IconSquareRoundedPlus",
cb: (state) => {
state.menus["input_book"] = {
title: "Search",
entries: [],
};
state.menus["loading"] = {
title: "Search",
entries: [
{
title: "Loading",
icon: "IconLoader2",
cb() {},
},
],
};
state.activeMenu.value = "input_book";
state.activeState.value = "normal";
let currentQuery: string;
const search = debounce(async function search(query: string) {
try {
currentQuery = query;
if (query.length < 2) {
state.menus["input_book"] = {
title: "Search",
entries: [
{
title: "Type at least 2 characters...",
cb: () => {},
},
],
};
state.activeMenu.value = "input_book";
return;
}
const response = await fetch("/api/hardcover/query?q=" + encodeURIComponent(query));
if (!response.ok) {
throw new Error(await response.text());
}
const books = await response.json() as HardcoverBook[];
if (query !== currentQuery) return;
if (books.length === 0) {
state.menus["input_book"] = {
title: "Search",
entries: [
{
title: "No results found",
cb: () => {},
},
],
};
} else {
state.menus["input_book"] = {
title: "Search",
entries: books.map((b) => {
return {
title: `${b.title}${b.release_year ? ` (${b.release_year})` : ""}${b.author_names?.length ? ` - ${b.author_names.join(", ")}` : ""}`,
cb: async () => {
try {
state.activeState.value = "loading";
const response = await fetch("/api/books/" + b.id, {
method: "POST",
});
if (!response.ok) {
throw new Error(await response.text());
}
const book = await response.json() as BookResource;
unsub();
globalThis.location.href = "/books/" + book.name;
} catch (_e: unknown) {
state.activeState.value = "error";
state.loadingText.value = _e instanceof Error ? _e.message : "Unknown error";
}
},
};
}),
};
}
state.activeMenu.value = "input_book";
} catch (_e: unknown) {
state.activeState.value = "error";
state.loadingText.value = _e instanceof Error ? _e.message : "Unknown error";
}
}, 500);
const unsub = state.commandInput.subscribe((value) => {
if (!value) {
state.menus["input_book"] = {
title: "Search",
entries: [],
};
state.activeMenu.value = "input_book";
return;
}
state.activeMenu.value = "loading";
search(value);
});
},
visible: () => {
if (!getCookie("session_cookie")) return false;
if (
!globalThis?.location?.pathname?.includes("book") &&
globalThis?.location?.pathname !== "/"
) return false;
return true;
},
};