From 4de44c7e8ca9b23daaf21b2183e3a457389fb772 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Wed, 12 Feb 2025 21:26:00 -0500
Subject: [PATCH 01/21] Add comments
---
lib/Config.rakumod | 25 ++-
lib/DB.rakumod | 9 +-
resources/cactus.css | 360 +++++++++++++++++++++++++++++++++++++++++++
3 files changed, 388 insertions(+), 6 deletions(-)
create mode 100644 resources/cactus.css
diff --git a/lib/Config.rakumod b/lib/Config.rakumod
index e988aa4..a7bdc67 100644
--- a/lib/Config.rakumod
+++ b/lib/Config.rakumod
@@ -13,9 +13,20 @@ unit class Config;
method generate-post(Int:D $id, Post:D $post, $db) {
my $meta = $db.meta;
my $content = $post.render-html;
+ my $cactus-script = qq「
+document.addEventListener('DOMContentLoaded', () => \{
+initComments(\{
+ node: document.getElementById("comment-section"),
+ defaultHomeserverUrl: "https://matrix.cactus.chat",
+ serverName: "cactus.chat",
+ siteName: "stranger.systems",
+ commentSectionId: "post-$id"
+\})\})」;
my $head =
head [
+ # Generate the universal header components
generate-head($meta, $post.title, $post.description);
+ # Open graph tags for embedding
meta :property, :content($post.title);
meta :property, :content(post-link-abs $db.meta, $id, $post);
meta :property, :content($db.meta.title);
@@ -26,6 +37,14 @@ method generate-post(Int:D $id, Post:D $post, $db) {
$post.tags.map(-> $tag {
meta :property, :content($tag)
});
+
+ # Cactus comments support
+ link :rel,
+ :href,
+ :type;
+ script :src;
+ # Only actually load the script if the post isn't hidden
+ optl !$post.hidden, -> {script $cactus-script};
];
my $body =
body [
@@ -34,8 +53,10 @@ method generate-post(Int:D $id, Post:D $post, $db) {
post-header $id, $post, $db;
div :class, [
$content;
- ]
- ]
+ ];
+ ];
+ # Only actually have the comment section if the post isn't hidden
+ optl !$post.hidden, -> {div :id, :class};
];
# TODO: Setup Comments
# TODO: Setup footer
diff --git a/lib/DB.rakumod b/lib/DB.rakumod
index e30ae1d..4373000 100644
--- a/lib/DB.rakumod
+++ b/lib/DB.rakumod
@@ -159,10 +159,11 @@ class PostDB {
mkdir $res-dir unless $res-dir.e;
# symlink the resources directory to make "interactive" styling eaiser
# TODO: Directories support
- %?RESOURCES.IO.symlink: $res-dir.add('colors.css') unless %?RESOURCES.IO.e;
- %?RESOURCES.IO.symlink: $res-dir.add('main.css') unless %?RESOURCES.IO.e;
- %?RESOURCES.IO.symlink: $res-dir.add('code.css') unless %?RESOURCES.IO.e;
- %?RESOURCES.IO.symlink: $res-dir.add('admonitions.css') unless %?RESOURCES.IO.e;
+ %?RESOURCES.IO.symlink: $res-dir.add('colors.css') unless $res-dir.add('colors.css').e;
+ %?RESOURCES.IO.symlink: $res-dir.add('main.css') unless $res-dir.add('main.css').e;
+ %?RESOURCES.IO.symlink: $res-dir.add('code.css') unless $res-dir.add('code.css').e;
+ %?RESOURCES.IO.symlink: $res-dir.add('admonitions.css') unless $res-dir.add('admonitions.css').e;
+ %?RESOURCES.IO.symlink: $res-dir.add('cactus.css') unless $res-dir.add('cactus.css').e;
}
#| Get a list of posts sorted by date
diff --git a/resources/cactus.css b/resources/cactus.css
new file mode 100644
index 0000000..48b87cd
--- /dev/null
+++ b/resources/cactus.css
@@ -0,0 +1,360 @@
+:root{
+ --cactus-text-color--soft: var(--dim-0);
+ --cactus-background-color--strong: var(--bg-1);
+ --cactus-border-color: var(--dim-0);
+ --cactus-box-shadow-color: transparent;
+ --cactus-button-text-color:inherit;
+ --cactus-button-color: var(--bg-2);
+ --cactus-button-color--strong: var(--bg-2);
+ --cactus-button-color--stronger: var(--bg-2);
+ --cactus-login-form-text-color:inherit;
+ --cactus-border-width:1px;
+ --cactus-border-radius:0.4em;
+ --cactus-text-color:inherit;
+ --cactus-background-color: var(--bg-0);
+ --cactus-error-color: var(--red)
+}
+.cactus-comment-header a {
+ color: var(--blue);
+}
+
+.cactus-container{
+ display:flex;
+ width: 100%;
+ max-width: var(--content-width);
+ padding: var(--box-padding-vert) var(--box-padding-horz);
+ border-radius: var(--box-radius);
+ flex-direction:column;
+ gap:1em;
+ color:var(--cactus-text-color);
+ background-color:var(--cactus-background-color)
+}
+.cactus-error{
+ padding:.5em;
+ padding-inline-end:1.5em;
+ border:var(--cactus-border-width) solid var(--cactus-error-color);
+ border-radius:var(--cactus-border-radius);
+ position:relative
+}
+.cactus-error-close{
+ position:absolute;
+ right:.2em;
+ top:0;
+ color:var(--cactus-button-color);
+ background-color:transparent;
+ border:none
+}
+.cactus-error-close:hover:not([disabled]){
+ color:var(--cactus-button-color--strong);
+ cursor:pointer
+}
+.cactus-error-close:active:not([disabled]){
+ color:var(--cactus-button-color--stronger);
+ cursor:pointer
+}
+.cactus-error-close-icon{
+ inline-size:20px;
+ block-size:20px
+}
+.cactus-error-text{
+ color:var(--cactus-error-color);
+ font-weight:700;
+ margin:0
+}
+.cactus-editor{
+ display:flex;
+ flex-direction:column;
+ gap:.5em
+}
+.cactus-editor>span{
+ display:flex
+}
+.cactus-editor-textarea{
+ display:flex;
+ flex:1;
+ height:9rem;
+ border-radius:var(--cactus-border-radius);
+ border:solid var(--cactus-border-width) var(--green);
+ padding:.5em;
+ box-sizing:content-box;
+ background-color:transparent;
+ color:inherit;
+ font:inherit
+}
+.cactus-editor-textarea::placeholder{
+ text-align:center;
+ line-height:8rem;
+ font-size:1.5rem;
+ color:var(--cactus-text-color--soft)
+}
+.cactus-editor-below{
+ display:flex;
+ flex-wrap:wrap;
+ gap:.5em;
+ justify-content:flex-end
+}
+.cactus-editor-name{
+ display:flex;
+ flex:1
+}
+.cactus-editor-name>span{
+ display:flex;
+ flex:1;
+ min-inline-size:20ch;
+ max-inline-size:40ch
+}
+.cactus-editor-name>span>input{
+ inline-size:100%;
+ border-radius:var(--cactus-border-radius);
+ border:solid var(--cactus-border-width) var(--green);
+ padding:.5em;
+ background-color:transparent;
+ color:inherit;
+ font-size:inherit
+}
+.cactus-editor-name>span>input::placeholder{
+ color:var(--cactus-text-color--soft)
+}
+.cactus-editor-buttons{
+ display:flex;
+ gap:.5em
+}
+.cactus-matrixdotto-only{
+ align-self:center;
+ text-decoration:none
+}
+.cactus-login-form-wrapper{
+ position:fixed;
+ top:0;
+ bottom:0;
+ left:0;
+ right:0;
+ z-index:1;
+ display:flex;
+ align-items:center;
+ justify-content:center
+}
+.cactus-login-form{
+ display:flex;
+ flex-direction:column;
+ gap:2em;
+ padding:2rem;
+ border-radius:var(--cactus-border-radius);
+ background-color:var(--cactus-background-color--strong);
+ color:var(--cactus-login-form-text-color);
+ box-shadow:0 .5em 1em .5em var(--cactus-box-shadow-color);
+ box-sizing:border-box;
+ inline-size:100%;
+ max-inline-size:300px
+}
+.cactus-login-close{
+ align-self:flex-end;
+ position:relative;
+ margin:-2em;
+ padding:0;
+ color:var(--cactus-button-color);
+ scale:2;
+ background-color:transparent;
+ border:none
+}
+.cactus-login-close:hover:not([disabled]){
+ color:var(--cactus-button-color--strong);
+ cursor:pointer
+}
+.cactus-login-close:active:not([disabled]){
+ color:var(--cactus-button-color--stronger);
+ cursor:pointer
+}
+.cactus-login-close-icon{
+ inline-size:20px;
+ block-size:20px
+}
+.cactus-login-title{
+ align-self:center;
+ font-size:1.17em;
+ font-weight:700;
+ margin:0
+}
+.cactus-login-client{
+ display:flex;
+ flex-direction:column;
+ gap:1em
+}
+.cactus-login-client-title{
+ font-size:1em;
+ font-weight:700;
+ margin:0
+}
+.cactus-matrixdotto-button{
+ justify-content:center;
+ text-decoration:none
+}
+.cactus-login-credentials{
+ display:flex;
+ flex-direction:column;
+ gap:1em
+}
+.cactus-login-credentials-title{
+ font-size:1em;
+ font-weight:700;
+ margin:0
+}
+.cactus-login-field{
+ display:flex;
+ flex-direction:column;
+ gap:.25em
+}
+.cactus-login-label{
+ font-size:1em;
+ padding-bottom:.25em;
+ color:var(--cactus-text-color--soft)
+}
+.cactus-login-error{
+ margin:0;
+ font-size:.8em;
+ color:var(--cactus-text-color--soft)
+}
+.cactus-login-field>input{
+ border-radius:var(--cactus-border-radius);
+ border:solid var(--cactus-border-width) var(--cactus-border-color);
+ padding:.5em;
+ background-color:transparent;
+ color:inherit;
+ font-size:inherit
+}
+.cactus-login-field>input::placeholder{
+ color:var(--cactus-text-color--soft)
+}
+.cactus-login-credentials-button{
+ justify-content:center
+}
+.cactus-comments-container{
+ display:flex;
+ flex-direction:column;
+ gap:1em
+}
+.cactus-comments-list{
+ display:flex;
+ flex-direction:column;
+ gap:.5em
+}
+.cactus-comment{
+ display:flex;
+ flex-direction:row;
+ gap:1em;
+ padding-block-end:.5em;
+ border-block-end: 2px dotted var(--dim-0);
+}
+.cactus-comment-avatar{
+ display:flex
+}
+.cactus-comment-avatar>*{
+ width:2.5rem;
+ height:2.5rem;
+ border-radius:50%;
+ margin:0
+}
+.cactus-comment-avatar-placeholder{
+ display:flex;
+ justify-content:center;
+ align-items:center;
+ background-color:var(--cactus-border-color);
+ color:var(--bg-2);
+}
+.cactus-comment-avatar-placeholder:before{
+ content:"?"
+}
+.cactus-comment-content{
+ display:flex;
+ flex-direction:column;
+ gap:.5em
+}
+.cactus-comment-header{
+ display:flex;
+ gap:.5em;
+ flex-wrap:wrap
+}
+.cactus-comment-displayname{
+ font-weight:700;
+ cursor:pointer;
+ text-decoration:none;
+ color:inherit
+}
+.cactus-comment-time{
+ color:var(--cactus-text-color--soft)
+}
+.cactus-message-text>:first-child{
+ margin-block-start:0
+}
+.cactus-message-text>:last-child{
+ margin-block-end:0
+}
+.cactus-message-emote{
+ padding-top:.5em;
+ color:var(--cactus-text-color--soft)
+}
+.cactus-message-image{
+ max-width:100%;
+ height:auto
+}
+.cactus-message-file{
+ line-height:3em;
+ margin-left:1em
+}
+.cactus-message-video{
+ max-width:100%
+}
+.cactus-button{
+ display:flex;
+ align-items:center;
+ padding-block:.6em;
+ padding-inline:1em;
+ background-color:var(--cactus-button-color);
+ font-weight:700;
+ border-radius:var(--cactus-border-radius);
+ color:var(--cactus-button-text-color);
+ font-size:inherit;
+ border:none
+}
+.cactus-button:hover:not([disabled]){
+ background-color:var(--cactus-button-color--strong);
+ cursor:pointer
+}
+.cactus-button:active:not([disabled]){
+ background-color:var(--cactus-button-color--stronger);
+ cursor:pointer
+}
+.cactus-view-more{
+ display:flex;
+ justify-content:center
+}
+.spinner{
+ align-self:center;
+ width:2em;
+ height:2em;
+ display:flex;
+ gap:.3em
+}
+.spinner>div{
+ flex:1;
+ background-color:var(--cactus-border-color);
+ animation:sk-stretchdelay 2.4s ease-in-out infinite
+}
+.spinner .rect2{
+ animation-delay:-2.2s
+}
+.spinner .rect3{
+ animation-delay:-2s
+}
+.spinner .rect4{
+ animation-delay:-1.8s
+}
+@keyframes sk-stretchdelay{
+ 0%,40%,to{
+ transform:scaleY(.4)
+ }
+ 20%{
+ transform:scaleY(1)
+ }
+}
+/*# sourceMappingURL=/style.css.map */
From 82485924be8fc6be6e6fdef113310bfa803a5c7d Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Fri, 14 Feb 2025 12:00:26 -0500
Subject: [PATCH 02/21] Spelling corrections and typo fixes
---
db/posts/5.json | 1 +
projects/Idris/src/LessMacrosMoreTypes/Printf.md | 10 +++++-----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/db/posts/5.json b/db/posts/5.json
index 62306f6..d2043ff 100644
--- a/db/posts/5.json
+++ b/db/posts/5.json
@@ -1,5 +1,6 @@
{
"edited-at": [
+ "2025-02-14T12:00:50.492225-05:00"
],
"hidden": false,
"idris": true,
diff --git a/projects/Idris/src/LessMacrosMoreTypes/Printf.md b/projects/Idris/src/LessMacrosMoreTypes/Printf.md
index a6dcd5b..bddd1d3 100644
--- a/projects/Idris/src/LessMacrosMoreTypes/Printf.md
+++ b/projects/Idris/src/LessMacrosMoreTypes/Printf.md
@@ -11,7 +11,7 @@ While C can provide "convenient" string formatting by having hideously unsafe va
This article is inspired by an exercise from chapter 6 of [Type Driven Development with Idris](https://www.manning.com/books/type-driven-development-with-idris), and is written as a literate Idris file, with the source available [here](https://git.stranger.systems/thatonelutenist/website/src/branch/trunk/projects/Idris/src/LessMacrosMoreTypes/Printf.md).
-## Gameplan
+## Game plan
Our goal is to provide a `printf` function that can be called much like it's C equivalent:
@@ -29,7 +29,7 @@ Idris lacks a dedicated facility for variadics, but we can call functions in typ
Idris allows us to manipulate types as first class values, and we can use the runtime values of previous arguments to the function we are declaring as arguments to later type-level functions. These type-level functions can, furthermore, recurse into themselves to produce type signatures of varying length and content.
> [!TIP]
-> Idris requires that these type level functions be [_total_](https://idris2.readthedocs.io/en/latest/tutorial/theorems.html#totality-checking) for them to be expanded at compile time, meaning they are known to the compiler to return a value in constant time for all possible inputs.
+> Idris requires that these type level functions be [_total_](https://idris2.readthedocs.io/en/latest/tutorial/theorems.html#totality-checking) for them to be expanded at compile time, meaning they are known to the compiler to return a value in finite time for all possible inputs.
This allows us to produce variadic functions, so long as the number of arguments and their types can be determined from one of the arguments of the function before the variadic portion.
@@ -43,7 +43,7 @@ First, we need a data structure to describe our format string. We define the `Fo
data Format : Type where
||| A slot that should be filled in with a number
Number : (next : Format) -> Format
- ||| A slot that should be filled in with a number, padded to a certian number
+ ||| A slot that should be filled in with a number, padded to a certain number
||| of digits
PaddedNumber : (digits : Nat) -> (next : Format) -> Format
||| A slot that should be filled in with a string
@@ -188,7 +188,7 @@ printfFmt (Just x) acc = printfFmt' x acc
### With a Format String
-`printf` is easily defined a simple wrapper, converting the provided string to a `Format` and passing it to `printfFmt`. After accepting the format string argument, we parse a `Maybe Format` from it, providing the parsed value to our `PrintfType` function to calculate the rest of our type signature.
+`printf` is easily defined as a simple wrapper, converting the provided string to a `Format` and passing it to `printfFmt`. After accepting the format string argument, we parse a `Maybe Format` from it, providing the parsed value to our `PrintfType` function to calculate the rest of our type signature.
> [!TIP]
> The `_` syntax that appears in this function introduces an anonymous hole. When the value of a hole is forced by type checking (resolved), the compiler fills in its value for you. Holes defined with `_` will result in an "unsolved holes" compile error if unresolved.
@@ -260,7 +260,7 @@ failing
runtimeFormatString = printf (blackbox "%s %s") "Hello" "World"
```
-We can construct a variant of `printf` that will work with runtime format strings by defining a custom, heterogenous list-like structure, and making a fallible version of our `printf` function based on this data structure:
+We can construct a variant of `printf` that will work with runtime format strings by defining a custom, heterogeneous list-like structure, and making a fallible version of our `printf` function based on this data structure:
```idris
data PrintfInput : Type where
From 63e581ac032ca880020eca33136eb97f27fe4a1e Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Fri, 14 Feb 2025 23:38:15 -0500
Subject: [PATCH 03/21] Select from static format strings at runtime
---
db/posts/5.json | 3 +-
.../Idris/src/LessMacrosMoreTypes/Printf.md | 50 ++++++++++++++++++-
2 files changed, 51 insertions(+), 2 deletions(-)
diff --git a/db/posts/5.json b/db/posts/5.json
index d2043ff..485766b 100644
--- a/db/posts/5.json
+++ b/db/posts/5.json
@@ -1,6 +1,7 @@
{
"edited-at": [
- "2025-02-14T12:00:50.492225-05:00"
+ "2025-02-14T12:00:50.492225-05:00",
+ "2025-02-14T23:42:15.625034-05:00"
],
"hidden": false,
"idris": true,
diff --git a/projects/Idris/src/LessMacrosMoreTypes/Printf.md b/projects/Idris/src/LessMacrosMoreTypes/Printf.md
index bddd1d3..150f08a 100644
--- a/projects/Idris/src/LessMacrosMoreTypes/Printf.md
+++ b/projects/Idris/src/LessMacrosMoreTypes/Printf.md
@@ -5,6 +5,9 @@ module LessMacrosMoreTypes.Printf
import Data.List
import System
+
+-- Only needed for addenda
+import Data.Vect
```
While C can provide "convenient" string formatting by having hideously unsafe variadics, and dynamic languages, like python, can do the same, many type safe languages, such as Rust, are forced to provide such functionality through the use of a macro. Dependently typed languages, like Idris, can provide a `printf` like formatting interface, while maintaining both memory and type safety, without the need for macros. We will explore this by implementing a simplified version of `printf` in Idris from scratch.
@@ -328,6 +331,9 @@ We've made a fun little function that works much like C's `printf` function or R
Avoiding using a macro also gives us a lot of neat bonuses, like `printf` automatically playing nice with partial application and currying, with no extra effort on our part.
+> [!NOTE]
+> The `the (List _) x` construction here in explicit type annotation, needed because of an ambiguity in list-type literals introduced by importing `Data.Vect` for the addenda
+
```idris hide
-- @@test partial application
partialApplication : IO Bool
@@ -336,7 +342,7 @@ partialApplication = do
```
```idris
- map (printf "Hello %s") ["Alice", "Bob"] == ["Hello Alice", "Hello Bob"]
+ map (printf "Hello %s") (the (List _) ["Alice", "Bob"]) == ["Hello Alice", "Hello Bob"]
```
@@ -393,5 +399,47 @@ The need for a bespoke data structure for `PrintfInput` also isn't ideal.
We will delve in ways to rectify all of these complaints in future entries in this series.
+## Addenda
+
+### Selecting from a list of format strings with matching type signatures
+
+As long as the format strings are known at compile time, `PrintfType` will fully expand to a function type signature that contains no references to the string. For example, `PrintfType (parseFormat (unpack "%s"))` expands to `String -> String`.
+
+We can take advantage of this by indexing a container type by the `PrintfType` of a "template" format string, and then our container can hold any formatting function of compatible type signature, like so:
+
+```idris
+formatChoices : Vect 3 (PrintfType (parseFormat (unpack "%s")))
+formatChoices = [printf "Hello %s!", printf "Nice to meet you %s", printf "\"%s\""]
+```
+
+We can then index out a single format function from this container (it doesn't have to be a `Vect`, it could also be a map indexed by the format string itself, for instance):
+
+```idris hide
+-- @@test formatChoicesOne
+formatChoicesOne : IO Bool
+formatChoicesOne = do
+ pure $
+```
+
+```idris
+ let f = index 1 formatChoices in f "Alice" == "Nice to meet you Alice"
+```
+
+
+Or apply all of the contained formatting functions to the same string:
+
+```idris hide
+-- @@test formatChoicesAll
+formatChoicesAll : IO Bool
+formatChoicesAll = do
+ pure $
+```
+
+```idris
+ (formatChoices <*> replicate _ "World")
+ == ["Hello World!", "Nice to meet you World", "\"World\""]
+```
+
+
[^1]: Ignoring for a second the special handling for format strings in modern compilers
From 3069e480a456903aa25dbc9fba9f147e2c9e2e15 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Feb 2025 04:48:40 -0500
Subject: [PATCH 04/21] Setup footer
---
lib/Config.rakumod | 9 +++--
lib/Render/Foot.rakumod | 69 +++++++++++++++++++++++++++++++++++++++
lib/Render/Head.rakumod | 5 ++-
lib/Render/Series.rakumod | 4 ++-
lib/Render/Util.rakumod | 8 +++++
resources/colors.css | 9 +++++
resources/main.css | 68 +++++++++++++++++++++++++++++++++++---
7 files changed, 163 insertions(+), 9 deletions(-)
create mode 100644 lib/Render/Foot.rakumod
diff --git a/lib/Config.rakumod b/lib/Config.rakumod
index a7bdc67..aa178de 100644
--- a/lib/Config.rakumod
+++ b/lib/Config.rakumod
@@ -5,6 +5,7 @@ use HTML::Functional;
use Render::Util;
use Render::Head;
use Render::Post;
+use Render::Foot;
use DB::BlogMeta;
use DB::Post;
@@ -57,10 +58,8 @@ initComments(\{
];
# Only actually have the comment section if the post isn't hidden
optl !$post.hidden, -> {div :id, :class};
+ generate-footer;
];
- # TODO: Setup Comments
- # TODO: Setup footer
- # my $footer;
my $html = html :lang, [
$head,
@@ -85,6 +84,7 @@ method generate-index($db) {
div :class, [
h1 "Recent Posts"
], @most-recent;
+ generate-footer;
];
my $html =
@@ -110,6 +110,7 @@ method generate-archive($db) {
div :class, [
h1 "All Posts"
], @most-recent;
+ generate-footer;
];
my $html =
@@ -168,6 +169,7 @@ method generate-tags-page($db, @tags) {
div :class, [
h1 "Tags";
], @tags.map(-> $tag {self.generate-tag-blurb($db, $tag, 4)});
+ generate-footer;
];
my $html =
@@ -184,6 +186,7 @@ method generate-tag-page($db, $tag) {
my $body = body [
site-header $db.meta;
self.generate-tag-blurb($db, $tag, 4);
+ generate-footer;
];
my $html =
diff --git a/lib/Render/Foot.rakumod b/lib/Render/Foot.rakumod
new file mode 100644
index 0000000..c48b141
--- /dev/null
+++ b/lib/Render/Foot.rakumod
@@ -0,0 +1,69 @@
+use v6.e.PREVIEW;
+unit module Render::Foot;
+
+use HTML::Functional;
+
+use Render::Util;
+use DB::BlogMeta;
+
+sub footer-link($name, $title, $path, $icon) {
+ div :class, [
+ $icon;
+ a :href($path), :title($title), [
+ $name;
+ ]
+ ]
+}
+
+sub generate-footer() is export {
+ footer [
+ div :class, [
+ div :class, span "Contact Me";
+ div :class, [
+ footer-link (^ "@thatonelutenist"),
+ "Discord",
+ "https://discordapp.com/users/thatonelutenist",
+ simple-icon "discord";
+ footer-link (^ "@thatonelutenist:stranger.systems"),
+ "Matrix",
+ "https://matrix.to/#/@thatonelutenist:stranger.systems",
+ simple-icon "matrix";
+ footer-link (^ "~thatonelutenist/public-inbox@lists.sr.ht"),
+ "Email",
+ "mailto:~thatonelutenist/public-inbox@lists.sr.ht",
+ icon-solid "envelope";
+ ];
+ ];
+ div :class, [
+ div :class, span "Find My Code";
+ div :class, [
+ footer-link "git.stranger.systems",
+ "Stranger Systems Forgejo",
+ "https://git.stranger.systems/explore/repos",
+ simple-icon "forgejo";
+ footer-link (^ "~thatonelutenist"),
+ "sr.ht",
+ "https://sr.ht/~thatonelutenist/",
+ simple-icon "sourcehut";
+ footer-link (^ "@nmccarty"),
+ "Github",
+ "https://github.com/nmccarty",
+ simple-icon "github";
+ footer-link (^ "@thatonelutenist"),
+ "Gitlab",
+ "https://gitlab.com/thatonelutenist",
+ simple-icon "gitlab";
+ ];
+ ];
+ div :class, [
+ div :class, span "Social Media";
+ div :class, [
+ footer-link "@thatonelutenist@hachyderm.io",
+ "Mastodon",
+ "https://hachyderm.io/@thatonelutenist",
+ simple-icon "mastodon";
+ ];
+ ]
+ ]
+}
+
diff --git a/lib/Render/Head.rakumod b/lib/Render/Head.rakumod
index e8177f8..88c2b13 100644
--- a/lib/Render/Head.rakumod
+++ b/lib/Render/Head.rakumod
@@ -41,11 +41,14 @@ sub generate-head(BlogMeta:D $meta, $title?, $description?) is export {
:href;
link :rel,
:href;
+
+ # Verify mastodon
+ link :rel, :href;
# Tell dark reader that we'll behave
meta :name, :content;
# Tell feed readers about our feed
link :rel, :type, :title($meta.title),
- :href;
+ :href;
]
}
diff --git a/lib/Render/Series.rakumod b/lib/Render/Series.rakumod
index f0b2adf..15f6579 100644
--- a/lib/Render/Series.rakumod
+++ b/lib/Render/Series.rakumod
@@ -3,6 +3,7 @@ unit module Render::Series;
use Render::Util;
use Render::Head;
+use Render::Foot;
use Render::Post;
use DB::Post;
use DB::Series;
@@ -93,7 +94,8 @@ sub series-page(Int:D $series-id, $db) is export {
series-header $series, $db;
div :class,
$series.post-ids.map(*.&generate-blurb($db));
- ]
+ ];
+ generate-footer;
];
my $html = html :lang, [
diff --git a/lib/Render/Util.rakumod b/lib/Render/Util.rakumod
index 4a94800..af63505 100644
--- a/lib/Render/Util.rakumod
+++ b/lib/Render/Util.rakumod
@@ -49,10 +49,18 @@ sub icon($icon) is export {
i(:class("bx bx-$icon"))
}
+sub icon-solid($icon) is export {
+ i(:class("bx bxs-$icon"))
+}
+
sub logo($logo) is export {
i(:class("bx bxl-$logo"))
}
+sub simple-icon($icon) is export {
+ img :src("https://cdn.simpleicons.org/$icon/474747/b9b9b9")
+}
+
sub mins-to-string($mins) is export {
if $mins < 60 {
$mins.Str ~ "m"
diff --git a/resources/colors.css b/resources/colors.css
index 7cbfe09..5f67b2c 100644
--- a/resources/colors.css
+++ b/resources/colors.css
@@ -91,6 +91,15 @@ blockquote {
.unit-test .bx-info-circle {
color: var(--yellow);
}
+footer {
+ background-color: var(--bg-0);
+}
+footer > div {
+ background-color: var(--bg-1);
+}
+.footer-link {
+ background-color: var(--bg-2);
+}
/* Colorization for idris code blocks */
code {
diff --git a/resources/main.css b/resources/main.css
index 80fb6d1..e36a915 100644
--- a/resources/main.css
+++ b/resources/main.css
@@ -8,6 +8,7 @@
--box-padding-horz: 1rem;
--box-margin-vert: 0.5rem;
--box-margin-horz: 0.5rem;
+ --footer-padding: 0.25rem;
--box-gap: 0.5rem;
--box-radius: 1rem;
}
@@ -79,10 +80,16 @@ body, .post, .series {
flex-wrap: wrap;
margin-top: var(--box-margin-vert);
}
-.header-links > a > span, .post-series-tag > a > span, .post-tag > a > span {
+.header-links > a > span,
+.post-series-tag > a > span,
+.post-tag > a > span,
+.footer-link > a > div {
text-decoration: underline;
}
-.header-links > a, .post-series-tag > a, .post-tag > a {
+.header-links > a,
+.post-series-tag > a,
+.post-tag > a,
+.footer-link > a {
text-decoration: none;
}
@@ -174,7 +181,6 @@ blockquote {
.tag-blurb-links {
display: block;
border-radius: var(--box-radius);
- border-radius: var(--box-radius);
display: flex;
flex-flow: row wrap;
gap: var(--box-gap);
@@ -197,6 +203,60 @@ blockquote {
.tag-blurb-title {
margin-top: var(--box-margin-vert);
margin-bottom: 0;
- font-size: 1.5em;
+ font-size: 1.5rem;
font-weight: bold;
}
+
+/* Style the footer */
+footer {
+ display: flex;
+ flex-direction: row;
+ align-items: stretch;
+ gap: var(--box-gap);
+ max-width: var(--content-width);
+ width: 100%;
+ border-radius: var(--box-radius);
+ padding: var(--box-padding-vert) var(--box-padding-horz);
+ font-size: 0.8rem;
+}
+footer > div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-around;
+ flex: 1;
+ gap: var(--box-gap);
+ border-radius: var(--box-radius);
+ padding: var(--footer-padding);
+}
+.footer-title {
+ font-size: 1rem;
+ font-weight: bold;
+}
+footer i {
+ font-size: 1rem;
+ line-height: 0.8rem;
+}
+.footer-link > a {
+ margin-left: 0.25rem;
+}
+.footer-link > img {
+ height: 1rem;
+ width: 1rem;
+ margin: 0.1rem;
+}
+.footer-links {
+ display: flex;
+ flex-flow: row wrap;
+ /* align-items: center; */
+ gap: var(--box-gap);
+ border-radius: var(--box-radius);
+ padding: var(--footer-padding);
+}
+.footer-link {
+ display: flex;
+ flex-flow: row nowrap;
+ align-content: center;
+ border-radius: 0.25rem;
+ padding: var(--footer-padding);
+}
From d6b17a4be08443b58dd2246bb5eb7479c83658e4 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Feb 2025 04:56:42 -0500
Subject: [PATCH 05/21] Credit mastodon
---
lib/Render/Head.rakumod | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/Render/Head.rakumod b/lib/Render/Head.rakumod
index 88c2b13..dc9f2ef 100644
--- a/lib/Render/Head.rakumod
+++ b/lib/Render/Head.rakumod
@@ -41,9 +41,10 @@ sub generate-head(BlogMeta:D $meta, $title?, $description?) is export {
:href;
link :rel,
:href;
-
# Verify mastodon
link :rel, :href;
+ # Atribute on mastodon
+ meta :name, :content<@thatonelutenist@hachyderm.io>;
# Tell dark reader that we'll behave
meta :name, :content;
# Tell feed readers about our feed
From 75b174f0a72d1e882044be932ac87ac377d0d6d0 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Feb 2025 04:57:39 -0500
Subject: [PATCH 06/21] fix oops
---
lib/Render/Series.rakumod | 1 +
1 file changed, 1 insertion(+)
diff --git a/lib/Render/Series.rakumod b/lib/Render/Series.rakumod
index 15f6579..f0f9e7d 100644
--- a/lib/Render/Series.rakumod
+++ b/lib/Render/Series.rakumod
@@ -135,6 +135,7 @@ sub series-list-page($db) is export {
div :class, [
h1 "All Series"
], @series-blurbs;
+ generate-footer;
];
my $html = html :lang, [
From 88bb800ec4d2411c2979d1f59a2fbdf5aef4ccc6 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Feb 2025 05:31:50 -0500
Subject: [PATCH 07/21] Clean up footer links
---
lib/Render/Foot.rakumod | 14 +++++++-------
resources/main.css | 3 ++-
2 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/lib/Render/Foot.rakumod b/lib/Render/Foot.rakumod
index c48b141..add915a 100644
--- a/lib/Render/Foot.rakumod
+++ b/lib/Render/Foot.rakumod
@@ -20,15 +20,15 @@ sub generate-footer() is export {
div :class, [
div :class, span "Contact Me";
div :class, [
- footer-link (^ "@thatonelutenist"),
+ footer-link (^ "Discord"),
"Discord",
"https://discordapp.com/users/thatonelutenist",
simple-icon "discord";
- footer-link (^ "@thatonelutenist:stranger.systems"),
+ footer-link (^ "Matrix"),
"Matrix",
"https://matrix.to/#/@thatonelutenist:stranger.systems",
simple-icon "matrix";
- footer-link (^ "~thatonelutenist/public-inbox@lists.sr.ht"),
+ footer-link (^ "Public Email Inbox"),
"Email",
"mailto:~thatonelutenist/public-inbox@lists.sr.ht",
icon-solid "envelope";
@@ -41,15 +41,15 @@ sub generate-footer() is export {
"Stranger Systems Forgejo",
"https://git.stranger.systems/explore/repos",
simple-icon "forgejo";
- footer-link (^ "~thatonelutenist"),
+ footer-link (^ "sr.ht"),
"sr.ht",
"https://sr.ht/~thatonelutenist/",
simple-icon "sourcehut";
- footer-link (^ "@nmccarty"),
+ footer-link (^ "github"),
"Github",
"https://github.com/nmccarty",
simple-icon "github";
- footer-link (^ "@thatonelutenist"),
+ footer-link (^ "gitlab"),
"Gitlab",
"https://gitlab.com/thatonelutenist",
simple-icon "gitlab";
@@ -58,7 +58,7 @@ sub generate-footer() is export {
div :class, [
div :class, span "Social Media";
div :class, [
- footer-link "@thatonelutenist@hachyderm.io",
+ footer-link "Mastodon",
"Mastodon",
"https://hachyderm.io/@thatonelutenist",
simple-icon "mastodon";
diff --git a/resources/main.css b/resources/main.css
index e36a915..b63f16e 100644
--- a/resources/main.css
+++ b/resources/main.css
@@ -248,7 +248,8 @@ footer i {
.footer-links {
display: flex;
flex-flow: row wrap;
- /* align-items: center; */
+ align-items: center;
+ justify-content: center;
gap: var(--box-gap);
border-radius: var(--box-radius);
padding: var(--footer-padding);
From 4aefda36fd961bfdc993ffa362a963a6d795fc9d Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Feb 2025 05:35:01 -0500
Subject: [PATCH 08/21] oops
---
lib/Render/Foot.rakumod | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/Render/Foot.rakumod b/lib/Render/Foot.rakumod
index add915a..5aed45f 100644
--- a/lib/Render/Foot.rakumod
+++ b/lib/Render/Foot.rakumod
@@ -45,11 +45,11 @@ sub generate-footer() is export {
"sr.ht",
"https://sr.ht/~thatonelutenist/",
simple-icon "sourcehut";
- footer-link (^ "github"),
+ footer-link (^ "Github"),
"Github",
"https://github.com/nmccarty",
simple-icon "github";
- footer-link (^ "gitlab"),
+ footer-link (^ "Gitlab"),
"Gitlab",
"https://gitlab.com/thatonelutenist",
simple-icon "gitlab";
From 4c6839c698d0b029e4c23b90f0cf46a96025efc4 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Feb 2025 05:47:47 -0500
Subject: [PATCH 09/21] add upload command
---
blog | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/blog b/blog
index 3d3cb5a..ac2c379 100755
--- a/blog
+++ b/blog
@@ -417,3 +417,23 @@ multi MAIN(
$series.post-ids.push: $post-id.Int;
$db.write: $db-dir;
}
+
+#| Do a clean build and upload the blog to the remote
+multi MAIN(
+ "upload",
+ #| The path of the database directory
+ IO::Path(Str) :$db-dir = $default-db-dir,
+ #| The path of the output directory
+ IO::Path(Str) :$site-dir = $default-site-dir,
+) {
+ my $db = read-db $db-dir;
+ my $site = "{$site-dir.absolute}/";
+ # Clean out the site dir
+ my $proc = run , $site;
+ die "clean failed" unless $proc;
+ # Render the site
+ $db.render: $site-dir;
+ # Upload it
+ $proc = run , $site, ;
+ die "rsync failed" unless $proc;
+}
From e01342c98065e51d98d2bb891d4c0be616777f85 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Feb 2025 21:05:08 -0500
Subject: [PATCH 10/21] fix footer on mobile
---
resources/main.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/main.css b/resources/main.css
index b63f16e..5b70eab 100644
--- a/resources/main.css
+++ b/resources/main.css
@@ -210,7 +210,7 @@ blockquote {
/* Style the footer */
footer {
display: flex;
- flex-direction: row;
+ flex-flow: row wrap;
align-items: stretch;
gap: var(--box-gap);
max-width: var(--content-width);
From 79ee0d4c63194344a1125baf4573e0aa603d2328 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Feb 2025 21:16:27 -0500
Subject: [PATCH 11/21] typo correct blog upload destination
---
blog | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/blog b/blog
index ac2c379..c0af5b8 100755
--- a/blog
+++ b/blog
@@ -434,6 +434,6 @@ multi MAIN(
# Render the site
$db.render: $site-dir;
# Upload it
- $proc = run , $site, ;
+ $proc = run , $site, ;
die "rsync failed" unless $proc;
}
From 0e87e752d23aa0c30e5f5d58d1d74231f3a7c432 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Mar 2025 14:28:22 -0400
Subject: [PATCH 12/21] Add header links to blog meta
---
db/meta.json | 2 ++
lib/DB/BlogMeta.rakumod | 4 ++++
lib/DB/HeaderLink.rakumod | 10 ++++++++++
3 files changed, 16 insertions(+)
create mode 100644 lib/DB/HeaderLink.rakumod
diff --git a/db/meta.json b/db/meta.json
index 2aae88d..ae53b19 100644
--- a/db/meta.json
+++ b/db/meta.json
@@ -1,6 +1,8 @@
{
"about-id": 2,
"base-url": "https://www.stranger.systems",
+ "header-links": [
+ ],
"placeholder-id": 0,
"tagline": "Making software better by making it weird",
"title": "Stranger Systems"
diff --git a/lib/DB/BlogMeta.rakumod b/lib/DB/BlogMeta.rakumod
index a5a1fd2..83b70d9 100644
--- a/lib/DB/BlogMeta.rakumod
+++ b/lib/DB/BlogMeta.rakumod
@@ -1,6 +1,7 @@
use v6.e.PREVIEW;
use JSON::Class:auth;
+use DB::HeaderLink;
# Top level metadata for the blog
unit class BlogMeta is json(:pretty);
@@ -17,6 +18,9 @@ has Int:D $.placeholder-id is rw = 0;
#| The id of the about post
has Int:D $.about-id is rw = 0;
+#| Optional list of extra header links
+has HeaderLink:D @.header-links is rw = [];
+
#| The base url of this post
has Str:D $.base-url is required;
diff --git a/lib/DB/HeaderLink.rakumod b/lib/DB/HeaderLink.rakumod
new file mode 100644
index 0000000..e33b3dd
--- /dev/null
+++ b/lib/DB/HeaderLink.rakumod
@@ -0,0 +1,10 @@
+use v6.e.PREVIEW;
+
+use JSON::Class:auth;
+
+# Additional header links
+unit class HeaderLink is json(:pretty);
+
+has Str:D $.link is required is rw;
+
+has Str $.icon is rw;
From c0780047bf1f394f5830c99625162fa53a62cc56 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Mar 2025 14:38:30 -0400
Subject: [PATCH 13/21] Basic hire-me post
---
db/posts/6.json | 12 ++++++++++++
projects/Markdown/HireMe.md | 5 +++++
2 files changed, 17 insertions(+)
create mode 100644 db/posts/6.json
create mode 100644 projects/Markdown/HireMe.md
diff --git a/db/posts/6.json b/db/posts/6.json
new file mode 100644
index 0000000..d45ddf4
--- /dev/null
+++ b/db/posts/6.json
@@ -0,0 +1,12 @@
+{
+ "edited-at": [
+ ],
+ "hidden": true,
+ "markdown": true,
+ "posted-at": "2025-03-15T14:36:38.402495-04:00",
+ "slugs": [
+ ],
+ "source": "/home/nathan/Projects/Blog/projects/Markdown/HireMe.md",
+ "tags": [
+ ]
+}
\ No newline at end of file
diff --git a/projects/Markdown/HireMe.md b/projects/Markdown/HireMe.md
new file mode 100644
index 0000000..2395768
--- /dev/null
+++ b/projects/Markdown/HireMe.md
@@ -0,0 +1,5 @@
+# Hire Me
+
+I am available for consulting gigs, if your company is new to using Rust, or interested in introducing Rust is into your tech stack, I can train your team on Rust, provide code review, and help with introducing Rust to your existing code base.
+
+I am also currently looking for a full time role. Professionally, I am most comfortable working in Rust in backend or systems programming roles, I have historically specialized in tooling and consensus protocols, but also have experience working in embedded and with ETL pipelines. I'm also comfortable working with Go, C#, and most functional programming languages like F# or Haskell.
From 3d2d1f897e151b5e4796e9415c72a237bc150a47 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Mar 2025 16:15:15 -0400
Subject: [PATCH 14/21] Special post handling
---
db/posts/0.json | 1 +
db/posts/1.json | 1 +
db/posts/2.json | 1 +
db/posts/3.json | 1 +
db/posts/4.json | 1 +
db/posts/5.json | 1 +
db/posts/6.json | 3 ++-
lib/Config.rakumod | 18 ++++++++++++++----
lib/DB/Post.rakumod | 2 ++
9 files changed, 24 insertions(+), 5 deletions(-)
diff --git a/db/posts/0.json b/db/posts/0.json
index d978506..a7aae12 100644
--- a/db/posts/0.json
+++ b/db/posts/0.json
@@ -7,6 +7,7 @@
"slugs": [
],
"source": "/dev/null",
+ "special": false,
"tags": [
]
}
\ No newline at end of file
diff --git a/db/posts/1.json b/db/posts/1.json
index 62e539a..0ccc785 100644
--- a/db/posts/1.json
+++ b/db/posts/1.json
@@ -7,6 +7,7 @@
"slugs": [
],
"source": "/home/nathan/Projects/Blog/projects/Markdown/MyNewBlog.md",
+ "special": false,
"tags": [
"meta",
"raku"
diff --git a/db/posts/2.json b/db/posts/2.json
index fa46788..c97b41b 100644
--- a/db/posts/2.json
+++ b/db/posts/2.json
@@ -7,6 +7,7 @@
"slugs": [
],
"source": "/home/nathan/Projects/Blog/projects/Markdown/About.md",
+ "special": false,
"tags": [
]
}
\ No newline at end of file
diff --git a/db/posts/3.json b/db/posts/3.json
index 527339e..9ffa6d3 100644
--- a/db/posts/3.json
+++ b/db/posts/3.json
@@ -7,6 +7,7 @@
"slugs": [
],
"source": "/home/nathan/Projects/Blog/projects/Markdown/CryptoSuite.md",
+ "special": false,
"tags": [
"cryptography"
]
diff --git a/db/posts/4.json b/db/posts/4.json
index 9effd7d..d474d97 100644
--- a/db/posts/4.json
+++ b/db/posts/4.json
@@ -7,6 +7,7 @@
"slugs": [
],
"source": "/home/nathan/Projects/Blog/projects/Markdown/2025/01-Jan/AdventOfBugs.md",
+ "special": false,
"tags": [
"idris",
"advent-of-code"
diff --git a/db/posts/5.json b/db/posts/5.json
index 485766b..4f6f206 100644
--- a/db/posts/5.json
+++ b/db/posts/5.json
@@ -11,6 +11,7 @@
],
"source": "/home/nathan/Projects/Blog/projects/Idris/src/LessMacrosMoreTypes/Printf.md",
"source-code": "https://git.stranger.systems/thatonelutenist/website/src/branch/trunk/projects/Idris/src/LessMacrosMoreTypes/Printf.md",
+ "special": false,
"tags": [
"idris"
]
diff --git a/db/posts/6.json b/db/posts/6.json
index d45ddf4..f4b3c3b 100644
--- a/db/posts/6.json
+++ b/db/posts/6.json
@@ -7,6 +7,7 @@
"slugs": [
],
"source": "/home/nathan/Projects/Blog/projects/Markdown/HireMe.md",
+ "special": true,
"tags": [
]
-}
\ No newline at end of file
+}
diff --git a/lib/Config.rakumod b/lib/Config.rakumod
index aa178de..9a612fc 100644
--- a/lib/Config.rakumod
+++ b/lib/Config.rakumod
@@ -51,10 +51,20 @@ initComments(\{
body [
site-header $meta;
article :class, [
- post-header $id, $post, $db;
- div :class, [
- $content;
- ];
+ # Only generate the post header if the post isn't special
+ optl !$post.special, -> {post-header $id, $post, $db};
+ # If the post is special, wrap it in a special div
+ do if $post.special {
+ div :class, [
+ div :class, [
+ $content;
+ ];
+ ];
+ } else {
+ div :class, [
+ $content;
+ ];
+ }
];
# Only actually have the comment section if the post isn't hidden
optl !$post.hidden, -> {div :id, :class};
diff --git a/lib/DB/Post.rakumod b/lib/DB/Post.rakumod
index 6cf7ad0..c95eab6 100644
--- a/lib/DB/Post.rakumod
+++ b/lib/DB/Post.rakumod
@@ -43,6 +43,8 @@ has Str:D @.tags is rw is json = [];
has Bool:D $.hidden is json is rw = False;
#| An optional link to the source code for the post
has Str $.source-code is rw is json;
+#| Special posts follow different rendering rules
+has Bool:D $.special is json is rw = False;
#| Get the title for this post, intended to be extracted from whatever
#| document produced it
From 5ced2f6298a44b40c227078477a52a777731bf3a Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Mar 2025 16:15:20 -0400
Subject: [PATCH 15/21] Hireme initial contents
---
projects/Markdown/HireMe.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/projects/Markdown/HireMe.md b/projects/Markdown/HireMe.md
index 2395768..7f2ee1e 100644
--- a/projects/Markdown/HireMe.md
+++ b/projects/Markdown/HireMe.md
@@ -1,5 +1,11 @@
# Hire Me
+## Hire Me!
+
I am available for consulting gigs, if your company is new to using Rust, or interested in introducing Rust is into your tech stack, I can train your team on Rust, provide code review, and help with introducing Rust to your existing code base.
I am also currently looking for a full time role. Professionally, I am most comfortable working in Rust in backend or systems programming roles, I have historically specialized in tooling and consensus protocols, but also have experience working in embedded and with ETL pipelines. I'm also comfortable working with Go, C#, and most functional programming languages like F# or Haskell.
+
+A PDF copy of my CV can be found [here](https://static.stranger.systems/cv-no-phone.pdf), more information about me can be found on my [About](/about.html) page.
+
+If you are interested in hiring me for a full time position, or retaining me for a consulting role, please reach out to me at . My consulting rate varies depending on the scope of the work and the nature of your company, if you are worried about affordability, please do still reach out to me, I'm sure we can work something out.
From 51e50cd0d66325ae4d84c5d0bf0daba15f9e508b Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Mar 2025 17:33:40 -0400
Subject: [PATCH 16/21] Make about special and style
---
db/posts/2.json | 4 ++--
projects/Markdown/About.md | 2 ++
resources/colors.css | 23 +++++++++++++++++++++--
resources/main.css | 15 +++++++++++++++
4 files changed, 40 insertions(+), 4 deletions(-)
diff --git a/db/posts/2.json b/db/posts/2.json
index c97b41b..7d0c93b 100644
--- a/db/posts/2.json
+++ b/db/posts/2.json
@@ -7,7 +7,7 @@
"slugs": [
],
"source": "/home/nathan/Projects/Blog/projects/Markdown/About.md",
- "special": false,
+ "special": true,
"tags": [
]
-}
\ No newline at end of file
+}
diff --git a/projects/Markdown/About.md b/projects/Markdown/About.md
index 1b76ca4..9bc5c12 100644
--- a/projects/Markdown/About.md
+++ b/projects/Markdown/About.md
@@ -1,5 +1,7 @@
# About Me
+## About Me
+
My name is Nathan, I'm an engineering psychologist by training, and a systems
engineer by trade.
diff --git a/resources/colors.css b/resources/colors.css
index 5f67b2c..4b0bf8c 100644
--- a/resources/colors.css
+++ b/resources/colors.css
@@ -69,9 +69,20 @@ a:visited {
.post-title, .post-blurbs h1, .series-header h1, .series-list h1 {
color: var(--green);
}
-.post-body h2, .post-body h3, .post-body h4 {
- color: var(--fg-1);
+
+.post-body h2 {
+ color: var(--red);
+ background-color: var(--bg-1);
}
+
+.post-body h3 {
+ color: var(--blue);
+}
+
+.post-body h4 {
+ color: var(--violet);
+}
+
.post-info > *, .series-info > *, .header-links > * {
background-color: var(--bg-2);
}
@@ -101,6 +112,14 @@ footer > div {
background-color: var(--bg-2);
}
+/* Formatting for special pages */
+.special-post h2 {
+ color: var(--green);
+}
+.special-post h3 {
+ color: var(--red);
+}
+
/* Colorization for idris code blocks */
code {
color: var(--code-fg-0);
diff --git a/resources/main.css b/resources/main.css
index 5b70eab..2798174 100644
--- a/resources/main.css
+++ b/resources/main.css
@@ -128,6 +128,13 @@ body, .post, .series {
.post-body h2, .post-body h3, .post-body h4 {
text-align: center;
}
+
+.post-body h2 {
+ padding: var(--box-margin-vert) var(--box-margin-horz);
+ border-radius: var(--box-radius);
+ width: 100%;
+}
+
.post-blurbs, .series-blurbs, .series-list {
display: flex;
flex-direction: column;
@@ -261,3 +268,11 @@ footer i {
border-radius: 0.25rem;
padding: var(--footer-padding);
}
+
+/* Formatting for special pages */
+.special-post h2 {
+ font-size: 2rem;
+}
+.special-post h3 {
+ font-size: 1.5rem;
+}
From a2a811aa4a0349b85562d486050f51d0b9f1d214 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Mar 2025 17:56:00 -0400
Subject: [PATCH 17/21] Header link support
---
blog | 25 +++++++++++++++++++++++++
db/meta.json | 5 +++++
db/posts/2.json | 2 +-
db/posts/6.json | 2 +-
lib/DB/HeaderLink.rakumod | 2 ++
lib/Render/Head.rakumod | 2 +-
resources/main.css | 1 +
7 files changed, 36 insertions(+), 3 deletions(-)
diff --git a/blog b/blog
index c0af5b8..3f5a5ed 100755
--- a/blog
+++ b/blog
@@ -3,6 +3,7 @@ use v6.e.PREVIEW;
use DB;
use DB::BlogMeta;
+use DB::HeaderLink;
use DB::Series;
use DB::MarkdownPost;
use DB::IdrisPost;
@@ -418,6 +419,30 @@ multi MAIN(
$db.write: $db-dir;
}
+#| Add a header link
+multi MAIN(
+ "header-link",
+ "add",
+ #| The path of the database directory
+ IO::Path(Str) :$db-dir = $default-db-dir,
+) {
+ my $db = read-db $db-dir;
+ print "Link Text: ";
+ my $text = get;
+ print "Link Location: ";
+ my $link = get;
+ print "Icon: ";
+ my $icon = get;
+
+ my $header-link =
+ HeaderLink.new:
+ text => $text, link => $link, icon => $icon;
+
+ $db.meta.header-links.push: $header-link;
+
+ $db.write: $db-dir;
+}
+
#| Do a clean build and upload the blog to the remote
multi MAIN(
"upload",
diff --git a/db/meta.json b/db/meta.json
index ae53b19..444c9f0 100644
--- a/db/meta.json
+++ b/db/meta.json
@@ -2,6 +2,11 @@
"about-id": 2,
"base-url": "https://www.stranger.systems",
"header-links": [
+ {
+ "icon": "receipt",
+ "link": "/posts/by-slug/hire-me.html",
+ "text": "Hire Me"
+ }
],
"placeholder-id": 0,
"tagline": "Making software better by making it weird",
diff --git a/db/posts/2.json b/db/posts/2.json
index 7d0c93b..67b06ee 100644
--- a/db/posts/2.json
+++ b/db/posts/2.json
@@ -10,4 +10,4 @@
"special": true,
"tags": [
]
-}
+}
\ No newline at end of file
diff --git a/db/posts/6.json b/db/posts/6.json
index f4b3c3b..b64a656 100644
--- a/db/posts/6.json
+++ b/db/posts/6.json
@@ -10,4 +10,4 @@
"special": true,
"tags": [
]
-}
+}
\ No newline at end of file
diff --git a/lib/DB/HeaderLink.rakumod b/lib/DB/HeaderLink.rakumod
index e33b3dd..815009a 100644
--- a/lib/DB/HeaderLink.rakumod
+++ b/lib/DB/HeaderLink.rakumod
@@ -7,4 +7,6 @@ unit class HeaderLink is json(:pretty);
has Str:D $.link is required is rw;
+has Str:D $.text is required is rw;
+
has Str $.icon is rw;
diff --git a/lib/Render/Head.rakumod b/lib/Render/Head.rakumod
index dc9f2ef..98e9a55 100644
--- a/lib/Render/Head.rakumod
+++ b/lib/Render/Head.rakumod
@@ -75,6 +75,6 @@ sub site-header(BlogMeta:D $meta) is export {
header-link 'Series', '/series.html', 'book';
header-link 'About', '/about.html', 'info-circle';
header-link 'Feed', '/atom.xml', 'rss';
- ];
+ ], $meta.header-links.map(-> $link {header-link $link.text, $link.link, $link.icon});
]
}
diff --git a/resources/main.css b/resources/main.css
index 2798174..2290586 100644
--- a/resources/main.css
+++ b/resources/main.css
@@ -79,6 +79,7 @@ body, .post, .series {
font-size: 1.1rem;
flex-wrap: wrap;
margin-top: var(--box-margin-vert);
+ justify-content: center;
}
.header-links > a > span,
.post-series-tag > a > span,
From a5f9c659ffff0a444e0f753b8f05caa8e60f0843 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Mar 2025 18:07:37 -0400
Subject: [PATCH 18/21] No special characters in slugs
---
lib/DB/Post.rakumod | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/DB/Post.rakumod b/lib/DB/Post.rakumod
index c95eab6..983088d 100644
--- a/lib/DB/Post.rakumod
+++ b/lib/DB/Post.rakumod
@@ -62,9 +62,9 @@ method updated(--> DateTime:D) {
#| Get the list of slugs for this post, including ones auto generated from
#| the title, as well as any additional slugs
method all-slugs(--> Array[Str:D]) {
- my $title-words = self.title.lc.trim.words;
- my $long-title-slug = $title-words.join('-');
- my $six-word-slug = self.title.lc.words.head(6).join('-');
+ my @title-words = self.title.lc.trim.words.map(*.subst(/<-alnum>/, :g));
+ my $long-title-slug = @title-words.join('-');
+ my $six-word-slug = @title-words.head(6).join('-');
my Str:D @slugs = @!slugs.clone;
@slugs.push($long-title-slug);
@slugs.push($six-word-slug);
From a85bfdcd2a2c08a9255fd5b1e7bf9b4418cd788b Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Mar 2025 18:19:25 -0400
Subject: [PATCH 19/21] Fix series list styling
---
resources/main.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/main.css b/resources/main.css
index 2290586..787e907 100644
--- a/resources/main.css
+++ b/resources/main.css
@@ -145,7 +145,7 @@ body, .post, .series {
padding: var(--box-padding-vert) var(--box-padding-horz);
border-radius: var(--box-radius);
}
-.post-blurb, .series-list-blurb {
+.post-blurb, .series-list-blurb, .series-list-blurb-title {
width: 100%;
display: block;
border-radius: var(--box-radius);
From d79c4b6f463a0fa254db816b2491b56fe9a90219 Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Mar 2025 18:20:56 -0400
Subject: [PATCH 20/21] Tweak LessMacrosMoreTypes blurb
---
db/series/0.json | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/db/series/0.json b/db/series/0.json
index 8661aef..dfdf5e6 100644
--- a/db/series/0.json
+++ b/db/series/0.json
@@ -1,7 +1,7 @@
{
- "desc": "Macros are annoying, but an unfortunate fact of life in many programming languages. Especially in languages with nominally \"strong\" type systems, like Rust, macros are quite frequently needed to work around the type system to avoid needless repetition when consuming an API, generate formulaic boilerplate that only exists to please the type system, or work around the lack of variadic functions for things like printf. Lets explore the ways we can use dependently typed constructs to eliminate the need for such macros.",
+ "desc": "Macros are annoying, but an unfortunate fact of life in many programming languages.
Especially in languages with nominally \"strong\" type systems, like Rust, macros are quite frequently needed to work around the type system to avoid needless repetition when consuming an API, generate formulaic boilerplate that only exists to please the type system, or work around the lack of variadic functions for things like printf. Lets explore the ways we can use dependently typed constructs to eliminate the need for such macros.",
"post-ids": [
5
],
"title": "Less Macros, More Types"
-}
\ No newline at end of file
+}
From 8fc5a0fbda9742b7b906893e9acc62c15bdcca3b Mon Sep 17 00:00:00 2001
From: Nathan McCarty
Date: Sat, 15 Mar 2025 18:22:52 -0400
Subject: [PATCH 21/21] FreshLists skeleton
---
db/posts/7.json | 16 ++++++++++++++++
db/series/1.json | 7 +++++++
projects/Idris/Idris.ipkg | 1 +
.../Idris/src/DependentNuggets/FreshLists.md | 8 ++++++++
4 files changed, 32 insertions(+)
create mode 100644 db/posts/7.json
create mode 100644 db/series/1.json
create mode 100644 projects/Idris/src/DependentNuggets/FreshLists.md
diff --git a/db/posts/7.json b/db/posts/7.json
new file mode 100644
index 0000000..a2a5700
--- /dev/null
+++ b/db/posts/7.json
@@ -0,0 +1,16 @@
+{
+ "edited-at": [
+ ],
+ "hidden": false,
+ "idris": true,
+ "ipkg": "/home/nathan/Projects/Blog/projects/Idris/Idris.ipkg",
+ "posted-at": "2025-03-15T18:07:00.649227-04:00",
+ "slugs": [
+ ],
+ "source": "/home/nathan/Projects/Blog/projects/Idris/src/DependentNuggets/FreshLists.md",
+ "source-code": "https://git.stranger.systems/thatonelutenist/website/src/branch/trunk/projects/Idris/src/DependentNuggets/FreshLists.md",
+ "special": false,
+ "tags": [
+ "idris"
+ ]
+}
\ No newline at end of file
diff --git a/db/series/1.json b/db/series/1.json
new file mode 100644
index 0000000..94f387d
--- /dev/null
+++ b/db/series/1.json
@@ -0,0 +1,7 @@
+{
+ "desc": "Introductions to the building blocks of dependently typed programs.",
+ "post-ids": [
+ 7
+ ],
+ "title": "Dependent Nuggets"
+}
\ No newline at end of file
diff --git a/projects/Idris/Idris.ipkg b/projects/Idris/Idris.ipkg
index 8331301..ce522e4 100644
--- a/projects/Idris/Idris.ipkg
+++ b/projects/Idris/Idris.ipkg
@@ -19,6 +19,7 @@ authors = "Nathan McCarty"
modules = Idris
, Posts.HelloWorld
, LessMacrosMoreTypes.Printf
+ , DependentNuggets.FreshLists
-- main file (i.e. file to load at REPL)
-- main =
diff --git a/projects/Idris/src/DependentNuggets/FreshLists.md b/projects/Idris/src/DependentNuggets/FreshLists.md
new file mode 100644
index 0000000..2998e8b
--- /dev/null
+++ b/projects/Idris/src/DependentNuggets/FreshLists.md
@@ -0,0 +1,8 @@
+# FreshLists: Containers That Only Accept "Fresh" Elements
+
+```idris hide
+module DependentNuggets.FreshLists
+```
+
+When programming, we quite frequently encounter the for a data structure that can contain at most one of each given element. Typically, we would use a `Set`, which satisfies this constraint, but does so as a runtime invariant that must be taken on trust, and results in ergonomic concerns when used as a component of API design.
+