feat: Adds sidebar to articles view.
Some checks failed
CI / release (push) Failing after 2m34s

This commit is contained in:
2025-04-15 14:41:10 -04:00
parent 9159ecc834
commit 500f4746e8
6 changed files with 105 additions and 11 deletions

View File

@@ -54,6 +54,29 @@ extension Item where M == ArticleMetadata {
}
}
extension Array where Element == Item<ArticleMetadata> {
func years() -> Set<String> {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy"
return reduce(into: Set()) { set, item in
let date = dateFormatter.string(from: item.getDate())
set.insert(date)
if let updatedDate = item.getUpdatedDate() {
set.insert(dateFormatter.string(from: updatedDate))
}
}
}
func uniqueTags() -> Set<String> {
reduce(into: Set()) { set, item in
for tag in item.metadata.tags {
set.insert(tag)
}
}
}
}
// NOTE: Most of these are taken from https://github.com/loopwerk/loopwerk.io
extension String {

View File

@@ -2,6 +2,8 @@ import Foundation
import HTML
import Saga
// TODO: Try a sidebar with navigation to tags & years.
/// Displays lists of articles sectioned by a key.
///
///
@@ -33,6 +35,29 @@ struct ArticleGrid: NodeConvertible {
self.header = header
}
private var allItems: [Item<ArticleMetadata>] {
articles.reduce(into: [Item<ArticleMetadata>]()) { $0 += $1.value }
}
private var sortedYears: [String] {
allItems.years().sorted { $0 > $1 }
}
private var sortedTags: [String] {
allItems.uniqueTags().sorted { $0 < $1 }
}
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") {
span(class: "mx-8") { label }
}
}
}
func asNode() -> Node {
baseLayout(
canocicalURL: canocicalURL,
@@ -41,12 +66,37 @@ struct ArticleGrid: NodeConvertible {
rssLink: rssLink,
extraHeader: extraHeader
) {
div(class: "mt-8 mb-10 bg-slate-800 border border-slate-200 rounded-lg") {
articles.map { key, articles in
section {
header(key)
div(class: "grid gap-10 mx-6 mb-16") {
articles.map(renderArticle)
div(class: "grid grid-cols-4 mt-10 mb-10") {
// Sidebar
div(class: "overflow-auto bg-slate-800 rounded-l-lg border border-slate-200") {
// 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 in
sidebarLink(tag, href: "/articles/tag/\(tag)")
}
}
}
// Articles
div(class: "col-span-3 bg-slate-800 border border-slate-200 rounded-r-lg") {
articles.map { key, articles in
section {
header(key)
div(class: "grid gap-10 mx-6 mb-16") {
articles.map(renderArticle)
}
}
}
}

View File

@@ -24,7 +24,8 @@ func baseLayout(
body(class: "text-white text-lg font-avenir \(section.rawValue)") {
siteHeader(section)
div(class: "mb-auto mx-10") {
// mx-10
div(class: "mb-auto") {
children()
}
if section == .articles {

View File

@@ -9,13 +9,13 @@ func renderArticles(context: ItemsRenderingContext<ArticleMetadata>) -> NodeConv
let articlesPerYear = Dictionary(grouping: context.items, by: { dateFormatter.string(from: $0.date) })
let sortedByYearDescending = articlesPerYear.sorted { $0.key > $1.key }
return ArticleGrid(
articles: sortedByYearDescending,
return baseRenderArticles(
sortedByYearDescending,
canocicalURL: "/articles/",
title: "Articles",
rssLink: "",
extraHeader: "",
header: yearHeader(_:)
label: yearHeader(_:)
)
}
@@ -74,6 +74,25 @@ private func yearHeader(_ year: String) -> Node {
}
}
private func baseRenderArticles(
_ articles: [(key: String, value: [Item<ArticleMetadata>])],
canocicalURL: String,
title pageTitle: String,
rssLink: String = "",
extraHeader: NodeConvertible = Node.fragment([]),
@NodeBuilder label: @escaping (String) -> Node = { _ in Node.fragment([]) }
) -> NodeConvertible {
ArticleGrid(
articles: articles,
canocicalURL: canocicalURL,
title: pageTitle,
rssLink: rssLink,
extraHeader: extraHeader
) { key in
label(key)
}
}
private func baseRenderArticles(
_ articles: (key: String, value: [Item<ArticleMetadata>]),
canocicalURL: String,

View File

@@ -1,6 +1,7 @@
import HTML
import Saga
// TODO: Currently not used, remove in the future.
struct TagGrid: NodeConvertible {
let items: [Item<ArticleMetadata>]