<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feed.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">

<title>Silas Jelley&#39;s Corner of the Web / Notes</title>
<link rel="self" type="application/atom+xml" href="https://silasjelley.com/feeds/notes" />
<link href="https://silasjelley.com/" />
<id>https://silasjelley.com/feeds/notes/</id>
<icon>/icon/48.ico</icon>
<author>
  <name>Silas Jelley</name>
  <email>reply@silasjelley.com</email>
</author>

<updated>2025-11-06T14:02:46Z</updated>
<entry>
  <title>Add @collection() shortcode</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2025/11/06/143146" />
  <id>tag:silasjelley.com,2020-08-20:1c1c8fae-67a6-4bf0-88c8-a6fd2988d395</id>
  <published>2025-11-06T14:31:46Z</published>
  <updated>2025-11-06T14:31:46Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;I wanted a more flexible way to show lists of related posts. The old way involved adding a collection block to a page’s front matter, like this:&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;toml&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;toml&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;toml&#34;&gt;[&lt;span style=&#34;color: #4b69c6&#34;&gt;collection&lt;/span&gt;]&lt;br/&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;include&lt;/span&gt; = [&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;essays&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This worked, but it had big limitations: you could only have one collection, and it always appeared at the bottom of the page. The new &lt;code&gt;\#collection()&lt;/code&gt; shortcode lets me drop collections (as many as I want) anywhere in any document, directly from the body of the document. I also added much improved sorting and filtering options.&lt;/p&gt;
&lt;p&gt;So, TLDR, to show the 5 most recent quotes I’ve saved that are tagged favourite:&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;\#collection(include_: &#34;quotes&#34;, limit: 5, filter_: &#34;tags=favourite&#34;)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which would render as:&lt;/p&gt;
&lt;section data-pagefind-ignore class=&#34;collection-section&#34;&gt;

&lt;article class=&#34;collection title&#34;&gt;
    &lt;header&gt;
        &lt;h2&gt;&lt;a href=&#34;/2026/01/30/154005&#34;&gt;Anne Frank, The Diary of a Young Girl, Penguin Books (2008), p. 249-250, 1947&lt;/a&gt;&lt;/h2&gt;&lt;time datetime=&#34;2026-01-30 15:40:05&#34;&gt;
        &lt;a href=&#34;/2026/01/30/154005&#34;&gt;
        Jan 30, 2026&lt;/a&gt;
    &lt;/time&gt;&lt;/header&gt;
&lt;/article&gt;

&lt;article class=&#34;collection title&#34;&gt;
    &lt;header&gt;
        &lt;h2&gt;&lt;a href=&#34;/2026/01/28/214251&#34;&gt;Anne Morriss, Starbucks &amp;#34;The Way I See It&amp;#34; cup #76, 2009&lt;/a&gt;&lt;/h2&gt;&lt;time datetime=&#34;2026-01-28 21:42:51&#34;&gt;
        &lt;a href=&#34;/2026/01/28/214251&#34;&gt;
        Jan 28, 2026&lt;/a&gt;
    &lt;/time&gt;&lt;/header&gt;
&lt;/article&gt;

&lt;article class=&#34;collection title&#34;&gt;
    &lt;header&gt;
        &lt;h2&gt;&lt;a href=&#34;/2026/01/04/210609&#34;&gt;William H. Whyte, Is Anybody Listening?, 2014&lt;/a&gt;&lt;/h2&gt;&lt;time datetime=&#34;2026-01-04 21:06:09&#34;&gt;
        &lt;a href=&#34;/2026/01/04/210609&#34;&gt;
        Jan  4, 2026&lt;/a&gt;
    &lt;/time&gt;&lt;/header&gt;
&lt;/article&gt;

&lt;article class=&#34;collection title&#34;&gt;
    &lt;header&gt;
        &lt;h2&gt;&lt;a href=&#34;/2025/11/13/112802&#34;&gt;Geoff Manaugh, Seer, BLDGBLOG, 2025&lt;/a&gt;&lt;/h2&gt;&lt;time datetime=&#34;2025-11-13 11:28:02&#34;&gt;
        &lt;a href=&#34;/2025/11/13/112802&#34;&gt;
        Nov 13, 2025&lt;/a&gt;
    &lt;/time&gt;&lt;/header&gt;
&lt;/article&gt;

&lt;article class=&#34;collection title&#34;&gt;
    &lt;header&gt;
        &lt;h2&gt;&lt;a href=&#34;/2025/11/04/193308&#34;&gt;Craig Mod, Day 6: Kiso-Fukushima to Narai, Between Two Mountains, 2025&lt;/a&gt;&lt;/h2&gt;&lt;time datetime=&#34;2025-11-04 19:33:08&#34;&gt;
        &lt;a href=&#34;/2025/11/04/193308&#34;&gt;
        Nov  4, 2025&lt;/a&gt;
    &lt;/time&gt;&lt;/header&gt;
&lt;/article&gt;
&lt;/section&gt;
&lt;p&gt;My first effort had a circular dependency problem: how can you generate a page that needs a list of other pages, when those other pages might not have been generated yet? The solution is necessarily two-pass, and so while sharing its syntax with the other shortcodes, it does not run until later in the build.&lt;/p&gt;
&lt;ol&gt;
&lt;li value=&#34;1&#34;&gt;
&lt;p&gt;First Pass: When the site generator sees a &lt;code&gt;\#collection()&lt;/code&gt; shortcode, it doesn’t build the list right away. Instead, it leaves a unique placeholder and makes a note of the document and the parameters that were used.&lt;/p&gt;
&lt;/li&gt;
&lt;li value=&#34;2&#34;&gt;
&lt;p&gt;Second Pass: After all the pages have had their main content generated, a final step loops over the documents it made a note of.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It now has all the data it needs to build the collection, so it renders the list into HTML and swaps it with the placeholder.&lt;/p&gt;
&lt;p&gt;Despite being two pass, it’s pretty efficient as it only re-visits the pages that actually need it.&lt;/p&gt;
&lt;p&gt;Collections exclude the current page unless the &lt;code&gt;include_self=True&lt;/code&gt; boolean is passed, such as for a series table of contents.&lt;/p&gt;
&lt;p&gt;Collections can contain documents that themselves contain collections, even wehn rendering the whole child document (e.g. &lt;code&gt;style=&#34;body&#34;&lt;/code&gt;). This is achieved through iterative.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Filtering:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;include=&#34;[tags]&#34;&lt;/code&gt;: Includes documents matching a comma-separated list of categories or tags.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\#collection(include_: &#34;essays, wandering&#34;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;exclude=&#34;[tags]&#34;&lt;/code&gt;: Excludes documents from the included set.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;k&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;\#collection(include_: &#34;essays&#34;, exclude_: &#34;personal&#34;)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;filter=&#34;[filters]&#34;&lt;/code&gt;: Filters documents by metadata fields. Supports . for nested fields (e.g., available.year) and checks for list membership (e.g., tags=…).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\#collection(filter_: &#34;favourite=True&#34;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\#collection(filter_: &#34;creator=Silas Jelley, available.year=2024&#34;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\#collection(include_: &#34;notes&#34;, filter_: &#34;tags=python&#34;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sorting &amp;amp; Slicing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;order=&#34;[order]&#34;&lt;/code&gt;: Sets the sort order.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reverse-chronological (default), chronological, alphabetical.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\#collection(include_: &#34;pearls&#34;, order: &#34;alphabetical&#34;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;limit=&#34;[int]&#34;&lt;/code&gt;: Limits the number of documents displayed.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;\#collection(include_: &#34;main&#34;, limit: 5)&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;offset=&#34;[int]&#34;&lt;/code&gt;: Skips the first N documents.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\#collection(include_: &#34;main&#34;, limit: &#34;5,&#34;, offset: 5)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Paginate collections: &lt;code&gt;\#collection(include_: &#34;photos&#34;, limit: &#34;10,&#34;, offset: 20)&lt;/code&gt; shows the third page of photos if you’re showing 10 per page.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Display Styles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;style=&#34;[style]&#34;&lt;/code&gt;: Controls the appearance.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;title (default): A simple list of linked titles and dates.&lt;/li&gt;
&lt;li&gt;summary: Titles, dates, and a short summary of the content.&lt;/li&gt;
&lt;li&gt;body: The full rendered HTML body of each document.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\#collection(include_: &#34;changelogs&#34;, style: &#34;body&#34;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;numbered=True&lt;/code&gt;: Renders a title style collection as a numbered list (&amp;lt;ol&amp;gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\#collection(include_: &#34;series-a&#34;, order: &#34;chronological&#34;, numbered: true)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;show_date=False&lt;/code&gt;: Hides the date in title and summary styles.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\#collection(include_: &#34;essays&#34;, show-date: false)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Behavior:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;include_self=True&lt;/code&gt;: Includes the current document in the list if it matches the filters. The default (False) is to exclude it to prevent circular references.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\#collection(include_: &#34;series-a&#34;, include-self: true)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr/&gt;
&lt;p&gt;This change completely removes the old front matter based collection system.&lt;/p&gt;
2:31pm on November  6, 2025 from Atyrau, Atyrau oblysy, Kazakhstan&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Add @collection() shortcode”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Fail build on unencoded parentheses in URLs</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2025/10/23/205446" />
  <id>tag:silasjelley.com,2020-08-20:b6863e79-27a9-4bec-b49e-7412959b99f3</id>
  <published>2025-10-23T20:54:46Z</published>
  <updated>2025-10-23T20:54:46Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;One of the outputs of this site is &lt;a href=&#34;/links.txt&#34;&gt;a plaintext file containing all the external links&lt;/a&gt; I reference across the site. I noticed recently that a couple of the Wikipedia links in there were incorrect. Wikipedia is one of the few websites that uses parentheses “()” in URLs, and my naive link extracting regex would finish looking at the first closing parenthesis because it took that to mean it was inside &lt;a href=&#34;https://htmlpreview.github.io/?https://github.com/jgm/djot/blob/master/doc/syntax.html#link&#34;&gt;a Djot format link&lt;/a&gt; and shouldn’t include the bracket.&lt;/p&gt;
&lt;p&gt;The result, a URL like &lt;code&gt;https://en.wikipedia.org/wiki/Cardinality_(data_modeling)&lt;/code&gt; would lose it’s last parenthesis and be rendered invalid.&lt;/p&gt;
&lt;p&gt;I first concocted a bit of clever parsing to handle this, and then I decided that the most robust thing to do would be throw away that cleverness and fail loudly if any such URL was found. Now, if I save a URL with unencoded parentheses into one of my markup files, it’ll raise a &lt;code&gt;ValueError&lt;/code&gt; and print a helpful error like so:&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;  ERROR: Document contains URL with improperly encoded characters:&lt;br/&gt;      Title: We shape our software; thereafter our software shapes us&lt;br/&gt;      File:  /home/silas/notes/essays/software-shapes-us.md&lt;br/&gt;&lt;br/&gt;      Replace with:&lt;br/&gt;            #link(&#34;https://en.wikipedia.org/wiki/Ontology_%28information_science%29&#34;)[ontlogies]&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(I’ve hidden the link that includes the link in the error output, else that would raise an error here :D)&lt;/p&gt;
&lt;p&gt;This involved changes to &lt;code&gt;extract_external_links()&lt;/code&gt; and &lt;code&gt;process_documents()&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;extract_external_links&lt;/span&gt;(&lt;br/&gt;    text: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, site, status&lt;br/&gt;) -&amp;gt; Tuple[List, Optional[Tuple[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;]]]:&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;Extract external links from text.&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #74747c&#34;&gt;    Returns:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #74747c&#34;&gt;        Tuple of (sorted external links list, optional (link_text, url) tuple if problematic link found)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #74747c&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Pattern that captures markdown links with URLs containing unencoded parens&lt;/span&gt;&lt;br/&gt;    markdown_link_pattern &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\[&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\]&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\]&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;https&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;://&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\(&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Check for markdown links with unencoded parentheses in URL&lt;/span&gt;&lt;br/&gt;    problematic_links &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; re.&lt;span style=&#34;color: #4b69c6&#34;&gt;findall&lt;/span&gt;(markdown_link_pattern, text)&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; problematic_links:&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; [], problematic_links[&lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;]  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Return first problematic link&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Pattern that handles URLs (now assumes parens are encoded)&lt;/span&gt;&lt;br/&gt;    url_pattern &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; (&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;https&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;://&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;\w&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;|&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;%&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;\d&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;a-f&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;A-F&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;{2}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; domain&lt;/span&gt;&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;\s&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&#34;&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;&#39;&amp;lt;&amp;gt;)&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; path (no unencoded parens allowed)&lt;/span&gt;&lt;br/&gt;    )&lt;br/&gt;    matches &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; re.&lt;span style=&#34;color: #4b69c6&#34;&gt;findall&lt;/span&gt;(url_pattern, text)&lt;br/&gt;&lt;br/&gt;    external_links &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;set&lt;/span&gt;()&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;for&lt;/span&gt; url &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; matches:&lt;br/&gt;        &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Clean up any trailing punctuation that might have been caught&lt;/span&gt;&lt;br/&gt;        url &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; url.&lt;span style=&#34;color: #4b69c6&#34;&gt;rstrip&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.,;:!?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;        parsed_url &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;urlparse&lt;/span&gt;(url)&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; parsed_url.netloc.&lt;span style=&#34;color: #4b69c6&#34;&gt;lower&lt;/span&gt;() &lt;span style=&#34;color: #d73948&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;silasjelley.com&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;:&lt;br/&gt;            external_links.&lt;span style=&#34;color: #4b69c6&#34;&gt;add&lt;/span&gt;(url)&lt;br/&gt;&lt;br/&gt;            &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Only add to site.links if not a draft&lt;/span&gt;&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; status &lt;span style=&#34;color: #d73948&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;draft&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;:&lt;br/&gt;                site.links[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;external&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;].&lt;span style=&#34;color: #4b69c6&#34;&gt;add&lt;/span&gt;(url)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;sorted&lt;/span&gt;(external_links), &lt;span style=&#34;color: #d73948&#34;&gt;None&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;async&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;process_document&lt;/span&gt;(&lt;br/&gt;    filepath: Path, site: SiteMetadata&lt;br/&gt;) -&amp;gt; Tuple[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, DocumentMetadata]:&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;Process a document file and return its UID and metadata.&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;open&lt;/span&gt;(filepath, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;rb&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; f:&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;            parsed_toml &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; tomllib.&lt;span style=&#34;color: #4b69c6&#34;&gt;load&lt;/span&gt;(f)&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt;:&lt;br/&gt;            logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Error while processing document: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;filepath&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;import&lt;/span&gt; sys&lt;br/&gt;&lt;br/&gt;            sys.&lt;span style=&#34;color: #4b69c6&#34;&gt;exit&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; The UID is now the top-level table name&lt;/span&gt;&lt;br/&gt;    uid &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; parsed_toml[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;uid&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;]&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Process metadata into DocumentMetadata instance&lt;/span&gt;&lt;br/&gt;    document &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;preprocess_metadata&lt;/span&gt;(filepath, parsed_toml)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Extract external links from the plain text content&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;        plain_text &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; (&lt;br/&gt;            document.content.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;plain&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt; document.source.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt; document.via.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;        )&lt;br/&gt;&lt;br/&gt;        status &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; document.status &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; document.status &lt;span style=&#34;color: #d73948&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;        external_links, problematic &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;extract_external_links&lt;/span&gt;(plain_text, site, status)&lt;br/&gt;&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; problematic:&lt;br/&gt;            link_text, url &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; problematic&lt;br/&gt;            encoded_url &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; url.&lt;span style=&#34;color: #4b69c6&#34;&gt;replace&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;%28&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;).&lt;span style=&#34;color: #4b69c6&#34;&gt;replace&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;%29&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;ValueError&lt;/span&gt;(&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;  URL contains unencoded parentheses in document:&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;      Title: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;document&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;      File:  &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;filepath&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;      Link:  #link(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;{url}&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)[&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;link_text&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;      Replace with:&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            #link(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;{encoded_url}&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)[&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;link_text&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\n&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;            )&lt;br/&gt;&lt;br/&gt;        document.links[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;external&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; external_links&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;KeyError&lt;/span&gt;:&lt;br/&gt;        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;warn&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;KeyError while compiling external links from &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;document&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;filepath&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;pass&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; uid, document&lt;br/&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
8:54pm on October 23, 2025 from Astrakhan, Astrakhan Oblast, Russia&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Fail build on unencoded parentheses in URLs”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Trim redundant CSS with PurgeCSS</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2025/10/23/192356" />
  <id>tag:silasjelley.com,2020-08-20:b21b87ee-3bbc-48a6-8d26-e614c8ceca74</id>
  <published>2025-10-23T19:23:56Z</published>
  <updated>2025-10-23T19:23:56Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;I figured it made good sense to see if there were any old skeletons lingering in stylesheet before I switch to inlining the core of my styles into the page head. Easily done with PurgeCSS so long as you can stomach another Node package in your filesystem.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npx&lt;/code&gt; makes it trivial.&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;sh&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;npx&lt;/span&gt; purgecss --css /tmp/silasjelley.com/styles.css --content /tmp/silasjelley.com/&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;.html --output /tmp/purge.css&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Diffing the two stylesheets (I use &lt;a href=&#34;https://meldmerge.org/&#34;&gt;Meld&lt;/a&gt;) showed up a handful of old CSS id’s and classes that I was no longer using, shaving a little off that all importing render blocking cascade.&lt;/p&gt;
7:23pm on October 23, 2025 from Astrakhan, Astrakhan Oblast, Russia&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Trim redundant CSS with PurgeCSS”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Preloading fonts to reduce request chaining</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2025/10/23/190913" />
  <id>tag:silasjelley.com,2020-08-20:27362b37-54ed-40b3-981b-a3ea059bbc0b</id>
  <published>2025-10-23T19:09:13Z</published>
  <updated>2025-10-23T19:09:13Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;Added the right incantation to get fonts to pre-load, reducing request chaining by one hop.&lt;/p&gt;
&lt;p&gt;Note, &lt;code&gt;crossorigin&lt;/code&gt; is required even though it’s from the same domain, else the browser will not use them and redownload them when the stylesheet arrives.&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;html&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;html&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;html&#34;&gt;    \&amp;lt;&lt;span style=&#34;color: #4b69c6&#34;&gt;link&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;rel&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;preload&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;href&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/library/misc/fonts/Inter/Regular.woff2&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;as&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;font&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;type&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;font/woff2&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;crossorigin&lt;/span&gt;&amp;gt;&lt;br/&gt;    \&amp;lt;&lt;span style=&#34;color: #4b69c6&#34;&gt;link&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;rel&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;preload&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;href&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/library/misc/fonts/Inter/Bold.woff2&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;as&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;font&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;type&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;font/woff2&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;crossorigin&lt;/span&gt;&amp;gt;&lt;br/&gt;    \&amp;lt;&lt;span style=&#34;color: #4b69c6&#34;&gt;link&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;rel&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;preload&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;href&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/library/misc/fonts/Source_Serif/Regular.woff2&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;as&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;font&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;type&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;font/woff2&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;crossorigin&lt;/span&gt;&amp;gt;&lt;br/&gt;    \&amp;lt;&lt;span style=&#34;color: #4b69c6&#34;&gt;link&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;rel&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;preload&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;href&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/library/misc/fonts/Source_Serif/Italic.woff2&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;as&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;font&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;type&lt;/span&gt;=&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;font/woff2&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;crossorigin&lt;/span&gt;&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
7:09pm on October 23, 2025 from Astrakhan, Astrakhan Oblast, Russia&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Preloading fonts to reduce request chaining”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Cache all image assets in Caddy with a Cache-Control directive</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2025/10/23/183953" />
  <id>tag:silasjelley.com,2020-08-20:da507ef4-6d86-43fd-95f8-61028987ab05</id>
  <published>2025-10-23T18:39:53Z</published>
  <updated>2025-10-23T18:39:53Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;Turns out AVIF images, which I serve to all modern browsers visiting my site, weren’t included in the cache TTL that I use for all my static assets, oops.&lt;/p&gt;
&lt;p&gt;Here’s the updated cache control header for my caddy webserver:&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;caddyfile&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;caddyfile&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;caddyfile&#34;&gt;# Cache Control&lt;br/&gt;# The &#39;file&#39; directive ensures that cache-control is only set for files that actually exist&lt;br/&gt;&lt;br/&gt;@static {&lt;br/&gt;  file&lt;br/&gt;  path *.avif *.ico *.css *.js *.gif *.jpg *.jpeg *.png *.svg *.woff *.woff2 *.gpx&lt;br/&gt;}&lt;br/&gt;header @static Cache-Control max-age=5184000&lt;br/&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
6:39pm on October 23, 2025 from Astrakhan, Astrakhan Oblast, Russia&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Cache all image assets in Caddy with a Cache-Control directive”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Validating HTML with html5validator</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2025/10/21/230131" />
  <id>tag:silasjelley.com,2020-08-20:444e7809-f476-449e-919c-778f7bb0baee</id>
  <published>2025-10-21T23:01:31Z</published>
  <updated>2025-10-21T23:01:31Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;html5validator is turning up a whole littany of validation errors. There all pretty minor, but I like for &lt;a href=&#34;https://en.wikipedia.org/wiki/Robustness_principle&#34;&gt;everything I emit to be well formed&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;sh&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;uvx&lt;/span&gt; html5validator --root /tmp/silasjelley.com&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Non exhaustively:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Figcaption apparently invalid in blockquotes, have replaced with the &amp;lt;cite&amp;gt; element across src/{attribution,shortcodes} (quotes), templates/{collection,default}&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tweaked tests/html_validation.py to ignore the unnamed &amp;lt;meta charset&amp;gt; declaration as this is correct HTML5 as far as I know.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Added alt text to tracking pixel to suppress that warning&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now stripping the &amp;lt;p&amp;gt; and &amp;lt;/p&amp;gt; opening and closing tags from the Jotdown/Djot formatted attribution string, which I’m glad to have done anyway.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Found and removed an erroneous/left-over trailing &amp;lt;/ul&amp;gt; tag from template/default&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Removed invalid &amp;lt;hr&amp;gt; from &amp;lt;summary&amp;gt; element in template/base&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed alert template using paragraph inside a &amp;lt;span&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed formatting of datetime attribute on some &amp;lt;time&amp;gt; elements in templates/collection&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed @aside(…) shortcodes not adding datetime to the &amp;lt;time&amp;gt; element&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed an issue where my external links finder regex was being over greedy&lt;/p&gt;
&lt;p&gt;The regex, &lt;code&gt;r&#34;(https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+(?:/[^)\s]*)?)&#34;&lt;/code&gt; would match some html elements, eg: ‘&lt;a href=&#34;https://player.vimeo.com/api/player.js&#34;&gt;https://player.vimeo.com/api/player.js&lt;/a&gt;“&amp;gt;&amp;lt;/script&amp;gt;‘ and a few other edge cases.&lt;/p&gt;
&lt;p&gt;Have replaced with a more robust regex: &lt;code&gt;r&#34;https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+(?:/[^\s\&#34;&#39;&amp;lt;&amp;gt;)]*)?(?=[\s\&#34;&#39;&amp;lt;&amp;gt;)]|\$)&#34;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;During this checking of external links via the /links.txt file in the output_dir I noticed a minor privacy leak in that external links in draft documents were leaking to the /links.txt file. Not major, but now patched.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed a sneaky HTML escaping issue in templates/collection (&lt;code&gt;{{ item.content.html|striptags|truncate(210) }}&lt;/code&gt;) where the use of striptags can, inadvertently, actually create tags, ie if item.content includes for example “&amp;lt;code&amp;gt;&amp;amp;lt;head&amp;amp;gt;&amp;lt;/code&amp;gt;”, then once the &amp;lt;code&amp;gt; tags have been stripped, a &amp;lt;head&amp;gt; reaches the final output.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fixed the technically invalid use of an unamed &amp;lt;meta&amp;gt; property in the &amp;lt;head&amp;gt; for pagefind’s default image (the favicon) by simply shifting it to the favicon link itself:&lt;/p&gt;
&lt;p&gt;Previously: &amp;lt;meta data-pagefind-default-meta=“image[content]” content=“/favicon.png” /&amp;gt; Now: &amp;lt;link rel=“icon” type=“image/png” href=“/favicon.png” data-pagefind-default-meta=“image[href]” /&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Numerous fixes in my source texts in  /notes, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Having tags closing in the wrong order: &amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;…&amp;lt;/pre&amp;gt;&amp;lt;/code&amp;gt;, things that browsers will tolerate fine, but not correct&lt;/li&gt;
&lt;li&gt;One case of an audio tag not being closed&lt;/li&gt;
&lt;li&gt;Replaced an obselete “frameborder” attribute on an iframe with border:none style&lt;/li&gt;
&lt;li&gt;Cleaned up URLs where trailing ‘?ref=…’ parameters had been left on&lt;/li&gt;
&lt;li&gt;Moved invalid &amp;lt;style&amp;gt; blocks inside body into inline styles&lt;/li&gt;
&lt;li&gt;Replace Djot inline raw &amp;lt;details&amp;gt; elements with block raw to prevent them being wrapping in &amp;lt;p&amp;gt; tags&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
11:01pm on October 21, 2025 from Astrakhan, Astrakhan Oblast, Russia&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Validating HTML with html5validator”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>September 25, 2025 12.09AM</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2025/09/25/000956" />
  <id>tag:silasjelley.com,2020-08-20:58d17e36-c7fb-47fd-8ecf-ddf4217b8367</id>
  <published>2025-09-25T00:09:56Z</published>
  <updated>2025-09-25T00:09:56Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;Fixed a bug where code blocks were inadvertently being double subtracted from &lt;code&gt;site.words.self&lt;/code&gt; (the wordcount of my own written prose), where they should only have been subtracted once.&lt;/p&gt;
12:09am on September 25, 2025 from Appledore, South West England, United Kingdom&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“September 25, 2025 12.09AM”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Added incremental generation of HTML output</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2025/09/24/034006" />
  <id>tag:silasjelley.com,2020-08-20:6f697035-19d7-46df-81c4-5466cb0cb803</id>
  <published>2025-09-24T03:40:06Z</published>
  <updated>2025-09-24T03:40:06Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;Took a simple approach to speeding up rebuilds of the site that I’ve been meaning to crack on with for a while. Ripped out the old output_html function and replaced it with one that natively supports incremental builds.&lt;/p&gt;
&lt;p&gt;May tweak the state that gets hashed for each document if the cache doesn’t stay as warm as it can, but initial results show lots of cache hits, with the &lt;code&gt;Generate Hypertext&lt;/code&gt; build step on a typical rebuild having been cut from around 10 seconds, to 300 milliseconds!&lt;/p&gt;
&lt;p&gt;That’s more than half the total build time gone in most cases :)&lt;/p&gt;
&lt;p&gt;Here’s the bulk of the code:&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color: #d73948&#34;&gt;import&lt;/span&gt; json&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;import&lt;/span&gt; logging&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;from&lt;/span&gt; dataclasses &lt;span style=&#34;color: #d73948&#34;&gt;import&lt;/span&gt; asdict&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;from&lt;/span&gt; hashlib &lt;span style=&#34;color: #d73948&#34;&gt;import&lt;/span&gt; md5&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;from&lt;/span&gt; pathlib &lt;span style=&#34;color: #d73948&#34;&gt;import&lt;/span&gt; Path&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;from&lt;/span&gt; typing &lt;span style=&#34;color: #d73948&#34;&gt;import&lt;/span&gt; Dict, Any, TYPE_CHECKING&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Import types only for type hints, avoiding circular imports&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; TYPE_CHECKING:&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;from&lt;/span&gt; jinja2 &lt;span style=&#34;color: #d73948&#34;&gt;import&lt;/span&gt; Environment&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;from&lt;/span&gt; build &lt;span style=&#34;color: #d73948&#34;&gt;import&lt;/span&gt; DocumentMetadata, AssetMetadata, SiteMetadata&lt;br/&gt;&lt;br/&gt;logger &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; logging.&lt;span style=&#34;color: #4b69c6&#34;&gt;getLogger&lt;/span&gt;(&lt;span style=&#34;color: #4b69c6&#34;&gt;__name__&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;calculate_content_hash&lt;/span&gt;(&lt;br/&gt;    page: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;DocumentMetadata&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, collections: Dict, site: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;SiteMetadata&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;) -&amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;:&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;Calculate a hash representing all content that could affect this page&#39;s output.&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Convert page data to string, excluding volatile fields like backlinks&lt;/span&gt;&lt;br/&gt;    page_dict &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;asdict&lt;/span&gt;(page)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Remove volatile fields that change frequently but don&#39;t affect content&lt;/span&gt;&lt;br/&gt;    volatile_fields &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; [&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;links&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;]  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Remove if backlinks change shouldn&#39;t trigger rebuild&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;for&lt;/span&gt; field &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; volatile_fields:&lt;br/&gt;        page_dict.&lt;span style=&#34;color: #4b69c6&#34;&gt;pop&lt;/span&gt;(field, &lt;span style=&#34;color: #d73948&#34;&gt;None&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Include relevant collection data (for pages that use collections)&lt;/span&gt;&lt;br/&gt;    collection_data &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; page.collection.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;include&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;):&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;for&lt;/span&gt; include &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; page.collection[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;include&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;]:&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; include &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; collections:&lt;br/&gt;                &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Hash just the UIDs and titles of collection items to detect changes&lt;/span&gt;&lt;br/&gt;                collection_items &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; [&lt;br/&gt;                    (item.uid, item.title, item.updated.&lt;span style=&#34;color: #4b69c6&#34;&gt;isoformat&lt;/span&gt;())&lt;br/&gt;                    &lt;span style=&#34;color: #d73948&#34;&gt;for&lt;/span&gt; item &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; collections[include][:&lt;span style=&#34;color: #b60157&#34;&gt;20&lt;/span&gt;]&lt;br/&gt;                ]  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Limit to avoid huge hashes&lt;/span&gt;&lt;br/&gt;                collection_data &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;(collection_items)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Include site metadata that might affect rendering&lt;/span&gt;&lt;br/&gt;    site_data &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; {&lt;br/&gt;        &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;stylesheet_hash&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: site.stylesheet_hash,&lt;br/&gt;        &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;name&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: site.name,&lt;br/&gt;        &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: site.url,&lt;br/&gt;        &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;baseurl&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: site.baseurl,&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Combine all data and hash&lt;/span&gt;&lt;br/&gt;    combined_data &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; json.&lt;span style=&#34;color: #4b69c6&#34;&gt;dumps&lt;/span&gt;(&lt;br/&gt;        [page_dict, collection_data, site_data], sort_keys&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;, default&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;&lt;br/&gt;    )&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;md5&lt;/span&gt;(combined_data.&lt;span style=&#34;color: #4b69c6&#34;&gt;encode&lt;/span&gt;()).&lt;span style=&#34;color: #4b69c6&#34;&gt;hexdigest&lt;/span&gt;()&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;load_build_cache&lt;/span&gt;(output_dir: Path) -&amp;gt; Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Any]]:&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;Load the build cache from previous run.&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;    cache_file &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_dir &lt;span style=&#34;color: #d73948&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.build_cache.json&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; cache_file.&lt;span style=&#34;color: #4b69c6&#34;&gt;exists&lt;/span&gt;():&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;open&lt;/span&gt;(cache_file, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; f:&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; json.&lt;span style=&#34;color: #4b69c6&#34;&gt;load&lt;/span&gt;(f)&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; (json.JSONDecodeError, &lt;span style=&#34;color: #4b69c6&#34;&gt;IOError&lt;/span&gt;):&lt;br/&gt;            logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;warning&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Could not load build cache, rebuilding all files&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; {}&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;save_build_cache&lt;/span&gt;(cache: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Any]], output_dir: Path):&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;Save the build cache for next run.&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;    cache_file &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_dir &lt;span style=&#34;color: #d73948&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.build_cache.json&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;open&lt;/span&gt;(cache_file, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;w&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; f:&lt;br/&gt;            json.&lt;span style=&#34;color: #4b69c6&#34;&gt;dump&lt;/span&gt;(cache, f)&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;IOError&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; e:&lt;br/&gt;        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;warning&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Could not save build cache: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;e&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;needs_rebuild&lt;/span&gt;(&lt;br/&gt;    page: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;DocumentMetadata&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;    collections: Dict,&lt;br/&gt;    site: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;SiteMetadata&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;    output_path: Path,&lt;br/&gt;    cache: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Any]],&lt;br/&gt;    template_file: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;,&lt;br/&gt;    env: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;) -&amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;bool&lt;/span&gt;:&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;Determine if a page needs to be rebuilt.&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Always rebuild if output doesn&#39;t exist&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;not&lt;/span&gt; output_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;exists&lt;/span&gt;():&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    page_key &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; page.uid&lt;br/&gt;    current_hash &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;calculate_content_hash&lt;/span&gt;(page, collections, site)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; page_key &lt;span style=&#34;color: #d73948&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; cache:&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    cached_data &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; cache[page_key]&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; cached_data.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;content_hash&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;!=&lt;/span&gt; current_hash:&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Check template modification time&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;        template_path &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; env.loader.&lt;span style=&#34;color: #4b69c6&#34;&gt;get_source&lt;/span&gt;(env, template_file)[&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;]&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; template_path:&lt;br/&gt;            template_mtime &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;Path&lt;/span&gt;(template_path).&lt;span style=&#34;color: #4b69c6&#34;&gt;stat&lt;/span&gt;().st_mtime&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; cached_data.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;template_mtime&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;&amp;lt;&lt;/span&gt; template_mtime:&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;Exception&lt;/span&gt;:&lt;br/&gt;        &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; If we can&#39;t check template mtime, rebuild to be safe&lt;/span&gt;&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Check source file modification time&lt;/span&gt;&lt;br/&gt;    source_mtime &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; page.filepath.&lt;span style=&#34;color: #4b69c6&#34;&gt;stat&lt;/span&gt;().st_mtime&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; cached_data.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;source_mtime&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;&amp;lt;&lt;/span&gt; source_mtime:&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;False&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;output_html_incremental&lt;/span&gt;(&lt;br/&gt;    assets: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;AssetMetadata&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;],&lt;br/&gt;    documents: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;DocumentMetadata&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;],&lt;br/&gt;    collections: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Any],&lt;br/&gt;    site: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;SiteMetadata&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;    env: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;    output_dir: Path,&lt;br/&gt;    build_page_collection_func,  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Function to build page collections&lt;/span&gt;&lt;br/&gt;) -&amp;gt; &lt;span style=&#34;color: #d73948&#34;&gt;None&lt;/span&gt;:&lt;br/&gt;    logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;info&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Generating Hypertext (incremental)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Load build cache&lt;/span&gt;&lt;br/&gt;    cache &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;load_build_cache&lt;/span&gt;(output_dir)&lt;br/&gt;    new_cache &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; {}&lt;br/&gt;&lt;br/&gt;    built_count &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;&lt;br/&gt;    skipped_count &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;for&lt;/span&gt; key, page &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; documents.&lt;span style=&#34;color: #4b69c6&#34;&gt;items&lt;/span&gt;():&lt;br/&gt;        template_file &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; page.layout&lt;br/&gt;        output_path &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_dir &lt;span style=&#34;color: #d73948&#34;&gt;/&lt;/span&gt; page.slug &lt;span style=&#34;color: #d73948&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;index.html&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;        &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Check if rebuild is needed&lt;/span&gt;&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;needs_rebuild&lt;/span&gt;(&lt;br/&gt;            page, collections, site, output_path, cache, template_file, env&lt;br/&gt;        ):&lt;br/&gt;            &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Copy cache entry to new cache&lt;/span&gt;&lt;br/&gt;            new_cache[key] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; cache[key]&lt;br/&gt;            skipped_count &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;br/&gt;            logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;debug&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;  SKIP: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;page&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;filepath&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;continue&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;        &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Rebuild the page&lt;/span&gt;&lt;br/&gt;        template &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; env.&lt;span style=&#34;color: #4b69c6&#34;&gt;get_template&lt;/span&gt;(template_file)&lt;br/&gt;        collection &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;build_page_collection_func&lt;/span&gt;(page, collections)&lt;br/&gt;&lt;br/&gt;        output &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; template.&lt;span style=&#34;color: #4b69c6&#34;&gt;render&lt;/span&gt;(&lt;br/&gt;            documents&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;documents,&lt;br/&gt;            assets&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;assets,&lt;br/&gt;            collections&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;collections,&lt;br/&gt;            collection&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;collection,&lt;br/&gt;            page&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;asdict&lt;/span&gt;(page),&lt;br/&gt;            site&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;site,&lt;br/&gt;        )&lt;br/&gt;&lt;br/&gt;        &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Ensure output directory exists&lt;/span&gt;&lt;br/&gt;        output_path.parent.&lt;span style=&#34;color: #4b69c6&#34;&gt;mkdir&lt;/span&gt;(parents&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;, exist_ok&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;        &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Write the file&lt;/span&gt;&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;open&lt;/span&gt;(output_path, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;w&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; f:&lt;br/&gt;            f.&lt;span style=&#34;color: #4b69c6&#34;&gt;write&lt;/span&gt;(output)&lt;br/&gt;&lt;br/&gt;        &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Update cache&lt;/span&gt;&lt;br/&gt;        current_hash &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;calculate_content_hash&lt;/span&gt;(page, collections, site)&lt;br/&gt;        template_mtime &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;            template_path &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; env.loader.&lt;span style=&#34;color: #4b69c6&#34;&gt;get_source&lt;/span&gt;(env, template_file)[&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;]&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; template_path:&lt;br/&gt;                template_mtime &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;Path&lt;/span&gt;(template_path).&lt;span style=&#34;color: #4b69c6&#34;&gt;stat&lt;/span&gt;().st_mtime&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;Exception&lt;/span&gt;:&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;pass&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;        new_cache[key] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;content_hash&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: current_hash,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;template_mtime&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: template_mtime,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;source_mtime&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: page.filepath.&lt;span style=&#34;color: #4b69c6&#34;&gt;stat&lt;/span&gt;().st_mtime,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;output_path&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;(output_path),&lt;br/&gt;        }&lt;br/&gt;&lt;br/&gt;        built_count &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;br/&gt;        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;debug&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;  BUILD: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;page&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;filepath&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;output_path&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Save updated cache&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;save_build_cache&lt;/span&gt;(new_cache, output_dir)&lt;br/&gt;&lt;br/&gt;    logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;info&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Built: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;built_count&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;, Skipped: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;skipped_count&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;clear_build_cache&lt;/span&gt;(output_dir: Path) -&amp;gt; &lt;span style=&#34;color: #d73948&#34;&gt;None&lt;/span&gt;:&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;Clear the build cache to force a full rebuild.&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;    cache_file &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_dir &lt;span style=&#34;color: #d73948&#34;&gt;/&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.build_cache.json&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; cache_file.&lt;span style=&#34;color: #4b69c6&#34;&gt;exists&lt;/span&gt;():&lt;br/&gt;        cache_file.&lt;span style=&#34;color: #4b69c6&#34;&gt;unlink&lt;/span&gt;()&lt;br/&gt;        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;info&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Build cache cleared&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
3:40am on September 24, 2025 from Appledore, South West England, United Kingdom&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Added incremental generation of HTML output”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>SEP 64: Redesign shortcodes</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2025/09/23/144534" />
  <id>tag:silasjelley.com,2020-08-20:ddcfd972-3c36-4bc6-848c-1d3841c3692c</id>
  <published>2025-09-23T14:45:34Z</published>
  <updated>2025-09-23T14:45:34Z</updated>
  <category term="notes" />
  <category term="proposals" />
  <content type="html">
    
&lt;aside class=&#34;aside&#34;&gt;
&lt;p&gt;PENDING:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Implement &lt;code&gt;@terminal/shell/command&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[x] Implement &lt;code&gt;@embed&lt;/code&gt;and replace &lt;code&gt;aside::&lt;/code&gt; and &lt;code&gt;import::&lt;/code&gt; substitutions/imports with &lt;code&gt;@embed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;[ ] Make assets support backlinks so that &lt;code&gt;@link&lt;/code&gt; can be used for assets too, then review all uses of &lt;code&gt;@val(&#34;id.slug&#34;)&lt;/code&gt; and replace with &lt;code&gt;@link&lt;/code&gt; where possible&lt;/li&gt;
&lt;li&gt;[x] Finish migrating all instances of old &lt;code&gt;link::&lt;/code&gt; and &lt;code&gt;slug:&lt;/code&gt; syntax (slug is now &lt;code&gt;@val(&#34;\$uid.slug&#34;)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;[x] Remove last of the legacy substitution code&lt;/li&gt;
&lt;li&gt;[ ] Ultimately I would love to get rid of all regex based substitution schemes and instead parse my documents into an AST and then walk the tree, altering nodes directly, but I’m a ways away from that yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;/aside&gt;
&lt;p&gt;I want a better design for the shortcodes I use throughout the site.&lt;/p&gt;
&lt;p&gt;The current “design” is captured in the regexes used to extract them:&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;    REF_LINK_RE &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; re.&lt;span style=&#34;color: #4b69c6&#34;&gt;compile&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\[&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\]&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\]&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    REF_SLUG_RE &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; re.&lt;span style=&#34;color: #4b69c6&#34;&gt;compile&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;!`&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;slug::&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;a-z&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;A-Z&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;0-9&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;\s&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    REF_TITLE_RE &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; re.&lt;span style=&#34;color: #4b69c6&#34;&gt;compile&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\{&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\{&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;title::&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;a-z&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;A-Z&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;0-9&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\}&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    REF_CITE_RE &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; re.&lt;span style=&#34;color: #4b69c6&#34;&gt;compile&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\{&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\{&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;cite::&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;a-z&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;A-Z&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;0-9&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\}&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    REF_IMPT_RE &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; re.&lt;span style=&#34;color: #4b69c6&#34;&gt;compile&lt;/span&gt;(&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\{&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\{&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;aside&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;|&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;import&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;::&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;a-z&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;A-Z&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;0-9&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\}&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    )&lt;br/&gt;&lt;br/&gt;    text &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; page.content.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;plain&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; text:&lt;br/&gt;        text &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;replace_import_references&lt;/span&gt;(text, REF_IMPT_RE, merged_data, key, page)&lt;br/&gt;        text &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;replace_cite_references&lt;/span&gt;(text, REF_CITE_RE, merged_data)&lt;br/&gt;        text &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;replace_title_references&lt;/span&gt;(text, REF_TITLE_RE, merged_data)&lt;br/&gt;        text &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;replace_slug_references&lt;/span&gt;(text, REF_SLUG_RE, merged_data)&lt;br/&gt;        text &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;process_reference_links&lt;/span&gt;(text, REF_LINK_RE, merged_data, key)&lt;br/&gt;        page.content[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;plain&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; text.&lt;span style=&#34;color: #4b69c6&#34;&gt;strip&lt;/span&gt;()&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For example, &lt;code&gt;::Author](quote::\$UID)&lt;/code&gt; would be replaced with a quote embed of the provided uid. This design was meant to closely follow the semantics of markdown/Djot links. As my document system expands, and I move further away from existing markup syntax, I want a design that generalises better, provides more legibility and extensibility, and has support for parameters.&lt;/p&gt;
&lt;p&gt;TLDR: &lt;code&gt;@keyword(option=&#34;value&#34;)&lt;/code&gt; or something similar.&lt;/p&gt;
&lt;p&gt;Core Idea: A single sigil, @, initiates a “function” with a clear name and parameters.&lt;/p&gt;
&lt;p&gt;Syntax &amp;amp; Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The Function Call: &lt;code&gt;@command(id, ...)&lt;/code&gt; or &lt;code&gt;@command{...}&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Links: &lt;code&gt;@link(&#34;a1b2c3d4&#34;, fragment: &#34;...&#34;)[]&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Could use positional params for brevity: &lt;code&gt;@link(&#34;a1b2c3d4&#34;)[]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Images/Media:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@img(&#34;a1b2c3d4&#34;, alt: &#34;...&#34;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@video(&#34;a1b2c3d4&#34;)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inline Data: A dedicated command to retrieve values.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The title is &lt;code&gt;@val(&#34;a1b2c3d4.title&#34;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Find it at &lt;code&gt;@val(&#34;a1b2c3d4.slug&#34;)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Formatted Content &amp;amp; Transclusion: Each has a clear, named command.&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;@cite(&#34;a1b2c3d4&#34;)&lt;br/&gt;@quote(&#34;a1b2c3d4&#34;)&lt;br/&gt;@embed(&#34;a1b2c3d4&#34;)&lt;br/&gt;@ gallery{&lt;br/&gt;  @img(&#34;b2c3d4e5&#34;)&lt;br/&gt;  @img(&#34;f6&#34;)&lt;br/&gt;} (Block-form for wrapping content)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr/&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Key/value parameters vs positional arguments?&lt;/strong&gt;.&lt;br/&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Positional arguments can be more terse, but like short-opts (vs long-opts) they are less legible and they bake in assumptions. A key/value system is more verbose but removes ambiguity and makes changing defaults, and adding or deprecating arguments much safer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There should be a syntax for escaping/skipping a shortcode, when I want the actual shortcode to remain in a document.&lt;/li&gt;
&lt;/ul&gt;
2:45pm on September 23, 2025 from Appledore, South West England, United Kingdom&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“SEP 64: Redesign shortcodes”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Improved rendering of justified text</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2025/01/15/121447" />
  <id>tag:silasjelley.com,2020-08-20:3661f564-715a-4ced-a2ca-3c9c8556d17a</id>
  <published>2025-01-15T12:14:47Z</published>
  <updated>2025-01-15T12:14:47Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;For a while I’ve been using justified text to give blockquotes on the site a distinct visual language, I like it, even though algorithmically justified text on the web is far from perfect, with certain viewport widths yielding ugly rivers in a text block.&lt;/p&gt;
&lt;p&gt;Last night I came across a post from Tyler Sticka — &lt;a href=&#34;https://cloudfour.com/thinks/justified-text-better-than-expected/&#34;&gt;Justified Text: Better Than Expected?&lt;/a&gt; — that introduced me to a couple of CSS properties that improve rendering,&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;css&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;css&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;css&#34;&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;blockquote&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;hyphens&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;auto&lt;/span&gt;;&lt;br/&gt;  hyphenate-limit-chars: &lt;span style=&#34;color: #b60157&#34;&gt;7&lt;/span&gt;;&lt;br/&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here’s what I use for blockquotes now,&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;css&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;css&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;css&#34;&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;blockquote&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;padding&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;em&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;em&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;margin&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;em&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;position&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;relative&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;color&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color: #4b69c6&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;blockquote-foreground&lt;/span&gt;);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;blockquote&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;p&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;text-align&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;justify&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;hyphens&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;auto&lt;/span&gt;;&lt;br/&gt;  hyphenate-limit-chars: &lt;span style=&#34;color: #b60157&#34;&gt;7&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;margin&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;em&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;blockquote&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;p&lt;/span&gt; + &lt;span style=&#34;color: #4b69c6&#34;&gt;p&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;text-indent&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;em&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;margin&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;;&lt;br/&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
12:14pm on January 15, 2025 from Vancouver, British Columbia, Canada&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Improved rendering of justified text”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Add dataclasses to ratchet up the strictness</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2024/12/11/234731" />
  <id>tag:silasjelley.com,2020-08-20:f544d737-8000-408d-b32d-86428a349e4d</id>
  <published>2024-12-16T09:54:32Z</published>
  <updated>2024-12-16T09:54:30Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;In continuing to ratchet up the strictness of the build system I landed on the concept of dataclasses, which are new to this novice programmer, though the concept of a schema for validation is not.&lt;/p&gt;
&lt;p&gt;Anyway, I landed on creating one dataclass for Documents and another for Assets.&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color: #301414&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color: #301414&#34;&gt;dataclass&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;DocumentMetadata&lt;/span&gt;:&lt;br/&gt;    filepath: Path&lt;br/&gt;    uid: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;&lt;br/&gt;    slug: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;&lt;br/&gt;    title: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;&lt;br/&gt;    primary: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;&lt;br/&gt;    secondary: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;&lt;br/&gt;    available: datetime.datetime&lt;br/&gt;    created: datetime.datetime&lt;br/&gt;    updated: datetime.datetime&lt;br/&gt;    creator: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    note: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    favourite: &lt;span style=&#34;color: #4b69c6&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;False&lt;/span&gt;&lt;br/&gt;    parent: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    description: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    layout: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; TEMPLATE_DEFAULT&lt;br/&gt;    source: Dict &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;dict&lt;/span&gt;)&lt;br/&gt;    via: Dict &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;dict&lt;/span&gt;)&lt;br/&gt;    location: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Any] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(&lt;br/&gt;        default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;lambda&lt;/span&gt;: {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;continent&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;country&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;region&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;city&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;note&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;lat&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;int&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;lng&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;int&lt;/span&gt;,&lt;br/&gt;        }&lt;br/&gt;    )&lt;br/&gt;    collection: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Any] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(&lt;br/&gt;        default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;lambda&lt;/span&gt;: {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;style&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;chronological&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;include&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: [],&lt;br/&gt;        }&lt;br/&gt;    )&lt;br/&gt;    attribution: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(&lt;br/&gt;        default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;lambda&lt;/span&gt;: {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;plain&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;djot&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;html&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;        }&lt;br/&gt;    )&lt;br/&gt;    media: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;application/toml&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    words: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Any] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(&lt;br/&gt;        default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;lambda&lt;/span&gt;: {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;code&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: {&lt;br/&gt;                &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;lines&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;                &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;words&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;            },&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;references&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;        }&lt;br/&gt;    )&lt;br/&gt;    status: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    links: LinksDict &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(&lt;br/&gt;        default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;lambda&lt;/span&gt;: {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;internal&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;list&lt;/span&gt;(),&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;external&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;list&lt;/span&gt;(),&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;backlinks&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;list&lt;/span&gt;(),&lt;br/&gt;        }&lt;br/&gt;    )&lt;br/&gt;    options: List[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;list&lt;/span&gt;)&lt;br/&gt;    tags: List[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;list&lt;/span&gt;)&lt;br/&gt;    content: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;dict&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;__post_init__&lt;/span&gt;(self):&lt;br/&gt;        &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Validate links dictionary structure&lt;/span&gt;&lt;br/&gt;        required_link_types &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;internal&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;external&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;backlinks&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;}&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;isinstance&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links, &lt;span style=&#34;color: #4b69c6&#34;&gt;dict&lt;/span&gt;)&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links.&lt;span style=&#34;color: #4b69c6&#34;&gt;keys&lt;/span&gt;()) &lt;span style=&#34;color: #d73948&#34;&gt;!=&lt;/span&gt; required_link_types&lt;br/&gt;        ):&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;ValueError&lt;/span&gt;(&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;links must be a dictionary with exactly these keys: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;required_link_types&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;            )&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;for&lt;/span&gt; key &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links:&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;isinstance&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links[key], &lt;span style=&#34;color: #4b69c6&#34;&gt;set&lt;/span&gt;):&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links[key] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links[key])&lt;br/&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color: #301414&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color: #301414&#34;&gt;dataclass&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;AssetMetadata&lt;/span&gt;:&lt;br/&gt;    filepath: Path&lt;br/&gt;    media: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;&lt;br/&gt;    uid: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;&lt;br/&gt;    slug: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;&lt;br/&gt;    title: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;&lt;br/&gt;    available: datetime.datetime&lt;br/&gt;    created: datetime.datetime&lt;br/&gt;    updated: datetime.datetime&lt;br/&gt;    creator: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    note: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    favourite: &lt;span style=&#34;color: #4b69c6&#34;&gt;bool&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;False&lt;/span&gt;&lt;br/&gt;    source: Dict &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;dict&lt;/span&gt;)&lt;br/&gt;    via: Dict &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;dict&lt;/span&gt;)&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;hash&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    output_width: &lt;span style=&#34;color: #4b69c6&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;&lt;br/&gt;    output_height: &lt;span style=&#34;color: #4b69c6&#34;&gt;int&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;&lt;br/&gt;    location: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Any] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(&lt;br/&gt;        default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;lambda&lt;/span&gt;: {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;continent&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;country&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;region&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;city&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;note&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;lat&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;int&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;lng&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;int&lt;/span&gt;,&lt;br/&gt;        }&lt;br/&gt;    )&lt;br/&gt;    attribution: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(&lt;br/&gt;        default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;lambda&lt;/span&gt;: {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;plain&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;djot&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;html&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;        }&lt;br/&gt;    )&lt;br/&gt;    words: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Any] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(&lt;br/&gt;        default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;lambda&lt;/span&gt;: {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;code&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: {&lt;br/&gt;                &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;lines&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;                &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;words&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;            },&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;references&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;        }&lt;br/&gt;    )&lt;br/&gt;    links: LinksDict &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(&lt;br/&gt;        default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;lambda&lt;/span&gt;: {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;internal&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;list&lt;/span&gt;(),&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;external&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;list&lt;/span&gt;(),&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;backlinks&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;list&lt;/span&gt;(),&lt;br/&gt;        }&lt;br/&gt;    )&lt;br/&gt;    tags: List[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;list&lt;/span&gt;)&lt;br/&gt;    content: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;dict&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;__post_init__&lt;/span&gt;(self):&lt;br/&gt;        &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Validate links dictionary structure&lt;/span&gt;&lt;br/&gt;        required_link_types &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;internal&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;external&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;backlinks&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;}&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;isinstance&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links, &lt;span style=&#34;color: #4b69c6&#34;&gt;dict&lt;/span&gt;)&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links.&lt;span style=&#34;color: #4b69c6&#34;&gt;keys&lt;/span&gt;()) &lt;span style=&#34;color: #d73948&#34;&gt;!=&lt;/span&gt; required_link_types&lt;br/&gt;        ):&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;raise&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;ValueError&lt;/span&gt;(&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;links must be a dictionary with exactly these keys: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;required_link_types&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;            )&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;for&lt;/span&gt; key &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links:&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;isinstance&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links[key], &lt;span style=&#34;color: #4b69c6&#34;&gt;set&lt;/span&gt;):&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links[key] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;self&lt;/span&gt;.links[key])&lt;br/&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This turned up a couple of oversights in metadata for existing documents, and also encouraged me to restructure the final built metadata, see backlinks and wordcounting.&lt;/p&gt;
&lt;p&gt;Speaking of backlinks, I coupled this work with my ongoing effort to make the building of the site completely deterministic. The builds already are deterministic in every way that matters. Same inputs make for the same output, except in one way: because documents are built asynchronously, it has been possible for the order of backlink references to change between builds. This doesn’t alter any functionality or correctness but does mean that the order of the backlinks in the hidden build summary of each page could change, and this makes diffing over the whole site to spot regressions more noisy, as many differences were not really differences at all.&lt;/p&gt;
&lt;p&gt;Solving this turned out to be easy once I realised that a few pages kept seeing the order shift because they referenced pages that had the same publish (&lt;code&gt;available&lt;/code&gt;) date, which is the key for the sort order. With that remedied, determinism is assured.&lt;/p&gt;
&lt;details&gt;&lt;summary&gt;For a glimpse at the surface area of the changes, click here to see a diff from implementing the first dataclass, though more changes have landed since.&lt;/summary&gt;&lt;div class=&#34;code-block&#34; data-lang=&#34;diff&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;diff&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;diff&#34;&gt; # Imports&lt;br/&gt; from collections import Counter&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;from dataclasses import dataclass, field&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;from dataclasses import dataclass, field, asdict&lt;/span&gt;&lt;br/&gt; from hashlib import md5&lt;br/&gt; from pathlib import Path&lt;br/&gt; from shutil import copyfile&lt;br/&gt; from subprocess import run&lt;br/&gt; from typing import List, Dict, Any, Tuple&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;from typing_extensions import TypedDict&lt;/span&gt;&lt;br/&gt; import asyncio&lt;br/&gt; import datetime&lt;br/&gt; import logging&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-73,11 +74,11&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;class SiteMetadata:&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     words: Dict[str, int] = field(&lt;br/&gt;         default_factory=lambda: {&#34;drafts&#34;: 0, &#34;references&#34;: 0, &#34;self&#34;: 0}&lt;br/&gt;     )&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    links: Dict[str, set] = field(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    links: Dict[str, Any] = field(&lt;/span&gt;&lt;br/&gt;         default_factory=lambda: {&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;internal&#34;: set(),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;internal&#34;: list(),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;backlinks&#34;: list(),&lt;/span&gt;&lt;br/&gt;             &#34;external&#34;: set(),&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;backlinks&#34;: set(),&lt;/span&gt;&lt;br/&gt;         }&lt;br/&gt;     )&lt;br/&gt;     pagecount: int = 0&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-91,9 +92,15&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;class SiteMetadata:&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     slug_to_title_lookup: Dict[str, str] = field(default_factory=dict)&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;class LinksDict(TypedDict):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    internal: list[str]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    external: list[str]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    backlinks: list[str]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt; \@dataclass&lt;br/&gt; class DocumentMetadata:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    filename: Path&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    filepath: Path&lt;/span&gt;&lt;br/&gt;     uid: str&lt;br/&gt;     slug: str&lt;br/&gt;     created: datetime.datetime&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-102,20 +109,78&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;class DocumentMetadata:&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     title: str&lt;br/&gt;     primary: str&lt;br/&gt;     secondary: str&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    creator: str = &#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    note: str = &#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    favourite: bool = False&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    parent: str = &#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    description: str = &#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    layout: str = TEMPLATE_DEFAULT&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    source: Dict = field(default_factory=dict)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    via: Dict = field(default_factory=dict)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    location: Dict = field(default_factory=dict)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    collection: Dict[str, Any] = field(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        default_factory=lambda: {&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;style&#34;: &#34;title&#34;,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;order&#34;: &#34;chronological&#34;,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;include&#34;: [],&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    attribution: Dict[str, str] = field(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        default_factory=lambda: {&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;plain&#34;: &#34;&#34;,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;djot&#34;: &#34;&#34;,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;html&#34;: &#34;&#34;,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    media: str = &#34;application/toml&#34;&lt;/span&gt;&lt;br/&gt;     words: int = 0&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    links: Dict[str, set] = field(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    status: str = &#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    links: LinksDict = field(&lt;/span&gt;&lt;br/&gt;         default_factory=lambda: {&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;internal&#34;: set(),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;external&#34;: set(),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;backlinks&#34;: set(),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;internal&#34;: list(),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;external&#34;: list(),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34;backlinks&#34;: list(),&lt;/span&gt;&lt;br/&gt;         }&lt;br/&gt;     )&lt;br/&gt;     options: List[str] = field(default_factory=list)&lt;br/&gt;     tags: List[str] = field(default_factory=list)&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    interlinks: List[str] = field(default_factory=list)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    backlinks: List[str] = field(default_factory=list)&lt;/span&gt;&lt;br/&gt;     content: Dict[str, str] = field(default_factory=dict)&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    html: str = &#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    def __post_init__(self):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        # Validate required string fields are not empty&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        # for field_name in [&#34;uid&#34;, &#34;slug&#34;, &#34;title&#34;, &#34;primary&#34;, &#34;secondary&#34;]:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        #    value = getattr(self, field_name)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        #    if not isinstance(value, str) or not value.strip():&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        #        raise ValueError(f&#34;\n\n{self}\n\n{field_name} {value} must be a non-empty string&#34;\n)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        # Validate filepath is a Path object&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        if not isinstance(self.filepath, Path):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            self.filepath = Path(self.filepath)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        ## Validate datetime fields&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        # for field_name in [&#34;created&#34;, &#34;updated&#34;, &#34;available&#34;]:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        #    value = getattr(self, field_name)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        #    if not isinstance(value, datetime.datetime):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        #        raise ValueError(f&#34;{field_name} must be a datetime object&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        #    # Ensure timezone is None&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        #    setattr(self, field_name, value.replace(tzinfo=None))&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        # Validate words is non-negative&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        if not isinstance(self.words, int) or self.words &amp;lt; 0:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            raise ValueError(&#34;words must be a non-negative integer&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        # Validate links dictionary structure&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        required_link_types = {&#34;internal&#34;, &#34;external&#34;, &#34;backlinks&#34;}&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        if (&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            not isinstance(self.links, dict)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            or set(self.links.keys()) != required_link_types&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        ):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            raise ValueError(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                f&#34;links must be a dictionary with exactly these keys: {required_link_types}&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        for key in self.links:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            if not isinstance(self.links[key], set):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                self.links[key] = set(self.links[key])&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def init_site():&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-132,12 +197,47&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def init_site():&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     )&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def load_assets():&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;def preprocess_asset_metadata(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    uid: str, asset_data: Dict[str, Any], manifest_path: Path&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;) -&amp;gt; Dict[str, Any]:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    &#34;&#34;&#34;Preprocess asset metadata to ensure it meets DocumentMetadata requirements.&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    processed = asset_data.copy()&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    # Handle dates&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    for date_field in [&#34;created&#34;, &#34;updated&#34;, &#34;available&#34;]:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        if isinstance(processed.get(date_field), str):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            processed[date_field] = _parse_date(processed[date_field])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        elif isinstance(processed.get(date_field), datetime.datetime):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            processed[date_field] = processed[date_field].replace(tzinfo=None)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        else:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            processed[date_field] = datetime.datetime.now()&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    # Set required fields with defaults if not present&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    processed.setdefault(&#34;uid&#34;, uid)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    processed.setdefault(&#34;primary&#34;, &#34;asset&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    processed.setdefault(&#34;secondary&#34;, processed[&#34;media&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    return processed&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;def load_assets() -&amp;gt; Dict[str, DocumentMetadata]:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    &#34;&#34;&#34;Load asset manifests and convert them to DocumentMetadata instances.&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;     assets = {}&lt;br/&gt;     asset_manifests = list(ASSET_DIR.glob(&#34;manifests/*.toml&#34;))&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;     for manifest in asset_manifests:&lt;br/&gt;         with open(manifest, &#34;rb&#34;) as f:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            assets.update(tomllib.load(f))&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            manifest_data = tomllib.load(f)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        for uid, asset_data in manifest_data.items():&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            try:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                processed_data = preprocess_asset_metadata(uid, asset_data, manifest)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                processed_data[&#34;filepath&#34;] = ASSET_DIR / processed_data[&#34;filepath&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                assets[uid] = DocumentMetadata(**processed_data)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            except Exception as e:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                print(f&#34;Error processing asset {uid} from {manifest}: {str(e)}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                continue&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;     return assets&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-176,7 +276,7&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def get_files() -&amp;gt; List[Path]:&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     return [f for f in NOTES_DIR.glob(&#34;**/*.md&#34;) if &#34;available = &#34; in f.read_text()]&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def extract_external_links(text: str) -&amp;gt; List:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;def extract_external_links(text: str, site) -&amp;gt; List:&lt;/span&gt;&lt;br/&gt;     url_pattern = r&#34;(https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+(?:/[^)\s]*)?)&#34;&lt;br/&gt;     matches = re.findall(url_pattern, text)&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-186,18 +286,21&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def extract_external_links(text: str) -&amp;gt; List:&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;         parsed_url = urlparse(url)&lt;br/&gt;         if parsed_url.netloc.lower() != &#34;silasjelley.com&#34;:&lt;br/&gt;             external_links.add(url)&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            site.links[&#34;external&#34;].add(url)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    return list(external_links)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    return sorted(external_links)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; async def process_document(&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    filename: Path, site: SiteMetadata&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;) -&amp;gt; Tuple[str, Dict[str, Any]]:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    with open(filename, &#34;rb&#34;) as f:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    filepath: Path, site: SiteMetadata&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;) -&amp;gt; Tuple[str, DocumentMetadata]:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    &#34;&#34;&#34;Process a document file and return its UID and metadata.&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    with open(filepath, &#34;rb&#34;) as f:&lt;/span&gt;&lt;br/&gt;         try:&lt;br/&gt;             parsed_toml = tomllib.load(f)&lt;br/&gt;         except:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            print(filename)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            print(filepath)&lt;/span&gt;&lt;br/&gt;             import sys&lt;br/&gt;&lt;br/&gt;             sys.exit(1)&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-205,52 +308,48&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;async def process_document(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     # The UID is now the top-level table name&lt;br/&gt;     uid = parsed_toml[&#34;uid&#34;]&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    # Preprocess metadata (assuming this function exists and works with the new format)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    document = preprocess_metadata(filename, parsed_toml)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    # Process metadata into DocumentMetadata instance&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    document = preprocess_metadata(filepath, parsed_toml)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;     # Calculate and update word counts&lt;br/&gt;     try:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        document[&#34;words&#34;] = len(document[&#34;content&#34;][&#34;plain&#34;].split())&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        document.words = len(document.content.get(&#34;plain&#34;, &#34;&#34;).split())&lt;/span&gt;&lt;br/&gt;     except:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        document[&#34;words&#34;] = 0&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        document.words = 0&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    if document.get(&#34;status&#34;, &#34;&#34;) == &#34;draft&#34;:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        site.words[&#34;drafts&#34;] += document[&#34;words&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    if document.status == &#34;draft&#34;:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        site.words[&#34;drafts&#34;] += document.words&lt;/span&gt;&lt;br/&gt;     else:&lt;br/&gt;         try:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            document[&#34;source&#34;][&#34;words&#34;] = len(document[&#34;source&#34;][&#34;text&#34;].split())&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            site.words[&#34;references&#34;] += document[&#34;source&#34;][&#34;words&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            source_words = len(document.source.get(&#34;text&#34;, &#34;&#34;).split())&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            site.words[&#34;references&#34;] += source_words&lt;/span&gt;&lt;br/&gt;         except KeyError:&lt;br/&gt;             pass&lt;br/&gt;         try:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            site.words[&#34;self&#34;] += document[&#34;words&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            site.words[&#34;self&#34;] += document.words&lt;/span&gt;&lt;br/&gt;         except:&lt;br/&gt;             pass&lt;br/&gt;&lt;br/&gt;     # Extract external links from the plain text content&lt;br/&gt;     try:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        plain_text = document.get(&#34;content&#34;, {}).get(&#34;plain&#34;, &#34;&#34;) + &#34; &#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        plain_text += document.get(&#34;source&#34;, {}).get(&#34;url&#34;, &#34;&#34;) + &#34; &#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        plain_text += document.get(&#34;via&#34;, {}).get(&#34;url&#34;, &#34;&#34;) + &#34; &#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        external_links = extract_external_links(plain_text)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        plain_text = (&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            document.content.get(&#34;plain&#34;, &#34;&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            + &#34; &#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            + document.source.get(&#34;url&#34;, &#34;&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            + &#34; &#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            + document.via.get(&#34;url&#34;, &#34;&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        )&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        # Store the external links in document[&#34;links&#34;][&#34;external&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        document[&#34;links&#34;] = {&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;internal&#34;: set(),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;external&#34;: set(),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;backlinks&#34;: set(),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        document[&#34;links&#34;][&#34;external&#34;].update(external_links)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        site.links[&#34;external&#34;].update(external_links)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        external_links = extract_external_links(plain_text, site)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        document.links[&#34;external&#34;] = external_links&lt;/span&gt;&lt;br/&gt;     except KeyError:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        print(f&#34;KeyError while compiling external links from {document[&#39;filename&#39;]}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        print(f&#34;KeyError while compiling external links from {document.filepath}&#34;)&lt;/span&gt;&lt;br/&gt;         pass&lt;br/&gt;&lt;br/&gt;     return uid, document&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;async def ingest_documents(site: SiteMetadata) -&amp;gt; Dict[str, Dict[str, Any]]:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;async def ingest_documents(site: SiteMetadata) -&amp;gt; Dict[str, Any]:&lt;/span&gt;&lt;br/&gt;     logger.info(&#34;Ingesting files&#34;)&lt;br/&gt;     file_list = get_files()&lt;br/&gt;     documents = {}&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-258,11 +357,9&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;async def ingest_documents(site: SiteMetadata) -&amp;gt; Dict[str, Dict[str, Any]]:&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     slug_to_uid_lookup = {}&lt;br/&gt;     site_primaries = set()&lt;br/&gt;     site_secondaries = set()&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    site_series = set()&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    tags = set()&lt;/span&gt;&lt;br/&gt;     uuid_collision_lookup = []&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    tasks = [process_document(filename, site) for filename in file_list]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    tasks = [process_document(filepath, site) for filepath in file_list]&lt;/span&gt;&lt;br/&gt;     results = await asyncio.gather(*tasks)&lt;br/&gt;     site.words[&#34;total&#34;] = (&lt;br/&gt;         site.words[&#34;drafts&#34;] + site.words[&#34;references&#34;] + site.words[&#34;self&#34;]&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-270,13 +367,11&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;async def ingest_documents(site: SiteMetadata) -&amp;gt; Dict[str, Dict[str, Any]]:&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;&lt;br/&gt;     for uid, doc in results:&lt;br/&gt;         documents[uid] = doc&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        slug_to_title_lookup[doc[&#34;slug&#34;]] = doc[&#34;title&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        slug_to_uid_lookup[doc[&#34;slug&#34;]] = uid&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        site_primaries.add(doc[&#34;primary&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        site_secondaries.add(doc[&#34;secondary&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        if &#34;series&#34; in doc:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            site_series.add(doc[&#34;series&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        tags.update(doc.get(&#34;tags&#34;) or [])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        slug_to_title_lookup[doc.slug] = doc.title&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        slug_to_uid_lookup[doc.slug] = uid&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        site_primaries.add(doc.primary)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        site_secondaries.add(doc.secondary)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        site.tags += doc.tags&lt;/span&gt;&lt;br/&gt;         uuid_collision_lookup.append(uid)&lt;br/&gt;&lt;br/&gt;     site.slug_to_uid_lookup = slug_to_uid_lookup&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-284,7 +379,6&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;async def ingest_documents(site: SiteMetadata) -&amp;gt; Dict[str, Dict[str, Any]]:&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     check_uuid_collisions(uuid_collision_lookup)&lt;br/&gt;     site.primaries = list(site_primaries)&lt;br/&gt;     site.secondaries = list(site_secondaries)&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    site.tags = list(tags)&lt;/span&gt;&lt;br/&gt;     site.pagecount = len(documents)&lt;br/&gt;&lt;br/&gt;     logger.info(f&#34;Ingested {site.pagecount} files&#34;)&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-373,14 +467,14&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def process_image_parallel(input_data: Tuple[Path, Path, int, str]) -&amp;gt; None:&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def process_assets(&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    assets: Dict[str, Dict[str, Any]], asset_dir: Path, output_dir: Path&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    assets: Dict[str, DocumentMetadata], asset_dir: Path, output_dir: Path&lt;/span&gt;&lt;br/&gt; ) -&amp;gt; None:&lt;br/&gt;     logger.info(&#34;Processing assets&#34;)&lt;br/&gt;     manifest_images = []&lt;br/&gt;&lt;br/&gt;     for asset_identifier, asset_metadata in assets.items():&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        source_path = Path(asset_metadata[&#34;filepath&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        output_path = output_dir / asset_metadata[&#34;slug&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        source_path = Path(asset_metadata.filepath)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        output_path = output_dir / asset_metadata.slug&lt;/span&gt;&lt;br/&gt;         os.makedirs(output_path.parent, exist_ok=True)&lt;br/&gt;&lt;br/&gt;         if not source_path.exists():&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-422,74 +516,52&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def _parse_date(date_str: str) -&amp;gt; datetime.datetime:&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;         return datetime.datetime.strptime(date_str, &#34;%Y-%m-%d&#34;).replace(tzinfo=None)&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def preprocess_metadata(filename: Path, metadata: Dict[str, Any]) -&amp;gt; Dict[str, Any]:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    &#34;&#34;&#34;Preprocesses metadata for a document, ensuring required fields exist and formatting data.&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    metadata[&#34;filename&#34;] = filename&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    # Validate required fields&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    required_fields = [&#34;uid&#34;, &#34;slug&#34;, &#34;available&#34;, &#34;created&#34;, &#34;primary&#34;, &#34;secondary&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    missing_fields = [field for field in required_fields if field not in metadata]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    if missing_fields:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        raise ValueError(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            f&#34;[ERROR] Document missing {&#39;, &#39;.join(missing_fields)}\n  {filename}&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    # Set default values&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    metadata.setdefault(&#34;updated&#34;, metadata[&#34;created&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;def preprocess_metadata(filepath: Path, metadata: Dict[str, Any]) -&amp;gt; DocumentMetadata:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    &#34;&#34;&#34;Preprocesses metadata for a document and converts it to a DocumentMetadata instance.&#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    # Create a working copy to avoid modifying the input&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    processed = metadata.copy()&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;     # Parse date fields&lt;br/&gt;     for date_field in [&#34;created&#34;, &#34;updated&#34;, &#34;available&#34;]:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        if date_field in metadata:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            if isinstance(metadata[date_field], str):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                metadata[date_field] = _parse_date(metadata[date_field])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            elif isinstance(metadata[date_field], datetime.datetime):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                metadata[date_field] = metadata[date_field].replace(tzinfo=None)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    # Process source information&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    if &#34;source&#34; in metadata:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        if &#34;via&#34; in metadata:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            metadata.update(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                process_source_information(metadata[&#34;source&#34;], metadata[&#34;via&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        else:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            metadata.update(process_source_information(metadata[&#34;source&#34;], {}))&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        if isinstance(processed.get(date_field), str):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            processed[date_field] = _parse_date(processed[date_field])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        elif isinstance(processed.get(date_field), datetime.datetime):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            processed[date_field] = processed[date_field].replace(tzinfo=None)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    # Set default updated time if not provided&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    processed.setdefault(&#34;updated&#34;, processed.get(&#34;created&#34;))&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    # Process source information if present&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    if &#34;source&#34; in processed:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        processed[&#34;attribution&#34;] = process_source_information(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            processed[&#34;source&#34;], processed.get(&#34;via&#34;, {})&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    else:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        processed[&#34;attribution&#34;] = {}&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        processed[&#34;source&#34;] = {}&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    if &#34;via&#34; not in processed:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        processed[&#34;via&#34;] = {}&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;     # Determine title&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    metadata[&#34;title&#34;] = (&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        metadata.get(&#34;title&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        or metadata.get(&#34;attribution&#34;, {}).get(&#34;plain&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        or metadata[&#34;created&#34;].strftime(&#34;%B %e, %Y %-I.%M%p&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    processed[&#34;title&#34;] = (&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        processed.get(&#34;title&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        or processed.get(&#34;attribution&#34;, {}).get(&#34;plain&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        or processed[&#34;created&#34;].strftime(&#34;%B %e, %Y %-I.%M%p&#34;)&lt;/span&gt;&lt;br/&gt;     )&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    # Ensure title and slug are strings&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    metadata[&#34;title&#34;] = str(metadata[&#34;title&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    if metadata.get(&#34;status&#34;) == &#34;draft&#34;:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        metadata[&#34;slug&#34;] = &#34;drafts/&#34; + metadata[&#34;uid&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        metadata[&#34;parent&#34;] = &#34;a26221ae-c742-4b73-8dc6-7f8807456a1b&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    else:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        metadata[&#34;slug&#34;] = str(metadata[&#34;slug&#34;])&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    # Initialize interlinks, backlinks, and tags&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    metadata[&#34;interlinks&#34;] = set()&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    metadata[&#34;backlinks&#34;] = set()&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    metadata[&#34;tags&#34;] = metadata.get(&#34;tags&#34;) or []&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    # Handle draft status&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    if processed.get(&#34;status&#34;) == &#34;draft&#34;:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        processed[&#34;slug&#34;] = f&#34;drafts/{processed[&#39;uid&#39;]}&#34;&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    # Strip whitespace from plain content&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    try:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        metadata[&#34;content&#34;][&#34;plain&#34;] = metadata[&#34;content&#34;][&#34;plain&#34;].strip()&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    except KeyError:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        pass&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    try:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        metadata[&#34;source&#34;][&#34;text&#34;] = metadata[&#34;source&#34;][&#34;text&#34;].strip()&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    except KeyError:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        pass&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    # Add filepath as it&#39;s required but comes from function parameter&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    processed[&#34;filepath&#34;] = filepath&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    return metadata&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    # Create and return DocumentMetadata instance&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    return DocumentMetadata(**processed)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def process_source_information(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    source: Dict[str, Any], via&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;) -&amp;gt; Dict[str, Dict[str, str]]:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;def process_source_information(source: Dict[str, Any], via) -&amp;gt; Dict[str, str]:&lt;/span&gt;&lt;br/&gt;     creator = source.get(&#34;creator&#34;) or source.get(&#34;director&#34;)&lt;br/&gt;     title = source.get(&#34;title&#34;) or (&lt;br/&gt;         &#34; ♫ &#34; + str(source.get(&#34;track&#34;))&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-587,13 +659,11&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def process_source_information(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;             partsvia = f&#34; (#link(&#34;{escape_url(via[&#34;url&#34;]&#34;)[via]}))&#34;&lt;br/&gt;&lt;br/&gt;     return {&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        &#34;attribution&#34;: {&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;plain&#34;: f&#34;{speaker}{&#39;, &#39;.join(partsplain + partsshared)}&#34;,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;djot&#34;: f&#34;{speaker}{&#39;, &#39;.join(partsdjot + partsshared)}&#34;,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;html&#34;: format_rich_attribution(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                &#34; — &#34; + f&#34;{speaker}{&#39;, &#39;.join(partshtml + partsshared) + partsvia}&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            ),&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        }&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        &#34;plain&#34;: f&#34;{speaker}{&#39;, &#39;.join(partsplain + partsshared)}&#34;,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        &#34;djot&#34;: f&#34;{speaker}{&#39;, &#39;.join(partsdjot + partsshared)}&#34;,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        &#34;html&#34;: format_rich_attribution(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            &#34; — &#34; + f&#34;{speaker}{&#39;, &#39;.join(partshtml + partsshared) + partsvia}&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        ),&lt;/span&gt;&lt;br/&gt;     }&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-619,8 +689,8&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def check_uuid_collisions(uuid_list):&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def insert_substitutions(&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    documents: Dict[str, Dict[str, Any]],&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    assets: Dict[str, Dict[str, Any]],&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    documents: Dict[str, DocumentMetadata],&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    assets: Dict[str, DocumentMetadata],&lt;/span&gt;&lt;br/&gt;     site: SiteMetadata,&lt;br/&gt; ) -&amp;gt; None:&lt;br/&gt;     logger.info(&#34;Performing substitutions&#34;)&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-635,53 +705,56&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def insert_substitutions(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     merged_data = {*documents, *assets}&lt;br/&gt;&lt;br/&gt;     for key, page in documents.items():&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        logger.debug(f&#34;  {key}, {page[&#39;title&#39;][:40]}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        logger.debug(f&#34;  {key}, {page.title[:40]}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        text = page.get(&#34;content&#34;, {}).get(&#34;plain&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        text = page.content.get(&#34;plain&#34;)&lt;/span&gt;&lt;br/&gt;         if text:&lt;br/&gt;             text = replace_import_references(text, REF_IMPT_RE, merged_data, key, page)&lt;br/&gt;             text = replace_cite_references(text, REF_CITE_RE, merged_data)&lt;br/&gt;             text = replace_title_references(text, REF_TITLE_RE, merged_data)&lt;br/&gt;             text = replace_slug_references(text, REF_SLUG_RE, merged_data)&lt;br/&gt;             text = process_reference_links(text, REF_LINK_RE, merged_data, key)&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            page[&#34;content&#34;][&#34;plain&#34;] = text.strip()&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            page.content[&#34;plain&#34;] = text.strip()&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def replace_slug_references(&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    text: str, regex: re.Pattern, merged_data: Dict[str, Dict[str, Any]]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    text: str, regex: re.Pattern, merged_data: Dict[str, DocumentMetadata]&lt;/span&gt;&lt;br/&gt; ) -&amp;gt; str:&lt;br/&gt;     for match in regex.finditer(text):&lt;br/&gt;         ref_type, ref_short_id = match.groups()&lt;br/&gt;         full_match = match.group(0)&lt;br/&gt;         ref_id = next((k for k in merged_data if k.startswith(ref_short_id)), None)&lt;br/&gt;         if ref_id:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            replacement = f&#34;/{merged_data[ref_id][&#39;slug&#39;]}&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            try:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                replacement = f&#34;/{merged_data[ref_id].slug}&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            except AttributeError:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                replacement = f&#34;/{merged_data[ref_id].slug}&#34;&lt;/span&gt;&lt;br/&gt;             text = text.replace(full_match, replacement)&lt;br/&gt;     return text&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def replace_title_references(&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    text: str, regex: re.Pattern, merged_data: Dict[str, Dict[str, Any]]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    text: str, regex: re.Pattern, merged_data: Dict[str, DocumentMetadata]&lt;/span&gt;&lt;br/&gt; ) -&amp;gt; str:&lt;br/&gt;     for match in regex.finditer(text):&lt;br/&gt;         opening, ref_type, ref_short_id, comment, closing = match.groups()&lt;br/&gt;         full_match = match.group(0)&lt;br/&gt;         ref_id = next((k for k in merged_data if k.startswith(ref_short_id)), None)&lt;br/&gt;         if ref_id:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            replacement = merged_data[ref_id][&#34;title&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            replacement = merged_data[ref_id].title&lt;/span&gt;&lt;br/&gt;             text = text.replace(full_match, replacement)&lt;br/&gt;     return text&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def replace_cite_references(&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    text: str, regex: re.Pattern, merged_data: Dict[str, Dict[str, Any]]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    text: str, regex: re.Pattern, merged_data: Dict[str, DocumentMetadata]&lt;/span&gt;&lt;br/&gt; ) -&amp;gt; str:&lt;br/&gt;     for match in regex.finditer(text):&lt;br/&gt;         opening, ref_type, ref_short_id, comment, closing = match.groups()&lt;br/&gt;         full_match = match.group(0)&lt;br/&gt;         ref_id = next((k for k in merged_data if k.startswith(ref_short_id)), None)&lt;br/&gt;         if ref_id:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            replacement = f&#34;[{merged_data[ref_id][&#34;attribution&#34;][&#34;djot&#34;]}] (/{merged_data[ref_id][&#34;slug&#34;]})&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            replacement = f&#34;[{merged_data[ref_id].attribution[&#34;djot&#34;]}] (/{merged_data[ref_id].slug})&#34;&lt;/span&gt;&lt;br/&gt;             text = text.replace(full_match, replacement)&lt;br/&gt;     return text&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-689,29 +762,32&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def replace_cite_references(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt; def replace_import_references(&lt;br/&gt;     text: str,&lt;br/&gt;     regex: re.Pattern,&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    merged_data: Dict[str, Dict[str, Any]],&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    merged_data: Dict[str, DocumentMetadata],&lt;/span&gt;&lt;br/&gt;     key: str,&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    page: Dict,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    page: DocumentMetadata,&lt;/span&gt;&lt;br/&gt; ) -&amp;gt; str:&lt;br/&gt;     for match in regex.finditer(text):&lt;br/&gt;         opening, ref_type, ref_short_id, comment, closing = match.groups()&lt;br/&gt;         full_match = match.group(0)&lt;br/&gt;         ref_id = next((k for k in merged_data if k.startswith(ref_short_id)), None)&lt;br/&gt;         if ref_id:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            ref_text = merged_data[ref_id][&#34;content&#34;][&#34;plain&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            ref_text = merged_data[ref_id].content[&#34;plain&#34;]&lt;/span&gt;&lt;br/&gt;             if ref_type == &#34;import::&#34;:&lt;br/&gt;                 replacement = ref_text&lt;br/&gt;             elif ref_type == &#34;aside::&#34;:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                ref_title = merged_data[ref_id][&#34;title&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                ref_slug = merged_data[ref_id][&#34;slug&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                ref_location = merged_data[ref_id].get(&#34;location&#34;, &#34;&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                location_string = (&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                    &#34; ⚕ &#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                    + ref_location.get(&#34;city&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                    + &#34;, &#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                    + ref_location.get(&#34;country&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                    or &#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                ref_title = merged_data[ref_id].title&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                ref_slug = merged_data[ref_id].slug&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                ref_location = merged_data[ref_id].location&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                if ref_location:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                    location_string = (&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                        &#34; ⚕ &#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                        + ref_location.get(&#34;city&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                        + &#34;, &#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                        + ref_location.get(&#34;country&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                        or &#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                    )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                else:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                    location_string = &#34;&#34;&lt;/span&gt;&lt;br/&gt;                 replacement = (&lt;br/&gt;                     f&#39;{{.aside}}\n{&#39;:&#39;*78}\n&#39;&lt;br/&gt;                     f&#39;{ref_text}\\\n&#39;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-722,18 +798,22&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def replace_import_references(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;                 )&lt;br/&gt;             else:&lt;br/&gt;                 raise ValueError(f&#34;Unrecognised reference type: {ref_type}&#34;)&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            if not page.get(&#34;status&#34;, &#34;&#34;) == &#34;draft&#34;:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                merged_data[ref_id][&#34;backlinks&#34;].add(key)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            if not page.status == &#34;draft&#34;:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                merged_data[ref_id].links[&#34;backlinks&#34;].add(key)&lt;/span&gt;&lt;br/&gt;             text = text.replace(full_match, replacement)&lt;br/&gt;     return text&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def process_reference_links(&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    text: str, regex: re.Pattern, merged_data: Dict[str, Dict[str, Any]], key: str&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    text: str, regex: re.Pattern, merged_data: Dict[str, DocumentMetadata], key: str&lt;/span&gt;&lt;br/&gt; ) -&amp;gt; str:&lt;br/&gt;     for ref_text_match, _, ref_type, ref_short_id in regex.findall(text):&lt;br/&gt;         match = f&#34;#link(&#34;{ref_type}{ref_short_id}&#34;)[{ref_text_match}]&#34;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        ref_id = next((k for k in merged_data if k.startswith(ref_short_id)), None)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        ref_id = next(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            (k for k in merged_data.keys() if k.startswith(ref_short_id)), None&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        if ref_id is None:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            print(f&#34;No match found for {ref_short_id}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;         if not ref_id:&lt;br/&gt;             raise ValueError(&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-746,16 +826,16&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def process_reference_links(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;             )&lt;br/&gt;&lt;br/&gt;         ref_text = get_reference_text(ref_text_match, merged_data[ref_id])&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        ref_slug = f&#34;/{merged_data[ref_id][&#39;slug&#39;]}&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        ref_slug = f&#34;/{merged_data[ref_id].slug}&#34;&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;         if ref_type == &#34;link::&#34;:&lt;br/&gt;             try:&lt;br/&gt;                 # Double quotes within a page title are escaped so that they don&#39;t break the HTML &#39;title&#39; element&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                ref_title = f&#34;{merged_data[ref_id][&#39;title&#39;]} | {merged_data[ref_id][&#39;created&#39;].strftime(&#39;%B %Y&#39;)}&#34;.replace(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                ref_title = f&#34;{merged_data[ref_id].title} | {merged_data[ref_id].created.strftime(&#39;%B %Y&#39;)}&#34;.replace(&lt;/span&gt;&lt;br/&gt;                     &#39;&#34;&#39;, &#39;\\&#34;&#39;&lt;br/&gt;                 )&lt;br/&gt;             except KeyError:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;                ref_title = merged_data[ref_id][&#34;title&#34;].replace(&#39;&#34;&#39;, &#39;\\&#34;&#39;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;                ref_title = merged_data[ref_id].title.replace(&#39;&#34;&#39;, &#39;\\&#34;&#39;)&lt;/span&gt;&lt;br/&gt;             replacement = f&#39;#link(&#34;{ref_slug}&#34;)[{ref_text}]{{title=&#34;{ref_title}&#34;}}&#39;&lt;br/&gt;         elif ref_type == &#34;img::&#34;:&lt;br/&gt;             replacement = f&#34;#link(&#34;{ref_slug}&#34;)[{ref_text}]&#34;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-778,18 +858,18&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def process_reference_links(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     return text&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def get_reference_text(ref_text_match: str, ref_data: Dict) -&amp;gt; str:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;def get_reference_text(ref_text_match: str, ref_data) -&amp;gt; str:&lt;/span&gt;&lt;br/&gt;     if ref_text_match.startswith(&#34;::&#34;) or ref_text_match == &#34;&#34;:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        return ref_data.get(&#34;title&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        return ref_data.title&lt;/span&gt;&lt;br/&gt;     return ref_text_match&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def create_quote_replacement(ref_data: Dict, ref_slug: str) -&amp;gt; str:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    ref_src = ref_data[&#34;attribution&#34;][&#34;djot&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;def create_quote_replacement(ref_data: DocumentMetadata, ref_slug: str) -&amp;gt; str:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    ref_src = ref_data.attribution[&#34;djot&#34;]&lt;/span&gt;&lt;br/&gt;     try:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        ref_text = ref_data[&#34;source&#34;][&#34;text&#34;].replace(&#34;\n\n&#34;, &#34;\n&amp;gt; \n&amp;gt; &#34;).strip()&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        ref_text = ref_data.source[&#34;text&#34;].replace(&#34;\n\n&#34;, &#34;\n&amp;gt; \n&amp;gt; &#34;).strip()&lt;/span&gt;&lt;br/&gt;     except:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        print(f&#34;Error creating quote replacement: {ref_data[&#34;uid&#34;]}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        print(f&#34;Error creating quote replacement: {ref_data.uid}&#34;)&lt;/span&gt;&lt;br/&gt;         import sys&lt;br/&gt;&lt;br/&gt;         sys.exit()&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-808,10 +888,10&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;Your browser does not support the video tag.&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt; def generate_html(documents):&lt;br/&gt;     logger.info(&#34;Generating HTML&#34;)&lt;br/&gt;     for key, page in documents.items():&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        if page.get(&#34;content&#34;, {}).get(&#34;plain&#34;):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            page[&#34;content&#34;][&#34;html&#34;] = run_jotdown(page[&#34;content&#34;][&#34;plain&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        if page.get(&#34;source&#34;, {}).get(&#34;text&#34;):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            page[&#34;source&#34;][&#34;html&#34;] = run_jotdown(page[&#34;source&#34;][&#34;text&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        if page.content.get(&#34;plain&#34;):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            page.content[&#34;html&#34;] = run_jotdown(page.content[&#34;plain&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        if page.source.get(&#34;text&#34;):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            page.source[&#34;html&#34;] = run_jotdown(page.source[&#34;text&#34;])&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; class LedgerLexer(RegexLexer):&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-841,7 +921,7&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;class LedgerLexer(RegexLexer):&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     }&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def highlight_code(code: str, language: str = None) -&amp;gt; str:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;def highlight_code(code: str, language: str) -&amp;gt; str:&lt;/span&gt;&lt;br/&gt;     &#34;&#34;&#34;&lt;br/&gt;     Highlight code using Pygments with specified or guessed language.&lt;br/&gt;     &#34;&#34;&#34;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-921,20 +1001,17&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def build_backlinks(documents, site):&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     FOOTNOTE_LINK_URL_RE = re.compile(r&#34;\[.+?\]:\s\/(.*)&#34;, re.DOTALL)&lt;br/&gt;     interlink_count = 0&lt;br/&gt;     for key, page in documents.items():&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        if (&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            &#34;nobacklinks&#34; in page.get(&#34;options&#34;, &#34;&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            or page.get(&#34;status&#34;, &#34;&#34;) == &#34;draft&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        ):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        if &#34;nobacklinks&#34; in page.options or page.status == &#34;draft&#34;:&lt;/span&gt;&lt;br/&gt;             continue&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        logger.debug(page[&#34;filename&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        logger.debug(page.filepath)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        text = page.get(&#34;content&#34;, {}).get(&#34;plain&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        text = page.content.get(&#34;plain&#34;)&lt;/span&gt;&lt;br/&gt;         # Skip if no main content&lt;br/&gt;         if not text:&lt;br/&gt;             continue&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        interlinks = set(documents[key][&#34;interlinks&#34;])&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        interlinks = set(documents[key].links[&#34;internal&#34;])&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;         combined_refs = INLINE_LINK_RE.findall(text) + FOOTNOTE_LINK_URL_RE.findall(&lt;br/&gt;             text&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-950,11 +1027,28&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def build_backlinks(documents, site):&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;                     continue&lt;br/&gt;                 logger.warning(f&#34;\nKeyError in {page[&#39;title&#39;]} ({key}): {slug}&#34;)&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        documents[key][&#34;interlinks&#34;] = list(interlinks)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        documents[key].links[&#34;internal&#34;] = interlinks&lt;/span&gt;&lt;br/&gt;         for interlink_key in interlinks:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            documents[interlink_key][&#34;backlinks&#34;].add(key)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            documents[interlink_key].links[&#34;backlinks&#34;].add(key)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    return interlink_count&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    for key, page in documents.items():&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        # Sort interlinks based on published dates&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        documents[key].links[&#34;internal&#34;] = sorted(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            documents[key].links[&#34;internal&#34;],&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            key=lambda x: documents[x].available,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            reverse=True,  # Most recent first&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        # Sort backlinks based on published dates&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        documents[key].links[&#34;backlinks&#34;] = sorted(&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            documents[key].links[&#34;backlinks&#34;],&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            key=lambda x: documents[x].available,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            reverse=True,  # Most recent first&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        )&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    &#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    TODO: REMOVE SITE.BACKLINKS in favour a &#39;stats&#39; or &#39;count&#39; (templates will need updating&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    &#34;&#34;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    site.backlinks += interlink_count&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def should_ignore_slug(slug):&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-966,7 +1060,7&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def should_ignore_slug(slug):&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def build_collections(&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    documents: Dict[str, Dict[str, Any]], site: SiteMetadata&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    documents: Dict[str, DocumentMetadata], site: SiteMetadata&lt;/span&gt;&lt;br/&gt; ) -&amp;gt; Tuple[Dict[str, List[Dict[str, Any]]], List[Dict[str, Any]]]:&lt;br/&gt;     collections = {&lt;br/&gt;         primary: []&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-978,24 +1072,24&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def build_collections(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     sitemap = []&lt;br/&gt;&lt;br/&gt;     for key, page in sorted(&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        documents.items(), key=lambda k_v: k_v[1][&#34;available&#34;], reverse=True&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        documents.items(), key=lambda k_v: k_v[1].available, reverse=True&lt;/span&gt;&lt;br/&gt;     ):&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        if page.get(&#34;status&#34;, &#34;&#34;) == &#34;draft&#34;:&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        if page.status == &#34;draft&#34;:&lt;/span&gt;&lt;br/&gt;             collections[&#34;cd68b918-ac5f-4d6c-abb5-a55a0318846b&#34;].append(page)&lt;br/&gt;             continue&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        elif &#34;nofeed&#34; in page.get(&#34;options&#34;, []):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        elif &#34;nofeed&#34; in page.options:&lt;/span&gt;&lt;br/&gt;             sitemap.append(page)&lt;br/&gt;             continue&lt;br/&gt;         else:&lt;br/&gt;             sitemap.append(page)&lt;br/&gt;             collections[&#34;everything&#34;].append(page)&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            collections[page[&#34;primary&#34;]].append(page)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            collections[page[&#34;secondary&#34;]].append(page)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            collections[page.primary].append(page)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            collections[page.secondary].append(page)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            for tag in page.get(&#34;tags&#34;, []):&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            for tag in page.tags:&lt;/span&gt;&lt;br/&gt;                 collections[tag].append(page)&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            if page[&#34;secondary&#34;] in [&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            if page.secondary in [&lt;/span&gt;&lt;br/&gt;                 &#34;essays&#34;,&lt;br/&gt;                 &#34;wandering&#34;,&lt;br/&gt;                 &#34;rambling&#34;,&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-1008,8 +1102,8&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def build_collections(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def output_html(&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    assets: Dict[str, Dict[str, Any]],&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    documents: Dict[str, Dict[str, Any]],&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    assets: Dict[str, DocumentMetadata],&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    documents: Dict[str, DocumentMetadata],&lt;/span&gt;&lt;br/&gt;     collections: Dict[str, List[Dict[str, Any]]],&lt;br/&gt;     site: SiteMetadata,&lt;br/&gt;     env: Environment,&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-1018,7 +1112,7&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def output_html(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     logger.info(&#34;Generating Hypertext&#34;)&lt;br/&gt;&lt;br/&gt;     for key, page in documents.items():&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        template_file = page.get(&#34;layout&#34;, TEMPLATE_DEFAULT)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        template_file = page.layout&lt;/span&gt;&lt;br/&gt;         template = env.get_template(template_file)&lt;br/&gt;&lt;br/&gt;         collection = build_page_collection(page, collections)&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-1028,28 +1122,29&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;def output_html(&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;             assets=assets,&lt;br/&gt;             collections=collections,&lt;br/&gt;             collection=collection,&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            page=page,&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            page=asdict(page),&lt;/span&gt;&lt;br/&gt;             site=site,&lt;br/&gt;         )&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        output_path = output_dir / page[&#34;slug&#34;] / &#34;index.html&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        output_path = output_dir / page.slug / &#34;index.html&#34;&lt;/span&gt;&lt;br/&gt;         output_path.parent.mkdir(parents=True, exist_ok=True)&lt;br/&gt;&lt;br/&gt;         with open(output_path, &#34;w&#34;) as f:&lt;br/&gt;             f.write(output)&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        logger.debug(f&#34;  {page[&#39;filename&#39;]} &amp;gt;&amp;gt; {output_path}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        logger.debug(f&#34;  {page.filepath} &amp;gt;&amp;gt; {output_path}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; def build_page_collection(page, collections):&lt;br/&gt;     try:&lt;br/&gt;         collection = [&lt;br/&gt;             item&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;            for include in page[&#34;collection&#34;][&#34;include&#34;]&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;            for include in page.collection[&#34;include&#34;]&lt;/span&gt;&lt;br/&gt;             for item in collections[include]&lt;br/&gt;         ]&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;        return sorted(collection, key=lambda x: x[&#34;available&#34;], reverse=True)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        return sorted(collection, key=lambda x: x.available, reverse=True)&lt;/span&gt;&lt;br/&gt;     except KeyError:&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;        print(f&#34;Failed collection for {page.filepath}&#34;)&lt;/span&gt;&lt;br/&gt;         return []&lt;br/&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-1126,8 +1221,7&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;async def main():&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     generate_html(documents)&lt;br/&gt;&lt;br/&gt;     # Build backlinks and collections&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    interlink_count = build_backlinks(documents, site)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    site.backlinks += interlink_count&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    build_backlinks(documents, site)&lt;/span&gt;&lt;br/&gt;     collections, sitemap = build_collections(documents, site)&lt;br/&gt;&lt;br/&gt;     # Output HTML, feeds, and sitemap&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;-1142,7 +1236,8&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;@@&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt; &lt;/span&gt;&lt;strong&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;async def main():&lt;/span&gt;&lt;/strong&gt;&lt;br/&gt;     logger.info(&#34;Build complete!&#34;)&lt;br/&gt;     logger.info(f&#34;Pages: {site.pagecount}&#34;)&lt;br/&gt;     logger.info(f&#34;Words: {site.words[&#34;total&#34;]}&#34;)&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;    logger.info(f&#34;Interlinks: {interlink_count}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    logger.info(f&#34;Internal links: {site.backlinks}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;    logger.info(f&#34;External links: {len(site.links[&#34;external&#34;])}&#34;)&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;br/&gt; if __name__ == &#34;__main__&#34;:&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/details&gt;
9:54am on December 16, 2024 from Vancouver, British Columbia, Canada&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Add dataclasses to ratchet up the strictness”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>CSS for responsive image galleries at all viewport widths</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2024/12/15/172527" />
  <id>tag:silasjelley.com,2020-08-20:875afe1a-3179-41ff-88df-8344816e6722</id>
  <published>2024-12-15T17:25:27Z</published>
  <updated>2024-12-15T17:25:27Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;CSS is pure mayhem, but whatever, it’s what we’ve got. For a long time I’ve had reasonably good responsive image galleries. Really the last remaining roadblocks has been with those galleries where I’ve decided I need to vary the width of images &lt;em&gt;for aesthetic reasons&lt;/em&gt;…&lt;/p&gt;
&lt;p&gt;Well, today I improved the style behaviour in half of those cases.&lt;/p&gt;
&lt;p&gt;The only caveat to this set of styles is that you have to be careful when using the &lt;code&gt;wide-first&lt;/code&gt; or &lt;code&gt;wide-last&lt;/code&gt; classes as they can leave an orphan image out in space if you don’t consider how many (odd or even) images are in the gallery or if they are of very different aspect ratios. But besides those I’ve found this to be pretty rock solid, if a little verbose.&lt;/p&gt;
&lt;p&gt;Here’s the relevant cascade,&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;css&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;css&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;css&#34;&gt;root {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;body-width&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;820&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;px&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;gallery-spacing&lt;/span&gt;: clamp(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;px&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;4&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;px&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;rem&lt;/span&gt;);&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;gallery-transition&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;s&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;ease-in-out&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;display&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;grid&lt;/span&gt;;&lt;br/&gt;  gap: &lt;span style=&#34;color: #4b69c6&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color: #4b69c6&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;gallery-spacing&lt;/span&gt;);&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;%&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;margin&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;rem&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;img&lt;/span&gt;,&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;picture&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;%&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;%&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;img&lt;/span&gt;,&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;picture&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;img&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;%&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;%&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;object-fit&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;cover&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;border-radius&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color: #4b69c6&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;image-radius&lt;/span&gt;);&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;transition&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;transform&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;var&lt;/span&gt;(&lt;span style=&#34;color: #4b69c6&#34;&gt;--&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;gallery-transition&lt;/span&gt;);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;img&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;hover&lt;/span&gt;,&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;picture&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;hover&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;img&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;transform&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;scale&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;01&lt;/span&gt;);&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;cursor&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;zoom-in&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #74747c&#34;&gt;/*&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Dynamic columns based on number of children &lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;*/&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-last-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)) {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-last-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;)),&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-last-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)) {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;repeat&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt;);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-last-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;3&lt;/span&gt;)),&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-last-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;)),&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;3&lt;/span&gt;)&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-last-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)) {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;repeat&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt;);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;+ 4&lt;/span&gt;)) {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;repeat&lt;/span&gt;(&lt;span style=&#34;color: #4b69c6&#34;&gt;auto-fit&lt;/span&gt;, &lt;span style=&#34;color: #4b69c6&#34;&gt;minmax&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;250&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;px&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt;));&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #74747c&#34;&gt;/*&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Layout variants &lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;*/&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;one-wide&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;!important&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;two-wide&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;repeat&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;!important&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;four-wide&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;repeat&lt;/span&gt;(&lt;span style=&#34;color: #4b69c6&#34;&gt;auto-fit&lt;/span&gt;, &lt;span style=&#34;color: #4b69c6&#34;&gt;minmax&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;185&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;px&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt;));&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;wide-first&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;first-child&lt;/span&gt;,&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;wide-last&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;last-child&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-column&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt; / &lt;span style=&#34;color: #b60157&#34;&gt;-1&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;max-height&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;80&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;vh&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;media&lt;/span&gt; (&lt;span style=&#34;color: #4b69c6&#34;&gt;max-width&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;768&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;px&lt;/span&gt;) {&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-last-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;3&lt;/span&gt;)),&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;)&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-last-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;)),&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;3&lt;/span&gt;)&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-last-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)) {&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;repeat&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt;);&lt;br/&gt;  }&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;four-wide&lt;/span&gt;,&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;+ 4&lt;/span&gt;)) {&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;repeat&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt;);&lt;br/&gt;  }&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;3&lt;/span&gt;)&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-last-child&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)) &amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;last-child&lt;/span&gt; {&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-column&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;span&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;;&lt;br/&gt;  }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;media&lt;/span&gt; (&lt;span style=&#34;color: #4b69c6&#34;&gt;max-width&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;480&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;px&lt;/span&gt;) {&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt; {&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt;;&lt;br/&gt;  }&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;four-wide&lt;/span&gt;,&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;has&lt;/span&gt;(&amp;gt; &lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;nth-child&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;+ 4&lt;/span&gt;)) {&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;grid-template-columns&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;repeat&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;fr&lt;/span&gt;);&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #74747c&#34;&gt;/*&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Disable hover effects on touch devices &lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;*/&lt;/span&gt;&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;img&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;hover&lt;/span&gt;,&lt;br/&gt;  &lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;picture&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;hover&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;img&lt;/span&gt; {&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;transform&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;none&lt;/span&gt;;&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #74747c&#34;&gt;/*&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Full viewport width variant &lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;*/&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;full-viewport&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;width&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;vw&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;margin-left&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;calc&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;50&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;50&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;vw&lt;/span&gt;);&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;margin-right&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;calc&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;50&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;50&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;vw&lt;/span&gt;);&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;full-viewport&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;img&lt;/span&gt;,&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;full-viewport&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;picture&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;transform&lt;/span&gt;: &lt;span style=&#34;color: #4b69c6&#34;&gt;none&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;!important&lt;/span&gt;;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;border-radius&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #74747c&#34;&gt;/*&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Square aspect ratio class &lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt;*/&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;square-items&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;img&lt;/span&gt;,&lt;br/&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;gallery&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;square-items&lt;/span&gt; &amp;gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;picture&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;img&lt;/span&gt; {&lt;br/&gt;  aspect-ratio: &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;;&lt;br/&gt;}&lt;br/&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
5:25pm on December 15, 2024 from Vancouver, British Columbia, Canada&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“CSS for responsive image galleries at all viewport widths”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Distinguishing between prose and code when counting words</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2024/12/15/102756" />
  <id>tag:silasjelley.com,2020-08-20:88862f11-7c81-450f-b8b7-9aa0fd261e21</id>
  <published>2024-12-15T10:27:56Z</published>
  <updated>2024-12-15T10:27:56Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;I’m including more and larger code snippets in documents throughout the site — such as this one — and I didn’t like that these were being counted amongst the ‘wordcount’ of the site. I’ve always distinguished between my words and reference words — when you see a quote in one of my documents, that isn’t counted in my own wordcount — but now it’s time do distinguish more granularly between &lt;em&gt;my prose&lt;/em&gt; and &lt;em&gt;my code&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The first change was to the &lt;code&gt;SiteMetadata&lt;/code&gt; dataclass.&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color: #301414&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color: #301414&#34;&gt;dataclass&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;SiteMetadata&lt;/span&gt;:&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;...&lt;/span&gt;&lt;br/&gt;    words: Dict[&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;, Any] &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;field&lt;/span&gt;(&lt;br/&gt;        default_factory&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;lambda&lt;/span&gt;: {&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;drafts&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;code&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: {&lt;br/&gt;                &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;lines&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;                &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;words&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;            },&lt;br/&gt;            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;references&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;: &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,&lt;br/&gt;        }&lt;br/&gt;    )&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The same block was also added to the &lt;code&gt;DocumentMetadata&lt;/code&gt; dataclass, just without the &lt;code&gt;drafts&lt;/code&gt; key.&lt;/p&gt;
&lt;p&gt;The logic for tallying up the totals isn’t very elegant, I consider this a first draft, but it works. First the total document is summed,&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;for&lt;/span&gt; key, page &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; documents.&lt;span style=&#34;color: #4b69c6&#34;&gt;items&lt;/span&gt;():&lt;br/&gt;        prose_wordcount &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;len&lt;/span&gt;(page.content.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;plain&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;).&lt;span style=&#34;color: #4b69c6&#34;&gt;split&lt;/span&gt;())&lt;br/&gt;        references_wordcount &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;len&lt;/span&gt;(page.source.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;text&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;).&lt;span style=&#34;color: #4b69c6&#34;&gt;split&lt;/span&gt;())&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; page.status &lt;span style=&#34;color: #d73948&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;draft&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;:&lt;br/&gt;            page.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; prose_wordcount&lt;br/&gt;            site.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;drafts&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; prose_wordcount&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;else&lt;/span&gt;:&lt;br/&gt;            page.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; prose_wordcount&lt;br/&gt;            site.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; prose_wordcount&lt;br/&gt;&lt;br/&gt;            site.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;references&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; references_wordcount&lt;br/&gt;            page.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;references&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; references_wordcount&lt;br/&gt;&lt;br/&gt;        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;debug&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;  &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;key&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;, &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;page&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;title&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;40&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And then, during the syntax highlighting, the code lines are tallied up and then subtracted from the prose lines.&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;save_code_block&lt;/span&gt;(match):&lt;br/&gt;    leading_space &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; match.&lt;span style=&#34;color: #4b69c6&#34;&gt;group&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;)&lt;br/&gt;    raw_html_marker &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; match.&lt;span style=&#34;color: #4b69c6&#34;&gt;group&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;2&lt;/span&gt;)&lt;br/&gt;    language &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; match.&lt;span style=&#34;color: #4b69c6&#34;&gt;group&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;3&lt;/span&gt;)&lt;br/&gt;    code &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; match.&lt;span style=&#34;color: #4b69c6&#34;&gt;group&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;4&lt;/span&gt;).&lt;span style=&#34;color: #4b69c6&#34;&gt;rstrip&lt;/span&gt;()&lt;br/&gt;    trailing_space &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; match.&lt;span style=&#34;color: #4b69c6&#34;&gt;group&lt;/span&gt;(&lt;span style=&#34;color: #b60157&#34;&gt;5&lt;/span&gt;)&lt;br/&gt;    code_words &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;len&lt;/span&gt;(code.&lt;span style=&#34;color: #4b69c6&#34;&gt;split&lt;/span&gt;())&lt;br/&gt;    code_lines &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;len&lt;/span&gt;(code.&lt;span style=&#34;color: #4b69c6&#34;&gt;splitlines&lt;/span&gt;())&lt;br/&gt;    page.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;code&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;][&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;lines&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; code_lines&lt;br/&gt;    page.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;code&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;][&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;words&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; code_words&lt;br/&gt;    site.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;code&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;][&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;lines&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; code_lines&lt;br/&gt;    site.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;code&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;][&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;words&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; code_words&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Remove the wordcount of codeblocks from the prose wordcounts&lt;/span&gt;&lt;br/&gt;    page.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;-=&lt;/span&gt; code_words&lt;br/&gt;    site.words[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;self&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;-=&lt;/span&gt; code_words&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The result is exactly what I wanted, but the method isn’t super elegant.&lt;/p&gt;
&lt;p&gt;Including this post, the current counts are as follows,&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;console&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;console&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;console&#34;&gt;222,852 words&lt;br/&gt;&lt;br/&gt;    136,784 of my own published words&lt;br/&gt;     21,441 words of unpublished drafts&lt;br/&gt;     64,627 words of quotes and reference material&lt;br/&gt;      6,659 lines of code&lt;br/&gt;     24,182 words of code&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
10:27am on December 15, 2024 from Vancouver, British Columbia, Canada&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Distinguishing between prose and code when counting words”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Include all external URLs in site statistics</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2024/12/11/094929" />
  <id>tag:silasjelley.com,2020-08-20:12b3458c-71f1-497d-a431-74f0a7f79a60</id>
  <published>2024-12-11T09:49:29Z</published>
  <updated>2024-12-11T09:49:29Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;The &lt;a href=&#34;/stats&#34; title=&#34;Stats | June 2022&#34;&gt;stats page&lt;/a&gt; has long shown a count of the number of unique external links referenced on the site. Before this change the stats showed 483 unique external links, the relevant code is contained in the snippet below,&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;extract_external_links&lt;/span&gt;(text: &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;) -&amp;gt; List:&lt;br/&gt;    url_pattern &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;r&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;https&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;://&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;\w&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;|&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;%&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;\d&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;a-f&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;A-F&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;{2}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;?:&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;^&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;\s&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    matches &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; re.&lt;span style=&#34;color: #4b69c6&#34;&gt;findall&lt;/span&gt;(url_pattern, text)&lt;br/&gt;&lt;br/&gt;    external_links &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;set&lt;/span&gt;()&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;for&lt;/span&gt; url &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; matches:&lt;br/&gt;        parsed_url &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;urlparse&lt;/span&gt;(url)&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; parsed_url.netloc.&lt;span style=&#34;color: #4b69c6&#34;&gt;lower&lt;/span&gt;() &lt;span style=&#34;color: #d73948&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;silasjelley.com&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;:&lt;br/&gt;            external_links.&lt;span style=&#34;color: #4b69c6&#34;&gt;add&lt;/span&gt;(url)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;list&lt;/span&gt;(external_links)&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;...&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;    plain_text &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; document[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;][&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;plain&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;]&lt;br/&gt;    external_links &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;extract_external_links&lt;/span&gt;(plain_text)&lt;br/&gt;&lt;br/&gt;    document[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;links&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;][&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;external&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;].&lt;span style=&#34;color: #4b69c6&#34;&gt;update&lt;/span&gt;(external_links)&lt;br/&gt;    site.links[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;external&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;].&lt;span style=&#34;color: #4b69c6&#34;&gt;update&lt;/span&gt;(external_links)&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;KeyError&lt;/span&gt;:&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Up to now I’ve only included links within the main body of a page (&lt;code&gt;content[&#34;plain&#34;]&lt;/code&gt;), but my document format has evolved since then with more and more links being structured into the &lt;code&gt;[source]&lt;/code&gt; and &lt;code&gt;[via]&lt;/code&gt; tables.&lt;/p&gt;
&lt;p&gt;First I wanted to grasp what kind of a difference this would make so, as usual, I started with a shell pipeline search through the output of the build script,&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;sh&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;sh&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;rg&lt;/span&gt; --no-filename \&lt;br/&gt;   --only-matching \&lt;br/&gt;   --no-line-number \&lt;br/&gt;   &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;http.*?://[^ \[\]&#34;&amp;amp;&amp;lt;&amp;gt;)]*&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt; \&lt;br/&gt;   &lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;/&lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt;.html &lt;span style=&#34;color: #d73948&#34;&gt;|&lt;/span&gt; \&lt;br/&gt;   &lt;span style=&#34;color: #4b69c6&#34;&gt;sed&lt;/span&gt; \&lt;br/&gt;     -e &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;s/&#39;.*//&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; \&lt;br/&gt;     -e &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/silasjelley\.com/d&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt; \&lt;br/&gt;     -e &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;s,/$,,&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt; \&lt;br/&gt;     -e &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;s/#.*//&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;|&lt;/span&gt; \&lt;br/&gt;     &lt;span style=&#34;color: #4b69c6&#34;&gt;sort&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;|&lt;/span&gt; \&lt;br/&gt;     &lt;span style=&#34;color: #4b69c6&#34;&gt;uniq&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;|&lt;/span&gt; \&lt;br/&gt;     &lt;span style=&#34;color: #4b69c6&#34;&gt;wc&lt;/span&gt; -l&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The regex for link matching was pretty coarse so I tidied/normalised the output and removed internal links with some &lt;code&gt;sed&lt;/code&gt; patterns, before sorting, de-duping, and counting (&lt;code&gt; sort | uniq | wc -l &lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;The result: 907 unique external links, almost double the 483 contained purely in the main body content.&lt;/p&gt;
&lt;p&gt;Now that number is slightly inflated as it includes links to external APIs such as &lt;code&gt;https://api.maptiler.com/maps/topo-v2/{z}/{x}/{y}.png?key=APIKEY&lt;/code&gt; for &lt;a href=&#34;/map&#34; title=&#34;Map of my world | July 2023&#34;&gt;my walk map&lt;/a&gt;, so our final number should be slightly lower.&lt;/p&gt;
&lt;p&gt;This change includes the &lt;code&gt;[source]&lt;/code&gt; and &lt;code&gt;[via]&lt;/code&gt; tables when searching for links,&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;    plain_text &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; document.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;content&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, {}).&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;plain&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    plain_text &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; document.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;source&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, {}).&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;    plain_text &lt;span style=&#34;color: #d73948&#34;&gt;+=&lt;/span&gt; document.&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;via&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, {}).&lt;span style=&#34;color: #4b69c6&#34;&gt;get&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;url&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;    external_links &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;extract_external_links&lt;/span&gt;(plain_text)&lt;br/&gt;&lt;br/&gt;    document[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;links&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;][&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;external&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;].&lt;span style=&#34;color: #4b69c6&#34;&gt;update&lt;/span&gt;(external_links)&lt;br/&gt;    site.links[&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;external&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;].&lt;span style=&#34;color: #4b69c6&#34;&gt;update&lt;/span&gt;(external_links)&lt;br/&gt;&lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;KeyError&lt;/span&gt;:&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;KeyError while compiling external links from &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;document&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;[&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;filename&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;]&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;After the change, the stats reflect 869 unique external links.&lt;/p&gt;
9:49am on December 11, 2024 from Vancouver, British Columbia, Canada&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Include all external URLs in site statistics”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Switch image references to use picture elements and AVIF encoded files</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2024/12/04/181603" />
  <id>tag:silasjelley.com,2020-08-20:5f39dd4e-8ae8-47d3-a3ff-120f9ce3d0c9</id>
  <published>2024-12-04T18:16:03Z</published>
  <updated>2024-12-04T18:16:03Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;Up to now, image references, eg. &lt;code&gt;#link(&#34;img:​:5f39dd4e&#34;)[::ImageAlt]&lt;/code&gt; have functioned like so:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The source section (between parentheses) is substituted directly from the asset manifest, in this case referenced by the first 8-chars of its UUIDv4.&lt;/li&gt;
&lt;li&gt;If the Alt-text [between square brackets] begins with ‘::’, it is also replaced by the description from the corresponding asset manifest, else it is left as is.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This has worked well, but I’ve been wanting to add AVIF support in order to take advantage of its better compression performance. AVIF is not universally supported, so a fallback (eg. JPEG) is necessary. The HTML &lt;code&gt;\&amp;lt;picture&amp;gt;&lt;/code&gt; element is well suited to this. The Djot image syntax has been bypassed, image references are now directly replaced with a final HTML snippet like so,&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;replacement &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;`​`​`=html&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;picture&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;source srcset=&#34;&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;Path&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;ref_slug&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;.&lt;/span&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;with_suffix&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.avif&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34; type=&#34;image/avif&#34; /&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;img alt=&#34;&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;ref_text&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34; src=&#34;&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;ref_slug&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/picture&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;`​`​`&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&#34;&#34;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The actual AVIF creation support was added using &lt;code&gt;pillow_avif-plugin&lt;/code&gt;, a plugin for the Pillow library I was already using for compressing JPEGs. Most of the relevant code is in the &lt;code&gt;process_image_parallel&lt;/code&gt; function,&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;process_image_parallel&lt;/span&gt;(input_data: Tuple[Path, Path, &lt;span style=&#34;color: #4b69c6&#34;&gt;int&lt;/span&gt;, &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;]) -&amp;gt; &lt;span style=&#34;color: #d73948&#34;&gt;None&lt;/span&gt;:&lt;br/&gt;    workaround_import &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; pillow_avif.AvifImagePlugin&lt;br/&gt;    input_image, output_path, output_width, uid &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; input_data&lt;br/&gt;    lock_path &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;with_suffix&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.lock&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    lock &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;FileLock&lt;/span&gt;(&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;(lock_path))&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Define AVIF output path&lt;/span&gt;&lt;br/&gt;    avif_output_path &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;with_suffix&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.avif&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Check if AVIF support is available&lt;/span&gt;&lt;br/&gt;    avif_available &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;AVIF&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; Image.SAVE&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;with&lt;/span&gt; lock:&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; output_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;exists&lt;/span&gt;() &lt;span style=&#34;color: #d73948&#34;&gt;and&lt;/span&gt; avif_output_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;exists&lt;/span&gt;():&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;            os.&lt;span style=&#34;color: #4b69c6&#34;&gt;makedirs&lt;/span&gt;(output_path.parent, exist_ok&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;with&lt;/span&gt; Image.&lt;span style=&#34;color: #4b69c6&#34;&gt;open&lt;/span&gt;(input_image) &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; im:&lt;br/&gt;                original_format &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; im.format&lt;br/&gt;                im &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; ImageOps.&lt;span style=&#34;color: #4b69c6&#34;&gt;exif_transpose&lt;/span&gt;(im)&lt;br/&gt;                output_height &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;int&lt;/span&gt;(im.size[&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt; (output_width &lt;span style=&#34;color: #d73948&#34;&gt;/&lt;/span&gt; im.size[&lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;]))&lt;br/&gt;&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;with&lt;/span&gt; im.&lt;span style=&#34;color: #4b69c6&#34;&gt;resize&lt;/span&gt;(&lt;br/&gt;                    (output_width, output_height), Image.Resampling.LANCZOS&lt;br/&gt;                ) &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; output_image:&lt;br/&gt;                    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Save JPEG version&lt;/span&gt;&lt;br/&gt;                    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (&lt;br/&gt;                        original_format &lt;span style=&#34;color: #d73948&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;JPEG&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;                        &lt;span style=&#34;color: #d73948&#34;&gt;and&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;(output_path).&lt;span style=&#34;color: #4b69c6&#34;&gt;endswith&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;jpg&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;                        &lt;span style=&#34;color: #d73948&#34;&gt;and&lt;/span&gt; output_image.mode &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; (&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;RGBA&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;P&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;                    ):&lt;br/&gt;                        output_image &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_image.&lt;span style=&#34;color: #4b69c6&#34;&gt;convert&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;RGB&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;                    output_image.&lt;span style=&#34;color: #4b69c6&#34;&gt;save&lt;/span&gt;(output_path, quality&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;85&lt;/span&gt;, optimize&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;                    &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Save AVIF version only if support is available&lt;/span&gt;&lt;br/&gt;                    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; avif_available:&lt;br/&gt;                        &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;                            &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; output_image.mode &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; (&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;RGBA&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;P&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;):&lt;br/&gt;                                avif_image &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_image.&lt;span style=&#34;color: #4b69c6&#34;&gt;convert&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;RGB&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;                            &lt;span style=&#34;color: #d73948&#34;&gt;else&lt;/span&gt;:&lt;br/&gt;                                avif_image &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_image.&lt;span style=&#34;color: #4b69c6&#34;&gt;copy&lt;/span&gt;()&lt;br/&gt;&lt;br/&gt;                            avif_image.&lt;span style=&#34;color: #4b69c6&#34;&gt;save&lt;/span&gt;(&lt;br/&gt;                                avif_output_path,&lt;br/&gt;                                format&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;AVIF&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,&lt;br/&gt;                                quality&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;60&lt;/span&gt;,  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Lower quality for better compression, still maintains good visual quality&lt;/span&gt;&lt;br/&gt;                                speed&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;5&lt;/span&gt;,  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Slowest speed = best compression (0 is slowest, 10 is fastest)&lt;/span&gt;&lt;br/&gt;                                bits&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;10&lt;/span&gt;,  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Use 10-bit color depth for better quality-to-size ratio&lt;/span&gt;&lt;br/&gt;                                compress_level&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;8&lt;/span&gt;,  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Highest compression level (range 0-8)&lt;/span&gt;&lt;br/&gt;                                color_space&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;bt709&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;,  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Use YUV BT.709 color space&lt;/span&gt;&lt;br/&gt;                                chroma&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; 4:4:4 chroma sampling (0=4:4:4, 1=4:2:0, 2=4:2:2)&lt;/span&gt;&lt;br/&gt;                                num_threads&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;,  &lt;span style=&#34;color: #74747c&#34;&gt;#&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Use all available CPU threads for encoding&lt;/span&gt;&lt;br/&gt;                            )&lt;br/&gt;                            logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;debug&lt;/span&gt;(&lt;br/&gt;                                &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Processed image: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;input_image&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; -&amp;gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;output_path&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; and &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;avif_output_path&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;                            )&lt;br/&gt;                        &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;Exception&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; e:&lt;br/&gt;                            logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;error&lt;/span&gt;(&lt;br/&gt;                                &lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Error saving AVIF version of &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;input_image&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;e&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;                            )&lt;br/&gt;                    &lt;span style=&#34;color: #d73948&#34;&gt;else&lt;/span&gt;:&lt;br/&gt;                        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;error&lt;/span&gt;(&lt;br/&gt;                            &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;AVIF support not available. Skipping AVIF conversion.&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;                        )&lt;br/&gt;                        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;debug&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Processed image: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;input_image&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; -&amp;gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;output_path&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;OSError&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; e:&lt;br/&gt;        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;OS error processing &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;input_image&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;e&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;Exception&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; e:&lt;br/&gt;        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Error processing &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;input_image&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;e&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;finally&lt;/span&gt;:&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; lock_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;exists&lt;/span&gt;():&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;                lock_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;unlink&lt;/span&gt;()&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;OSError&lt;/span&gt;:&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It was also necessary to update my gallery/lightbox JavaScript code, as the previous version was loading the JPEG fallback when the lightbox opened, wasting bandwidth.&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;js&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;js&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;js&#34;&gt;&lt;span style=&#34;color: #d73948&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;Lightbox&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;constructor&lt;/span&gt;(galleryElement) {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.galleryElement &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; galleryElement;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.galleryItems &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.galleryElement.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelectorAll&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;img, picture&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.currentIndex &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;null&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.content &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;null&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.image &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;null&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.caption &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;null&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.closeButton &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;null&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.prevButton &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;null&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.nextButton &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;null&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.isOpen &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;false&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;init&lt;/span&gt;();&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.imageCount &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; galleryElement.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelectorAll&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;img&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;).length;&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;init&lt;/span&gt;() {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;createLightbox&lt;/span&gt;();&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;addEventListeners&lt;/span&gt;();&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;createLightbox&lt;/span&gt;() {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;document&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;createElement&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;div&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.className &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;lightbox-overlay&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.innerHTML &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;`&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;div class=&#34;lightbox-content&#34;&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;img src=&#34;&#34; alt=&#34;&#34; class=&#34;lightbox-image&#34;&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;p class=&#34;lightbox-caption&#34;&amp;gt;&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/p&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;button class=&#34;lightbox-prev&#34;&amp;gt;&amp;amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/button&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;button class=&#34;lightbox-next&#34;&amp;gt;&amp;amp;gt;&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/button&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/div&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;      &lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;button class=&#34;lightbox-close&#34;&amp;gt;&amp;amp;times;&lt;/span&gt;&lt;span style=&#34;color: #1d6c76&#34;&gt;\&amp;lt;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/button&amp;gt;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #198810&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;`&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;document&lt;/span&gt;.body.&lt;span style=&#34;color: #4b69c6&#34;&gt;appendChild&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay);&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.content &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelector&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.lightbox-content&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.image &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelector&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.lightbox-image&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.caption &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelector&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.lightbox-caption&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.closeButton &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelector&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.lightbox-close&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.prevButton &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelector&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.lightbox-prev&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.nextButton &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelector&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.lightbox-next&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;getImageDetails&lt;/span&gt;(element) {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (element.tagName.&lt;span style=&#34;color: #4b69c6&#34;&gt;toLowerCase&lt;/span&gt;() &lt;span style=&#34;color: #d73948&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;picture&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;) {&lt;br/&gt;      &lt;span style=&#34;color: #d73948&#34;&gt;const&lt;/span&gt; img &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; element.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelector&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;img&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;      &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; {&lt;br/&gt;        &lt;span style=&#34;color: #74747c&#34;&gt;//&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; currentSrc gives us the source that the browser actually loaded&lt;/span&gt;&lt;br/&gt;        src: img.currentSrc &lt;span style=&#34;color: #d73948&#34;&gt;||&lt;/span&gt; img.src,&lt;br/&gt;        alt: img.alt&lt;br/&gt;      };&lt;br/&gt;    } &lt;span style=&#34;color: #d73948&#34;&gt;else&lt;/span&gt; {&lt;br/&gt;      &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt; {&lt;br/&gt;        src: element.currentSrc &lt;span style=&#34;color: #d73948&#34;&gt;||&lt;/span&gt; element.src,&lt;br/&gt;        alt: element.alt&lt;br/&gt;      };&lt;br/&gt;    }&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;addEventListeners&lt;/span&gt;() {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.galleryItems.&lt;span style=&#34;color: #4b69c6&#34;&gt;forEach&lt;/span&gt;((item, index) &lt;span style=&#34;color: #d73948&#34;&gt;=&amp;gt;&lt;/span&gt; {&lt;br/&gt;      &lt;span style=&#34;color: #74747c&#34;&gt;//&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; For picture elements, we need to attach the listener to the img inside&lt;/span&gt;&lt;br/&gt;      &lt;span style=&#34;color: #d73948&#34;&gt;const&lt;/span&gt; clickTarget &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; item.tagName.&lt;span style=&#34;color: #4b69c6&#34;&gt;toLowerCase&lt;/span&gt;() &lt;span style=&#34;color: #d73948&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;picture&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;?&lt;/span&gt; item.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelector&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;img&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;)&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;:&lt;/span&gt; item;&lt;br/&gt;&lt;br/&gt;      clickTarget.&lt;span style=&#34;color: #4b69c6&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;click&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;, (e) &lt;span style=&#34;color: #d73948&#34;&gt;=&amp;gt;&lt;/span&gt; {&lt;br/&gt;        e.&lt;span style=&#34;color: #4b69c6&#34;&gt;preventDefault&lt;/span&gt;();&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;openLightbox&lt;/span&gt;(index);&lt;br/&gt;      });&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.&lt;span style=&#34;color: #4b69c6&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;click&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;, (e) &lt;span style=&#34;color: #d73948&#34;&gt;=&amp;gt;&lt;/span&gt; {&lt;br/&gt;      &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color: #d73948&#34;&gt;!&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.content.&lt;span style=&#34;color: #4b69c6&#34;&gt;contains&lt;/span&gt;(e.target) &lt;span style=&#34;color: #d73948&#34;&gt;||&lt;/span&gt; e.target &lt;span style=&#34;color: #d73948&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.content) {&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;closeLightbox&lt;/span&gt;();&lt;br/&gt;      }&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.closeButton.&lt;span style=&#34;color: #4b69c6&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;click&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;, () &lt;span style=&#34;color: #d73948&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;closeLightbox&lt;/span&gt;());&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;document&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;keydown&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;, (e) &lt;span style=&#34;color: #d73948&#34;&gt;=&amp;gt;&lt;/span&gt; {&lt;br/&gt;      &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.isOpen) {&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (e.key &lt;span style=&#34;color: #d73948&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Escape&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;closeLightbox&lt;/span&gt;();&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (e.key &lt;span style=&#34;color: #d73948&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;ArrowLeft&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;showPrevious&lt;/span&gt;();&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (e.key &lt;span style=&#34;color: #d73948&#34;&gt;===&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;ArrowRight&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;showNext&lt;/span&gt;();&lt;br/&gt;      }&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;window&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;popstate&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;, () &lt;span style=&#34;color: #d73948&#34;&gt;=&amp;gt;&lt;/span&gt; {&lt;br/&gt;      &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.isOpen) {&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;closeLightbox&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;false&lt;/span&gt;);&lt;br/&gt;      }&lt;br/&gt;    });&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.prevButton.&lt;span style=&#34;color: #4b69c6&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;click&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;, () &lt;span style=&#34;color: #d73948&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;showPrevious&lt;/span&gt;());&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.nextButton.&lt;span style=&#34;color: #4b69c6&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;click&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;, () &lt;span style=&#34;color: #d73948&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;showNext&lt;/span&gt;());&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;openLightbox&lt;/span&gt;(index) {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.currentIndex &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; index;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;updateLightboxContent&lt;/span&gt;();&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.style.display &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;block&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;document&lt;/span&gt;.body.style.overflow &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;hidden&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.isOpen &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;true&lt;/span&gt;;&lt;br/&gt;    history.&lt;span style=&#34;color: #4b69c6&#34;&gt;pushState&lt;/span&gt;({ lightboxOpen: &lt;span style=&#34;color: #d73948&#34;&gt;true&lt;/span&gt; }, &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;closeLightbox&lt;/span&gt;(pushState &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;true&lt;/span&gt;) {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.overlay.style.display &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;none&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #4b69c6&#34;&gt;document&lt;/span&gt;.body.style.overflow &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.isOpen &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;false&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (pushState) {&lt;br/&gt;      history.&lt;span style=&#34;color: #4b69c6&#34;&gt;pushState&lt;/span&gt;({ lightboxOpen: &lt;span style=&#34;color: #d73948&#34;&gt;false&lt;/span&gt; }, &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;    }&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;updateLightboxContent&lt;/span&gt;() {&lt;br/&gt;  &lt;span style=&#34;color: #d73948&#34;&gt;const&lt;/span&gt; currentItem &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.galleryItems[&lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.currentIndex];&lt;br/&gt;  &lt;span style=&#34;color: #d73948&#34;&gt;const&lt;/span&gt; { src, alt } &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;getImageDetails&lt;/span&gt;(currentItem);&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.image.src &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; src;&lt;br/&gt;  &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.image.alt &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; alt;&lt;br/&gt;  &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.caption.textContent &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; alt;&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #74747c&#34;&gt;//&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Use imageCount instead of galleryItems.length&lt;/span&gt;&lt;br/&gt;  &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (&lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.imageCount &lt;span style=&#34;color: #d73948&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;) {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.prevButton.style.display &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;none&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.nextButton.style.display &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;none&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;;&lt;br/&gt;  } &lt;span style=&#34;color: #d73948&#34;&gt;else&lt;/span&gt; {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.prevButton.style.display &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;block&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.nextButton.style.display &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;block&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;;&lt;br/&gt;  }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;showPrevious&lt;/span&gt;() {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.currentIndex &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.currentIndex &lt;span style=&#34;color: #d73948&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.galleryItems.length) &lt;span style=&#34;color: #d73948&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.galleryItems.length;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;updateLightboxContent&lt;/span&gt;();&lt;br/&gt;  }&lt;br/&gt;&lt;br/&gt;  &lt;span style=&#34;color: #4b69c6&#34;&gt;showNext&lt;/span&gt;() {&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.currentIndex &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; (&lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.currentIndex &lt;span style=&#34;color: #d73948&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color: #d73948&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.galleryItems.length;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;this&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;updateLightboxContent&lt;/span&gt;();&lt;br/&gt;  }&lt;br/&gt;}&lt;br/&gt;&lt;br/&gt;&lt;span style=&#34;color: #74747c&#34;&gt;//&lt;/span&gt;&lt;span style=&#34;color: #74747c&#34;&gt; Initialize the lightbox for each gallery&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;document&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;addEventListener&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;DOMContentLoaded&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;, () &lt;span style=&#34;color: #d73948&#34;&gt;=&amp;gt;&lt;/span&gt; {&lt;br/&gt;  &lt;span style=&#34;color: #d73948&#34;&gt;const&lt;/span&gt; galleries &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;document&lt;/span&gt;.&lt;span style=&#34;color: #4b69c6&#34;&gt;querySelectorAll&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.gallery&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#39;&lt;/span&gt;);&lt;br/&gt;  galleries.&lt;span style=&#34;color: #4b69c6&#34;&gt;forEach&lt;/span&gt;(gallery &lt;span style=&#34;color: #d73948&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;new&lt;/span&gt; Lightbox(gallery));&lt;br/&gt;});&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The result: higher image quality (raised image default width from 1400px to 1600px) and &lt;strong&gt;half&lt;/strong&gt; the file-size, eg. &lt;a href=&#34;/nonsense&#34; title=&#34;Some of the nonsense from my old loaf | May 2022&#34;&gt;my nonsense page&lt;/a&gt; has been cut from 7.8MB transferred to load, to 3.45MB. Or, to compare only the actual images, 6.8MB to 2.94MB, a 57% saving. At higher quality! Madness.&lt;/p&gt;
&lt;p&gt;Coupled with the previous change —&lt;span style=&#34;white-space: pre-wrap&#34;&gt; &lt;/span&gt;&lt;a href=&#34;/2024/11/17/175502&#34; title=&#34;Convert heavy PNGs to JPEG where appropriate | November 2024&#34;&gt;Convert heavy PNGs to JPEG where appropriate&lt;/a&gt; — that page has been cut from 15.9 to 3.45MB. Happy with that.&lt;/p&gt;
&lt;aside class=&#34;aside &#34;&gt;

&lt;p&gt;Small problem with the new image reference solution: classes specified in Djot are no longer passed through as the Djot rendering is bypassed. Most images on the site are wrapped in a ‘gallery’ div, so I can still class that, but it does create a small annoyance for bare images.&lt;/p&gt;

&lt;time datetime=&#34;2024-12-05 10:08:23&#34; class=&#34;smallcaps&#34;&gt;&lt;a href=&#34;/2024/12/05/100823&#34;&gt;December  5, 2024 10.08AM ⚕ Vancouver, Canada&lt;/a&gt;&lt;/time&gt;
&lt;/aside&gt;
6:16pm on December  4, 2024 from Vancouver, British Columbia, Canada&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Switch image references to use picture elements and AVIF encoded files”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Switched to non-breaking spaces in Chapter, Volume, Edition, and Page attribution</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2024/11/19/203127" />
  <id>tag:silasjelley.com,2020-08-20:8d5230e3-f7ce-43c0-9fe4-e79b2071fd2b</id>
  <published>2024-11-19T20:31:27Z</published>
  <updated>2024-11-19T20:31:27Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;A very minor change to resolve a bit of acute visual nit-picking. The space in eg, ‘p. 56’, ‘Ch. 7’, ‘Vol. 9’, or ‘(3rd edition)’, created by my attribution logic, could cause the two parts to appear on separate lines in a long attribution string or a narrow viewport. Switching to a non-breaking space ‘ ‘ (visually indistinguishable from a normal space) means they will always appear side by side.&lt;/p&gt;
&lt;p&gt;Unicode codepoint U+00A0, commonly abbreviated as NBSP.&lt;/p&gt;
8:31pm on November 19, 2024 from Vancouver, British Columbia, Canada&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Switched to non-breaking spaces in Chapter, Volume, Edition, and Page attribution”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Convert heavy PNGs to JPEG where appropriate</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2024/11/17/175502" />
  <id>tag:silasjelley.com,2020-08-20:5f25baf8-9305-4a85-af66-6ae111ee7cdd</id>
  <published>2024-11-17T17:55:02Z</published>
  <updated>2024-11-17T17:55:02Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;While viewing my &lt;a href=&#34;/nonsense&#34; title=&#34;Some of the nonsense from my old loaf | May 2022&#34;&gt;nonsense page&lt;/a&gt; with the network tab of the developer tools open I noticed the page was transferring a total of 15.8MB to load. The page loads hundreds of posts so this isn’t completely absurd, but it felt excessive. Turns out most of the weight was coming from a handful of PNG posters and screenshots.&lt;/p&gt;
&lt;p&gt;In the general case, JPEGs are smaller than PNGs, but I didn’t want to hardcode conversion of all PNGs because there are times when lossless or transparent images are wanted.&lt;/p&gt;
&lt;p&gt;I toyed around with a couple of implementations. My first prototype added a &lt;code&gt;convert_to&lt;/code&gt; key to the asset manifests, but in the end I decided to simply convert on the basis of the output_path suffix being different than the input_path suffix in a manifest entry, allowing every stage of the build to seamlessly pick up the correct path to reference.&lt;/p&gt;
&lt;p&gt;Switching those few heavy-weight PNGs to JPEG output cut the page weight from 15.8MB down to 5.18MB.&lt;/p&gt;
&lt;p&gt;The most egregious offender was &lt;a href=&#34;/2022/06/10/212140&#34; title=&#34;Princess Mononoke, my first Ghibli&#34;&gt;Princess Mononoke, my first Ghibli&lt;/a&gt;“) coming in at 2.8MB initially, down to 240kB after conversion to JPEG. Shown below, the only change required in a manifest entry is to change the suffix/file-extension.&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;toml&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;toml&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;toml&#34;&gt;[&lt;span style=&#34;color: #4b69c6&#34;&gt;27991f33-f40f-48ab-9690-0003db7b2038&lt;/span&gt;]&lt;br/&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;type&lt;/span&gt; = &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;poster&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;title&lt;/span&gt; = &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;The poster for the film Blade Runner (1982)&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;filepath&lt;/span&gt; = &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;/home/silas/library/images/posters/films/1982_Blade-Runner.png&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;&lt;span style=&#34;color: #4b69c6&#34;&gt;slug&lt;/span&gt; = &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;library/images/posters/films/1982_Blade-Runner.jpg&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The only change I needed to make to the build script was to check the colour-space when converting to JPEG and switch it to RGB in the case that the source colour-space was either ‘RGBA’ or ‘P’:&lt;/p&gt;
&lt;div class=&#34;code-block&#34; data-lang=&#34;python&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;span class=&#34;code-lang&#34;&gt;python&lt;/span&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code data-lang=&#34;python&#34;&gt;&lt;span style=&#34;color: #d73948&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;process_image_parallel&lt;/span&gt;(input_data: Tuple[Path, Path, &lt;span style=&#34;color: #4b69c6&#34;&gt;int&lt;/span&gt;]) -&amp;gt; &lt;span style=&#34;color: #d73948&#34;&gt;None&lt;/span&gt;:&lt;br/&gt;    input_image, output_path, output_width &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; input_data&lt;br/&gt;    lock_path &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;with_suffix&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;.lock&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    lock &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;FileLock&lt;/span&gt;(&lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;(lock_path))&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;with&lt;/span&gt; lock:&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; output_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;exists&lt;/span&gt;():&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;return&lt;/span&gt;&lt;br/&gt;&lt;br/&gt;            os.&lt;span style=&#34;color: #4b69c6&#34;&gt;makedirs&lt;/span&gt;(output_path.parent, exist_ok&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;with&lt;/span&gt; Image.&lt;span style=&#34;color: #4b69c6&#34;&gt;open&lt;/span&gt;(input_image) &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; im:&lt;br/&gt;                original_format &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; im.format&lt;br/&gt;                im &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; ImageOps.&lt;span style=&#34;color: #4b69c6&#34;&gt;exif_transpose&lt;/span&gt;(im)&lt;br/&gt;                output_height &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;int&lt;/span&gt;(im.size[&lt;span style=&#34;color: #b60157&#34;&gt;1&lt;/span&gt;] &lt;span style=&#34;color: #d73948&#34;&gt;*&lt;/span&gt; (output_width &lt;span style=&#34;color: #d73948&#34;&gt;/&lt;/span&gt; im.size[&lt;span style=&#34;color: #b60157&#34;&gt;0&lt;/span&gt;]))&lt;br/&gt;&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;with&lt;/span&gt; im.&lt;span style=&#34;color: #4b69c6&#34;&gt;resize&lt;/span&gt;(&lt;br/&gt;                    (output_width, output_height), Image.Resampling.LANCZOS&lt;br/&gt;                ) &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; output_image:&lt;br/&gt;                    &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; (&lt;br/&gt;                        original_format &lt;span style=&#34;color: #d73948&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;JPEG&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;br/&gt;                        &lt;span style=&#34;color: #d73948&#34;&gt;and&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;str&lt;/span&gt;(output_path).&lt;span style=&#34;color: #4b69c6&#34;&gt;endswith&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;jpg&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;                        &lt;span style=&#34;color: #d73948&#34;&gt;and&lt;/span&gt; output_image.mode &lt;span style=&#34;color: #d73948&#34;&gt;in&lt;/span&gt; (&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;RGBA&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;, &lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;P&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;                    ):&lt;br/&gt;                        output_image &lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt; output_image.&lt;span style=&#34;color: #4b69c6&#34;&gt;convert&lt;/span&gt;(&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;RGB&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;                    output_image.&lt;span style=&#34;color: #4b69c6&#34;&gt;save&lt;/span&gt;(output_path, quality&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #b60157&#34;&gt;85&lt;/span&gt;, optimize&lt;span style=&#34;color: #d73948&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color: #d73948&#34;&gt;True&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;            logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;debug&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Processed image: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;input_image&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt; -&amp;gt; &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;output_path&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;OSError&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; e:&lt;br/&gt;        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;OS error processing &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;input_image&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;e&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;Exception&lt;/span&gt; &lt;span style=&#34;color: #d73948&#34;&gt;as&lt;/span&gt; e:&lt;br/&gt;        logger.&lt;span style=&#34;color: #4b69c6&#34;&gt;error&lt;/span&gt;(&lt;span style=&#34;color: #d73948&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;Error processing &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;input_image&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;: &lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;{&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;e&lt;/span&gt;&lt;span style=&#34;color: #8b41b1&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color: #198810&#34;&gt;&#34;&lt;/span&gt;)&lt;br/&gt;    &lt;span style=&#34;color: #d73948&#34;&gt;finally&lt;/span&gt;:&lt;br/&gt;        &lt;span style=&#34;color: #d73948&#34;&gt;if&lt;/span&gt; lock_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;exists&lt;/span&gt;():&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;try&lt;/span&gt;:&lt;br/&gt;                lock_path.&lt;span style=&#34;color: #4b69c6&#34;&gt;unlink&lt;/span&gt;()&lt;br/&gt;            &lt;span style=&#34;color: #d73948&#34;&gt;except&lt;/span&gt; &lt;span style=&#34;color: #4b69c6&#34;&gt;OSError&lt;/span&gt;:&lt;br/&gt;                &lt;span style=&#34;color: #d73948&#34;&gt;pass&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
5:55pm on November 17, 2024 from Vancouver, British Columbia, Canada&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Convert heavy PNGs to JPEG where appropriate”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>Import referenced documents at build time</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2024/11/15/132717" />
  <id>tag:silasjelley.com,2020-08-20:5a0d3cbb-f865-49ce-8b97-2a33a61296f8</id>
  <published>2024-11-15T13:27:17Z</published>
  <updated>2024-11-15T13:27:17Z</updated>
  <category term="notes" />
  <category term="changelogs" />
  <content type="html">
    
&lt;p&gt;Added import references, restoring and improving the functionality of the ‘transclude’ reference that I previously deprecated and removed.&lt;/p&gt;
&lt;p&gt;Has two modes, both of which support comments.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;import&lt;/code&gt;, transcludes the content of the referenced document as is, without any additional markup or visual cue.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aside&lt;/code&gt;, creates an aside-type block that also includes an outbound link.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both support an optional comment at the reference-site that is discarded during substitution.&lt;/p&gt;
&lt;p&gt;The following snippet (minus the backslash),&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;{{ aside::501108be // getting arrested again }\}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Would be substituted with this markup,&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;&lt;div class=&#34;code-header&#34;&gt;&lt;button aria-label=&#34;Copy code&#34; class=&#34;code-copy&#34; type=&#34;button&#34;&gt;Copy&lt;/button&gt;&lt;/div&gt;&lt;pre&gt;&lt;code&gt;{{ aside::501108be // getting arrested again }}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which would then be rendered into the following later in the build,&lt;/p&gt;
&lt;p&gt;{{ aside::501108be&lt;/p&gt;
&lt;p&gt;Both forms of import create a backlink in the referenced document, but only &lt;code&gt;aside&lt;/code&gt; creates a visible outward link at the reference-site. This allows documents to be assembled seamlessly where that is a better fit than a call-out, but generally I expect I’ll use the &lt;code&gt;aside&lt;/code&gt; mode more often, as I generally prefer for connections to be bi-directional.&lt;/p&gt;
1:27pm on November 15, 2024 from Calgary, Alberta, Canada&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“Import referenced documents at build time”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>SEP 63: Multiple URLs for source records</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2024/11/06/112332" />
  <id>tag:silasjelley.com,2020-08-20:c5a2e4c4-e7c5-4dcb-94b6-2add1517bd5d</id>
  <published>2024-11-06T11:23:32Z</published>
  <updated>2024-11-06T11:23:32Z</updated>
  <category term="notes" />
  <category term="proposals" />
  <content type="html">
    
&lt;p&gt;Currently a given reference may optionally include a &lt;code&gt;url&lt;/code&gt; key, with a single URL as its value. The idea of a single canonical URL for every resource is somewhat limiting.&lt;/p&gt;
&lt;p&gt;Replacing the current key/value &lt;code&gt;url&lt;/code&gt; with an array, &lt;code&gt;urls&lt;/code&gt;, has the potential to make preservation more adaptable and complete. Example, I don’t subscribe to the New York Times, so when something/someone points me to an article there, I use &amp;lt;archive.is&amp;gt; to view it. So that’s two URLs related to that resource.&lt;/p&gt;
&lt;p&gt;The most significant implementation question seems to be whether the new &lt;code&gt;urls&lt;/code&gt; key should contain an Array, or a Dict.&lt;/p&gt;
&lt;p&gt;A simple array would force meaning into the order of the array, eg. the attribution constructor would have to index from the beginning or the end of the array, taking the first or the last available value. That feels clumsy.&lt;/p&gt;
&lt;p&gt;Using a Dict would allow picking URLs on the basis of semantic keys, eg. an &amp;lt;archive.is&amp;gt; URLs could be under &lt;code&gt;archive_is&lt;/code&gt;, a Quote Investigator link could be under &lt;code&gt;quote_in&lt;/code&gt; OR in a nested &lt;code&gt;references&lt;/code&gt; array etc. Would have to explore whether I really want to ‘hardcode’ such sites.&lt;/p&gt;
11:23am on November  6, 2024 from Istanbul, Marmara, Turkey&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“SEP 63: Multiple URLs for source records”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>
<entry>
  <title>What question would you like to be asked?</title>
  <link rel="alternate" type="text/html" href="https://silasjelley.com/2024/11/05/135930" />
  <id>tag:silasjelley.com,2020-08-20:d70b1778-afa3-4291-bf0f-2555ecb5d3dd</id>
  <published>2024-11-05T13:59:30Z</published>
  <updated>2024-11-05T13:59:30Z</updated>
  <category term="notes" />
  <category term="questions" />
  <content type="html">
    
&lt;p&gt;Pavlos posed the question as I hitch hiked back to my line after detouring back to Albania to see Jess, but it had slipped from my mind until just now when I overheard someone at the hostel ask, “What’s your favourite question?”&lt;/p&gt;
1:59pm on November  5, 2024 from Istanbul, Marmara, Turkey&lt;p&gt;&lt;a href=&#34;mailto:reply@silasjelley.com?subject=Reply%20to:%20“What question would you like to be asked?”&#34;&gt;Reply via email&lt;/a&gt;&lt;/p&gt;
</content>
</entry>

</feed>