<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/css" href="https://www.aaron-gustafson.com/c/feed.min.css" ?><feed xmlns="http://www.w3.org/2005/Atom"
      xmlns:amg="https://www.aaron-gustafson.com.com/amg-dtd/"><title>Aaron Gustafson: Content tagged this site</title><subtitle>The latest 20 posts and links tagged this site.</subtitle><id>https://www.aaron-gustafson.com</id><link href="https://www.aaron-gustafson.com/feeds/this-site.xml" rel="self"/><link href="https://www.aaron-gustafson.com"/><author><name>Aaron Gustafson</name><uri>https://www.aaron-gustafson.com</uri></author><updated>2023-10-09T22:38:54Z</updated><entry><id>https://www.aaron-gustafson.com/notebook/widgets/</id><title type="html"><![CDATA[✍🏻 Widgets!]]></title><link href="https://www.aaron-gustafson.com/notebook/widgets/" rel="alternate" type="text/html" /><published>2023-10-09T22:38:54Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>It was a long time coming, but I finally had a chance to put the work I did on <a href="https://github.com/MicrosoftEdge/MSEdgeExplainers/tree/main/PWAWidgets">a widgets proposal for PWAs</a> into practice on my own site. I’m pretty excited about it!</p><h2 id="where-it-all-started" tabindex="-1"><a class="header-anchor" href="#where-it-all-started" aria-hidden="true">#</a> Where it all started</h2><p>I had <a href="https://web.archive.org/web/20200929174844/https://discourse.wicg.io/t/noodling-on-an-idea-projections-for-web-apps/3900">the original idea for “projections”</a> way back in 2019. Inspired by <a href="https://en.wikipedia.org/wiki/Dashboard_(macOS)#Widget_functions_and_capabilities">OS X’s Dashboard Widgets</a> and <a href="https://en.wikipedia.org/wiki/Adobe_AIR">Adobe AIR</a>, I’d begun to wonder if it might be possible to <em>project</em> a component from a website into those kinds of surfaces. Rather than building a bespoke widget that connected to an API, I thought it made sense to leverage an installed PWA to manage those “projections.” I shared the idea at TPAC that year and got some interest from a broad range of folks, but didn’t have much time to work on the details until a few years later.</p><p>In the intervening time, I kept working through the concept in my head. I mean in an ideal world, the widget would just be a responsive web page, right? But if that were the case, what happens when every widget loads the entirety of React to render their stock ticker? That seemed like a performance nightmare.</p><p>In my gut, I felt like the right way to build things would be to have a standard library of widget templates and to enable devs to flow data into them via a Service Worker. Alex Russell suggested I model the APIs on how Notifications are handled (since they serve a similar function) and I was off to the races.</p><p>I drafted <a href="https://github.com/aarongustafson/pwa-widgets">a substantial proposal for my vision of how PWA widgets should work</a>. Key aspects included:</p><ul><li>A declarative way to define and configure a widget from within the Web App Manifest;</li><li>A progressively enhanced pathway for devs to design a widget that adapts to its host environment, from using predefined templates to using custom templates to full-blown web-based widgets (with rendering akin to an <code>iframe</code>);</li><li>A collection of recommended stock templates that implementors should offer to support most widget types;</li><li>Extensibility to support custom templates using any of a variety of templating languages; and</li><li>A complete suite of tools for managing widgets and any associated business logic within a Service Worker.</li></ul><h2 id="widgets-became-a-reality" tabindex="-1"><a class="header-anchor" href="#widgets-became-a-reality" aria-hidden="true">#</a> Widgets became a reality</h2><p>After continuing to gently push on this idea with colleagues across Microsoft (and beyond), I discovered that the Windows 11 team was looking to open up the new Widget Dashboard to third-party applications. I saw this as an opportunity to turn my idea into a reality. After working my way into the conversation, I made a solid case for why PWAs needed to be a part of that story and… it worked! (It no doubt helped that companies including Meta, Twitter, and Hulu were all invested in PWA as a means of delivering apps for Windows.)</p><p>While the timeline for implementation didn’t allow us to tackle the entirety of my proposal, we did carve out the pieces that made for a compelling MVP. This allowed us to show what’s possible, see how folks use it, and plan for future investment in the space.</p><p>Sadly, it meant tabling two features I really loved:</p><ul><li><strong>Stock/predefined templates</strong>. A library of lightly theme-able, consistent, cross-platform templates based on common data structures (e.g., RSS/Atom, iCal) would make it incredibly simple for devs to build a widget. If implemented well, devs might not even need to write a single line of business logic in their Service Worker as the browser could pick up all of the configuration details from the Manifest.</li><li><strong>Configurable widget instances.</strong> Instead of singleton widgets, these would allow you to define a single widget type and replicate it for different use cases. For example, a widget to follow a social media user’s profile could be defined once and the individual instances could be configured with the specific account to be followed.</li></ul><p>I’m sincerely hopeful these two features eventually make their way to us as I think they truly unlock the power of the widget platform. Perhaps, with enough uptake on the current implementation, we can revisit these in the not-too-distant future.</p><hr><p>To test things out, I decided to build two widgets for this site:</p><ol><li>Latest posts</li><li>Latest links</li></ol><p>Both are largely the same in terms of their setup: They display a list of linked titles from this site.</p><h2 id="designing-my-widget-templates" tabindex="-1"><a class="header-anchor" href="#designing-my-widget-templates" aria-hidden="true">#</a> Designing my widget templates</h2><p>Given that they were going to be largely identical, I made <a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/w/feed.ac.json">a single “feed” template for use in both widgets</a>. The templating tech I used is called <a href="https://adaptivecards.io">Adaptive Cards</a>, which is what Windows 11 uses for rendering.</p><p>Adaptive Card templates are relatively straightforward JSON:</p><pre class="language-json" tabindex="0"><code class="language-json">&amp;#<span class="token number">123</span>;<span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“AdaptiveCard”</span><span class="token punctuation">,</span><span class="token property">“$schema”</span><span class="token operator">:</span><span class="token string">“<a href="http://adaptivecards.io/schemas/adaptive-card.json">http://adaptivecards.io/schemas/adaptive-card.json</a>”</span><span class="token punctuation">,</span><span class="token property">“version”</span><span class="token operator">:</span><span class="token string">“1.6”</span><span class="token punctuation">,</span><span class="token property">“body”</span><span class="token operator">:</span> &amp;#<span class="token number">91</span>;&amp;#<span class="token number">123</span>;<span class="token property">“$data”</span><span class="token operator">:</span><span class="token string">“$&amp;#123;take(items,5)&amp;#125;”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“Container”</span><span class="token punctuation">,</span><span class="token property">“items”</span><span class="token operator">:</span> &amp;#<span class="token number">91</span>;&amp;#<span class="token number">123</span>;<span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“TextBlock”</span><span class="token punctuation">,</span><span class="token property">“text”</span><span class="token operator">:</span><span class="token string">“&amp;#91;$&amp;#123;title&amp;#125;&amp;#93;($&amp;#123;url&amp;#125;)”</span><span class="token punctuation">,</span><span class="token property">“wrap”</span><span class="token operator">:</span><span class="token boolean">true</span><span class="token punctuation">,</span><span class="token property">“weight”</span><span class="token operator">:</span><span class="token string">“Bolder”</span><span class="token punctuation">,</span><span class="token property">“spacing”</span><span class="token operator">:</span><span class="token string">“Padding”</span><span class="token punctuation">,</span><span class="token property">“height”</span><span class="token operator">:</span><span class="token string">“stretch”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">93</span>;<span class="token punctuation">,</span><span class="token property">“height”</span><span class="token operator">:</span><span class="token string">“stretch”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">93</span>;<span class="token punctuation">,</span><span class="token property">“backgroundImage”</span><span class="token operator">:</span> &amp;#<span class="token number">123</span>;<span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“<a href="https://www.aaron-gustafson.com/i/background-logo.png">https://www.aaron-gustafson.com/i/background-logo.png</a>”</span><span class="token punctuation">,</span><span class="token property">“verticalAlignment”</span><span class="token operator">:</span><span class="token string">“Bottom”</span><span class="token punctuation">,</span><span class="token property">“horizontalAlignment”</span><span class="token operator">:</span><span class="token string">“Center”</span>&amp;#<span class="token number">125</span>;&amp;#<span class="token number">125</span>;</code></pre><p>What this structure does is:</p><ol><li>Create a container into which I will place the content;</li><li>Extract the first five <code>items</code> from the data being fed into the template (more on that in a moment);</li><li>Loop through each <code>item</code> and<ul><li>create a text block,</li><li>populate its content with Markdown to generate a linked title (using the <code>title</code> and <code>url</code> keys from the <code>item</code> object)</li><li>Set some basic styles to make the text bold, separate the titles a little and make them grow to fill the container; then, finally</li></ul></li><li>Set a background on the widget.</li></ol><p>The way Adaptive Cards work is that they flow JSON data into a template and render that. The variable names in the template map directly to the incoming data structure, so are totally up to you to define. As these particular widgets are feed-driven and <a href="https://www.aaron-gustafson.com/feeds/">this site already supports JSONFeed</a>, I set up the widgets to flow the appropriate feed into each and used the keys that were already there. For reference, here’s a sample JSONFeed <code>item</code>:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“id”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“title”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“summary”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“content_html”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">,</span><span class="token property">“tags”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">“date_published”</span><span class="token operator">:</span><span class="token string">“…”</span><span class="token punctuation">}</span></code></pre><p>If you want to tinker with Adaptive Cards and make your own, you can do so with <a href="https://adaptivecards.io/designer/">their Designer tool</a>.</p><h3 id="defining-the-widgets-in-the-manifest" tabindex="-1"><a class="header-anchor" href="#defining-the-widgets-in-the-manifest" aria-hidden="true">#</a> Defining the widgets in the Manifest</h3><p>With a basic template created, the next step was to set up the two widgets in my Manifest. As they both function largely the same, I’ll just focus on the definition for one of them.</p><p>First off, defining widgets in the Manifest is done via the <code>widgets</code> member, which is an array (much like <code>icons</code> and <code>shortcuts</code>). Each widget is represented as an object in that array. Here is the definition for the “latest posts” widget:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span><span class="token property">“name”</span><span class="token operator">:</span><span class="token string">“Latest Posts”</span><span class="token punctuation">,</span><span class="token property">“short_name”</span><span class="token operator">:</span><span class="token string">“Posts”</span><span class="token punctuation">,</span><span class="token property">“tag”</span><span class="token operator">:</span><span class="token string">“feed-posts”</span><span class="token punctuation">,</span><span class="token property">“description”</span><span class="token operator">:</span><span class="token string">“The latest posts from Aaron Gustafson’s blog”</span><span class="token punctuation">,</span><span class="token property">“template”</span><span class="token operator">:</span><span class="token string">“feed”</span><span class="token punctuation">,</span><span class="token property">“ms_ac_template”</span><span class="token operator">:</span><span class="token string">“/w/feed.ac.json”</span><span class="token punctuation">,</span><span class="token property">“data”</span><span class="token operator">:</span><span class="token string">“/feeds/latest-posts.json”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“application/json”</span><span class="token punctuation">,</span><span class="token property">“auth”</span><span class="token operator">:</span><span class="token boolean">false</span><span class="token punctuation">,</span><span class="token property">“update”</span><span class="token operator">:</span><span class="token number">21600</span><span class="token punctuation">,</span><span class="token property">“icons”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/icons/webicon-rss.png”</span><span class="token punctuation">,</span><span class="token property">“type”</span><span class="token operator">:</span><span class="token string">“image/png”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“120x120”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token property">“screenshots”</span><span class="token operator">:</span><span class="token punctuation">[</span><span class="token punctuation">{</span><span class="token property">“src”</span><span class="token operator">:</span><span class="token string">“/i/screenshots/widget-posts.png”</span><span class="token punctuation">,</span><span class="token property">“sizes”</span><span class="token operator">:</span><span class="token string">“387x387”</span><span class="token punctuation">,</span><span class="token property">“label”</span><span class="token operator">:</span><span class="token string">“The latest posts widget”</span><span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">}</span></code></pre><p>Breaking this down:</p><ol><li><code>name</code> and <code>short_name</code> act much like these keys in the root of the Manifest as well as in <code>shortcuts</code>: The <code>name</code> value is used as the name for the widget unless there’s not enough room, in which case <code>short_name</code> is used.</li><li>You can think of <code>tag</code> as analogous to <code>class</code> in HTML sense. It’s a way of labeling a widget so you can easily reference it later. Each widget instance will have a unique id created by the widget service, but that instance (or all instances, if the widget supports multiple instances) can be accessed via the <code>tag</code>. But more on that later.</li><li>The <code>description</code> key is used for marketing the widget within a host OS or digital storefront. It should accurately (and briefly) describe what the widget does.</li><li>The <code>template</code> key is not currently used in the Windows 11 implementation but refers to the expected standard library widget template provided by the system. As a template library is not currently available, the <code>ms_ac_template</code> value is used to provide a URL to get the custom Adaptive Card (hence “ac”) template. The “ms_” prefix is there because it’s expected that this would be a Microsoft-proprietary property. It follows <a href="https://www.w3.org/TR/appmanifest/#proprietary-extensions">the guidance for extending the Manifest</a>.</li><li>The <code>data</code> and <code>type</code> keys define the path to the data that should be fed into the template for rendering by the widget host and the MIME of the data format it’s in. The Windows 11 implementation currently only accepts JSON data, but the design of widgets is set up to allow for this to eventually extend to other standardized formats like RSS, iCal, vCard, and such.</li><li><code>update</code> is an optional configuration member allowing you to set how often you’d like the widget to update, in seconds. Developers currently need to add the logic for implementing this into their Service Worker, but this setup allows the configuration to remain independent of the JavaScript code, making it easier to maintain.</li><li>Finally, <code>icons</code> and <code>screenshots</code> allow us to define how the widget shows up in the widget host and how it is promoted for install.</li></ol><p>When someone installs my site as a PWA, the information about the available widgets gets ingested by the browser. The browser then determines, based on the provided values and its knowledge of the available widget service(s) on the device, which widgets should be offered. On Windows 11, this information is <a href="https://learn.microsoft.com/en-us/windows/apps/develop/widgets/implement-widget-provider-cs#update-the-package-manifest">routed into the AppXManifest that governs how apps are represented in Windows</a>. The Windows 11 widget service can then read in the details about the available widgets and offer them for users to install.</p><figure id="2023-10-09-01"><p><img src="https://www.aaron-gustafson.com/i/posts/2023-10-09/widgets-promotion.gif" alt=""></p><figcaption>An animated capture of Windows 11’s widget promotion surface, showing 2 widgets available from this site’s PWA.</figcaption></figure><h2 id="adding-widget-support-to-my-service-worker" tabindex="-1"><a class="header-anchor" href="#adding-widget-support-to-my-service-worker" aria-hidden="true">#</a> Adding widget support to my Service Worker</h2><p>As I mentioned earlier, all of the plumbing for widgets is done within a Service Worker and is modeled on the Notifications API. I’m not going to exhaustively detail how it all works, but I’ll give you enough detail to get you started.</p><p>First off, widgets are exposed via the <code>self.widgets</code> interface. Most importantly, this interface lets you access and update any instances of a widget connected to your PWA.</p><h3 id="installing-a-widget" tabindex="-1"><a class="header-anchor" href="#installing-a-widget" aria-hidden="true">#</a> Installing a widget</h3><p>When a user chooses to install a widget, that emits a “widgetinstall” event in your Service Worker. You use that to kickoff the widget lifecycle by gathering the template and data needed to instantiate the widget:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetinstall”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Installing &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">initializeWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>The event argument comes in with details of the specific widget being instantiated (as <code>event.widget</code>). In the code above, you can see I’ve logged the widget’s <code>tag</code> value to the console. I pass the widget information over to my <code>initializeWidget()</code> function and it updates the widget with the latest data and, if necessary, sets up a <a href="https://developer.mozilla.org/docs/Web/API/Web_Periodic_Background_Synchronization_API">Periodic Background Sync</a>:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">initializeWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">await</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">await</span><span class="token function">registerPeriodicSync</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>The code for my <code>updateWidget()</code> function is as follows:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">updateWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">const</span> template <span class="token operator">=</span><span class="token keyword">await</span><span class="token punctuation">(</span><span class="token keyword">await</span><span class="token function">fetch</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>msAcTemplate<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> data <span class="token operator">=</span><span class="token keyword">await</span><span class="token punctuation">(</span><span class="token keyword">await</span><span class="token function">fetch</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">try</span><span class="token punctuation">{</span><span class="token keyword">await</span> self<span class="token punctuation">.</span>widgets<span class="token punctuation">.</span><span class="token function">updateByTag</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">,</span><span class="token punctuation">{</span> template<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">catch</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Couldn’t update the widget &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>This function does the following:</p><ol><li>Get the template for this widget</li><li>Get the data to flow into the template</li><li>Use the <code>self.widgets.updateByTag()</code> method to push the <var>template</var> and <var>data</var> to the widget service to update any widget instances connected to the widget’s <code>tag</code>.</li></ol><p>As I mentioned, I also have code in place to take advantage of Periodic Background Sync if/when it’s available and the browser allows my site to do it:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">registerPeriodicSync</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">let</span> tag <span class="token operator">=</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">;</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token string">“update”</span><span class="token keyword">in</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">)</span><span class="token punctuation">{</span>registration<span class="token punctuation">.</span>periodicSync<span class="token punctuation">.</span><span class="token function">getTags</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">tags</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token comment">// only one registration per tag</span><span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>tags<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>tag<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">{</span>periodicSync<span class="token punctuation">.</span><span class="token function">register</span><span class="token punctuation">(</span>tag<span class="token punctuation">,</span><span class="token punctuation">{</span><span class="token literal-property property">minInterval</span><span class="token operator">:</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>update<span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>This function also receives the widget details and:</p><ol><li>Looks to see if the widget <code>definition</code> (from the Manifest) includes an <code>update</code> member. If it has one, it…</li><li>Checks to see if there’s already a Periodic Background Sync that is registered for this tag. If none exists, it…</li><li>Registers a new Periodic Background Sync using the <code>tag</code> value and a minimum interval equal to the <code>update</code> requested.</li></ol><p>The <code>update</code> member, as you may recall, is the frequency (in seconds) you’d ideally like the widget to be updated. In reality, you’re at the mercy of the browser as to when (or even if) your sync will run, but that’s totally cool as there are other ways to update widgets as well.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p><h3 id="uninstalling-a-widget" tabindex="-1"><a class="header-anchor" href="#uninstalling-a-widget" aria-hidden="true">#</a> Uninstalling a widget</h3><p>When a user uninstalls a widget, your Service Worker will receive a “widgetuninstall” event. Much like the “widgetinstall” event, the argument contains details about that widget which you can use to clean up after yourself:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetuninstall”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Uninstalling &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">uninstallWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>Your application may have different cleanup needs, but this is a great time to clean up any unneeded Periodic Sync registrations. Just be sure to check the length of the widget’s <code>instances</code> array (<code>widget.instances</code>) to make sure you’re dealing with the last instance of a given widget <em>before</em> you unregister the sync:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">async</span><span class="token keyword">function</span><span class="token function">uninstallWidget</span><span class="token punctuation">(</span><span class="token parameter">widget</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">if</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>instances<span class="token punctuation">.</span>length <span class="token operator">===</span><span class="token number">1</span><span class="token operator">&amp;&amp;</span><span class="token string">“update”</span><span class="token keyword">in</span> widget<span class="token punctuation">.</span>definition<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">await</span> self<span class="token punctuation">.</span>registration<span class="token punctuation">.</span>periodicSync<span class="token punctuation">.</span><span class="token function">unregister</span><span class="token punctuation">(</span>widget<span class="token punctuation">.</span>definition<span class="token punctuation">.</span>tag<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token keyword">return</span><span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><h3 id="refreshing-your-widgets" tabindex="-1"><a class="header-anchor" href="#refreshing-your-widgets" aria-hidden="true">#</a> Refreshing your widgets</h3><p>Widget platforms may periodically freeze your widget(s) to save resources. For example, they may do this when widgets are not visible. To keep your widgets up to date, they will periodically issue a “widgetresume” event. If you’ve modeled your approach on the one I’ve outlined above, you can route this event right through to your <code>updateWidget()</code> function:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetresume”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span>console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Resuming &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;widget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tag&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">;</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><h3 id="actions" tabindex="-1"><a class="header-anchor" href="#actions" aria-hidden="true">#</a> Actions</h3><p>While I don’t want to get too into the weeds here, I do want to mention that widgets can have predefined user actions as well. These actions result in “widget click” events being sent back to the Service Worker so you can respond to them:</p><pre class="language-js" tabindex="0"><code class="language-js">self<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">“widgetclick”</span><span class="token punctuation">,</span><span class="token punctuation">(</span><span class="token parameter">event</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token keyword">const</span> widget <span class="token operator">=</span> event<span class="token punctuation">.</span>widget<span class="token punctuation">;</span><span class="token keyword">const</span> action <span class="token operator">=</span> event<span class="token punctuation">.</span>action<span class="token punctuation">;</span><span class="token keyword">switch</span><span class="token punctuation">(</span>action<span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token comment">// Custom Actions</span><span class="token keyword">case</span><span class="token string">“refresh”</span><span class="token operator">:</span>event<span class="token punctuation">.</span><span class="token function">waitUntil</span><span class="token punctuation">(</span><span class="token function">updateWidget</span><span class="token punctuation">(</span>widget<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">break</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>For a great example of how a widget can integrate actions, you should check out <a href="https://microsoftedge.github.io/Demos/pwamp/">the demo PWAmp project</a>. <a href="https://github.com/MicrosoftEdge/Demos/blob/main/pwamp/sw-widgets.js">Their Service Worker widget code</a> is worth a read.</p><h2 id="result!" tabindex="-1"><a class="header-anchor" href="#result!" aria-hidden="true">#</a> Result!</h2><p>With all of these pieces in place, I was excited to see my site showing up in the Widget Dashboard in Windows 11.</p><figure id="2023-10-09-02"><p><img src="https://www.aaron-gustafson.com/i/posts/2023-10-09/widgets-in-windows.jpg" alt=""></p><figcaption>A screenshot of Windows 11 showing the Widget Dashboard overlaying the desktop with this site installed as a PWA to the right. The “latest posts” and “latest links” widgets are shown.</figcaption></figure><p>You can view the full source code on GitHub:</p><ul><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/w/feed.ac.json">“Feed” Adaptive Card Template</a></li><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/static/manifest.json#L158-L237">Widget definitions in the Manifest</a></li><li><a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/main/src/_javascript/serviceworker/widgets.js">Widgets code in my Service Worker</a></li></ul><hr><p>I’m quite hopeful this will be the first of many places PWA-driven widgets will appear. If you’s like to see them supported elsewhere, be sure to tell your browser and OS vendor(s) of choice. The more they hear from their user base that this feature is needed, the more likely we are to see it get implemented in more places.</p><h2 id="addendum%3A-gotchas" tabindex="-1"><a class="header-anchor" href="#addendum%3A-gotchas" aria-hidden="true">#</a> Addendum: Gotchas</h2><p>In wiring this all up, I ran into a few current bugs I wanted to flag so you can avoid them:</p><ul><li>The <code>icons</code> member won’t accept SVG images. This should eventually be fixed, but it was keeping my widgets from appearing as installable.</li><li>The <code>screenshots</code> members can’t be incredibly large. I’m told you should provide square screenshots no larger than 500px ×500px.</li></ul><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Have you checked out <a href="https://developer.mozilla.org/docs/Web/API/Server-sent_events/Using_server-sent_events">Server Events</a>? <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:twitter><![CDATA[I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. It’s pretty exciting!]]></amg:twitter><amg:summary><![CDATA[I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. It’s pretty exciting!]]></amg:summary><summary type="html"><![CDATA[<p>I finally had a chance to put the work I did on a widgets proposal for PWAs into practice on my own site. It’s pretty exciting!</p>]]></summary><category term="progressive web apps" /><category term="experiments" /><category term="JavaScript" /><category term="Microsoft" /><category term="this site" /><category term="user experience" /><category term="web development" /><category term="web standards" /><category term="Windows" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/posts/2023-10-09/hero1.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/automate-netlify-redirects-in-11ty/</id><title type="html"><![CDATA[🔗 Automate Netlify Redirects in 11ty]]></title><link href="https://www.aaron-gustafson.com/notebook/links/automate-netlify-redirects-in-11ty/" rel="alternate" type="text/html" /><link href="https://www.aleksandrhovhannisyan.com/blog/eleventy-netlify-redirects/" rel="related" type="text/html" /><published>2023-02-09T22:22:38Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I found this post really helpful in porting my old Jekyll front matter-based redirects (which use the <code>redirect_from</code> key) to Eleventy &amp; Netlify.</p>]]></content><amg:twitter><![CDATA[I found this post really helpful in porting my old #Jekyll front matter-based #redirects (which use the `redirect_from` key) to #Eleventy & #Netlify.]]></amg:twitter><category term="this site" /><category term="Eleventy" /><category term="Netlify" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aleksandrhovhannisyan.com/assets/images/AbcpioI1CN-1280.jpeg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/salvaging-linkrot-with-the-wayback-machine/</id><title type="html"><![CDATA[✍🏻 Salvaging linkrot with the Wayback Machine]]></title><link href="https://www.aaron-gustafson.com/notebook/salvaging-linkrot-with-the-wayback-machine/" rel="alternate" type="text/html" /><published>2022-08-31T21:37:31Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>While making some updates to the site, I did a 404 scan of <a href="/notebook/links/">my link blog</a> and the results were… less than awesome. So I decided to work some Eleventy magic to recover from them.</p><h2 id="step-1%3A-log-the-404s-to-a-file" tabindex="-1"><a class="header-anchor" href="#step-1%3A-log-the-404s-to-a-file" aria-hidden="true">#</a> Step 1: Log the 404s to a file</h2><p>I make ample use of <a href="https://www.11ty.dev/docs/data-global/">Eleventy’s global data files</a>, but 404s didn’t feel like something I needed to have as part of the data cascade. Instead, I’m logging them to a YAML file in my <code>./_cache</code> folder. For simplicity, they get logged like this:</p><pre class="language-yml" tabindex="0"><code class="language-yml"><span class="token key atrule"><a href="https://path.to/original/page/that-is-404ing/">https://path.to/original/page/that-is-404ing/</a></span><span class="token punctuation">:</span><span class="token boolean important">true</span></code></pre><p>I chose YAML as it’s about as bare-bones as you can get when it comes to file formats and is pretty easy to work with in the context of Eleventy.</p><h2 id="step-2%3A-add-an-eleventy-data-file-to-my-links-folder" tabindex="-1"><a class="header-anchor" href="#step-2%3A-add-an-eleventy-data-file-to-my-links-folder" aria-hidden="true">#</a> Step 2: Add an Eleventy data file to my links folder</h2><p>If you’re not familiar, Eleventy allows you to create <a href="https://www.11ty.dev/docs/data-template-dir/">directory-level data files</a> that can be used to augment file-level data. I was originally using it to define the <var>layout</var> and <var>permalink</var> front matter variables for all the links using the JSON option, but <a href="https://www.11ty.dev/docs/data-js/">as a JavaScript file, directory-level data becomes even more powerful</a>.</p><p>Setting up your data file is relatively straightforward using <code>module.exports</code>:</p><pre class="language-js" tabindex="0"><code class="language-js">module<span class="token punctuation">.</span>exports <span class="token operator">=</span><span class="token punctuation">{</span><span class="token literal-property property">layout</span><span class="token operator">:</span><span class="token string">“layouts/link.njk”</span><span class="token punctuation">,</span><span class="token literal-property property">permalink</span><span class="token operator">:</span><span class="token string">“/notebook/{{ page.filePathStem }}/”</span><span class="token punctuation">,</span><span class="token literal-property property">eleventyComputed</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token function-variable function">custom_property</span><span class="token operator">:</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token keyword">return</span> some_value_based_on_data<span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre><p>Here I’m defining two static values (<var>layout</var> and <var>permalink</var>) and a computed value (the hypothetical <var>custom_property</var>).</p><h2 id="step-3%3A-consult-the-404-log" tabindex="-1"><a class="header-anchor" href="#step-3%3A-consult-the-404-log" aria-hidden="true">#</a> Step 3: Consult the 404 log</h2><p>As I mentioned, the 404 logging happens separately and results in updates to <code>_cache/404.yml</code>. To make use of all this in the Eleventy data file, I need to set up a few things at the top of the file:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">const</span> fs <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“fs”</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> yaml <span class="token operator">=</span><span class="token function">require</span><span class="token punctuation">(</span><span class="token string">“js-yaml”</span><span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">const</span> cached404s <span class="token operator">=</span> yaml<span class="token punctuation">.</span><span class="token function">load</span><span class="token punctuation">(</span>fs<span class="token punctuation">.</span><span class="token function">readFileSync</span><span class="token punctuation">(</span><span class="token string">“_cache/404s.yml”</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre><p>Here I’m bringing in Node’s File System and <a href="https://www.npmjs.com/package/js-yaml">JS-YAML</a>. Then I am loading the YAML file into memory as <var>cached404s</var>, leveraging those utilities.</p><p>Next up is defining a helper function to search <var>cached404s</var> for a match:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">function</span><span class="token function">is404ing</span><span class="token punctuation">(</span><span class="token parameter">url</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">return</span> url <span class="token keyword">in</span> cached404s<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>This function takes the URL as an argument and returns <code>true</code> or <code>false</code>. Making use of this in the <code>eleventyComputed</code> section is straightforward:</p><pre class="language-js" tabindex="0"><code class="language-js">module<span class="token punctuation">.</span>exports <span class="token operator">=</span><span class="token punctuation">{</span><span class="token literal-property property">layout</span><span class="token operator">:</span><span class="token string">“layouts/link.njk”</span><span class="token punctuation">,</span><span class="token literal-property property">permalink</span><span class="token operator">:</span><span class="token string">“/notebook/{{ page.filePathStem }}/”</span><span class="token punctuation">,</span><span class="token literal-property property">eleventyComputed</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token function-variable function">is_404</span><span class="token operator">:</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token keyword">return</span><span class="token function">is404ing</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>ref_url<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre><p>In my case, <var>ref_url</var> is the front matter field storing the URL I’m linking to from my link blog, so I return the value of passing that to <code>is404ing()</code> as <var>is_404</var>.</p><h2 id="step-4%3A-lean-on-the-wayback-machine" tabindex="-1"><a class="header-anchor" href="#step-4%3A-lean-on-the-wayback-machine" aria-hidden="true">#</a> Step 4: Lean on the Wayback Machine</h2><p>The next thing I want to do is generate a link that has a good chance of working for my readers. Thankfully the <a href="https://web.archive.org/">Wayback Machine</a> has a predictable URL structure for entries and it’s pretty good about handgun redirects to the most temporally-proximate snapshot when you give it a date to work from. Knowing that, I set up another helper function:</p><pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">function</span><span class="token function">archived</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token keyword">let</span> archive_url <span class="token operator">=</span><span class="token string">“<a href="https://web.archive.org/web/">https://web.archive.org/web/</a>{{ DATE }}/{{ URL }}”</span><span class="token punctuation">;</span><span class="token keyword">let</span> month <span class="token operator">=</span> data<span class="token punctuation">.</span>date<span class="token punctuation">.</span><span class="token function">getUTCMonth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">+</span><span class="token number">1</span><span class="token punctuation">;</span>month <span class="token operator">=</span> month <span class="token operator">&lt;</span><span class="token number">10</span><span class="token operator">?</span><span class="token string">“0”</span><span class="token operator">+</span> month <span class="token operator">:</span> month<span class="token punctuation">;</span><span class="token keyword">let</span> day <span class="token operator">=</span> data<span class="token punctuation">.</span>date<span class="token punctuation">.</span><span class="token function">getDay</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>day <span class="token operator">=</span> day <span class="token operator">&lt;</span><span class="token number">10</span><span class="token operator">?</span><span class="token string">“0”</span><span class="token operator">+</span> day <span class="token operator">:</span> day<span class="token punctuation">;</span>archive_url <span class="token operator">=</span> archive_url<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">“{{ DATE }}”</span><span class="token punctuation">,</span><span class="token template-string"><span class="token template-punctuation string"><code>&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;date&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getUTCFullYear&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;month&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;day&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;</code></span></span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">“{{ URL }}”</span><span class="token punctuation">,</span> data<span class="token punctuation">.</span>ref_url<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token keyword">return</span> archive_url<span class="token punctuation">;</span><span class="token punctuation">}</span></code></pre><p>Note: I know this isn’t the most elegant/efficient code, I wanted to show step-by-step what’s happening here.</p><p>This function takes the <var>data</var> object as an argument and composes a URL that points to a snapshot of the given page (<var>data.ref_url</var>) at the time I saved the link (<var>data.date</var>). The <var>data.date</var> value is already a JavaScript date, so it’s pretty easy to turn it into the format the Wayback Machine expects (YYYYMMDD). In the end, this method returns a URL that looks something like this:</p><blockquote><p><a href="https://web.archive.org/web/20150102/http://andregarzia.com/posts/en/whatsappdoesntunderstandtheweb/">https://web.archive.org/web/20150102/http://andregarzia.com/posts/en/whatsappdoesntunderstandtheweb/</a></p></blockquote><p>With that helper in place, I can make use of it within <code>eleventyComputed</code>:</p><pre class="language-js" tabindex="0"><code class="language-js">module<span class="token punctuation">.</span>exports <span class="token operator">=</span><span class="token punctuation">{</span><span class="token literal-property property">layout</span><span class="token operator">:</span><span class="token string">“layouts/link.njk”</span><span class="token punctuation">,</span><span class="token literal-property property">permalink</span><span class="token operator">:</span><span class="token string">“/notebook/{{ page.filePathStem }}/”</span><span class="token punctuation">,</span><span class="token literal-property property">eleventyComputed</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token function-variable function">is_404</span><span class="token operator">:</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token keyword">return</span><span class="token function">is404ing</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>ref_url<span class="token punctuation">)</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token function-variable function">archived</span><span class="token operator">:</span><span class="token punctuation">(</span><span class="token parameter">data</span><span class="token punctuation">)</span><span class="token operator">=&gt;</span><span class="token punctuation">{</span><span class="token keyword">return</span><span class="token function">is404ing</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>ref_url<span class="token punctuation">)</span><span class="token operator">?</span><span class="token function">archived</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token operator">:</span><span class="token boolean">false</span><span class="token punctuation">;</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">,</span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre><p>Now every link in my link blog will have an <var>is_404</var> value that is <code>true</code> or <code>false</code> and an <var>archived</var> value that is either a valid Wayback Machine URL (if the page is 404-ing) or <code>false</code>.</p><h2 id="step-5%3A-using-these-in-the-my-template" tabindex="-1"><a class="header-anchor" href="#step-5%3A-using-these-in-the-my-template" aria-hidden="true">#</a> Step 5: Using these in the my template</h2><p>I use Nunjucks for most of my site’s templating, but you can make use of these computed properties in any supporting templating language. Knowing if a linked URL is 404-ing allows me to</p><ul><li>display the title without a link,</li><li>display the source without a link, and</li><li>provide additional copy about the link’s 404 status and provide the Wayback Machine link instead.</li></ul><p>I am only going to share code with you for that final bit as it should give you enough of a sense of how you can use these properties in the other contexts too.</p><pre class="language-liquid" tabindex="0"><code class="language-liquid"><span class="token liquid language-liquid"><span class="token delimiter punctuation">{%</span><span class="token keyword">if</span> is_404 <span class="token delimiter punctuation">%}</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>This link is 404-ing<span class="token liquid language-liquid"><span class="token delimiter punctuation">{%</span><span class="token keyword">if</span> archived <span class="token delimiter punctuation">%}</span></span>, but<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span><span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span>bookmark<span class="token punctuation">”</span></span><span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">“</span><span class="token liquid language-liquid"><span class="token delimiter punctuation">{{</span> archived <span class="token delimiter punctuation">}}</span></span><span class="token punctuation">”</span></span><span class="token punctuation">&gt;</span></span>you can view anarchived version on the Wayback Machine<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span><span class="token liquid language-liquid"><span class="token delimiter punctuation">{%</span><span class="token keyword">endif</span><span class="token delimiter punctuation">%}</span></span>.<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span><span class="token liquid language-liquid"><span class="token delimiter punctuation">{%</span><span class="token keyword">endif</span><span class="token delimiter punctuation">%}</span></span></code></pre><p>Here you can see I am injecting a <code>footer</code> into the markup when the entry is 404-ing. Within that footer, I note the link’s status. Then I inject some additional text to point to the Wayback Machine’s archive of the page. It’s worth noting that I am being overly cautious here and only injecting the link if <var>post.data.archived</var> is truthy. This will ensure that the link won’t be shown if something fails in my code or I change how I am implementing the <var>archived</var> property.</p><h2 id="crossing-my-fingers" tabindex="-1"><a class="header-anchor" href="#crossing-my-fingers" aria-hidden="true">#</a> Crossing my fingers</h2><p>Relying on an unverified URL, even one at the Wayback Machine, is risky, but so far this approach seems to be working. If you’ve got a link blog suffering from link rot, you might consider setting up something similar. Hopefully this will help jumpstart that project for you.</p>]]></content><amg:twitter><![CDATA[While making some updates to the site, I did a 404 scan of my link blog and the results were… less than awesome. So I decided to work some @eleven_ty magic to recover from them.]]></amg:twitter><amg:summary><![CDATA[While making some updates to the site, I did a 404 scan of my link blog and the results were… less than awesome. So I decided to work some Eleventy magic to recover from them.]]></amg:summary><summary type="html"><![CDATA[<p>While making some updates to the site, I did a 404 scan of my link blog and the results were… less than awesome. So I decided to work some Eleventy magic to recover from them.</p>]]></summary><category term="this site" /><category term="URLs" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.aaron-gustafson.com/i/posts/2022-08-31/hero.jpg" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/links/no-comment-2-the-webmentioning/</id><title type="html"><![CDATA[🔗 No Comment 2: The Webmentioning]]></title><link href="https://www.aaron-gustafson.com/notebook/links/no-comment-2-the-webmentioning/" rel="alternate" type="text/html" /><link href="https://lukeb.co.uk/blog/2022/06/28/no-comment-2-the-webmentioning/" rel="related" type="text/html" /><published>2022-07-29T16:50:26Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>You can now use webmentions in Eleventy via a plugin rather than rolling the whole thing yourself.</p>]]></content><amg:twitter><![CDATA[You can now use webmentions in Eleventy via a plugin rather than rolling the whole thing yourself.]]></amg:twitter><amg:summary><![CDATA[You can now use webmentions in Eleventy via a plugin rather than rolling the whole thing yourself.]]></amg:summary><summary type="html"><![CDATA[<p>You can now use webmentions in Eleventy via a plugin rather than rolling the whole thing yourself.</p>]]></summary><category term="this site" /><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://lukeb.co.uk/static/images/logo-banner.png" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/my-own-personal-pwa/</id><title type="html"><![CDATA[✍🏻 My own, personal, PWA]]></title><link href="https://www.aaron-gustafson.com/notebook/my-own-personal-pwa/" rel="alternate" type="text/html" /><published>2019-04-26T16:00:39Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Progressive Web Apps are often something we think of as building for others, but while I was redoing the Service Worker implementation on this site—to improve performance for you, dear reader—I decided to throw in a little goody for me as well, in the form of the <a href="https://wicg.github.io/web-share-target/">Share Target</a>.</p><p>Early last year, <a href="https://timkadlec.com/remembers/2018-02-06-saving-links-to-my-site-with-a-bookmarklet/">Tim Kadlec shared his bookmarklet for saving links to his site</a>. I thought it was a brilliant setup, especially for a static site:</p><ol><li>The bookmarklet captures the URL, page title, and any selection you’ve made and pipes it over to a form on your site;</li><li>The form contains the code that will generate a new static file in you site’s GitHub repo.</li></ol><p>Smartly, the form requires you to log in with your GitHub credentials. That keeps it from being abused by others. Sadly, I always struggled to get this setup working fluidly on mobile. With the introduction of the Share Target, it’s become a lot easier.</p><p>You define a Share Target in your <a href="https://developer.mozilla.org/docs/Web/Manifest">Web App Manifest</a>. The original design only allowed for links and text to be shared, but version 2 is coming soon with support for any file type.<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup> Pretty cool stuff! As you’d expect, the key is <code>share_target</code> and it takes a JSON object that looks a lot like a form configuration:</p><pre class="language-json" tabindex="0"><code class="language-json"><span class="token property">“share_target”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“action”</span><span class="token operator">:</span><span class="token string">“linky/poo/”</span><span class="token punctuation">,</span><span class="token property">“method”</span><span class="token operator">:</span><span class="token string">“GET”</span><span class="token punctuation">,</span><span class="token property">“enctype”</span><span class="token operator">:</span><span class="token string">“application/x-www-form-urlencoded”</span><span class="token punctuation">,</span><span class="token property">“params”</span><span class="token operator">:</span><span class="token punctuation">{</span><span class="token property">“title”</span><span class="token operator">:</span><span class="token string">“title”</span><span class="token punctuation">,</span><span class="token property">“text”</span><span class="token operator">:</span><span class="token string">“body”</span><span class="token punctuation">,</span><span class="token property">“url”</span><span class="token operator">:</span><span class="token string">“url”</span><span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre><p>The first key is the <code>action</code> page. In this case, it points at my link posting form. I want the shared data passed via the query string, so I’m using GET as the method (but you could use other HTTP request methods as well). I set the encoding (<code>enctype</code>) and then identify the parameters I want to send and what they should be called in the payload.</p><p>With this in place, I installed my site on my phone and could immediately share links directly to it:</p><figure id="fig-2019-04-26-01" class="media-container"><p><img src="https://www.aaron-gustafson.com/i/posts/2019-04-26/share-target.png" alt=""></p><figcaption>This screenshot shows my PWA as a share target available within Android.</figcaption></figure><p>On the receiving end, everything works pretty well. Android doesn’t support sharing selected text along with the title of the page and the URL (like Tim’s bookmarklet does), but I can always copy the text I want to quote before I share the page. Another oddity in Android is that it currently sends the URL over as the body for some strange reason, but I set the JavaScript up on the resulting page to enable me to look for a URL in that field and pop it into the right spot. That way, when Android fixes the issue, it won’t cause any issues with a true text body (like your text selection—hint, hint).</p><p>Another nice enhancement I added to the form was autocomplete for the tag field. Using <a href="https://developer.mozilla.org/docs/Web/HTML/Element/datalist">a <code>datalist</code> element</a>, I have all of my site’s tags ready to autocomplete that field. Unfortunately, autocomplete doesn’t work great for multiple items out of the box, so I got some inspiration from <a href="https://stackoverflow.com/posts/47232367/revisions">this StackOverflow solution</a> and implemented <a href="https://github.com/aarongustafson/aaron-gustafson.com/blob/4bd713d2440edd1fd33dab3a22292af60b9e93b3/linky/poo.html#L353-L436">a vanilla JavaScript multiple choice <code>datalist</code>-driven input</a>. Sweet!</p><p>While we often think of PWAs as being something we build for others, I’m totally stoked that I can also add PWA functionality that’s just for me (or, more broadly, internally-focused). That’s pretty exciting and demonstrates just how powerful and adaptable PWAs are.</p><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>Incidentally, the <a href="https://docs.microsoft.com/en-us/windows/uwp/app-to-app/receive-data">Share Target implementation for Universal Windows Apps</a> has supported file type association for a long time now, which is why the Windows Store version of Twitter (which is a PWA) can receive images, videos, and more, right from the File Manager. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:summary><![CDATA[While I was redoing the Service Worker implementation on this site —to improve performance for you, dear reader—I decided to throw in a little goody for me as well.]]></amg:summary><summary type="html"><![CDATA[<p>While I was redoing the Service Worker implementation on this site —to improve performance for you, dear reader—I decided to throw in a little goody for me as well.</p>]]></summary><category term="progressive web apps" /><category term="this site" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/moved-from-octopress-to-jekyll/</id><title type="html"><![CDATA[✍🏻 Moved from Octopress to Jekyll]]></title><link href="https://www.aaron-gustafson.com/notebook/moved-from-octopress-to-jekyll/" rel="alternate" type="text/html" /><published>2017-03-23T18:43:26Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>A while back I mentioned my desire to move this site from <a href="http://octopress.org/">Octopress</a> to <a href="https://jekyllrb.com/">Jekyll</a>. I liked Octopress when I re-christened this site, but I didn’t really see much benefit to moving to <a href="https://github.com/octopress/octopress">Octopress 3</a> over stripping away the Octopress 2 bits and going with a core Jekyll install.</p><p>Octopress was built on top of Jekyll, so I figured the move would be fairly easy. On the whole, it was. I was able to move the content over without too much difficulty. I ended up having to back out some of the Octopress plugins and either write the functionality myself or look for alternatives. Thankfully none were a terribly big deal.</p><p>The only major change was to the overall structure of the site. I opted to move <a href="/notebook/">my blog</a> and <a href="/notebook/links/">linkblog</a> into <a href="https://jekyllrb.com/docs/collections/">Jekyll collections</a>, which worked out pretty well. I opted to go with the newer <a href="https://github.com/sverrirs/jekyll-paginate-v2"><code>jekyll-paginate-v2</code> gem</a> to build out the archive pages for each and used <a href="https://github.com/pattex/jekyll-tagging"><code>jekyll-tagging</code></a> to generate the tag archive pages. In the process, I also moved all of my post “categories” over to being “tags”, since that was more appropriate to how I was using them anyway.</p><p>It took a few days to get it to the point where I feel I can merge the Jekyll-only branch back and publish for the first time, but I think it is pretty stable. That said, there are likely some bugs yet to be discovered. Please let me know if you find one; more sets of eyes are always better.</p>]]></content><amg:summary><![CDATA[It took a few days, but I think the site is pretty stable in Jekyll now. That said, there are likely some bugs yet to be discovered. Let me know if you find one.]]></amg:summary><summary type="html"><![CDATA[<p>It took a few days, but I think the site is pretty stable in Jekyll now. That said, there are likely some bugs yet to be discovered. Let me know if you find one.</p>]]></summary><category term="this site" /><category term="Octopress" /><category term="Jekyll" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/any-advice-on-moving-from-octopress-to-jekyll/</id><title type="html"><![CDATA[✍🏻 Any Advice on Moving From Octopress to Jekyll?]]></title><link href="https://www.aaron-gustafson.com/notebook/any-advice-on-moving-from-octopress-to-jekyll/" rel="alternate" type="text/html" /><published>2017-01-21T16:55:37Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Next Tuesday I’m planning to take the day to do a little refresh on this site. The largest part of that effort will be moving this project off of <a href="http://octopress.org/">Octopress</a> to <a href="https://jekyllrb.com/">Jekyll</a>. I’m not expecting it to be a huge challenge—Jekyll underpins Octopress anyway—but I’m sure there are some gotchas I should look out for. If you’ve made the leap yourself in the past and have any advice or recommended reading, please leave a comment or <a href="https://indieweb.org/Webmention">Web Mention</a>.</p><p>In case you’re curious why I’m making the shift, it’s pretty simple:</p><ul><li>Octopress development seems <a href="https://github.com/octopress/octopress/graphs/code-frequency">relatively stalled</a>;</li><li>Octopress includes dependencies I don’t really need;</li><li>I’ve been <a href="https://github.com/aarongustafson?utf8=%E2%9C%93&amp;tab=repositories&amp;q=jekyll&amp;type=public&amp;language=ruby">writing a lot of Jekyll plugins</a> and maintaining them without using them on the latest Jekyll is challenging;</li><li>I’m comfortable writing my own <a href="http://rake.rubyforge.org/">Rake</a> commands; and</li><li>This is the big one: <a href="http://idratherbewriting.com/2015/11/04/jekyll-30-released-incremental-regeneration-rocks/">Incremental builds</a>.</li></ul><p>I’m hopeful this shift will make it easier for me to get content published more quickly and more reliably. I may even move my build processes into <a href="https://travis-ci.org/">Travis CI</a> to further offload the work. But I’m getting ahead of myself… let’s see how Tuesday goes first.</p>]]></content><amg:twitter><![CDATA[I’m moving from @octopress to @jekyllrb on Tuesday. Any advice or recommended reading?]]></amg:twitter><amg:summary><![CDATA[Next Tuesday I’m planning to take the day to do a little refresh on this site. The largest part of that effort will be moving this project off of <a href="http://octopress.org/">Octopress</a> to <a href="https://jekyllrb.com/">Jekyll</a>. I’m not expecting it to be a huge challenge—Jekyll underpins Octopress anyway—but I’m sure there are some gotchas I should look out for. If you’ve made the leap yourself in the past and have any advice or recommended reading, please leave a comment or <a href="https://indieweb.org/Webmention">Web Mention</a>.]]></amg:summary><summary type="html"><![CDATA[<p>Next Tuesday I’m planning to take the day to do a little refresh on this site. The largest part of that effort will be moving this project off of <a href="http://octopress.org/">Octopress</a> to <a href="https://jekyllrb.com/">Jekyll</a>. I’m not expecting it to be a huge challenge—Jekyll underpins Octopress anyway—but I’m sure there are some gotchas I should look out for. If you’ve made the leap yourself in the past and have any advice or recommended reading, please leave a comment or <a href="https://indieweb.org/Webmention">Web Mention</a>.</p>]]></summary><category term="this site" /><category term="Jekyll" /><category term="Octopress" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/moved-to-https/</id><title type="html"><![CDATA[✍🏻 Moved to HTTPS]]></title><link href="https://www.aaron-gustafson.com/notebook/moved-to-https/" rel="alternate" type="text/html" /><published>2015-09-03T20:06:03Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I’ve been complaining about <a href="https://www.aaron-gustafson.com/notebook/more-proof-we-dont-control-our-web-pages/">“man in the middle” attacks brought on by internet service providers</a> a bunch over the last year. The only way to keep uninvited third parties from injecting JavaScript and more—potentially screwing up your page—is to move to HTTPS. So, as much as it pains me to abandon good old fashioned HTTP, I’ve decided to lock things down a bit.</p><p>I was using <a href="https://github.com/">Github</a> to host my site as a <a href="https://pages.github.com/">Github page</a>. It worked really well given this is a static site, but you can’t run Github-hosted sites under HTTPS unless you go with their <code>*.github.io</code> domain name (they have a <a href="https://en.wikipedia.org/wiki/Wildcard_certificate">wildcard certificate</a> for that domain). There’s been <a href="https://github.com/isaacs/github/issues/156">a ton of interest in Github allowing custom cert installation, but no movement yet</a>, so… <i>onward!</i><sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup></p><p>I opted to move to <a href="https://www.digitalocean.com/?refcode=5270a681c6fe">DigitalOcean</a> since <a href="http://easy-designs.net">my consultancy</a> recently relocated all of its sites there in a mass exodus from MediaTemple. Migrating the site was as simple as <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-automatic-deployment-with-git-with-a-vps">setting up the DigitalOcean server as a new “live” <code>remote</code> on my local git install</a> and pushing it up there. Since it’s a static site, I didn’t have to worry too much about the server config. Apache is really great at hosting static files.</p><p>With the contents in place, I went through <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-apache-with-a-free-signed-ssl-certificate-on-a-vps">the rather convoluted process of getting SSL set up following the instructions from DigitalOcean</a>. I opted for the free <a href="http://www.startssl.com/">StartSSL</a> certificate to begin with (a rather convoluted process, but we got there in the end) and then flipped the DNS records to point to the new box. Given that the StartSSL certificate needs to be renewed every 30 days, I may opt for a paid certificate in the not too distant future.</p><p>Once the DNS propagated, I had to go back and button up a few scripts that were requesting non-HTTPS content. I also had to tweak my Jekyll plugins and Rake tasks to include the legacy “http://&quot; URLs when querying for webmentions and the like (since I didn’t want to lose those references). I also updated the Apache’s <code>VirtualHost</code> configuration for the non-secure site to make all traffic redirect:</p><p>Redirect permanent / <a href="https://www.aaron-gustafson.com/">https://www.aaron-gustafson.com/</a></p><p>All in all, it was a relatively painless migration. Admittedly, the initial re-build of the site (after updating the Rake tasks) did re-submit all of the webmentions I’d previously sent in order to provide the new address. If I referenced you a bunch in the past, I apologize for the flood of traffic, but it had to be done.</p><p>Anyway, so now this site is running under HTTPS. If you encounter any issues, please let me know. And if you want to read a really good account of migrating a site to HTTPS, you should definitely <a href="https://adactio.com/articles/7435">read Jeremy Keith’s step-by-step guide</a>.</p><hr class="footnotes-sep"><section class="footnotes"><h4 class="hidden">Footnotes</h4><ol class="footnotes-list"><li id="fn1" class="footnote-item"><p>It’s worth noting that <a href="https://github.com/aarongustafson/aaron-gustafson.com/tree/main/">the source of the site</a> will remain on Github for the forseeable future. <a href="#fnref1" class="footnote-backref">↩︎</a></p></li></ol></section>]]></content><amg:summary><![CDATA[So, as much as it pains me to abandon good old fashioned HTTP, I’ve decided to lock things down a bit.]]></amg:summary><summary type="html"><![CDATA[<p>So, as much as it pains me to abandon good old fashioned HTTP, I’ve decided to lock things down a bit.</p>]]></summary><category term="web design" /><category term="security" /><category term="this site" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/the-linkblog/</id><title type="html"><![CDATA[✍🏻 The Linkblog]]></title><link href="https://www.aaron-gustafson.com/notebook/the-linkblog/" rel="alternate" type="text/html" /><published>2015-01-14T14:34:34Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>Since re-starting my blog I’ve been continuing to tinker with Octopress and Jekyll in an effort to customize things a bit more to my liking.</p><p>I recently began posting links (with commentary) in a bit of a link blog, but I wasn’t really happy with having it mixed in with the rest of my Notebook posts. I finally took a few minutes to formally bust out the links into <a href="/notebook/links/">their own paginated section</a>, so you can keep up with them independently. I also set up a three distinct Atom feeds to let you consume this site’s content how you want to: <a href="/feeds/all.xml">Latest 20 posts and links</a>, <a href="/feeds/latest-posts.xml">latest 20 posts</a>, and <a href="/feeds/latest-links.xml">Latest 20 links</a>.</p><p>I’m hopeful this organization will prove as helpful to you as it is for my compartmentalization anxiety.</p>]]></content><amg:summary><![CDATA[I’m doing a little housekeeping and cleaning up the links a bit. The linkblog now has its own permenant home.]]></amg:summary><summary type="html"><![CDATA[<p>I’m doing a little housekeeping and cleaning up the links a bit. The linkblog now has its own permenant home.</p>]]></summary><category term="blogging" /><category term="web design" /><category term="this site" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/indecisiveness-and-urls/</id><title type="html"><![CDATA[✍🏻 Indecisiveness and URLs]]></title><link href="https://www.aaron-gustafson.com/notebook/indecisiveness-and-urls/" rel="alternate" type="text/html" /><published>2014-12-16T15:23:15Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>If you know me, you know I am a pretty indecisive guy. It is not uncommon for <a href="https://twitter.com/shirleytemper">Kelly</a> and I to spend 15 minutes or more just trying to figure out where we want to grab a meal.</p><p>What I’m trying to say (or rather excuse) is that I have been struggling with some of my initial decisions with respect to URLs on this site and finally decided to make some changes before too many links to its content get out there. I was a bit reluctant to do so as we all know <a href="http://www.w3.org/Provider/Style/URI.html">what Sir Tim Berners-Lee says</a></p><blockquote><p>Cool URIs don’t change.</p></blockquote><p>Well, consider me uncool as I decided to change some things around here:</p><ol><li>I dropped the year indicator from blog post URLs. I realized that the likelihood of me having two posts with the same name was pretty much nil, so it was unnecessary to disambiguate like that. Plus it would save me from having to create the annual archives I would feel compelled to make in order to justify the “2014” directory in the URL.</li><li>I consolidated my book and article pages to <a href="/publications">a single page or publications</a>. I thought it might be nice to maintain them separately, but in retrospect that seems unnecessarily complicated.</li><li>I changed the URL to my speaking engagements from “events” to “speaking-engagements” as it just made more sense.</li></ol><p>On the off chance you ever consider changing URLs on a Jekyll or Octopress site, I thought I’d share my process.</p><h2 id="redirecting-old-links" tabindex="-1"><a class="header-anchor" href="#redirecting-old-links" aria-hidden="true">#</a> Redirecting Old Links</h2><p>I was quite concerned concerned about old links being broken in this site. It’s just not a good thing to do.</p><p>In a traditional hosting scenario, I could use <code>.htaccess</code> to set up <a href="https://en.wikipedia.org/wiki/HTTP_302">302 redirects</a>, but I am hosting on Github so that isn’t an option. On top of that, this site is built using Octopress (and Jekyll), so there is no dynamic system in place to programmatically manage those redirects.</p><p>Thankfully, there is <a href="https://github.com/jekyll/jekyll-redirect-from">a plugin for Jekyll to manage redirects</a>. With it, you can redirect from an an existing page in the YAML front matter using the <code>redirect_to</code> key or you can use the <code>redirect_from</code> key in the YAML front matter on the destination page. I ended up using the former for old pages (articles, books, and events), and the latter for blog posts.</p><p>The plugin covers all the bases. It generates pages at the old URLs that redirect using the good old <code>meta</code> refresh, a JavaScript redirect, and a fallback link just in case neither of those work. Oh, and it sets the new URL as the <a href="https://support.google.com/webmasters/answer/139066?hl=en">canonical reference</a> to boot.</p><p>Done and done.</p><h2 id="keeping-webmentions" tabindex="-1"><a class="header-anchor" href="#keeping-webmentions" aria-hidden="true">#</a> Keeping Webmentions</h2><p>As I mentioned a few weeks back, I wrote <a href="/notebook/enabling-webmentions-in-jekyll/">a Jekyll plugin to enable webmentions</a>. As web mentions are tied to the “mentioned” URL, changing a post’s URL was going to cause me to lose any previous webmentions. I didn’t like that idea, so <a href="https://github.com/aaronpk/webmention.io/issues/31">I talked to Aaron about adding multiple URL support to the webmention.io API</a> and he agreed it was a good idea.</p><p>The feature landed late last week and I adjusted <a href="https://github.com/aarongustafson/jekyll-webmention_io">my Jekyll Webmention.io plugin</a> to allow you to supply multiple URLs. While I was at it, I did some other upgrades: I added caching, downloading of webmention titles if the API didn’t supply one, and a test for the existence of avatars before inserting them (so you don’t end up with missing images).</p><p>If you were using the plugin, I definitely recommend upgrading as it performs a lot better now. I also added <a href="https://github.com/aarongustafson/jekyll-webmention_io/blob/master/webmention.Rakefile">a Rake task for sending webmentions</a> which is super handy.</p><h2 id="pardon-my-dust" tabindex="-1"><a class="header-anchor" href="#pardon-my-dust" aria-hidden="true">#</a> Pardon My Dust</h2><p>I apologize for changing URLs on you, but I am hopeful this will be the last major change on the site. As it (currently) says at the top, <a href="/notebook/a-grand-experiment/">this is an open redesign</a>, so there are bound to be a few bumps here and there. That said, I will try to keep them to a minimum in the future.</p>]]></content><amg:summary><![CDATA[I have been struggling with some of my initial decisions with respect to URLs on this site and finally decided to make some changes before too many links to its content get out there.]]></amg:summary><summary type="html"><![CDATA[<p>I have been struggling with some of my initial decisions with respect to URLs on this site and finally decided to make some changes before too many links to its content get out there.</p>]]></summary><category term="web design" /><category term="this site" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/a-grand-experiment/</id><title type="html"><![CDATA[✍🏻 A Grand Experiment]]></title><link href="https://www.aaron-gustafson.com/notebook/a-grand-experiment/" rel="alternate" type="text/html" /><published>2014-07-08T00:46:48Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>So, a mere three years after my old “life blog” stopped working, I decided to scrap it and start fresh.</p><p>In my quest to learn something new (more on that in a forthcoming post), I decided to give <a href="http://octopress.org">Octopress</a> a whirl. We’ll see how it goes, but the content entry has gone well so far and the Octopress community has been pretty responsive to my requests. I just need to get used to the workflow.</p><p>In the spirit of openness, I’ve decided to host the new site on <a href="https://github.com">Github</a> as a <a href="https://pages.github.com/">Github Page</a>. This frees me up from worrying about hosting fees of course, but it also means this site can serve as an educational tool for those inclined to dig in to the work I do and want some introspection into the way I do it. You can <a href="https://github.com/aarongustafson/aaron-gustafson.com/">view (and fork) the whole darn thing</a> at your leisure.</p><p>Finally, I’m designing and building this site in the open and, for posterity, taking screen shot of the progress. The screenshots will go up in time as an animated GIF, but those of you who wish will be able to follow along at home or work, seeing how I typically piece together a site, layer by layer, using <a href="https://adaptivewebdesign.info">progressive enhancement</a>. So far it’s just the content, but that’s what I preach: content first.</p><p>Now it’s time to get back to it. Feel free to reach out if you have any questions.</p>]]></content><amg:summary><![CDATA[So, a mere three years after my old “life blog” stopped working, I decided to scrap it and start fresh.]]></amg:summary><summary type="html"><![CDATA[<p>So, a mere three years after my old “life blog” stopped working, I decided to scrap it and start fresh.</p>]]></summary><category term="blogging" /><category term="web design" /><category term="this site" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/were-back-sort-of/</id><title type="html"><![CDATA[✍🏻 We’re back (sort of)]]></title><link href="https://www.aaron-gustafson.com/notebook/were-back-sort-of/" rel="alternate" type="text/html" /><published>2009-04-22T11:05:51Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>After making a ridiculously stupid mistake by axing the server that hosted this blog (without checking that I had actually moved it to the new server and without making sure I had a backup of the DB), Easy! Reader is back. Sort of. Thankfully, I had a backup from late ‘06 and I haven’t been an incredibly prolific blogger in the time since that backup. And, thanks to the Internet Archive, it looks like we should be able to recover all but one article (my last post, from about a year ago) from the ether. It may take a little time, but we should have it all up in the next few weeks.</p><p>So what’s going on? Well, a lot.</p><p>For one, we’ve relocated from New Haven, CT to Chattanooga, TN after being urged to visit by <a href="http://shauninman.com">Mr. Shaun Inman</a> and his lovely bride <a href="http://morellc.com">Leslie</a> and falling in love with this awesome city. We made the move in August of last year and, after spending a few months in an apartment, have bought a house and will be moving in this weekend. Chattanooga is an amazing place. There’s always something going on, it has a wonderful art scene and tech community, and is nestled in the mountains, right along the Tennessee River. It has many of the perks of Portland, OR and San Francisco, CA (other cities we considered moving to), but at 1/4-1/3 the cost. I couldn’t ask for a better place to live.</p><p>Since relocating, Easy! Designs has also been growing. We’ve taken on two interns – Matt Turnure and Sean McCarthy – and have been joined full-time by both Dave Stewart (who I had previously worked with in CT) and Matt Harris (an excellent developer from the UK), so expect to be hearing from them on this site soon as well. In addition to our client work, we’ve been busily coding away on a few products of our own that should hopefully see the light of day in the coming year. We’re also working on a relaunch of our own website and this blog.</p><p>Finally, there’s <a href="http://eCSStender.org">eCSStender</a>. I’ve been working on this project for ages and it’s currently in a closed beta. Things are progressing smoothly on its development though and I expect it will be ready for its initial public release in the coming weeks.</p><p>Anyway, that’s the nickel tour of the changes. I apologize profusely for rendering this blog pretty useless with my error, but hopefully we’ll have it all back up and running shortly.</p><p>PS - If you happen to have an archive of my blog post on IE8 Standards Mode, please forward a copy of it to me. I can’t seem to find it even though it appears to be somewhere in the Google Cache.</p>]]></content><amg:summary><![CDATA[After making a ridiculously stupid mistake by axing the server that hosted this blog (without checking that I had actually moved it to the new server and without making sure I had a backup of the DB ), Easy! Reader is back. Sort of…]]></amg:summary><summary type="html"><![CDATA[<p>After making a ridiculously stupid mistake by axing the server that hosted this blog (without checking that I had actually moved it to the new server and without making sure I had a backup of the DB ), Easy! Reader is back. Sort of…</p>]]></summary><category term="this site" /><category term="personal" /></entry><entry><id>https://www.aaron-gustafson.com/notebook/whoops/</id><title type="html"><![CDATA[✍🏻 Whoops…]]></title><link href="https://www.aaron-gustafson.com/notebook/whoops/" rel="alternate" type="text/html" /><published>2007-06-26T15:58:42Z</published><content type="html" xml:base="https://www.aaron-gustafson.com"><![CDATA[<p>I was doing a little server cleanup and moved this site’s folder, forgetting to set the new folder up for mod_rewrite, so permalinks have been broken for the last week or so. Everything is better now (I hope). Please let me know if you notice any lingering issues.</p>]]></content><amg:summary><![CDATA[I was doing a little server cleanup and moved this site’s folder, forgetting to set the new folder up for mod_rewrite, so permalinks have been broken for the last week or so. Everything is better now (I hope). Please let me know if you…]]></amg:summary><summary type="html"><![CDATA[<p>I was doing a little server cleanup and moved this site’s folder, forgetting to set the new folder up for mod_rewrite, so permalinks have been broken for the last week or so. Everything is better now (I hope). Please let me know if you…</p>]]></summary><category term="this site" /></entry></feed>