Ivory Siege Tower

mobile construct built of thoughts and parentheses

SiegeTower Blog Setup

project siegetower

Here, on this blueprints page, the configuration of the Siege Tower itself are declared.

In my org-roam wiki this is just an Org-mode document with code blocks, exported for the blog likewise the engine that powers the following declaration forms of the website's sections.

Therefore, it's a live document. Layout edits and new sections will be reflected here once, and then appear within the walls of the Tower.

§ SiegeTower appears

First a blog site instance must be instantiated. A sample definition may go as follows:

  (use-package org-roam-blog              ; load blogware engine
     :ensure nil
     :load-path "/path/to/local/org-roam-blog.el")             
  (org-roam-blog-load-dynamic-module)     ; load the rust dynamic module

  (setf siegetower                        ; bind instance to variable
      (org-roam-blog-site-create
       :staging-dir  "/path/to/staging/dir" ; where the resulting website will be generated
       :src-root-dir "/path/to/root/dir"    ; with the pre-generated root files, e.g. stylesheets, js, etc
       :src-template-dir "/path/to/template/dir")) ; where mustache base templates are visible

§ Templates

For clarity, generators of "standard" (= most used) custom templates are defined here.

The org-roam-blog sites are static sets of pages by default, meaning all template transforms, e.g. changing cover image on a "standard" template for different index sections, must be done upon registration of indexes.

The following function returns a mustache template (as a string) that is reused in several of the entries categories later, with only a small change (of a cover image).

  (defsubst siegetower/st-entry-template (cover-image)
    "Return a standard SiegeTower entry material template with selected COVER-IMAGE."
    (format
     "<div class=\"columns\">
   <div class=\"column is-three-quarters\">
     <figure class=\"image \"><img src=\"/image/%s\"></figure>
   </div> </div>
   <div class=\"columns\">
     <div class=\"column is-three-quarters\">
       {{> standard-title}}
       {{& main}}
     </div>
     <div class=\"column is-one-quarter\">
       {{> toc}}
       {{> backlinks}}
       {{> mt-timeline}}
     </div>
   </div>"
     cover-image))

This short template part itself includes some other templates located in :src-template-dir of the site instance: standard-title.mustache, backlincks.mustache. In the :src-template-dir there can also be found wrapper-top.mustache and wrapper-bottom.mustache.

At the moment, the default (and only) behavior of the site generator is to "sandwich" index :template and :entry-template between wrapper-top and wrapper-bottom templates and populate the resulting template page with index or entry context.

§ Index Registry

For declaring org-roam-blog index sections for a site instance there is org-roam-blog-register-index function and its shorter macro version org-roam-blog-reg.

§ Literate Style Org-Roam-Blog Engine

The first section to appear within the Tower - its own engine, programmed in a "literate" style inside emacs-org!

  (org-roam-blog-reg
   siegetower
   :root-spec '(all)
   :entry-dir nil
   :filter-fn
   (lambda (node) (let ((tags (org-roam-node-tags node)))
                    (and (member "orb" tags)
                         (member "source" tags)
                         (not (member "noexport" tags)))))
   :title "Orb's Engine"
   :entry-template (siegetower/st-entry-template "orbs_engine_cover.png"))

The registration form says: "From all the org-roam nodes take those that have orb and source tags attached to them but not noexport thus form an Orb's Engine section index". Only :entry-template and not just (index) :template defined means only generate individual entry pages. :entry-dir set to nil means the entry files will go directly under the index route, which is by default a slugified :title (in this case it will be /orbs-engine/ ).

§ the Siege Tower Blog Setup

Likewise, this very section of the Siege Tower website configuration has its own declaration (resulting, at the moment, in a single page placed under specific route).

  (org-roam-blog-reg
   siegetower
   :root-spec '(all)
   :entry-dir nil
   :filter-fn
   (lambda (node) (member
                   (org-roam-node-id node)
                   '("siegetower-blog-setup"
                     "siegetower-styles")))
   :title "SiegeTower Blueprints"
   :entry-template (siegetower/st-entry-template "torre_sketch.png"))

§ Emacs Literate Config

Foundation of my Digital Garden of Words and Instructions.

Includes literate source blocks that compose my init.el configuration, as well as the useful definitions library.

  (org-roam-blog-reg
   siegetower
   :root-spec '(all)
   :entry-dir nil
   :filter-fn
   (lambda (node) (member
                   (org-roam-node-id node)
                   '("emacs-literate-config"
                     "setup-my-elisp")))
   :title "Emacs Literate Config"
   :entry-template (siegetower/st-entry-template "emacs_node_cover.png"))

§ StumpWM Literate Config

Part of public configs of my work environment, that section also fits on a single page since the StumpWM config is quite short.

  (org-roam-blog-reg
   siegetower
   :root-spec '(all)
   :entry-dir nil
   :filter-fn
   (lambda (node) (string-equal
                   (org-roam-node-id node)
                   "project-stumpwm-literate-config"))
   :title "StumpWM Config"
   :entry-template (siegetower/st-entry-template "stump_desk.png"))

§ General Info Pages

Lonely static pages have st_keepers_notes tag attached. There are very few of them.

  (org-roam-blog-reg
   siegetower
   :root-spec '(all)
   :entry-dir nil
   :filter-fn
   (lambda (node) (let ((tags (org-roam-node-tags node)))
                    (and (member "st_keepers_notes" tags)
                         (not (member "noexport" tags)))))
   :title "Keeper's Notes"
   :entry-template (siegetower/st-entry-template "keepers_table.png"))

§ FTL:Nomad RPG Generators

An interactive leisure tool - first of a kind - on my homepage.

Speed up preparation to ttRPG sessions with a fast and furious SciFi System.

  (org-roam-blog-reg
   siegetower
   :root-spec '(all)
   :entry-dir nil
   :filter-fn
   (lambda (node) (let ((tags (org-roam-node-tags node)))
                    (and (member "st_ftl_nomad_generators" tags)
                         (not (member "noexport" tags)))))
   :title "Trident Nexus"
   :entry-template (siegetower/st-entry-template "trident_nexus.png"))

§ PaperGraph Section

The antique alien device that requires widescreen to work properly.

  (org-roam-blog-reg
   siegetower
   :root-spec '(all)
   :entry-dir nil
   :filter-fn
   (lambda (node) (let ((tags (org-roam-node-tags node)))
                    (and (member "st_papergraph" tags)
                         (not (member "noexport" tags)))))
   :title "Papergraph"
   :entry-template
   "<h1 class=\"title has-text-danger\"> Ether Gate: PaperGraph </h1>
    {{& main}}
    {{> backlinks}} ")

§ Blog posts and the Feed of the front page

The Infinite Staircase on the front is a mixture of standalone blog posts and also some other materials, sorted by date (newest first).

Later I added some other indexes: Parapet a.k.a Dev Pages for anything development-related, Lounge a.k.a Floating Gardens for leisure and hobby notes. Everything else either ends up in Posts (e.g. personal notes) or lives in different index.

One template to rule them all the post feeds:

  (defsubst siegetower/st-feed-template (cover-image)
    "Return a standard SiegeTower materials feed template with selected COVER-IMAGE."
    (format "<div class=\"columns\">
     <div class=\"column is-four-fifths\">
       <figure class=\"image \"><img src=\"/image/%s\"></figure>
     </div> </div>
     <div class=\"columns\">
       <div class=\"column is-three-quarters\">
         <h2 class=\"title has-text-danger\">{{title}}</h2>
         <ul class=\"has-text-info\">
           {{#entries}}
             <li>
               {{#show-date}}<h5 class=\"has-text-info\" style=\"display: inline;\">{{date}}</h5>{{/show-date}}
               <h3 style=\"display: inline;\"><a href=\"{{self-url}}\">{{title}}</a></h3>
             </li>
           {{/entries}}
         </ul>
         <p>
           {{#has-prev-page}}
              <a href=\"{{prev-page}}\">
              <button class=\"button is-small is-primary\">&lt previous</button>
              </a>
           {{/has-prev-page}}
           page <code>{{page}}</code> of <code>{{page-max}}</code>
           {{#has-next-page}}
              <a href=\"{{next-page}}\">
              <button class=\"button is-small is-primary\">next &gt</button>
              </a>
           {{/has-next-page}}
         </p>
       </div>
       <div class=\"column is-one-quarter\">
         {{> mt-timeline}}
       </div>
   </div>"
             cover-image))

Parapet and Lounge will be leading indexes for corresponding post materials (note alternative entry-fname-fn that will prepend date to the slug string of the entries):

  (org-roam-blog-reg
   siegetower
   :root-spec '(all)
   :group-by 30
   :entry-dir nil
   :filter-fn
   (lambda (node) (let ((tags (org-roam-node-tags node)))
                    (and (member "st_dev" tags)
                         (not (member "noexport" tags)))))
   :title "Parapet: Dev Pages"
   :slug  "dev"
   :entry-fname-fn #'org-roam-blog--entry-fname-with-date
   :template (siegetower/st-feed-template "parapet.png")
   :entry-template (siegetower/st-entry-template "parapet.png"))
  (org-roam-blog-reg
   siegetower
   :root-spec '(all)
   :group-by 30
   :entry-dir nil
   :filter-fn
   (lambda (node) (let ((tags (org-roam-node-tags node)))
                    (and (member "st_lounge" tags)
                         (not (member "noexport" tags)))))
   :title "Floating Gardens: Lounge"
   :slug  "lounge"
   :entry-fname-fn #'org-roam-blog--entry-fname-with-date
   :template (siegetower/st-feed-template "floating_gardens.png")
   :entry-template (siegetower/st-entry-template "floating_gardens.png"))

The post entries definition is similar to what already appeared above, only it does not define it's own :template, therefore there is no dedicated list of standalone posts:

  (org-roam-blog-reg                      ;
   siegetower
   :root-spec '(all)
   :group-by 30
   :entry-dir nil
   :filter-fn
   (lambda (node) (let ((tags (org-roam-node-tags node)))
                    (and (member "st_post" tags)
                         (not (member "noexport" tags)))))
   :title "Posts"
   :entry-fname-fn #'org-roam-blog--entry-fname-with-date
   :entry-template (siegetower/st-entry-template "infinite_staircase.png"))

However, the Staircase feed itself is trickier:

  (org-roam-blog-reg
   siegetower
   :leading nil
   :root-spec '(all)
   :group-by 30
   :entry-dir nil
   :filter-fn
   (lambda (node) (let ((tags (org-roam-node-tags node)))
                    (and (or
                          (member "st_dev" tags)
                          ;; (member "st_lounge" tags)
                          (member "st_post" tags)
                          (member "st_feed" tags))
                         (not (member "noexport" tags)))))
   :title "Infinite Staircase: Journal"
   :slug  "infinite-staircase"
   :template (siegetower/st-feed-template "infinite_staircase.png"))

So, this is an index built from materials of other sections - :leading set to nil will make the links to other roam nodes within the site point to corresponding (correct) section routes. That's why for the Staircase index I only define index :template and omit :entry-template it is only an aggregator section and does not export individual entry pages of its own. And in order to appear on the Staircase, a node must be tagged as st_lounge, st_dev st_post (for corresponding lead indexes) or st_feed (for other materials that I would like to drag onto the front page).

On a second thought, I've removed the Lounge from the Staircase.

§ Library Hall - BookTracker Section

This is a reading tracking section built inside my Org-Roam with help of org-books. At the moment, the source is just one file with short review reflections (sometimes one-liners).

I want to display that section as a set of books category indexes (roughly five), joined as one general index.

§ BookTracker Styles and Templates

Booktracker title colors:

  (defsubst siegetower/booktracker-color (index-slug)
    (pcase index-slug
      ("booktracker-fiction" "#7ec7ff")
      ("booktracker-cs"      "#9cffca")
      ("booktracker-science" "#ff6a6a")
      ("booktracker-social"  "#d57dff")
      ("booktracker-gaming"  "#fcff93")
      (default "#bdddda")))

Index template for booktracker sections:

  (defsubst siegetower/booktracker-index-template (cover-image section-title index-slug &optional cover-text)
    (format "<div class=\"columns\">
       <div class=\"column is-four-fifths\">
        <figure class=\"image \"><img src=\"/image/%s\"></figure>
        <h2 class=\"title\" style=\"color: %s;\">%s</h2>
        %s
       </div> </div>
       <div class=\"columns\">
         <div class=\"column is-three-quarters\">
          <ul class=\"has-text-info\">
          {{#entries}}
              <li>
              {{#show-date}}<h5 class=\"has-text-info\" style=\"display: inline;\">{{date}}</h5>{{/show-date}}
              <h3 style=\"display: inline; color: {{title-color}};\">
                  <a href=\"{{self-url}}\" style=\"color: inherit;\">{{title}}</a>
              </h3>
              <div style=\"display: inline;\"> by {{author}}</div>
              </li>
          {{/entries}}
          </ul>
          <p>
          {{#has-prev-page}}
              <a href=\"{{prev-page}}\">
              <button class=\"button is-small is-primary\">&lt previous</button>
              </a>
          {{/has-prev-page}}
          page <code>{{page}}</code> of <code>{{page-max}}</code>
          {{#has-next-page}}
              <a href=\"{{next-page}}\">
              <button class=\"button is-small is-primary\">next &gt</button>
              </a>
          {{/has-next-page}}
          </p>
         </div>
         <div class=\"column is-one-quarter\">
           {{> mt-timeline}}
         </div>
     </div>"
            cover-image
            (siegetower/booktracker-color index-slug)
            section-title
            (or cover-text "")))

Each booktracker entry knows its category color. When in the general library hall, they will be of nice different colors:

  (defun siegetower/booktracker-entry-context (node)
    (let ((context-ht (org-roam-blog--entry-context-default node)))
      (let* ((lead-index (org-roam-node--lead-index-for (org-roam-node-id node)))
             (lead-index-slug (org-roam-blog-index-slug lead-index))
             (title-color (siegetower/booktracker-color lead-index-slug))
             (author (cdr (assoc "AUTHOR" (org-roam-node-properties node))))
             (catalog-url (cdr (assoc "GOODREADS" (org-roam-node-properties node)))))
        (ht-merge context-ht
                  (ht ("title-color" title-color)
                      ("author" author)
                      ("catalog-url" catalog-url))))))

Booktracker entry template:

  (defsubst siegetower/booktracker-entry-template (cover-image)
    (format
     "<div class=\"columns\">
   <div class=\"column is-three-quarters\">
     <figure class=\"image \"><img src=\"/image/%s\"></figure>
   </div> </div>
   <div class=\"columns\">
     <div class=\"column is-three-quarters\">
       {{> booktracker-title}}
       {{& main}}
     </div>
     <div class=\"column is-one-quarter\">
       {{> backlinks}}
       {{> mt-timeline}}
     </div>
   </div>"
     cover-image))

§ BookTracker Categories

Books category registration macro shortcut:

  (defmacro siegetower/booktracker-cat-reg (spec-id index-title cover-image cover-title)
    `(org-roam-blog-reg
       siegetower
       :root-spec '(node ,spec-id)
       :group-by 30
       :filter-fn (lambda (node)
                    (let ((tags (org-roam-node-tags node)))
                      (not (member "noexport" tags))))
       :title ,index-title
       :entry-context-fn #'siegetower/booktracker-entry-context
       :template (siegetower/booktracker-index-template
                  ,cover-image
                  ,cover-title
                  (org-roam-blog-slugify ,index-title))
       :entry-template (siegetower/booktracker-entry-template ,cover-image)))

Register site index for each of the books category:

  (siegetower/booktracker-cat-reg
   "books-fiction" "BookTracker Fiction" "libhall_fiction.png" "Library Hall: Fiction")

  (siegetower/booktracker-cat-reg
   "books-computer" "BookTracker CS" "libhall_compsci.png" "Library Hall: Computer Science & IT")

  (siegetower/booktracker-cat-reg
   "books-natural" "BookTracker Science" "libhall_natural.png" "Library Hall: (Popular) Science")

  (siegetower/booktracker-cat-reg
   "books-social" "BookTracker Social" "libhall_social.png" "Library Hall: Psy, Soc and Economics")

  (siegetower/booktracker-cat-reg
   "books-gaming" "BookTracker Gaming" "libhall_gaming.png" "Library Hall: Narrative Gaming")

The topmost (and currently only visible in the nav menu) Library Hall book tracker index:

  (let ((index-title "BookTracker")
        (cover-text (format "<p>
  In this dusty hall I track my readings
  with <code>org-books</code> extension for <code>emacs</code>,
  leaving short comments upon completion of each tome.
  </p>
  <p>
  Almost all the books I do classify in five categories:
  <span style=\"color: %s\">fiction</span>, (popular) science of 
  <span style=\"color: %s\">natural phenomena</span>,
  <span style=\"color: %s\">sociology and economics</span>,
  <span style=\"color: %s\">IT & computer science</span>,
  and <span style=\"color: %s\">tabletop rpg-related</span> materials.
  Each category assignment may be quite arbitrary though.
  </p><hr/>"
                            (siegetower/booktracker-color "booktracker-fiction")
                            (siegetower/booktracker-color "booktracker-science")
                            (siegetower/booktracker-color "booktracker-social")
                            (siegetower/booktracker-color "booktracker-cs")
                            (siegetower/booktracker-color "booktracker-gaming"))))
    (org-roam-blog-reg
     siegetower
     :root-spec '(all)
     :group-by 30
     :filter-fn (lambda (node)
                  (let ((id (org-roam-node-id node))
                        (tags (org-roam-node-tags node)))
                    (and (member "st_library_hall" tags)
                         (not (member id '("books-revised"
                                           "books-fiction"
                                           "books-computer"
                                           "books-social"
                                           "books-natural"
                                           "books-gaming")))
                         (not (member "noexport" tags)))))
     :title index-title
     :entry-context-fn #'siegetower/booktracker-entry-context
     :template (siegetower/booktracker-index-template
                "libhall.png"
                "Library Hall: BookTracker"
                (org-roam-blog-slugify index-title)
                cover-text)
     :entry-template (siegetower/booktracker-entry-template "libhall.png")))

§ Siegetower Org-Capture Template

  (defun my-init/siegetower-post-file ()
    "Helper file finding Org-capture shortcut for Siegetower posts."
    (expand-file-name
       (format
        "%s/projects/siegetower/infinite_staircase/%s.org"
        my-init/org-roam-dir
        (read-minibuffer "post filename: "))))

  (add-to-list
   'org-capture-templates
   '("s" "Siegetower Staircase" plain
         (function (lambda ()
                     (find-file (my-init/siegetower-post-file))
                     (goto-char 0)))
         ":PROPERTIES:
  :ID:            %(org-id-uuid)
  :ADDED:         [%<%Y-%m-%d>]
  :TOC_LEVEL:     1
  :END:
  ,#+title: %^{Title}
  ,#+filetags: %^{Tags}

  %?")
  t)  ;; append