Matthew Roach All articles from matthewroach.me 2021-02-07T00:00:00+00:00 https://matthewroach.me/ Matthew Roach feed@matthewroach.me JAMStack URL Shortener with Netlify, 11ty and GitHub 2021-02-07T00:00:00+00:00 https://matthewroach.me/jamstack-url-shortener-with-netlify-11ty-and-github/ <h2>Why...</h2> <p>I wanted a place where I can &quot;bookmark&quot; things I come across on the internet. Either for reading later, sharing or to keep a record of it for future reference.</p> <p>For the sharing part I wanted to a way to use a shortened style link</p> <p>Also, I want to own all this data. Along with not having to maintain a web application.</p> <p><strong>What if I could do all this with static site generation tool 11ty, and Netlify?</strong></p> <h2>How...</h2> <p>Well turns out it's very possible, and much easier than you might think.</p> <p>Broken down into the following parts</p> <ul> <li>Netlify to host the site and handle the URL redirection</li> <li>Eleventy to handle the website output and redirect file</li> <li>GitHub issues as a UI to add items</li> <li>GitHub actions to handle adding the content from the issue to the site</li> </ul> <p>And the output of all this is my own URL shortner/bookmark &quot;application&quot; - https://roach.link</p> <h2>Netlfiy</h2> <p>For the Netlify part it's very straight forward. The provide a way to host a site, and also a mechanism for handling redirects. Setting up a site is pretty straight forward from their UI with a few customisable options. For the <a href="https://www.netlify.com/blog/2019/01/16/redirect-rules-for-all-how-to-configure-redirects-for-your-static-site/">redirect</a> part as long as you have a <code>_redirects</code> file in the directory you want to serve your site from they take care of the rest. The also allow you have use a <code>netlify.toml</code> file.</p> <h2>Eleventy (11ty)</h2> <p>For the <a href="https://www.11ty.dev/">11ty</a> part I went for a very similar set up to my personal site (this site) set up. Each link would be a markdown file inside a <code>posts</code> directory that looks like this</p> <pre><code>--- title: &quot;Matthew Roach&quot; date: &quot;2021-01-24&quot; permalink: &quot;b/view/&quot; link: &quot;https://matthewroach.me&quot; tags: [&quot;personal&quot;, &quot;blog&quot;] --- Personal website of Matthew Roach </code></pre> <p>The frontmatter data is where the majority of the needed data is housed. You may notice the <code>permalink</code> seems a little odd why is it <code>b/view</code>? The <code>b</code> part is the URL shortner unique ID, and the view part is used to build the output for each post to be <code>roach.link/b/view</code> allowing you to view the individual bookmark item (single view page still work in progress).</p> <p>Hang on.. You said unique ID... That's going to be fun to maintain I hear you ask. Well it would be if you were trying to do it by hand. But I am not, I am using an <code>npm</code> package called <a href="https://www.npmjs.com/package/bijective-link-shortener">bijective-link-shortener</a> to increment them each time. Which is handled with GitHub as you'll see later.</p> <h3>Redirect file</h3> <p>Without the ability to do redirection a URL shortner isn't much use, and as previously mentioned Netlfiy allows you to do redirects if you provide either a <code>_redirects</code> or <code>netlify.toml</code> file in the root of the published site. And with 11ty this is made super simple to generate, as with 11ty you can configure it to output the input file to any output file (give or take), by setting the <code>permalink</code> to be the output.</p> <p>Given all the bookmarks are all going to be individual files in a folder I set up a new collection in the <code>.eleventy.js</code> config file named <code>allLinks</code> and reversed the collection so the newest would come first.</p> <p>With a file named <code>_redirects.11ty.js</code> in my <code>src</code> directory I can consume the <code>allLinks</code> collection and make 11ty render out the links in a format needed for Netlify. The <code>_redirects.11ty.js</code> looks as follows</p> <pre><code>class Redirects { data() { return { permalink: &quot;netlify.toml&quot; }; } render(data) { return data.collections.allLinks.map(l =&gt; ` [[redirects]] from = &quot;${l.url.split('/')[1]}&quot; // permalink is in format {shortLink}/view to = &quot;${l.data.link}&quot;` ).join(&quot;&quot;) } } module.exports = Redirects; </code></pre> <p>Broken down the file is looping over the <code>allLinks</code> collection, and setting up the Netlify redirect file in the format they require by using the permalink data (first part) and the link from each posts frontmatter data. As as the <code>permalink</code> of this file is set to <code>netlify.toml</code> it will create that file based on the <code>render</code> method.</p> <p>The output file looks as follows</p> <pre><code>[[redirects]] from = &quot;b&quot; to = &quot;https://matthewroach.me&quot; </code></pre> <h2>GitHub</h2> <p>GitHub is where I am hosted the source code and then have Netlify linked to the repo to build the site and publish on each commit to the <code>main</code> branch.</p> <h3>Issues</h3> <p>But GitHub is also the primary way I add new data to the site. Rather than having to create a new file and fill in all the frontmater data, and do a host of git commands I wanted a way I could actually use this &quot;application&quot; with minimal effort. Turns out GitHub issues is a <s>great</s> perfectly acceptable way to be the UI for adding new bookmarks... That is if you are willing to make a small adjustment.</p> <p>So, GitHub issues provides all but one of the interface items I needed. This is how I mapped the GitHub issue UI to the frontmatter data</p> <ul> <li>Issue Comment = The post body</li> <li>Labels = Tags</li> <li>Title = Title and link</li> </ul> <p>Title = title and link? That seems odd. Well turns out the issue UI doesn't really have a field or place I could enter the URL for the item I want to bookmark. So I went with the format of making the title as follows:</p> <pre><code>Matthew Roach Blog::https://matthewroach.me </code></pre> <p>The two colons has no meaning other than for when the GitHub action runs and splits the title using <code>title.split('::')</code> to parse it into two parts. The first part is the Title of the Site, and the second is then used in the link frontmatter data field.</p> <h3>Actions</h3> <p><a href="https://github.com/features/actions">GitHub actions</a> is the glue that makes all the previous steps work together. Without actions the process of adding bookmarks would be very manual. Actions are a way for you to automate workflows from your GitHub repository. Based on an action you can configure an action to run and do &quot;things&quot;. Those &quot;things&quot; are what makes this all possible.</p> <p>As mentioned I am using GitHub issues as a UI to add new bookmarks. Broken down this works as follows</p> <ol> <li>Issue <ol> <li>Add a new issue to my roach.link repository</li> <li>Once I am happy with the description and tags</li> <li>Mark the issue as closed</li> </ol> </li> <li>GitHub action runs on issue closed event <ol> <li>The action is triggered and starts its &quot;steps&quot;</li> <li>Action checks out code, sets up node and makes the issue object available on an environment variable</li> <li>Then a node command runs a script <code>node new-item-from-issue.js</code> 1. Within the script I have access to the issue object by doing <code>JSON.parse(process.env.ISSUE_CONTEXT)</code> 2. Script checks how many posts are currently in the <code>posts</code> folder, and using the package <code>bijective-link-shortener</code> gets the next short link value 3. Using the data from the issue object and the short link value from the previous step I use <code>transformAndWriteToFile</code> from the package <code>json-to-frontmatter-markdown</code> to create a new markdown file in the format noted above</li> <li>Add and commit is then run from within the action to add the newly created file to the repo</li> </ol> </li> <li>Netlify is triggered due to a new commit on the <code>main</code> branch <ol> <li>Netlify runs its build steps I have configured</li> </ol> </li> <li>New version of site is live at <a href="https://roach.link/">https://roach.link</a> <ol> <li>Homepage is updated</li> <li>New redirect added - example: <a href="https://roach.link/b">https://roach.link/b</a></li> </ol> </li> </ol> VSCode focus between terminal and code 2021-01-30T00:00:00+00:00 https://matthewroach.me/vscode-focus-between-terminal-and-code/ <p> I recently began to utilise the integrated terminal for VSCode more. And being a big fan of using my keyboard to switch around different parts is huge part of my work flows. One of my sticking points with the terminal in VSCode is by default you can not switch into and out of the terminal easily. </p> <p> For example if I am editing some code, then need to switch to the terminal to run some commands, and then switch back to the code window. I got so used to using command + tab to switch windows. Wanting to try and use VSCode more it turns out this was a sticking point of my flow. With some digging and trial and error it turns out you can setting up a custom keybinding to handle this. </p> <p> As there was already a default key binding to open a terminal, once opened you can not easily switch back. But with VSCode and key bindings you are able to use the <code>when</code> key to tell a key binding "when" something can be run. This allowed me to set the same key binding used to focus terminal as to focus back to the editor. Below is instructions and the key binding JSON to achieve this on a Mac. </p> <h2>Instructions for Mac</h2> <ol> <li>Code > Preferences > Keyboard Shortcuts</li> <li> This opens a GUI for keyboard shortcuts - click on the tab "Keyboard Shortcuts", and an icon with a file and arrow will appear (title - Open Keyboard Shortcuts JSON) </li> <li>Copy paste the below JSON into the file and save</li> <li>Done</li> </ol> <pre> [ { "key": "ctrl+`", "command": "workbench.action.terminal.focus" }, { "key": "ctrl+`", "command": "workbench.action.focusActiveEditorGroup", "when": "terminalFocus" } ] </pre> <hr> <p> Another VSCode tip - <a href="https://matthewroach.me/vscode-zoom-only-font-not-whole-editor/">VSCode zoom only font not whole editor</a>. </p> Web Share API - Progressively enhanced web component 2021-01-24T00:00:00+00:00 https://matthewroach.me/web-share-api-progressively-enhanced-web-component/ <p><em>How I implemented a share button at the bottom of my blog posts on this site using the Web Share API and web components. Scroll to the bottom and see if your browser supports the Web Share API... You'll see a share button.</em></p> <p>Being able to hook into native functionality when its available is a great especially when you are able to do it in a progressively enhanced manner. A great example of this is the <a href="https://www.w3.org/TR/web-share/">Web Share API</a>. It is quite a new API that is still in working draft at the W3C. Even though it is still in working draft and not a common API available on all browsers. The main point is that <strong>it is</strong> available on <strong>some</strong> <a href="https://caniuse.com/?search=share">browsers</a>. This allows you to start using it, and by allow the users of those devices and browsers to have an extra feature available to them. Aka - Progressive enhancement. While this allows you to use a new feature, its also a way for browsers and the W3C to get people testing the features, get excited and for it to become more than a working draft.</p> <p>As with all technical web docs, a great starting point is MDN Web Docs, and the docs for the Web Share API is no different. The API is exposed to you via JavaScript as <a href="https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share">Navigator.share()</a>.</p> <p>Broken down the Web Share API is one asynchronous method call, <em>if its available</em> in the users browsers. The function takes an object containing the data to share.</p> <pre><code>navigator.share({}) </code></pre> <p>The data you pass to the function is</p> <ul> <li><strong>url</strong> - A URL string referring to a resource being shared</li> <li><strong>title</strong> - The title of the document being shared. May be ignored by the target</li> <li><strong>text</strong> - Arbitrary text that forms the body of the message being shared. (not used in my examples)</li> <li><strong>files</strong> - Files to be shared (not covered in this post)</li> </ul> <p>In its most basic example you can just pass the url and title to the <code>share()</code> method and it will work for supported browsers. (Try copy and pasting the following into console of dev tools of a supported browser).</p> <pre><code>navigator.share({ url: &quot;http://matthewroach.me/web-share-api-progressively-enhanced-web-component/&quot;, title: &quot;Web Share API - Progressively enhanced web component&quot;, }); </code></pre> <p>While this is all great and all, I've mentioned progressively enhanced quite a few times already, and that this API is not available in all browsers. Being able to implement this as an enhanced feature is very neat, and if you pair it with web components you can have a fully isolated and truly enhanced feature for some of your users. Web components (<a href="https://caniuse.com/custom-elementsv1">customElements</a>) pairs well here as they are available in the browsers that have support for the Web Share API.</p> <h2>The button</h2> <p>We are dealing with an action, so we use a <code>button</code>, and as the button should only be shown if the browser supports the Web Share API its default state is <code>hidden</code>. As the Web Share API is a JavaScript based API and we can only detect it in the users browser we will control the visibility of the button via JavaScript. This also means if you are browsing without JavaScript enabled you are none the wiser.</p> <pre><code>&lt;button hidden is=&quot;s-hare&quot; a-title=&quot;Web Share API - Progressively enhanced web component&quot; a-url=&quot;http://matthewroach.me/web-share-api-progressively-enhanced-web-component/&quot; &gt; Share &lt;/button&gt; </code></pre> <p>You maybe wondering what the extra attributes on the button are. the <code>is</code> and the <code>a-title</code> and <code>a-url</code> are.</p> <ul> <li>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes#attr-is"><code>is</code></a> global HTML attribute: Allows you to specify that a standard HTML element should behave like a registered custom built-in element.</li> <li>The <code>a-title</code> and <code>a-url</code> are custom attributes I decided to use for the component to read them for when the share API is called. Remember from above to share you need a url and title passed to the method.</li> </ul> <h2>The JavaScript</h2> <p>All the code that makes the button.. Pop.. Or well work in situations where it is able to. The progressive enhanced experience.</p> <p>Below is the full JavaScript class and custom element define call to set up the button code from above to work.. Side note, view source to see it on my site.</p> <p>As I used the <code>is</code> attribute on the <code>button</code> element our class extends the <code>HTMLButtonElement</code></p> <p>In the <code>constructor</code> I call a class method <code>canWebShare</code> - if this returns false. Browsers that don't support the Web Share API nothing more happens. But if your browsers has the Web Share API available <code>navigator.share</code>, the code will call <code>showButton</code> which actually shows the button and adds a click event listener to it.</p> <p><strong>Accessibility Tip:</strong> As I have extended the native HTML <code>button</code> you only need to set up the click event as the button element will fire this event when the user uses either the enter or space key on their keyboard. It will also catch the mouse click event too.</p> <p>Now the event listener is added the button is all set for user interaction, and this is where the <code>a-title</code> and <code>a-url</code> attributes come into play. When a user activates the button the event listener function will use the values of these attributes to pass the data to the <code>navigator.share</code> method. Remember this method call returns a promise, you can use <code>await</code> here.. And guess what no need to polyfil for this as the browsers that support Web Share also support <code>await</code></p> <p>The share dialog is part of the users operating system (OS) and from when the user clicks the button you have passed the share off to the OS.</p> <pre><code>class Share extends HTMLButtonElement { constructor() { // Always call super first in constructor self = super() if (this.canWebShare()) { this.showButton() } } canWebShare() { return navigator.share !== undefined } showButton() { self.removeAttribute(&quot;hidden&quot;) self.addEventListener(&quot;click&quot;, self.shareEvent) } async shareEvent() { try { await navigator.share(self.shareData()) } catch(err) { alert(&quot;Sorry, sharing failed - you could try again&quot;) } } shareData() { const shareData = { title: self.getAttribute(&quot;a-title&quot;), url: self.getAttribute(&quot;a-url&quot;) || window.location.href, } if (self.getAttribute(&quot;a-text&quot;)) { shareData.text = self.getAttribute(&quot;a-text&quot;) } return shareData } } customElements.define('s-hare', Share, { extends: 'button' }) </code></pre> VSCode zoom only font not whole editor 2021-01-12T00:00:00+00:00 https://matthewroach.me/vscode-zoom-only-font-not-whole-editor/ <p> Ever been screen sharing your code editor (in my case VSCode) and someone asks to bump up the font size? Yes, me too.. All the time. The annoying part of VSCode is they provide keyboard short cuts to zoom. But this applies to the whole of VSCode. </p> <p> Being able to just increase the editor font size isn't something standard, but as with VSCode pretty much everything is customisable. To create custom key bindings to only increase the editor font size (zoom) you do the following: (Mac instructions) </p> <h2>Instructions for Mac</h2> <ol> <li>Code > Preferences > Keyboard Shortcuts</li> <li> This opens a GUI for keyboard shortcuts - click on the tab "Keyboard Shortcuts", and an icon with a file and arrow will appear (title - Open Keyboard Shortcuts JSON) </li> <li>Copy paste the below JSON into the file and save</li> <li>Done</li> </ol> <pre> [ { "key": "cmd+=", "command": "editor.action.fontZoomIn" }, { "key": "cmd+-", "command": "editor.action.fontZoomOut" }, { "key": "cmd+0", "command": "editor.action.fontZoomReset" } ] </pre> <h2>Instructions for Windows</h2> <ol> <li>File > Preferneces > Keyboard Shortcuts</li> <li> This opens a GUI for keyboard shortcuts - click on the tab "Keyboard Shortcuts", and an icon with a file and arrow will appear (title - Open Keyboard Shortcuts JSON) </li> <li>Copy paste the below JSON into the file and save</li> <li>Done</li> </ol> <pre> [ { "key": "ctrl+=", "command": "editor.action.fontZoomIn" }, { "key": "ctrl+-", "command": "editor.action.fontZoomOut" }, { "key": "ctrl+0", "command": "editor.action.fontZoomReset" } ] </pre> <p> <strong>Note:</strong> you will need to change the keybaord item, <code>cmd</code> for Mac and <code>ctrl</code> for Windows to match the correct key for your environment. </p> <p> <strong>Note:</strong> as pointed out on <a href="https://twitter.com/MVelludini/status/1351254660351483905"> twitter</a> by <a href="https://twitter.com/MVelludini">Matteo</a> the <code>+</code> and <code>-</code> key values are the keys "normally" next to the 0 and backspace, not the keys on a numpad </p> <blockquote class="twitter-tweet"><p lang="en" dir="ltr">yeah, thanks if you look in the gif I already changed to ctrl, but one thing i realize now is that you should not use + and - from the numpad, <br>the + and - not from the numpad works as expected</p>&mdash; Matteo (@MVelludini) <a href="https://twitter.com/MVelludini/status/1351254660351483905?ref_src=twsrc%5Etfw">January 18, 2021</a></blockquote> <script async="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 2020 2020-12-31T00:00:00+00:00 https://matthewroach.me/2020/ <p> Well, a global pandemic. That is what 2020 will be known for, and highly likely part of future history lessons. With the pandemic aside this year had many ups and downs. </p> <p> As the year had the pandemic cast over it lets not dwell on the downs to much more. For the ups of the year: </p> <ul> <li> After joining the waiting list for a Concept 2 rower, the week after lock down was announced I finally got the e-mail to place and order then took delivery in August. Since then I've managed to row <strong>384,638</strong> meters </li> <li> In the middle of August I joined the UI Engineering team at FreeAgent. A little nervous and scary to be switching jobs during a global pandemic. </li> <li> As a family we started Saturday movie nights. Each week we take it in turns to pick a movie that we watch as a family. Having watched some classics and new releases, its been great to experience old and new movies together as a family. </li> <li> My son finished up his final year in primary school. This leading to him starting secondary school. While he never got the end of his primary school years as everyone expected. It was nice for him to be able to start secondary school in a little more normal manner. </li> <li> My daughter started primary school, again under slightly different circumstances but due to the pandemic it was as good as an experience as could be had based on the circumstances. </li> <li> We managed a small weekend family holiday in November to the Highlands in a self suffienct motorhome - 12 years to convince my wife to go "camping". Our family holiday to New York got cancelled (something about a global pandemic) </li> <li> <strong>Lots</strong> of family time that included many walks, bike rides, BBQ's and general family time together. </li> </ul> <p> 2021 does not look like its going to be much different to the current situation for a while. With that in mind I am looking forward to more family time and making the most of a bad situation. </p>