The original vision for the world wide web was a read/write medium. A few years ago I thought, “what if an HTML file could update itself?”
When I first started making goofy web pages many years ago, the index.html file felt magical. It felt like the file was the website, and a few .html files in a directory were a little community of pages. Over time, working with things like PHP and WordPress, then Django, Rails, and Node.js, “web pages” faded into the background, even my small personal websites felt like systems that just generated ephemeral files by necessity.
After moving my personal website (this one!) to 11ty in 2020, and inspired by ideas like the “file-first” approach of Omar Rizwan’s TabFS and the “read/write web” revitalization of XH’s mmm.page and Beaker Browser, I had the idea: “A web page that can update itself!”
I quickly threw together a few getElementById() and .innerHTML s and had a working prototype of an index.html file that could update its own DOM via the UI, and then save a copy of itself to a local file.
Next, realizing that I needed a way to reach beyond the UI in the browser to update things like the page title, or global styles, I added a control panel to update metadata.
After adding controls for global styles like the <body> width I realized I needed to rethink the UX if I wanted the project to be easy to use. For example, shrinking the <body> width also squished the control panel to the same width, after all, it was just another element in the <body> .
I had started the prototype with the classic trio of index.html , script.js and style.css , but after a few commits switched to a single file with <script> and <style> tags within the HTML. Coming back to the project a few months after that initial prototype, the code was getting to be quite repetitive and hard to follow. It was hard to keep track of logic, there were a lot of event listeners. I decided to refactor all of the JavaScript into modules using Vite as a bundler, and followed that with another refactor in TypeScript.
One early design choice was that when switching an element like an <h1> or <p> into editing mode, I would hide the original element and insert a <textarea> with the original element’s content. At first this made things like canceling the edit easier because I could just un-hide the “real” element, but I needed lots of custom logic for saving the change for different types of elements.
The TypeScript refactor had helped me finally pin down some of these bugs and I realized that it would be easier to use contenteditable="true" to edit the “real” element in-place. With this change, I was able to avoid any synchronization on save, and simplify the editing UI to just a set of buttons that I inserted below the element in the DOM. I was still able to restore the original content after a “cancel” action by storing it in data- attributes.
My original intention was to add a few more element types, things like <ul> and <strong> and <em> , but I decided it had been long enough and I should finish up with my current feature set of:
... continue reading