{"id":22941707,"url":"https://github.com/plaidweb/publ-templates-beesbuzz.biz","last_synced_at":"2025-04-05T07:08:38.967Z","repository":{"id":129727470,"uuid":"136222274","full_name":"PlaidWeb/Publ-templates-beesbuzz.biz","owner":"PlaidWeb","description":"Sample templates for Publ","archived":false,"fork":false,"pushed_at":"2024-08-09T18:45:36.000Z","size":10193,"stargazers_count":1,"open_issues_count":4,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-08-09T20:14:53.883Z","etag":null,"topics":["atom-feed","comics","feed","feed-template","html-template","publ","stylesheets"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PlaidWeb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-05T19:02:28.000Z","updated_at":"2024-08-09T18:45:33.000Z","dependencies_parsed_at":"2023-04-04T19:24:36.984Z","dependency_job_id":"12a75f35-c513-45cb-adc0-722086e9d035","html_url":"https://github.com/PlaidWeb/Publ-templates-beesbuzz.biz","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlaidWeb%2FPubl-templates-beesbuzz.biz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlaidWeb%2FPubl-templates-beesbuzz.biz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlaidWeb%2FPubl-templates-beesbuzz.biz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PlaidWeb%2FPubl-templates-beesbuzz.biz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PlaidWeb","download_url":"https://codeload.github.com/PlaidWeb/Publ-templates-beesbuzz.biz/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247299833,"owners_count":20916190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["atom-feed","comics","feed","feed-template","html-template","publ","stylesheets"],"created_at":"2024-12-14T13:44:46.578Z","updated_at":"2025-04-05T07:08:38.947Z","avatar_url":"https://github.com/PlaidWeb.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Publ templates for http://beesbuzz.biz/\n\nThese templates are based on the ones I use on [my website](https://beesbuzz.biz). Here is some documentation on how they are put together.\n\nThis repository also contains some basic content to demonstrate parts of the site in operation.\n\nTo run the sample site, you can do the following:\n\n1. Install [Python](https://python.org/) (at least version 3.6) and [Poetry](https://python-poetry.org/)\n2. Clone this repository\n3. Run `run.sh` (Linux, macOS, WSL, Cygwin) or `winrun.cmd` (Windows)\n4. Point a browser to `http://localhost:5000`\n\nSee the [Publ getting started guide](https://publ.plaidweb.site/manual/328-Getting-started) for more thorough setup instructions.\n\n## App setup\n\nThe application lives in `app.py`, which is set up with basic logging and the following facets:\n\n### Run scripts\n\nThe `run.sh` script installs the current package versions (namely Publ and its dependencies) and indexes all new content (`flask publ reindex`).\n\nThe `fix_dates.py` script goes through and renames my content files to make them easier to find based on date and/or entry ID, and also makes it obvious if I've forgotten about a draft entry or if an entry's been deleted. This script can be run with\n\n```sh\npoetry run python fix_dates.py\n```\n\nAs of Publ v0.7.4 a similar thing can be done as a Publ built-in with e.g.\n\n```sh\npoetry run flask normalize -a\n```\n\nbut I don't especially like changing the filenames quite so substantially.\n\n### Publ configuration\n\nThe database is just local SQLite (yes, even in production; it's actually faster than MySQL or Postgres!).\n\nIf running in debug, there is no cache, otherwise it uses a memcached running on localhost. The cache itself doesn't really do much to speed up the site (as the hit rate is pretty low most of the time), but it does help to  mitigate heavy load spikes whenever Hacker News decides to pay me a visit or whatever.\n\nThe index gets a rescan once a day, which is probably not necessary but it doesn't hurt anything either.\n\n### Authentication\n\nFor friends-only/private entries, this sample site is configured to support emailed magic links (using the local mail server), Mastodon, and IndieAuth; `test:` URLs are also available when the site is running in debug mode. If you have a Twitter API key you can also set the `TWITTER_CLIENT_KEY` and `TWITTER_CLIENT_SECRET` environment variables accordingly, and then people can sign in with Twitter as well.\n\n### Routing rules\n\n`/favicon.ico` redirects to `/static/favicon.ico`. This could also have been done using `send_file('favicon.ico')` in the current directory to make this more transparent, but it doesn't really matter.\n\nLegacy comics paths like `/d/20060606.php` get redirected to an appropriate date-based view within `/comics/`.\n\nMissing blog entries (which I haven't ported from my old site) get redirected to [a placeholder apology](https://beesbuzz.biz/7821).\n\nActivityPub requests are redirected to [bridgy fed](https://fed.brid.gy), which serves as a proxy for the ActivityPub identity `@beesbuzz.biz@beesbuzz.biz`. The intention was to support directly publishing my content to subscribers on Mastodon but the experience there isn't great, due to a fairly large impedance mismatch. Oh well.\n\n## Template overview\n\nThe root-level `index.html` and `entry.html` handle the generic layout for index and entry pages throughout the site. `feed.xml` is the Atom feed.\n\nThe provided `error.html` is just a simple default handler; my actual site also has handlers for `400`, `403`, and `404`. Each of these overrides the `flair` block within the `error` template. The `404.html` in this repository shows a tiny example of how to make 404-specific content on the error page.\n\n`style.css` is the generic stylesheet for sections which have not overridden the stylesheet. It makes use of the `bubbly.css` and `pygments.default.css` libraries, which are stored in the `static` directory.\n\n`robots.txt` is also a template, just to make things simpler. It links to the `sitemap.xml` template.\n\nThe `sitemap.xml` template is for search engines to have indexing hints.\n\n`sitemap.html` is basically a human-readable site map; pretty much all it does is list out the categories on the site.\n\n### Index template (`index.html`)\n\nA few things of note in the `index.html` template:\n\n* It does a relative link to `style.css` for its stylesheet; this means that it will use the dynamically-generated stylesheet for the category.\n* Similarly it does a relative link to the `feed` template, so if someone points their RSS reader at a category page they only subscribe to that category (and its subcategories).\n* Entries with an `Entry-Type` of `sidebar` will appear in the navigation section, rather than in the main content flow\n* If a user is logged out and there are unauthorized entries that would potentially be visible, it displays a login link, and also provides a `\u003clink rel=\"authorization\"\u003e` with the hopes that eventually social readers will support that as a UI cue\n\n#### Articles extensions (`articles/index.html`)\n\nIf an entry has a `Cut` header it will put the cut text next to the \"read more\" link. This is useful for providing content warnings or the like.\n\n### Feed template (`feed.xml`)\n\nThe `feed.xml` template is the Atom feed template. It uses the `_feed_entry.html` template fragment to format entries within the feed. The top-level `_feed_entry.html` is the default for the whole site.\n\nThese `_feed_entry` fragments support a custom entry header, `Cut`, which I use to indicate whether the below-the-fold content should be done as a link or should be put directly into the feed. For example, an entry which looks like this:\n\n```\nTitle: This is a regular entry\n\nHere is the intro text.\n\n.....\n\nHere is the extended text.\n```\n\nwill have both paragraphs appear in the Atom feed; however, an entry like this:\n\n```\nTitle: A spoilered entry\nCut: yes\n\nHere is the intro text.\n\n.....\n\nHere is the extended text.\n```\n\nwill only have the first paragraph appear in the feed, with the below-the-fold content linked to with a \"Read more...\" link. This behavior is controlled by the `_feed_entry.html` fragment.\n\nOther things of note:\n\n* It uses a recursive view; a feed for a category will also include all of the content for its subcategories\n* Items with an `Entry-Type` of `sidebar` will not appear in the feed\n* Items with response-type microformat headers (`in-reply-to`, `bookmark-of`, `like-of`, `rsvp`) will not appear unless there's a URL parameter of `push=1`\n* If a user is logged out and there are unauthorized entries that would potentially be visible, it displays a stub entry to indicate that login is possible, and also a `\u003clink rel=\"authorization\"\u003e` on the feed with the hopes that eventually social readers will support that as a UI cue\n\n### Entry template (`entry.html`)\n\nEntries can override their individual stylesheet by setting a `Stylesheet` header.\n\nEntries with a `cut` will display the extended text inside a `\u003cdetails\u003e`, with the cut text as the `\u003csummary\u003e`. This is to give fair warning to folks who are linked directly to the entry from the outside or who are navigating between adjacent entries.\n\nComments can be disabled on an individual entry by setting a `Disable-Comments` header, e.g.\n\n```\nTitle: Please don't reply to this post\nDisable-Comments: asdf\n```\n\n#### Webmention support\n\nThere are some custom reply-context headers for this template for better outgoing [Webmention](http://indieweb.org/Webmention) support:\n\n* Like-of: Indicates that this entry \"likes\" the specified URL\n* In-reply-to: Indicates that this entry is a reply to the specified URL\n* Repost-of: Indicates that this entry is a repost of the specified URL\n* Bookmark-of: Indicates that this entry collects the specified URL as a resource\n* Mention-of: Indicates that this entry simply mentions the URL (this is also implied without a header though)\n* RSVP: Indicates that this entry is a response to an invitation. In this case, after the URL you provide an RSVP disposition, such as `yes`, `no`, or `maybe`. For example:\n\n    ```\n    RSVP: http://example.com/cool-party yes\n    RSVP: http://example.com/bad-party no\n    ```\n\nIn general you should only have one reply context header; more than one is *technically* supported but it can cause weird things to happen.\n\nThe entry template includes [`_webmention.html`](#incoming-webmention) for incoming webmention support.\n\n#### Per-category extensions (`(category)/entry.html`)\n\nSeveral of the categories override parts of the master `entry.html`, primarily to change the way that images are displayed by default.\n\nSubcategories can remove comments entirely by overriding the `comments` block, although no categories currently do this.\n\n## Main/landing page (`_mainpage.html`, `index.css`)\n\nThe landing page for the site is handled by the `_mainpage.html` template; this is selected by there being a content file called `_site.cat` (this filename is arbitrary) with the following content:\n\n```\nName: busybee\nIndex-Template: _mainpage\nPath-Alias: /feeds.php feed\n\nGraphic design, music production, and blogging tools. The web of yesterday, tomorrow!\n```\n\nThis configures the name of the root category to be \"busybee,\" maps the index page to the `_mainpage` template (this could also be specified as `_mainpage.html`), and it maps a legacy URL for the RSS feed from my old site to the `feed` template.\n\n`_mainpage.html` references `index.css` as its stylesheet. This stylesheet template is only intended to be used from the main page; while it can load from other categories it doesn't actually do anything useful (and it's harmless if some random person decides to poke around with it).\n\n`index.css` does some fancy logic; in particular, for all of the top-level entries with an `Entry-Type` of `sidebar`, it generates a CSS fragment for its link icon. For example, my Twitter link icon has the following entry file associated with it:\n\n```\nTitle: @fluffy\nIcon: twitter\nEntry-Type: sidebar\nRedirect-To: https://twitter.com/fluffy\nDate: 200\nEntry-ID: 4329\nUUID: bfec3987-2754-47b0-b6f5-0231b1b35672\nPath-Alias: /twitter\n```\n\nGiven the CSS fragment generated in `index.css`, this will get its link icon with the content file `_layout/twitter.png`. It also goes ahead and sets up a path-alias such that if someone visits [/twitter](https://beesbuzz.biz/twitter) they get redirected to my Twitter page. (This is useful in that now on other pages I can simply link to `/twitter` and if I ever change my Twitter username for some reason -- [unlikely as that is](https://twitter.com/fluffy/status/1140411704414621696) -- those links will remain valid.)\n\nThe sidebar is sorted using the `Sort-Title` attribute.\n\nThe `#categories li.{{cat.basename}}` rule works similarly to the link fragments; it simply generates CSS rules for each top-level site category where the background image is set to, for example, `_layout/cat-comics.jpg`.\n\n## Art section\n\nThis section is notable in that it has a few different layout overrides; for example, `drawings/style.css` changes the layout of the thumbnails on the page, and `photography/entry.html` suppresses the index-page thumbnail. A typical photography entry looks like this:\n\n```\nTitle: Astoria\nPath-Alias: /art/photography/astoria.php\nCategory: art/photography\nDate: 2018-04-29 23:26:14-07:00\nEntry-ID: 18\nUUID: b3c6f3cd-16fc-4cf2-b165-8a059e66d052\n\n![](DSC06786.jpg) Photos in and around Astoria, OR, centered around the [iLLuMiNART festival](https://www.facebook.com/1928Ratrod/)\n\n.....\n\n![](DSC06750.jpg\n|DSC06754.jpg\n|DSC06786.jpg\n|IMG_4332.jpg\n|IMG_4334.jpg\n|etc...\n)\n```\n\n## Comment forms (`_comment_thread.html`)\n\nI currently use [Isso](https://posativ.org/isso/), which is a Disqus-like self-hosted comment system. If you can run Publ on your site you can also run Isso, although the setup is a bit complicated and outside the scope of this writeup.\n\n`_comment_thread.html` is a template that the other templates use to insert the comment embed code. If you want to use this you'll need to change the thread ID generation and the Isso instance URL accordingly. You will also probably want to implement [`app.thread_id`](app.py#L87) so that you can provide [private, signed thread URIs](http://beesbuzz.biz/blog/4678-Proper-comment-privacy-Yay).\n\nIf you need to update the thread ID key, you can run the script `update-thread-ids.py` against the Isso database.\n\nAlso, the `migrations/` directory contains a few random comment-migration scripts that others might find useful, although keep in mind that this is for preservation of data going back to 2003:\n\n* `disqus-import.py`: Imports threads from Disqus, using the named Disqus thread ID mapping (rather than the page-URI-based scheme that the Isso importer uses)\n* `import-mt.py`: Converts legacy Movable Type comment threads to Isso threads; assumes that the entries have a `Thread-ID: mt_NNNNN` header (where `NNNNN` is the Movable Type entry ID)\n* `import-mt-specific.py`: Transfers MT comment threads that weren't mapped correctly due to various things (for example, in my journal comics, many of them were originally posted on my blog before I had a comics section and I never got around to migrating *those* comments either)\n* `reimport-phpbb.py`: Imports comments from phpBB (using `phpbb_integrate`, my ancient system for [shoehorning phpBB 2.x into Movable Type](http://web.archive.org/web/20121014130936/http://beesbuzz.biz/blog/e/2003/10/24-phpbb-integrate.php))\n* `unmangle.py`: Try to unmangle some of the weirder artifacts from the many layers of phpBB \u0026rarr; Disqus \u0026rarr; Isso\n\nFor all the above cases I had converted database dumps from MySQL to SQLite using [mysql2sqlite](https://github.com/dumblob/mysql2sqlite).\n\n### Use with Disqus\n\nWhen I used Disqus, my `_comment_thread.html` template looked something like this:\n\n```html\n\u003cdiv id=\"disqus_thread\"\u003e\u003c/div\u003e\n\u003cscript\u003e\n    var disqus_config = function () {\n        this.page.url = \"{{entry.permalink(absolute=True)}}\";  // Replace PAGE_URL with your page's canonical URL variable\n        /* {# NOTE: this doesn't lend itself well to privacy since it makes thread IDs very easy to guess; see the note in README.md #} */\n        this.page.identifier = \"{{entry.get('Thread-ID', 'publ_' ~ entry.id)}}\"; // Replace PAGE_IDENTIFIER with your page's unique identifier variable\n    };\n    (function() {  // DON'T EDIT BELOW THIS LINE\n        var d = document, s = d.createElement('script');\n\n        s.src = 'https://[DISQUS-SHORTNAME}.disqus.com/embed.js';\n\n        s.setAttribute('data-timestamp', +new Date());\n        (d.head || d.body).appendChild(s);\n    })();\n\u003c/script\u003e\n\u003cnoscript\u003ePlease enable JavaScript to view the \u003ca href=\"https://disqus.com/?ref_noscript\" rel=\"nofollow\"\u003ecomments powered by Disqus.\u003c/a\u003e\u003c/noscript\u003e\n```\n\nHowever, Disqus was [pretty bad for private entries](http://beesbuzz.biz/blog/1768-Moving-away-from-Disqus), so I moved away from it. As a partial mitigation for one of the privacy concerns I had been using an ad-hoc mechanism to obscure the Disqus thread IDs, but it was ultimately ineffective. If you care about privacy, don't use Disqus. Currently I use [isso](https://posativ.org/isso/), but at some point I will probably build a more purpose-specific commenting system.\n\n## \u003cspan id=\"incoming-webmention\"\u003eIncoming webmentions (`/_webmention.html`, `/static/webmention.js`)\u003c/span\u003e\n\nIncoming webmentions are displayed via [webmention.js](https://github.com/PlaidWeb/webmention.js).\n\nEntries can register additional source URLs using one or more `Old-URL` headers, e.g.:\n\n```\nTitle: This title has changed\nEntry-ID: 1234\nOld-URL: https://blog.example.com/blog/1234-This-is-the-old-title\n```\n\n## Comics section\n\nThis has by far the most complex layout (and unlike most of the site these templates stand alone, aside from the Atom feed), and there is a *lot* to take in. Let's break it down piece by piece.\n\n### Index page (`comics/index.html`)\n\n#### Top stuff\n\n```\n{# Filter out entries which we want to show up in the main flow #}\n{% set TYPEFILTER = [''] %}\n{% set comics = view(entry_type=TYPEFILTER,recurse=True,count=1,order=\"oldest\" if \"date\" in view.spec else \"newest\") %}\n```\n\nThis provides `TYPEFILTER` as a common list of entry types that should be treated as comics; currently that is just the empty/default type. (Previously there was a separate type for `newchapter` back before Publ had a tagging system.)\n\nThe `comics` view is restricted to entries that fit the type filter, and includes all subcategories. `count=1` means it will only show the most recent comic unless there's a date filter in place (as date filters take priority over count-based pagination). If there is a date specified in the default view, it will sort oldest-to-newest so they appear in reading order; otherwise they appear newest-to-oldest so that the most recent comic will display by default.\n\n(This could probably be better.)\n\n```\n{# Find all the categories visible on this page #}\n{% set categories = comics.entries|groupby('category') %}\n```\n\nThis sets up a `categories` grouping which is currently only used for selecting the page stylesheet; if a single series is visible it will use that series stylesheet instead of the default. The stylesheet mostly just chooses which image to put inside the heading.\n\nThe various `macro` sections are all to make the navigation bars easier to manage; they define the following macro functions for the rest of the page:\n\n* `navButton(size,anchor,button,alt,link,title)`: Render a navigation button, `size` pixels tall, with an image given by `_BUTTON.png`, with the provided alt text, link target, and popup (title) text. If `anchor` is specified it will be appended to the link.\n* `navEntry(size, anchor, button, alt, entry)`: Make a navigation button that links to the specified entry. If `entry` is `None` the button will be `_BUTTON-disabled.png` instead of `_BUTTON.png`.\n* `navView(size, anchor, button, alt, view)`: Make a navigation button that links to the provided view. It does some fancy stuff to set the title text automatically; if there's only a single comic on the view it will provide the comic name, otherwise it will give a date range.\n* `navBar(size, mini, anchor)`: Render a navigation bar with the specified size and link anchor. If `mini` is specified it will omit the \"latest\" icon and reduce the size of less-used icons by 1/8.\n\nEntries with a tag of `newchapter` will be used for the previous/next chapter links. I use the `Hidden-Tag` variant, so that if I ever get around to adding browseable tags these won't be visible.\n\n#### `\u003chead\u003e` section\n\nIf only one subcategory's comics are visible, it will use that subcategory's stylesheet; otherwise it will use the current category's. (This is mostly used to affect the title bar image.)\n\nThe page title will be the comic's title if there's only one, or a date range if there's multiple.\n\nThe generated OpenGraph card will be a square-aspect excerpt from the top or left of the comic. If there's a CW cut on the entry it'll also get downsized to 32x32 to make it blurry.\n\n#### Navigation stuff\n\nThe navigation breadcrumb trail shows the parent categories, the current category, then any subcategories. The parent categories' links will link to the current comic view in the context of that category; the subcategory links simply link to the subcategory for now (as the current comic might not exist within the subcategory).\n\n#### Comic display\n\nThis simply displays the comics which match the `TYPEFILTER`. The comic itself will link to the comment page. If there is extended text, the below-cut content gets displayed below, with a thumbnail image gallery if there's images in it.\n\nAlso, if the entry has a `Cut` header (for a content warning or the like), the comic will display inside a collapsed `\u003cdetails\u003e` box, to allow people to use their discretion in looking at it.\n\n#### News box\n\nThis displays all entries with an `Entry-Type` of `News` that come after the first displayed comic and before the first comic of the next page.\n\n### Entry page (`comics/entry.html`)\n\nIndividual entries can override their stylesheet with the `Stylesheet` entry header, if so desired. I do not actually use this function.\n\nCW cuts work similarly to the index page; the comic will be hidden behind a `\u003cdetails\u003e`, and the OpenGraph image excerpt will be blurred.\n\n### Stylesheet (`comics/style.css`)\n\nThe main interesting thing about this one is that the `h1` link background gets the `_logo.jpg` from the current category's content directory if available; by default this will fall back to the `_logo.jpg` file in the comics directory instead (as part of Publ's standard image lookup rules).\n\n\n## Other configuration/scripts\n\nI have configured my site to be deployed via a [git hook](http://publ.beesbuzz.biz/manual/deploying/441-Continuous-deployment-with-git), and I send out WebSub and Webmention pings using [Pushl](https://github.com/PlaidWeb/Pushl).\n\n### `pushl.sh`\n\nThis script runs `pushl` on my various feeds in order to push out webmentions to other sites. My own feeds have a `?push=1` parameter so that response-type entries appear.\n\n### `pub.sh`\n\nThis is a quick-and-dirty script for generating stub entries for webmention responses. It's a bit fiddly but it lets me use Publ to more easily respond to other things using IndieWeb, as well as on Mastodon via [Bridgy Fed](https://fed.brid.gy). For example, if I want to \"like\" a post I can say:\n\n```\n./pub.sh like-of https://beesbuzz.biz/blog/7950-Things-I-accomplished-today\n```\n\nor if I want to reply I can say:\n\n```\nvi $(./pub.sh in-reply-to https://beesbuzz.biz/blog/7950-Things-I-accomplished-today)\n```\n\nand then fill in the entry body.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplaidweb%2Fpubl-templates-beesbuzz.biz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplaidweb%2Fpubl-templates-beesbuzz.biz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplaidweb%2Fpubl-templates-beesbuzz.biz/lists"}