(1791,2423,'Hon-ee Pot Captcha for ExpressionEngine',NULL,'<p>I have a confession to make: <em>I hate captcha</em>. But on the other hand, spam is one of the most frustrating issues ever. So with that being said, I am a big fan of <a href=\"http://haacked.com/archive/2007/09/11/honeypot-captcha.aspx\">Honeypot Captcha</a>.</p>\n\n<p>I couldn’t find any ExpressionEngine addons that added honeypot captcha functionality for both the comment form and the <a href=\"http://www.solspace.com/software/detail/freeform/\">Freeform</a> module, so I decided to go ahead and create one.</p>\n\n<h2>Basic Functionality</h2>\n<p>This extension validates the EE comment form and Freeform forms to make sure a field that is hidden with CSS is left empty.</p>\n\n<h2>Getting Started</h2>\n<ul><li><a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">Download Hon-ee Pot Captcha</a></li>\n  <li>Unzip the folder and copy the <em>honeepot</em> folder into your /system/expressionengine/third_party/ directory.</li>\n  <li>Go to Add-ons → Extensions and “Enable extension”.</li>\n  <li>You can customize the field name for the honey pot field and the message a user receives if the field is filled in by going to the extension settings.</li>\n</ul><h3>Setup</h3>\n<p>In the Freeform module, go to Fields → Create a New Field. Now create a field with the field name matching the field name in the Hon-ee Pot Captcha settings. The default is <em>honeepot</em>.</p>\n\n<p>Now in your form, add the honey pot field:</p>\n\n<pre>&lt;li class=\"screen-reader\"&gt;\n  &lt;label for=\"honeepot\"&gt;Don\'t put anything here&lt;/label&gt;\n  &lt;input type=\"text\" name=\"honeepot\" id=\"honeepot\" /&gt;\n&lt;/li&gt;</pre>\n\n<p>In my CSS, I have a class to move things off of the page:</p>\n<pre>.screen-reader {\n  display: block !important;\n  left: -9999px !important;\n  position: absolute !important;\n  top: -9999px !important;\n}</pre>\n\n<p>You can add the same form field to your comment forms as well.</p>\n\n<h2>That is all</h2>\n<p>Hopefully this addition will help to combat some of the spam contact form submissions and comments (even though they get caught by <a href=\"http://loweblog.com/software/low-nospam/\">Low NoSpam</a>). You can <a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">download the Hon-ee Pot Captcha extension on Github</a>. Let me know if you run into any issues.</p>',NULL,1971,NULL,'2022-04-22 15:17:21','2022-04-22 15:17:21','e11e4e2e-2da8-4f42-b897-c68aa1a45916',1,NULL,NULL,0,NULL),(1792,2424,'Hon-ee Pot Captcha for ExpressionEngine',NULL,'<p>I have a confession to make: <em>I hate captcha</em>. But on the other hand, spam is one of the most frustrating issues ever. So with that being said, I am a big fan of <a href=\"http://haacked.com/archive/2007/09/11/honeypot-captcha.aspx\">Honeypot Captcha</a>.</p>\n\n<p>I couldn’t find any ExpressionEngine addons that added honeypot captcha functionality for both the comment form and the <a href=\"http://www.solspace.com/software/detail/freeform/\">Freeform</a> module, so I decided to go ahead and create one.</p>\n\n<h2>Basic Functionality</h2>\n<p>This extension validates the EE comment form and Freeform forms to make sure a field that is hidden with CSS is left empty.</p>\n\n<h2>Getting Started</h2>\n<ul><li><a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">Download Hon-ee Pot Captcha</a></li>\n  <li>Unzip the folder and copy the <em>honeepot</em> folder into your /system/expressionengine/third_party/ directory.</li>\n  <li>Go to Add-ons → Extensions and “Enable extension”.</li>\n  <li>You can customize the field name for the honey pot field and the message a user receives if the field is filled in by going to the extension settings.</li>\n</ul><h3>Setup</h3>\n<p>In the Freeform module, go to Fields → Create a New Field. Now create a field with the field name matching the field name in the Hon-ee Pot Captcha settings. The default is <em>honeepot</em>.</p>\n\n<p>Now in your form, add the honey pot field:</p>\n\n<pre>&lt;li class=\"screen-reader\"&gt;\n  &lt;label for=\"honeepot\"&gt;Don\'t put anything here&lt;/label&gt;\n  &lt;input type=\"text\" name=\"honeepot\" id=\"honeepot\" /&gt;\n&lt;/li&gt;</pre>\n\n<p>In my CSS, I have a class to move things off of the page:</p>\n<pre>.screen-reader {\n  display: block !important;\n  left: -9999px !important;\n  position: absolute !important;\n  top: -9999px !important;\n}</pre>\n\n<p>You can add the same form field to your comment forms as well.</p>\n\n<h2>That is all</h2>\n<p>Hopefully this addition will help to combat some of the spam contact form submissions and comments (even though they get caught by <a href=\"http://loweblog.com/software/low-nospam/\">Low NoSpam</a>). You can <a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">download the Hon-ee Pot Captcha extension on Github</a>. Let me know if you run into any issues.</p>',NULL,1971,NULL,'2022-04-22 15:17:37','2022-04-22 15:17:37','6e61b13a-e5df-4e82-9609-c969033f5018',1,NULL,NULL,1,NULL),(1793,2425,'Hon-ee Pot Captcha for ExpressionEngine',NULL,'<p>I have a confession to make: <em>I hate captcha</em>. But on the other hand, spam is one of the most frustrating issues ever. So with that being said, I am a big fan of <a href=\"http://haacked.com/archive/2007/09/11/honeypot-captcha.aspx\">Honeypot Captcha</a>.</p>\n\n<p>I couldn’t find any ExpressionEngine addons that added honeypot captcha functionality for both the comment form and the <a href=\"http://www.solspace.com/software/detail/freeform/\">Freeform</a> module, so I decided to go ahead and create one.</p>\n\n<h2>Basic Functionality</h2>\n<p>This extension validates the EE comment form and Freeform forms to make sure a field that is hidden with CSS is left empty.</p>\n\n<h2>Getting Started</h2>\n<ul><li><a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">Download Hon-ee Pot Captcha</a></li>\n  <li>Unzip the folder and copy the <em>honeepot</em> folder into your /system/expressionengine/third_party/ directory.</li>\n  <li>Go to Add-ons → Extensions and “Enable extension”.</li>\n  <li>You can customize the field name for the honey pot field and the message a user receives if the field is filled in by going to the extension settings.</li>\n</ul><h3>Setup</h3>\n<p>In the Freeform module, go to Fields → Create a New Field. Now create a field with the field name matching the field name in the Hon-ee Pot Captcha settings. The default is <em>honeepot</em>.</p>\n\n<p>Now in your form, add the honey pot field:</p>\n\n<pre>&lt;li class=\"screen-reader\"&gt;\n  &lt;label for=\"honeepot\"&gt;Don\'t put anything here&lt;/label&gt;\n  &lt;input type=\"text\" name=\"honeepot\" id=\"honeepot\" /&gt;\n&lt;/li&gt;</pre>\n\n<p>In my CSS, I have a class to move things off of the page:</p>\n<pre>.screen-reader {\n  display: block !important;\n  left: -9999px !important;\n  position: absolute !important;\n  top: -9999px !important;\n}</pre>\n\n<p>You can add the same form field to your comment forms as well.</p>\n\n<h2>That is all</h2>\n<p>Hopefully this addition will help to combat some of the spam contact form submissions and comments (even though they get caught by <a href=\"http://loweblog.com/software/low-nospam/\">Low NoSpam</a>). You can <a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">download the Hon-ee Pot Captcha extension on Github</a>. Let me know if you run into any issues.</p>',NULL,1971,NULL,'2022-04-22 15:17:57','2022-04-22 15:17:57','5a23926e-27b1-41f3-a0fa-34e480850ffb',1,NULL,NULL,0,NULL),(1794,2426,'Hon-ee Pot Captcha for ExpressionEngine',NULL,'<p>I have a confession to make: <em>I hate captcha</em>. But on the other hand, spam is one of the most frustrating issues ever. So with that being said, I am a big fan of <a href=\"http://haacked.com/archive/2007/09/11/honeypot-captcha.aspx\">Honeypot Captcha</a>.</p>\n\n<p>I couldn’t find any ExpressionEngine addons that added honeypot captcha functionality for both the comment form and the <a href=\"http://www.solspace.com/software/detail/freeform/\">Freeform</a> module, so I decided to go ahead and create one.</p>\n\n<h2>Basic Functionality</h2>\n<p>This extension validates the EE comment form and Freeform forms to make sure a field that is hidden with CSS is left empty.</p>\n\n<h2>Getting Started</h2>\n<ul><li><a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">Download Hon-ee Pot Captcha</a></li>\n  <li>Unzip the folder and copy the <em>honeepot</em> folder into your /system/expressionengine/third_party/ directory.</li>\n  <li>Go to Add-ons → Extensions and “Enable extension”.</li>\n  <li>You can customize the field name for the honey pot field and the message a user receives if the field is filled in by going to the extension settings.</li>\n</ul><h3>Setup</h3>\n<p>In the Freeform module, go to Fields → Create a New Field. Now create a field with the field name matching the field name in the Hon-ee Pot Captcha settings. The default is <em>honeepot</em>.</p>\n\n<p>Now in your form, add the honey pot field:</p>\n\n<pre>&lt;li class=\"screen-reader\"&gt;\n  &lt;label for=\"honeepot\"&gt;Don\'t put anything here&lt;/label&gt;\n  &lt;input type=\"text\" name=\"honeepot\" id=\"honeepot\" /&gt;\n&lt;/li&gt;</pre>\n\n<p>In my CSS, I have a class to move things off of the page:</p>\n<pre>.screen-reader {\n  display: block !important;\n  left: -9999px !important;\n  position: absolute !important;\n  top: -9999px !important;\n}</pre>\n\n<p>You can add the same form field to your comment forms as well.</p>\n\n<h2>That is all</h2>\n<p>Hopefully this addition will help to combat some of the spam contact form submissions and comments (even though they get caught by <a href=\"http://loweblog.com/software/low-nospam/\">Low NoSpam</a>). You can <a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">download the Hon-ee Pot Captcha extension on Github</a>. Let me know if you run into any issues.</p>',NULL,1971,NULL,'2022-04-22 15:18:11','2022-04-22 15:18:11','6f0f9ffa-de37-4de3-bc8c-11c66a2cebd6',1,NULL,NULL,1,NULL),(1795,2427,'Hon-ee Pot Captcha for ExpressionEngine',NULL,'<p>I have a confession to make: <em>I hate captcha</em>. But on the other hand, spam is one of the most frustrating issues ever. So with that being said, I am a big fan of <a href=\"http://haacked.com/archive/2007/09/11/honeypot-captcha.aspx\">Honeypot Captcha</a>.</p>\n\n<p>I couldn’t find any ExpressionEngine addons that added honeypot captcha functionality for both the comment form and the <a href=\"http://www.solspace.com/software/detail/freeform/\">Freeform</a> module, so I decided to go ahead and create one.</p>\n\n<h2>Basic Functionality</h2>\n<p>This extension validates the EE comment form and Freeform forms to make sure a field that is hidden with CSS is left empty.</p>\n\n<h2>Getting Started</h2>\n<ul><li><a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">Download Hon-ee Pot Captcha</a></li>\n  <li>Unzip the folder and copy the <em>honeepot</em> folder into your /system/expressionengine/third_party/ directory.</li>\n  <li>Go to Add-ons → Extensions and “Enable extension”.</li>\n  <li>You can customize the field name for the honey pot field and the message a user receives if the field is filled in by going to the extension settings.</li>\n</ul><h3>Setup</h3>\n<p>In the Freeform module, go to Fields → Create a New Field. Now create a field with the field name matching the field name in the Hon-ee Pot Captcha settings. The default is <em>honeepot</em>.</p>\n\n<p>Now in your form, add the honey pot field:</p>\n\n<pre>&lt;li class=\"screen-reader\"&gt;\n  &lt;label for=\"honeepot\"&gt;Don\'t put anything here&lt;/label&gt;\n  &lt;input type=\"text\" name=\"honeepot\" id=\"honeepot\" /&gt;\n&lt;/li&gt;</pre>\n\n<p>In my CSS, I have a class to move things off of the page:</p>\n<pre>.screen-reader {\n  display: block !important;\n  left: -9999px !important;\n  position: absolute !important;\n  top: -9999px !important;\n}</pre>\n\n<p>You can add the same form field to your comment forms as well.</p>\n\n<h2>That is all</h2>\n<p>Hopefully this addition will help to combat some of the spam contact form submissions and comments (even though they get caught by <a href=\"http://loweblog.com/software/low-nospam/\">Low NoSpam</a>). You can <a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">download the Hon-ee Pot Captcha extension on Github</a>. Let me know if you run into any issues.</p>',NULL,1971,NULL,'2022-04-22 15:18:20','2022-04-22 15:18:20','708e711f-90f8-48a8-8cb4-ba77a2c29213',1,NULL,NULL,0,NULL),(1796,2428,'Hon-ee Pot Captcha for ExpressionEngine',NULL,'<p>I have a confession to make: <em>I hate captcha</em>. But on the other hand, spam is one of the most frustrating issues ever. So with that being said, I am a big fan of <a href=\"http://haacked.com/archive/2007/09/11/honeypot-captcha.aspx\">Honeypot Captcha</a>.</p>\n\n<p>I couldn’t find any ExpressionEngine addons that added honeypot captcha functionality for both the comment form and the <a href=\"http://www.solspace.com/software/detail/freeform/\">Freeform</a> module, so I decided to go ahead and create one.</p>\n\n<h2>Basic Functionality</h2>\n<p>This extension validates the EE comment form and Freeform forms to make sure a field that is hidden with CSS is left empty.</p>\n\n<h2>Getting Started</h2>\n<ul><li><a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">Download Hon-ee Pot Captcha</a></li>\n  <li>Unzip the folder and copy the <em>honeepot</em> folder into your /system/expressionengine/third_party/ directory.</li>\n  <li>Go to Add-ons → Extensions and “Enable extension”.</li>\n  <li>You can customize the field name for the honey pot field and the message a user receives if the field is filled in by going to the extension settings.</li>\n</ul><h3>Setup</h3>\n<p>In the Freeform module, go to Fields → Create a New Field. Now create a field with the field name matching the field name in the Hon-ee Pot Captcha settings. The default is <em>honeepot</em>.</p>\n\n<p>Now in your form, add the honey pot field:</p>\n\n<pre>&lt;li class=\"screen-reader\"&gt;\n  &lt;label for=\"honeepot\"&gt;Don\'t put anything here&lt;/label&gt;\n  &lt;input type=\"text\" name=\"honeepot\" id=\"honeepot\" /&gt;\n&lt;/li&gt;</pre>\n\n<p>In my CSS, I have a class to move things off of the page:</p>\n<pre>.screen-reader {\n  display: block !important;\n  left: -9999px !important;\n  position: absolute !important;\n  top: -9999px !important;\n}</pre>\n\n<p>You can add the same form field to your comment forms as well.</p>\n\n<h2>That is all</h2>\n<p>Hopefully this addition will help to combat some of the spam contact form submissions and comments (even though they get caught by <a href=\"http://loweblog.com/software/low-nospam/\">Low NoSpam</a>). You can <a href=\"https://github.com/davist11/Hon-ee-Pot-Captcha\">download the Hon-ee Pot Captcha extension on Github</a>. Let me know if you run into any issues.</p>',NULL,1971,NULL,'2022-04-22 15:18:24','2022-04-22 15:18:24','20704290-d95c-417a-af80-3f91d0950a1f',1,NULL,NULL,1,NULL),(1797,2429,'Simple jQuery Text Resizer',NULL,'<p>I have done JavaScript text resizers a couple of different ways before, but I think I found my favorite way on a recent project. In previous projects, I have messed around with determining the current font size and adjusting it appropriately. But, in my opinion, simplifying things has made it a much better implementation.</p>\n\n\n<p>If you want to skip right to the <a href=\"//assets.trevor-davis.com/play/jquery-text-resizer/\">demo</a>, feel free.</p>\n\n<p><a href=\"//assets.trevor-davis.com/play/jquery-text-resizer/\" class=\"more-link\">View Demo</a></p>\n\n<h2>Disclaimer</h2>\n<p>Let me first say that I have started to size text in pixels and <a href=\"http://danielmall.com/archives/2007/04/20/soft_serve.php\">soft serving</a> EMs to IE6. There are tons of different font sizing techniques you can use, but this is what is working for me. So use whatever technique you please, and adjust as needed.</p>\n\n<p>Another thing: <strong>I hate text resizers</strong>. In my opinion, there is no reason to duplicate browser functionality. I feel the same about back and print links, but sometimes the functionality is requested, so I have to build it.</p>\n\n<h2>Concept</h2>\n<p>The idea is very simple: you click on the text size you want, and a class is added to the body to indicate which size the user has selected. Then, we take advantage of that body class when we are writing our CSS to size the different elements appropriately.</p>\n\n<h2>Code</h2>\n<p>Let’s first start as we always should, by creating the markup:</p>\n\n<div class=\"codeblock\"><code>\n&lt;ul class=\"resizer\"&gt;<br /> &lt;li class=\"small\"&gt;&lt;a href=\"#\"&gt;A&lt;/a&gt;&lt;/li&gt;<br /> &lt;li class=\"medium\"&gt;&lt;a href=\"#\"&gt;A&lt;/a&gt;&lt;/li&gt;<br /> &lt;li class=\"large\"&gt;&lt;a href=\"#\"&gt;A&lt;/a&gt;&lt;/li&gt;<br />&lt;/ul&gt; \n\n</code></div>\n\n<h3>jQuery</h3>\n<p>The markup was simple enough, so let’s get started on the jQuery. First, we want to execute our code when the document is loaded, and then add an event when one of the resizer links are clicked:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function() {<br /> $(\'.resizer a\').click(function() {<br /><br /> });<br />}); \n\n</code></div>\n\n<p>Next, we want to get the class of the parent of the link that has been clicked, remove any of the resizer classes from the body, and add the new one:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function() {<br /> $(\'.resizer a\').click(function() {<br />  var textSize = $(this).parent().attr(\'class\');<br />  $(\'body\').removeClass(\'small medium large\').addClass(textSize);<br /> });<br />}); \n\n</code></div>\n\n<p>We also don’t want to regular action of the link to be followed, so we need to <strong>return false</strong>:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function() {<br /> $(\'.resizer a\').click(function() {<br />  var textSize = $(this).parent().attr(\'class\');<br />  $(\'body\').removeClass(\'small medium large\').addClass(textSize);<br />  return false;<br /> });<br />}); \n\n</code></div>\n\n<p>I also think it is important to remember what text size a user has selected while browsing the site, so we will take advantage of the <a href=\"http://plugins.jquery.com/project/cookie\">jQuery Cookie plugin</a> to help us with that. We will set a cookie that denotes which size the user has selected:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function() {<br /> $(\'.resizer a\').click(function() {<br />  var textSize = $(this).parent().attr(\'class\');<br />  $(\'body\').removeClass(\'small medium large\').addClass(textSize);<br />  $.cookie(\'TEXT_SIZE\',textSize, { path: \'/\', expires: 10000 });<br />  return false;<br /> });<br />}); \n\n</code></div>\n\n<p>Finally, we just need to add a check to the beginning of the script, so that if that cookie is set, we add that size as a class to the body:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function() {<br /> if($.cookie(\'TEXT_SIZE\')) {<br />  $(\'body\').addClass($.cookie(\'TEXT_SIZE\')); <br /> }<br /> $(\'.resizer a\').click(function() {<br />  var textSize = $(this).parent().attr(\'class\');<br />  $(\'body\').removeClass(\'small medium large\').addClass(textSize);<br />  $.cookie(\'TEXT_SIZE\',textSize, { path: \'/\', expires: 10000 });<br />  return false;<br /> });<br />}); \n\n</code></div>\n\n<h3>CSS</h3>\n<p> Now is where we get to the really powerful part of doing the text resizing like this: the CSS. We can start by simply stating our text size when it is in the small/default state:</p>\n\n<div class=\"codeblock\"><code>\nbody { font: 12px/18px Arial, sans-serif; } \n\n</code></div>\n\n<p>Then, we just need to make a simple adjustment for the medium and large sizes:</p>\n\n<div class=\"codeblock\"><code>\n.medium { font-size: 16px; line-height: 22px; }<br />.large { font-size: 20px; line-height: 26px; } \n\n</code></div>\n\n<p>So you can just carry through this idea for the rest of the elements that have been assigned a font size:</p>\n\n<div class=\"codeblock\"><code>\nh1 { font-size: 30px; line-height: 36px; }<br />.medium h1 { font-size: 34px; line-height: 40px; }<br />.large h1 { font-size: 38px; line-height: 44px; }<br />h2 { font-size: 24px; line-height: 30px; }<br />.medium h2 { font-size: 28px; line-height: 34px; }<br />.large h2 { font-size: 32px; line-height: 38px; }<br />h3 { font-size: 18px; line-height: 24px; }<br />.medium h3 { font-size: 22px; line-height: 28px; }<br />.large h3 { font-size: 26px; line-height: 32px; } \n\n</code></div>\n\n<p><a href=\"//assets.trevor-davis.com/play/jquery-text-resizer/\" class=\"more-link\">View Demo</a></p>\n\n<p>The idea is very simple, and it really separates the presentation and behavioral layers. So what do you think? Do you have another technique that you like?</p>',NULL,1930,NULL,'2022-04-22 15:35:24','2022-04-22 15:35:24','47cc9207-4970-4c56-8b3b-be2db1a463e4',1,NULL,NULL,1,NULL),(1798,2430,'Simple jQuery Text Resizer',NULL,'<p>I have done JavaScript text resizers a couple of different ways before, but I think I found my favorite way on a recent project. In previous projects, I have messed around with determining the current font size and adjusting it appropriately. But, in my opinion, simplifying things has made it a much better implementation.</p>\n\n\n<p>If you want to skip right to the <a href=\"//assets.trevor-davis.com/play/jquery-text-resizer/\">demo</a>, feel free.</p>\n\n<p><a href=\"//assets.trevor-davis.com/play/jquery-text-resizer/\" class=\"more-link\">View Demo</a></p>\n\n<h2>Disclaimer</h2>\n<p>Let me first say that I have started to size text in pixels and <a href=\"http://danielmall.com/archives/2007/04/20/soft_serve.php\">soft serving</a> EMs to IE6. There are tons of different font sizing techniques you can use, but this is what is working for me. So use whatever technique you please, and adjust as needed.</p>\n\n<p>Another thing: <strong>I hate text resizers</strong>. In my opinion, there is no reason to duplicate browser functionality. I feel the same about back and print links, but sometimes the functionality is requested, so I have to build it.</p>\n\n<h2>Concept</h2>\n<p>The idea is very simple: you click on the text size you want, and a class is added to the body to indicate which size the user has selected. Then, we take advantage of that body class when we are writing our CSS to size the different elements appropriately.</p>\n\n<h2>Code</h2>\n<p>Let’s first start as we always should, by creating the markup:</p>\n\n<div class=\"codeblock\"><code>\n&lt;ul class=\"resizer\"&gt;<br /> &lt;li class=\"small\"&gt;&lt;a href=\"#\"&gt;A&lt;/a&gt;&lt;/li&gt;<br /> &lt;li class=\"medium\"&gt;&lt;a href=\"#\"&gt;A&lt;/a&gt;&lt;/li&gt;<br /> &lt;li class=\"large\"&gt;&lt;a href=\"#\"&gt;A&lt;/a&gt;&lt;/li&gt;<br />&lt;/ul&gt; \n\n</code></div>\n\n<h3>jQuery</h3>\n<p>The markup was simple enough, so let’s get started on the jQuery. First, we want to execute our code when the document is loaded, and then add an event when one of the resizer links are clicked:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function() {<br /> $(\'.resizer a\').click(function() {<br /><br /> });<br />}); \n\n</code></div>\n\n<p>Next, we want to get the class of the parent of the link that has been clicked, remove any of the resizer classes from the body, and add the new one:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function() {<br /> $(\'.resizer a\').click(function() {<br />  var textSize = $(this).parent().attr(\'class\');<br />  $(\'body\').removeClass(\'small medium large\').addClass(textSize);<br /> });<br />}); \n\n</code></div>\n\n<p>We also don’t want to regular action of the link to be followed, so we need to <strong>return false</strong>:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function() {<br /> $(\'.resizer a\').click(function() {<br />  var textSize = $(this).parent().attr(\'class\');<br />  $(\'body\').removeClass(\'small medium large\').addClass(textSize);<br />  return false;<br /> });<br />}); \n\n</code></div>\n\n<p>I also think it is important to remember what text size a user has selected while browsing the site, so we will take advantage of the <a href=\"http://plugins.jquery.com/project/cookie\">jQuery Cookie plugin</a> to help us with that. We will set a cookie that denotes which size the user has selected:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function() {<br /> $(\'.resizer a\').click(function() {<br />  var textSize = $(this).parent().attr(\'class\');<br />  $(\'body\').removeClass(\'small medium large\').addClass(textSize);<br />  $.cookie(\'TEXT_SIZE\',textSize, { path: \'/\', expires: 10000 });<br />  return false;<br /> });<br />}); \n\n</code></div>\n\n<p>Finally, we just need to add a check to the beginning of the script, so that if that cookie is set, we add that size as a class to the body:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function() {<br /> if($.cookie(\'TEXT_SIZE\')) {<br />  $(\'body\').addClass($.cookie(\'TEXT_SIZE\')); <br /> }<br /> $(\'.resizer a\').click(function() {<br />  var textSize = $(this).parent().attr(\'class\');<br />  $(\'body\').removeClass(\'small medium large\').addClass(textSize);<br />  $.cookie(\'TEXT_SIZE\',textSize, { path: \'/\', expires: 10000 });<br />  return false;<br /> });<br />}); \n\n</code></div>\n\n<h3>CSS</h3>\n<p> Now is where we get to the really powerful part of doing the text resizing like this: the CSS. We can start by simply stating our text size when it is in the small/default state:</p>\n\n<div class=\"codeblock\"><code>\nbody { font: 12px/18px Arial, sans-serif; } \n\n</code></div>\n\n<p>Then, we just need to make a simple adjustment for the medium and large sizes:</p>\n\n<div class=\"codeblock\"><code>\n.medium { font-size: 16px; line-height: 22px; }<br />.large { font-size: 20px; line-height: 26px; } \n\n</code></div>\n\n<p>So you can just carry through this idea for the rest of the elements that have been assigned a font size:</p>\n\n<div class=\"codeblock\"><code>\nh1 { font-size: 30px; line-height: 36px; }<br />.medium h1 { font-size: 34px; line-height: 40px; }<br />.large h1 { font-size: 38px; line-height: 44px; }<br />h2 { font-size: 24px; line-height: 30px; }<br />.medium h2 { font-size: 28px; line-height: 34px; }<br />.large h2 { font-size: 32px; line-height: 38px; }<br />h3 { font-size: 18px; line-height: 24px; }<br />.medium h3 { font-size: 22px; line-height: 28px; }<br />.large h3 { font-size: 26px; line-height: 32px; } \n\n</code></div>\n\n<p><a href=\"//assets.trevor-davis.com/play/jquery-text-resizer/\" class=\"more-link\">View Demo</a></p>\n\n<p>The idea is very simple, and it really separates the presentation and behavioral layers. So what do you think? Do you have another technique that you like?</p>',NULL,1930,NULL,'2022-04-22 15:35:32','2022-04-22 15:35:32','224b0800-b917-4b32-b37e-6e0b8dadac3b',1,NULL,NULL,0,NULL),(1799,2431,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-04-22 20:40:50','2022-04-22 20:40:50','86677c15-f6f0-4621-a408-b4ee7a0179fe',1,NULL,NULL,23,NULL),(1800,2432,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-04-22 20:40:53','2022-04-22 20:40:53','701e3ab5-c5d8-4b5d-a082-98ed174ba2d6',1,NULL,NULL,24,NULL),(1801,2433,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-04-25 15:23:04','2022-04-25 15:23:04','d4bf4881-1c22-4826-aee1-363f883e2c3f',1,NULL,NULL,25,NULL),(1802,2434,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-04-25 15:23:10','2022-04-25 15:23:10','fb2ff162-b66d-4101-ad63-c4ac8304d5c1',1,NULL,NULL,24,NULL),(1803,2435,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-05-02 15:51:56','2022-05-02 15:51:56','fb7ffd06-0458-4175-b523-9d5059e2e0e5',1,NULL,NULL,25,NULL),(1804,2436,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-05-05 06:13:12','2022-05-05 06:13:12','5af42998-52cc-4d30-8452-b5150af7f6c2',1,NULL,NULL,26,NULL),(1805,2437,'Ajax Forms with jQuery',NULL,'<p>There are so many different javascript frameworks out there, but I have recently started to use <a href=\"http://jquery.com\">jQuery</a>, and I love it. Not only is the library much smaller than others, but it is so simple to use. I wanted to show how easy it is to turn a regular form into a AJAX form.</p>\n\n\n\n<h2 class=\"replaceHeadline\">Start with a Regular Form</h2>\n\n<p>First, I am going to build a <a href=\"//assets.trevor-davis.com/play/jqueryajaxform/\">regular form</a>. The form is just going to be a basic email form. You enter an email address to send to, an email address to send from, a subject, and a message. All fields are required. So the process of the form is:</p>\n\n<ol><li>User fills out the form.</li>\n <li>User submits the form to the server.</li>\n <li>Server side script makes sure that there are no blank fields and that the email addresses are valid.</li>\n <li>If there are errors, show the form again with the error messages.</li>\n <li>If there are no errors, send the email.</li>\n</ol><p>Ok, so this form is pretty simple, and doesn’t take much time to submit since it’s so short, but let’s see how much better we can make it with a little jQuery.</p>\n\n<h2 class=\"replaceHeadline\">Add in JavaScript</h2>\n\n<p>OK, so next, I am going to make an <a href=\"//assets.trevor-davis.com/play/jqueryajaxform/ajaxed/\">AJAX version, which is a duplicate of the first form</a>. The only other things I am going to include on the page are the jquery library (<a href=\"http://docs.jquery.com/Downloading_jQuery\">which you can download here</a>) and my JavaScript to process this form.</p>\n\n\n<h3 class=\"replaceHeadline\">Firing My Script When the Document is Ready</h3>\n<p>jQuery has a nice little function to have you script start when the document is ready:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function(){<br /> //Script goes here<br />}); \n\n</code></div>\n\n<p>OK, easy enough. Now, let’s make something happen when the button to submit the form is clicked. I was smart enough to add an id of <strong>submit</strong> to my button, so it makes it really easy to reference:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function(){<br /> $(\"#submit\").click(function(){<br />  return false;<br /> });<br />}); \n\n</code></div>\n\n<p>First things first, I’m going to hide anything with the class of error if it’s showing (which nothing will be unless the form is unsuccessfully submitted twice). I also create a variable that I am going to use later to see if the form has an error and a variable to store the email regular expression:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function(){<br /> $(\"#submit\").click(function(){<br />  $(\".error\").hide();<br />  var hasError = false;<br />  var emailReg = /^([w-.]+@([w-]+.)+[w-]{2,4})?$/;<br /><br />  return false;<br /> });<br />}); \n\n</code></div>\n\n\n<h3 class=\"replaceHeadline\">Error Checking</h3>\n\n<p>Now that we’ve got the beginning of the script setup, we need to do the error checking. So first, let’s check to make sure that the email to address is not empty or invalid:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function(){<br /> $(\"#submit\").click(function(){<br />  $(\".error\").hide();<br />  var hasError = false;<br />  var emailReg = /^([w-.]+@([w-]+.)+[w-]{2,4})?$/;<br /><br />  var emailToVal = $(\"#emailTo\").val();<br />  if(emailToVal == \'\') {<br />   $(\"#emailTo\").after(\'&lt;span class=\"error\"&gt;You forgot to enter the email address to send to&lt;/span&gt;\');<br />   hasError = true;<br />  } else if(!emailReg.test(emailToVal)) {<br />   $(\"#emailTo\").after(\'&lt;span class=\"error\"&gt;Enter a valid email address to send to.&lt;/span&gt;\');<br />   hasError = true;<br />  }<br /><br />  return false;<br /> });<br />}); \n\n</code></div>\n\n<p>Remember, the email address to send to input has an id of <strong>emailTo</strong>. What that code is saying is:</p>\n\n<ul><li>Get the value of the email to address input.</li>\n <li>If it is empty, add an error message after the input</li>\n <li>If it has an invalid email address, add an error message after the input.</li>\n</ul><p>Now, we just need to repeat the error checking for the other form fields:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function(){<br /> $(\"#submit\").click(function(){<br />  $(\".error\").hide();<br />  var hasError = false;<br />  var emailReg = /^([w-.]+@([w-]+.)+[w-]{2,4})?$/;<br /><br />  var emailToVal = $(\"#emailTo\").val();<br />  if(emailToVal == \'\') {<br />   $(\"#emailTo\").after(\'&lt;span class=\"error\"&gt;You forgot to enter the email address to send to.&lt;/span&gt;\');<br />   hasError = true;<br />  } else if(!emailReg.test(emailToVal)) {<br />   $(\"#emailTo\").after(\'&lt;span class=\"error\"&gt;Enter a valid email address to send to.&lt;/span&gt;\');<br />   hasError = true;<br />  }<br /><br />  var emailFromVal = $(\"#emailFrom\").val();<br />  if(emailFromVal == \'\') {<br />   $(\"#emailFrom\").after(\'&lt;span class=\"error\"&gt;You forgot to enter the email address to send from.&lt;/span&gt;\');<br />   hasError = true;<br />  } else if(!emailReg.test(emailFromVal)) {<br />   $(\"#emailFrom\").after(\'&lt;span class=\"error\"&gt;Enter a valid email address to send from.&lt;/span&gt;\');<br />   hasError = true;<br />  }<br /><br />  var subjectVal = $(\"#subject\").val();<br />  if(subjectVal == \'\') {<br />   $(\"#subject\").after(\'&lt;span class=\"error\"&gt;You forgot to enter the subject.&lt;/span&gt;\');<br />   hasError = true;<br />  }<br /><br />  var messageVal = $(\"#message\").val();<br />  if(messageVal == \'\') {<br />   $(\"#message\").after(\'&lt;span class=\"error\"&gt;You forgot to enter the message.&lt;/span&gt;\');<br />   hasError = true;<br />  }<br /><br />  return false;<br /> });<br />}); \n\n</code></div>\n\n<p>Ok, error checking is done. If there are no errors, we need to send the email via an AJAX request. I’m not going to include the whole script here, just because it’s getting long, but you can <a href=\"//assets.trevor-davis.com/uploads/images/content/2008/01/submitform.js\">see the whole script</a>.</p>\n\n<p>So, there are no errors. Let’s remove the button from the form, and add in a loading graphic:</p>\n\n<div class=\"codeblock\"><code>\nif(hasError == false) {<br /> $(this).hide();<br /> $(\"#sendEmail li.buttons\").append(\'&lt;img src=\"/images/template/loading.gif\" alt=\"Loading\" id=\"loading\" /&gt;\');<br />} \n\n</code></div>\n\n<h3 class=\"replaceHeadline\">Submit the POST Request</h3>\n<p>Ok, now let’s send the values via an AJAX POST request to our send email script that just sends the email. Once the request is completed, let’s hide the form and display a success message:</p>\n\n<div class=\"codeblock\"><code>\n$.post(\"/play/jqueryajaxform/sendEmail.php\",<br />   { emailTo: emailToVal, emailFrom: emailFromVal, subject: subjectVal, message: messageVal },<br />    function(data){<br />  $(\"#sendEmail\").slideUp(\"normal\", function() {<br /><br />   $(\"#sendEmail\").before(\'&lt;h1&gt;Success&lt;/h1&gt;&lt;p&gt;Your email was sent.&lt;/p&gt;\');<br />  });<br /> }<br />); \n\n</code></div>\n\n<p>Pretty nice huh? So now, you have a perfectly accessible form. It works fine without JavaScript. But it works even better with JavaScript. I highly recommend checking out the <a href=\"http://docs.jquery.com/Main_Page\">jQuery Documentation</a>. So go ahead, send yourself some emails to see for yourself.</p>\n\n<h3 class=\"replaceHeadline\">Update…</h3>\n<p>I was asked in the comments if I could include the PHP script. So as requested, here are the files that are used:</p>\n<ul><li><a href=\"//assets.trevor-davis.com/uploads/images/content/2008/01/index.phps\">index.php</a></li>\n<li><a href=\"//assets.trevor-davis.com/uploads/images/content/2008/01/verify.phps\">verify.php</a></li>\n<li><a href=\"//assets.trevor-davis.com/uploads/images/content/2008/01/sendemail.phps\">sendEmail.php</a></li>\n<li><a href=\"//assets.trevor-davis.com/uploads/images/content/2008/01/thanks.phps\">thanks.php</a></li>\n</ul>',NULL,1740,NULL,'2022-06-11 20:50:28','2022-06-11 20:50:28','8584a7b7-ec1d-4b74-8aac-7eca183aa8be',1,NULL,NULL,1,NULL),(1806,2438,'Input vs. Button',NULL,'<p>My latest redesign project at work was for <a href=\"http://www.grouplogic.com/\">Group Logic</a>. Another company did the design and created static HTML templates. My role was to create a single header and footer to make maintaining the site easier, and to wrap the store and other dynamic applications in the new design.</p>\n<p>The company who designed it used images for the form buttons, and I found some interesting things while implementing them.</p>\n\n<h2>A Disclaimer</h2>\n<p>I think before you design form buttons to be images, you should seriously consider just styling the buttons with CSS. Just think about all of the images you will have to maintain. Is the value of the image button style really that great that you will deal with all of that hassle? In my opinion, it’s not. But whatever, to each <strong>his</strong> own (you like that <a href=\"http://monadology.net/\">Nate</a>?). Ok, enough with the disclaimer, now on to the good stuff.</p>\n\n<h2>Some Options</h2>\n<p>Ok, so I decided that there were 3 options for implementing these buttons:</p>\n<ol><li>Use <code>&lt;input type=\"submit\" /&gt;</code> and use an image replacement technique (that I didn’t think would work in Safari).</li>\n<li>Use <code>&lt;input type=\"image\" /&gt;</code> and have the image source as an attribute.</li>\n<li>Use <code>&lt;button type=\"submit\"&gt;&lt;/button&gt;</code> and insert the image within the button element. </li>\n</ol><h3>My Decision</h3>\n<p>With ease of implementation in mind (since the time line was short), I decided to go with option 2: <code>&lt;input type=\"image\" /&gt;</code>. Everything went fine until we decided to test in IE (6 and 7). Apparently, the value from the button does not get passed onto the server. After some Googling around, I found out that this is a well known problem, and the only way to get this to work is by using JavaScript. Whenever I code something with JavaScript, I want to make sure that it works without JavaScript. So that solution would not work.</p>\n\n<h3>The Next Option</h3>\n<p>Next, I went with option 3: <code>&lt;button type=\"submit\"&gt;&lt;/button&gt;</code>. I really did not know much about the <code>button</code> element until I read <a href=\"http://particletree.com/features/rediscovering-the-button-element/\">Rediscovering the Button Element</a> a year ago.</p>\n<p>Again, that option worked fine in everything, except IE6 this time (in IE7, it passed in the entire source of the image as the value, but we could find a way to make that work). Apparently, if you have multiple button elements with the same name attributes, the value that comes out from the server in IE6 is the value from the last element on the form.</p>\n\n<p>Ok, maybe that was confusing. The <a href=\"//assets.trevor-davis.com/play/input-vs-button/\">example page</a> might clarify some. The first example is just <code>&lt;input type=\"submit\" /&gt;</code>. The second example is <code>&lt;input type=\"image\" /&gt;</code>. Finally, the third example is <code>&lt;button type=\"submit\"&gt;&lt;/button&gt;</code>.</p>\n\n<p>Be sure to try that in IE6 and IE7 to see the problems I explained.</p>\n\n<h3>A Third Try?</h3>\n<p>Ok, this sucks, obviously I went with option 1 this time: <code>&lt;input type=\"submit\" /&gt;</code>. We know from the <a href=\"//assets.trevor-davis.com/play/input-vs-button/\">first example on the previous page</a> that the value is at least passed through to the server properly in all browsers. Now the fun part is just trying to style the buttons in all browsers.</p>\n\n<h2>Stying the Input Element</h2>\n<p>Let’s assign a class to all of the buttons we are going to style, and just add some basic styles to the button:</p>\n\n<div class=\"codeblock\"><code>\ninput.buttons {<br /> background: none no-repeat top left;<br /> border: none;<br /> cursor: pointer;<br /> display: block;<br /> height: 25px;<br /> overflow: hidden;<br /> padding: 0;<br /> margin: 0 2px 0 0;<br /> text-indent: -9999px;<br /> width: auto;<br />} \n\n</code></div>\n\n<p>Most of that should be pretty self explanatory. So let’s now add in the background images for the 3 buttons and give them a width:</p>\n\n<div class=\"codeblock\"><code>\ninput.updateCart { background-image: url(update-cart.gif); width: 107px; }<br />input.continueShopping { background-image: url(continue-shopping.gif); width: 146px; }<br />input.proceedCheckout { background-image: url(proceed-to-checkout.gif); width: 156px; } \n\n</code></div>\n\n<p>The images are actually CSS Sprites so let’s add in the hover effect:</p>\n<div class=\"codeblock\"><code>\ninput.buttons:hover { background-position: 0 -25px; } \n\n</code></div>\n\n<p>Sweet, let’s <a href=\"//assets.trevor-davis.com/play/input-vs-button/regular-input/\">take a look at the page</a>. Nice, it even looks good in Safari. Of course, it’s broken in IE 6 and IE 7.</p>\n\n<p><img src=\"//assets.trevor-davis.com/uploads/images/content/2008/05/buttons-ie.gif\" alt=\"Strange effect on buttons in IE\" class=\"imageCenter\" height=\"25\" width=\"413\" /><em>Check out the weird buttons in IE</em></p>\n\n<h3>Fixing for IE</h3>\n<p>Ok, so I had no idea how to even attack this problem. So I started playing around, and finally came up with this:</p>\n\n<div class=\"codeblock\"><code>\ninput.buttons { font-size: 0; line-height: 25px; } \n\n</code></div>\n\n<p>So I just serve those properties to IE6 and IE7. Beautiful, <a href=\"//assets.trevor-davis.com/play/input-vs-button/regular-input-fixed-for-ie/\">it works now</a>.</p>\n\n<h3>One Last Thing</h3>\n<p>So we were able to get these buttons working in all browsers. There is just one minor thing that bugged me. The hover effect does not work on the buttons in IE6. How about we use <a href=\"http://jquery.com/\">jQuery</a> to fix that for IE6:</p>\n\n<div class=\"codeblock\"><code>\n$(document).ready(function(){<br /> $(\'input.buttons\').hover(function() {<br />   $(this).css(\'background-position\',\'0 -25px\');<br />  },<br />  function() {<br />   $(this).css(\'background-position\',\'0 0\');<br />  }<br /> );<br />}); \n\n</code></div>\n\n<p>Nice, <a href=\"//assets.trevor-davis.com/play/input-vs-button/regular-input-hover-fixed-for-ie/\">it works in IE6</a>. So yeah, that was really long. But form buttons are always a fun topic to get working across all browsers. Anyone have anything they would do differently?</p>',NULL,1797,NULL,'2022-07-23 03:12:27','2022-07-23 03:12:27','70aca3e4-bb94-4053-8f2a-d5a32a3f0609',1,NULL,NULL,1,NULL),(1807,2439,'Why I’m So Excited About Remix',NULL,'<p>I’ve been building on the web for a long time. There’s a part of me that still has a bit of an old school mentality, and it’s hard for me to get really excited about new technology. Sure React was cool when it came out for its state management, but I didn’t see it as the be-all and end-all for building on the web. I really like <a href=\"https://craftcms.com/\">Craft CMS</a> for building CMS powered sites since I have full control over the HTML being sent to the browser, and it’s really easy to extend for complex functionality, but it can be a bit complex to setup for beginners and with additional complexity can come performance issues.&nbsp;So it’s been a while since I’ve seen something to be <em>really</em> excited about.</p>\r\n<p>Enter <a href=\"https://remix.run/\">Remix</a>.</p>\r\n<p>Remix is an interesting mix of old school techniques along with modern technology. Ryan Florence actually sums it up perfectly as to where Remix fits in.</p>\r\n<blockquote class=\"twitter-tweet\" data-dnt=\"true\" data-theme=\"dark\"><p lang=\"en\" dir=\"ltr\">Forget “full stack”. Remix is center stack.<br><br>It’s an HTTP tunnel between interactivity and business logic. It’s the part of the stack that no tool before it has solved, not completely.<br><br>I think I finally know how to talk about Remix.</p>— Ryan Florence (@ryanflorence) <a href=\"https://twitter.com/ryanflorence/status/1482233495195770883?ref_src=twsrc%5Etfw\">January 15, 2022</a></blockquote>\r\n<script async=\"\" src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\r\n<p>I was able to easily switch my site that was fully powered by Craft to utilize Craft only as a GraphQL endpoint and Remix powering the rest. The two big things I needed to figure out were: how to get data from the GraphQL endpoint, and then how to process input from a user (for my contact form). I would definitely recommend reading through the Remix docs and the two tutorials, but if you can understand these two big pictures things, you’ll have enough to get started with Remix and go deeper once you get into it.</p>\r\n<h2>Getting GraphQL Data (aka Loaders)</h2>\r\n<p>Remix provides a <a href=\"https://remix.run/docs/en/v1/api/conventions#loader\">loader hook</a> that runs on the server to hydrate your component. In this simple example, I run a GraphQL request and return JSON with the results of that request in the loader function.</p>\r\n<script src=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550.js?file=loader.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550#file-loader-jsx\">View Gist</a></noscript>\r\n<p>Then in my <code>Index</code> component, I access the data from the loader hook with <a href=\"https://remix.run/docs/en/v1/api/remix#useloaderdata\">useLoaderData()</a>, and my component is now hydrated with that data. In MVC style of thinking, think of the loader hook as the controller processing the page load request, populating with data from the model, and then passing to the view.</p>\r\n<p>The great part about Remix is that this doesn’t have to be specifically a GraphQL request. This data can come from anywhere. The <a href=\"https://remix.run/docs/en/v1/tutorials/jokes\">Jokes tutorial</a> in the Remix docs even thoroughly walks through how you can manage all this data in <a href=\"https://www.prisma.io/\">Prisma</a>.</p>\r\n<h2>Processing User Input (aka Actions)</h2>\r\n<p>Ok great, so we’ve got data onto the page, but now we need to be able to handle user input. Remix also provides an <a href=\"https://remix.run/docs/en/v1/api/conventions#action\">action hook</a> to process an action request (ex: a form submission). In my case, I have a contact form that sends me an email via SendGrid after submission.</p>\r\n<script src=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550.js?file=action.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550#file-action-jsx\">View Gist</a></noscript>\r\n<p>If you don’t include an <code>action</code> attribute on your form, it will automatically submit to the same page. Again, in MVC thinking, the action would again be equivalent to the controller taking the request input and processing it. But honestly, this takes me back 15 years when form actions would point directly to PHP files.</p>\r\n<h2>Bonus: Progressive Enhancement</h2>\r\n<p>Since Remix is running on the server, you can turn off JavaScript and everything still works great. Data still loads, form actions still process; it’s beautiful. This is not running loaders and actions on the client side; it’s running them on the server. This isn’t an accident, the Remix team is carefully considering the UX.</p>\r\n<blockquote class=\"twitter-tweet\" data-dnt=\"true\" data-theme=\"dark\"><p lang=\"en\" dir=\"ltr\">User experience (UX) and developer experience (DX) are both important. UX is more important than DX.<a href=\"https://twitter.com/remix_run?ref_src=twsrc%5Etfw\">@remix_run</a> has taught me: It\'s easier to start with a great UX and work toward a good DX than it is to start with a great DX and work toward a good UX.</p>— Kent C. Dodds &#x1f4bf; (@kentcdodds) <a href=\"https://twitter.com/kentcdodds/status/1482390454389805058?ref_src=twsrc%5Etfw\">January 15, 2022</a></blockquote>\r\n<script async=\"\" src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\r\n<h2>Bonus: So Speedy</h2>\r\n<p>Did I mention how fast everything renders? I don’t tend to put too much stock into Lighthouse scores since they seem so random, but Remix crushes Lighthouse without me having to invest any additional effort into improving performance.</p>\r\n<h2>Give it a Shot</h2>\r\n<p>Remix is built on the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\">native Fetch API</a>, so it can run anywhere. While Remix is relatively new, it’s certainly production ready, and there is a dedicated team behind it. If you aren’t ready to dive into Remix yet, at least keep your eye on it because this is a game changer.</p>',NULL,0,NULL,'2022-08-11 13:25:56','2022-08-11 13:25:56','9b026df0-82d1-4f58-b41a-c27df25d2682',1,NULL,NULL,21,NULL),(1808,2440,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-10-04 09:24:37','2022-10-04 09:24:37','bb029079-8e54-4b7a-94fc-0c43bbe202a2',1,NULL,NULL,27,NULL),(1809,2441,'Why I’m So Excited About Remix',NULL,'<p>I’ve been building on the web for a long time. There’s a part of me that still has a bit of an old school mentality, and it’s hard for me to get really excited about new technology. Sure React was cool when it came out for its state management, but I didn’t see it as the be-all and end-all for building on the web. I really like <a href=\"https://craftcms.com/\">Craft CMS</a> for building CMS powered sites since I have full control over the HTML being sent to the browser, and it’s really easy to extend for complex functionality, but it can be a bit complex to setup for beginners and with additional complexity can come performance issues.&nbsp;So it’s been a while since I’ve seen something to be <em>really</em> excited about.</p>\r\n<p>Enter <a href=\"https://remix.run/\">Remix</a>.</p>\r\n<p>Remix is an interesting mix of old school techniques along with modern technology. Ryan Florence actually sums it up perfectly as to where Remix fits in.</p>\r\n<blockquote class=\"twitter-tweet\" data-dnt=\"true\" data-theme=\"dark\"><p lang=\"en\" dir=\"ltr\">Forget “full stack”. Remix is center stack.<br><br>It’s an HTTP tunnel between interactivity and business logic. It’s the part of the stack that no tool before it has solved, not completely.<br><br>I think I finally know how to talk about Remix.</p>— Ryan Florence (@ryanflorence) <a href=\"https://twitter.com/ryanflorence/status/1482233495195770883?ref_src=twsrc%5Etfw\">January 15, 2022</a></blockquote>\r\n<script async=\"\" src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\r\n<p>I was able to easily switch my site that was fully powered by Craft to utilize Craft only as a GraphQL endpoint and Remix powering the rest. The two big things I needed to figure out were: how to get data from the GraphQL endpoint, and then how to process input from a user (for my contact form). I would definitely recommend reading through the Remix docs and the two tutorials, but if you can understand these two big pictures things, you’ll have enough to get started with Remix and go deeper once you get into it.</p>\r\n<h2>Getting GraphQL Data (aka Loaders)</h2>\r\n<p>Remix provides a <a href=\"https://remix.run/docs/en/v1/api/conventions#loader\">loader hook</a> that runs on the server to hydrate your component. In this simple example, I run a GraphQL request and return JSON with the results of that request in the loader function.</p>\r\n<script src=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550.js?file=loader.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550#file-loader-jsx\">View Gist</a></noscript>\r\n<p>Then in my <code>Index</code> component, I access the data from the loader hook with <a href=\"https://remix.run/docs/en/v1/api/remix#useloaderdata\">useLoaderData()</a>, and my component is now hydrated with that data. In MVC style of thinking, think of the loader hook as the controller processing the page load request, populating with data from the model, and then passing to the view.</p>\r\n<p>The great part about Remix is that this doesn’t have to be specifically a GraphQL request. This data can come from anywhere. The <a href=\"https://remix.run/docs/en/v1/tutorials/jokes\">Jokes tutorial</a> in the Remix docs even thoroughly walks through how you can manage all this data in <a href=\"https://www.prisma.io/\">Prisma</a>.</p>\r\n<h2>Processing User Input (aka Actions)</h2>\r\n<p>Ok great, so we’ve got data onto the page, but now we need to be able to handle user input. Remix also provides an <a href=\"https://remix.run/docs/en/v1/api/conventions#action\">action hook</a> to process an action request (ex: a form submission). In my case, I have a contact form that sends me an email via SendGrid after submission.</p>\r\n<script src=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550.js?file=action.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550#file-action-jsx\">View Gist</a></noscript>\r\n<p>If you don’t include an <code>action</code> attribute on your form, it will automatically submit to the same page. Again, in MVC thinking, the action would again be equivalent to the controller taking the request input and processing it. But honestly, this takes me back 15 years when form actions would point directly to PHP files.</p>\r\n<h2>Bonus: Progressive Enhancement</h2>\r\n<p>Since Remix is running on the server, you can turn off JavaScript and everything still works great. Data still loads, form actions still process; it’s beautiful. This is not running loaders and actions on the client side; it’s running them on the server. This isn’t an accident, the Remix team is carefully considering the UX.</p>\r\n<blockquote class=\"twitter-tweet\" data-dnt=\"true\" data-theme=\"dark\"><p lang=\"en\" dir=\"ltr\">User experience (UX) and developer experience (DX) are both important. UX is more important than DX.<a href=\"https://twitter.com/remix_run?ref_src=twsrc%5Etfw\">@remix_run</a> has taught me: It\'s easier to start with a great UX and work toward a good DX than it is to start with a great DX and work toward a good UX.</p>— Kent C. Dodds &#x1f4bf; (@kentcdodds) <a href=\"https://twitter.com/kentcdodds/status/1482390454389805058?ref_src=twsrc%5Etfw\">January 15, 2022</a></blockquote>\r\n<script async=\"\" src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\r\n<h2>Bonus: So Speedy</h2>\r\n<p>Did I mention how fast everything renders? I don’t tend to put too much stock into Lighthouse scores since they seem so random, but Remix crushes Lighthouse without me having to invest any additional effort into improving performance.</p>\r\n<h2>Give it a Shot</h2>\r\n<p>Remix is built on the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\">native Fetch API</a>, so it can run anywhere. While Remix is relatively new, it’s certainly production ready, and there is a dedicated team behind it. If you aren’t ready to dive into Remix yet, at least keep your eye on it because this is a game changer.</p>',NULL,0,NULL,'2022-10-21 12:13:28','2022-10-21 12:13:28','93aa99c2-6dc6-44a5-a262-10d4c0cd8086',1,NULL,NULL,22,NULL),(1810,2442,'IMG 1415',NULL,NULL,NULL,0,NULL,'2022-11-16 21:25:47','2022-11-16 21:26:36','8d6fb6c7-5dd2-446a-8726-46ba4eeb3af1',1,NULL,NULL,NULL,NULL),(1812,2449,'IMG 6036',NULL,NULL,NULL,0,NULL,'2022-11-16 21:25:50','2022-11-16 21:26:23','12f9e805-c223-49e9-a585-81fa57ed2a43',1,NULL,NULL,NULL,NULL),(1813,2450,'About',NULL,NULL,NULL,0,NULL,'2022-11-16 21:26:00','2022-11-16 21:26:00','9a44c135-fec1-4fca-9de2-ac0bfd81975a',1,NULL,NULL,NULL,NULL),(1814,2456,'IMG 0932',NULL,NULL,NULL,0,NULL,'2022-11-16 21:27:22','2024-02-09 20:55:53','9510c017-d4e4-4262-a6c3-a02850757fc4',1,NULL,NULL,NULL,NULL),(1816,2463,'About',NULL,NULL,NULL,0,NULL,'2022-11-16 21:27:31','2022-11-16 21:27:31','d849bf5d-f340-44d0-ad2a-e4226d609772',1,NULL,NULL,NULL,NULL),(1817,2469,'jQuery One Page Navigation Plugin',NULL,'<p>When appropriate, I am a fan of the one-page sites. I really like the ones that add smooth scrolling and highlight the navigation depending upon which part of the page you have scrolled to. Here are a few examples: <a href=\"http://www.brizk.com/\">Brizk Design</a> and <a href=\"http://crushlovely.com/\">Crush + Lovely</a>. I finally have a freelance project where a one-page site makes sense, so I needed to write the JavaScript to make the navigation work how I wanted.</p>\n\n<p>I wanted the page to scroll smoothly when the navigation was clicked, so I used the <a href=\"http://flesler.blogspot.com/2007/10/jqueryscrollto.html\">jQuery ScrollTo plugin</a>. I also wanted the page to automatically highlight the correct navigation section depending upon which section was scrolled to, and that was where I added my custom code.</p>\n\n<p>If you want to skip ahead, you can <a href=\"//assets.trevor-davis.com/play/jquery-one-page-nav/\">check out the demo</a> and <a href=\"http://github.com/davist11/jQuery-One-Page-Nav\">download the plugin from GitHub</a>.</p>\n\n<h2>The Markup</h2>\n<p>I started with an unordered list for the navigation and a bunch of sections:</p>\n<pre><code>&lt;ul id=\"nav\"&gt;\n  &lt;li class=\"current\"&gt;&lt;a href=\"#section-1\"&gt;Section 1&lt;/a&gt;&lt;/li&gt;\n  &lt;li&gt;&lt;a href=\"#section-2\"&gt;Section 2&lt;/a&gt;&lt;/li&gt;\n  &lt;li\"&gt;&lt;a href=\"#section-3\"&gt;Section 3&lt;/a&gt;&lt;/li&gt;\n&lt;/ul&gt;\n\n&lt;div id=\"section-1\"&gt;\n  &lt;strong&gt;Section 1&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;\n&lt;div id=\"section-2\"&gt;\n  &lt;strong&gt;Section 2&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor \n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;\n&lt;div id=\"section-3\"&gt;\n  &lt;strong&gt;Section 3&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor \n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;</code></pre>\n\n<h2>The JavaScript</h2>\n<p>Then, I added jQuery, the ScrollTo plugin, and my plugin to the page:</p>\n<pre><code>&lt;script src=\"jquery.js\"&gt;&lt;/script&gt;\n&lt;script src=\"jquery.scrollTo.js\"&gt;&lt;/script&gt;\n&lt;script src=\"jquery.nav.min.js\"&gt;&lt;/script&gt;</code></pre>\n\n<p>Finally, I just need to call my plugin on the navigation:</p>\n<pre><code>$(document).ready(function() {\n  $(\'#nav\').onePageNav();\n});</code></pre>\n\n<h3>Options</h3>\n<p>There are a few options for this plugin:</p>\n<ul><li>\n<h4>currentClass: \'current\'</h4>\nClass to add to the list item when the navigation item is selected\n</li>\n<li>\n<h4>changeHash: false</h4>\nIf you want the hash to change when the user clicks on the navigation, change this to <strong>true</strong>\n</li>\n<li>\n<h4>scrollSpeed: 750</h4>\nSpeed to scroll the page when the navigation is clicked\n</li>\n</ul><p>And that’s it. <a href=\"//assets.trevor-davis.com/play/jquery-one-page-nav/\">Check out the demo</a> and <a href=\"http://github.com/davist11/jQuery-One-Page-Nav\">download the plugin from GitHub</a>.</p>',NULL,1962,NULL,'2022-11-21 06:18:02','2022-11-21 06:18:02','1f70a871-ebc1-4863-aff7-30f6d47a16aa',1,NULL,NULL,1,NULL),(1818,2470,'jQuery One Page Navigation Plugin',NULL,'<p>When appropriate, I am a fan of the one-page sites. I really like the ones that add smooth scrolling and highlight the navigation depending upon which part of the page you have scrolled to. Here are a few examples: <a href=\"http://www.brizk.com/\">Brizk Design</a> and <a href=\"http://crushlovely.com/\">Crush + Lovely</a>. I finally have a freelance project where a one-page site makes sense, so I needed to write the JavaScript to make the navigation work how I wanted.</p>\n\n<p>I wanted the page to scroll smoothly when the navigation was clicked, so I used the <a href=\"http://flesler.blogspot.com/2007/10/jqueryscrollto.html\">jQuery ScrollTo plugin</a>. I also wanted the page to automatically highlight the correct navigation section depending upon which section was scrolled to, and that was where I added my custom code.</p>\n\n<p>If you want to skip ahead, you can <a href=\"//assets.trevor-davis.com/play/jquery-one-page-nav/\">check out the demo</a> and <a href=\"http://github.com/davist11/jQuery-One-Page-Nav\">download the plugin from GitHub</a>.</p>\n\n<h2>The Markup</h2>\n<p>I started with an unordered list for the navigation and a bunch of sections:</p>\n<pre><code>&lt;ul id=\"nav\"&gt;\n  &lt;li class=\"current\"&gt;&lt;a href=\"#section-1\"&gt;Section 1&lt;/a&gt;&lt;/li&gt;\n  &lt;li&gt;&lt;a href=\"#section-2\"&gt;Section 2&lt;/a&gt;&lt;/li&gt;\n  &lt;li\"&gt;&lt;a href=\"#section-3\"&gt;Section 3&lt;/a&gt;&lt;/li&gt;\n&lt;/ul&gt;\n\n&lt;div id=\"section-1\"&gt;\n  &lt;strong&gt;Section 1&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;\n&lt;div id=\"section-2\"&gt;\n  &lt;strong&gt;Section 2&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor \n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;\n&lt;div id=\"section-3\"&gt;\n  &lt;strong&gt;Section 3&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor \n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;</code></pre>\n\n<h2>The JavaScript</h2>\n<p>Then, I added jQuery, the ScrollTo plugin, and my plugin to the page:</p>\n<pre><code>&lt;script src=\"jquery.js\"&gt;&lt;/script&gt;\n&lt;script src=\"jquery.scrollTo.js\"&gt;&lt;/script&gt;\n&lt;script src=\"jquery.nav.min.js\"&gt;&lt;/script&gt;</code></pre>\n\n<p>Finally, I just need to call my plugin on the navigation:</p>\n<pre><code>$(document).ready(function() {\n  $(\'#nav\').onePageNav();\n});</code></pre>\n\n<h3>Options</h3>\n<p>There are a few options for this plugin:</p>\n<ul><li>\n<h4>currentClass: \'current\'</h4>\nClass to add to the list item when the navigation item is selected\n</li>\n<li>\n<h4>changeHash: false</h4>\nIf you want the hash to change when the user clicks on the navigation, change this to <strong>true</strong>\n</li>\n<li>\n<h4>scrollSpeed: 750</h4>\nSpeed to scroll the page when the navigation is clicked\n</li>\n</ul><p>And that’s it. <a href=\"//assets.trevor-davis.com/play/jquery-one-page-nav/\">Check out the demo</a> and <a href=\"http://github.com/davist11/jQuery-One-Page-Nav\">download the plugin from GitHub</a>.</p>',NULL,1962,NULL,'2022-11-21 06:29:51','2022-11-21 06:29:51','4afec486-682d-4fd6-ae20-8a444e1e815e',1,NULL,NULL,2,NULL),(1819,2471,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-12-09 20:12:42','2022-12-09 20:12:42','1f0dfd53-aeb7-4312-af3b-9eb93880a6d3',1,NULL,NULL,28,NULL),(1820,2472,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-12-09 20:12:44','2022-12-09 20:12:44','6ffa9779-8125-4718-9b02-f1f74ae3b9dd',1,NULL,NULL,27,NULL),(1821,2473,'jQuery One Page Navigation Plugin',NULL,'<p>When appropriate, I am a fan of the one-page sites. I really like the ones that add smooth scrolling and highlight the navigation depending upon which part of the page you have scrolled to. Here are a few examples: <a href=\"http://www.brizk.com/\">Brizk Design</a> and <a href=\"http://crushlovely.com/\">Crush + Lovely</a>. I finally have a freelance project where a one-page site makes sense, so I needed to write the JavaScript to make the navigation work how I wanted.</p>\n\n<p>I wanted the page to scroll smoothly when the navigation was clicked, so I used the <a href=\"http://flesler.blogspot.com/2007/10/jqueryscrollto.html\">jQuery ScrollTo plugin</a>. I also wanted the page to automatically highlight the correct navigation section depending upon which section was scrolled to, and that was where I added my custom code.</p>\n\n<p>If you want to skip ahead, you can <a href=\"//assets.trevor-davis.com/play/jquery-one-page-nav/\">check out the demo</a> and <a href=\"http://github.com/davist11/jQuery-One-Page-Nav\">download the plugin from GitHub</a>.</p>\n\n<h2>The Markup</h2>\n<p>I started with an unordered list for the navigation and a bunch of sections:</p>\n<pre><code>&lt;ul id=\"nav\"&gt;\n  &lt;li class=\"current\"&gt;&lt;a href=\"#section-1\"&gt;Section 1&lt;/a&gt;&lt;/li&gt;\n  &lt;li&gt;&lt;a href=\"#section-2\"&gt;Section 2&lt;/a&gt;&lt;/li&gt;\n  &lt;li\"&gt;&lt;a href=\"#section-3\"&gt;Section 3&lt;/a&gt;&lt;/li&gt;\n&lt;/ul&gt;\n\n&lt;div id=\"section-1\"&gt;\n  &lt;strong&gt;Section 1&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;\n&lt;div id=\"section-2\"&gt;\n  &lt;strong&gt;Section 2&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor \n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;\n&lt;div id=\"section-3\"&gt;\n  &lt;strong&gt;Section 3&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor \n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;</code></pre>\n\n<h2>The JavaScript</h2>\n<p>Then, I added jQuery, the ScrollTo plugin, and my plugin to the page:</p>\n<pre><code>&lt;script src=\"jquery.js\"&gt;&lt;/script&gt;\n&lt;script src=\"jquery.scrollTo.js\"&gt;&lt;/script&gt;\n&lt;script src=\"jquery.nav.min.js\"&gt;&lt;/script&gt;</code></pre>\n\n<p>Finally, I just need to call my plugin on the navigation:</p>\n<pre><code>$(document).ready(function() {\n  $(\'#nav\').onePageNav();\n});</code></pre>\n\n<h3>Options</h3>\n<p>There are a few options for this plugin:</p>\n<ul><li>\n<h4>currentClass: \'current\'</h4>\nClass to add to the list item when the navigation item is selected\n</li>\n<li>\n<h4>changeHash: false</h4>\nIf you want the hash to change when the user clicks on the navigation, change this to <strong>true</strong>\n</li>\n<li>\n<h4>scrollSpeed: 750</h4>\nSpeed to scroll the page when the navigation is clicked\n</li>\n</ul><p>And that’s it. <a href=\"//assets.trevor-davis.com/play/jquery-one-page-nav/\">Check out the demo</a> and <a href=\"http://github.com/davist11/jQuery-One-Page-Nav\">download the plugin from GitHub</a>.</p>',NULL,1962,NULL,'2022-12-11 11:01:35','2022-12-11 11:01:35','caa51fbb-9210-4d25-9cad-a1edfba59dcf',1,NULL,NULL,1,NULL),(1822,2474,'jQuery One Page Navigation Plugin',NULL,'<p>When appropriate, I am a fan of the one-page sites. I really like the ones that add smooth scrolling and highlight the navigation depending upon which part of the page you have scrolled to. Here are a few examples: <a href=\"http://www.brizk.com/\">Brizk Design</a> and <a href=\"http://crushlovely.com/\">Crush + Lovely</a>. I finally have a freelance project where a one-page site makes sense, so I needed to write the JavaScript to make the navigation work how I wanted.</p>\n\n<p>I wanted the page to scroll smoothly when the navigation was clicked, so I used the <a href=\"http://flesler.blogspot.com/2007/10/jqueryscrollto.html\">jQuery ScrollTo plugin</a>. I also wanted the page to automatically highlight the correct navigation section depending upon which section was scrolled to, and that was where I added my custom code.</p>\n\n<p>If you want to skip ahead, you can <a href=\"//assets.trevor-davis.com/play/jquery-one-page-nav/\">check out the demo</a> and <a href=\"http://github.com/davist11/jQuery-One-Page-Nav\">download the plugin from GitHub</a>.</p>\n\n<h2>The Markup</h2>\n<p>I started with an unordered list for the navigation and a bunch of sections:</p>\n<pre><code>&lt;ul id=\"nav\"&gt;\n  &lt;li class=\"current\"&gt;&lt;a href=\"#section-1\"&gt;Section 1&lt;/a&gt;&lt;/li&gt;\n  &lt;li&gt;&lt;a href=\"#section-2\"&gt;Section 2&lt;/a&gt;&lt;/li&gt;\n  &lt;li\"&gt;&lt;a href=\"#section-3\"&gt;Section 3&lt;/a&gt;&lt;/li&gt;\n&lt;/ul&gt;\n\n&lt;div id=\"section-1\"&gt;\n  &lt;strong&gt;Section 1&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;\n&lt;div id=\"section-2\"&gt;\n  &lt;strong&gt;Section 2&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor \n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;\n&lt;div id=\"section-3\"&gt;\n  &lt;strong&gt;Section 3&lt;/strong&gt;\n  &lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor \n  incididunt ut labore et dolore magna aliqua.&lt;/p&gt;\n&lt;/div&gt;</code></pre>\n\n<h2>The JavaScript</h2>\n<p>Then, I added jQuery, the ScrollTo plugin, and my plugin to the page:</p>\n<pre><code>&lt;script src=\"jquery.js\"&gt;&lt;/script&gt;\n&lt;script src=\"jquery.scrollTo.js\"&gt;&lt;/script&gt;\n&lt;script src=\"jquery.nav.min.js\"&gt;&lt;/script&gt;</code></pre>\n\n<p>Finally, I just need to call my plugin on the navigation:</p>\n<pre><code>$(document).ready(function() {\n  $(\'#nav\').onePageNav();\n});</code></pre>\n\n<h3>Options</h3>\n<p>There are a few options for this plugin:</p>\n<ul><li>\n<h4>currentClass: \'current\'</h4>\nClass to add to the list item when the navigation item is selected\n</li>\n<li>\n<h4>changeHash: false</h4>\nIf you want the hash to change when the user clicks on the navigation, change this to <strong>true</strong>\n</li>\n<li>\n<h4>scrollSpeed: 750</h4>\nSpeed to scroll the page when the navigation is clicked\n</li>\n</ul><p>And that’s it. <a href=\"//assets.trevor-davis.com/play/jquery-one-page-nav/\">Check out the demo</a> and <a href=\"http://github.com/davist11/jQuery-One-Page-Nav\">download the plugin from GitHub</a>.</p>',NULL,1962,NULL,'2022-12-11 11:01:37','2022-12-11 11:01:37','8b3a4a2c-5967-4aa5-af3b-e806a1c74ceb',1,NULL,NULL,2,NULL),(1823,2475,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-12-11 13:24:10','2022-12-11 13:24:10','78822cbd-a0f6-4ece-9bf3-746b808c9ae0',1,NULL,NULL,28,NULL),(1824,2476,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2022-12-11 13:24:13','2022-12-11 13:24:13','edb75dd1-4ef2-4d2f-9137-8ba393d01ebe',1,NULL,NULL,27,NULL),(1825,2477,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2023-01-17 21:18:08','2025-03-27 13:12:16','8c47cd79-8c0f-43af-b8f5-7f4b4457c577',1,NULL,NULL,30,NULL),(1826,2478,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-17 21:19:45','2023-01-17 21:19:45','cbe4b502-5a31-4244-aea9-d6679a8942a8',1,NULL,NULL,28,NULL),(1827,2479,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-17 21:19:47','2023-01-17 21:19:47','e1e2ba31-018b-4a87-a8c2-eed2eee73cc5',1,NULL,NULL,27,NULL),(1836,2488,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-19 20:59:47','2023-01-19 20:59:47','b8491275-aa4c-4213-af3c-598b21b54963',1,NULL,NULL,28,NULL),(1837,2489,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-19 20:59:50','2023-01-19 20:59:50','ef382e6a-236d-4aab-9c22-75fc8bb405d5',1,NULL,NULL,27,NULL),(1838,2490,'Like component',NULL,NULL,NULL,0,NULL,'2023-01-19 21:01:08','2023-01-19 21:01:08','daf06208-f770-4def-885b-d5ce78416306',1,NULL,NULL,NULL,NULL),(1871,2523,'My Long Journey from WordPress to ExpressionEngine',NULL,'<p>About the time I started my new job at <a href=\"http://www.viget.com/\">Viget Labs</a> in December 2009, I decided that it was time to start working on a redesigned website. This redesign was a chance for me to play around with some of the new CSS3 properties, @font-face, & jQuery.</p>\r\n<p>My first three designs have also been very white, so I wanted to try something a little different.</p>\r\n<figure><img src=\"//assets.trevor-davis.com/uploads/images/content/v1.jpg\" alt=\"Version 1\" class=\"centered\" width=\"630\" height=\"320\" data-image=\"3d9gh2sv5xv1\"></figure>\r\n<figure><img src=\"//assets.trevor-davis.com/uploads/images/content/v2.jpg\" alt=\"Version 2\" class=\"centered\" width=\"630\" height=\"320\" data-image=\"k0n0xie3mnnj\"></figure>\r\n<figure><img src=\"//assets.trevor-davis.com/uploads/images/content/v3.jpg\" alt=\"Version 3\" class=\"centered\" width=\"630\" height=\"320\" data-image=\"kb36l6b7ljo9\"></figure>\r\n<p>I wanted to go with a dark background, and I wanted to be able to change the feel of the site very easily. So by changing the background color in the stylesheet, I can completely change how the site feels. Check it out with <a href=\"#ad1010\" class=\"color-change\">red</a>, <a href=\"#10ad53\" class=\"color-change\">green</a>, or <a href=\"#7d1baa\" class=\"color-change\">purple</a>. All that is done by just simply changing the background color on the body.</p>\r\n<p>Another big step in this redesign is finally transitioning to ExpressionEngine. My first experience with content management systems began with WordPress, and I had a good run with it, but I felt like it was time to transition to a “real” CMS. Let me just say that going transferring posts form WordPress to ExpressionEngine was a huge pain in the butt.</p>\r\n<p>ExpressionEngine can import a Movable Type format, but WordPress cannot export into that format. There are various scripts on the web that can convert WordPress to Movable Type, but all of them are outdated. So I decided to approach it by installing Movable Type on my server, importing the WordPress file, and then exporting the Movable Type file. Unfortunately, that process doesn’t work that smoothly, and Movable Type kept failing to import the WordPress file. After much searching, I finally found an <a href=\"http://www.flashgamer.com/2010/04/converting_from_wordpress_to_e.html\">AIR application</a> that can convert from WordPress to Movable Type format.</p>\r\n<p>So after some cleanup of the posts, I was finally ready to launch. I haven’t looked at the site in IE yet, and I’m sure there will be some broken URLs, but I will get to that eventually.</p>\r\n<p>I’m also hoping to get back to posting articles more regularly here, so stay tuned.</p>\r\n<h2>Some Other Notes</h2>\r\n<ul><li>Headings are using Aller & being displayed with @font-face.</li>\r\n<li>Thanks go out to my friend <a href=\"http://twitter.com/neagle\">Nate Eagle</a> for helping to write my about page.</li>\r\n<li>Thanks also go out to <a href=\"http://twitter.com/erikreagan\">Erik Reagan</a> for helping me with some last minute htaccess rules</li>\r\n</ul>',NULL,1952,NULL,'2023-01-22 14:56:06','2023-01-22 14:56:06','b419c7d1-9cbe-43c0-9ac5-f04fe5ce7bda',1,NULL,NULL,NULL,NULL),(1872,2524,'My Long Journey from WordPress to ExpressionEngine',NULL,'<p>About the time I started my new job at <a href=\"http://www.viget.com/\">Viget Labs</a> in December 2009, I decided that it was time to start working on a redesigned website. This redesign was a chance for me to play around with some of the new CSS3 properties, @font-face, & jQuery.</p>\r\n<p>My first three designs have also been very white, so I wanted to try something a little different.</p>\r\n<figure><img src=\"//assets.trevor-davis.com/uploads/images/content/v1.jpg\" alt=\"Version 1\" class=\"centered\" width=\"630\" height=\"320\" data-image=\"3d9gh2sv5xv1\"></figure>\r\n<figure><img src=\"//assets.trevor-davis.com/uploads/images/content/v2.jpg\" alt=\"Version 2\" class=\"centered\" width=\"630\" height=\"320\" data-image=\"k0n0xie3mnnj\"></figure>\r\n<figure><img src=\"//assets.trevor-davis.com/uploads/images/content/v3.jpg\" alt=\"Version 3\" class=\"centered\" width=\"630\" height=\"320\" data-image=\"kb36l6b7ljo9\"></figure>\r\n<p>I wanted to go with a dark background, and I wanted to be able to change the feel of the site very easily. So by changing the background color in the stylesheet, I can completely change how the site feels. Check it out with <a href=\"#ad1010\" class=\"color-change\">red</a>, <a href=\"#10ad53\" class=\"color-change\">green</a>, or <a href=\"#7d1baa\" class=\"color-change\">purple</a>. All that is done by just simply changing the background color on the body.</p>\r\n<p>Another big step in this redesign is finally transitioning to ExpressionEngine. My first experience with content management systems began with WordPress, and I had a good run with it, but I felt like it was time to transition to a “real” CMS. Let me just say that going transferring posts form WordPress to ExpressionEngine was a huge pain in the butt.</p>\r\n<p>ExpressionEngine can import a Movable Type format, but WordPress cannot export into that format. There are various scripts on the web that can convert WordPress to Movable Type, but all of them are outdated. So I decided to approach it by installing Movable Type on my server, importing the WordPress file, and then exporting the Movable Type file. Unfortunately, that process doesn’t work that smoothly, and Movable Type kept failing to import the WordPress file. After much searching, I finally found an <a href=\"http://www.flashgamer.com/2010/04/converting_from_wordpress_to_e.html\">AIR application</a> that can convert from WordPress to Movable Type format.</p>\r\n<p>So after some cleanup of the posts, I was finally ready to launch. I haven’t looked at the site in IE yet, and I’m sure there will be some broken URLs, but I will get to that eventually.</p>\r\n<p>I’m also hoping to get back to posting articles more regularly here, so stay tuned.</p>\r\n<h2>Some Other Notes</h2>\r\n<ul><li>Headings are using Aller & being displayed with @font-face.</li>\r\n<li>Thanks go out to my friend <a href=\"http://twitter.com/neagle\">Nate Eagle</a> for helping to write my about page.</li>\r\n<li>Thanks also go out to <a href=\"http://twitter.com/erikreagan\">Erik Reagan</a> for helping me with some last minute htaccess rules</li>\r\n</ul>',NULL,1952,NULL,'2023-01-22 15:09:07','2023-01-22 15:09:07','e744c6d1-44c9-4ff1-b44a-7783438b756c',1,NULL,NULL,NULL,NULL),(1873,2525,'Dumbwaiter Chrome Extension',NULL,'<p>I recently got around to trying out <a href=\"http://statamic.com/\">Statamic</a> to create a little <a href=\"http://recipes.trevor-davis.com/\">recipe manager</a>. Yes, I know there are tons of apps out there that do this type of stuff, but I wanted to really be able to control everything.</p>\n\n<p>I also wanted to figure out some way that my wife could bookmark recipes for us, and I didn’t expect her to login to my Statamic install and go through that whole process of adding a new entry. I wanted her to be able to click a button when she was on a site displaying a recipe that she wanted to bookmark, and an entry would then be created. That led me to create my first Chrome Extension, <a href=\"https://chrome.google.com/webstore/detail/jeeeliidcblacogbcefhcbljkllhpjdd?hl=en&amp;gl=US\">Dumbwaiter</a>.</p>\n\n<p><img src=\"//assets.trevor-davis.com/uploads/images/content/dumbwaiter-options.png\" alt=\"Dumbwaiter Options\" /></p>\n\n<p>The basic idea is that you provide a URL that will be opened in a popup window when you click the exntension. Then you provide some JavaScript to do something on the current page you are on, and then some more JavaScript to do something with that data. So in my case, I grab the URL of the page where the recipe is coming from, grab the name of the recipe, and then whatever text is highlighted on the page. So, the process to add a recipe is to highlight the actual recipe, then click the Dumbwaiter icon, and all the data will be pre-populated into my Statamic new entry form.</p>\n\n<p>Here is some example code that I am using, but the options are limitless: whatever you can do with jQuery, you can do with Dumbwaiter.</p>\n\n<h3>Retrieve Data Code</h3>\n<pre><code>var title = jQuery(\'meta[property=\"og:title\"]\').attr(\'content\');\nvar source = window.location.href;\nvar img = jQuery(\'meta[property=\"og:image\"]\').attr(\'content\');\nvar content = window.getSelection().toString();\n\nif(!title) {\n	title = jQuery(\'title\').text();\n}\n\nvar data = {\n	title: title,\n	source: source,\n	img: img,\n	content: content\n};</code></pre>\n\n<h3>Insert Data Code</h3>\n<pre><code>function makeSlug(str) { \n	str = str.replace(/^\\s+|\\s+$/g, \'\'); // trim\n	str = str.toLowerCase();\n  \n	// remove accents, swap ñ for n, etc\n	var from = \"àáäâèéëêìíïîòóöôùúüûñç·/_,:;\";\n	var to   = \"aaaaeeeeiiiioooouuuunc------\";\n	for (var i=0, l=from.length ; i &lt; l ; i++) {\n		str = str.replace(new RegExp(from.charAt(i), \'g\'), to.charAt(i));\n	}\n  \n	str = str.replace(/[^a-z0-9 -]/g, \'\') // remove invalid chars\n	.replace(/\\s+/g, \'-\') // collapse whitespace and replace by -\n	.replace(/-+/g, \'-\'); // collapse dashes\n  \n	return str;\n};\n\n$(\'#publish-title\').val(data.title).focus();\nvar slug = makeSlug(data.title);\n$(\'#publish-slug\').val(slug);\n$(\'input[name=\"page[yaml][source]\"]\').val(data.source);\n$(\'textarea.markItUpEditor\').val(data.content);</code></pre>\n\n<p>You can <a href=\"https://chrome.google.com/webstore/detail/jeeeliidcblacogbcefhcbljkllhpjdd?hl=en&amp;gl=US\">download Dumbwaiter here</a>, and let me know if you guys find any creative uses!</p>',NULL,2021,NULL,'2023-01-22 15:09:34','2023-01-22 15:09:34','c18612c3-6707-4162-9791-ba8efee48805',1,NULL,NULL,NULL,NULL),(1875,2527,'Homepage',NULL,NULL,NULL,0,NULL,'2023-01-22 15:21:50','2023-01-22 15:21:50','f8cc90b7-993e-4cf5-8000-de436af404ec',1,'I Build Things on the Internet','<p>I’m a <strong>Senior Software Engineer</strong> at <a href=\"https://dutchie.com/\">Dutchie</a>.<br />If you have a project that you think I can help with, <a href=\"/contact\">get in touch</a>.</p>',NULL,NULL),(1877,2529,'Homepage',NULL,NULL,NULL,0,NULL,'2023-01-22 15:22:35','2023-01-22 15:22:35','f16124ad-48a9-4799-a26f-da025bfc44e7',1,'I Build Things on the Internet','<p>I’m a <strong>Senior Software Engineer</strong> at <a href=\"https://dutchie.com/\">Dutchie</a>.<br />If you have a project that you think I can help with, <a href=\"/contact\">get in touch</a>.</p>',NULL,NULL),(1887,2779,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-26 22:35:53','2023-01-26 22:35:53','d2f718d1-24de-4c81-8195-af6dae110206',1,NULL,NULL,27,NULL),(1889,2795,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-26 22:36:56','2023-01-26 22:36:56','d7efac7d-e708-4ece-9aba-fe45959aa451',1,NULL,NULL,27,NULL),(1891,2811,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-26 22:37:15','2023-01-26 22:37:15','2391e2f3-6bae-45ab-8e06-8debe8230e5d',1,NULL,NULL,27,NULL),(1893,2827,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-26 22:38:04','2023-01-26 22:38:04','384d8257-e4e2-431c-bae4-646339450327',1,NULL,NULL,27,NULL),(1895,2843,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-26 22:40:54','2023-01-26 22:40:54','3640992a-0ee0-42c7-9e4f-87f9c19b5349',1,NULL,NULL,27,NULL),(1897,2859,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-26 22:41:11','2023-01-26 22:41:11','3804399c-fc5e-48c4-a69d-c7b2e1bf0acf',1,NULL,NULL,27,NULL),(1898,2867,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-26 22:41:22','2023-01-26 22:41:22','57daeb58-cb78-40b6-9384-82d090923498',1,NULL,NULL,27,NULL),(1900,2883,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"//assets.trevor-davis.com/uploads/images/content/live-preview.mp4\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2023-01-26 22:41:42','2023-01-26 22:41:42','34222381-560d-4750-a049-27466012db22',1,NULL,NULL,27,NULL),(1902,2947,'Why I’m So Excited About Remix',NULL,'<p>I’ve been building on the web for a long time. There’s a part of me that still has a bit of an old school mentality, and it’s hard for me to get really excited about new technology. Sure React was cool when it came out for its state management, but I didn’t see it as the be-all and end-all for building on the web. I really like <a href=\"https://craftcms.com/\">Craft CMS</a> for building CMS powered sites since I have full control over the HTML being sent to the browser, and it’s really easy to extend for complex functionality, but it can be a bit complex to setup for beginners and with additional complexity can come performance issues.&nbsp;So it’s been a while since I’ve seen something to be <em>really</em> excited about.</p>\r\n<p>Enter <a href=\"https://remix.run/\">Remix</a>.</p>\r\n<p>Remix is an interesting mix of old school techniques along with modern technology. Ryan Florence actually sums it up perfectly as to where Remix fits in.</p>\r\n<blockquote class=\"twitter-tweet\" data-dnt=\"true\" data-theme=\"dark\"><p lang=\"en\" dir=\"ltr\">Forget “full stack”. Remix is center stack.<br><br>It’s an HTTP tunnel between interactivity and business logic. It’s the part of the stack that no tool before it has solved, not completely.<br><br>I think I finally know how to talk about Remix.</p>— Ryan Florence (@ryanflorence) <a href=\"https://twitter.com/ryanflorence/status/1482233495195770883?ref_src=twsrc%5Etfw\">January 15, 2022</a></blockquote>\r\n<script async=\"\" src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\r\n<p>I was able to easily switch my site that was fully powered by Craft to utilize Craft only as a GraphQL endpoint and Remix powering the rest. The two big things I needed to figure out were: how to get data from the GraphQL endpoint, and then how to process input from a user (for my contact form). I would definitely recommend reading through the Remix docs and the two tutorials, but if you can understand these two big pictures things, you’ll have enough to get started with Remix and go deeper once you get into it.</p>\r\n<h2>Getting GraphQL Data (aka Loaders)</h2>\r\n<p>Remix provides a <a href=\"https://remix.run/docs/en/v1/api/conventions#loader\">loader hook</a> that runs on the server to hydrate your component. In this simple example, I run a GraphQL request and return JSON with the results of that request in the loader function.</p>\r\n<script src=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550.js?file=loader.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550#file-loader-jsx\">View Gist</a></noscript>\r\n<p>Then in my <code>Index</code> component, I access the data from the loader hook with <a href=\"https://remix.run/docs/en/v1/api/remix#useloaderdata\">useLoaderData()</a>, and my component is now hydrated with that data. In MVC style of thinking, think of the loader hook as the controller processing the page load request, populating with data from the model, and then passing to the view.</p>\r\n<p>The great part about Remix is that this doesn’t have to be specifically a GraphQL request. This data can come from anywhere. The <a href=\"https://remix.run/docs/en/v1/tutorials/jokes\">Jokes tutorial</a> in the Remix docs even thoroughly walks through how you can manage all this data in <a href=\"https://www.prisma.io/\">Prisma</a>.</p>\r\n<h2>Processing User Input (aka Actions)</h2>\r\n<p>Ok great, so we’ve got data onto the page, but now we need to be able to handle user input. Remix also provides an <a href=\"https://remix.run/docs/en/v1/api/conventions#action\">action hook</a> to process an action request (ex: a form submission). In my case, I have a contact form that sends me an email via SendGrid after submission.</p>\r\n<script src=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550.js?file=action.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550#file-action-jsx\">View Gist</a></noscript>\r\n<p>If you don’t include an <code>action</code> attribute on your form, it will automatically submit to the same page. Again, in MVC thinking, the action would again be equivalent to the controller taking the request input and processing it. But honestly, this takes me back 15 years when form actions would point directly to PHP files.</p>\r\n<h2>Bonus: Progressive Enhancement</h2>\r\n<p>Since Remix is running on the server, you can turn off JavaScript and everything still works great. Data still loads, form actions still process; it’s beautiful. This is not running loaders and actions on the client side; it’s running them on the server. This isn’t an accident, the Remix team is carefully considering the UX.</p>\r\n<blockquote class=\"twitter-tweet\" data-dnt=\"true\" data-theme=\"dark\"><p lang=\"en\" dir=\"ltr\">User experience (UX) and developer experience (DX) are both important. UX is more important than DX.<a href=\"https://twitter.com/remix_run?ref_src=twsrc%5Etfw\">@remix_run</a> has taught me: It\'s easier to start with a great UX and work toward a good DX than it is to start with a great DX and work toward a good UX.</p>— Kent C. Dodds &#x1f4bf; (@kentcdodds) <a href=\"https://twitter.com/kentcdodds/status/1482390454389805058?ref_src=twsrc%5Etfw\">January 15, 2022</a></blockquote>\r\n<script async=\"\" src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\r\n<h2>Bonus: So Speedy</h2>\r\n<p>Did I mention how fast everything renders? I don’t tend to put too much stock into Lighthouse scores since they seem so random, but Remix crushes Lighthouse without me having to invest any additional effort into improving performance.</p>\r\n<h2>Give it a Shot</h2>\r\n<p>Remix is built on the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\">native Fetch API</a>, so it can run anywhere. While Remix is relatively new, it’s certainly production ready, and there is a dedicated team behind it. If you aren’t ready to dive into Remix yet, at least keep your eye on it because this is a game changer.</p>',NULL,0,NULL,'2023-01-27 14:50:36','2023-01-27 14:50:36','abbaa8b3-a265-48fa-813a-129b4f707ce0',1,NULL,NULL,22,NULL),(1903,2953,'Why I’m So Excited About Remix',NULL,'<p>I’ve been building on the web for a long time. There’s a part of me that still has a bit of an old school mentality, and it’s hard for me to get really excited about new technology. Sure React was cool when it came out for its state management, but I didn’t see it as the be-all and end-all for building on the web. I really like <a href=\"https://craftcms.com/\">Craft CMS</a> for building CMS powered sites since I have full control over the HTML being sent to the browser, and it’s really easy to extend for complex functionality, but it can be a bit complex to setup for beginners and with additional complexity can come performance issues.&nbsp;So it’s been a while since I’ve seen something to be <em>really</em> excited about.</p>\r\n<p>Enter <a href=\"https://remix.run/\">Remix</a>.</p>\r\n<p>Remix is an interesting mix of old school techniques along with modern technology. Ryan Florence actually sums it up perfectly as to where Remix fits in.</p>\r\n<blockquote class=\"twitter-tweet\" data-dnt=\"true\" data-theme=\"dark\"><p lang=\"en\" dir=\"ltr\">Forget “full stack”. Remix is center stack.<br><br>It’s an HTTP tunnel between interactivity and business logic. It’s the part of the stack that no tool before it has solved, not completely.<br><br>I think I finally know how to talk about Remix.</p>— Ryan Florence (@ryanflorence) <a href=\"https://twitter.com/ryanflorence/status/1482233495195770883?ref_src=twsrc%5Etfw\">January 15, 2022</a></blockquote>\r\n<script async=\"\" src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\r\n<p>I was able to easily switch my site that was fully powered by Craft to utilize Craft only as a GraphQL endpoint and Remix powering the rest. The two big things I needed to figure out were: how to get data from the GraphQL endpoint, and then how to process input from a user (for my contact form). I would definitely recommend reading through the Remix docs and the two tutorials, but if you can understand these two big pictures things, you’ll have enough to get started with Remix and go deeper once you get into it.</p>\r\n<h2>Getting GraphQL Data (aka Loaders)</h2>\r\n<p>Remix provides a <a href=\"https://remix.run/docs/en/v1/api/conventions#loader\">loader hook</a> that runs on the server to hydrate your component. In this simple example, I run a GraphQL request and return JSON with the results of that request in the loader function.</p>\r\n<script src=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550.js?file=loader.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550#file-loader-jsx\">View Gist</a></noscript>\r\n<p>Then in my <code>Index</code> component, I access the data from the loader hook with <a href=\"https://remix.run/docs/en/v1/api/remix#useloaderdata\">useLoaderData()</a>, and my component is now hydrated with that data. In MVC style of thinking, think of the loader hook as the controller processing the page load request, populating with data from the model, and then passing to the view.</p>\r\n<p>The great part about Remix is that this doesn’t have to be specifically a GraphQL request. This data can come from anywhere. The <a href=\"https://remix.run/docs/en/v1/tutorials/jokes\">Jokes tutorial</a> in the Remix docs even thoroughly walks through how you can manage all this data in <a href=\"https://www.prisma.io/\">Prisma</a>.</p>\r\n<h2>Processing User Input (aka Actions)</h2>\r\n<p>Ok great, so we’ve got data onto the page, but now we need to be able to handle user input. Remix also provides an <a href=\"https://remix.run/docs/en/v1/api/conventions#action\">action hook</a> to process an action request (ex: a form submission). In my case, I have a contact form that sends me an email via SendGrid after submission.</p>\r\n<script src=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550.js?file=action.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/87cdd99eb66d01f2a9dbc026324f9550#file-action-jsx\">View Gist</a></noscript>\r\n<p>If you don’t include an <code>action</code> attribute on your form, it will automatically submit to the same page. Again, in MVC thinking, the action would again be equivalent to the controller taking the request input and processing it. But honestly, this takes me back 15 years when form actions would point directly to PHP files.</p>\r\n<h2>Bonus: Progressive Enhancement</h2>\r\n<p>Since Remix is running on the server, you can turn off JavaScript and everything still works great. Data still loads, form actions still process; it’s beautiful. This is not running loaders and actions on the client side; it’s running them on the server. This isn’t an accident, the Remix team is carefully considering the UX.</p>\r\n<blockquote class=\"twitter-tweet\" data-dnt=\"true\" data-theme=\"dark\"><p lang=\"en\" dir=\"ltr\">User experience (UX) and developer experience (DX) are both important. UX is more important than DX.<a href=\"https://twitter.com/remix_run?ref_src=twsrc%5Etfw\">@remix_run</a> has taught me: It\'s easier to start with a great UX and work toward a good DX than it is to start with a great DX and work toward a good UX.</p>— Kent C. Dodds &#x1f4bf; (@kentcdodds) <a href=\"https://twitter.com/kentcdodds/status/1482390454389805058?ref_src=twsrc%5Etfw\">January 15, 2022</a></blockquote>\r\n<script async=\"\" src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\"></script>\r\n<h2>Bonus: So Speedy</h2>\r\n<p>Did I mention how fast everything renders? I don’t tend to put too much stock into Lighthouse scores since they seem so random, but Remix crushes Lighthouse without me having to invest any additional effort into improving performance.</p>\r\n<h2>Give it a Shot</h2>\r\n<p>Remix is built on the <a href=\"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\">native Fetch API</a>, so it can run anywhere. While Remix is relatively new, it’s certainly production ready, and there is a dedicated team behind it. If you aren’t ready to dive into Remix yet, at least keep your eye on it because this is a game changer.</p>',NULL,0,NULL,'2023-02-15 21:55:50','2023-02-15 21:55:50','14beb170-3cbd-44a5-9bf0-b1f14f781e79',1,NULL,NULL,23,NULL),(1905,2960,'Volunteers of America',NULL,NULL,NULL,0,NULL,'2023-03-07 22:03:28','2023-03-07 22:03:28','3fd9f368-2b08-4b95-a4a2-841fa303b4b2',1,NULL,NULL,NULL,NULL),(1925,3106,'Freeform + FieldFrame = ExpressionEngine Form Builder',NULL,'<p>A form builder is a regular request for some clients when they are looking for a content management system. There aren’t that many CMS that have this kind of functionality built-in, and even if they do, the implementation is usually less than desirable.</p>\r\n<p>If you follow along with what I’ve been up to, you can probably tell that I have been on a big <a href=\"http://expressionengine.com/\">ExpressionEngine</a> kick. I’ve been using it on a ton of projects recently, and I love it for its flexibility. Although it does lack a true form builder, I figured out a simple way to build one.</p>\r\n<p>If you are impatient and just want to get to the <a href=\"http://ee.trevor-davis.com/forms/contact-form/\">demo</a>, go ahead. Although the form will look like nothing special, as I have just added some simple styling. The real magic of the form builder happens in the Control Panel.</p>\r\n<h2>We Need Add-ons</h2>\r\n<p>This form builder would not be possible without the use of some awesome add-ons:</p>\r\n<ul><li><a href=\"http://devot-ee.com/add-ons/extensions/fieldframe/\"><strong>FieldFrame</strong></a>\r\n<p>This one is a no-brainer. I can’t even imagine building a site without it.</p></li>\r\n <li><a href=\"http://devot-ee.com/add-ons/modules/freeform/\"><strong>Freeform</strong></a>\r\n<p>If you need a form in EE, this is the way to do it.</p></li>\r\n <li><a href=\"http://devot-ee.com/add-ons/extensions/freeform-data-select/\"><strong>Freeform Data Select</strong></a>\r\n<p>A new fieldtype which pulls in templates and fields from the Freeform module.</p></li>\r\n</ul>\r\n<h2>Creating a Field Group</h2>\r\n<p>I built a custom Field Group called Forms, which consisted of four fields:</p>\r\n<ul><li>Template</li>\r\n <li>Notification Email Addresses</li>\r\n <li>Return Page</li>\r\n <li>Fields</li>\r\n</ul>\r\n<p>The first three are pretty simple, so I will just give a quick run through on the specifications for those fields and give a more detail review of the <strong>Fields</strong> field:</p>\r\n<h3>Template</h3>\r\n<p>This is where you select the Freeform template that will be used for the email. This field is not completely necessary, as just using the default template should be good enough, but I figured since it was an option, to throw it in.</p>\r\n<ul><li><strong>Field Name</strong>: form_template</li>\r\n <li><strong>Field Type</strong>: Freeform Data Select</li>\r\n <li><strong>Populate drop-down with</strong>: Freeform Templates</li>\r\n</ul>\r\n<h3>Notification Email Addresses</h3>\r\n<p>This is where you will enter the email address(es) that you want to email when the form is submitted. Since Freeform wants multiple email addresses separated by the | character, I added some help text describing that as well.</p>\r\n<ul><li><strong>Field Name</strong>: form_notify</li>\r\n <li><strong>Field Type</strong>: Text Input</li>\r\n <li><strong>Default Text Formatting for This Field</strong>: None</li>\r\n <li><strong>Hide Formatting Menu</strong></li>\r\n</ul>\r\n<h3>Return Page</h3>\r\n<p>This is where you enter the URL to the thank you page. Once a user successfully submits the form, they will be redirected here.</p>\r\n<ul><li><strong>Field Name</strong>: form_return</li>\r\n <li><strong>Field Type</strong>: Text Input</li>\r\n <li><strong>Default Text Formatting for This Field</strong>: None</li>\r\n <li><strong>Hide Formatting Menu</strong></li>\r\n</ul>\r\n<h3>Fields</h3>\r\n<p>This is where the magic happens. This field is a FF Matrix field type that consists of five other fields. Each row of the FF Matrix represents one field (or field group) in the form. I’ll give a brief description of each field of the FF Matrix and show you a screenshot so you can get a better picture:</p>\r\n<ul><li><strong>Field Name</strong>: form_field</li>\r\n <li><strong>Field Type</strong>: FF Matrix</li>\r\n</ul>\r\n<h4>Label</h4>\r\n<p>This is where you will enter the label for the form field:</p>\r\n<ul><li><strong>Col Name</strong>: label</li>\r\n <li><strong>Cell Type</strong>: Text Input</li>\r\n</ul>\r\n<h4>Field</h4>\r\n<p>This field pulls in all of the fields that have been entered into the Freeform module. So you are presented with a dropdown of all of the fields, and you just select the appropriate one:</p>\r\n<ul><li><strong>Col Name</strong>: field</li>\r\n <li><strong>Cell Type</strong>: Freeform Data Select</li>\r\n <li><strong>Populate drop-down with</strong>: Freeform fields</li>\r\n</ul>\r\n<h4>Field Type</h4>\r\n<p>This is where you select the type of form field you want to use. I put in a few different options, but this could be expanded upon too:</p>\r\n<ul><li><strong>Col Name</strong>: type</li>\r\n <li><strong>Cell Type</strong>: FF Select</li>\r\n <li><strong>Select Options</strong>: Text Input<br>\r\n  Textarea<br>\r\n  Select<br>\r\n  Multi-select<br>\r\n  Checkbox<br>\r\n  Checkbox Group<br>\r\n  Radio Group\r\n </li>\r\n</ul>\r\n<h4>Is this field required?</h4>\r\n<p>With most forms, there are some fields that are required and some that are not. If you want the field to be required, you just check the checkbox and the Freeform module will make sure it is filled in:</p>\r\n<ul><li><strong>Col Name</strong>: required</li>\r\n <li><strong>Cell Type</strong>: FF Checkbox</li>\r\n <li><strong>Checkbox Label</strong>: Is this field required?</li>\r\n</ul>\r\n<h4>Options</h4>\r\n<p>Finally, this field is used when you select a field type that has multiple options. For example, if you select a “Select” field type, you need to provide the options to populate it. So you enter the options separated by the | character:</p>\r\n<ul><li><strong>Col Name</strong>: options</li>\r\n <li><strong>Cell Type</strong>: Text Input</li>\r\n</ul>\r\n<figure><a href=\"//assets.trevor-davis.com/uploads/images/content/2009/11/ff-matrix.png\" title=\"FieldFrame Matrix Configuration\"><img src=\"//assets.trevor-davis.com/uploads/images/content/2009/11/ff-matrix_thumb.png\" alt=\"FieldFrame Matrix Configuration\" width=\"570\" height=\"203\" class=\"pullLeft frame\" data-image=\"b5js0bktz1as\"></a></figure>\r\n<p>So that is all you need for the Field Group, now go ahead and create a Forms Channel (or Weblog) and make sure to assign the Forms Field Group to it.</p>\r\n<p>Now, when you go to publish a Forms entry, you should see something like this:</p>\r\n<figure><a href=\"//assets.trevor-davis.com/uploads/images/content/2009/11/form-entry.png\" title=\"New Forms Entry\"><img src=\"//assets.trevor-davis.com/uploads/images/content/2009/11/form-entry_thumb.png\" alt=\"New Forms Entry\" width=\"570\" height=\"258\" class=\"pullLeft frame\" data-image=\"yuyhw5avjb9u\"></a></figure>\r\n<p>I made a <a href=\"http://ee.trevor-davis.com/forms/contact-form/\">demo form</a> showing off the various types of form fields. Here is what that entry looks like in the Control Panel:</p>\r\n<figure><a href=\"//assets.trevor-davis.com/uploads/images/content/2009/11/demo-entry.png\" title=\"Demo Form Entry in Control Panel\"><img src=\"//assets.trevor-davis.com/uploads/images/content/2009/11/demo-entry_thumb.png\" alt=\"Demo Form Entry in Control Panel\" width=\"570\" height=\"400\" class=\"pullLeft frame\" data-image=\"wu4yx2snnz46\"></a></figure>\r\n<h2>Template</h2>\r\n<p>Now that we have our Channel and Field Group all setup, we need to deal with the template. You could really setup your template/template group in various ways, so I’m not going to get into those details. One detail that you need to know is that <strong>I enabled PHP for my template</strong>.</p>\r\n<p>First, we just need to start with a simple <strong>exp:weblog:entries</strong> tag:</p>\r\n<pre>{exp:weblog:entries weblog=\"forms\" limit=\"1\" disable=\"categories|category_fields|member_data|pagination|trackbacks\"}\r\n…\r\n{/exp:weblog:entries} </pre>\r\n<p>Next, we want to start the Freeform form tag:</p>\r\n<pre>{exp:weblog:entries weblog=\"forms\" limit=\"1\" disable=\"categories|category_fields|member_data|pagination|trackbacks\"}\r\n {exp:freeform:form form_name=\"{url_title}\" form_id=\"{url_title}\" template=\"{form_template}\" notify=\"{form_notify}\" return=\"{form_return}\"}\r\n …\r\n {/exp:freeform:form}\r\n{/exp:weblog:entries} </pre>\r\n<p>Since we are inside of the exp:weblog:entries tag, we have access to everything that is being returned from that entry. If you have any questions about the various parameters, <a href=\"http://www.solspace.com/docs/detail/freeform_form/#parameters\">refer to the Freeform Documentation</a>.</p>\r\n<p>So far, the values that we are passing into the parameters are pretty straightforward, just custom fields from our entry. But, passing in the <a href=\"http://www.solspace.com/docs/detail/freeform_form/#required\">required</a> parameter is a little more challenging, since we have to loop through all of the rows in our FF Matrix.</p>\r\n<p>If we <a href=\"http://brandon-kelly.com/fieldframe/docs/ff-matrix\">refer to the FF Matrix documentation</a>, we can see how to loop through the rows in the matrix. I only want to return rows that have the required checkbox checked, and I want to separate the names of the fields with the | character. Then, I use the backspace parameter to remove the last one:</p>\r\n<pre>{exp:freeform:form form_name=\"{url_title}\" form_id=\"{url_title}\" template=\"{form_template}\" notify=\"{form_notify}\" return=\"{form_return}\" &lt;strong&gt;required=\"{form_field search:required=\'y\' backspace=\'1\'}{field}|{/form_field}\"&lt;/strong&gt;}\r\n…\r\n{/exp:freeform:form}</pre>\r\n<p>Next, we want to add a title, ordered list that will contain the form fields, and the submit button:</p>\r\n<pre>{exp:freeform:form form_name=\"{url_title}\" form_id=\"{url_title}\" template=\"{form_template}\" notify=\"{form_notify}\" return=\"{form_return}\" required=\"{form_field search:required=\'y\' backspace=\'1\'}{field}|{/form_field}\"}\r\n &lt;h1&gt;{title}&lt;/h1&gt;\r\n &lt;ol class=\"forms\"&gt;\r\n  …\r\n  &lt;li class=\"buttons\"&gt;&lt;input type=\"submit\" name=\"submit\" value=\"submit\" /&gt;&lt;/li&gt;\r\n &lt;/ol&gt;\r\n{/exp:freeform:form} </pre>\r\n<p>Now here is where the code gets interesting. Just like we did with the required parameter, we want to loop through each row in the FF Matrix and display a list item for each one:</p>\r\n<pre>{form_field}\r\n &lt;li&gt;\r\n …\r\n &lt;/li&gt;\r\n{/form_field} </pre>\r\n<p>First, let’s display the label, if there is one, and denote if the field is required or not:</p>\r\n<pre>{if \"{label}\"}\r\n &lt;label for=\"{field}\"&gt;{if \"{required}\" == \"y\"}&lt;em class=\"required\"&gt;*&lt;/em&gt;{/if}{label}&lt;/label&gt;\r\n{/if} </pre>\r\n<p>The rest of the code is a big conditional statement which depends upon the Field Types that you specified in the FF Matrix. Let’s first take a look at the <strong>Text Input</strong>, <strong>Textarea</strong>, and <strong>Checkbox</strong> types since they are pretty straightforward:</p>\r\n<pre>{if \"{type}\" == \"Text Input\"}\r\n &lt;input type=\"text\" name=\"{field}\" id=\"{field}\" /&gt;\r\n{if:elseif \"{type}\" == \"Textarea\"}\r\n &lt;textarea name=\"{field}\" id=\"{field}\"&gt;&lt;/textarea&gt;\r\n{if:elseif \"{type}\" == \"Checkbox\"}\r\n &lt;input type=\"checkbox\" class=\"checkbox\" name=\"{field}\" id=\"{field}\" /&gt;\r\n…\r\n{/if} </pre>\r\n<p>So we are just checking the type column in the FF Matrix, and then displaying the rest of the data from the row in the appropriate form field.</p>\r\n<p>Next, let’s walk through what happens if you select the <strong>Select</strong> or <strong>Multi-select</strong> types:</p>\r\n<pre>{if:elseif \"{type}\" == \"Select\" || \"{type}\" == \"Multi-select\" && \"{options}\"}\r\n &lt;select name=\"{field}{if \"{type}\" == \"Multi-select\"}[]{/if}\" id=\"{field}\"{if \"{type}\" == \"Multi-select\"} multiple=\"multiple\" size=\"4\"{/if}&gt;\r\n  &lt;?php $items = explode(\"|\", \'{options}\'); ?&gt;\r\n  &lt;?php foreach ($items as $item) { ?&gt;\r\n   &lt;option value=\"&lt;?php echo $item; ?&gt;\"&gt;&lt;?php echo $item; ?&gt;&lt;/option&gt;\r\n  &lt;?php } ?&gt;\r\n &lt;/select&gt;\r\n…\r\n{/if} </pre>\r\n<p>So first, we are displaying the <strong>select element</strong>, and if it is a <strong>Multi-select</strong>, we are adding <strong>[]</strong> to the name attribute, and adding the <strong>multiple</strong> and <strong>size</strong> attributes.</p>\r\n<p>Next, we want to take the string of <strong>options</strong> that are separated by the | character, and create an array out of the string using the <a href=\"http://php.net/manual/en/function.explode.php\">explode function</a>.</p>\r\n<p>Then, we loop through the array, and display the options for each one.</p>\r\n<p>Finally, we do virtually the same thing for the <strong>Radio Group</strong> and <strong>Checkbox Group</strong> field types:</p>\r\n<pre>{if:elseif \"{type}\" == \"Radio Group\" && \"{options}\"}\r\n &lt;ul class=\"group\"&gt;\r\n  &lt;?php $items = explode(\"|\", \'{options}\'); $count = 1; ?&gt;\r\n  &lt;?php foreach ($items as $item) { ?&gt;\r\n   &lt;li&gt;&lt;input type=\"radio\" class=\"radio\" name=\"{field}\" id=\"{field}&lt;?php echo $count; ?&gt;\" value=\"&lt;?php echo $item; ?&gt;\" /&gt;\r\n   &lt;label for=\"{field}&lt;?php echo $count; ?&gt;\"&gt;&lt;?php echo $item; ?&gt;&lt;/label&gt;&lt;/li&gt;\r\n   &lt;?php $count++; ?&gt;\r\n  &lt;?php } ?&gt;\r\n &lt;/ul&gt;\r\n{if:elseif \"{type}\" == \"Checkbox Group\" && \"{options}\"}\r\n &lt;ul class=\"group\"&gt;\r\n  &lt;?php $items = explode(\"|\", \'{options}\'); $count = 1; ?&gt;\r\n  &lt;?php foreach ($items as $item) { ?&gt;\r\n   &lt;li&gt;&lt;input type=\"checkbox\" class=\"checkbox\" name=\"{field}[]\" id=\"{field}&lt;?php echo $count; ?&gt;\" value=\"&lt;?php echo $item; ?&gt;\" /&gt;\r\n   &lt;label for=\"{field}&lt;?php echo $count; ?&gt;\"&gt;&lt;?php echo $item; ?&gt;&lt;/label&gt;&lt;/li&gt;\r\n   &lt;?php $count++; ?&gt;\r\n  &lt;?php } ?&gt;\r\n &lt;/ul&gt;\r\n{/if} </pre>\r\n\r\n<p>So that’s it really. Here is the full template:</p>\r\n<pre>{exp:weblog:entries weblog=\"forms\" limit=\"1\" disable=\"categories|category_fields|member_data|pagination|trackbacks\"}\r\n\r\n {exp:freeform:form form_name=\"{url_title}\" form_id=\"{url_title}\" template=\"{form_template}\" notify=\"{form_notify}\" return=\"{form_return}\" required=\"{form_field search:required=\'y\' backspace=\'1\'}{field}|{/form_field}\"}\r\n\r\n  &lt;h1&gt;{title}&lt;/h1&gt;\r\n  &lt;ol class=\"forms\"&gt;\r\n   {form_field}\r\n    &lt;li&gt;\r\n     {if \"{label}\"}\r\n      &lt;label for=\"{field}\"&gt;{if \"{required}\" == \"y\"}&lt;em class=\"required\"&gt;*&lt;/em&gt;{/if}{label}&lt;/label&gt;\r\n     {/if}\r\n     {if \"{type}\" == \"Text Input\"}\r\n      &lt;input type=\"text\" name=\"{field}\" id=\"{field}\" /&gt;\r\n     {if:elseif \"{type}\" == \"Textarea\"}\r\n      &lt;textarea name=\"{field}\" id=\"{field}\"&gt;&lt;/textarea&gt;\r\n     {if:elseif \"{type}\" == \"Checkbox\"}\r\n      &lt;input type=\"checkbox\" class=\"checkbox\" name=\"{field}\" id=\"{field}\" /&gt;\r\n     {if:elseif \"{type}\" == \"Select\" || \"{type}\" == \"Multi-select\" && \"{options}\"}\r\n      &lt;select name=\"{field}{if \"{type}\" == \"Multi-select\"}[]{/if}\" id=\"{field}\"{if \"{type}\" == \"Multi-select\"} multiple=\"multiple\" size=\"4\"{/if}&gt;\r\n       &lt;?php $items = explode(\"|\", \'{options}\'); ?&gt;\r\n       &lt;?php foreach ($items as $item) { ?&gt;\r\n        &lt;option value=\"&lt;?php echo $item; ?&gt;\"&gt;&lt;?php echo $item; ?&gt;&lt;/option&gt;\r\n       &lt;?php } ?&gt;\r\n      &lt;/select&gt;\r\n     {if:elseif \"{type}\" == \"Radio Group\" && \"{options}\"}\r\n      &lt;ul class=\"group\"&gt;\r\n       &lt;?php $items = explode(\"|\", \'{options}\'); $count = 1; ?&gt;\r\n       &lt;?php foreach ($items as $item) { ?&gt;\r\n        &lt;li&gt;&lt;input type=\"radio\" class=\"radio\" name=\"{field}\" id=\"{field}&lt;?php echo $count; ?&gt;\" value=\"&lt;?php echo $item; ?&gt;\" /&gt;\r\n        &lt;label for=\"{field}&lt;?php echo $count; ?&gt;\"&gt;&lt;?php echo $item; ?&gt;&lt;/label&gt;&lt;/li&gt;\r\n        &lt;?php $count++; ?&gt;\r\n       &lt;?php } ?&gt;\r\n      &lt;/ul&gt;\r\n     {if:elseif \"{type}\" == \"Checkbox Group\" && \"{options}\"}\r\n      &lt;ul class=\"group\"&gt;\r\n       &lt;?php $items = explode(\"|\", \'{options}\'); $count = 1; ?&gt;\r\n       &lt;?php foreach ($items as $item) { ?&gt;\r\n        &lt;li&gt;&lt;input type=\"checkbox\" class=\"checkbox\" name=\"{field}[]\" id=\"{field}&lt;?php echo $count; ?&gt;\" value=\"&lt;?php echo $item; ?&gt;\" /&gt;\r\n        &lt;label for=\"{field}&lt;?php echo $count; ?&gt;\"&gt;&lt;?php echo $item; ?&gt;&lt;/label&gt;&lt;/li&gt;\r\n        &lt;?php $count++; ?&gt;\r\n       &lt;?php } ?&gt;\r\n      &lt;/ul&gt;\r\n     {/if}\r\n    &lt;/li&gt;\r\n   {/form_field}\r\n   &lt;li class=\"buttons\"&gt;&lt;input type=\"submit\" name=\"submit\" value=\"submit\" /&gt;&lt;/li&gt;\r\n  &lt;/ol&gt;\r\n {/exp:freeform:form}  \r\n{/exp:weblog:entries} </pre>\r\n<h2>Conclusion</h2>\r\n<p>This is not meant to be an all-inclusive form builder, as you can see there are <a href=\"http://www.solspace.com/docs/detail/freeform_form/#parameters\">many other parameters for the Freeform module</a>. But, this was meant to show a creative use of ExpressionEngine and EE add-ons to add functionality that is not directly built into the CMS.</p>\r\n<p>So go forth and create.</p>',NULL,1939,NULL,'2023-08-03 12:35:56','2023-08-03 12:35:56','148e3582-9d79-4203-97e2-0989de4ef6dd',1,NULL,NULL,NULL,NULL),(1966,3425,'About',NULL,NULL,NULL,0,NULL,'2024-02-09 20:55:17','2024-02-09 20:55:17','f5ce81b3-880b-4bf9-b00f-692f639ac93a',1,NULL,NULL,NULL,NULL),(1986,3583,'About',NULL,NULL,NULL,0,NULL,'2024-04-15 18:28:06','2024-04-15 18:28:06','fe0b0c95-65aa-4f7f-870d-7b575d08a7e6',1,NULL,NULL,NULL,NULL),(1987,3589,'About',NULL,NULL,NULL,0,NULL,'2024-04-15 18:28:06','2024-04-15 18:28:06','aee10d3f-ebef-4fd8-8eaf-cdc502b2c083',1,NULL,NULL,NULL,NULL),(1988,3595,'About',NULL,NULL,NULL,0,NULL,'2024-04-15 18:28:07','2024-04-15 18:28:07','9bead1a2-6fcd-4148-ba32-6dbb3ee00b43',1,NULL,NULL,NULL,NULL),(1990,3625,'About',NULL,NULL,NULL,0,NULL,'2024-04-15 18:36:12','2024-04-15 18:36:12','3a4d7389-fce9-4717-9216-d5bf5f4b3114',1,NULL,NULL,NULL,NULL),(1992,3674,'About',NULL,NULL,NULL,0,NULL,'2024-04-15 18:39:57','2024-04-15 18:39:57','519ecb1c-5ca0-4e0a-9e57-dd6d1daad634',1,NULL,NULL,NULL,NULL),(1994,3703,'About',NULL,NULL,NULL,0,NULL,'2024-04-15 18:41:19','2024-04-15 18:41:19','584d2ab8-4268-47c8-8605-7bccaceea9ee',1,NULL,NULL,NULL,NULL),(1996,3721,'About',NULL,NULL,NULL,0,NULL,'2024-04-15 18:43:25','2024-04-15 18:43:25','a4101ae5-9d43-471c-a512-277495c59c4d',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Remix\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(1998,3739,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 15:25:13','2024-04-16 15:25:13','edbea0ab-2d3e-4158-bfd0-7314ae8298f0',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2000,3757,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 16:57:47','2024-04-16 16:57:47','b0a26021-6b4e-49b6-9e03-7e0b9b77563e',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2001,3766,'IMG 0117',NULL,NULL,NULL,0,NULL,'2024-04-16 17:01:02','2024-04-16 17:01:02','ee48626a-2fa1-4486-82d1-db452ba30db2',1,NULL,NULL,NULL,NULL),(2003,3776,'IMG 1361',NULL,NULL,NULL,0,NULL,'2024-04-16 17:01:05','2024-04-16 17:01:05','ca067447-e1a4-46f6-84dc-7f261fa1eae1',1,NULL,NULL,NULL,NULL),(2004,3777,'IMG 1752',NULL,NULL,NULL,0,NULL,'2024-04-16 17:02:49','2024-04-16 17:02:49','1713a574-433d-4819-8db5-38c090425ebb',1,NULL,NULL,NULL,NULL),(2005,3778,'IMG 1957',NULL,NULL,NULL,0,NULL,'2024-04-16 17:02:50','2024-04-16 17:02:50','ae5788fd-0264-4f1d-ac66-8338a82aa64c',1,NULL,NULL,NULL,NULL),(2006,3779,'IMG 6414',NULL,NULL,NULL,0,NULL,'2024-04-16 17:02:51','2024-04-16 17:04:48','ddadcb29-afee-4d5a-a43e-48b572f0d978',1,NULL,NULL,NULL,NULL),(2007,3780,'IMG 7256',NULL,NULL,NULL,0,NULL,'2024-04-16 17:02:52','2024-04-16 17:03:34','11c0099a-7938-456d-a6d3-ea26d05bc3b1',1,NULL,NULL,NULL,NULL),(2008,3781,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:03:42','2024-04-16 17:03:42','d235c84e-d26a-47b5-a8fc-578017619e45',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2010,3799,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:10:04','2024-04-16 17:10:04','c538e78b-6bd1-4cc4-abe0-1e8fb6fa658b',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2012,3817,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:10:39','2024-04-16 17:10:39','790d757a-72ae-40cd-8138-3bea7847e70f',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2013,3826,'58232660140 EDE13 FD5 B93 D 4 BA8 BBA8 8 F905630341 E',NULL,NULL,NULL,0,NULL,'2024-04-16 17:15:28','2024-04-16 17:15:47','18696f85-c2f7-4846-982b-1ba8cdd3149d',1,NULL,NULL,NULL,NULL),(2014,3827,'IMG 2226',NULL,NULL,NULL,0,NULL,'2024-04-16 17:15:29','2024-04-16 17:16:02','98f926ab-53e9-4bb8-b2b1-fa2888ba7cd7',1,NULL,NULL,NULL,NULL),(2016,3837,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:16:09','2024-04-16 17:16:09','8cc5bc43-f153-41ea-80b9-035e42b8db3e',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2018,3855,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:16:26','2024-04-16 17:16:26','1aec4b52-2edf-445f-987b-5c022856b796',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2019,3864,'1591575874636',NULL,NULL,NULL,0,NULL,'2024-04-16 17:29:39','2024-04-16 17:30:13','8a83c459-9eea-4ff3-a3ac-91c0571260cc',1,NULL,NULL,NULL,NULL),(2021,3874,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:29:55','2024-04-16 17:29:55','b54f1484-05cf-4f43-9f52-371d4fc62132',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2023,3892,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:50:17','2024-04-16 17:50:17','2492f165-912b-40c7-83b2-adcbf6ba15ed',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2025,3910,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:50:34','2024-04-16 17:50:34','ffdc108d-1259-4acd-94da-7b868dcb618b',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2027,3928,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:51:07','2024-04-16 17:51:07','4b07da21-357f-4c8a-af46-9e17bc51c06b',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2029,3946,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:51:35','2024-04-16 17:51:35','be98136b-2f83-4d18-a4d6-42f6fc835a16',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2031,3964,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:51:59','2024-04-16 17:51:59','d9c3687f-6f4f-4677-9fe7-90a008ed0270',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2033,3982,'About',NULL,NULL,NULL,0,NULL,'2024-04-16 17:52:15','2024-04-16 17:52:15','f5d86a16-60b8-49ce-8ea8-11b7716cfe34',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]'),(2057,4175,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"{asset:2299:url||//assets.trevor-davis.com/uploads/images/content/live-preview.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2024-06-17 21:12:06','2024-06-17 21:12:06','9c2a87f8-2ba0-4645-89b1-c2b30cab830b',1,NULL,NULL,28,NULL),(2073,4303,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-07-29 14:35:24','2024-07-29 14:35:24','fdf65389-c299-484c-a27a-efa176fc34f6',1,NULL,NULL,26,NULL),(2074,4311,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-07-29 14:35:25','2024-07-29 14:35:25','563bf207-3f86-4360-b9bf-a91b17adc5b8',1,NULL,NULL,25,NULL),(2075,4319,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-07-29 14:35:28','2024-07-29 14:35:28','bb0596f0-0819-4469-a735-58d0ccedce31',1,NULL,NULL,25,NULL),(2076,4327,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-07-29 14:35:29','2024-07-29 14:35:29','d6fc5357-282e-45fa-b648-1a85ddf59440',1,NULL,NULL,26,NULL),(2077,4335,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-07-29 14:35:30','2024-07-29 14:35:30','e14b3497-8cae-4427-88e3-2084f9d71875',1,NULL,NULL,25,NULL),(2079,4352,'About',NULL,NULL,NULL,0,NULL,'2024-08-11 18:10:14','2024-08-11 18:10:14','87343aca-8763-445b-b773-e6d925fb0e37',1,NULL,NULL,NULL,'[{\"col1\":\"HTML\"},{\"col1\":\"CSS\"},{\"col1\":\"JavaScript\\/TypeScript\"},{\"col1\":\"React\"},{\"col1\":\"Next.js\"},{\"col1\":\"Remix\"},{\"col1\":\"GraphQL\"},{\"col1\":\"Tailwind CSS\"},{\"col1\":\"Git\"},{\"col1\":\"PHP\"},{\"col1\":\"MySQL\"},{\"col1\":\"Craft CMS\"},{\"col1\":\"Yii\"},{\"col1\":\"Twig\"},{\"col1\":\"Ruby on Rails\"}]');
INSERT INTO `craft_content` VALUES (2080,4361,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-09-12 10:20:05','2024-09-12 10:20:05','f2e18def-b8e2-442e-bdc7-042ac32b64e4',1,NULL,NULL,26,NULL),(2081,4369,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-09-12 10:20:06','2024-09-12 10:20:06','614a4bb9-7a67-439b-93ea-bea760e445ea',1,NULL,NULL,25,NULL),(2082,4377,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-09-12 10:20:08','2024-09-12 10:20:08','01be9f1a-ee8d-4c8f-bf18-21426f435e5c',1,NULL,NULL,26,NULL),(2083,4385,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-09-19 09:44:11','2024-09-19 09:44:11','a556869e-d948-4f45-8fd9-759434082c54',1,NULL,NULL,27,NULL),(2084,4393,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-09-19 09:44:13','2024-09-19 09:44:13','f36386e3-79b4-4e22-8f1b-1d10fc83f2c7',1,NULL,NULL,27,NULL),(2085,4401,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-09-19 09:44:14','2024-09-19 09:44:14','245065c2-98c9-4db5-93ac-c7733a0dc7ec',1,NULL,NULL,26,NULL),(2086,4409,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"{asset:2299:url||//assets.trevor-davis.com/uploads/images/content/live-preview.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2024-10-09 09:59:48','2024-10-09 09:59:48','f0e3b78f-a795-4fc1-bf46-75a3166fc59f',1,NULL,NULL,29,NULL),(2087,4417,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-10-09 13:00:49','2024-10-09 13:00:49','ddd1c4d9-7d0d-48fd-a2b7-1e5e6efe9b42',1,NULL,NULL,27,NULL),(2088,4425,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-10-09 13:00:50','2024-10-09 13:00:50','7f895c1c-8b4c-427a-9722-2436d086a2ee',1,NULL,NULL,26,NULL),(2089,4433,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-10-11 07:54:50','2024-10-11 07:54:50','be3b9785-f34a-4867-ad2c-5dd1469953ff',1,NULL,NULL,27,NULL),(2090,4441,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-10-11 07:54:50','2024-10-11 07:54:50','fea8bbe3-87c6-48c1-b1f6-519584debf97',1,NULL,NULL,26,NULL),(2091,4449,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-10-11 07:54:52','2024-10-11 07:54:52','15b11532-044d-45ef-bdb8-9436deeeca41',1,NULL,NULL,27,NULL),(2092,4457,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-10-11 07:54:56','2024-10-11 07:54:56','deb58084-b219-4e15-a7e1-c2bba0f100e5',1,NULL,NULL,26,NULL),(2093,4465,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-10-22 03:57:21','2024-10-22 03:57:21','aeb78d26-76b2-47b8-885d-5e700391b627',1,NULL,NULL,27,NULL),(2094,4473,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-10-22 03:57:25','2024-10-22 03:57:25','6a61972b-0f00-44ff-8ba3-ab55622a5c42',1,NULL,NULL,26,NULL),(2095,4481,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-10-31 18:22:31','2024-10-31 18:22:31','5bb39c48-7995-400c-a767-d2a2a933e6f6',1,NULL,NULL,28,NULL),(2096,4489,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-12-12 16:11:21','2024-12-12 16:11:21','7e09831d-e71c-475c-ae6d-20f607bcf527',1,NULL,NULL,29,NULL),(2097,4497,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-12-12 16:11:23','2024-12-12 16:11:23','cb7c4c65-7210-429f-87fd-cf82f0e63af0',1,NULL,NULL,28,NULL),(2098,4505,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-12-12 16:11:25','2024-12-12 16:11:25','9d54a123-0d07-47f4-87e8-8fda8f79c42e',1,NULL,NULL,29,NULL),(2099,4513,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-12-12 16:12:18','2024-12-12 16:12:18','40750543-2e97-4528-9d31-060f4a141e13',1,NULL,NULL,28,NULL),(2100,4521,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-12-12 16:12:21','2024-12-12 16:12:21','d564cad4-e9f0-48ca-b1c8-319360fe4ba1',1,NULL,NULL,29,NULL),(2101,4529,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-12-12 16:12:23','2024-12-12 16:12:23','44914f61-839b-4f1b-aa7e-ba6f628b5881',1,NULL,NULL,29,NULL),(2102,4537,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-12-12 16:12:26','2024-12-12 16:12:26','ea3e47a0-b44b-479d-a938-0710404bed72',1,NULL,NULL,28,NULL),(2103,4545,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2024-12-12 16:12:28','2024-12-12 16:12:28','96b4880d-ef9c-40c3-963d-f2b57521400c',1,NULL,NULL,29,NULL),(2104,4553,'Setting Up Live Preview with Craft CMS in Headless Mode',NULL,'<p>In my <a href=\"https://www.trevor-davis.com/blog/why-im-so-excited-about-remix\">previous post</a>, I talked about transitioning my site from being powered by <a href=\"https://craftcms.com/\">Craft</a> to being powered by <a href=\"https://remix.run/\">Remix</a> and a GraphQL endpoint coming from Craft. One of the best features of Craft is <a href=\"https://craftcms.com/features/all#live-preview\">Live Preview</a>, and it took a little configuration to get this working on my new Remix powered site.</p>\r\n<h2>Remix Configuration</h2>\r\n<p>These were specific changes that I needed to make on the Remix side, but really it would be the same for any GraphQL request. As Andrew Welch breaks down in <a href=\"https://nystudio107.com/blog/headless-preview-in-craft-cms\">his article</a>, the key is passing the token parameter back that Craft included in the URL.</p>\r\n<p>In the loader function of my blog post route, I call a helper function to setup the GraphQL client, and I pass the request to it:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=$slug.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-slug-jsx\">View Gist</a></noscript>\r\n<p>And then in my helper function, I take that request, grab the URL params that I want to include, and add those to my GraphQL request endpoint URL:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=graphql.server.jsx\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-graphql-server-jsx\">View Gist</a></noscript>\r\n<p>It wasn’t totally necessary to include the <code>x-craft-preview</code> and <code>x-craft-live-preview</code> parameters, but I figured it couldn’t hurt.</p>\r\n<h2>Craft Configuration</h2>\r\n<p>Now with the Remix configuration in place, I just needed to make two small changes to Craft to get it working correctly. First, I had to update the preview target for my Blog section to point to my Remix site:</p>\r\n<figure><img src=\"{asset:2283:url||//assets.trevor-davis.com/uploads/images/content/blog-section-preview-target.png}\" data-image=\"2283\" alt=\"Setting the preview target in my Blog Section\"></figure>\r\n<p>Then, I had to update the <code>config/general.php</code> to allow iframe requests from my Remix domain:</p>\r\n<script src=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e.js?file=general.php\"></script>\r\n<noscript><a href=\"https://gist.github.com/davist11/129038286f04bc5e2941baae32ee509e#file-general-php\">View Gist</a></noscript>\r\n\r\n<h2>Voilà, Live Preview</h2>\r\n<p>And just like that, I had live preview working.</p>\r\n<video src=\"{asset:2299:url||//assets.trevor-davis.com/uploads/images/content/live-preview.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" width=\"100%\"></video>',NULL,0,NULL,'2024-12-17 16:39:36','2024-12-17 16:39:36','e2dbb313-5d04-4df3-bbdb-6508c118672f',1,NULL,NULL,30,NULL),(2105,4561,'ESPN Green Sneakers',NULL,NULL,'https://www.espn.com/espn/feature/story/_/id/42192420/nba-christmas-day-players-want-their-sneakers-wearable-environmentally-friendly',0,NULL,'2024-12-30 16:14:22','2025-01-03 16:50:34','56acc82d-f56e-48d6-8cd3-8cba55365d9d',1,NULL,NULL,NULL,NULL),(2106,4562,'ESPN Green Sneakers',NULL,NULL,'https://www.espn.com/espn/feature/story/_/id/42192420/nba-christmas-day-players-want-their-sneakers-wearable-environmentally-friendly',0,NULL,'2024-12-30 16:15:01','2024-12-30 16:15:01','f8818324-2ee1-4bc8-b7b2-52c4c5287801',1,NULL,NULL,NULL,NULL),(2107,4563,'Espn green sneakers',NULL,NULL,NULL,0,NULL,'2025-01-03 16:50:19','2025-01-03 16:50:19','41cb1f53-511e-4f13-bab1-3ef3679717c8',1,NULL,NULL,NULL,NULL),(2109,4565,'ESPN Green Sneakers',NULL,NULL,'https://www.espn.com/espn/feature/story/_/id/42192420/nba-christmas-day-players-want-their-sneakers-wearable-environmentally-friendly',0,NULL,'2025-01-03 16:50:34','2025-01-03 16:50:34','f0014239-7103-477f-a0a7-7d56eb8756d0',1,NULL,NULL,NULL,NULL),(2111,4573,'NEA',NULL,'<p>NEA is a leading a leading global venture capital firm who came to Viget to redesign their website. We rebuilt the site to adapt to whatever size screen it is being viewed on, and my role was to build the front-end of the site and integrate into ExpressionEngine.</p>','http://www.nea.com/',0,NULL,'2025-01-03 16:53:22','2025-01-03 16:53:22','d445391f-db30-46f6-8da8-18872fa15881',1,NULL,NULL,NULL,NULL),(2112,4580,'Lupus Resource Center',NULL,NULL,NULL,0,NULL,'2025-01-03 16:53:38','2025-01-03 16:53:38','dab819e9-d9e0-4171-93b5-eb658f3e2def',1,NULL,NULL,NULL,NULL),(2114,4586,'GoPole',NULL,'<p>We migrated the old GoPole site off of ExpressionEngine and into a Craft and Shopify combo. My role was to configure and integrate Craft and to integrate with Shopify as well. I built a <a href=\"https://github.com/davist11/craft-shopify\">Craft plugin that integrates with Shopify</a> and did some custom development work to integrate the forms with Zendesk.</p>','https://www.gopole.com/',0,NULL,'2025-01-03 16:55:42','2025-01-03 16:55:42','55eb4f96-ab12-46aa-9f82-99ea57ae9cbe',1,NULL,NULL,NULL,NULL),(2115,4591,'Viget',NULL,'<p>After four years and a few failed attempts, we finally made it a point to redo the <strong>Viget</strong> website. Our old design served us well, but it was time to step it up to the next level. I was able to step in and implement the ExpressionEngine piece of the site, which included creating custom add-ons as well.</p>','http://viget.com/',0,NULL,'2025-01-03 16:56:24','2025-01-03 16:56:24','218448e9-d59d-4b66-ae9e-75ac1bcbd9bf',1,NULL,NULL,NULL,NULL),(2116,4596,'Hamilton Company',NULL,NULL,'https://www.hamiltoncompany.com/',0,NULL,'2025-01-03 16:56:24','2025-01-03 16:56:24','945b78ac-ad63-4083-8952-ec3788f4e34c',1,NULL,NULL,NULL,NULL),(2117,4597,'Pointless Corp',NULL,'<p>We relaunched the Pointless Corp website, which is the innovation lab at Viget. The site is now a place for us to showcase side projects and experiments. My role was the build the site and implement with Craft.</p>','http://pointlesscorp.com/',0,NULL,'2025-01-03 19:02:21','2025-01-03 19:02:21','6e92ef57-7c26-402b-bef8-56c2e6f211b7',1,NULL,NULL,NULL,NULL),(2118,4604,'Lully Sleep',NULL,NULL,NULL,0,NULL,'2025-01-03 19:02:21','2025-01-03 19:02:21','1f066be8-19c9-40f3-bd15-56a4e8ba081b',1,NULL,NULL,NULL,NULL),(2120,4606,'Bethesda Creation Club',NULL,NULL,NULL,0,NULL,'2025-01-03 19:02:52','2025-01-03 19:02:52','b533aa02-5f64-4bea-a68b-6fb3081f3545',1,NULL,NULL,NULL,NULL),(2121,4607,'position: sticky and 100vh',NULL,NULL,NULL,0,NULL,'2025-01-03 19:42:18','2025-01-03 19:42:46','89dfc9cc-6ad4-4fa1-bc2f-5f52a9114532',1,NULL,NULL,0,NULL),(2122,4608,'position: sticky and 100vh',NULL,NULL,NULL,0,NULL,'2025-01-03 19:42:46','2025-01-03 19:42:46','a7bbcbd6-9a2d-4293-b9f2-a03d9f5e9fc7',1,NULL,NULL,0,NULL),(2123,4609,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-01-18 16:07:21','2025-01-18 16:07:21','b4a29365-3649-4453-bc80-b645be493df8',1,NULL,NULL,30,NULL),(2124,4617,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-01-18 16:07:22','2025-01-18 16:07:22','00834eb1-1a33-4f33-980b-34569ab309ae',1,NULL,NULL,29,NULL),(2125,4625,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-01-18 16:07:31','2025-01-18 16:07:31','167c259b-9c3e-4ad5-bf68-07ca980c7958',1,NULL,NULL,30,NULL),(2126,4633,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-01-18 16:07:32','2025-01-18 16:07:32','254f16f1-3931-4637-a204-79d8543b1183',1,NULL,NULL,29,NULL),(2127,4641,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-01-18 16:07:35','2025-01-18 16:07:35','c8748f7a-6c91-40b5-bf96-179085e5ffda',1,NULL,NULL,30,NULL),(2128,4649,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-01-18 16:07:36','2025-01-18 16:07:36','8f3e3a41-f6df-4cd6-8b8e-907badb02c04',1,NULL,NULL,29,NULL),(2129,4657,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-02-06 09:59:23','2025-02-06 09:59:23','f92c6776-3924-41bf-bd63-bfecf72b97cf',1,NULL,NULL,30,NULL),(2130,4665,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-02-06 09:59:24','2025-02-06 09:59:24','509c6f24-75e1-4dac-b70b-ef3ac17ed93f',1,NULL,NULL,29,NULL),(2131,4673,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-02-06 09:59:25','2025-02-06 09:59:25','ce5e779d-b733-428f-8bbf-4ae2f44fd58e',1,NULL,NULL,30,NULL),(2132,4681,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-02-06 09:59:26','2025-02-06 09:59:26','cdbff251-4d2c-4b83-ba15-5fb56ea8af12',1,NULL,NULL,29,NULL),(2133,4689,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-02-06 09:59:27','2025-02-06 09:59:27','de0efd81-f845-4c04-91af-3c90a73d323d',1,NULL,NULL,30,NULL),(2134,4697,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-02-06 09:59:28','2025-02-06 09:59:28','bb4cb390-9dce-4f59-be75-4fd62ff546ee',1,NULL,NULL,29,NULL),(2135,4705,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-02-06 09:59:31','2025-02-06 09:59:31','5ed91785-9fc3-4a64-8d04-2c31b4ee4068',1,NULL,NULL,30,NULL),(2136,4713,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-02-22 15:01:04','2025-02-22 15:01:04','63355e87-11de-4c14-8f0d-16c29012417b',1,NULL,NULL,31,NULL),(2137,4721,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-02-22 15:01:06','2025-02-22 15:01:06','fe57ea25-d080-4467-ac2d-97f5006af202',1,NULL,NULL,30,NULL),(2138,4729,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-03-12 11:27:25','2025-03-12 11:27:25','3c8dc9f6-66ed-48a2-8e5f-d0d4a9d318e1',1,NULL,NULL,31,NULL),(2139,4737,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-03-12 11:27:27','2025-03-12 11:27:27','3df5e794-337d-45d5-847d-df322024f817',1,NULL,NULL,30,NULL),(2140,4745,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-03-27 13:11:53','2025-03-27 13:11:53','417c6382-7298-4b54-97e6-5beda9147bb5',1,NULL,NULL,31,NULL),(2141,4753,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-03-27 13:11:54','2025-03-27 13:11:54','1ea5a2f2-dc3b-4ab5-ba9c-414f4e06f2f9',1,NULL,NULL,30,NULL),(2142,4761,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-03-27 13:12:00','2025-03-27 13:12:00','d10d70a7-d30d-4c54-a419-3f345158aac8',1,NULL,NULL,31,NULL),(2143,4769,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-03-27 13:12:14','2025-03-27 13:12:14','4d3b0dad-7a50-4411-8bfb-31f89691f6ae',1,NULL,NULL,31,NULL),(2144,4777,'Using localStorage in Remix',NULL,'<p>I wanted to get some more experience working with GraphQL mutations, so I worked on a new feature for my site that enabled a user to “like” a blog article. I ran into a little gotcha that is kind of obvious now that I know the solution but wasn’t obvious at the time.</p>\r\n<p><video src=\"{asset:2490:url||//assets.trevor-davis.com/uploads/images/content/like-component.mp4}\" autoplay=\"\" loop=\"\" muted=\"\" controls=\"\" playsinline></video></p>\r\n<p>I didn’t think I needed to be fancy and store the “like” in the database and associate with the user, so I decided that tracking whether the user had liked the article in <code>localStorage</code> was enough and then keep track of the total article like count in the database. Seems easy enough right?</p>\r\n<h2>The Problem</h2>\r\n<p>This was my first approach in retrieving whether or not the user had liked the article from <code>localStorage</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/c5903d524119c16448dbb56e2ac65701.js\"></script>\r\n<p>And this is the error that I was getting:</p>\r\n<pre>ReferenceError: window is not defined</pre>\r\n<h2>The Fix</h2>\r\n<p>At first I was &#x1f914;, but the fix is super simple and totally makes sense. Remix renders on the server, but I wanted to run this in the browser, so I simply needed to wrap it in a <code>useEffect</code>:</p>\r\n<script src=\"https://gist.github.com/davist11/f1ac5d35cde425d875d477068203996e.js\"></script>\r\n<p>This is actually captured in the <a href=\"https://remix.run/docs/en/v1/guides/constraints#rendering-with-browser-only-apis\">Remix docs</a>, but it was kind of buried.</p>',NULL,0,NULL,'2025-03-27 13:12:16','2025-03-27 13:12:16','3083bbf9-4709-44cf-94f3-470e518451f6',1,NULL,NULL,30,NULL),(2145,4785,'Contact','So you want to get in touch?',NULL,NULL,NULL,NULL,'2025-04-18 14:59:28','2025-04-18 14:59:28','62579baa-fc14-4f0f-b8a7-e5f293abec10',1,NULL,NULL,NULL,NULL);
commit;

--
