feat: Adds more navigation items to sidebar and removes top navigation bar.
All checks were successful
CI / release (push) Successful in 6m53s
All checks were successful
CI / release (push) Successful in 6m53s
This commit is contained in:
@@ -56,6 +56,8 @@ extension Item where M == 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> {
|
||||
let dateFormatter = DateFormatter()
|
||||
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> {
|
||||
reduce(into: Set()) { set, item in
|
||||
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
|
||||
|
||||
@@ -41,16 +41,19 @@ struct ArticleGrid: NodeConvertible {
|
||||
allItems.years().sorted { $0 > $1 }
|
||||
}
|
||||
|
||||
private var sortedTags: [String] {
|
||||
allItems.uniqueTags().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") {
|
||||
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 }
|
||||
}
|
||||
}
|
||||
@@ -64,16 +67,19 @@ struct ArticleGrid: NodeConvertible {
|
||||
rssLink: rssLink,
|
||||
extraHeader: extraHeader
|
||||
) {
|
||||
div(class: "grid grid-cols-4 -mt-2") {
|
||||
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)")
|
||||
sidebarLink(year, href: "/articles/\(year)/")
|
||||
}
|
||||
}
|
||||
// Tags
|
||||
@@ -81,8 +87,8 @@ struct ArticleGrid: NodeConvertible {
|
||||
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)")
|
||||
sortedTags.map { tag, count in
|
||||
sidebarLink("\(tag) (\(count))", href: "/articles/tag/\(tag)/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,14 @@ func baseLayout(
|
||||
if section == .articles {
|
||||
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 {
|
||||
div(class: "pt-6") {
|
||||
ul(class: "flex flex-wrap gap-x-2 lg:gap-x-5") {
|
||||
li {
|
||||
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")
|
||||
}
|
||||
|
||||
// TODO: Explore search being hidden / triggered by a button and hover above
|
||||
// the page content.
|
||||
div(class: "font-avenir w-full p-4 px-8", id: "search") {}
|
||||
div(class: "mt-2 mb-0 w-full border-b border-slate-200")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,6 +103,11 @@ func renderArticle(context: ItemRenderingContext<ArticleMetadata>) -> Node {
|
||||
extraHeader: generateHeader(.article(context.item))
|
||||
) {
|
||||
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: "mx-10") {
|
||||
h1 { context.item.title }
|
||||
|
||||
@@ -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") {
|
||||
Node.raw(body)
|
||||
}
|
||||
div {
|
||||
div(class: "px-10") {
|
||||
div(class: "bg-slate-800 p-10 rounded-lg border border-slate-400") {
|
||||
h2 { "Quick Links" }
|
||||
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>
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ struct TagGrid: NodeConvertible {
|
||||
}
|
||||
// Grid items.
|
||||
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 {
|
||||
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") {
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -116,6 +116,12 @@ body {
|
||||
@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 {
|
||||
@apply text-6xl pb-2;
|
||||
}
|
||||
@@ -197,3 +203,11 @@ blockquote p {
|
||||
pre {
|
||||
@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
Reference in New Issue
Block a user