Page 1 of 4
#
 Now that I've got everything working properly (and shouldn't be hitting any rate limits) I wanted to detail how the Bluesky integration is all set up.

As I mentioned before, I'm using Clark Rasmussen's simple BlueskyAPI library but have now forked it – more on that later.

Posting

When posting to the blog (except for certain conditions) I will send it over to Bluesky – I'll strip tags and decode any HTML entities then check the post length. For anything over 275 characters (Bluesky has a limit of 300) the post gets truncated at the nearest full-stop and a link card generated:

$args = [
  'collection' => 'app.bsky.feed.post',
  'repo' => $bluesky->getAccountDid(),
  'record' => [
    'text' => $htmlcontent,
    'createdAt' => date('c'),
    '$type' => 'app.bsky.feed.post',
    'embed' => [
      '$type' => 'app.bsky.embed.external',
      'external' => [
        'uri' => $postlink,
        'title' => $title,
        'description' => 'Read the full post on the blog...',
      ],
    ],
  ],
];

If I decide to have a featured image (add the 'feat' class to it) this gets uploaded via the uploadBlob endpoint:

$body = file_get_contents($imageUrl);
$response = $bluesky->request('POST', 'com.atproto.repo.uploadBlob', [], $body, 'image/'.$imgExt);
$image = $response->blob;

$embed = [
  'embed' => [
    '$type' => 'app.bsky.embed.images',
    'images' => [
      [
        'alt' => $imageAlt,
        'image' => $image,
      ],
    ],
  ],
];

I can then add it as the thumbnail for the link card:

$args['record']['embed']['external']['thumb'] = $image;

Or, for a short post, have it as a normal image:

$args['record'] = array_merge($args['record'], $embed);

The post is submitted to the com.atproto.repo.createRecord endpoint with the relevant body ($args), I get the at:// address of the item on Bluesky and attach it to the blog post in the database.

Replies

When viewing the blog, if a post has an at:// address attached this will get passed to the app.bsky.feed.getPostThread endpoint with the required thread depth (currently 2). If the returned data includes replies I pull out the array and reverse it (Bluesky has them newest first) then recursively get the author, avatar and content of each to be displayed under the post comments.

Limits

I was having a problem hitting the rate limits on the com.atproto.server.createSession endpoint so needed to be able to reuse sessions. This is where forking the library comes in.

When creating a session I now save the API refresh token (refreshJwt) to a PHP session variable. Instead of going straight to 'createSession' I check if the session variable exists and pass it to com.atproto.server.refreshSession to get a new apiKey. I've read that atprotocol refresh tokens last for 2 months so I could technically save them to a more permanent location (the database) but this is easiest for now.

If no refresh token exists (or one has expired – unlikely) then a new session is created as normal. Much better.

#
 SPARKS is now available on GitHub.

A few tweaks and bug fixes have been made since the original post.

The repository is just the proof of concept and doesn't include any authentication or security (except for an example .htaccess file to stop unwanted access to the .txt files.)

Note I've opted not to add search to SPARKS as I feel it goes against the ethos of spark files. The idea is that you periodically read through your files to get inspiration from serendipitous discovery and those happy accidents – search would take you away from this.

#
 You may recall me mentioning an app called ruff which acts like a spark file but with the ability to make more than one.

I said at the time: 'Maybe it will inspire me to create a "spark file" type feature on the site.'

I think you know where this is going...

That's right, I created a 'spark file' editor (allowing for multiple files) that runs in the browser and works equally well on mobile.

I've called it SPARKS.

Here's a quick video.

The interface allows you to edit the existing 'spark', delete it, create a new one, or load old ones from 'the vault'.

When editing a spark it is automatically saved each time you hit enter, though you can use the manual save button at the bottom. A little, unobtrusive, toast notification appears at the top on each save, just to let you know it's working.

If you choose to create a new spark the existing one will be saved and appear in the vault (unless it is empty). The vault displays sparks in order of creation; when you restore a spark from the vault it gets resaved with the current date/time so will appear first. There is always an empty card at the end of the vault with a 'new' button.

The latest spark is always the one displayed when you first go to the site.

The 'new' button will disappear if the current spark is empty.

If used on mobile, the vault and new buttons will not be visible. Instead, you swipe left to create a new spark and swipe right to view the vault.

The techie bit

Sparks uses jQuery and HTMX so that everything happens within the context of a single page (making AJAX calls out to the bits that do the work.) Including these two libraries the whole thing is made up of only 6 files – 7 if you include CSS – but that might change.

Try it out

There is a public version of Sparks that anyone can try. I reserve the right to clear out any and all 'sparks' without notice. There is no login system on the public version so play nice or I may be forced to change that. Obviously my personal implementation uses the site wide security.

Sparks originally linked to the master CSS for the blog as a shortcut to making things look consistent but I've now removed that dependency and the public version is fully standalone.

Once I'm happy there are no bugs and everything is tidied up I'll probably put it on GitHub.

Let me know what you think or if you find any issues.

Warning I may have temporarily broken the CSS on mobile.

I think it's fixed.

#
 What's better than a custom page editor?

A custom page editor with DIY JavaScript Markdown preview, of course:

#
 I think the file editor is done.

I've sorted a couple of issues and added some new features:

  • proper nesting & indenting of subfolders
  • folders listed before files rather than purely alphabetical
  • detection of file type based on extension which loads the corresponding highlighting template
  • open a file for editing immediately after creating it rather than have to select it manually in a separate step
  • fixed an issue with the generated textarea overlay being slightly the wrong size (32px too narrow and 16px too long 🤷‍♂️)

The only thing left to add (should I be so inclined) is file uploads.

It's one of those projects that started life as a silly idea 1 but has turned into something with genuine utility.


  1. inspired by a site building framework, I can't remember which, that mentioned full "in browser editing"