This is such an improvement to the backend of my website it warrants a blog post of celebration. For far too long I've been dealing with a tiny ass edit form on thoughts. I tend to not edit them, and try to get them right in one go. This is kinda the point of a thought, its a quick post meant to be the size of a tweet, but sometimes I'm leaving thoughts on a video or long post and want to make sure I have a good save point, but I just keep the thing in draft and hope I don't loose if for far too long.

Results

Let's see this change in action!!

before

This is the tiny ass form nested deeply in the flow of the feed. When I made it I naively just swapped out the post itself with the edit form, and swapped the post back in after edit.

screenshot-2024-12-19T00-58-43-976Z.png

thoughts is built with HTMX btw so all html is rendered in the backend and swapped by htmx client side.

after

Now the edit is a full page modal with a nice blurry backdrop effect to the rest of the content. This feels pretty similar to making a new post on twitter.

screenshot-2024-12-19T00-59-21-503Z.png

How

How did I do this with htmx? I had to break out of the mindset my brain was in with swapping in place and letting the edit form take over the entire screen.

First the empty #modal-container was added to the top of every page.


<div id="modal-container"></div>

Then each post that gets added to the page already had an edit button, but now the target is set to #modal-container, and the swap is set to innerHTML so that we keep the #modal-container in place.


<button class="h-8 w-8 p-1 text-center" hx-get="{{ config.root }}/edit-thought/{{ post.id }}"
        hx-target="#modal-container" hx-swap="innerHTML" title="Edit">
    {% include 'edit.svg' %}
</button>

Now the edit post that is returned from the server is turned into a full height and width modal with a nice backgrop blur over the content.


<div id="modal-container">
    <div class="fixed inset-0 z-50 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
        <!-- Background backdrop -->
        <div class="fixed inset-0 bg-black/30 backdrop-blur-sm"></div>

        <!-- Modal panel -->
        <div class="flex min-h-screen items-center justify-center p-4">
            <div class="relative w-full max-w-4xl transform rounded-xl bg-zinc-900 p-6 shadow-2xl transition-all">
... similar to the original edit form in here
            </div>
        </div>
    </div>
</div>

Clearing the modal

One notable change to the original edit form is clearing the modal container on submit. It is done with an hx-on::after-request event and one line of js.


<form id="websiteForm"
      hx-patch="{{ config.root }}/post/html/"
      method="POST"
      name="newPost"
      hx-target="#post-{{ post.id }}"
      hx-swap="outerHTML"
      hx-on::after-request="document.getElementById('modal-container').innerHTML = ''">
    ```

Similarly on the Cancel button.

``` html
<button class="rounded-lg border border-black bg-zinc-950 px-6 py-3 text-lg hover:bg-zinc-900"
        type="button"
        onclick="document.getElementById('modal-container').innerHTML = ''">
    Cancel
</button>

Hotkeys

Now I struggled to get this right with htmx, and it seemed like things were getting more complicated by trying to get the buttons to trigger using htmx triggered keyboard events, I ended up just using javascript. Its still in the same file, so locality of behavior is barely an issue on this one anyways, and the js was just working.


<script>
document.addEventListener('keydown', function(e) {
    if (e.key === 'Escape') {
        document.getElementById('modal-container').innerHTML = '';
    }
    if (e.key === 'Enter' && e.ctrlKey) {
        e.preventDefault();
        htmx.trigger('#websiteForm', 'submit');
    }
});
</script>

Other small change

When the server is returns an empty list of posts the post container would change size and cause a layout shift. Now the empty posts element, while not great does not cause layout shift.


<ul id="posts" class="min-h-screen"><li>No posts</li></ul>