Tweaking my 11ty blog to link to the Mastodon post defined in an Org Mode property
| 11ty, mastodon, org
One of the things I like about blogging from Org
Mode in Emacs is that it's easy to add properties
to the section that I'm working on and then use
those property values elsewhere. For example, I've
modified Emacs to simplify tooting a link to my
blog post and saving the Mastodon status URL in
the EXPORT_MASTODON
property. Then I can use
that in my 11ty static site generation process to
include a link to the Mastodon thread as a comment
option.
First, I need to export the property and include
it in the front matter. I use .11tydata.json files
to store the details for each blog post. I
modified ox-11ty.el so that I could specify
functions to change the front matter
(org-11ty-front-matter-functions
,
org-11ty--front-matter
):
(defvar org-11ty-front-matter-functions nil "Functions to call with the current front matter plist and info.") (defun org-11ty--front-matter (info) "Return front matter for INFO." (let* ((date (plist-get info :date)) (title (plist-get info :title)) (modified (plist-get info :modified)) (permalink (plist-get info :permalink)) (categories (plist-get info :categories)) (collections (plist-get info :collections)) (extra (if (plist-get info :extra) (json-parse-string (plist-get info :extra) :object-type 'plist)))) (seq-reduce (lambda (prev val) (funcall val prev info)) org-11ty-front-matter-functions (append extra (list :permalink permalink :date (if (listp date) (car date) date) :modified (if (listp modified) (car modified) modified) :title (if (listp title) (car title) title) :categories (if (stringp categories) (split-string categories) categories) :tags (if (stringp collections) (split-string collections) collections))))))
Then I added the EXPORT_MASTODON
Org property as
part of the front matter. This took a little
figuring out because I needed to pass it as one of
org-export-backend-options
, where the parameter
is defined as MASTODON
but the actual property
needs to be called EXPORT_MASTODON
.
(defun my-org-11ty-add-mastodon-to-front-matter (front-matter info) (plist-put front-matter :mastodon (plist-get info :mastodon))) (with-eval-after-load 'ox-11ty (cl-pushnew '(:mastodon "MASTODON" nil nil) (org-export-backend-options (org-export-get-backend '11ty))) (add-hook 'org-11ty-front-matter-functions #'my-org-11ty-add-mastodon-to-front-matter))
Then I added the Mastodon field as an option to my
comments.cjs shortcode. This was a little tricky
because I'm not sure I'm passing the data
correctly to the shortcode (sometimes it ends up
as item.data, sometimes it's item.data.data,
…?), but with ?.
, I can just throw all the
possibilities in there and it'll eventually find
the right one.
const pluginRss = require('@11ty/eleventy-plugin-rss'); module.exports = function(eleventyConfig) { function getCommentChoices(data, ref) { const mastodonUrl = data.mastodon || data.page?.mastodon || data.data?.mastodon; const mastodon = mastodonUrl && `<a href="${mastodonUrl}" target="_blank" rel="noopener noreferrer">comment on Mastodon</a>`; const url = ref.absoluteUrl(data.url || data.permalink || data.data?.url || data.data?.permalink, data.metadata?.url || data.data?.metadata?.url); const subject = encodeURIComponent('Comment on ' + url); const body = encodeURIComponent("Name you want to be credited by (if any): \nMessage: \nCan I share your comment so other people can learn from it? Yes/No\n"); const email = `<a href="mailto:sacha@sachachua.com?subject=${subject}&body=${body}">e-mail me at sacha@sachachua.com</a>`; const disqusLink = url + '#comment'; const disqusForm = data.metadata?.disqusShortname && `<div id="disqus_thread"></div> <script> var disqus_config = function () { this.page.url = "${url}"; this.page.identifier = "${data.id || ''} ${data.metadata?.url || ''}?p=${ data.id || data.permalink || this.page?.url}"; this.page.disqusTitle = "${ data.title }" this.page.postId = "${ data.id || data.permalink || this.page?.url }" }; (function() { // DON'T EDIT BELOW THIS LINE var d = document, s = d.createElement('script'); s.src = 'https://${ data.metadata?.disqusShortname }.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); })(); </script> <noscript>Disqus requires Javascript, but you can still e-mail me if you want!</noscript>`; return { mastodon, disqusLink, disqusForm, email }; } eleventyConfig.addShortcode('comments', function(data, linksOnly=false) { const { mastodon, disqusForm, disqusLink, email } = getCommentChoices(data, this); if (linksOnly) { return `You can ${mastodon ? mastodon + ', ' : ''}<a href="${disqusLink}">comment with Disqus (JS required)</a>${mastodon ? ',' : ''} or ${email}.`; } else { return `<div id="comment"></div> You can ${mastodon ? mastodon + ', ' : ''}comment with Disqus (JS required)${mastodon ? ', ' : ''} or you can ${email}. ${disqusForm || ''}`;} }); }
I included it in my post.cjs shortcode:
module.exports = eleventyConfig => eleventyConfig.addShortcode('post', async function(item, index, includeComments) { let comments = '<div class="comments">' + (includeComments ? this.comments(item) : this.comments(item, true)) + '</div>'; let categoryList = item.categories || item.data && item.data.categories; let categoriesFooter = '', categories = ''; if (categoryList && categoryList.length > 0) { categoriesFooter = `<div class="footer-categories">More posts about ${this.categoryList(categoryList)}</div>`; categories = `| <span class="categories">${this.categoryList(categoryList)}</span>`; } return `<article class="post" id="index${index}" data-url="${item.url || item.permalink || ''}"> <header><h2 data-pagefind-meta="title"><a href="${item.url || item.permalink || ''}">${item.title || item.data && item.data.title}</a></h2> ${this.timeInfo(item)}${categories} </header> <div class="entry"> ${await (item.templateContent || item.layoutContent || item.data?.content || item.content || item.inputContent)} </div> ${comments} ${categoriesFooter} </article>`; });
I also included it in my RSS item template to make it easier for people to send me comments without having to dig through my website for contact info.
const posthtml = require("posthtml"); const urls = require("posthtml-urls"); module.exports = (eleventyConfig) => { eleventyConfig.addAsyncShortcode('rssItem', async function(item) { let content = item.templateContent.replace(/--/g, '--'); if (this.transformWithHtmlBase) { content = await this.transformWithHtmlBase(content); } return `<item> <title>${item.data.title}</title> <link>${this.absoluteUrl(item.url, item.data.metadata.url)}</link> <dc:creator><![CDATA[${item.data.metadata.author.name}]]></dc:creator> <pubDate>${item.date.toUTCString()}</pubDate> ${item.data.categories?.map((cat) => `<category>${cat}</category>`).join("\n") || ''} <guid isPermaLink="false">${this.guid(item)}</guid> <description><![CDATA[${content} <p>${this.comments(item, true)}</p>]]></description> </item>`; }); };
The new workflow I'm trying out seems to be working:
- Keep
npx eleventy --serve
running in the background, using.eleventyignore
to make rebuilds reasonably fast. - Export the subtree with
C-c e s 1 1
, which usesorg-export-dispatch
to callmy-org-11ty-export
with the subtree. - After about 10 seconds, use
my-org-11ty-copy-just-this-post
and verify. - Use
my-mastodon-11ty-toot-post
to compose a toot. Edit the toot and post it. - Check that the
EXPORT_MASTODON
property has been set. - Export the subtree again, this time with the front matter.
- Publish my whole blog.
Next, I'm thinking of modifying
my-mastodon-11ty-toot-post
so that it includes a
list of links to blog posts I might be building on
or responding to, and possibly the handles of
people related to those blog posts or topics.
Hmm…