Coding Adventure: Micro.blog Private Notes + 7.css Fun
2025-02-11
Back in October I posted a bit[1] about using YAML front-matter with Micro.blog private notes to extend it's functionally. I ended up releasing that to Lillihub[2] users earlier this year.
I've moved over to using analog index cards[3] for most of my daily note taking but I still have several collections of notes I'm growing. And I needed a place for them. Why not use Micro.blog? Is what I asked myself. Now that Manton has built out a versioning system that saves note changes for 60 days I feel more confident using the service with a larger volume of notes. But what I've built out so far in Lillihub (tagging and having titles) is not quite enough.
A Fetch Quest - Getting my notes
Just like my last coding adventure, first I needed to authenticate. I'm not going to write that up again. But do not fear fellow adventurer. I have a map with the secrets of Micro.blog's indieauth spelled out[4].
So now that I have the magical token, I need only ask the API nicely for what I need.
Firstly, my notebooks
const fetching = await fetch(`https://micro.blog/notes/notebooks`, { method: "GET", headers: { "Authorization": "Bearer " + token } } );
const results = await fetching.json();
And then upon choosing a notebook and divining its id, I can get all my lovely notes...
const fetching = await fetch(`https://micro.blog/notes/notebooks/${id}`, { method: "GET", headers: { "Authorization": "Bearer " + token } } );
const eNotes = await fetching.json();
But wait! I cannot read my lovely notes! They are garbled and undecipherable to my eye.
A Wizards Secret - Decrypt and encrypt my notes 🧙♀️
Luckily some documentation[5] lights the way.
The end. 🤣
Okay, kidding! I'll share (some of) my secrets.
Behold!
The first of three spells - creating a key 🔑
The first spell component needed is the private note key from Micro.blog. This I saved in localStorage so I could access when needed.
const imported_key = localStorage.getItem("notes_key") ? await crypto.subtle.importKey(
'raw',
hexStringToArrayBuffer(localStorage.getItem("notes_key").substr(4)),
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
) : '';
function hexStringToArrayBuffer(hexString) {
const length = hexString.length / 2;
const array_buffer = new ArrayBuffer(length);
const uint8_array = new Uint8Array(array_buffer);
for (let i = 0; i < length; i++) {
const byte = parseInt(hexString.substr(i * 2, 2), 16);
uint8_array[i] = byte;
}
return array_buffer;
}
The second of three spells - decrypting 🧩
async function decryptWithKey(encryptedText, imported_key) {
const encrypted_data = new Uint8Array(atob(encryptedText).split('').map(char => char.charCodeAt(0)));
const iv = encrypted_data.slice(0, 12);
const ciphertext = encrypted_data.slice(12);
const decrypted_buffer = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
imported_key,
ciphertext
);
const decoder = new TextDecoder();
const decrypted_text = decoder.decode(decrypted_buffer);
return decrypted_text;
}
The third of three spells - encrypting 🪄
async function encryptWithKey(text, imported_key) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoder = new TextEncoder();
const plaintext_buffer = encoder.encode(text);
const ciphertext_buffer = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
imported_key,
plaintext_buffer
);
const encrypted_data = new Uint8Array([...iv, ...new Uint8Array(ciphertext_buffer)]);
const base64_encoded = btoa(String.fromCharCode(...encrypted_data));
return base64_encoded;
}
With these spells in our arsenal we can decrypt any of our notes and then save them again by re-encrypting them.
const markdown = await decryptWithKey(note, imported_key); const eNote = await encryptWithKey(markdown, imported_key);
With that out of the way, let's see what we can can do 🫠
I Can Never Make Up My Mind 🫥
I'll admit it. I change up how I save my data regularly. No structure is safe for more than a couple of months. Maybe I'm strange. I find it soooooo soooothing to rename and reorganize digital files.
Not being able to move notes around in notebooks started to drive me insane.
Nor did the documentation hand me a simple endpoint to get it done either. Oh well. I'm sure I'll think of something.
What about.... copy and delete? As in copy the note into a new notebook and then delete the original.
So with a little HTML, CSS, and JS I have lovely modal I can use to select a notebook and thus send my note along to it's new home... Oh yeah, I better put up a warning for other users.
[6]
The code isn't that complicated and there is no need to decrypt anything.
let fetching = await fetch(`https://micro.blog/notes/${id}`, { method: "GET", headers: { "Authorization": "Bearer " + accessTokenValue } } );
const eNote = await fetching.json();
const formBody = new URLSearchParams();
formBody.append("notebook_id", notebook);
formBody.append("text", eNote.content_text);
let posting = await fetch('https://micro.blog/notes', {
method: "POST",
body: formBody.toString(),
headers: {
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
"Authorization": "Bearer " + accessTokenValue
}
});
if(posting.ok) {
posting = await fetch(`https://micro.blog/notes/${id}`, {
method: "DELETE",
headers: {
"Authorization": "Bearer " + accessTokenValue
}
});
}
Sweet.
I also added in the ability to rename a notebook and delete a notebook. I won't go into details since those are just endpoint you call and they are in the documentation.
Danger! ☠️ Loura lost 10 HP!
So while writing the first draft of this blog post, I stepped away from my desk and didn't lock my computer. The cat took advantage of this and decided to step all over my keyboard. I lost the first draft 😢
Okay, that sucked. Maybe I should do something about that...
I know! auto save!
This was pretty easy. The editor I'm using is EasyMDE[7] and it includes it out of the box. Sweet! Just pass it the right configuration autosave:{enabled:true} and....
Uh oh.....
Did you spot it?
Maybe I should let people know I'm saving a copy of the decrypted note in localStorage.
And thus a toggle was born.
[8]
Side Quest - Storing other one-off project data.
One other fun thing I started doing, was using using private notes to hold other small amounts of data for my growing collection of personal PWA's. Remeber this project with the iframes[9]? I did end up finishing that (though not the blog posts), turned it into a PWA and hooked it up to a private note. I just serialize the data and store it.
It's my personal todo list calendar hybrid on my phone.
A Dragon's Hoard Must Be Shiny! 🪙
Okay, okay. I made a micro post on January 15th[10] when I discovered 7.css.
And I uh....
had a bit too much fun.
🤣😅🤣😅🤣
[11]
What I love about it? opening up all my notes in different windows! I can even drag the windows around and resize them.
Yep... 😅
The End
♥️ Loura
=^..^= =^..^= =^..^= =^..^= =^..^= =^..^= =^..^=
You can always strike up a conversation if you would like, You should try:
Yours truly, Loura 👩🚀