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("tesoft-loader-section")!; this.listDiv = shadowRoot.querySelector(".list")!; this.tagsDiv = shadowRoot.querySelector(".tags")!; this.pagination = shadowRoot.querySelector("tesoft-pagination")!; this.error = shadowRoot.querySelector("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 = { 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("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(".dates")!.textContent = formatArticleDate(new Date(article.date), article['mod-date'] ? new Date(article['mod-date']) : null); const tags = anchor.querySelector(".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 { 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);