feat: Initial working site, with single network article.

This commit is contained in:
2025-04-02 14:24:40 -04:00
parent e66f29f910
commit 74147f8b67
16 changed files with 629 additions and 3015 deletions

View File

@@ -5,58 +5,72 @@ import PathKit
import SagaParsleyMarkdownReader
import SagaSwimRenderer
func permalink(item: Item<ArticleMetadata>) {
// Insert the publication year into the permalink.
// If the `relativeDestination` was "articles/looking-for-django-cms/index.html", then it becomes "articles/2009/looking-for-django-cms/index.html"
var components = item.relativeDestination.components
components.insert("\(Calendar.current.component(.year, from: item.date))", at: 1)
item.relativeDestination = Path(components: components)
}
func removingBreaks<M>(item: Item<M>) {
// remove explicit <br /> from items that show up likely due to how prettier formats
// markdown files inside of neovim.
item.body = item.body.removeBreaks
}
@main
struct Run {
static func main() async throws {
// try await Saga(input: "content", output: "deploy")
// // All markdown files within the "articles" subfolder will be parsed to html,
// // using ArticleMetadata as the Item's metadata type.
// // Furthermore we are only interested in public articles.
// .register(
// folder: "articles",
// metadata: ArticleMetadata.self,
// readers: [.parsleyMarkdownReader],
// itemProcessor: sequence(removingBreaks, publicationDateInFilename, permalink),
// filter: \.public,
// writers: [
// .itemWriter(swim(renderArticle)),
// .listWriter(swim(renderArticles)),
// .tagWriter(swim(renderTag), tags: \.metadata.tags),
// .yearWriter(swim(renderYear)),
// // Atom feed for all articles, and a feed per tag
// .listWriter(
// atomFeed(
// title: SiteMetadata.name,
// author: SiteMetadata.author,
// baseURL: SiteMetadata.url,
// summary: \.metadata.summary
// ),
// output: "feed.xml"
// ),
// .tagWriter(
// atomFeed(
// title: SiteMetadata.name,
// author: SiteMetadata.author, baseURL: SiteMetadata.url, summary: \.metadata.summary
// ),
// output: "tag/[key]/feed.xml",
// tags: \.metadata.tags
// )
// ]
// )
// // All the remaining markdown files will be parsed to html,
// // using the default EmptyMetadata as the Item's metadata type.
// .register(
// metadata: PageMetadata.self,
// readers: [.parsleyMarkdownReader],
// itemProcessor: removingBreaks,
// itemWriteMode: .keepAsFile, // need to keep 404.md as 404.html, not 404/index.html
// writers: [.itemWriter(swim(renderPage))]
// )
//
// // Run the steps we registered above
// .run()
// // All the remaining files that were not parsed to markdown, so for example images, raw html files and css,
// // are copied as-is to the output folder.
// .staticFiles()
try await Saga(input: "content", output: "deploy")
// All markdown files within the "articles" subfolder will be parsed to html,
// using ArticleMetadata as the Item's metadata type.
// Furthermore we are only interested in public articles.
.register(
folder: "articles",
metadata: ArticleMetadata.self,
readers: [.parsleyMarkdownReader],
itemProcessor: sequence(removingBreaks, publicationDateInFilename, permalink),
filter: \.public,
writers: [
.itemWriter(swim(renderArticle)),
.listWriter(swim(renderArticles)),
.tagWriter(swim(renderTag), tags: \.metadata.tags),
.yearWriter(swim(renderYear)),
// Atom feed for all articles, and a feed per tag
.listWriter(
atomFeed(
title: SiteMetadata.name,
author: SiteMetadata.author,
baseURL: SiteMetadata.url,
summary: \.metadata.summary
),
output: "feed.xml"
),
.tagWriter(
atomFeed(
title: SiteMetadata.name,
author: SiteMetadata.author, baseURL: SiteMetadata.url, summary: \.metadata.summary
),
output: "tag/[key]/feed.xml",
tags: \.metadata.tags
)
]
)
// All the remaining markdown files will be parsed to html,
// using the default EmptyMetadata as the Item's metadata type.
.register(
metadata: PageMetadata.self,
readers: [.parsleyMarkdownReader],
itemProcessor: removingBreaks,
itemWriteMode: .keepAsFile, // need to keep 404.md as 404.html, not 404/index.html
writers: [.itemWriter(swim(renderPage))]
)
// Run the steps we registered above
.run()
// All the remaining files that were not parsed to markdown, so for example images, raw html files and css,
// are copied as-is to the output folder.
.staticFiles()
}
}

View File

@@ -24,7 +24,7 @@ func baseLayout(
body(class: "bg-page text-white pb-5 font-avenir \(section.rawValue)") {
siteHeader(section)
div(class: "container pt-12 lg:pt-28") {
div(class: "container") {
children()
}
@@ -40,7 +40,7 @@ private func siteHeader(_ section: Section) -> Node {
div(class: "header__logo") {
a(href: "/") {
div(class: "logo") {
"mhoush.com"
"docs.housh.dev"
}
}
}
@@ -59,44 +59,27 @@ private func siteHeader(_ section: Section) -> Node {
}
private func footer(_ rssLink: String) -> Node {
div(class: "site-footer text-gray gray-links border-t border-light text-center pt-6 mt-8 text-sm") {
p {
div(class: "site-footer text-slate-200 border-t border-light text-center pt-6 mt-8 text-sm") {
div {
"Copyright © Michael Housh 2023-\(Date().description.prefix(4))."
}
p {
p(class: "mb-2") {
"Built in Swift using"
a(href: "https://github.com/loopwerk/Saga", rel: "nofollow", target: "_blank") { "Saga" }
a(
class: "text-orange-400 [&:hover]:border-b border-green-400",
href: "https://github.com/loopwerk/Saga",
rel: "nofollow",
target: "_blank"
) { "Saga" }
"("
%a(href: "https://github.com/m-housh/mhoush.com", rel: "nofollow", target: "_blank") { "source" }
%a(
class: "[&:hover]:border-b border-green-400",
href: "https://github.com/m-housh/mhoush.com",
rel: "nofollow",
target: "_blank"
) { "source" }
%")."
}
p {
a(
href: "\(SiteMetadata.url.absoluteString)/articles/\(rssLink)feed.xml",
rel: "nofollow",
target: "_blank"
) { "RSS" }
" | "
a(href: "https://github.com/m-housh", rel: "nofollow", target: "_blank") { "Github" }
" | "
a(
href: "https://www.youtube.com/channel/UCb58SeURd5bObfTiL0KoliA",
rel: "nofollow",
target: "_blank"
) { "Youtube" }
" | "
a(href: "https://www.facebook.com/michael.housh", rel: "nofollow", target: "_blank") { "Facebook" }
" | "
a(href: "mailto:michael@mhoush.com", rel: "nofollow") { "Email" }
}
p {
span {
"All articles are licensed under Creative-Commons (CC BY-NC) 4.0"
}
a(href: "https://creativecommons.org/licenses/by-nc/4.0/") {
img(class: "justify-center", src: "/static/images/by-nc.png", width: "100")
}
}
script(src: "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js")
script(src: "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/keep-markup/prism-keep-markup.min.js")
script(src: "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js")

View File

@@ -14,7 +14,7 @@ func tagPrefix(index: Int, totalTags: Int) -> Node {
}
func renderArticleInfo(_ article: Item<ArticleMetadata>) -> Node {
div(class: "text-gray gray-links text-sm") {
div(class: "text-slate-400 gray-links text-sm mb-8") {
span(class: "border-r border-gray pr-2 mr-2") {
article.date.formatted("MMMM dd, yyyy")
}
@@ -27,7 +27,7 @@ func renderArticleInfo(_ article: Item<ArticleMetadata>) -> Node {
Node.raw("""
<i class="fa fa-home"></i>
"""),
%a(class: "text-orange [&:hover]:border-b border-green", href: "/articles/tag/\(tag.slugified)/") {
%a(class: "text-orange-400 [&:hover]:border-b border-green-400", href: "/articles/tag/\(tag.slugified)/") {
tag
}
])
@@ -100,12 +100,12 @@ func renderArticle(context: ItemRenderingContext<ArticleMetadata>) -> Node {
title: context.item.title,
extraHeader: generateHeader(.article(context.item))
) {
article(class: "prose") {
article(class: "prose pt-8") {
h1 { context.item.title }
div(class: "-mt-6") {
div {
renderArticleInfo(context.item)
}
img(alt: "banner", src: context.item.imagePath)
// img(alt: "banner", src: context.item.imagePath)
Node.raw(context.item.body)
}
@@ -135,23 +135,6 @@ func renderArticle(context: ItemRenderingContext<ArticleMetadata>) -> Node {
}
}
}
// Giscus comment section.
commentSection
div(class: "border-t border-light mt-8 pt-8") {
h2(class: "text-4xl font-extrabold mb-8") { "Author" }
div(class: "flex flex-col lg:flex-row gap-8") {
div(class: "flex-[0_0_120px]") {
img(class: "w-[120px] h-[120px] rounded-full", src: "/static/images/avatar.png")
}
div(class: "prose") {
h3(class: "!m-0") { SiteMetadata.author }
p(class: "text-gray") { SiteMetadata.summary }
}
}
}
}
}
@@ -171,26 +154,3 @@ func renderArticleForGrid(article: Item<ArticleMetadata>) -> Node {
}
}
}
private var commentSection: Node {
div(class: "border-t border-light pt-8") {
Node.raw("""
<script src="https://giscus.app/client.js"
data-repo="m-housh/mhoush.com"
data-repo-id="R_kgDOJagAXA"
data-category="Article Discussions"
data-category-id="DIC_kwDOJagAXM4CnLfv"
data-mapping="pathname"
data-strict="0"
data-reactions-enabled="1"
data-emit-metadata="0"
data-input-position="bottom"
data-theme="preferred_color_scheme"
data-lang="en"
data-loading="lazy"
crossorigin="anonymous"
async>
</script>
""")
}
}

View File

@@ -27,7 +27,7 @@ func renderPage(context: ItemRenderingContext<PageMetadata>) -> Node {
func renderHome(body: String) -> Node {
div {
img(alt: "Avatar", class: "my-24 w-[315px] h-200px mx-auto", src: "/static/images/avatar.png")
// img(alt: "Avatar", class: "my-24 w-[315px] h-200px mx-auto", src: "/static/images/avatar.png")
div(class: "my-24 uppercase font-avenir text-[40px] leading-[1.25] font-thin text-center [&>h1>strong]:font-bold") {
Node.raw(body)

7
content/about.md Normal file
View File

@@ -0,0 +1,7 @@
---
section: about
---
# About
Internal documentation site for **Housh - The Home Energy Experts**

View File

@@ -1,7 +1,7 @@
---
date: 2025-4-02
updated: 2025-04-02
author: "Michael Housh"
tags: network, infrastructure
---
# Networking
@@ -65,7 +65,7 @@ in the future).
The unifi management console is what handles firewall rules for the networks. It
is accessed via `Settings -> Security -> Firewall` on the management console.
![firewall](./img/firewall.png)
![firewall](/static/img/firewall.png)
This is where settings are made to either allow or deny traffic on the networks
from communicating with other networks or the internet.
@@ -74,7 +74,7 @@ from communicating with other networks or the internet.
DNS is what translates IP addresses to domain names (i.e. `po.housh.dev` ->
`192.168.50.6`). This is managed by the unifi management console and is accessed
via `Settigns -> Routing -> DNS`.
via `Settings -> Routing -> DNS`.
We primarily use wildcard records, which allow the actual routing to be handled
by the servers to the correct service.

View File

@@ -0,0 +1,3 @@
---
section: home
---

BIN
content/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

@@ -0,0 +1,136 @@
@import "tailwindcss";
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
:root {
--accent: #a6e3a1;
--background: rgb(34, 33, 41);
--color: #fff;
--border-color: hsla(0, 0%, 100%, 0.1);
--phoneWidth: (max-width: 684px);
--tabletWidth: (max-width: 900px) --orange: #f5a87f;
--green: #a6e3a1;
}
/* HEADER */
.header {
display: flex;
flex-direction: column;
position: relative;
}
.header__inner {
display: flex;
align-items: center;
justify-center: space-between;
}
.header__logo {
display: flex;
flex: 1;
}
.header__logo:after {
content: "";
background: repeating-linear-gradient(
90deg,
#ffa86a,
#ffa86a 2px,
transparent 0,
transparent 10px
);
background: repeating-linear-gradient(
90deg,
var(--accent),
var(--accent) 2px,
transparent 0,
transparent 10px
);
display: block;
width: 100%;
right: 10px;
}
.header__logo a {
flex: 0 0 auto;
max-width: 100%;
text-decoration: none;
}
.logo {
display: flex;
align-items: center;
text-decoration: none;
background: #ffa86a;
background: var(--accent);
color: #000;
padding: 5px 10px;
}
.header .menu {
display: flex;
flex-wrap: wrap;
list-style: none;
margin: 0;
padding: 10px;
}
nav a:hover {
@apply border-b-2 border-orange-400;
}
body {
@apply bg-slate-900;
}
h1 {
@apply text-4xl;
}
h2 {
@apply text-3xl mb-4 pt-4;
color: var(--green);
}
h3 {
@apply text-2xl text-amber-500 py-4;
}
p {
@apply mb-8;
}
img {
padding-top: 10px;
padding-bottom: 10px;
}
article h2 {
@apply border-b-2 border-slate-200;
}
.container {
@apply px-10;
}
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
nav a:active {
@apply border-yellow-300 text-white;
}

File diff suppressed because one or more lines are too long

123
content/static/prism.css Normal file
View File

@@ -0,0 +1,123 @@
/* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+swift */
/**
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
* Based on https://github.com/chriskempson/tomorrow-theme
* @author Rose Pritchard
*/
code[class*="language-"],
pre[class*="language-"] {
color: #ccc;
background: none;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #2d2d2d;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #999;
}
.token.punctuation {
color: #ccc;
}
.token.tag,
.token.attr-name,
.token.namespace,
.token.deleted {
color: #e2777a;
}
.token.function-name {
color: #6196cc;
}
.token.boolean,
.token.number,
.token.function {
color: #f08d49;
}
.token.property,
.token.class-name,
.token.constant,
.token.symbol {
color: #f8c555;
}
.token.selector,
.token.important,
.token.atrule,
.token.keyword,
.token.builtin {
color: #cc99cd;
}
.token.string,
.token.char,
.token.attr-value,
.token.regex,
.token.variable {
color: #7ec699;
}
.token.operator,
.token.entity,
.token.url {
color: #67cdcc;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.inserted {
color: green;
}

10
content/static/style.css Normal file
View File

@@ -0,0 +1,10 @@
body {
font-family: Helvetica;
}
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}

View File

@@ -0,0 +1,8 @@
[private]
default:
@just --list
# Run the development server.
[group('dev')]
run:
@swift run watch content Sources deploy

View File

@@ -2,6 +2,7 @@
"name": "docs.housh.dev",
"version": "1.0.0",
"description": "",
"type": "module",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
@@ -12,13 +13,9 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@tailwindcss/typography": "^0.5.16",
"tailwindcss": "^3.4.17"
"tailwindcss": "^4.0.8"
},
"dependencies": {
"autoprefixer": "^10.4.20",
"postcss": "^8.5.3",
"sass": "^1.85.0",
"tailwind": "^4.0.0"
"@tailwindcss/cli": "^4.0.8"
}
}

3123
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff