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> {
|
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
|
||||||
|
|||||||
@@ -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)/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,22 +56,12 @@ 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
|
// TODO: Explore search being hidden / triggered by a button and hover above
|
||||||
// the page content.
|
// the page content.
|
||||||
div(class: "font-avenir w-full pt-4 px-8", id: "search") {}
|
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")
|
div(class: "mt-2 mb-0 w-full border-b border-slate-200")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func footer(_ rssLink: String) -> Node {
|
private func footer(_ rssLink: String) -> Node {
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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>
|
|
||||||
""")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 }
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user