added content, post display

This commit is contained in:
Adam Piontek 2021-04-03 23:22:35 -04:00
parent ae9fff231b
commit a6186ba639
36 changed files with 751 additions and 199 deletions

View file

@ -2,7 +2,7 @@
$primary: #e48663;
$secondary: #00b0b0;
$success: #99c24d;
$info: #a67db8;
$info: #b489c7;
$warning: #f4d35e;
$white: #fff;

View file

@ -5,12 +5,15 @@
// Font, line-height, and color for body text, headings, and more.
// stylelint-disable value-keyword-case
$font-family-sans-serif: Lato, system-ui, -apple-system, "Segoe UI", Roboto,
$font-family-sans-serif: "Work Sans", system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
$font-family-secondary: Righteous, system-ui, -apple-system, "Segoe UI", Roboto,
$font-family-base: $font-family-sans-serif;
$font-family-brand: Righteous, system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
$font-family-monospace: "Fira Mono", SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace !important;
$font-family-code: "Fira Code", "Fira Mono", SFMono-Regular, Menlo, Monaco,

View file

@ -1,21 +1,30 @@
/* Fontsource Lato */
// @import "../node_modules/@fontsource/lato/100.css"; /* thin | normal */
// @import "../node_modules/@fontsource/lato/100-italic.css"; /* thin | italic */
@import "../node_modules/@fontsource/lato/300.css"; /* light | normal */
// @import "../node_modules/@fontsource/lato/300-italic.css"; /* light | italic */
@import "../node_modules/@fontsource/lato/400.css"; /* normal | normal */
// @import "../node_modules/@fontsource/lato/400-italic.css"; /* normal | italic */
@import "../node_modules/@fontsource/lato/700.css"; /* bold | normal */
// @import "../node_modules/@fontsource/lato/700-italic.css"; /* bold | italic */
// @import "../node_modules/@fontsource/lato/900.css"; /* black | normal */
// @import "../node_modules/@fontsource/lato/900-italic.css"; /* black | italic */
/* Fontsource Work Sans */
@import "../node_modules/@fontsource/work-sans/100.css"; /* thin | normal */
@import "../node_modules/@fontsource/work-sans/100-italic.css"; /* thin | italic */
// @import "../node_modules/@fontsource/work-sans/200.css"; /* thin-light | normal */
// @import "../node_modules/@fontsource/work-sans/200-italic.css"; /* thin-light | italic */
@import "../node_modules/@fontsource/work-sans/300.css"; /* light | normal */
@import "../node_modules/@fontsource/work-sans/300-italic.css"; /* light | italic */
@import "../node_modules/@fontsource/work-sans/400.css"; /* normal | normal */
@import "../node_modules/@fontsource/work-sans/400-italic.css"; /* normal | italic */
// @import "../node_modules/@fontsource/work-sans/500.css"; /* heavier | normal */
// @import "../node_modules/@fontsource/work-sans/500-italic.css"; /* heavier | italic */
@import "../node_modules/@fontsource/work-sans/600.css"; /* heavier? | normal */
@import "../node_modules/@fontsource/work-sans/600-italic.css"; /* heavier | italic */
@import "../node_modules/@fontsource/work-sans/700.css"; /* bold | normal */
@import "../node_modules/@fontsource/work-sans/700-italic.css"; /* bold | italic */
// @import "../node_modules/@fontsource/work-sans/800.css"; /* bolder? | normal */
// @import "../node_modules/@fontsource/work-sans/800-italic.css"; /* bolder? | italic */
// @import "../node_modules/@fontsource/work-sans/900.css"; /* black | normal */
// @import "../node_modules/@fontsource/work-sans/900-italic.css"; /* black | italic */
/* Fontsource Righteous */
@import "../node_modules/@fontsource/righteous/400.css"; /* normal | normal */
/* Fontsource Fira Mono */
@import "../node_modules/@fontsource/fira-mono/400.css"; /* normal | normal */
// @import "../node_modules/@fontsource/fira-mono/500.css"; /* heavier normal? */
@import "../node_modules/@fontsource/fira-mono/500.css"; /* heavier normal? */
// @import "../node_modules/@fontsource/fira-mono/700.css"; /* bold | normal */
/* Fontsource Fira Code */

View file

@ -23,25 +23,64 @@ body {
background-color: $gray-800;
height: 100%;
}
a {
color: $primary;
&:visited {
color: $info;
}
&:hover {
color: $secondary;
}
}
.border-gray-900 {
border-color: $gray-900 !important;
}
.text-gray-200 {
color: $gray-200;
}
.text-gray-300 {
color: $gray-300;
}
.text-gray-400 {
color: $gray-400;
}
.text-gray-500 {
color: $gray-500;
}
.border-10 {
border-width: 10px !important;
}
.border-20 {
border-width: 20px !important;
}
.fw-500 {
font-weight: 500;
}
.fw-600 {
font-weight: 600;
}
.font-sans-serif {
font-family: $font-family-sans-serif;
}
.font-brand {
font-family: $font-family-brand;
}
.font-code {
font-family: $font-family-code;
}
.fs-larger {
font-size: larger;
}
.fs-smaller {
font-size: smaller;
}
/* social icons */
#social-icons .link-light:hover {
color: $primary;
#social-icons .link-light {
color: $gray-100;
&:hover {
color: $primary;
}
}
/* resume separators */
@ -49,26 +88,46 @@ body {
display: flex;
align-items: center;
text-align: center;
color: $gray-300;
color: $gray-500;
font-family: $font-family-monospace;
font-size: smaller;
text-transform: uppercase;
font-weight: 600;
font-weight: 700;
&::before,
&::after {
content: "";
flex: 1;
border-bottom: 1px solid $secondary;
}
&:not(:empty)::before {
margin-right: 0.5em;
}
&:not(:empty)::after {
margin-left: 0.33em;
}
}
.separator::before,
.separator::after {
content: "";
flex: 1;
border-bottom: 1px solid $secondary;
/* blog */
.post-title a {
color: $gray-100;
text-decoration: none;
&:hover {
color: $primary;
text-decoration: underline;
}
}
.separator:not(:empty)::before {
margin-right: 0.5em;
.post-lede,
.post-body {
color: $gray-200;
h2, h3, h4, h5, h6 {
color: $gray-300;
margin-top: 2rem;
}
}
.separator:not(:empty)::after {
margin-left: 0.33em;
.post-lede.lead {
font-size: 1.25rem;
font-weight: 300;
color: $gray-300;
}
/* extra */

View file

@ -8,10 +8,13 @@ import "../css/app.scss";
import "../node_modules/@mdi/svg/svg/desktop-classic.svg"; // brand
// other:///
import "../node_modules/@mdi/svg/svg/home.svg";
import "../node_modules/@mdi/svg/svg/information.svg";
import "../node_modules/@mdi/svg/svg/account.svg";
import "../node_modules/@mdi/svg/svg/briefcase-account.svg";
import "../node_modules/@mdi/svg/svg/zip-disk.svg";
import "../node_modules/@mdi/svg/svg/typewriter.svg";
import "../node_modules/@mdi/svg/svg/calendar-clock.svg";
import "../node_modules/@mdi/svg/svg/tag-multiple.svg";
import "../node_modules/@mdi/svg/svg/rss.svg";
import "../node_modules/@mdi/svg/svg/account-hard-hat.svg";
// social

View file

@ -8,8 +8,8 @@
"dependencies": {
"@fontsource/fira-code": "^4.x",
"@fontsource/fira-mono": "^4.x",
"@fontsource/lato": "^4.x",
"@fontsource/righteous": "^4.x",
"@fontsource/work-sans": "^4.2.2",
"@mdi/svg": "^5.x",
"@popperjs/core": "^2.x",
"bootstrap": "^5.0.0-beta3",
@ -1292,16 +1292,16 @@
"resolved": "https://registry.npmjs.org/@fontsource/fira-mono/-/fira-mono-4.2.2.tgz",
"integrity": "sha512-t2WRThg+eLkQNQCtPG2sCCq40lz3xeb7nsL7P8l4+wfSRbdLQXAY5IebMftI2YEZR4MRRhdgrg0p5fi/2yXypA=="
},
"node_modules/@fontsource/lato": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/lato/-/lato-4.2.2.tgz",
"integrity": "sha512-ZE5WvqZQZinXpH8MaEiM9klDsUOfCHVQJ/tZKpNVQhi8mHt9WqPCROu500oI5jC3s6jaJuWsM7LfJ1zyEeW+XA=="
},
"node_modules/@fontsource/righteous": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/righteous/-/righteous-4.2.2.tgz",
"integrity": "sha512-mUjFblfCV6eWZj+lkrXFZsER8pq/3LOCoT3ezKPcerYH7StXQ8Gflcs0uMqacZP7CVLyzVUkPvSgLMQJTQvypg=="
},
"node_modules/@fontsource/work-sans": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/work-sans/-/work-sans-4.2.2.tgz",
"integrity": "sha512-fFm8a1TbE+qDnRNsf4R6Z/yZP2f3mbj54zSoWUMIWVkgTHM0RfpcOTqoIp7Aj5jon6na0Oynlq5yXvMA5p3pKA=="
},
"node_modules/@mdi/svg": {
"version": "5.9.55",
"resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-5.9.55.tgz",
@ -10919,16 +10919,16 @@
"resolved": "https://registry.npmjs.org/@fontsource/fira-mono/-/fira-mono-4.2.2.tgz",
"integrity": "sha512-t2WRThg+eLkQNQCtPG2sCCq40lz3xeb7nsL7P8l4+wfSRbdLQXAY5IebMftI2YEZR4MRRhdgrg0p5fi/2yXypA=="
},
"@fontsource/lato": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/lato/-/lato-4.2.2.tgz",
"integrity": "sha512-ZE5WvqZQZinXpH8MaEiM9klDsUOfCHVQJ/tZKpNVQhi8mHt9WqPCROu500oI5jC3s6jaJuWsM7LfJ1zyEeW+XA=="
},
"@fontsource/righteous": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/righteous/-/righteous-4.2.2.tgz",
"integrity": "sha512-mUjFblfCV6eWZj+lkrXFZsER8pq/3LOCoT3ezKPcerYH7StXQ8Gflcs0uMqacZP7CVLyzVUkPvSgLMQJTQvypg=="
},
"@fontsource/work-sans": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/work-sans/-/work-sans-4.2.2.tgz",
"integrity": "sha512-fFm8a1TbE+qDnRNsf4R6Z/yZP2f3mbj54zSoWUMIWVkgTHM0RfpcOTqoIp7Aj5jon6na0Oynlq5yXvMA5p3pKA=="
},
"@mdi/svg": {
"version": "5.9.55",
"resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-5.9.55.tgz",

View file

@ -9,8 +9,8 @@
"dependencies": {
"@fontsource/fira-code": "^4.x",
"@fontsource/fira-mono": "^4.x",
"@fontsource/lato": "^4.x",
"@fontsource/righteous": "^4.x",
"@fontsource/work-sans": "^4.2.2",
"@mdi/svg": "^5.x",
"@popperjs/core": "^2.x",
"bootstrap": "^5.0.0-beta3",

View file

@ -7,6 +7,7 @@
# General application configuration
use Mix.Config
# Tzdata db config
config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
# Custom application global variables

View file

@ -53,7 +53,8 @@ config :home73k, Home73kWeb.Endpoint,
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
~r"priv/gettext/.*(po)$",
~r"lib/home73k_web/(live|views)/.*(ex)$",
~r"lib/home73k_web/templates/.*(eex)$"
~r"lib/home73k_web/templates/.*(eex)$",
~r"priv/content/*/.*(md)$"
]
]

View file

@ -18,9 +18,16 @@ defmodule Home73k.Blog do
def list_posts, do: @posts
def list_tags, do: @tags
# defmodule NotFoundError do
# defexception [:message, plug_status: 404]
# end
defmodule NotFoundError do
defexception [:message, plug_status: 404]
end
def get_post_by_id!(id) do
case Enum.find(list_posts(), nil, &(&1.id == id)) do
%Post{} = post -> post
nil -> raise NotFoundError, "post with id=#{id} not found"
end
end
# def get_posts_by_tag!(tag) do
# case Enum.filter(list_posts(), &(tag in &1.tags)) do

View file

@ -1,8 +1,8 @@
defmodule Home73k.Blog.Post do
@enforce_keys [:title, :slug, :date, :author, :tags, :lede, :body, :corpus]
defstruct [:title, :slug, :date, :author, :tags, :lede, :body, :corpus]
@enforce_keys [:title, :id, :date, :author, :tags, :lede, :body, :corpus]
defstruct [:title, :id, :date, :author, :tags, :lede, :body, :corpus]
@strip_words ~w(the and are for not but had has was all any too one you his her can that with have this will your from they want been much some very them into which then now get its youll youre)
@strip_words ~w(the and are for not but had has was all any too one you his her can that with have this will your from they want been much some very them into which then now get its youll youre isnt wasnt)
@doc """
The public parse!/1 function begins the post parse process by reading
@ -36,7 +36,7 @@ defmodule Home73k.Blog.Post do
# """
defp parse_frontmatter([fm, md]) do
case parse_frontmatter_string(fm) do
{%{} = parsed_fm, _} -> {set_post_slug(parsed_fm), String.trim(md)}
{%{} = parsed_fm, _} -> {set_post_id(parsed_fm), String.trim(md)}
{:error, _} -> nil
end
end
@ -46,6 +46,11 @@ defmodule Home73k.Blog.Post do
# """ parse_lede/1
# Look for lede/excerpt/summary in content and extract it if present.
# We return updated frontmatter, and content with <!--more--> stripped.
defp parse_lede({%{lede: lede} = fm, md}) do
lede = String.trim(lede) |> Earmark.as_html!()
{Map.put(fm, :lede, lede), md}
end
defp parse_lede({fm, md}) do
{lede, body_md} = String.split(md, "<!--more-->", parts: 2) |> extract_lede()
{Map.put(fm, :lede, lede), String.replace(body_md, "<!--more-->", " ")}
@ -58,6 +63,8 @@ defmodule Home73k.Blog.Post do
# TODO: handle syntax highlighting
defp parse_body({fm, md}) do
Map.put(fm, :body, Earmark.as_html!(md))
# TODO: Earmark.as_ast(md) |> parse_body(fm)
# def parse_body({:ok, ast, _}, fm)
end
defp parse_body(_), do: nil
@ -66,12 +73,15 @@ defmodule Home73k.Blog.Post do
# Create a searchable word list for the post, for live searching
defp build_corpus(%{title: title, lede: lede, body: body, tags: tags} = post_data) do
# initialize corpus string from: title, lede, body, tags
corpus = (tags ++ [title, (lede && lede) || " ", body]) |> Enum.join(" ") |> String.downcase()
# scrub out (but replace with spaces):
# code blocks, html tags, html entities, newlines, forward and back slashes
html_scrub_regex = ~r/(<pre><code(.|\n)*?<\/code><\/pre>)|(<(.|\n)+?>)|(&#(.)+?;)|(&(.)+?;)|\n|\/|\\/
corpus = Regex.replace(html_scrub_regex, corpus, " ")
# grab text only, rejecting HTML
# downcase & scrub line breaks, slashes
corpus =
(tags ++ [title, (lede && lede) || " ", body])
|> Enum.join(" ")
|> Floki.parse_fragment!()
|> Floki.text()
|> String.downcase()
|> String.replace(["\n", "/", "\\", "(", ")", ":", "=", "_", ".", ",", "[", "]"], " ")
# restrict corpus to letters & numbers,
# then split to words (space delim), trimming as we go
@ -108,8 +118,7 @@ defmodule Home73k.Blog.Post do
# """
defp parse_frontmatter_string(fm) do
try do
String.trim_leading(fm, "-")
|> Code.eval_string()
Code.eval_string(fm)
rescue
_ -> {:error, nil}
end
@ -124,22 +133,23 @@ defmodule Home73k.Blog.Post do
defp extract_lede([body]), do: {nil, body}
# """ set_frontmatter_slug
# If no slug in frontmatter, convert title to slug and add to map
# """ set_post_id
# If no id in frontmatter, convert title to id and add to map
# """
defp set_post_slug(%{slug: _} = fm), do: fm
defp set_post_id(%{id: _} = fm), do: fm
defp set_post_slug(%{title: title} = fm) do
Map.put(fm, :slug, parse_title_to_slug(title))
defp set_post_id(%{title: title} = fm) do
Map.put(fm, :id, parse_title_to_id(title))
end
# """ parse_title_to_slug
# Takes a post title and returns a slug cleansed for URI request path
# """ parse_title_to_id
# Takes a post title and returns a id cleansed for URI request path
# """
defp parse_title_to_slug(title) do
title = String.downcase(title)
def parse_title_to_id(title) do
title_text = Floki.parse_fragment!(title) |> Floki.text() |> String.downcase()
Regex.replace(~r/[^a-z0-9 ]/, title, "")
~r/[^a-z0-9 ]/
|> Regex.replace(title_text, "")
|> String.split(" ", trim: true)
|> Stream.reject(&reject_word?/1)
|> Enum.join("-")
@ -147,7 +157,7 @@ defmodule Home73k.Blog.Post do
# """ reject_word?
# Returns true to reject short or common words
# Used by parse_title_to_slug and build_corpus
# Used by parse_title_to_id and build_corpus
# """
defp reject_word?(word), do: String.length(word) < 3 || word in @strip_words
end

View file

@ -5,6 +5,10 @@ defmodule Home73kWeb.HomeController do
render(conn, "index.html")
end
def about(conn, _params) do
render(conn, "about.html", page_title: "About")
end
def resume(conn, _params) do
render(conn, "resume.html", page_title: "Résumé")
end

View file

@ -23,7 +23,7 @@ defmodule Home73kWeb.Endpoint do
plug Plug.Static,
at: "/",
from: :home73k,
gzip: true,
gzip: (Mix.env() not in [:dev, :test]),
only: ~w(css fonts images js favicon.ico robots.txt DF185CEE29A3D443_public_key.asc)
# Code reloading can be explicitly enabled under the

View file

@ -0,0 +1,54 @@
defmodule Home73kWeb.BlogLive do
use Home73kWeb, :live_view
alias Home73k.Blog
@impl true
def mount(_params, _session, socket) do
socket
|> live_okreply()
end
@impl true
def handle_params(_params, _url, socket) do
socket
|> assign(:page_title, "Blog")
|> assign(:posts, Blog.list_posts())
|> live_noreply()
end
# @impl true
# def handle_event("suggest", %{"q" => query}, socket) do
# {:noreply, assign(socket, results: search(query), query: query)}
# end
# @impl true
# def handle_event("search", %{"q" => query}, socket) do
# case search(query) do
# %{^query => vsn} ->
# {:noreply, redirect(socket, external: "https://hexdocs.pm/#{query}/#{vsn}")}
# _ ->
# {:noreply,
# socket
# |> put_flash(:error, "No dependencies found matching \"#{query}\"")
# |> assign(results: %{}, query: query)}
# end
# end
# defp search(query) do
# if not Home73kWeb.Endpoint.config(:code_reloader) do
# raise "action disabled when not in development"
# end
# for {app, desc, vsn} <- Application.started_applications(),
# app = to_string(app),
# String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
# into: %{},
# do: {app, vsn}
# end
defp format_date(date) do
Calendar.strftime(date, "%B %-d, %Y")
end
end

View file

@ -0,0 +1,41 @@
<main class="container d-flex justify-content-center">
<div class="col-12 col-md-10 col-lg-8 col-xl-7 col-xxl-6 pb-2 mb-4 mt-3">
<%= for post <- @posts do %>
<div class="post border-bottom border-gray pb-4 mb-3">
<h2 class="post-title fs-2 fw-normal mb-2">
<%= live_redirect "#{post.title}", to: Routes.post_path(@socket, :show, post) %>
</h2>
<div class="post-date font-monospace text-gray-500 <%= if length(post.tags) == 0, do: "mb-3" %>">
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline"] %>
<%= post.date |> format_date() %>
</div>
<%= if length(post.tags) > 0 do %>
<div class="post-tags fs-6 mb-3">
<%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %>
<%= for {tag, i} <- Enum.with_index(post.tags) do %>
#<%= tag %><%= i < (length(post.tags) - 1) && "," || "" %>
<% end %>
</div>
<% end %>
<div class="post-lede lead">
<%= raw post.lede %>
</div>
<p>
<%= live_redirect raw("Read more&hellip;"), to: Routes.post_path(@socket, :show, post), class: "fs-6" %>
</p>
</div>
<% end %>
</div>
</main>

View file

@ -1,48 +0,0 @@
defmodule Home73kWeb.PageLive do
use Home73kWeb, :live_view
@impl true
def mount(_params, _session, socket) do
{:ok,
assign(socket, query: "", results: %{}, page_title: "~")
|> put_flash(:success, "Log in was a success. Good for you.")
|> put_flash(:error, "Lorem ipsum dolor sit amet consectetur adipisicing elit.")
|> put_flash(
:info,
"Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatibus dolore sunt quia aperiam sint id reprehenderit? Dolore incidunt alias inventore accusantium nulla optio, ducimus eius aliquam hic, pariatur voluptate distinctio."
)
|> put_flash(:warning, "Oh no, there's nothing to worry about!")
|> put_flash(:primary, "Something in the brand color.")}
end
@impl true
def handle_event("suggest", %{"q" => query}, socket) do
{:noreply, assign(socket, results: search(query), query: query)}
end
@impl true
def handle_event("search", %{"q" => query}, socket) do
case search(query) do
%{^query => vsn} ->
{:noreply, redirect(socket, external: "https://hexdocs.pm/#{query}/#{vsn}")}
_ ->
{:noreply,
socket
|> put_flash(:error, "No dependencies found matching \"#{query}\"")
|> assign(results: %{}, query: query)}
end
end
defp search(query) do
if not Home73kWeb.Endpoint.config(:code_reloader) do
raise "action disabled when not in development"
end
for {app, desc, vsn} <- Application.started_applications(),
app = to_string(app),
String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
into: %{},
do: {app, vsn}
end
end

View file

@ -1,48 +0,0 @@
<section class="phx-hero">
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
<p>Peace of mind from prototype to production</p>
<form phx-change="suggest" phx-submit="search">
<input type="text" name="q" value="<%= @query %>" placeholder="Live dependency search" list="results" autocomplete="off"/>
<datalist id="results">
<%= for {app, _vsn} <- @results do %>
<option value="<%= app %>"><%= app %></option>
<% end %>
</datalist>
<button type="submit" phx-disable-with="Searching...">Go to Hexdocs</button>
</form>
</section>
<section class="row">
<article class="column">
<h2>Resources</h2>
<ul>
<li>
<a href="https://hexdocs.pm/phoenix/overview.html">Guides &amp; Docs</a>
</li>
<li>
<a href="https://github.com/phoenixframework/phoenix">Source</a>
</li>
<li>
<a href="https://github.com/phoenixframework/phoenix/blob/v1.5/CHANGELOG.md">v1.5 Changelog</a>
</li>
</ul>
</article>
<article class="column">
<h2>Help</h2>
<ul>
<li>
<a href="https://elixirforum.com/c/phoenix-forum">Forum</a>
</li>
<li>
<a href="https://webchat.freenode.net/?channels=elixir-lang">#elixir-lang on Freenode IRC</a>
</li>
<li>
<a href="https://twitter.com/elixirphoenix">Twitter @elixirphoenix</a>
</li>
<li>
<a href="https://elixir-slackin.herokuapp.com/">Elixir on Slack</a>
</li>
</ul>
</article>
</section>

View file

@ -0,0 +1,61 @@
defmodule Home73kWeb.PostLive do
use Home73kWeb, :live_view
alias Home73k.Blog
@impl true
def mount(%{"id" => id}, session, socket) do
# IO.inspect(params, label: "postlive params")
IO.inspect(session, label: "postlive session")
post = Blog.get_post_by_id!(id)
socket
|> assign(:page_title, "Blog \\ #{post.title}")
|> assign(:post, post)
|> live_okreply()
end
# @impl true
# def handle_params(params, _url, socket) do
# socket
# |> assign(:page_title, "Blog")
# |> assign(:posts, Blog.list_posts())
# |> live_noreply()
# end
# @impl true
# def handle_event("suggest", %{"q" => query}, socket) do
# {:noreply, assign(socket, results: search(query), query: query)}
# end
# @impl true
# def handle_event("search", %{"q" => query}, socket) do
# case search(query) do
# %{^query => vsn} ->
# {:noreply, redirect(socket, external: "https://hexdocs.pm/#{query}/#{vsn}")}
# _ ->
# {:noreply,
# socket
# |> put_flash(:error, "No dependencies found matching \"#{query}\"")
# |> assign(results: %{}, query: query)}
# end
# end
# defp search(query) do
# if not Home73kWeb.Endpoint.config(:code_reloader) do
# raise "action disabled when not in development"
# end
# for {app, desc, vsn} <- Application.started_applications(),
# app = to_string(app),
# String.starts_with?(app, query) and not List.starts_with?(desc, ~c"ERTS"),
# into: %{},
# do: {app, vsn}
# end
defp format_date(date) do
Calendar.strftime(date, "%B %-d, %Y")
end
end

View file

@ -0,0 +1,36 @@
<main class="container d-flex justify-content-center">
<div class="col-12 col-md-10 col-lg-9 col-xl-8 col-xxl-7 pb-2 mb-4 mt-3">
<div class="post border-bottom border-gray pb-4 mb-3">
<h2 class="post-title fs-2 fw-normal mb-2"><%= raw @post.title %></h2>
<div class="post-date font-monospace text-gray-500 <%= if length(@post.tags) == 0, do: "mb-3" %>">
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline"] %>
<%= @post.date |> format_date() %>
</div>
<%= if length(@post.tags) > 0 do %>
<div class="post-tags fs-6 mb-3">
<%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %>
<%= for {tag, i} <- Enum.with_index(@post.tags) do %>
#<%= tag %><%= i < (length(@post.tags) - 1) && "," || "" %>
<% end %>
</div>
<% end %>
<div class="post-lede lead">
<%= raw @post.lede %>
</div>
<div class="post-body">
<%= raw @post.body %>
</div>
</div>
</div>
</main>

View file

@ -18,9 +18,12 @@ defmodule Home73kWeb.Router do
pipe_through :browser
get "/", HomeController, :index
get "/about", HomeController, :about
get "/resume", HomeController, :resume
get "/folio", HomeController, :folio
live "/live", PageLive, :index
live "/blog", BlogLive, :index
# live "/blog/page/:page", BlogLive, :older
live "/blog/:id", PostLive, :show
end
# Other scopes may use custom stacks.

View file

@ -0,0 +1,48 @@
<main class="container d-flex justify-content-center">
<div class="col-12 col-md-10 col-lg-8 col-xl-7 col-xxl-6 border-bottom border-gray pb-3 mb-5 mt-3">
<h2 class="fs-2 fw-600 mb-0">About</h2>
<div class="fs-5 font-monospace text-gray-500">Adam Piontek. Human.</div>
<figure class="my-4 border-start border-gray border-5 ps-3 py-2">
<blockquote class="blockquote">
<p>&ldquo;Probabilities collapse. I become increasingly unlikely.&rdquo;</p>
</blockquote>
<figcaption class="blockquote-footer text-gray-400 mb-0">
Robert Charles Wilson, <cite title="Divided by Infinity">Divided by Infinity</cite>
</figcaption>
</figure>
<div class="post-body">
<p>
From Minnesota, via California, now New York. I'm always learning, and I often think most things should slow down a little, though I'm still working on that practice for myself. I've been a tech nerd since my teens in the 1990s, but didn't begin to take it seriously until around 2009.
</p>
<p>
I'm now a Desktop Systems Engineer with a large global law firm, involved in desktop & mobile security, testing, deployment, and elevated user support.
</p>
<p>
I enjoy spending time with my many pets, talking science fiction & politics, going hiking, and tinkering with programming projects. I've written a few things I'm reasonably proud of &mdash; for work, for family, for myself &mdash; including this website!
</p>
<p>
This site is powered by a custom little <%= link "Phoenix", to: "https://phoenixframework.org/" %> project hosted on a <%= link "Linode", to: "https://www.linode.com/" %> VPS, where I also run instances of <%= link "Gitea", to: "https://gitea.io/" %>, <%= link "Miniflux", to: "https://miniflux.app/" %>, <%= link "ZNC", to: "https://znc.in/" %>, <%= link "The Lounge", to: "https://thelounge.chat/" %>, <%= link "Calibre-Web", to: "https://github.com/janeczku/calibre-web" %>, <%= link "BicBucStriim", to: "https://projekte.textmulch.de/bicbucstriim/" %>, a little <%= link "hubot", to: "https://hubot.github.com/" %> for my family's group chat (Discord for now), and some other odds & ends.
</p>
<p>
At home, between a plucky Raspberry Pi 4 running Ubuntu Linux, and a beefier Windows machine, I also run: <%= link "Jellyfin", to: "https://jellyfin.org/" %>, <%= link "Plex", to: "https://www.plex.tv/" %>, and several apps via Docker, including <%= link "audioserve", to: "https://github.com/izderadicka/audioserve" %>, <%= link "Komga", to: "https://komga.org/" %>, <%= link "Unifi Controller", to: "https://github.com/jacobalberty/unifi-docker" %>, and a group of others (like <%= link "transmission-openvpn", to: "https://hub.docker.com/r/haugene/transmission-openvpn/" %> and <%= link "nzbget", to: "https://hub.docker.com/r/linuxserver/nzbget" %> (Arr!))
</p>
<p>
I'm very lucky to have some super amazing people in my life, always reminding me of how little I know, enabling beautiful experiences, and appreciating places and systems I otherwise wouldn't give a second thought to.
</p>
</div>
</div>
</main>

View file

@ -13,11 +13,11 @@
<div class="col-12 col-sm-10 col-md-7 col-lg-6 col-xl-5 col-xxl-4 justify-content-start ms-lg-3">
<h2 class="font-monospace fs-2 fw-600 mb-0">
<h2 class="font-monospace fs-2 fw-500 mb-0">
<%= icon_div @conn, "mdi-account-hard-hat", [class: "icon baseline me-2"] %><span>Working on it!</span>
</h2>
<div class="font-monospace fs-5">I've made some things over the years (like this site!), and at some point I'll highlight some here.</div>
<div class="font-monospace text-gray-200 fs-5">I've made some things over the years (like this site!), and at some point I'll highlight some here.</div>
</div>

View file

@ -13,11 +13,11 @@
<div class="col-auto justify-content-start ms-lg-3">
<h2 class="font-monospace fs-2 fw-600 mb-0">
<h2 class="font-monospace fs-2 fw-500 mb-0">
<%= icon_div @conn, "mdi-account", [class: "icon baseline me-2"] %><span>Adam Piontek</span>
</h2>
<div class="font-monospace fs-5">Desktop Systems Engineer. Human.</div>
<div class="font-monospace text-gray-500 fs-5">Desktop Systems Engineer. Human.</div>
<div id="social-icons" class="mt-1">
<%= for s <- socials(@conn) do %>

View file

@ -1,14 +1,14 @@
<main class="container-fluid h-100 d-flex justify-content-center align-items-center">
<main class="container-fluid d-flex justify-content-center align-items-center">
<div class="d-flex flex-column mt-5">
<div class="col-auto justify-content-start">
<h2 class="font-monospace fs-2 fw-600 mb-0">
<h2 class="font-monospace fs-2 fw-500 mb-0">
<%= icon_div @conn, "mdi-account", [class: "icon baseline me-2"] %><span>Adam Piontek</span>
</h2>
<div class="font-monospace fs-5">Desktop Systems Engineer.</div>
<div class="font-monospace text-gray-500 fs-5">Desktop Systems Engineer.</div>
<div id="social-icons" class="mt-1">
<%= for s <- socials_prof(@conn) do %>
@ -24,21 +24,21 @@
<div class="separator mt-4 mb-2">qualifications</div>
<ul>
<ul class="text-gray-200">
<%= for qualif <- resume_qualifs() do %>
<%= content_tag :li, qualif %>
<% end %>
</ul>
<div class="separator mt-4 mb-2">experience</div>
<div class="separator mt-4 mb-n2">experience</div>
<%= for %{employer: employer, positions: positions} <- resume_experience() do %>
<div class="fs-5 border-bottom border-gray mt"><%= employer%></div>
<div class="fs-5 border-bottom border-gray mt-3 mb-2"><%= employer%></div>
<div class="mb-3">
<%= for position <- positions do %>
<div>
<span><%= position.title %></span>
<span class="text-gray-300">&middot; <%= position.start %> &mdash; <%= position.end %></span>
<span class="text-gray-200"><%= position.title %></span>
<span class="text-gray-500">&middot; <%= position.start %> &mdash; <%= position.end %></span>
</div>
<% end %>
</div>

View file

@ -1,10 +1,10 @@
<nav class="navbar fixed-top navbar-expand-lg navbar-dark bg-dark px-1 px-sm-2 px-lg-3 px-xl-4 px-xxl-5 py-3">
<nav class="navbar <%= if navbar_fixed?(@conn), do: "fixed-top" %> navbar-expand-lg navbar-dark bg-dark px-1 px-sm-2 px-lg-3 px-xl-4 px-xxl-5 py-3">
<div class="container-fluid">
<h1 class="my-0 py-0 lh-base">
<%= link to: Routes.home_path(@conn, :index), class: "navbar-brand fs-1 text-secondary" do %>
<%= icon_div @conn, "mdi-desktop-classic", [class: "icon baseline"] %>
<span class="fw-light" style="font-family: Righteous;">\\73k</span>
<span class="fw-light font-brand">\\73k</span>
<% end %>
</h1>
@ -41,7 +41,13 @@
</li>
<li class="nav-item">
<%= link nav_link_opts(@conn, to: Routes.page_path(@conn, :index), class: "nav-link font-monospace fs-6") do %>
<%= link nav_link_opts(@conn, to: Routes.home_path(@conn, :about), class: "nav-link font-monospace fs-6") do %>
<%= icon_div @conn, "mdi-information", [class: "icon baseline"] %><span>\About</span>
<% end %>
</li>
<li class="nav-item">
<%= link nav_link_opts(@conn, to: Routes.blog_path(@conn, :index), class: "nav-link font-monospace fs-6") do %>
<%= icon_div @conn, "mdi-typewriter", [class: "icon baseline"] %><span>\Blog</span>
<% end %>
</li>

View file

@ -1,6 +1,11 @@
defmodule Home73kWeb.LayoutView do
use Home73kWeb, :view
def navbar_fixed?(conn) do
[Routes.home_path(conn, :index), Routes.home_path(conn, :folio)]
|> Enum.member?(Phoenix.Controller.current_path(conn))
end
def nav_link_opts(conn, opts) do
case Keyword.get(opts, :to) == Phoenix.Controller.current_path(conn) do
false -> opts

View file

@ -35,7 +35,7 @@ defmodule Home73k.MixProject do
[
{:phoenix, "~> 1.5.8"},
{:phoenix_live_view, "~> 0.15.1"},
{:floki, ">= 0.27.0", only: :test},
{:floki, "~> 0.30.1"},
{:phoenix_html, "~> 2.11"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_dashboard, "~> 0.4"},

View file

@ -7,11 +7,14 @@
"credo": {:hex, :credo, "1.5.5", "e8f422026f553bc3bebb81c8e8bf1932f498ca03339856c7fec63d3faac8424b", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dd8623ab7091956a855dc9f3062486add9c52d310dfd62748779c4315d8247de"},
"earmark": {:hex, :earmark, "1.4.14", "d04572cef64dd92726a97d92d714e38d6e130b024ea1b3f8a56e7de66ec04e50", [:mix], [{:earmark_parser, ">= 1.4.12", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "df338b8b1852ee425180b276c56c6941cb12220e04fe8718fe4acbdd35fd699f"},
"earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"},
"elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"},
"fast_html": {:hex, :fast_html, "2.0.4", "4910ee49f2f6b19692e3bf30bf97f1b6b7dac489cd6b0f34cd0fe3042c56ba30", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "3bb49d541dfc02ad5e425904f53376d758c09f89e521afc7d2b174b3227761ea"},
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
"floki": {:hex, :floki, "0.30.1", "75d35526d3a1459920b6e87fdbc2e0b8a3670f965dd0903708d2b267e0904c55", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e9c03524447d1c4cbfccd672d739b8c18453eee377846b119d4fd71b1a176bb8"},
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"},
"hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"},
"html5ever": {:hex, :html5ever, "0.8.0", "2114c27c28dbb0fc6a8e3936cfb7ca3ea12edc722f865cde4702ba9da4ed3f1c", [:mix], [{:rustler, "~> 0.21.0", [hex: :rustler, repo: "hexpm", optional: false]}], "hexpm", "4c50ac60a9dca114c359f657aca063cbc2970cdd572aa5e202b1368c2ecdb45a"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.1", "e8a67da405fe9f0d1be121a40a60f70811192033a5b8d00a95dddd807f5e053e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "68d92656f47cd73598c45ad2394561f025c8c65d146001b955fd7b517858962a"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
@ -23,6 +26,7 @@
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mochiweb": {:hex, :mochiweb, "2.20.1", "e4dbd0ed716f076366ecf62ada5755a844e1d95c781e8c77df1d4114be868cdf", [], [], "hexpm", "d1aeee7870470d2fa9eae0b3d5ab6c33801aa2d82b10e9dade885c5c921b36aa"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"phoenix": {:hex, :phoenix, "1.5.8", "71cfa7a9bb9a37af4df98939790642f210e35f696b935ca6d9d9c55a884621a4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "35ded0a32f4836168c7ab6c33b88822eccd201bcd9492125a9bea4c54332d955"},
"phoenix_html": {:hex, :phoenix_html, "2.14.3", "51f720d0d543e4e157ff06b65de38e13303d5778a7919bcc696599e5934271b8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "efd697a7fff35a13eeeb6b43db884705cba353a1a41d127d118fda5f90c8e80f"},
@ -34,11 +38,13 @@
"plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"},
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"rustler": {:hex, :rustler, "0.21.1", "5299980be32da997c54382e945bacaa015ed97a60745e1e639beaf6a7b278c65", [:mix], [{:toml, "~> 0.5.2", [hex: :toml, repo: "hexpm", optional: false]}], "hexpm", "6ee1651e10645b2b2f3bb70502bf180341aa058709177e9bc28c105934094bc6"},
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.0", "da9d49ee7e6bb1c259d36ce6539cd45ae14d81247a2b0c90edf55e2b50507f7b", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5cfe67ad464b243835512aa44321cee91faed6ea868d7fb761d7016e02915c3d"},
"telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
"toml": {:hex, :toml, "0.5.2", "e471388a8726d1ce51a6b32f864b8228a1eb8edc907a0edf2bb50eab9321b526", [:mix], [], "hexpm", "f1e3dabef71fb510d015fad18c0e05e7c57281001141504c6b69d94e99750a07"},
"tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"yamerl": {:hex, :yamerl, "0.8.1", "07da13ffa1d8e13948943789665c62ccd679dfa7b324a4a2ed3149df17f453a4", [:rebar3], [], "hexpm", "96cb30f9d64344fed0ef8a92e9f16f207de6c04dfff4f366752ca79f5bceb23f"},

View file

@ -1,15 +0,0 @@
---
%{
title: "Markdown for blog posts is nice, says Sebastian",
slug: "markdown-for-blog-posts",
date: ~N[2016-05-01 13:30:00],
author: "Adam Piontek",
tags: ["sample", "demo"]
}
---
All the **Markdown** here...
```javascript
console.log('hello world!')
```

View file

@ -0,0 +1,40 @@
---
%{
title: "Pi-Hole Love",
id: "pihole-love",
date: ~N[2018-03-24 14:00:00],
author: "Adam Piontek",
tags: ["home", "privacy", "tech", "raspberrypi"],
}
---
2021 UPDATE: while the pi-hole is a very cool project, I eventually grew tired of maintaining a separate DNS service. I still use unbound on my edgerouter but now my raspberry pi just runs some local web services.
Original post below:
<!--more-->
I'm really liking my [Pi-Hole](https://pi-hole.net/)
Katie gave me a Raspberry Pi Zero W for Christmas, and while it's also inspired other projects to come down the line, I finally settled on using it for Pi-hole.
It's better than an ad-blocker in your browser because it means the ads — and a lot of malware! — never get loaded in the first place. Nothing on your network can even *find* the stuff you don't want, so you save some meagre bandwidth, and computing power that would be spent scanning stuff as it comes in.
It's really easy to set up and use — as long as you can change the DNS setting for your router — and has a helpful browser interface.
The default block lists are plenty good, but since it's so easy to use it's well worth adding some more block lists.
I'm using every "ticked" list from "[The Big Blocklist Collection](https://firebog.net/)," and it's working well so far.
I also whitelisted several relevant domains from both the bottom of the Big Blocklist page, and Pi-hole's own "[Commonly Whitelisted Domains](https://discourse.pi-hole.net/t/commonly-whitelisted-domains/212)" page.
### What happens if something I need is blocked?
We found my partner couldn't use a website related to her profession. It would just get stuck loading and it wasn't clear why. With Chrome (can do this in Firefox, too) I was able to use F12 to open the DevTools, click Network, reload the page, and see in red what was failing to load.
In this case, the Pi-hole was blocking "cdns.gigya.com," a content delivery service hosting a javascript the page wanted to use. Off to the Pi-hole web interface (or cli with `pihole -w`) to quickly whitelist, and the site worked again.
The same for "prod.imgur.map.fastlylb.net," apparently needed for imgur to work.
And the best part is you don't really need anything other than a Raspberry Pi Zero W, an SD card, and a micro USB power connection to make it work -- though I added in a USB OTG cable and a USB ethernet adapter I had laying around to avoid any issues with wifi.

View file

@ -0,0 +1,75 @@
---
%{
title: "If no one will do it, it neednt be done — on BS jobs and toddler bosses",
id: "if-noone-will-do-it-neednt-be-done-bs-jobs-toddler-bosses",
date: ~N[2018-05-17 15:10:00],
author: "Adam Piontek",
tags: ["economics", "society", "work"],
}
---
There's a new book out by David Graeber, and I haven't read it yet, but I'm dying to write about a *review* of it, whose sole criticism is stuck in my craw. I'm talking about Miranda Purves' [Bloomberg review](https://www.bloomberg.com/news/articles/2018-05-15/bullshit-jobs-by-david-graeber-review).
I imagine a lot of people identifying with her view, yet I find it deeply flawed and misguided.
<!--more-->
First, an aside:
She complains that Graeber's argument "gets into a 'Why Capitalism? Because Capitalism!' tautology." But she's oversimplifying.
Capitalism isn't one thing. It's a web of beliefs, as all ideologies are, and it has many effects and many activities. It's fully possible to take something complex and explore aspects of it as arising in relation to other aspects. In other words, some complex human phenomena do seem tautological if you reduce them to extremely simple single-word labels.
Take addiction, for example. Some addicts may use partly due to feelings of disconnect — loneliness, social anxiety, etc. Yet, using will exacerbate those problems in the long run, not solve them. One could be overly simplistic and say "Oh you're just saying 'Why addiction? Because addiction!' That's a tautology, you must be completely wrong!" Oh, so smart…
But let's get to her real complaint about the book, which, aside from her hand-wringing anecdote about a housecleaner she was dissatisfied with once, amounts to this paragraph:
> But when I read Graebers account of how a boss taught him and his fellow dishwashers to be less efficient at a restaurant job he held in his youth by yelling at them for washing the dishes as fast as possible and then loafing around, smoking, I sympathized with the boss. When youre the one who owns or feels ownership over the endeavor, you think theres always something more that needs doing. To you, its important. Its hard to get other people to see what you see needs to get done; its human nature to want to have things done your own way. Sometimes Graeber reminds me of a 12-year-old who insists theres no reason to clean up his or her room, while the parent knows that bugs, vermin, mental chaos, or depression could otherwise ensue.
Look, I get it. I do. Other humans can be frustrating or disappointing sometimes.
But …
First, adult employees / workers / laborers — are not children. It's utterly disrespectful to imagine them so. It's paternalistic. It's ugly. Don't do it. I shouldn't have to say more about that.
Second … ok, in her anecdote about the disappointing housecleaner, she identifies herself as as "wrong and an entitled a--hole," but only in the context of the cleaner's difficult life experience.
My argument would be this:
As a human relating with another human, if you do not clearly agree with the other human on your expectaions, and become angry and resentful when your unspoken assumptions are not met, you're being "wrong and an entitled a**hole" regardless of the other person's life experience to date.
Purves wants to imagine that employees will jump on board with the world projects &amp; endeavors of their employers.
Why would they?
Her words suggest that if they don't, there's something childish about them.
Her words suggest that, if you don't see the importance of what your employer wants done, you're not worthy of respect.
Honestly, isn't the opposite true?
If you get angry and resentful when people don't read your minds, aren't *you* the jerk? If you expect to be able to change the terms of your agreements and relationships on a dime, without renegotiating, aren't *you* the jerk?
Here's my radical response:
If people are unwilling to do some work that you imagine as important, that work doesn't need to be done.
Even more precisely:
If people are unwilling to do some work that you imagine as important, by the terms that you imagine they should accept, that work doesn't need to be done, or you need to negotiate different terms — *or* you need to find new ways of persuading people.
The unspoken criticism she's not really bringing herself to say is: "Just because you think it's bullshit doesn't mean it is! It might be really important!"
And this is where it's extra ridiculous to equate workers with children: it's really the boss's attitude that is the most childish. Who but a toddler imagines that other people should honor their every whim? Who but King Baby thinks their ideas are obviously correct and that the world ought to revolve around them?
She said, "its human nature to want to have things done your own way."
Is it?
Or is that the nature of a toddler? Wouldn't we rather prefer that an adult human would want things done in a way that all involved find acceptable?
But I guess if this sort of thing is the best critique someone can muster, Graeber's new book must be pretty good.
I'm sorry, Miranda, but you and your boss friends are going to have to, ultimately, one day, learn to respect and treat other humans as equals, as I imagine (well, hope) you would expect in other relationships you conduct.
It's going to require more skills than appealing to authority and wishing people would read your minds.

View file

@ -0,0 +1,126 @@
---
%{
title: "Lana Del Arr's Ultrapirates (parody lyrics for songs from Lana Del Reys Ultraviolence",
id: "lana-del-arr-ultrapirates-parody-lyrics",
date: ~N[2018-11-09 13:30:00],
author: "Adam Piontek",
tags: ["song", "lyrics", "lanadelrey", "music", "parody", "pirates"],
}
---
A couple years back I was into the idea of Lana Del Rey's album "Ultraviolence" as a pirate-themed album. My family joked about a version called "Ultrapirates."
Naturally I ended up writing some alternate lyrics. There's only two; I never got around to doing "Buccaneer Baby" (for "Brooklyn Baby"). Anyway, I figured I should put them online for posterity…
<!--more-->
### Songs by Lana Del Arr, from the hit album "Ultrapirates"
(credit to Katie for the title & artist)
- ["Florida Cargo" (to the tune of "Florida Kilos")](#florida-cargo)
- ["The Other Schooner" (to the tune of "The Other Woman")](#other-schooner)
#### "Florida Cargo" (to the tune of "Florida Kilos")
<a name="florida-cargo"></a>
White shine, royal sailor, doubloons,
Don't you see them gleam,
They're special, just for you.
White palms, black gun powder on the move,
Sailin' for a dream,
Stowin' diamonds in the hold.
I feel you, royal sailor, feel me,
Leavin' us is hard, joinin' us is free.
We fly false flag, fly false flag on each go,
But you already know that, you already know.
Come on down to Florida,
We got somethin' for ya.
We could steal the cargo
From the keys, sailor, arr yeah.
Guns in the summertime,
Don't get scurvy, eat a lime.
Prison isn't nothin' to us,
If ye'll be on our side.
Yo-ho, yo-ho, yo-ho,
And all the rope things
Yo-ho, yo-ho, yo-ho.
Gold on your ears in round hoops,
You'll like us royal sailor,
If you like lots of loot.
White shine, from the silver in your teeth,
Or wear it in your beard,
Or round your neck beneath.
Come on down to Florida,
We got somethin' for ya.
We could steal the cargo
From the keys, sailor, arr yeah.
Guns in the summertime,
Don't get scurvy, eat a lime.
Prison isn't nothin' to us,
If ye'll be on our side.
Yo-ho, yo-ho, yo-ho,
And all the rope things
Yo-ho, yo-ho, yo-ho.
We could dry dock in Port Royal,
Arr, drink the night away.
Parrots even drink in Port Royal,
Arr, that's what they all say.
(You believe us, don't you, sailor?)
Come on down to Florida,
We got somethin' for ya.
We could steal the cargo,
From the keys, sailor, arr yeah.
Guns in the summertime,
Don't get scurvy, eat a lime.
Prison isn't nothin' to us,
If ye'll be on our side.
Yo-ho, yo-ho, yo-ho,
All the Floridians say,
Yo-ho, yo-ho, yo-ho,
All the Colombians say,
Yo-ho, yo-ho, yo-ho,
And all my shipmates,
Yo-ho, yo-ho, yo-ho.
That's how we do it like.
Mm, arr, royal sailor,
White shine, royal sailor,
Gold teeth, royal sailor, yeah,
Drink the night away.
#### "The Other Schooner" (to the tune of "The Other Woman")
<a name="other-schooner"></a>
The other schooner has time to trim and furl her sails
The other schooner is perfect where her rival fails
And she's never seen with caulking in her hull anywhere
The other schooner is always filled with French doubloons
The other schooner has fresh taught rope for every boom
There are never rats that scatter everywhere
And when each sailor comes to call
He finds her waiting like a lonesome queen
'Cause to be by her side it's such a change from old routine
But the other schooner will always have you on the swab
The other schooner will never let you have some grog
And as the years go by the other schooner will bore you to the bone
The bone
The bone

View file

@ -0,0 +1,63 @@
---
%{
title: "Creating a gif from a video",
id: "creating-gif-from-video",
date: ~N[2020-05-23 04:41:00],
author: "Adam Piontek",
tags: ["cli", "ffmpeg ", "gif", "multimedia", "video", "notes"],
}
---
As a personal note, since I've done it for family a few times now, here are steps one can use to create a gif from a longer video. <!--more-->
### Stage 1: trim out a short video clip
Use ffmpeg to trim out the part you want. It's ok to grab more than you need, you can trim it further in the next stage, but you don't want to upload a massive video to the gif site, so best to trim a small bit first:
```shell
ffmpeg -ss 00:14:23 -t 4 -i "/path/to/source/video.mkv" \
-c:v copy -an "output_video_clip.mp4"
```
- `-ss` is the timecode you want to start from
- `-t` is the ultimate duration of the clip you want, so, seconds from start time
- `-an` tells it to discard audio since you ultimately want a gif
Sometimes merely copying doesn't work, due to keyframes in the source or whatever. It's ok to re-encode, here's an example with higher-quality settings since it's just a small clip:
```shell
ffmpeg -ss 00:14:23 -t 4 -i "/path/to/source/video.mkv" \
-c:v libx264 -preset veryslow -crf 16 -an "output_video_clip.mp4"
```
### Stage 2: modify online
I've been using [Kapwing](https://www.kapwing.com/) to create gifs. You can trim more, add subtitles, crop, etc.
Default options tend to create pretty big gifs, so it's a really good idea to set the output size to a custom scale, say, no bigger than 480 on the longest edge? Also a good idea to crop it if there's unecessary stuff.
Scaled properly, then using Settings of "Output: GIF" and "Quality: High" should get pretty good results.
However, if you want to try using ffmpeg to generate the gif, set "Output: Default" for an MP4 file, and try the optional 3rd stage:
### (Optional) Stage 3: convert MP4 to gif
Using the Kapwing MP4 output, here's how to convert to gif with ffmpeg, using 2 passes with a color pallette to get better quality:
```shell
vidname="final_kapwing_video_name"
fps="20"
scale="500"
palette="/tmp/palette.png"
filters="fps=${fps},scale=${scale}:-1:flags=lanczos"
ffmpeg -i "${vidname}.mp4" -vf "$filters,palettegen" -y $palette
ffmpeg -i "${vidname}.mp4" -i $palette \
-filter_complex "$filters &#91;x]; &#91;x]&#91;1:v] paletteuse" \
"${vidname}.fps${fps}.scale${scale}.gif"
```
You can play around with the fps &amp; scale to adjust the size but this should give a decent result.
### Final note:
In future, it might be worth exploring doing the 2nd part, or the 1st &amp; 2nd parts, with a video editor like Shotcut or OneShot …. but for now, I'm familiar with Kapwing and it's pretty easy.

View file

@ -11,6 +11,8 @@ Preheat oven to 350
So as you know, I just throw my meatloaf together, but here are my estimates based on a bunch of published recipes:
<!--more-->
- Put 2 pounds ground beef (80/20) in a large bowl.
- Squirt about 1/2 cup ketchup over it.
- Sprinkle about a tablespoon of Worcestershire sauce over.

View file

@ -1,4 +1,4 @@
defmodule Home73kWeb.PageLiveTest do
defmodule Home73kWeb.BlogLiveTest do
use Home73kWeb.ConnCase
import Phoenix.LiveViewTest