feat: add ash-related posts.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
James Harton 2023-08-16 17:18:23 +12:00
parent 4b4d603f25
commit af54dd8216
Signed by: james
GPG key ID: 90E82DAA13F624F4
27 changed files with 325 additions and 26 deletions

View file

@ -1,14 +1,15 @@
base_url = "https://harton.nz"
build_search_index = true
compile_sass = true
build_search_index = false
generate_feed = true
generate_rss = true
theme = "anatole-zola"
title = "James Harton"
description = "Nerd, maker and quadcopter breaker from Wairarapa, Aotearoa."
title = "James Harton"
taxonomies = [
{name = "tags"},
{name = "tags", feed = true},
]
[markdown]
@ -19,22 +20,22 @@ highlight_theme = "base16-ocean-light"
author = "James Harton"
[extra.show]
tags = false
links = false
tags = true
[languages.en.translations]
language_name = "English"
about = "About"
home = "Home"
tags = "Tags"
archive = "Archive"
links = "Links"
date_format = "%Y-%m-%d"
next_page = "Next Page"
home = "Home"
language_name = "English"
last_page = "Last Page"
links = "Links"
next_page = "Next Page"
tags = "Tags"
[extra.social]
github = "jimsynz"
linkedin = "james-harton-35013418a"
gitlab = "jimsy"
linkedin = "james-harton-35013418a"
mastodon = "jmshrtn@social.coop"

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View file

@ -0,0 +1,19 @@
<svg width="939" height="741" viewBox="0 0 939 741" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M470.482 294L743.966 741H729.078L394.43 418.305L470.482 294Z" fill="#FF5757"/>
<path d="M296.391 741H328.49L500.904 533.333L392.216 428.527L296.391 741Z" fill="#FF914D"/>
<path d="M431.037 741H340.052L432.58 629.553L431.037 741Z" fill="#FFBD59"/>
<path d="M439.934 741H606.641L441.62 619.199L439.934 741Z" fill="#FF5757"/>
<path d="M625.59 741L513.772 545.742L716.263 741H625.59Z" fill="#FF914D"/>
<path d="M610.739 732.969L447.037 612.141L502.967 544.774L610.739 732.969Z" fill="#FFBD59"/>
<path d="M287.086 741H196.999L377.62 445.78L287.086 741Z" fill="#FFBD59"/>
<path d="M470.482 294L766.5 741H729.078L394.43 418.305L470.482 294Z" fill="#FF5757"/>
<path d="M610.739 732.969L447.037 612.141L502.967 544.774L610.739 732.969Z" fill="#FFBD59"/>
<path d="M196.999 725.5L218.5 689L366 448.5H269.5L196.999 725.5Z" fill="#FF5757"/>
<path d="M197 688.5L197 294L260.5 441.5L197 688.5Z" fill="#FFBD59"/>
<path d="M763.305 317.27L763.305 690.5L710.805 510.77L763.305 317.27Z" fill="#FF5757"/>
<path d="M764.5 720L481 294L633.5 294L764.5 720Z" fill="#FFBD59"/>
<path d="M218.5 295L458.5 295L380 427.5L218.5 295Z" fill="#FFBD59"/>
<path d="M205 294L378.56 439.5L270.5 439.5L205 294Z" fill="#FF914D"/>
<path d="M641.439 294L763 294L705 500L641.439 294Z" fill="#FF914D"/>
<path d="M321 251.125V189.375C321 148.432 337.264 109.166 366.215 80.2154C395.166 51.2645 434.432 35 475.375 35C516.318 35 555.584 51.2645 584.535 80.2154C613.486 109.166 629.75 148.432 629.75 189.375V251.125" stroke="#FF5757" stroke-width="70" stroke-linecap="square" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,91 @@
+++
title = "Announcing Ash Authentication"
date = "2023-01-17"
[taxonomies]
tags = ["elixir", "ash", "oauth", "security"]
[extra]
cover_image = "ash_auth.webp"
+++
> This post is mirrored from it's original location on [the Alembic blog](https://alembic.com.au/blog/announcing-ash-authentication).
We're extremely pleased to announce the first public release of [ash_authentication](https://github.com/team-alembic/ash_authentication/) and [ash_authentication_phoenix](https://github.com/team-alembic/ash_authentication_phoenix/). Ash Authentication allows you to authenticate users in your Elixir applications using a simple DSL on your Ash resources.
Using Ash Authentication saves you the time, effort and maintenance burden of implementing your own solution. This improves significantly upon hand rolling or generating code because you rely on a library to implement authentication rather than managing that code yourself making it much more secure.
## What is Ash?
[Ash Framework](https://ash-hq.com/) is a declarative, resource-oriented application development framework for [Elixir](https://elixir-lang.org/). Resources can model anything; a database table, an external API, or even your own code. See the [recent 2.0 announcement here](https://elixirforum.com/t/ash-framework-a-declarative-resource-oriented-application-development-framework-for-elixir/51119).
You can think of Ash as a standardised way to model and build your applications by composing features together. It takes care of the common requirements of building an Elixir application so you can focus on building the parts that make your application special.
Ash Authentication fits neatly into the Ash ecosystem by defining a handful of extensions which can modify Ash resources to add new functionality. The ability of Ash to be extended is what makes Ash so powerful. In fact, Ashs own resources and APIs are extensions!
{{ figure(file="ash_auth_logo.png", caption="The Ash Authentication logo") }}
## What is Ash Authentication?
Ash Authentication is a drop-in authentication solution for users of the Ash framework who want to provide **password-based** or **social sign-in via OAuth 2.0**.
```elixir
defmodule MyApp.Accounts.User do
use Ash.Resource, extensions: [AshAuthentication]
authentication do
api MyApp.Accounts
strategies do
password do
identity_field :email
end
end
end
end
```
Current features include:
- Registration and sign-in using passwords.
- Registration and sign-in using OAuth 2.0.
- Confirmation and reset flows.
- Extensible LiveView components.
- Predefined configuration for Auth0 and GitHub sign-in with more coming soon.
- Works with or without Phoenix.
## Why Ash Authentication?
Critically, Ash Authentication is not a code generator, but an extension to the Ash ecosystem of declaratively defined applications. This means that youll receive security updates, performance improvements or new features just like any other Elixir library - with a simple `mix deps.update`. Additionally, it can be extended to provide new and novel authentication systems by third-party extensions or your own team.
Importantly, all the authentication code lives in the library, so youll never have to manage generated or hand written code in your own app. Stop worrying about security or updating generated code as it drifts over time.
## Future plans
We have a lot of exciting new features planned for Ash Authentication, including two factor authentication, lots more pre-configured social sign in options, and the ability to act as an OAuth2 provider allowing other apps to sign into your app.
If you feel Ash Authentication is missing something important then please feel free to [raise an issue](https://github.com/team-alembic/ash_authentication/issues).
## Can I use it in my app?
Yes. Weve put a lot of effort into making sure that the code is well structured and safe by default. It is in production already in several applications, including [Ash HQ](https://ash-hq.org/) and a handful of community owned apps from which we've received and addressed a lot of feedback. With that said, this is a new library that hasnt seen wider production usage, so by all means try it out but please don't use it for anything you would consider mission critical just yet.
With the help of the community, well be addressing feedback and issues as they arise. The benefit of the Ash approach is that any issues that are resolved and released will be available to your app via a simple upgrade of the Ash Auth dependency.
## Im interested, now what?
The first place to start would be our [Getting Started Guide](https://ash-hq.org/docs/guides/ash_authentication/latest/tutorials/getting-started-with-authentication), followed by the [extension documentation](https://ash-hq.org/docs/module/ash_authentication/latest/ashauthentication). If you run into any trouble - or just want a chat - wed love to help over on the [Ash Discord](https://discord.gg/NnkhUmkv).
😎 Happy Coding! 😎
## Links
- [https://hex.pm/packages/ash_authentication](https://hex.pm/packages/ash_authentication)
- [https://hex.pm/packages/ash_authentication_phoenix](https://hex.pm/packages/ash_authentication_phoenix)
- [https://github.com/team-alembic/ash_authentication](https://github.com/team-alembic/ash_authentication)
- [https://github.com/team-alembic/ash_authentication_phoenix](https://github.com/team-alembic/ash_authentication_phoenix)
- [Getting Started Guide](https://ash-hq.org/docs/guides/ash_authentication/latest/tutorials/getting-started-with-authentication)
- [Ash Auth Documentation](https://ash-hq.org/docs/module/ash_authentication/latest/ashauthentication)
- [https://ash-hq.org](https://ash-hq.org)
- [https://alembic.com.au](https://alembic.com.au)
- [Ash Discord](https://discord.gg/NnkhUmkv)

View file

@ -0,0 +1,70 @@
+++
title = "Announcing Reactor"
date = "2023-05-10"
[taxonomies]
tags = ["elixir", "ash", "reactor", "saga"]
[extra]
cover_image = "reactor.jpg"
+++
I'm excited to announce the first release of [Reactor](https://hex.pm/packages/reactor) - we've extracted the core ideas from `Ash.Engine` into it's own package, added compensation and turned it into a dynamic, concurrent, dependency resolving saga orchestrator.
With reactor you break up your workflow into a bunch of [steps](https://hexdocs.pm/reactor/Reactor.Step.html) and define the dependencies between then using arguments. Reactor will calculate the dependencies and run steps concurrency as their dependencies are fulfilled until there are no more steps left running.
Here's an example of a very simple step:
```elixir
defmodule Greeter do
use Reactor.Step
def run(%{whom: nil}, _, _), do: {:ok, "Hello, World!"}
def run(%{whom: whom}, _, _), do: {:ok, "Hello, #{whom}!"}
end
```
You can construct a Reactor statically using a nice DSL
```elixir
defmodule HelloWorldReactor do
use Reactor
input :whom
step :greet, Greeter do
argument :whom, input(:whom)
end
return :greet
end
```
iex> Reactor.run(HelloWorldReactor, %{whom: "Dear Reader"})
{:ok, "Hello, Dear Reader!"}
or you can build it programmatically:
iex> reactor = Builder.new()
...> {:ok, reactor} = Builder.add_input(reactor, :whom)
...> {:ok, reactor} = Builder.add_step(reactor, :greet, Greeter, whom: {:input, :whom})
...> {:ok, reactor} = Builder.return(reactor, :greet)
...> Reactor.run(reactor, %{whom: nil})
{:ok, "Hello, World!"}
## Rollback
If any step fails and it defines the `compensate/4` callback, Reactor will call the compensation function, giving the step the opportunity to recover, retry or clean up after itself. If the step is unable to recover, then any previously executed steps which define the `undo/4` callback will be called. This allows for transaction-like semantics even when working with multiple disparate resources.
## Summary
Shipping Reactor was a huge undertaking and I'm very proud of the result. Both [Zach](https://genserver.social/zachdaniel) and I are very eager to start using it to replace the existing `Ash.Engine` and unlocking new features of the Ash community.
## Links
- [hex.pm/packages/reactor](https://hex.pm/packages/reactor)
- [hexdocs.pm/reactor](https://hexdocs.pm/reactor/readme.html)
- [github.com/ash-project/reactor](https://github.com/ash-project/reactor)
- [Getting Started Guide](https://hexdocs.pm/reactor/getting-started-with-reactor.html)
- [ash-hq.com](https://ash-hq.com)
- [Ash Discord](https://discord.gg/NnkhUmkv)

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

View file

@ -3,12 +3,16 @@ title = "Elixir for Robots"
draft = false
date = "2020-06-12"
description = "Elixir's feature set is almost perfect for Robots. Why aren't we doing it this way?"
[taxonomies]
tags = ["robotics", "ros", "erlang", "elixir"]
[extra]
cover_image = "augie.jpg"
+++
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/elixir-for-robots-544e).
I've been working on [a robotics project](https://www.hackster.io/james-harton/augie-the-hexapod-robot-8e5125) for quite some time. Around the time that I started working on it in earnest I also started learning [Elixir](https://elixir-lang.org/). For those of you who don't know, Elixir is a programming language that lives on top of the [Erlang](https://www.erlang.org/) virtual machine (called [BEAM](<https://en.wikipedia.org/wiki/BEAM_(Erlang_virtual_machine)>)). Erlang's semantics were designed for [Ericsson](https://www.ericsson.com/) to run on it's telephony switches. The requirements of massive concurrency, fault tolerance and hot code reloading forced the team at Ericsson to make a bunch of really interesting decisions causing the creation of an immutable, functional language based around a robust actor system, message passing and network-transparent clustering.
> Erlang's original creator, Joe Armstrong died early in 2019. The New Stack has a great article [summarising Joe's contributions to computer science](https://thenewstack.io/why-erlang-joe-armstrongs-legacy-of-fault-tolerant-computing/).

View file

@ -3,9 +3,14 @@ title: Events vs Actions in Ember.js
published: false
description: Short explanation of javascript events and how they differ to actions in Ember.
date: 2019-01-28
tags: ember, javascript
taxonomies:
tags:
- ember
- javascript
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/events-vs-actions-in-emberjs-84o).
Recently I was working with some of my team on an Ember component which needed to react to [JavaScript events](http://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Events) they expressed some confusion about the difference between JavaScript events and Ember's [Action system](https://guides.emberjs.com/v3.7.0/templates/actions/). I decided to write up the basics here.
## Blowing bubbles

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View file

@ -2,11 +2,19 @@
title: Fold for COVID
published: true
description: Help fight the COVID-19 pandemic with your old laptop, Raspberry Pi, or other spare computer
tags: pandemic, rosettaathome, boinc, balena
date: 2020-04-16
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/98nfcrx4s0zmofjrsfs6.png
taxonomies:
tags:
- pandemic
- rosettaathome
- boinc
- balena
extra:
cover_image: fold-for-covid.png
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/fold-for-covid-3981).
Hi.
As some of you fine folks may know, I work as a backend software engineer for [Balena](https://www.balena.io/), occasionally building [cool robots](https://www.hackster.io/james-harton/augie-the-hexapod-robot-8e5125) or [shooting things with lasers](https://www.balena.io/blog/add-new-functionality-to-affordable-cnc-machines-using-cnc-js-and-balena/).

View file

@ -2,11 +2,18 @@
title: In defence of idiotic products
published: true
description: What you can learn by building a silly product.
tags: product, showdev, discuss
date: 2020-06-20
cover_image: https://live.staticflickr.com/3739/9476761515_c563c49b45_c_d.jpg
taxonomies:
tags:
- product
- showdev
- discuss
extra:
cover_image: yak.jpg
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/in-defense-of-idiotic-products-3bpd).
I was talking to a friend recently about how work on [my robotics project](@/blog/elixir-for-robots/index.md) and I said that over the weekend I had "popped a few things off my yak stack". Suddenly I realised what I had said, and immediately wondered what it would be like to have a todo list that behaved like a [stack machine](@/blog/stack-vm-part-1.md) with a limited set of instructions available to the user. Of course it's not very practical but since when has that stopped anyone?
If, like me, you get a lot of joy from the process of building a product then you'll probably also know that it's hard to find a project that's the right size to build by yourself. My last product attempt was a management system for sailing regattas - unfortunately it wound up being put on the back burner, and is likely to stay there indefinitely. Making things that people will part with money for is hard - especially if you're doing it on your own. Unfortunately I can't stop making things.

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

View file

@ -3,11 +3,17 @@ title: Lightweight dev tools.
published: true
description: Learning to live small.
date: 2023-08-10
tags: programming, productivity, developer
taxonomies:
tags:
- programming
- productivity
- developer
extra:
cover_image: huey.jpg
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/lightweight-dev-tools-565j).
Ive been forcing myself to use a barely usable laptop for any couch coding I do because I have a puppy who likes to jump into my lap - regardless of whether theres already something in it.
Usually I'd use a 15" MacBook Pro, but since both I and [my employer](https://alembic.com.au) would be pretty upset if it was damaged I've been using something with [a little lower stakes](https://laptoping.com/specs/product/lenovo-ideapad-100s-11/) that has been sitting unused on a shelf in my garage for quite a while.

View file

@ -3,9 +3,14 @@ title: Please keep using Ruby
published: true
description: This programmers plea for you learn some good OO
date: 2018-09-19
tags: ruby, healthydebate
taxonomies:
tags:
- ruby
- healthydebate
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/please-keep-using-ruby-4771).
There was a lot of mixed response to my [last post about Ruby](@/blog/please-stop-using-ruby.md) - and rightly so. Although I still don't think that any of my arguments are wrong, I talked only about the technical reasons why Ruby isn't a good fit for modern development. In this post I want to talk about why Ruby can still be a good choice for you.
## Rails is not Ruby

View file

@ -3,9 +3,14 @@ title: Please stop using Ruby
published: true
date: 2018-09-12
description: This programmers plea for you to stop making a mess
tags: ruby, healthydebate
taxonomies:
tags:
- ruby
- healthydebate
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/please-stop-using-ruby-4lf1).
I know what you're thinking but I want to start by saying that I love Ruby. Ruby is the language that made programming fun again for me after losing interest for a number of years. Ruby revitalised my career and allowed me to grow into the senior software engineer that I am today. Ruby is a lovely language. I just want you to stop using it.
For me Ruby came along in 2008 when I was working as a systems administrator at a local computer science department. I had written code in a bunch of languages before, and even enjoyed some of it but I was never able to feel completely productive or in tune with the language the way I felt when I started using Ruby. I was working with another team member to build a system to reimage lab computers using a combination of Rails, [PXELINUX](https://www.syslinux.org/wiki/index.php?title=PXELINUX) and BitTorrent. It was truly mad science. Also it worked really well - but that's a story for another day.

View file

@ -3,9 +3,14 @@ title: Pull Request Unicorn
published: true
date: 2018-09-03
description: How to be a pull request unicorn and make sure that everyone is happy all the time.
tags: discuss, codereview
taxonomies:
tags:
- discuss
- codereview
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/pull-request-unicorn-2749).
This is an adaptation of a talk I gave at at previous workplace. It's not a proscription - it's just what worked for one team at one particular time. The most important thing is to communicate with your team and set expectations.
## Objectives

View file

@ -3,9 +3,16 @@ title: Building a stack-based virtual machine
published: true
description: Exploration of stack based VMs and how they work
date: 2018-08-30
tags: rust, language, theory, computerscience
taxonomies:
tags:
- rust
- language
- theory
- computerscience
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/building-a-stack-based-virtual-machine-5gkd).
I've been reading [Fletcher Haynes' great series about register-based VM's](https://blog.subnetzero.io/post/building-language-vm-part-01/) over the last few days and I thought it was about time I talked about how I created [stack-vm crate](https://crates.io/crates/stack-vm).
Let's start with some background; my goto exercise when I am learning a new language is to implement a programming language. Building a language uses all the tools in a programmer's toolbox and gives you plenty of opportunity to flex your brain muscle. I figure if you can make a working (if simple) programming language using a particular tool then you can make almost anything your customer or employer wants. Over the years I've build languages in Ruby, Rubinius, Elixir, Erlang, Rust and C++/LLVM. Collectively all these languages have been called "Huia", despite not having anything in common except their author. Some of them are lying around the internet, some have been removed for posterity.

View file

@ -3,9 +3,16 @@ title: Building a stack-based virtual machine, part 2 - the stack
published: true
description: The second part in my exploration of stack based VMs and how they work
date: 2018-08-30
tags: rust, language, theory, computerscience
taxonomies:
tags:
- rust
- language
- theory
- computerscience
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/building-a-stack-based-virtual-machine-part-2---the-stack-d07).
You'll note that in [part one](@/blog/stack-vm-part-1.md) there wasn't really any code. Sorry about that. Let's fix that now.
If you want to jump to the end the completed source code for stack-vm is [on Gitlab](https://gitlab.com/jimsy/stack-vm).

View file

@ -3,9 +3,16 @@ title: Building a stack-based virtual machine, part 3 - instructions
published: true
description: The third part in my exploration of stack based VMs and how they work
date: 2018-09-05
tags: rust, language, theory, computerscience
taxonomies:
tags:
- rust
- language
- theory
- computerscience
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/building-a-stack-based-virtual-machine-part-3---instructions-4b3a).
In [part 2](@/blog/stack-vm-part-2.md) we build a stack that we can use to store our operands. That's pretty cool. The next step for us is to define instructions that allow us to perform operations on the operands in the stack.
In case you missed the definitions in [part 1](@/blog/stack-vm-part-1.md) let's recap what we need to make instructions.

View file

@ -3,9 +3,16 @@ title: Building a stack-based virtual machine, part 4 - code
published: true
description: The fourth part in my exploration of stack based VMs and how they work
date: 2018-09-14
tags: rust, language, theory, computerscience
taxonomies:
tags:
- rust
- language
- theory
- computerscience
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/building-a-stack-based-virtual-machine-part-4---code-3lmi).
Welcome back.
Now that we have our [instructions](@/blog/stack-vm-part-3.md) and our [stack](@/blog/stack-vm-part-2.md), we need a program that we can run. I've called this `Code`.

View file

@ -3,9 +3,16 @@ title: Building a stack-based virtual machine, part 5 - the machine
published: true
description: The fifth part in my exploration of stack based VMs and how they work
date: 2018-09-14
tags: rust, language, theory, computerscience
taxonomies:
tags:
- rust
- language
- theory
- computerscience
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/building-a-stack-based-virtual-machine-part-5---the-machine-3jif).
In the [last episode](@/blog/stack-vm-part-4.md) we built a code generator and a program representation so we now have all but that last bit we need to run our virtual machine.
## Introducing the machine

View file

@ -3,9 +3,16 @@ title: Building a stack-based virtual machine, part 6 - function calls
published: true
description: The sixth part in my exploration of stack based VMs and how they work
date: 2018-09-20
tags: rust, language, theory, computerscience
taxonomies:
tags:
- rust
- language
- theory
- computerscience
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/building-a-stack-based-virtual-machine-part-6---function-calls-2md5).
In [our last episode](@/blog/stack-vm-part-5.md) we finally ran a program. It was pretty exciting. This episode we're going to add function calling.
Up until now whenever we've used the term "stack" we've been referring to the operand stack, but typically when programmers talk about stacks they're talking about [call stacks](https://en.wikipedia.org/wiki/Call_stack).

View file

@ -3,9 +3,16 @@ title: Building a stack-based virtual machine, part 7 - conditionals
published: true
description: The seventh and last part in my exploration of stack based VMs and how they work
date: 2018-09-20
tags: rust, language, theory, computerscience
taxonomies:
tags:
- rust
- language
- theory
- computerscience
---
> This post is mirrored from it's original location on [dev.to](https://dev.to/jimsy/building-a-stack-based-virtual-machine-part-7---conditionals-5ee0).
In the [last episode](@/blog/stack-vm-part-6.md) we learned all about how to make our stack machine handle function calls. A pretty handy language feature, if I do say so. Today we're going to learn how to model conditionals. If you've been following along at home then you may have already figured this out for yourself, but in case you haven't let's walk through how it's done.
Note that we have all the infrastructure in place already to make conditionals work in our programs, it's all down to making new instructions that can optionally perform jumps.

9
sass/blog.scss Normal file
View file

@ -0,0 +1,9 @@
figure {
width: 50%;
margin-left: auto;
margin-right: auto;
}
figure figcaption {
text-align: center;
}

View file

@ -1,4 +1,7 @@
{% extends "anatole-zola/templates/basic.html" %}
{% block extra_head %}
<link rel="stylesheet" href="{{ get_url(path="blog.css") }}">
{% endblock %}
{% block more_social_link %}
<a href="https://code.harton.nz/james" aria-label="Go to ForgeFed instance">
<i class="fa-solid fa-code-branch"></i>

View file

@ -0,0 +1,12 @@
{% set large = resize_image(path=page.colocated_path ~ file, width=1000, height=750, op="fit_height") %}
{% set medium = resize_image(path=page.colocated_path ~ file, width=500, height=375, op="fit_height") %}
{% set small = resize_image(path=page.colocated_path ~ file, width=250, height=188, op="fit_height") %}
<figure>
<img src="{{ large.url }}" srcset="{{small.url}} 250w, {{medium.url}} 500w, {{large.url}} 1000w"
sizes="(max-width: 300px) 250px, (max-width: 600px) 500px, 1000px" />
{% if caption %}
<figcaption>
{{ caption }}
</figcaption>
{% endif %}
</figure>