Compare commits

...

4 commits

14 changed files with 262 additions and 7 deletions

View file

@ -58,7 +58,8 @@ config :jol, JOLWeb.Endpoint,
patterns: [
~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/gettext/.*(po)$",
~r"lib/jol_web/(controllers|live|components)/.*(ex|heex)$"
~r"lib/jol_web/(controllers|live|components)/.*(ex|heex)$",
~r"posts/*/.*(md)$"
]
]

View file

@ -1,14 +1,33 @@
defmodule JOL.Blog do
alias JOL.Blog.Post
defmodule NotFoundError do
defexception [:message, plug_status: 404]
end
use NimblePublisher,
build: Post,
build: JOL.Blog.Post,
from: Application.app_dir(:jol, "priv/posts/**/*.md"),
as: :posts
as: :posts,
parser: JOL.Blog.Parser
@posts Enum.sort_by(@posts, & &1.date, {:desc, Date})
@tags @posts |> Enum.flat_map(& &1.tags) |> Enum.uniq() |> Enum.sort()
def posts, do: @posts
def all_posts, do: @posts
def unique_tag_list, do: @tags
def recent_posts(num \\ 10) do
Enum.take(all_posts(), num)
end
def get_post_by_slug!(slug) do
Enum.find(all_posts(), &(&1.slug == slug)) ||
raise NotFoundError, "post ``slug=#{slug}` not found"
end
def get_posts_by_tag!(tag) do
case Enum.filter(all_posts(), &(tag in &1.tags)) do
[] -> raise NotFoundError, "posts tagged `#{tag}` not found"
posts -> posts
end
end
end

View file

@ -24,7 +24,8 @@ defmodule JOL.Blog.Parser do
title: toml_attrs["title"],
draft: toml_attrs["draft"],
tags: toml_attrs["taxonomies"]["tags"],
date: toml_attrs["date"]
date: toml_attrs["date"],
slug: toml_attrs["slug"]
}
parsed_body = String.trim(body)

8
lib/jol/blog/post.ex Normal file
View file

@ -0,0 +1,8 @@
defmodule JOL.Blog.Post do
@enforce_keys [:author, :title, :body, :tags, :date, :slug]
defstruct [:author, :draft, :title, :body, :tags, :date, :slug]
def build(_filename, attrs, body) do
struct!(__MODULE__, [author: "Jessica Phoenix Canady", body: body] ++ Map.to_list(attrs))
end
end

View file

@ -0,0 +1,13 @@
defmodule JOLWeb.BlogController do
use JOLWeb, :controller
alias JOL.Blog
def index(conn, _params) do
render(conn, "index.html", posts: Blog.recent_posts())
end
def show(conn, %{"slug" => slug}) do
render(conn, "show.html", post: Blog.get_post_by_slug!(slug))
end
end

View file

@ -0,0 +1,5 @@
defmodule JOLWeb.BlogHTML do
use JOLWeb, :html
embed_templates "blog_html/*"
end

View file

@ -0,0 +1,7 @@
<h1>The Blog</h1>
<ul>
<%= for post <- @posts do %>
<li><.link href={~p"/blog/#{post.slug}"}><%= post.title %></.link></li>
<% end %>
</ul>

View file

@ -0,0 +1,13 @@
<.link href={~p"/blog"}>← All posts</.link>
<h1><%= @post.title %></h1>
<p>
<time><%= @post.date %></time> by <%= @post.author %>
</p>
<p>
Tagged as <%= Enum.join(@post.tags, ", ") %>
</p>
<%= raw @post.body %>

View file

@ -17,7 +17,8 @@ defmodule JOLWeb.Router do
scope "/", JOLWeb do
pipe_through :browser
get "/", PageController, :home
get "/blog", BlogController, :index
get "/blog/:slug", BlogController, :show
end
# Other scopes may use custom stacks.

View file

@ -0,0 +1,58 @@
+++
slug = "mumble_voice_chat"
title = "LAN Voice Chat with Mumble"
date = 2024-04-16 11:51:33-04:00
draft = false
[taxonomies]
# these go in quotes, like the title
tags = ["software", "family"]
+++
My kids, 11yo and 7yo, are *really* into playing Minecraft and Terraria together. But, being siblings, it doesn't always go as well as anyone really wants it to. Sometimes, it's just that they can't be in the same *room* together. And since they both have fancy gaming headsets, they might as well get used to using voice chat.
But how?
It might be easy to give them Discord accounts and let them get to know the wide world of _Shitty Internet Culture_, _Awful Desktop Software_ and _Yay Notifications Forever_, but that seems like a horrible idea. Besides, what kind of Tech would I be if I didn't solve this problem by deploying software?
Enter: [Mumble](https://www.mumble.info/downloads/).
## The Server
The server is called Murnur, and has [an official Docker image](https://github.com/mumble-voip/mumble-docker).
In my case, I just plugged this into [Portainer](https://www.portainer.io) using their Docker Compose example.
(If, like me, you're trying to copy/paste a reasonable looking `docker-compose.json` file while two kids are louodly wondering why this is taking so long, you might also miss the `image: mumblevoip/mumble-server:<tag>` placeholder in their example. I've replaced that with `latest` below.)
```json
services:
mumble-server:
image: mumblevoip/mumble-server:latest
container_name: mumble-server
hostname: mumble-server
restart: on-failure
ports:
- 64738:64738
- 64738:64738/udp
```
Literally nothing to do but run that. The Docker logs will show a password for the `SuperUser` account, which you may want, but for our simple use-case here I never needed it.
## The Client
Mumble has [clients](https://www.mumble.info/downloads/) for Windows, Mac, Linux, iOS, and Android.
After running, you'll go through an Audio Setup Wizard that's fairly self-explanatory. Then it'll ask for connection details.
Here's the fun bit:
Since you're doing this locally and not publically, feel free to just pick a username for the server. Mumble will generate a certificate that identifies you, and normally you'd want to back that up and register with the server and all that, but for my use case none of that really matters. I just had the kids type in whatever name they wanted, and bam, they're in.
There's a `Root` channel everyone joins by default. Others can be created, but since I've just got the two kids, it's easy enough just to let them both be in that channel.
## How'd It Go?
It's been running faultless for a few days, with the only real issue being that the client likes to be restarted after the kids' laptops resume from suspend.
This rocks. Way better than having my kids ask me to buy _Discord Nitro_ for them.

View file

@ -0,0 +1,45 @@
+++
slug = "odyssey_ark_gen2_kvm"
title = "HOWTO: Use the KVM in the Odyssey Ark Gen2"
draft = false
date = 2024-01-02 14:00:00-05:00
[taxonomies]
tags = ["howto", "hardware"]
+++
I splurged on the absolutely monstrous Samsung Odyssey Ark 55" (Gen 2) monitor.
If you want a gigantic beast of a monitor and you don't mind it also being a Smart TV kinda thing with its own Home screen and wifi connection, this is a fantastic device!
<!-- more -->
One of my use cases for a primary display is plugging it into my desktop and also my laptop. The built-in KVM on the monitor is *superbly* under-documented. Here's how to set it up quickly:
## Connect your computers
The control box that the display connects to has four inputs: three HDMI and a DisplayPort. Pick the ones you like, and plug them in. Note which numbered port you connect which machine to.
You also need a USB connection to each, so connect a USB B or C cable from one of the labeled ports on the control box. There's three B and a C. Same as before, note which port you plugged which computer into.
## Connect your peripherals
On the right side (from the front) of the control box, there's a couple of USB A ports. Each provides a different amount of power.
There's only two, which will work for a mouse and a keyboard. I highly recommend plugging a USB hub of some kind into the control box, and then plugging your stuff into there.
## Configure the Odyssey Ark
In the Settings menu of the Odyssey Ark, navigate to Connection -> External Device Manager -> USB Input Port Setup.
This menu maps USB input Ports on the control box to display inputs. For each USB input you've plugged into on the back of the control box, map it to one (or more) displays.
---
That's it! Now, when you switch the monitor to a given input, it'll map the peripherals on the side ports to whichever USB Host port you've mapped them to.
## What about the multi-monitor thing?
Hell if I know, it requires some Windows-only software and so I haven't even attempted to give it a try yet.
Hope this helps someone!

View file

@ -0,0 +1,27 @@
+++
title = "HOWTO: Fix Steam Deck Unresponsive Touchscreen"
slug = "fix_steam_deck_touchscreen"
date = 2024-01-07 13:40:00-05:00
[taxonomies]
tags = ["howto", "hardware", "games", "shit's on fire yo"]
+++
If you've picked up your Steam Deck recently and the touchscreen just randomly stopped working, there is a *super* quick fix! Instead of factory resetting and wondering if you accidentally dropped the damn thing, do this:
<!-- more -->
1. Turn your Steam Deck off. Actually off from the Steam menu, not just suspended by tapping the power button.
1. Unplug the Deck if it's plugged in.
1. Hold the volume up button while you tap the power button. Keep holding the volume up button until you hear the chime.
1. Use the D-Pad to select "Setup Utility" and press A.
1. Navigate to the Power menu.
1. Choose Battery storage mode, and confirm.
Your Deck will power off and the power LED will blink three times to confirm battery storage mode.
Plug back into the power supply, and turn it back on normally. This will automatically disable battery storage mode.
After this, my touchscreen worked perfectly again.
Hope this helps someone!

View file

@ -0,0 +1,52 @@
+++
title = "The Names We Discard"
slug = "names_we_discard"
date = 2024-01-30 15:53:22-05:00
[taxonomies]
tags = ["trans"]
+++
> It's the name you used for _thirty-some years_. It's not _evil_.
>
> -- My wife
<!-- more -->
We were packing up boxes, finally clearing out our old house so we could sell it. A good few of the boxes were full of stuff from my childhood, things that my Mom gave me when I had could store them and get them out of the cramped apartment we lived in. They all had my deadname on them.
I was visibly upset by those boxes, and had just commented about the bittersweetness of these items (and others, like pictures of me from Back When), when my wife fired off the quote above and I had to take a minute to get myself together.
My wife isn't trans; her first experience with trans people in real life was when I transitioned; and she didn't say it maliciously. Just _thoughtlesly_, as if I had been acting silly. I had to figure out why her offhanded remark hurt _so damn much._
---
The name I discarded when I finally decided to embrace _the real me_ was indeed part of my life for 37 years. It was my email domain, it was (is) on my Bachelor of Science degree, On trophies. On boxes of childhood treasures in my Mom's neat handwriting. I don't hate it, and I don't hate who that person was.
But seeing it makes me uncomfortable.
It reminds me of the decades I outright suppressed and ignored my feelings.
The young child afraid to be _too_ girly, so folks wouldn't make fun of them.
The adolescent that knew that if they could wave a wand and, Sailor Moon-style, magically transform into a girl, that they'd do it. Y'know, just to _try it out_.
The 20-something that would occasionally get pretty drunk and refer to themselves as a girl, but even then would look around to make sure nobody heard.
The 30-something that was getting drunk _far_ too often, at times daily, and ordering cheap women's clothes from Amazon and hiding them from their wife and kids.
My deadname reminds me that, despite my driver's license and credit cards all having Jessica Phoenix Canady printed on them, there are still people I love (or loved) who will absolutely use t to refer to me. Sometimes to my face. Sometimes after I just corrected them, hoping in vain that they "just forgot."
It also reminds me of how I figured out why I never looked *right* in mirrors or pictures, and that I finally _do_.
Of the card my eldest son made me that says he'll always love me no matter what my gender is.
How my youngest son calls me "Dad" in public and doesn't understand or care why some folks give us weird looks sometimes.
That my wife still loves me, despite everything.
None of this even scratches the surface! The name kind of stores all my feelings about my transness encoded in it, and seeing it unexpectedly can drop them all back onto me at once.
Hearing or seeing my deadname is going to be a weird, bittersweet, somewhat painful thing for a while. Eventually, I hope, it's only used by the folks I'll never speak to again because they can't put it down.
But it's damn heavy, and I'm glad I don't always have to carry it anymore.

View file

@ -6,6 +6,7 @@ defmodule JOL.Blog.ParserTest do
content = """
+++
title = "test post"
slug = "test_post"
draft = false
date = 2024-01-02 14:00:00-05:00
@ -29,6 +30,10 @@ defmodule JOL.Blog.ParserTest do
assert post.attrs.title == "test post"
end
test "parses the title from zola-style posts", post do
assert post.attrs.title == "test_post"
end
test "parses the tags from zola-style posts", post do
assert post.attrs.tags == ["howto", "hardware"]
end