feat: Adds more navigation items to sidebar and removes top navigation bar.
All checks were successful
CI / release (push) Successful in 6m53s

This commit is contained in:
2025-04-16 09:16:24 -04:00
parent e9c1dfa2e5
commit c0a8e3ced8
8 changed files with 60 additions and 38 deletions

View File

@@ -56,6 +56,8 @@ extension Item where M == ArticleMetadata {
extension Array where Element == Item<ArticleMetadata> { extension Array where Element == Item<ArticleMetadata> {
/// Iterate through the aritcles, getting all the years that articles
/// have been written or updated.
func years() -> Set<String> { func years() -> Set<String> {
let dateFormatter = DateFormatter() let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy" dateFormatter.dateFormat = "yyyy"
@@ -68,6 +70,7 @@ extension Array where Element == Item<ArticleMetadata> {
} }
} }
/// Iterate through the articles and get the unique tags.
func uniqueTags() -> Set<String> { func uniqueTags() -> Set<String> {
reduce(into: Set()) { set, item in reduce(into: Set()) { set, item in
for tag in item.metadata.tags { for tag in item.metadata.tags {
@@ -75,6 +78,14 @@ extension Array where Element == Item<ArticleMetadata> {
} }
} }
} }
/// Iterate through the articles and get the unique tags along with the count of
/// how many times the tag is used.
func uniqueTagsWithCount() -> [(String, Int)] {
let tags = flatMap { $0.metadata.tags }
let tagsWithCounts = tags.reduce(into: [:]) { $0[$1, default: 0] += 1 }
return tagsWithCounts.sorted { $0.1 > $1.1 }
}
} }
// NOTE: Most of these are taken from https://github.com/loopwerk/loopwerk.io // NOTE: Most of these are taken from https://github.com/loopwerk/loopwerk.io

View File

@@ -41,16 +41,19 @@ struct ArticleGrid: NodeConvertible {
allItems.years().sorted { $0 > $1 } allItems.years().sorted { $0 > $1 }
} }
private var sortedTags: [String] { private var sortedTags: [(String, Int)] {
allItems.uniqueTags().sorted { $0 < $1 } allItems.uniqueTagsWithCount()
} }
private func sidebarLink( private func sidebarLink(
_ label: String, _ label: String,
href: String href: String
) -> Node { ) -> Node {
a(class: "text-slate-300 font-semibold [&:hover]:text-slate-200", href: href) { a(
div(class: "flex w-full p-2 [&:hover]:border-b border-orange-400") { 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 } span(class: "mx-8") { label }
} }
} }
@@ -64,16 +67,19 @@ struct ArticleGrid: NodeConvertible {
rssLink: rssLink, rssLink: rssLink,
extraHeader: extraHeader extraHeader: extraHeader
) { ) {
div(class: "grid grid-cols-4 -mt-2") { div(class: "grid grid-cols-4") {
// Sidebar // Sidebar
div(class: "overflow-auto border-r border-slate-200") { div(class: "overflow-auto border-r border-slate-200") {
section(class: "pt-2") {
sidebarLink("All Articles", href: "/articles/")
}
// Years // Years
section(class: "pt-2") { section(class: "pt-2") {
div(class: "flex ps-2") { div(class: "flex ps-2") {
span(class: "mt-2 ps-2 font-extrabold text-slate-400") { "YEARS" } span(class: "mt-2 ps-2 font-extrabold text-slate-400") { "YEARS" }
} }
sortedYears.map { year in sortedYears.map { year in
sidebarLink(year, href: "/articles/\(year)") sidebarLink(year, href: "/articles/\(year)/")
} }
} }
// Tags // Tags
@@ -81,8 +87,8 @@ struct ArticleGrid: NodeConvertible {
div(class: "flex ps-2 pt-2") { div(class: "flex ps-2 pt-2") {
span(class: "mt-2 ps-2 font-extrabold text-slate-400") { "TAGS" } span(class: "mt-2 ps-2 font-extrabold text-slate-400") { "TAGS" }
} }
sortedTags.map { tag in sortedTags.map { tag, count in
sidebarLink(tag, href: "/articles/tag/\(tag)") sidebarLink("\(tag) (\(count))", href: "/articles/tag/\(tag)/")
} }
} }
} }

View File

@@ -31,6 +31,14 @@ func baseLayout(
if section == .articles { if section == .articles {
footer(rssLink) footer(rssLink)
} }
// NOTE: These need to stay at / near bottom of page, so that icons are
// generated properly.
script(src: "https://unpkg.com/lucide@latest")
Node.raw("""
<script>
lucide.createIcons();
</script>
""")
} }
} }
] ]
@@ -48,21 +56,11 @@ private func siteHeader(_ section: Section) -> Node {
} }
} }
} }
nav(class: "menu flex justify-between") {
if section != .home { // TODO: Explore search being hidden / triggered by a button and hover above
div(class: "pt-6") { // the page content.
ul(class: "flex flex-wrap gap-x-2 lg:gap-x-5") { div(class: "font-avenir w-full p-4 px-8", id: "search") {}
li { div(class: "mt-2 mb-0 w-full border-b border-slate-200")
a(class: section == .articles ? "active" : "", href: "/articles/") { "Articles" }
}
}
}
}
// TODO: Explore search being hidden / triggered by a button and hover above
// the page content.
div(class: "font-avenir w-full pt-4 px-8", id: "search") {}
div(class: "mt-2 mb-0 w-full border-b border-slate-200")
}
} }
} }

View File

@@ -103,6 +103,11 @@ func renderArticle(context: ItemRenderingContext<ArticleMetadata>) -> Node {
extraHeader: generateHeader(.article(context.item)) extraHeader: generateHeader(.article(context.item))
) { ) {
article(class: "pt-8 mx-10") { article(class: "pt-8 mx-10") {
div(class: "relative") {
a(class: "absolute top-4 right-4", href: "/articles", id: "close") {
i(customAttributes: ["data-lucide": "x"])
}
}
div(class: "bg-slate-800 py-10") { div(class: "bg-slate-800 py-10") {
div(class: "mx-10") { div(class: "mx-10") {
h1 { context.item.title } h1 { context.item.title }

View File

@@ -30,7 +30,7 @@ func renderHome(body: String) -> Node {
div(class: "my-24 font-avenir leading-[1.25] font-thin text-center [&>h1>strong]:font-bold") { div(class: "my-24 font-avenir leading-[1.25] font-thin text-center [&>h1>strong]:font-bold") {
Node.raw(body) Node.raw(body)
} }
div { div(class: "px-10") {
div(class: "bg-slate-800 p-10 rounded-lg border border-slate-400") { div(class: "bg-slate-800 p-10 rounded-lg border border-slate-400") {
h2 { "Quick Links" } h2 { "Quick Links" }
div(class: "grid lg:grid-cols-2 gap-6") { div(class: "grid lg:grid-cols-2 gap-6") {
@@ -92,12 +92,6 @@ func renderHome(body: String) -> Node {
} }
} }
} }
script(src: "https://unpkg.com/lucide@latest")
Node.raw("""
<script>
lucide.createIcons();
</script>
""")
} }
} }

View File

@@ -18,7 +18,7 @@ struct TagGrid: NodeConvertible {
} }
// Grid items. // Grid items.
div(class: "grid sm:grid-cols-2 lg:grid-cols-4 gap-4 px-6 pb-6") { div(class: "grid sm:grid-cols-2 lg:grid-cols-4 gap-4 px-6 pb-6") {
uniqueTagsWithCount(items).map { tag, count in items.uniqueTagsWithCount().map { tag, count in
div { div {
a(class: "bg-slate-900 [&:hover]:bg-slate-800", href: "/articles/tag/\(tag)") { a(class: "bg-slate-900 [&:hover]:bg-slate-800", href: "/articles/tag/\(tag)") {
div(class: "flex flex-row justify-between bg-slate-900 [&:hover]:bg-slate-800 py-2 px-4 border-2 border-orange-400 rounded-lg") { div(class: "flex flex-row justify-between bg-slate-900 [&:hover]:bg-slate-800 py-2 px-4 border-2 border-orange-400 rounded-lg") {
@@ -38,9 +38,3 @@ struct TagGrid: NodeConvertible {
} }
} }
} }
func uniqueTagsWithCount(_ articles: [Item<ArticleMetadata>]) -> [(String, Int)] {
let tags = articles.flatMap { $0.metadata.tags }
let tagsWithCounts = tags.reduce(into: [:]) { $0[$1, default: 0] += 1 }
return tagsWithCounts.sorted { $0.1 > $1.1 }
}

View File

@@ -116,6 +116,12 @@ body {
@apply bg-slate-900 font-avenir text-xl; @apply bg-slate-900 font-avenir text-xl;
} }
/* Apply border to sidebar links, when page is active. */
a.active,
div.active {
@apply border-b-2 border-orange-400;
}
h1 { h1 {
@apply text-6xl pb-2; @apply text-6xl pb-2;
} }
@@ -197,3 +203,11 @@ blockquote p {
pre { pre {
@apply mb-6; @apply mb-6;
} }
#close {
@apply text-slate-300;
}
#close:hover {
@apply text-slate-400;
}

File diff suppressed because one or more lines are too long