Blog

Post Hubs

Changelog

  • 2024-05-31 - Created this note
  • 2024-11-04 - Added “Maintenance Romanticism” Hub

Note

A hub is a new idea of mine to resurface and connect my blog posts that more or less have something to do with a topic. I’ll collect posts of mine - maybe posts of others? - that fit the topic of a hub.

In comparison to a category, a hub allows for giving some commentary and is supposed to grow and change over time.

  • Similar concepts in the pkm space: MOC - Maps of Content

Hubs

A New Cross-Posting Workflow, Part 1: Requirements And Thoughts On Implementation

(2024-05-20 - EDIT: I have decided, that I do not have enough time to devote to this project at this time, however I would really like it and it was good to think this through for when I am able to pick it up)

You know what I don’t like about my blog? The way it crossposts to other services. Micro.blog’s understanding of cross-posting is as follows:

Micro.blog cross-posting copies your posts from this feed to external services automatically when you post to your blog.

Which is not what I want. I would like it to work more in this way:

  • Blog posts (with our without title) should be shared automatically on my Mastodon account with a proper link, maybe an applicable hashtag. But it should be clear that I’m sharing a blog post, which is not the same as writing a toot and that difference shouldn’t therefore be invisible by just tooting the same content 1:1 as I posted on my blog
  • An exception are the DailyDogos and other posts like it (not that there are any at the moment…). These should be shared with the image attached. Maybe there is not even a need for the link, but it would be fine if it would be there
  • When I’m writing a toot, I’d like my blog to archive a copy of that toot. The archived version should include images, links and whathaveyou.

So basically there are two workflows with a couple of conditionals I would like to implement:

  • Cross-Post all blog posts from my micro.blog to my mastodon (excluding posts that are archival copies of mastodon posts; denoted by being part of the category “archivalCopyOfToot”)
    • if the post has a title, use the title to create a link to the post and add any applicable hashtags by converting the assigned categories of said blog post to the toot
    • if the post has no title
      • if the post has category of “tootAsIs”
        • add any images to the toot and post the contents of the blog post as is to mastodon (maybe add a link to the original on my blog)
      • if the post has no category of “tootAsIs”
        • add a short (80 Chars and up to the next full word maybe?) excerpt and otherwise behave like a titled post

I happen to have an Echofeed account, which is part of the solution. I can point it at an RSS/JSON/Atom-Feed and it will post in a format I can specify to any of its supported services, which includes Mastodon and Micro.blog. It doesn’t have any conditional logic, though, so I will have to provide it with feeds that only include posts that I want it to cross post.

On the micro.blog side, this is maybe not trivial, but at least is possible, since micro.blog uses Hugo and you can - if you know how - customize it quite a bit. I’ll need three feeds:

  • A feed with only title posts, excluding any posts with the category “archivalCopyOfToot”
  • A feed with only non-title posts, including only those of “tootAsIs” (and excluding any from the category “archivalCopyOfToot”)
  • A feed with only non-title, excluding any of “tootAsIs” AND “archivalCopyOfToot”

I would then set up three echos that cross post to mastodon in the right way.

Mastodon on the other hand has a feed of my posts too, but it’s much more limited and creating new custom feeds that filter posts based on e.g. a hashtag are not possible, I believe. This means that we can’t filter for cross-posted blog posts and will necessarily import those items to our blog, too. I would really like to avoid having to run my own script - locally or on a server - talking to their api or whatever, so I think I’ll will have to live with the fact these duplicated posts will be crossposted back to my blog…

What I can do is filter these posts out on the micro.blog side. Meaning posts that include a certain Hashtag like “crossPostByEchoFeed” (this could even be added inside Echofeed for the workflow that posts from Mastodon to MB and could be invisible to Mastodon followers) need to be excluded from the homepage, archive and any feeds of my blog (hashtags can be converted to categories using filters on micro.blog, so if I figure out how to filter by one or more categories in Hugo’s template syntax, I should be fine implementing this). In that way they will be part of the blog, but they will not pollute the reading experience.

So yes. I have to set up some feeds and some logic to make undesired toots invisible but then I should be able to get what I want without the need to setup any scripts run by me.

Okay fixed the theme next/previous links: {{ $cleanContentNext := .NextPage.Content | plainify }} {{ if gt (len $cleanContentNext) 20 }} {{ printf "%.20s" $cleanContentNext }} {{ else }} {{ $cleanContentNext }} {{ end }} {{ end }} And the other problem seems to have been a category filter not having run through. There is no pagination, it just shows all the posts on one page. Which is fine for now.

My theme switch has lead to a couple of problems that I have to solve it seems in time I don’t have. 1. posts without a title lead to links without one in the single post view An annotated screenshot ilustrating the invisible links problem 2. for some reason the categories view is not paged and you can’t view posts in a category past the 11th post. Why? An annotated screenshot ilustrating the missing pagination in categories problem Great. Great stuff.

Took the plunge today and changed my theme for the first time since establishing my blog on micro.blog. I’m very apprehensive changing themes and other things that I consider default behavior. Because this either means maintenance for me or maintenance for somebody else. I’m not a very visual person especially when I have to create things. I want to write and I want it to look okay, but I’m not sweating it if the blog looks antiquated or whatever. However, a blog needs some form of a (design) theme and so I have generally opted for whatever the default is. The default is going to be maintained no matter what so it’s a pretty safe bet. One thing that irks me a lot about micro.blog’s default theme: How invisible the categories are! They are only visible on the archive page, not in the list of posts and not on the single post page either. I find this very impractical, because you can’t browse other posts in the same category. Another thing is that the default theme doesn’t do proper pagination. The homepage just includes 20 or so posts and that’s it. You have to go to the archive page to see more posts, but there they are only visible in a truncated fashion. Long story short: I changed the theme! This one is called Minos and shows the categories above every post and has pagination. For posterity’s sake, here are some more or less random screenshots from the blog before changing themes: A screenshot showing my blog using the old default theme A screenshot showing my blog using the old default theme A screenshot showing my blog using the old default theme A screenshot showing my blog using the old default theme A screenshot showing my blog using the old default theme A screenshot showing my blog using the old default theme A screenshot showing my blog using the old default theme

The (Hu)go template syntax is bad.

There is no two ways about it. It’s just bad and often counterintuitive. It’s hard to read, hard to write, hard to debug, hard to fill in blanks. Easy things should be easy and hard things should be possible. I feel like everything is just hard with it - not impossible per se, but often harder then it should be.

I mean just look at this one line:

{{ $paginator := .Paginate (where .Site.Pages.ByDate.Reverse "Type" "post") (index .Site.Params "archive-paginate" | default 25) }}

This does/means the following:

  • $paginator - that’s a custom variable, it needs to be prefixed with a $
  • := - you need to use this operator to declare a variable and assign a value. But later it’s fine to just use = to reassign it.
  • . - “the dot”. It holds the context, which is basically a kind of scope. Why the scope is not implied? I don’t know. I guess if you’d have a this keyword, you’d end up writing this.whatever… and you would still need to differentiate globals and in-scope vars, so I guess that’s better?
  • .Paginate - Inside “the dot” there exists a Paginate function which has to be upper case because that is the way to make a function visible outside of its “package” in go. If you look at the list of available functions in hugo, you will know that a function was exported, but not why. Also a lot of functions were not exported, but you also do not know why. I assume that the lower case functions are all part of hugo’s templating standard lib. But it’s not explained what is going on in the docs explicitly.
  • - A space (yup). Functions and parameters are separated by spaces, so this space indicates the start of the Paginate function call (And not the parentheses in front of the where, as one might think when coming from other languages).
  • ( - The start of the parentheses denotes a nested expression.
  • where - that’s the where function which takes an array and compares a value at a given key using an operator (equals is implied and can be omitted like has been done here) to a value and only keeps the elements that pass the test.
  • .Site.Pages.ByDate.Reverse - The .Site.Pages part is an array of all pages of the blog. The ByDate.Reverse part is used (and available only) on list pages - another detail you’ll have to know - that is pages that have other pages under them in the file hierarchy. AND that the homepage is a special kind of list page. This is a snippet from the home page, so you can use it here to change the order of the retrieved array. Why you can’t retrieve pages in similar manner on non list pages is unclear to me.
  • "Type" - This is the key parameter of the where function. The key is “Type” here. Why is Type capitalized? Maybe it has to because of exports? Maybe it’s some other reason that I don’t know. In any case it is part of the keys of the element that is kept (or discarded). For the pages array an element includes things like a Permalink, a published Date and a also a Type. How do you know what an element includes? Well, it’s hard to say, because the proposed solution to use something like &#123;&#123; printf "%#v" . }} within a range function call that references the pages from the Paginator above only prints garbage. An actual solution is to use <pre>&#123;&#123; . | jsonify (dict "indent" " ") }}</pre> which gives a pretty printed JSON representation of all the available properties within a given context. But you’ll not find this solution in the docs, you have to get lucky and find it in the forum or Stack Overflow.
  • "post" - this is the match parameter of the where function. In other words the value found for the key “Type” must match this value in order to be included in the filtered array.
  • ) ( - we finish one nested expression - which is parameter one of our .Paginate call (the array to paginate) and start another nested expression - parameter two (the page size) of .Paginate
  • index .Site.Params "archive-paginate" - this returns the value at index or key n of a given array. So in this case the value at .SiteParams.archive-paginate
  • | default 25) if index does not return a value the default function can take over and return a default value. I don’t know why this has to be a function. I also find the whole part index .Site.Params "archive-paginate" | default 25 difficult to parse: Is the pipe still part of the index function call? You’ll have to know what pipes are.

Finally, we have parsed the whole thing:

instantiate customVar = 
functionCallThatReturnsAPaginator(
    functionCallThatReturnsAFilteredArray,
    (
        functionCallThatReturnsANumber ||
        functionCallThatReturnsaAdefaultValue
    )
)

And .Paginator itself is just an object that makes it easier to refer to and implement a paged navigation for an array of given elements (posts).

Apart from the unusual syntax I find (Hu)go templates hard to parse, even if I grok them somewhat. (Hu)go’s way to write function expressions, namely using spaces instead of parentheses and commas make the whole line harder to read than it needs to be. Compare:

{{ $paginator := .Paginate (where .Site.Pages.ByDate.Reverse "Type" "post") (index .Site.Params "archive-paginate" | default 25) }}

vs.

{{$paginator := .Paginate(where(.Site.Pages.ByDate.Reverse,"Type","post"),(index(.Site.Params,"archive-paginate") | default(25)))}}

Granted, this is still hard to read, because a lot is happening in this one line of code, but still: I’d argue it’s much easier to parse, because opening and closing parentheses and commas carry much more information than a simple space could. Spaces are also commonly used to align or balance things as has happened around the pipe char. Does this carry semantic meaning? Nope, not in this case!

A visualization of what different symbols of the templating syntax mean in our example. It turns out that the space char carries three differnt meanings: an aesthtic space, a start of a parameter list, a delimiter between parameters.

The space is doing an enormous amount of overtime here and I have yet to see a good justification of muddling the waters like this. The only reason I could see is that you have to balance parentheses, meaning you’re ending up with the line ending in ))). The best part is that you still need parens in any case, you just have to put them around the whole function expression! The real template version safes you two parentheses for the price of a parsing headache. I feel like that’s not worth it.

Let’s move on: It should be super easy to limit the list of pages to only include a certain category of posts, right? Would this have been what you’d come up with on the first try?

{{ $allPosts := where .Site.Pages.ByDate.Reverse "Type" "post" }}
{{ $allDailyDogos := where .Site.Pages "Params.categories" "intersect" (slice "DailyDogo") }}
{{ $onlyDogos := intersect $allPosts $allDailyDogos }}
{{ $paginator := .Paginate ($onlyDogos) (index .Site.Params "archive-paginate" | default 25) }}

So far so good. How about the inverse? You’ll find that there is no way to tell the line $allDailyDogos to simply do the inverse. There is no "not intersect" or whatever. You have to use another function called symdiff:

{{ $allPosts := where .Site.Pages.ByDate.Reverse "Type" "post" }}
{{ $allDailyDogos := where .Site.Pages "Params.categories" "intersect" (slice "DailyDogo") }}
{{ $noDogos := symdiff $allDailyDogos $allPosts }}
{{ $paginator := .Paginate ($noDogos) (index .Site.Params "archive-paginate" | default 25) }}

Symdiff is short for symmetric difference and means here that we want all elements from the $allPosts array that are not part of the $allDailyDogos array. Meaning we are keeping only those posts, which is like filtering for the inverse of the $onlyDogos array from before.

We could have mashed this all into one line to make it totally unreadable, but I think this is instructive and it would have not been anymore readable in other template languages (if they even would’ve been able to deal with this as a one liner). Still:

  • Why do I need to make a slice/array out of “DailyDogo”?
  • Why is there no NOT operator? - it would’ve been nice in two instances: 1. inside the where function to save an extra call to symdiff or 2. as a better (I’d say) alternative to using symdiff, because people think more along the lines of “all but not these kinds of things” instead of “symmetrical difference of these two sets”.
  • Why is it so hard to chain conditions to the where function to the point where you’d rather create two arrays instead of filtering the array down in one step?

Ugh. There is so much in just this one line - and the subsequent slight changes I have done - that I find weird and unergonomic. I hope I could also show that it’s not complete and utter failure to understand what’s going on, either. Sometimes things are just badly designed for the most obvious use cases in order to accommodate fancier goals. I’d rahter have a templating syntax/language that makes things easier. If that makes it more boring: Good. I’m here to improve my blog first and foremost.

Yesterday I tried to write three different blog posts about three different half baked ideas and published none. I aborted them one after another after being dissatisfied with their individual messages. Too much hot take, not enough general validity.

I swear: Someday I will write a blog post again.

Even though I want to use the services that Omg.lol provides, I struggle to do so.

I set up a profile page now, but I feel like this should be a page reachable under my domain (I can probably set this up). I love the idea of a status.log, but I have a micro.blog already and all the content should come from that as the canonical place and be fed into other means to consume my content. I don’t want to produce parallel content or keep things up to date manually.

I’ll keep my omg.log address, because my mastodon account is registered on their instance, but I’m not sure what I’ll use it for otherwise at the moment.

UPDATE: My profile page is now reachable under my domain.

As somebody who frequently edits older posts to maybe add links and/or fix typos, etc., I just have to say how pleasant it is to do this through @danielpunkass' MarsEdit.

The more I read about how mastodon works, the happier I am that I ended up being so curious about the publishing options in iA Writer that I had to check out micro.blog about 1.5 years ago.

Don't get me wrong: It's great that people are checking out mastodon. It lets me follow them on micro.blog. But moderation and all the other social aspects of running a social network (even if it's "just" a timeline of blogs) are what's hard.

Many people that are leaving Twitter right now (or at least create a mastodon account on the side, for the time after Twitter finally implodes) end up on mastodon.social, the biggest mastodon instance there is. When I scrolled through their explore timeline I was surprised to find a post by fellow corgi lover and micro.blogger Hollie Butler (@hollie) that urged people to find a better instance, because mastodon.social has had a bad moderation for a long time.

In the replies to this post a user asked: Can I move my content over to a new (e.g. better moderated) instance? The answer is no. You can move the people you follow, but not your content, it seems.

With micro.blog this combination of problems doesn't really exist. On the one hand they have and enforce community guidelines that protect the community against hate speech, harassment and so on. Community manager Jean MacDonald (@jean) does an excellent job as far as I can tell. So there is no reason to move. Micro.blog is just a blog hoster with a social timeline component.

On the other hand exporting your data is easy enough, too. Even if there would be a reason at some point to move: I do own all my content and it's not useless content, because a micro.blog is just a blog and so the content I'd export are just blog posts.

The point is: At the moment micro.blog seems like the ideal place that is not twitter, but has moderation figured out (and still lets you connect to mastodon users). I feel like I'm in good hands with them. If you're willing to fiddle, you can connect your own blog for free, or you could pay them 5$ a month and have everything set up for you. Connect a domain and you really truly own your content, even in the case you feel the need to move away, since the permalinks point to your domain not micro.blog's (I'm not claiming the moving blog hosters is easy, but it's possible…).

In an effort to make my blog perform better - lots of images means slow load times - I have updated all my images to use this somewhat undocumented micro.blog feature that resizes images:

Micro.blog offers an endpoint to resize uploaded images. Once you upload the image, if you do [micro.blog/photos/](https://micro.blog/photos/#)##x/, but replace ### with the pixel width you want, followed by the full URL to the image, you’ll show an image with that width. For example, if I wanted the image at [json.blog/foo.jpg](https://json.blog/foo.jpg) to be 480px wide, I could use <img src=“https://micro.blog/photos/480x/[json.blog/foo.jpg](https://json.blog/foo.jpg)”>.

@jsonbecker in the Micro.Blog Forums

This first page load still takes a long while, since my home page weighs in at about 25 mb of mostly image data. But it's a start.

Taking a break from microcasting

I recently published a post about my microcasts and I‘m now writing this to tell you I’m going to microcast less. Less frequent and less scheduled. This is even weirder because I just created an intricate shortcut that automatically publishes a post with the right episode title and number without having to look up any of those details, which streamlined the entire process a lot.

But I have noticed that I‘m not as much in the mood at the moment to record my thoughts in this way. The early riser project, for example, was a great McGuffin to get me out of bed, but since I have changed my approach to waking up, it has become less important to me. I don‘t want to record these little episodes for the ramblings alone.

Being a big fan of podcasts like Back To Work and Cortex, I thought I could do a microcast about my work and how I approach it, but I have noticed that I would like to write about these things, not only talk about them extemporaenously. LeadDev is also the microcast for which the ratio of recorded/published episodes is the worst: I take this as an indicator of my wanting to express myself more carefully than I can while walking the dog and having to observe the environment.

À propos the dog: I created my first microcast, the PuppyCast, because I wanted to have a record of the challenges and joys of raising a puppy. I wanted to look (DailyDoogo, which I will not abandon, btw.) and listen back to this important time in my life. But I think that this project has run its course: I don‘t feel the need to publish this every week anymore. It‘s not like nothing changes or that there is nothing to report dog-wise, but it all comes down to the need to simplify and de-schedule my life. And I have episodes for the first six months, which seems like a good place to stop.

Seasons change. And this is how I feel about my microcasting: Right now I‘d much rather write than talk. Looking at my blog I mostly see noise that was produced hastily to satisfy a self-imposed schedule. I will let this stuff go, for now.

Short Posts With Titles

One kind of blog post that I really like is a short post with a title. One or two paragraphs about a topic (almost any topic really) is a great length for me. A title makes sure that it’s going to be a somewhat framed thought.

A short post with a title can contain a picture or two to illustrate what’s going on. It could have links or a video embed. I would really like to write more posts of this kind myself.

Adventures with Hugo: Configure Hugo's Native Syntax Highlighting On Micro.Blog

If you are one of the brave people that uses Hugo Version 0.91 and wants to take advantage of Hugo’s native syntax highlighting capabilities on a light theme (like the default theme in this example), you might be surprised:

As you can see… you can’t really see much.

Thankfully this can be changed, by changing the configuration for syntax highlighting. But in order to do that you have to know the following:

[@matti](https://micro.blog/matti) interesting … so much is still a blackbox for me. I just tinker until I get results. My two favorite finds are the config directory (which take precedent ahead of the json files) and the assets directory (with which you can build Javascript and Sass files from templates).

You have to know about the precedence because for some reason, changing the config.json in your theme won’t do the trick. This is how it works:

Get A Functioning Custom “New-Plugin-Style”-Default-Theme For Your Blog

EDIT 18.02.2022: The following is not up to date anymore. Plugin-Themes now behave much more convenient: Just install the theme, activate for your blog, create a custom theme and only add config/_default/markup.json template.

First of all make sure you are on the Hugo version 0.91 (dropdown in the Design screen, remember to save) and install the default theme as a plugin (Plug-ins screen). Then go to Design again and click on “Edit Custom Themes”, on the next screen click on the default theme and duplicate it with the button in the next screen. You should now be back in the custom themes overview list. Click Design in the sidebar once more and choose your custom theme, save. Click “Edit Custom Themes” again and then on your copy of the Default theme.[^1]

Change Configuration

You should now be inside your custom theme screen. Click on “New Template”. You will be presented wie the template editor and theme preview split view.

Name the template config/_default/markup.json and set its contents to:

{
    "highlight": {
      "anchorLineNos": false,
      "codeFences": true,
      "guessSyntax": false,
      "hl_Lines": "",
      "lineAnchors": "",
      "lineNoStart": 1,
      "lineNos": true,
      "lineNumbersInTable": true,
      "noClasses": true,
      "style": "monokailight",
      "tabWidth": 4
    }
}

This is basically the default config from the Hugo documentation, except that for style we use monokailight instead of monokay. Click “Update Template”, your site should be rebuilding as indicated by the spinner. When Hugo is done, syntax highlighting should now be working as intended and the code be legible.

Example:

{{ define "main" }}
<div class="home h-feed">
  <ul class="post-list">
  {{ $paginator := .Paginate (where (where .Site.RegularPages.ByDate.Reverse "Type" "post") "Params.title" "!=" nil   ) (index .Site.Params "archive-paginate" | default 25) }}
  {{ range $paginator.Pages  }}
      <li class="h-entry">
			<h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>

        <a href="{{ .Permalink }}" class="u-url"><span class="post-meta"><time class="dt-published" datetime="{{ .Date.Format "2006-01-02 15:04:05 -0700" }}">{{ .Date.Format "Jan 2, 2006" }}</time></span></a>

        <div class="e-content">
         	{{ .Content }}
        </div>
      </li>
    {{ end }}
  </ul>

  <p class="rss-subscribe">subscribe <a href="{{ "feed.xml" | absURL }}">via RSS</a></p>

</div>
{{ end }}

[^1]: I’m not sure if this is all necessary or even the right way to go about it. It seems pretty involved and I don’t even know how you would migrate your customizations in an easy way if you happen to have used the old style of choosing a theme… but this way works, that much I could prove.