Veit's Blog

How I write and publish blog posts from Glamorous Toolkit II

11/9/2023

In a previous blog post, we looked at how to publish one of my blog posts from Glamorous Toolkit. This blog post will essentially continue where we left off and explore how to publish all of my blog posts that way.

To make that happen, we will implement a button on the top right of the UI that essentially does automatically what we did manually in the last blog post. The code for this will end up in the repository for this blog post, in a package called VhBlog.

When we look at the buttons that are already on the toolbar of the page (you can inspect their definition by pressing alt while clicking on them), you will see that they are methods on LePage LeContent subclass: #LePage instanceVariableNames: 'type latestEditTime uid editHistory' classVariableNames: '' package: 'Lepiter-Core-Model' that are annotated with a lePageAction pragma. For instance, check out the definition of the removal action LePage>>#gtRemoveActionFor: gtRemoveActionFor: anAction <lePageAction> ^ anAction button tooltip: 'Remove Page'; icon: BrGlamorousVectorIcons remove; action: [:aButton | (LePageRemoveDropdownContentStencil new page: self; anchor: aButton)] (you can click on any of the blue-ish code things to expand them).

This means we can create such methods ourselves and they will be added to the toolbar. Let’s try it now, and create a method named LePage>>#gtPublishBlogActionFor: gtPublishBlogActionFor: anAction <lePageAction> ^ anAction dropdown tooltip: 'Publish Blog Post'; priority: 1; icon: BrGlamorousVectorIcons link; content: [ :aButton | aButton phlow spawnObject: (VhBlogExporter new export: self) ] .

We haven’t looked at VhBlogExporter Object subclass: #VhBlogExporter instanceVariableNames: 'sequencer linksBuilder layoutFile blogDirectory' classVariableNames: '' package: 'VhBlog-Exporter' yet, but if you’ve read the last blog post, you already know what it does. We could pretty much copy the code verbatim into LePage>>#gtPublishBlogActionFor: gtPublishBlogActionFor: anAction <lePageAction> ^ anAction dropdown tooltip: 'Publish Blog Post'; priority: 1; icon: BrGlamorousVectorIcons link; content: [ :aButton | aButton phlow spawnObject: (VhBlogExporter new export: self) ] , but we probably want to factor out some of the logic into the initialization method. Let’s look at VhBlogExporter>>#initialize initialize sequencer := LeExportUUIDSequencer new. linksBuilder := LeExportPageLinksBuilder new html; sequencer: sequencer .

Nothing much there, but at least we removed some boilerplate from the actual workhorse method. We referred to that one above already, it is VhBlogExporter>>#export: export: aPage | pageLinks ourPageLink aContext anExporter htmlString | pageLinks := linksBuilder database: aPage database; build; links. ourPageLink := pageLinks linkForPage: aPage ifFound: #yourself ifNone: [ self error: 'This should not happen' ]. aContext := LeHtmlContext new page: aPage; pageLinks: pageLinks; date: (Date today printFormat: #(1 2 3 $/ 1 1)); sequencer: sequencer. anExporter := LeHtmlPageExporter new context: aContext; piece: (LeHtmlGtBookPiece fromFile: self layoutFile). htmlString := anExporter contents. aContext assembler assemble. LeExportResourcesDirectorySaver new resources: aContext resources; rootDirectory: self blogDirectory; save. self blogDirectory / ((aPage title asString copyReplaceAll: ' ' with: '_') , '.html') writeStreamDo: [ :aStream | aStream nextPutAll: htmlString ]. ^ anExporter .

It’s a pretty long method, but it’s all familar code. Except for the lazy accessors VhBlogExporter>>#blogDirectory blogDirectory ^ blogDirectory ifNil: [ blogDirectory := self defaultBlogDirectory ] and VhBlogExporter>>#layoutFile layoutFile ^ layoutFile ifNil: [ layoutFile := self defaultLayoutFile ] that I added. They are simple accessors that initialize slots with defaults if they aren’t set. Take, for instance, VhBlogExporter>>#layoutFile layoutFile ^ layoutFile ifNil: [ layoutFile := self defaultLayoutFile ] .

Now that all of that infrastructure is in place, we should be able to try out the button by clicking on it. And, if you are reading this blog post on my personal blog, it worked!

It was a fair bit of work, and all for a simple button, but at least we can end this blog post with a glamor shot.

Fig. 1: A rendered blog post.

Fin

This blog post was quite a bit shorter than the last, no least because moving code from Lepiter into methods really is a triviality. Nonetheless, it has to be done, and I thought I might as well show that part. Now, I suppose, you know how the sausage is made. I hope you nonetheless still enjoy it, and it hasn’t spoiled your appetite for reading more. Perhaps, with this new system, I will be tempted to finally write more again.

sts?