import Foundation import HTML import Saga /// Displays lists of articles sectioned by a key. /// /// struct ArticleGrid: NodeConvertible { let articles: [(key: String, value: [Item])] let canocicalURL: String let title: String let rssLink: String let extraHeader: NodeConvertible let renderArticle: (Item) -> Node let header: (String) -> Node init( articles: [(key: String, value: [Item])], canocicalURL: String, title: String, rssLink: String, extraHeader: any NodeConvertible = Node.fragment([]), renderArticle: @escaping (Item) -> Node = { renderArticleForGrid(article: $0, border: false) }, header: @escaping (String) -> Node ) { self.articles = articles self.canocicalURL = canocicalURL self.title = title self.rssLink = rssLink self.extraHeader = extraHeader self.renderArticle = renderArticle self.header = header } private var allItems: [Item] { articles.reduce(into: [Item]()) { $0 += $1.value } } private var sortedYears: [String] { allItems.years().sorted { $0 > $1 } } private var sortedTags: [(String, Int)] { allItems.uniqueTagsWithCount() } private func sidebarLink( _ label: String, href: String ) -> Node { a( class: "text-slate-300 font-semibold [&:hover]:text-slate-200", href: href ) { div(class: "flex w-full p-2 [&:hover]:border-b border-orange-400\(href == canocicalURL ? " active" : "")") { span(class: "mx-8") { label } } } } func asNode() -> Node { baseLayout( canocicalURL: canocicalURL, section: .articles, title: title, rssLink: rssLink, extraHeader: extraHeader ) { div(class: "grid grid-cols-4") { // Sidebar div(class: "overflow-auto border-r border-slate-200") { section(class: "pt-2") { sidebarLink("All Articles", href: "/articles/") } // Years section(class: "pt-2") { div(class: "flex ps-2") { span(class: "mt-2 ps-2 font-extrabold text-slate-400") { "YEARS" } } sortedYears.map { year in sidebarLink(year, href: "/articles/\(year)/") } } // Tags section(class: "pt-2") { div(class: "flex ps-2 pt-2") { span(class: "mt-2 ps-2 font-extrabold text-slate-400") { "TAGS" } } sortedTags.map { tag, count in sidebarLink("\(tag) (\(count))", href: "/articles/tag/\(tag)/") } } } // Articles div(class: "col-span-3") { articles.map { key, articles in section { header(key) div(class: "grid gap-10 mx-6 mb-16") { articles.map(renderArticle) } } } } } } } }