<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Paul</title>
    <description>The latest articles on Forem by Paul (@peiche).</description>
    <link>https://forem.com/peiche</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F16434%2Ff0f74a4b-93d4-4d3e-a3db-d4aa28920361.jpg</url>
      <title>Forem: Paul</title>
      <link>https://forem.com/peiche</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/peiche"/>
    <language>en</language>
    <item>
      <title>KendoReact Challenge: Epicure's Lost Recipes</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Sat, 22 Mar 2025 23:26:37 +0000</pubDate>
      <link>https://forem.com/peiche/kendoreact-challenge-epicures-lost-recipes-5kd</link>
      <guid>https://forem.com/peiche/kendoreact-challenge-epicures-lost-recipes-5kd</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/kendoreact"&gt;KendoReact Free Components Challenge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Earlier this year, a company called Epicure closed down their business and website. They were primarily focused on cooking products, but they also had over 3,000 recipes on their site which were no longer accessible. I built a NextJS static site using data scraped from the Internet Archive to resurrect Epicure's lost recipes. When I built this website, I originally created it using MUI components. For this challenge, I have created a branch of that project using KendoReact components instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demo
&lt;/h3&gt;

&lt;p&gt;Take a look at the KendoReact Challenge branch of epicure-recipes here:&lt;br&gt;
&lt;a href="https://deploy-preview-23--epicure-recipes.netlify.app/" rel="noopener noreferrer"&gt;https://deploy-preview-23--epicure-recipes.netlify.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you'd like, browse the source code here:&lt;br&gt;
&lt;a href="https://github.com/peiche/epicure-recipes/tree/kendo-challenge/" rel="noopener noreferrer"&gt;https://github.com/peiche/epicure-recipes/tree/kendo-challenge/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are a few screenshots of the website's different browsing capabilities. It is possible to view recipes in a list; recipes tagged under a category; and recipes tagged as using a particular Epicure product. This can vary widely, since Epicure made seasoning packets and jars, as well as kitchen utensils and tools like burger presses and microwave steamers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbff3jm9x3y9w0091b70.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbff3jm9x3y9w0091b70.jpg" alt="Main list of recipes" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I was scraping data from Epicure's recipes, I found that each page contained data in JSON format that contained not only the recipe itself, but also category and product information. This made it quite easy for me to build out tag and product pages as well. I don't have any interest in upselling their products, of course. I'm not affiliated with Epicure, and they aren't manufacturing any new ones, anyway. But it &lt;em&gt;is&lt;/em&gt; useful to know what products are used in a recipe, and if you have one of those products, the ability to find more recipes that use it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcju475473wsr78g6q7v.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcju475473wsr78g6q7v.jpg" alt="List of recipes with a single tag" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F561y5ld5ppvidriw3iwz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F561y5ld5ppvidriw3iwz.jpg" alt="List of recipes with a single product" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Because this is a static site built from JSON data, there is no way to reorder recipes on any given page (although the metadata for dates published and the like does exist in the original JSON). These pages are good for browsing, but if you're looking for a specific recipe, you're better off using the search page.&lt;/p&gt;

&lt;p&gt;The search is built using Algolia's InstantSearch for React. I mentioned that the site is built on static JSON data; that very same data was also used to generate the Algolia search index.&lt;/p&gt;

&lt;p&gt;The site's search is separated into two parts. The first is the search page, which uses the aforementioned index. Since I already have data for tags and products, it required no extra effort on my part to include that data as facets.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4uk20criyub8upp9nwh.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp4uk20criyub8upp9nwh.jpg" alt="List of recipes in a search result" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjrlua3hl4colbq01otk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjrlua3hl4colbq01otk.jpg" alt="List of recipes in a search result, with facets checked" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The second part of the search is the suggest, which Algolia offers as a "learning" index. It uses the primary search index and updates it based on usage, adding and modifying records as people use the site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa97j68wcuc734a7sbd8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa97j68wcuc734a7sbd8.jpg" alt="The default query suggestions" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3n5e6g0tsabe0vuybhdt.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3n5e6g0tsabe0vuybhdt.jpg" alt="The query suggestions with matching results" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  KendoReact Experience
&lt;/h2&gt;



&lt;p&gt;Below is a list of KendoReact components I used in the project, followed by some custom work when they couldn't offer enough functionality or customization for my liking.&lt;/p&gt;
&lt;h3&gt;
  
  
  Typography
&lt;/h3&gt;

&lt;p&gt;This is a pretty obvious one, and almost shouldn't even count in the list of components. But it is one, since with this single component one can render a variety of typography-related tags.&lt;/p&gt;
&lt;h3&gt;
  
  
  Button
&lt;/h3&gt;

&lt;p&gt;Buttons are used throughout the site, primarily for functionality that doesn't directly result in navigation to a different page. This includes things like the Search button in the navbar and pagination on the search page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1a4m83ljxm1jgo0am2s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1a4m83ljxm1jgo0am2s.jpg" alt="Search result pagination" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The difficulty I encountered with converting from MUI to KendoReact was that Kendo's Button component can't be a link. It can only render a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; element, never a &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;. Sure, you can set the &lt;code&gt;onClick&lt;/code&gt; event to trigger navigation, but for SEO purposes I prefer to render a link. To that end, there are instances on the site where I manually created anchor elements that merely look like buttons using the same CSS classes as the Button component. You can see an example of this in the "button" on the homepage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26kzu2moy4gisjj8qgz0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F26kzu2moy4gisjj8qgz0.jpg" alt="The site homepage" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  AppBar
&lt;/h3&gt;

&lt;p&gt;The site's top navigation uses this, along with subsequent components. Rather than use &lt;code&gt;display: flex&lt;/code&gt; in custom CSS to create spacing, I was able to leverage the AppBarSection and AppBarSpacer components with zero utility class usage.&lt;/p&gt;
&lt;h3&gt;
  
  
  GridLayout and GridLayoutItem
&lt;/h3&gt;

&lt;p&gt;In places where I needed to create a grid, such as the list pages and search results, I used these components. Unlike MUI's Grid counterpart, these use &lt;code&gt;display: grid&lt;/code&gt; rather than &lt;code&gt;display: flex&lt;/code&gt;. I am less familiar with CSS Grid than I am with Flex, but making a symmetrical layout is simple enough.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcju475473wsr78g6q7v.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqcju475473wsr78g6q7v.jpg" alt="List of recipes with a single tag" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What I did have issues with is making the layout responsive. I found tutorials on how to do so with KendoReact (like &lt;a href="https://www.telerik.com/blogs/creating-responsive-design-react-kendoreact-gridlayout-stacklayout" rel="noopener noreferrer"&gt;this one&lt;/a&gt;) that require extensive configuration or creating a listener to window resizing. None of that appealed to me, so I created a couple utility classes in my stylesheet, one for a two-column layout (used on the search results page) and one for a thee-column layout (used on all the list grid pages).&lt;/p&gt;
&lt;h3&gt;
  
  
  Card
&lt;/h3&gt;

&lt;p&gt;Inside every GridLayoutItem component was a shared recipe card component, which used Card and subsequent components, like CardImage, CardHeader, and CardBody. Some style specificity I had to override with the utility classes provided by the themes' stylesheets (more on that later).&lt;/p&gt;
&lt;h3&gt;
  
  
  TextBox
&lt;/h3&gt;

&lt;p&gt;I don't have much by way of forms on the site, but there are two: the search suggest and the search results page itself.&lt;/p&gt;
&lt;h3&gt;
  
  
  Chip
&lt;/h3&gt;

&lt;p&gt;You might think that I'm using the Chip component on the recipe detail page, but you'd be wrong. Those are actually a custom component with all the classes to recreate the appearance of a Chip. The reason I didn't use the KendoReact component is simple: you can't make it a link, only a button. For SEO purposes, I prefer to use a link when navigation is required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy51zv0sx3vaaggzemp7o.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy51zv0sx3vaaggzemp7o.jpg" alt="The recipe detail page's tag links" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the search results page, however, I am using the Chip as provided by KendoReact. There's no navigation there: clicking on one of those will rearrange the search filters. The hit count next to each facet is also a Chip, but used solely for its display design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjrlua3hl4colbq01otk.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjrlua3hl4colbq01otk.jpg" alt="List of recipes in a search result, with facets checked" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Checkbox
&lt;/h3&gt;

&lt;p&gt;On the search results page, each facet is represented by a checkbox. Checking it will filter according to that selection; unchecking it will undo the selection.&lt;/p&gt;
&lt;h3&gt;
  
  
  Dialog
&lt;/h3&gt;

&lt;p&gt;To pop up the search suggest modal, I'm using the Dialog component. Super simple, and no customization required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa97j68wcuc734a7sbd8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqa97j68wcuc734a7sbd8.jpg" alt="The search suggest dialog" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Drawer
&lt;/h3&gt;

&lt;p&gt;Like every page, the search results page is responsive. I designed the entire site to be usable on a phone as well as a computer, and the search is no exception. When your screen is too small to display the search filters alongside the results, I hide the filters in a sidebar. Inside the drawer, the selected facets are also displayed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzoz1ppm5u2in855m7tz.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjzoz1ppm5u2in855m7tz.jpg" alt="Search results with filters hidden" width="449" height="947"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr54e2zgaph4s6ldqdrm4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr54e2zgaph4s6ldqdrm4.jpg" alt="Search results with filters displayed" width="449" height="947"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I did find the Drawer somewhat counterintuitive to use, as the default behavior expects to be given a list of items as navigation. However, there is a component called &lt;code&gt;DrawerNavigation&lt;/code&gt; that can be used to manually insert any kind of content inside the drawer.&lt;/p&gt;
&lt;h3&gt;
  
  
  Switch
&lt;/h3&gt;

&lt;p&gt;I put a Switch in the header to swap between light and dark modes (more on that in a minute). There isn't a whole lot of customization; it only allows for custom text in the different toggle states, and I really wanted to use sun and moon icons. I found a &lt;a href="https://emojicombos.com/" rel="noopener noreferrer"&gt;site&lt;/a&gt; that lets you search for symbols, so I copied those characters and put them in the text props.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhdk49rxdz7ewax57nfok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhdk49rxdz7ewax57nfok.png" alt="Grid of sun and moon related symbols" width="732" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The selection defaults to light mode, but switching it saves the change to local storage, allowing a user to keep using that theme without having to click it again the next time they visit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhy1xlqlyrpqrvbxnp0h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhy1xlqlyrpqrvbxnp0h.jpg" alt="The site with dark mode enabled" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Custom Components
&lt;/h3&gt;

&lt;p&gt;As I mentioned, some of KendoReact's components did not offer enough versatility to use out-of-the-box. I want to talk about those for a minute here.&lt;/p&gt;
&lt;h4&gt;
  
  
  Button
&lt;/h4&gt;

&lt;p&gt;I already mentioned this one, but I think it bears repeating. Sometimes you want to style a link like a button, and KendoReact does not provide an easy way to do that. Every time I wanted a link that looked like a button, I would create the button, copy its CSS classes, and then manually create the link. (In my case, I used NextJS's Link component.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Button component */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;
  &lt;span class="na"&gt;themeColor&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'primary'&lt;/span&gt;
  &lt;span class="na"&gt;fillMode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'solid'&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Print&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Recreated link  */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;
  &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'k-button k-button-md k-button-solid k-button-solid-primary k-rounded-md'&lt;/span&gt;
  &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/recipe/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/print`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'_blank'&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'k-button-text'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Print&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Chip
&lt;/h4&gt;

&lt;p&gt;The "chips" on the recipe detail page, as I've mentioned, are custom anchor elements. Much like I did with the links that look like buttons, I created a Chip, copied its CSS classes, and created an anchor element with those classes. You can see the custom shared component here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/peiche/epicure-recipes/blob/kendo-challenge/src/components/chipLink.tsx" rel="noopener noreferrer"&gt;https://github.com/peiche/epicure-recipes/blob/kendo-challenge/src/components/chipLink.tsx&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Pager
&lt;/h4&gt;

&lt;p&gt;As far as I could tell from the documentation, the Pager component was to be used within the context of a data table. I had two use-cases: pagination for the browse pages and the search results page. The former had to result in navigation, so they had to be links. The latter are buttons since they update the page but don't result in navigation away from a page. (The URL does get updated, though, so a specific search result can be linked to.)&lt;/p&gt;

&lt;p&gt;I ended up writing my own pagination components, one for each use-case. I briefly considered writing a single component that could handle both, but there is such a thing as over-engineering! 😆 As a result, there are two components, each with identical logic to skip over displaying every single page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/peiche/epicure-recipes/blob/kendo-challenge/src/components/pagination.tsx" rel="noopener noreferrer"&gt;https://github.com/peiche/epicure-recipes/blob/kendo-challenge/src/components/pagination.tsx&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/peiche/epicure-recipes/blob/kendo-challenge/src/components/searchPagination.tsx" rel="noopener noreferrer"&gt;https://github.com/peiche/epicure-recipes/blob/kendo-challenge/src/components/searchPagination.tsx&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Breadcrumb
&lt;/h4&gt;

&lt;p&gt;The breadcrumb component was another case where the elements could not be given a destination outside of an &lt;code&gt;onClick&lt;/code&gt; event. It made even less sense here because each leaf in the breadcrumb's path is an anchor element, but is &lt;a href="https://www.telerik.com/kendo-react-ui/components/updates/rendering-changes/8-0-0#breadcrumb" rel="noopener noreferrer"&gt;hard-coded to have "#" as the href value&lt;/a&gt;. In order to have a breadcrumb component that rendered functional anchor elements, I wrote my own, using the existing CSS classes used by the Kendo component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/peiche/epicure-recipes/blob/kendo-challenge/src/components/breadcrumbs.tsx" rel="noopener noreferrer"&gt;https://github.com/peiche/epicure-recipes/blob/kendo-challenge/src/components/breadcrumbs.tsx&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Delightfully Designed
&lt;/h2&gt;

&lt;p&gt;I created an account on Telerik and signed up for the seven-day trial of the Theme Builder pro, just so I could have more than one design. (The free tier has a limit of one.) I created two color themes: one light and one dark.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnp9zwy7tknt2veaam75.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnp9zwy7tknt2veaam75.jpg" alt="Light theme in Theme Builder" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7embgr26q8eojlqb05g.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo7embgr26q8eojlqb05g.jpg" alt="Dark theme in Theme Builder" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I exported the two themes, but my use of the downloads changed over the course of development. I originally used the Sass exports, but I found that the variables used there were Sass variables, not custom properties (i.e., native CSS variables), so I switched to the CSS exports instead. (My separate global stylesheet still uses Sass.)&lt;/p&gt;

&lt;p&gt;I initially put the stylesheets inside &lt;code&gt;/src/themes&lt;/code&gt;, but moved it into &lt;code&gt;/public&lt;/code&gt; because the CSS theme switcher dependency I was using required a static, public URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhy1xlqlyrpqrvbxnp0h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhy1xlqlyrpqrvbxnp0h.jpg" alt="The site with dark mode enabled" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;I did mention using some utility classes to override some default styles. Before I did a deep dive into the styles included with the theme -- both the base stylesheet and the exported files from Theme Builder -- I considered using Tailwind to supplement what I needed. After some digging, however, I found that Kendo already provided most of what I needed. (I did,     as I mentioned, have to create a couple grid-related utility classes.)&lt;/p&gt;

&lt;p&gt;I also want to note that while I am using a few icons, none of them are Kendo's. I did start out using them, but there are some they don't have, like the fork and knife icon I'm using on the recipe detail page. I used Font Awesome for that. Rather than have visual inconsistency (heaven forbid!) I decided to make &lt;em&gt;all&lt;/em&gt; the icons come from Font Awesome.&lt;/p&gt;

&lt;p&gt;The best hobby web apps are the ones you build for yourself. That's no less true in this case, since I use it quite a bit. ("I'm not just the president, I'm also a client!") It's invaluable to me to be able to pull up a recipe on my phone; to that end, the entire site is responsive: from the home page and browse to the search and recipe detail pages. Maybe you can have a use for it too!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F574h7ndma2fffajji3pb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F574h7ndma2fffajji3pb.jpg" alt="Responsive recipe detail page" width="426" height="947"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Thanks for reading!
&lt;/h2&gt;

&lt;p&gt;I've had a lot of fun (and some frustration) in refactoring my existing site to use a new set of components. And if you're a foodie, feel free to poke around my recipe site. It's a labor of love.&lt;/p&gt;




&lt;p&gt;I may still write about the process I used to generate the JSON files which form the base of the static site, but that is a story for another time.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>kendoreactchallenge</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a recipe website</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Fri, 07 Feb 2025 04:44:52 +0000</pubDate>
      <link>https://forem.com/peiche/i-built-a-recipe-website-18mg</link>
      <guid>https://forem.com/peiche/i-built-a-recipe-website-18mg</guid>
      <description>&lt;p&gt;I want to share the recipe website I made:&lt;br&gt;
&lt;a href="https://epicure-recipes.netlify.app/recipes" rel="noopener noreferrer"&gt;https://epicure-recipes.netlify.app/recipes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Recently, the company Epicure went bankrupt. They primarily sold seasonings and meal kit packets and supplies, but their website was also host to 3,300+ recipes. When the company shut down their website, those recipes were lost... sort of. Using the &lt;a href="https://web.archive.org" rel="noopener noreferrer"&gt;Wayback Machine&lt;/a&gt;, I scraped all those recipes and built a static site from the data. The source code for that site can be found here:&lt;br&gt;
&lt;a href="https://github.com/peiche/epicure-recipes" rel="noopener noreferrer"&gt;https://github.com/peiche/epicure-recipes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I initially built the site using a static site technology called &lt;a href="https://www.gatsbyjs.com" rel="noopener noreferrer"&gt;Gatsby&lt;/a&gt;, not realizing that it was actually a dead project. After a little research, I found that &lt;a href="https://nextjs.org" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; is a pretty popular alternative for building a static site, so I set myself to the task of porting it.&lt;/p&gt;

&lt;p&gt;Not only is the port complete, but I've added a lot to the site since then. I've added images to recipe pages, tags (think like blog post tags), and lists of products used in each recipe. I also added dark mode because I love it and prefer it.&lt;/p&gt;

&lt;p&gt;The most recent addition is a faceted search powered by &lt;a href="https://www.algolia.com" rel="noopener noreferrer"&gt;Algolia&lt;/a&gt;. I already use it on the family website, but since that's running WordPress, I really haven't ever had to build a search index from the ground up, using all-new data. Well, now I have.&lt;/p&gt;

&lt;p&gt;I built the UI with &lt;a href="https://mui.com" rel="noopener noreferrer"&gt;MUI&lt;/a&gt;. I'm a big fan of Google's Material Design, and this port of it is top notch. It's my go-to whenever I build a React app.&lt;/p&gt;

&lt;p&gt;Usually Next.js sites are hosted on Vercel, but I am well experienced with hosting static sites on &lt;a href="https://www.netlify.com" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, so that's where this one is hosted. I have a local script that rebuilds the site if/when I make some improvements to the extracted data, which gets checked into the GitHub repo. From there, Netlify automatically deploys the updated site.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>algolia</category>
      <category>netlify</category>
      <category>mui</category>
    </item>
    <item>
      <title>Ankur 1.0.4</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Thu, 15 Feb 2024 17:36:03 +0000</pubDate>
      <link>https://forem.com/peiche/ankur-104-2323</link>
      <guid>https://forem.com/peiche/ankur-104-2323</guid>
      <description>&lt;p&gt;I’ve made a few updates since launching &lt;a href="https://dev.to/peiche/ankur-a-wordpress-block-theme-for-everyone-3md3"&gt;Ankur&lt;/a&gt; on the WordPress theme directory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsllkv5y4ulxu1msk1vvz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsllkv5y4ulxu1msk1vvz.png" alt="Screenshot of Ankur" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have been remiss in updating the blog as much as I’ve updated what the blog runs on. The recent updates have really just been maintenance and behind the scenes fixes, for the most part. There are a few things worth mentioning, though.&lt;/p&gt;

&lt;h2&gt;
  
  
  1.0.1
&lt;/h2&gt;

&lt;p&gt;The first update really was just housekeeping stuff. I added PHPCS to the build scripts, to ensure the use of code standards and best practices. A side effect of doing that, then, was it discovering some errors and warnings in the PHP files. This update also included those fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  1.0.2
&lt;/h2&gt;

&lt;p&gt;This was even more of a minor update. I moved several anonymous functions to named functions. (This was at the recommendation of a reviewer.)&lt;/p&gt;

&lt;h2&gt;
  
  
  1.0.3
&lt;/h2&gt;

&lt;p&gt;I do have some custom styles and overrides in Ankur. One such example is maintaining the same spacing between blocks, like paragraphs and images. The quote block, however, was missing this spacing in the top margin. This update fixed that.&lt;/p&gt;

&lt;h2&gt;
  
  
  1.0.4
&lt;/h2&gt;

&lt;p&gt;This update fixed a number of things that had been piling up; some housekeep and maintenance, and some user-facing issues. First, the housekeeping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updated &lt;a href="https://www.npmjs.com/package/caniuse" rel="noopener noreferrer"&gt;caniuse&lt;/a&gt; to the latest version. This is a library that handles compatibility validation across browsers.&lt;/li&gt;
&lt;li&gt;Merged in some package updates handled by the dependency checker bot (&lt;code&gt;axios&lt;/code&gt;, &lt;code&gt;@wordpress/scripts&lt;/code&gt;, and &lt;code&gt;follow-redirects&lt;/code&gt;, if you must know 😜).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And now, the visible fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Removed the class attributes from custom block style registration. These were unnecessary because I’m already using the default CSS class generation.&lt;/li&gt;
&lt;li&gt;Fixed responsive font sizes to improve readability. Now fonts won’t shrink indefinitely if you resize your browser window to be smaller.&lt;/li&gt;
&lt;li&gt;Removed old quote block large style selector. There were two, &lt;code&gt;.large&lt;/code&gt; and &lt;code&gt;.is-style-large&lt;/code&gt;. Only the latter is used in the block editor.&lt;/li&gt;
&lt;li&gt;Removed unneeded quote block plain style from stylesheet. The block style is supported in WordPress core, so only its definition in &lt;code&gt;theme.json&lt;/code&gt; is needed.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>wordpress</category>
    </item>
    <item>
      <title>Ankur, a WordPress block theme for everyone</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Thu, 02 Nov 2023 02:22:32 +0000</pubDate>
      <link>https://forem.com/peiche/ankur-a-wordpress-block-theme-for-everyone-3md3</link>
      <guid>https://forem.com/peiche/ankur-a-wordpress-block-theme-for-everyone-3md3</guid>
      <description>&lt;p&gt;Introducing a new WordPress block theme with support for color variations and custom patterns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fje5fh6ezy6edd0pq2kvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fje5fh6ezy6edd0pq2kvr.png" alt="Screenshot of Ankur" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ankur is a block theme for WordPress. It’s named after a Sanskrit word which means “flower” or “blossom,” imagery often used to depict a new beginning. At the same time, an anchor (because I love wordplay) symbolizes stability and confidence. With Ankur as the fully customizable starting point, whether as a brand new site or revamping an existing one, you can tell your story with clean typography and bold colors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Theme style variants
&lt;/h2&gt;

&lt;p&gt;Ankur comes bundled with multiple style variants: light, dark, and three nature-themed alternates.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf73kgd0tenfhxklzbir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf73kgd0tenfhxklzbir.png" alt="Screenshot of color variations" width="334" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To apply one of these styles, open the new Site Editor and select the Styles menu item. In the Styles sidebar, you’ll be able to pick your theme’s style.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5r19vho89cl2woafi1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw5r19vho89cl2woafi1g.png" alt="Screenshot of Ankur using the " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose the one that speaks to you, and click Save to apply it to your site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default patterns and customization
&lt;/h2&gt;

&lt;p&gt;Ankur also comes bundled with several patterns to give you a head start in making your site your own. A little bit of everything is included: headers and footers, featured content areas, post previews, and even a custom large blockquote.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81jglktzhoijijv3hyze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F81jglktzhoijijv3hyze.png" alt="Screenshot of Ankur's included block patterns" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remember that you don’t just have to use what Ankur provides, either. Click the three-dot menu on any pattern and select “Copy to My patterns” to customize it to your liking.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fimr6ec3iivuizofbc1na.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fimr6ec3iivuizofbc1na.png" alt="Screenshot of a website's patterns" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Goodbye child themes, probably
&lt;/h2&gt;

&lt;p&gt;The WordPress site editor removes, in large part, the necessity to create a child theme. For style or design changes, it’s no longer a requirement; simply make your changes and save right in the editor.&lt;/p&gt;

&lt;p&gt;My own website is, however, running a child theme of Ankur. The reason for this is — beyond simply because “Paul is a programmer” — I have custom functionality not needed for a general-purpose theme. For example, I have a color theme switcher in the site footer. The custom JavaScript for this functionality is not included in the base theme. (In fact, no custom JavaScript is included with Ankur.) Therefore, child theme.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftqlsmm0zfruqz65xh6zk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftqlsmm0zfruqz65xh6zk.png" alt="Screenshot of my website's footer" width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also use a custom plugin and service to handle the search functionality. The service is a cloud search called Algolia (and I highly recommend them, I don’t get paid to say it but they’re awesome). The templates I use for displaying the search results are custom. So again, a child theme is necessary for that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fge3mlq6qf08m67zuj12e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fge3mlq6qf08m67zuj12e.png" alt="Screenshot of my website's search page" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But if you don’t need to do anything super custom, then you probably don’t need a child theme.&lt;/p&gt;

</description>
      <category>wordpress</category>
    </item>
    <item>
      <title>Writing a contract when the work is already done?</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Wed, 12 Jul 2023 02:06:16 +0000</pubDate>
      <link>https://forem.com/peiche/writing-a-contract-when-the-work-is-already-done-nmp</link>
      <guid>https://forem.com/peiche/writing-a-contract-when-the-work-is-already-done-nmp</guid>
      <description>&lt;p&gt;I suppose this is a bit of a weird question, but I couldn't find anything pertaining to my situation.&lt;/p&gt;

&lt;p&gt;My wife works for a preschool and childcare, and she was put in charge of scheduling for the staff. In order to do that, she had to calculate the teacher-student ratio (it differs depending on a child's age) for each classroom. I was horrified to learn that this was all done with pencil and paper, so I wrote a web app to assist with this.&lt;/p&gt;

&lt;p&gt;When the web app was complete -- and my wife had been using it for several weeks -- she presented it to the director of the facility. The director was impressed with it, and (some weeks later) had me attend a staff meeting to present it in proper form to the entire staff.&lt;/p&gt;

&lt;p&gt;Now that the app is done, and has not been in need of any debugging or any kind of maintenance, really, the director informed my wife that she wants to pay me for the work. I certainly didn't keep track of the number of hours I put into it; it was a labor of love, literally: I love my wife and saw a way in which I could make her job a little less stressful. I can guess, but a guess is all it would be.&lt;/p&gt;

&lt;p&gt;So as to my question. I have found plenty of templates for web development service agreements, but how do I write one when the work has already been done?&lt;/p&gt;

</description>
      <category>freelance</category>
      <category>webdev</category>
      <category>discuss</category>
      <category>help</category>
    </item>
    <item>
      <title>Teacher-Student Ratio Calculator</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Wed, 17 May 2023 13:24:02 +0000</pubDate>
      <link>https://forem.com/peiche/teacher-student-ratio-calculator-3ao1</link>
      <guid>https://forem.com/peiche/teacher-student-ratio-calculator-3ao1</guid>
      <description>&lt;p&gt;Appwrite is doing a hackathon with Hashnode. Is it a no-no to post about one developer community site on another? I dunno. But since I do cross-post the majority of my code-related stuff here, I figured I'd post it anyway. :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Team Details
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Paul: It’s me. I built this by myself*.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About This Project
&lt;/h2&gt;

&lt;p&gt;My wife is a teacher and scheduler in a childcare facility. They have a wide age range of children, from infants to toddlers to preschoolers. It is admirable work, but unfortunately for her, the scheduling system that tracked children was all done on paper. As she told me more about the details that went into the scheduling, I became convinced that together, we could create a reliable digital replacement.&lt;/p&gt;

&lt;p&gt;This, the version I’m submitting for the Appwrite hackathon, is not quite the final version. A few pieces are missing for the sake of a public demo: authentication, for example. Another is the student list: none of the children in this public demo are real people. The primary functionality, however, is intact with generating a list of times of day, with classrooms of students and their calculated decimal ratio values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Netlify for automatic deployment from GitHub repo.&lt;/li&gt;
&lt;li&gt;Appwrite Cloud

&lt;ul&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;React

&lt;ul&gt;
&lt;li&gt;React Router for pages.&lt;/li&gt;
&lt;li&gt;Redux and Redux Persist to save settings locally.&lt;/li&gt;
&lt;li&gt;MUI for easy layouts.&lt;/li&gt;
&lt;li&gt;Day.js because dates can be hard in JavaScript.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges We Faced
&lt;/h2&gt;

&lt;p&gt;The first and largest challenge was entering all the children at the daycare into the database. The first table was created for this purpose, which stores the first and last name, the times the child arrives and leaves the daycare, and their birthday.&lt;/p&gt;

&lt;p&gt;Each child is assigned a decimal value to calculate the teacher-student ratio. As they age, the decimal value changes. (The older the child, the smaller the value.) In the previous (pencil-and-paper) system, each child’s value was recalculated by hand every day, because the time intervals which required a ratio change were not necessarily only on their birthday. Since the digital system tracks the child’s age, calculating the decimal value happens automatically.&lt;/p&gt;

&lt;p&gt;Since the daycare is a smaller one — with a smaller staff — children do not necessarily stay in the classroom in which they start the day. For that reason, we chose not to create static classroom groups, but rather allow the user to create and (re)name them as they see fit. The ability to remove and add children to different groups was likewise important. Drag and drop would perhaps offer a nicer UX, but checkboxes for addition and removal were deemed sufficient.&lt;/p&gt;

&lt;p&gt;In adding children to classroom groups and moving them around, my wife found that sometimes she needed to edit their starting or ending times. The daycare allowed parents to text, email, or call with last-minute schedule changes, so the ability to quickly edit a student proved important. Clicking on a student’s name within the selection screen, or even the final ratio calculation screen, opens a new tab with all navigation removed. Clicking the “Cancel” button simply closes the tab, bringing the user back to the previous tab. Clicking “Save” — as you would expect — updates the student’s record and also closes the tab. It’s important to note that after updating a student, the previous tab does not refresh; that must still be done manually.&lt;/p&gt;

&lt;p&gt;Originally, the time slots for which the report was needed were hard-coded. It was not too difficult, however, to create another table — just a simple one with a single column — which stores each slot. These slots will rarely, if ever, need to change; that said, the ability to do so can now be handled by the user and does not require a redeploy.&lt;/p&gt;

&lt;p&gt;A final generated ratio report is not saved to the database, but it is possible to export it to a saved JSON file. This file can then be imported for quick editing or printing.&lt;/p&gt;

&lt;p&gt;It’s quite possible — probable, even — that adding dark mode was not necessary for anyone other than myself. In all likelihood, it was simply bikeshedding. That said, I am a dark mode enjoyer and use it whenever possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public Code Repo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/peiche/teacher-student-ratio-calculator" rel="noopener noreferrer"&gt;https://github.com/peiche/teacher-student-ratio-calculator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feel free to poke around! I know enough React to be dangerous, but by no means do I claim to be an expert. Much of this project was a learning experience, especially with Redux Persist. And by all means, submit a PR if you see something you think can be improved upon. I am never too proud to learn. :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo Link
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://tsrc.netlify.app/" rel="noopener noreferrer"&gt;https://tsrc.netlify.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The database holds a list of students and time slots, but a generated list of ratios per time of day is not saved to the database. Rather, it is saved to local storage via Redux Persist. It is easy enough to create, name, and organize groups of students, however. After at least one group is created, the “Generate” button appears to calculate the decimal ratio values for each student, totaled in each group for each time of day in the database.&lt;/p&gt;

&lt;p&gt;For the sake of this open-source demo, I am not requiring authentication. I have prepopulated the database with times of day and students (all fictitious), so generating ratio calculations is a snap.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlli53gyf9dheq1v95xh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlli53gyf9dheq1v95xh.png" alt="Screenshot of the " width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xfvntmoxp9inqgg0s6e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xfvntmoxp9inqgg0s6e.png" alt="Screenshot of the " width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi01ph6io1j6bclzexigf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi01ph6io1j6bclzexigf.png" alt="Screenshot of the calculated teacher-student ratios" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;*Although I configured the Appwrite database tables and wrote all the code, my wife — the inspiration behind this project — provided tireless feedback to improve the experience, plenty of love, and many cups of coffee. (Electric Unicorn by Bones Coffee might just as well be a sponsor here, too.) &lt;/p&gt;

</description>
      <category>appwrite</category>
      <category>appwritehackathon</category>
    </item>
    <item>
      <title>Setting up Steam Link</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Mon, 29 Aug 2022 16:36:41 +0000</pubDate>
      <link>https://forem.com/peiche/setting-up-steam-link-4b0n</link>
      <guid>https://forem.com/peiche/setting-up-steam-link-4b0n</guid>
      <description>&lt;p&gt;The other day, I got it into my head to look for an HDMI switch I had laying around. I moved into a house from an apartment a little over a year ago, so a lot of my stuff is still boxed up. I did find it, though. The whole point of this exercise was to connect another HDMI input to my TV, which only has three ports, all of which were already in use.&lt;/p&gt;

&lt;p&gt;I disconnected my PS3 from the third port, and instead connected the switch. (This is not to be confused with the Nintendo Switch, which is connected via the first port.) I then connected the PS3 to one port on the switch (not the Switch ).&lt;/p&gt;

&lt;p&gt;Now the real point of this exercise: I connected a Raspberry Pi to the other port on the switch. Some RPi computers are only the actual computer; this one, a 400 model, has a built-in keyboard. It does have a mouse, but I wouldn’t need it.&lt;/p&gt;

&lt;p&gt;I plugged the USB dongle to my Steam game controller into the RPi. This allowed me to use it as a mouse before I got the streaming gaming set up. (The right analog input moves the mouse cursor, and the right trigger acts as the mouse click.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80312kag9sq8v4424u2h.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80312kag9sq8v4424u2h.jpg" alt="Raspberry Pi 400 with Steam controller" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next step was to pair my two game devices: the RPi to display the games and my computer to broadcast the signal. I opened the console and ran the following commands on the RPi to install the Steam Link app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo apt install steamlink
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The installation put a shortcut on my desktop, which I can just double-click when I want to connect to a computer running Steam. I couldn’t do that, however, until I set up the connection between them. So on the source computer, I opened the Remote Play settings in Steam to pair it with the RPi.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwh14c19pla1rs4rtut0t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwh14c19pla1rs4rtut0t.png" alt="Screenshot of Steam remote play settings" width="740" height="585"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It did involve a little bit of running back and forth down the hallway, since I had to see the verification number displayed on one machine and enter it on the other. Once that was done, I could run Steam Link on the RPi and connect to the computer to play (pretty much) any game from my Steam library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2cnekygkjeuemrszp10.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx2cnekygkjeuemrszp10.jpg" alt="Browsing my Steam library from my TV" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I will mention a couple caveats to my “pretty much any game” statement. Steam (along with any other website that has directions on how to do this) recommends a wired connection, not wireless, because there can be some lag when connecting via the latter. Point-and-click type adventure games seem fine, like Telltale’s The Walking Dead. Turn-based games, too, pose no problem. I did test out Dark Souls 2 and Tomb Raider with mixed results. The former was a little slow for my liking; the latter was playable.&lt;/p&gt;

&lt;p&gt;The other issue I ran into was controller support. Not every game on Steam has full controller support, but most do. (Namine’s favorite game of mine on Steam, Turbo Pug, unfortunately does not.) All games on Steam do indicate in their library thumbnail whether or not they have full support, so determining that involves no uncertainty.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This was originally published on &lt;a href="https://eichefam.net/2022/08/26/setting-up-steam-link/" rel="noopener noreferrer"&gt;eichefam.net&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>steam</category>
    </item>
    <item>
      <title>Custom icons in the WordPress block editor</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Sat, 27 Mar 2021 01:43:54 +0000</pubDate>
      <link>https://forem.com/peiche/custom-icons-in-the-wordpress-block-editor-5am2</link>
      <guid>https://forem.com/peiche/custom-icons-in-the-wordpress-block-editor-5am2</guid>
      <description>&lt;p&gt;It's been a while since I've done some WordPress work, so this was a nice change of pace. I read on the &lt;a href="https://wptavern.com/insert-font-awesome-icons-into-the-block-editor-via-the-jvm-gutenberg-rich-text-icons-plugin" rel="noopener noreferrer"&gt;WP Tavern&lt;/a&gt; about a new plugin called &lt;a href="https://wordpress.org/plugins/jvm-rich-text-icons/" rel="noopener noreferrer"&gt;JVM Gutenberg Rich Text Icons&lt;/a&gt;, which allows icons to be used in rich text fields (e.g. paragraph and headings, etc).&lt;/p&gt;

&lt;p&gt;I'm a big fan of &lt;a href="https://nucleoapp.com/" rel="noopener noreferrer"&gt;Nucleo&lt;/a&gt; icons, some of which I already use in my WordPress theme &lt;a href="https://github.com/peiche/garrick" rel="noopener noreferrer"&gt;Garrick&lt;/a&gt;, but I've thought for a while now that it would be nice to have access to more icons as inline elements -- not just outside the editor or as block-level elements. This plugin fills that gap nicely, but it comes with Font Awesome 4 out of the box. Fortunately, it also provides some filters that allow for overriding this default functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Nucleo icon font
&lt;/h2&gt;

&lt;p&gt;I'm getting ahead of myself, a little bit. Before I can override Font Awesome, I need a replacement icon font and stylesheet. Because Nucleo is a paid icon library and contains over 30,000 icons, it doesn't have a ready-made icon font the way Font Awesome 4 does. As a result, I followed Nucleo's &lt;a href="https://nucleoapp.com/docs/exporting-icons#export-font" rel="noopener noreferrer"&gt;directions&lt;/a&gt; to create my own. As of this writing, the font I exported contains about 2,700 line icons. (The two main styles to choose from are solid and line, since full-color and duotone icons aren't possible with an icon font.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the icon font
&lt;/h2&gt;

&lt;p&gt;The resulting exported fonts and stylesheet can be used directly in my WordPress child theme. Since I have a webpack (Laravel Mix, actually) build process, I put the files in these locations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/resources/fonts/
/resources/scss/nucleo.scss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My build process copies the fonts and compiles the styles here, respectively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dist/fonts/
/dist/css/nucleo.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the stylesheet already references the font files, I just need to override the WordPress plugin's stylesheet with its provided filter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'jvm_richtext_icons_css_file'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$cssfile&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$cssfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_stylesheet_directory_uri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/dist/css/nucleo.css'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$cssfile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This stylesheet will get loaded in two places: the block editor and the rendered website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Selectable icons in the block editor
&lt;/h2&gt;

&lt;p&gt;The stylesheet, however, is only half the puzzle. There still needs to be a way for the user to know what icons are available to select in the block editor. To solve this, a separate file is needed, which lists the class names. The plugin's default file is in &lt;code&gt;json&lt;/code&gt; format, which looks like this (albeit truncated here for readability):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"fa fa-address-book"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"fa fa-address-card"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"fa fa-adjust"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To be perfectly blunt, there is no easy or direct way to recreate this list for my Nucleo icons. The app does have a JSON export option, but it exports the SVG content of each icon, not just the names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"icons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"piano"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;svg xmlns=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;http://www.w3.org/2000/svg&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; viewBox=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;0 0 24 24&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; width=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;24&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; height=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;24&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;...&amp;lt;/svg&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"set_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The icon font export contains an unformatted unicode list of all the icons, which is actually pretty close to what I want (again, edited for brevity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"nucleo-icon-digital-key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;59906&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"nucleo-icon-line-chart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;59907&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of an object with key-value paired properties, I just need a list of class named. To get this, I had to restructure the file a bit. I don't know of a way to completely automate this, but the process isn't so complicated that it can't be duplicated if I decide to add more icons.&lt;/p&gt;

&lt;p&gt;First, I opened the file in Visual Studio Code and formatted the file (right-click &amp;gt; Format Document, or Shift+Alt+F):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nucleo-icon-digital-key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;59906&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nucleo-icon-line-chart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;59907&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added a comma to the last item, then selected all the lines inside the curly brackets. From the Selection menu, I clicked Add Cursors to Line Ends (Shift+Alt+I) and deleted the comma, unicode value, and colon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"nucleo-icon-digital-key"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="s2"&gt;"nucleo-icon-line-chart"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While the multiple cursor mode was still active, I added a comma which added it to all the lines. Then I removed the comma from the last line and changed the curly brackets to square. I saved this file alongside my other pre-build files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/resources/json/nucleo.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I run my build process, it's placed in the directory I want to reference from the filter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dist/json/nucleo.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Just JSON file things
&lt;/h2&gt;

&lt;p&gt;Now here's where things got hairy. According to the documentation, I should have been able to use the following filter to override the plugin's &lt;code&gt;json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;
&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'jvm_richtext_icons_iconset_file'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_stylesheet_directory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/dist/json/nucleo.json'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this never worked. After some digging, I found this logic in the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$iconFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_filters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jvm_richtext_icons_iconset_file'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$iconFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$icons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$iconFile&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$iconData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$iconFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nv"&gt;$icons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$iconData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nv"&gt;$icons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;apply_filters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'jvm_richtext_icons_iconset'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$icons&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For whatever reason, the &lt;code&gt;file_exists()&lt;/code&gt; function call was returning false, even though I confirmed that my new &lt;code&gt;json&lt;/code&gt; file was being loaded successfully. I did notice, however, that there was another filter where I could override things. So I updated my filter logic to use the &lt;code&gt;jvm_richtext_icons_iconset&lt;/code&gt; filter instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'jvm_richtext_icons_iconset'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$icons&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_stylesheet_directory_uri&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/dist/json/nucleo.json'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nv"&gt;$icons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nv"&gt;$iconData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nv"&gt;$icons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$iconData&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$icons&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I already knew my file existed, so instead of overriding the file, I overrode the collection being built using the file. And with that, I have successfully replaced Font Awesome with a custom Nucleo icon library for use within the WordPress block editor.&lt;/p&gt;




&lt;p&gt;I do want to mention that in my WordPress theme, I have implemented icons by using individual SVGs instead of by icon font. By doing this, I avoid a couple problems which this plugin cannot in its current implementation address. Firstly, the entire icon library is loaded (via its fonts and stylesheet) with every page view. Secondly, there is a flash of the unsupported character in place of each icon until the fonts and stylesheet are loaded and parsed.&lt;/p&gt;

&lt;p&gt;It is my hope that the plugin author will eventually address this and add support for loading SVGs in place of icon fonts. As it is, this plugin is the only one of its kind which allows for inline icons within the WordPress block editor.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>css</category>
    </item>
    <item>
      <title>Building a local running tracker</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Wed, 27 Jan 2021 17:46:02 +0000</pubDate>
      <link>https://forem.com/peiche/building-a-local-running-tracker-3h22</link>
      <guid>https://forem.com/peiche/building-a-local-running-tracker-3h22</guid>
      <description>&lt;p&gt;My local runners' club has a four-month-long challenge to run 100 miles. They provided a PDF of a 10x10 checkbox grid, instructing runners to print it and check off each mile completed. My reaction was something akin to the brat in Back to the Future 2:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz3mqwclysqaomr0prv4s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz3mqwclysqaomr0prv4s.jpg" alt="" width="623" height="329"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I thought to myself, &lt;em&gt;Self, we should build an interactive version of this form.&lt;/em&gt; So I did. The final version is available &lt;a href="https://100-miles-in-100-days.netlify.app/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. It's written in React, the source is available on &lt;a href="https://github.com/peiche/100-miles-in-100-days" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, and deployed automatically by &lt;a href="https://app.netlify.com/sites/100-miles-in-100-days/deploys" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the app
&lt;/h2&gt;

&lt;p&gt;I used &lt;code&gt;create-react-app&lt;/code&gt; to build the scaffolding for this webapp. It's the first time I've done so, but since I've built a couple sites with Gatsby (which is itself a React project), I felt comfortable enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSS framework
&lt;/h3&gt;

&lt;p&gt;It's pretty typical for modern projects to either use Bootstrap or Tailwind. The former is pretty drop-in ready, while the latter requires a little more tooling. I myself have preferred the CodyFrame library for some time. Its grid system is as easy to use as Bootstrap's, so I customized it to be 10 columns wide (instead of the default 12).&lt;/p&gt;

&lt;h3&gt;
  
  
  100 checkboxes
&lt;/h3&gt;

&lt;p&gt;Sure, I could have copy-and-pasted 100 grid columns and checkboxes. But one of the advantages of rendering the whole app inside JavaScript is the ability to iterate and render dynamically (without the need for a backend server, anyway). So that's what I did:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// simplified HTML 😉&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"checkbox"&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`day-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)})}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Saving data
&lt;/h3&gt;

&lt;p&gt;It was important to me that this app wouldn't save any data, while at the same time allowing the user to keep track of their progress. To accomplish that, data would only be saved locally -- no data would be stored outside of local storage. In JavaScript, that's as simple as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to minimize the number of calls to directly modify local storage, the name and checkboxes' change events modify the state. This is also done to make sure that all properties are stored in local storage as JSON. The &lt;code&gt;componentDidMount&lt;/code&gt; and &lt;code&gt;componentDidUpdate&lt;/code&gt; functions are then responsible both for getting from and setting to local storage, as well as parsing and stringifying (it is too a word) JSON formatting. For example, here's all the functionality for the name textbox (for simplicity's sake):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;componentDidMount&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameJson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nameJson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nameJson&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;componentDidUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prevState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exporting a DOM node to an image
&lt;/h3&gt;

&lt;p&gt;Something I thought would be pretty cool is the ability to generate (and download) an image of one's progress. I've dabbled a bit with generating images with Java on the server side, but never in a client-side app. My searching led me to find &lt;a href="https://github.com/tsayen/dom-to-image" rel="noopener noreferrer"&gt;dom-to-image&lt;/a&gt;, which has options for exporting to a JPEG, a PNG, or even a blob (not the killer kind; a "blob" is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob" rel="noopener noreferrer"&gt;raw file data&lt;/a&gt;, which can be read or processed as you wish). Combined with another library to &lt;a href="https://github.com/eligrey/FileSaver.js/" rel="noopener noreferrer"&gt;make saving files easier&lt;/a&gt;, exporting the image is done easily:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;domtoimage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;bgcolor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#ffffff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;saveAs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100-miles.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trial and error taught me that I had to manually set the background color of the image. I also found, per dom-to-image's documentation, that Safari is not supported. (This also means iPhones.) This particular issue might prevent a one-click image download, but it doesn't prevent the app's primary intent from being used. Being responsive, it's usable on any device -- and any user can take a screenshot, anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying the app
&lt;/h2&gt;

&lt;p&gt;I wrote this with the intent of it being a solely static, client-side app, so deploying to Netlify made the most sense to me. They have a wonderful, easy process for deploying any git site. Since I put the source on GitHub, the entire process couldn't have been easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The final product!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk407zvjlxzrxnuri012a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk407zvjlxzrxnuri012a.png" alt="Alt Text" width="800" height="951"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>react</category>
      <category>showdev</category>
    </item>
    <item>
      <title>New Gatsby Starter</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Tue, 12 Jan 2021 14:33:53 +0000</pubDate>
      <link>https://forem.com/peiche/new-gatsby-starter-30m6</link>
      <guid>https://forem.com/peiche/new-gatsby-starter-30m6</guid>
      <description>&lt;p&gt;I've built a couple different sites now with Gatsby: my own and my sister's portfolio sites. I built them both using CodyHouse's CSS framework &lt;a href="https://codyhouse.co/ds/get-started" rel="noopener noreferrer"&gt;CodyFrame&lt;/a&gt;, but the process to build each has been a little bit of a learning experience. I wanted to take that experience and generalize it for future use, both for myself and for anyone else who wanted it. To that end, I'm happy to announce:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/peiche/gatsby-starter-codyhouse" rel="noopener noreferrer"&gt;Gatsby + CodyHouse Starter&lt;/a&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is CodyFrame?
&lt;/h3&gt;

&lt;p&gt;CodyFrame is a front-end framework, complete with CSS classes (both BEM and utility) and utility JavaScript, which has no dependencies (like jQuery). CodyFrame's benefit is enhanced with its optional components. &lt;/p&gt;

&lt;h3&gt;
  
  
  What are CodyHouse Components?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://codyhouse.co/ds/components" rel="noopener noreferrer"&gt;Components&lt;/a&gt; is a library of components built with CodyFrame in mind. The CSS and JS is easily dropped into any project using CodyFrame, making it easy to build a fully interactive web app. The JS in each component is plain JavaScript, ensuring that it will work with any framework -- including React and Gatsby.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a component with Gatsby
&lt;/h3&gt;

&lt;p&gt;I recommend following CodyHouse's &lt;a href="https://codyhouse.co/blog/post/using-the-codyhouse-components-with-react" rel="noopener noreferrer"&gt;directions&lt;/a&gt; for including Components within React. With regard to this starter, a new component file can be created within the &lt;code&gt;src/components&lt;/code&gt; directory. The SCSS code would be placed inside a new include created inside the &lt;code&gt;src/styles/components&lt;/code&gt; directory, and don't forget to update the &lt;code&gt;_index.scss&lt;/code&gt; file in there! Place the JS code in its own file in &lt;code&gt;static/js&lt;/code&gt;, and follow the CodyHouse directions for creating/removing the script element in the component's &lt;code&gt;componentDidMount&lt;/code&gt; and &lt;code&gt;componentWillUnmount&lt;/code&gt; functions, respectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optionally use with PurgeCSS
&lt;/h3&gt;

&lt;p&gt;The starter also includes a commented-out section in the &lt;code&gt;gatsby-config.json&lt;/code&gt; file, which can be uncommented to enable using PurgeCSS. Keep in mind that this is not a zero-config option; each use-case will differ, so be sure to follow the &lt;a href="https://www.gatsbyjs.com/plugins/gatsby-plugin-purgecss/" rel="noopener noreferrer"&gt;plugin directions&lt;/a&gt; for fine-tuning the configuration.&lt;/p&gt;




&lt;p&gt;Under normal circumstances, I would also submit this starter to Gatsby's official starters page. They're currently not accepting any new submissions, so I'll have to wait to do that. 🤷‍♂️&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>react</category>
      <category>github</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Migrating from Travis CI to GitHub Actions</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Thu, 10 Dec 2020 00:47:53 +0000</pubDate>
      <link>https://forem.com/peiche/migrating-from-travis-ci-to-github-actions-7k1</link>
      <guid>https://forem.com/peiche/migrating-from-travis-ci-to-github-actions-7k1</guid>
      <description>&lt;p&gt;I maintain an open source WordPress theme called &lt;a href="https://boldoak.design/work/garrick/" rel="noopener noreferrer"&gt;Garrick&lt;/a&gt; which I also use to power my family blog. (This is known as "dogfooding," in case you're wondering.) It's my third WordPress theme which I've developed from scratch, more or less, not counting the starter theme used as the base. (The first two used Underscores, while Garrick is built on &lt;a href="https://github.com/justintadlock/mythic" rel="noopener noreferrer"&gt;Justin Tadlock's Mythic&lt;/a&gt;.) Since the very start of my WordPress theme development journey, however, the one thing that hasn't changed is my use of Travis CI to validate file changes. In that regard, to my mind, this is the end of an era.&lt;/p&gt;

&lt;p&gt;Travis CI recently announced a &lt;a href="https://blog.travis-ci.com/2020-11-02-travis-ci-new-billing" rel="noopener noreferrer"&gt;change in their pricing plans&lt;/a&gt; which make things a little more difficult for open source maintainers. Garrick is a small enough project where it isn't really affected, but I've &lt;a href="https://eichefam.net/2016/10/14/all-that-you-love-will-be-refactored-away/" rel="noopener noreferrer"&gt;seen this happen&lt;/a&gt; enough times to know that it's better, wherever possible, to not depend on third parties for functionality that may suddenly be removed or crippled in some way.&lt;/p&gt;

&lt;p&gt;Since Garrick is actively maintained and hosted on GitHub, it makes sense to migrate the CI functionality to &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt;, GitHub's relatively new workflow automation system. It can do more than just CI/CD; in fact, I built an action to &lt;a href="https://boldoak.design/blog/automatically-creating-the-release-for-a-wordpress-theme/" rel="noopener noreferrer"&gt;automate bundling a release&lt;/a&gt; zip file whenever a new release is created.&lt;/p&gt;

&lt;p&gt;The Travis config I use is relatively simple. Since the WordPress theme uses Composer and npm to manage dependencies, the config must install them first. The theme already has scripts set up to validate the PHP files using phpcs, Sass using Stylelint, and JavaScript using ESLint. I simply reuse those within the Travis config. Here is an example (edited for brevity):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;php&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;fast_finish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;php&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;7.4&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SNIFF=1&lt;/span&gt;
&lt;span class="na"&gt;before_install&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if [[ "$SNIFF" == 1 ]]; then nvm install --lts; fi&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;composer install&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if [[ "$SNIFF" == 1 ]]; then npm install; fi&lt;/span&gt;
&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;composer validate --no-check-all --strict&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if [[ "$SNIFF" == 1 ]]; then npm run lint:php; fi&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if [[ "$SNIFF" == 1 ]]; then npm run lint:scripts; fi&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if [[ "$SNIFF" == 1 ]]; then npm run lint:styles; fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting up an equivalent GitHub Action was pretty simple. I clicked on the Actions tab, then clicked on "New Workflow." Choosing "set up a workflow yourself" allows you to start with a sample &lt;code&gt;.yml&lt;/code&gt; file, which is what I did. It took a little bit of trial and error to get it right, but the resulting config runs validation on every update, including on all branches, not just on the master.&lt;/p&gt;

&lt;p&gt;While Travis makes things a little easier to use PHP and Composer, doing the same on GitHub Actions requires manual setup. There exists an action for running &lt;code&gt;npm install&lt;/code&gt;; there is also one for composer, but I found that installing the composer dependencies can be done just by calling the command directly. So in the interest of limiting my action dependencies, I opted for that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bahmutov/npm-install@v1&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;composer install --optimize-autoloader&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;composer validate --no-check-all --strict&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;npm run lint:php&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;npm run lint:scripts&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;npm run lint:styles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I do want to talk for a moment about when the action runs. GitHub's sample action has this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I didn't want the action to run &lt;em&gt;only&lt;/em&gt; on the main branch, since I do want validation on all branches. So I tried this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That resulted in the action actually being run &lt;em&gt;twice&lt;/em&gt;. After rereading the documentation, I ended up with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That results in only running the action once, for any branch. It's not exactly intuitive, since it looks (to me, at least) like some configuration is missing. But it works, so I'm not going to argue with that.&lt;/p&gt;

&lt;p&gt;So all that was left to be done at this point was to delete the old Travis config. Good bye Travis CI, hello GitHub Action.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>github</category>
    </item>
    <item>
      <title>Automatically creating the release for a WordPress theme</title>
      <dc:creator>Paul</dc:creator>
      <pubDate>Fri, 28 Aug 2020 05:44:05 +0000</pubDate>
      <link>https://forem.com/peiche/automatically-creating-the-release-for-a-wordpress-theme-1bdm</link>
      <guid>https://forem.com/peiche/automatically-creating-the-release-for-a-wordpress-theme-1bdm</guid>
      <description>&lt;p&gt;The new GitHub Action I’ve set up will build a new downloadable copy of a WordPress theme when I create a new release or tag in its GitHub repository. In this regard, it’s still fired off by a manual action, but the uploaded file is automatically attached to the new release. This way I can create a new release and just concern myself with writing the description, without having to manually build the theme and upload its zip file.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Workflow
&lt;/h3&gt;

&lt;p&gt;My WordPress theme uses Justin Tadlock's Mythic starter theme as its base. As such, it has both Composer and npm dependencies, both of which must be included for the theme to work. To that end, I've created a new GitHub Action called "Generate Installable Theme and Upload as Release Asset" to automate this process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fokox737p1hcsnfpr4tbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fokox737p1hcsnfpr4tbk.png" alt="Alt Text" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The action is fired off when I create a new release (through either the Releases or Tags tab), which will download the production build's Composer and npm dependencies. (These are slightly different than a development build, which has linting utilities not used in production.) The theme's internal build process uses webpack to compile CSS and JavaScript assets, so this same process is invoked by the action once all dependencies are downloaded.&lt;/p&gt;

&lt;p&gt;The final result is a zip file suitable for upload and installation in WordPress, which is then attached to the aforementioned release (or tag).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzovc1zoqq7g58vcxge9r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzovc1zoqq7g58vcxge9r.png" alt="Alt Text" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;DIY Deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/peiche/garrick/blob/master/.github/workflows/main.yml" rel="noopener noreferrer"&gt;main.yml&lt;/a&gt;&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peiche" rel="noopener noreferrer"&gt;
        peiche
      &lt;/a&gt; / &lt;a href="https://github.com/peiche/garrick" rel="noopener noreferrer"&gt;
        garrick
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Garrick is a modern, multi-purpose WordPress theme built to be compatible with the core block editor and page builder.
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;I took inspiration from Leonardo Losoviz's &lt;a href="https://dev.to/leoloso/automatically-creating-the-release-for-a-wordpress-plugin-3j1l"&gt;post&lt;/a&gt; on how he accomplished a similar process for a WordPress plugin. I probably would have had a more difficult time figuring this out without his code to learn from.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was originally published on &lt;a href="https://boldoak.design/blog/automatically-creating-the-release-for-a-wordpress-theme/" rel="noopener noreferrer"&gt;Bold Oak Design&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>actionshackathon</category>
      <category>wordpress</category>
      <category>github</category>
    </item>
  </channel>
</rss>
