feat: emit capsule entries as lists, with no separator after the date

This commit is contained in:
randogoth 2026-10-11 16:10:07 +03:00
parent e025f32f61
commit a873ef23ea

View file

@ -32,23 +32,18 @@ setting, and every setting has a default, so a site can switch it on with just
every site using this plugin has been every site using this plugin has been
checked for footnotes checked for footnotes
Why link-only paragraphs rather than lists: gemtext has one kind of link, a line Every run of links here is a **Markdown list**, which is what each format needs:
beginning `=>`, so a renderer has to lift links out of the prose. itsybitsy's Mews Profile reads a dated link as a feed entry only when it starts a list item
gemtext renderer emits a bare `=>` line for a paragraph that holds nothing but (mews.page/spec 6.1), and the `text` renderer used for Nex and Gopher keeps one
links, so a run of links comes out as a clean run of link lines. A Markdown list item per line instead of reflowing a paragraph into a run-on line. gemtext has
would instead print a bullet *and* a link line for every entry, listing one kind of link, a line beginning `=>`, and itsybitsy prints only those lines
everything twice. for a list item that holds nothing but links, so no entry is listed twice.
That last part is a floor on the daemon version: an itsybitsy before the fix in
its gemtext renderer prints a bullet *and* a link line for every entry here.
Gemini clients number link lines themselves, which means a menu of areas is Gemini clients number link lines themselves, which means a menu of areas is
dialled by number without this plugin writing a single bracket. dialled by number without this plugin writing a single bracket.
The links are separated by **hard breaks**, which is two trailing spaces. gemtext
lifts each link out either way, but the inline formats lay a paragraph out as
flowing text, so with soft breaks a six-item menu renders as one run-on line in a
browser and on a handset. A hard break becomes `<br/>` there and costs gemtext
nothing. The `text` renderer used for Nex and Gopher flattens both break kinds to
a space and still reflows, which is the one format this shape does not serve; a
site whose main face is Gopher should emit lists instead.
""" """
import os import os
@ -350,6 +345,11 @@ def link(label, href):
return "[%s](%s)" % (label.replace("]", ""), href) return "[%s](%s)" % (label.replace("]", ""), href)
def bullets(items):
"""A Markdown list, one item per line. See the module header."""
return "\n".join("- %s" % item for item in items)
def gate(block, formats): def gate(block, formats):
"""Wrap a block in an itsybitsy format gate. """Wrap a block in an itsybitsy format gate.
@ -490,7 +490,7 @@ class CapsuleGenerator(Generator):
index = ["# %s" % s.get("SITENAME", "index"), ""] index = ["# %s" % s.get("SITENAME", "index"), ""]
if sections: if sections:
index += ["## %s" % labels["sections"], ""] index += ["## %s" % labels["sections"], ""]
index.append(" \n".join( index.append(bullets(
link("%s · %d" % (category, len(items)), link("%s · %d" % (category, len(items)),
"/%s/%s" % (section_dir, category.slug)) "/%s/%s" % (section_dir, category.slug))
for category, items in sections for category, items in sections
@ -502,7 +502,7 @@ class CapsuleGenerator(Generator):
link(labels["archive"], "/archive"), ""] link(labels["archive"], "/archive"), ""]
if pages: if pages:
index += ["## %s" % labels["pages"], "", index += ["## %s" % labels["pages"], "",
" \n".join(link(p.title, "/%s" % p.slug) for p in pages), ""] bullets(link(p.title, "/%s" % p.slug) for p in pages), ""]
tagline = s.get("SMOLNET_TAGLINE") tagline = s.get("SMOLNET_TAGLINE")
if tagline: if tagline:
index += ["---", "", tagline, ""] index += ["---", "", tagline, ""]
@ -514,10 +514,7 @@ class CapsuleGenerator(Generator):
self._copy_assets(out) self._copy_assets(out)
def _links(self, docs): def _links(self, docs):
"""One link per line in a single paragraph. """A list of dated links, one entry per item.
Deliberately not a Markdown list: a list item renders as a bullet *and*
a link line, so every entry would appear twice.
Targets are root-relative. A section index sits one directory down, so Targets are root-relative. A section index sits one directory down, so
relative targets would need `../` and would break again at any further relative targets would need `../` and would break again at any further
@ -525,29 +522,27 @@ class CapsuleGenerator(Generator):
against the site root. against the site root.
The date is ISO 8601 and it leads the label, which is protocol surface The date is ISO 8601 and it leads the label, which is protocol surface
rather than a style choice. Gemini's subscription convention takes a rather than a style choice. Both subscription conventions this capsule
link line for a feed entry when the first ten characters of its label is read through take a link as a feed entry when its label begins with
are a `YYYY-MM-DD` date, reads the rest as the title, and ignores every a `YYYY-MM-DD` date and read the rest as the title: Gemini's over the
line where they are not, so this one format is what makes each index a `=>` lines, Mews Profile's over the list items (6.1). That one format is
feed: the front page, the archive and all six areas, subscribable in a what makes each index a feed: the front page, the archive and all six
client and submittable to an aggregator. A board's `09 Oct 26` parses areas, subscribable in a client and submittable to an aggregator. A
as nothing and registers zero entries. board's `09 Oct 26` parses as nothing and registers zero entries.
The separator is the one the convention's own examples use. Clients are Nothing separates the date from the title. Gemini's examples put a dash
asked to sanitise whatever sits between the date and the title, but not there and ask clients to strip whatever they find, but Mews reads the
all of them do, and a `·` that survives arrives as part of the title. label from the date to the end of the line, so a separator arrives as
part of the title -- as does a `·` in a Gemini client that skips the
sanitising step.
""" """
rows = [] rows = []
for doc in docs: for doc in docs:
label = doc.title label = doc.title
if getattr(doc, "date", None): if getattr(doc, "date", None):
label = "%s - %s" % (doc.date.strftime("%Y-%m-%d"), doc.title) label = "%s %s" % (doc.date.strftime("%Y-%m-%d"), doc.title)
rows.append(link(label, "/%s" % doc.slug)) rows.append(link(label, "/%s" % doc.slug))
# Hard breaks, which is two trailing spaces in CommonMark. gemtext lifts return bullets(rows)
# each link to its own => line either way, but the inline formats lay a
# paragraph out as flowing text, so without these the whole menu renders
# as one run-on line in a browser and on a handset.
return " \n".join(rows)
def _read(self, doc): def _read(self, doc):
with open(doc.source_path, encoding="utf-8") as fh: with open(doc.source_path, encoding="utf-8") as fh: