28dd7e2400
Signed-off-by: Tobias Erbshäußer <tobias@tesoft.dev>
177 lines
6.5 KiB
TypeScript
177 lines
6.5 KiB
TypeScript
import {capitalize, sendApiGet, TesoftComponent} from "../scripts/main.ts";
|
|
import {TesoftLoaderSection} from "./loader-section.ts";
|
|
import {TesoftPagination} from "./pagination.ts";
|
|
import {TesoftError} from "./error.ts";
|
|
import {TesoftTag} from "./tag.ts";
|
|
import {formatArticleDate, getBadgeText} from "./blog-article.ts";
|
|
import {TesoftBadge} from "./badge.ts";
|
|
|
|
export class TesoftBlogArticleList extends TesoftComponent {
|
|
private readonly listItemTemplate: HTMLTemplateElement;
|
|
private readonly loaderSection: TesoftLoaderSection;
|
|
private readonly listDiv: HTMLDivElement;
|
|
private readonly tagsDiv: HTMLDivElement;
|
|
private readonly pagination: TesoftPagination;
|
|
private readonly error: TesoftError;
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
const template = document.getElementById("tesoft-blog-article-list-template") as HTMLTemplateElement;
|
|
const templateContent = template.content;
|
|
|
|
const shadowRoot = this.attachShadow({mode: "open"});
|
|
shadowRoot.appendChild(templateContent.cloneNode(true));
|
|
|
|
this.listItemTemplate = document.getElementById("tesoft-blog-article-list-item-template") as HTMLTemplateElement;
|
|
this.loaderSection = shadowRoot.querySelector<TesoftLoaderSection>("tesoft-loader-section")!;
|
|
this.listDiv = shadowRoot.querySelector<HTMLDivElement>(".list")!;
|
|
this.tagsDiv = shadowRoot.querySelector<HTMLDivElement>(".tags")!;
|
|
this.pagination = shadowRoot.querySelector<TesoftPagination>("tesoft-pagination")!;
|
|
this.error = shadowRoot.querySelector<TesoftError>("tesoft-error")!;
|
|
}
|
|
|
|
async load(page: number, tags: string[], onParametersChange: (page: number, tags: string[]) => void) {
|
|
try {
|
|
this.tagsDiv.innerHTML = "";
|
|
for (const tagName of await this.fetchTags()) {
|
|
const tag = document.createElement("tesoft-tag") as TesoftTag;
|
|
tag.textContent = tagName;
|
|
this.tagsDiv.appendChild(tag);
|
|
}
|
|
} catch (error: any) {
|
|
// Ignore
|
|
}
|
|
|
|
this.pagination.addEventListener("update-page", (e) => {
|
|
const page = (e as CustomEvent).detail.page;
|
|
onParametersChange(page, tags);
|
|
this.reload(page, tags, onParametersChange);
|
|
});
|
|
|
|
await this.reload(page, tags, onParametersChange);
|
|
}
|
|
|
|
private async reload(page: number, tags: string[], onParametersChange: (page: number, tags: string[]) => void) {
|
|
const itemsPerPage = 20;
|
|
|
|
for (const tag of this.tagsDiv.children as any as TesoftTag[]) {
|
|
const tagName = tag.textContent.trim();
|
|
|
|
tag.onclick = () => {
|
|
const tagSet = new Set(tags);
|
|
if (tagSet.has(tagName)) {
|
|
tagSet.delete(tagName);
|
|
} else {
|
|
tagSet.add(tagName);
|
|
}
|
|
const newTags = Array.from(tagSet);
|
|
|
|
onParametersChange(page, newTags);
|
|
this.reload(page, newTags, onParametersChange);
|
|
};
|
|
|
|
if (tags.includes(tagName)) {
|
|
tag.selected = "";
|
|
} else {
|
|
tag.selected = null;
|
|
}
|
|
}
|
|
|
|
this.loaderSection.reset();
|
|
this.listDiv.innerHTML = "";
|
|
this.pagination.classList.add("hidden");
|
|
|
|
const parameters: Record<string, any> = {
|
|
offset: (page - 1) * itemsPerPage,
|
|
limit: itemsPerPage,
|
|
};
|
|
if (tags) {
|
|
parameters.tags = tags.join(",");
|
|
}
|
|
|
|
try {
|
|
const response = await sendApiGet("blog", parameters);
|
|
if (response.ok) {
|
|
const body = await response.json();
|
|
|
|
if (body.articles.length === 0) {
|
|
throw "No articles found.";
|
|
}
|
|
|
|
for (const article of body.articles) {
|
|
const anchor = this.listItemTemplate.content.children[0].cloneNode(true) as HTMLAnchorElement;
|
|
anchor.href = `/blog?id=${article.id}`;
|
|
|
|
const heading = anchor.querySelector<HTMLHeadingElement>("h3")!;
|
|
heading.textContent = article.title;
|
|
const badgeText = getBadgeText(body.status);
|
|
if (badgeText) {
|
|
const badge = document.createElement("tesoft-badge") as TesoftBadge;
|
|
badge.textContent = badgeText;
|
|
heading.appendChild(badge);
|
|
}
|
|
|
|
anchor.querySelector<HTMLDivElement>(".dates")!.textContent = formatArticleDate(new Date(article.date), article['mod-date'] ? new Date(article['mod-date']) : null);
|
|
|
|
const tags = anchor.querySelector<HTMLDivElement>(".tags")!;
|
|
for (const tagName of article.tags) {
|
|
const tag = document.createElement("tesoft-tag") as TesoftTag;
|
|
tag.textContent = tagName;
|
|
tag.selected = "";
|
|
tag.disabled = "";
|
|
tags.appendChild(tag);
|
|
}
|
|
|
|
this.listDiv.appendChild(anchor);
|
|
}
|
|
|
|
this.pagination.curPage = page;
|
|
this.pagination.lastPage = Math.ceil(body.total / itemsPerPage);
|
|
this.pagination.classList.remove("hidden");
|
|
} else {
|
|
const body = await response.json();
|
|
throw `${capitalize(body.message)}.`;
|
|
}
|
|
} catch (error: any) {
|
|
this.error.textContent = error;
|
|
this.error.classList.remove("hidden");
|
|
this.listDiv.classList.add("hidden");
|
|
}
|
|
|
|
this.loaderSection.finish();
|
|
}
|
|
|
|
private async fetchTags(): Promise<string[]> {
|
|
const tags: string[] = [];
|
|
|
|
let offset = 0;
|
|
const limit = 50;
|
|
|
|
while (true) {
|
|
const response = await sendApiGet("blog/tags", {
|
|
offset: offset,
|
|
limit: limit,
|
|
});
|
|
if (!response.ok) {
|
|
const body = await response.json();
|
|
throw `${capitalize(body.message)}.`;
|
|
}
|
|
|
|
const body = await response.json();
|
|
|
|
for (const tag of body.tags) {
|
|
tags.push(tag);
|
|
}
|
|
|
|
if (body.tags.length < limit) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return tags;
|
|
}
|
|
}
|
|
|
|
customElements.define("tesoft-blog-article-list", TesoftBlogArticleList);
|