take me home

Coding Adventure: Micro.blog Private Notes + 7.css Fun

take me home

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.

[1]
[2]
[3]

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.

[4]

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!

[5]

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.

[6]

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]

[7]
[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.

[9]

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... 😅

[10]
[11]

The End

♥️ Loura

=^..^=   =^..^=   =^..^=    =^..^=    =^..^=    =^..^=    =^..^=

You can always strike up a conversation if you would like, You should try:

emailing me
or replying to this post on my website

Yours truly, Loura 👩‍🚀

take me home