<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
	>

<channel>
	<title>ChrisLo.ca</title>
	<atom:link href="https://chrislo.ca/feed/" rel="self" type="application/rss+xml" />
	<link>https://chrislo.ca</link>
	<description>a software engineer that writes a blog post very infrequently</description>
	<lastBuildDate>Fri, 31 Jan 2025 23:39:07 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.7.10</generator>
<site xmlns="com-wordpress:feed-additions:1">24211816</site>	<item>
		<title>NPM install lifecycle scripts (preinstall, install, postinstall) not being called for your package?</title>
		<link>https://chrislo.ca/npm-install-lifecycle-scripts-preinstall-install-postinstall-not-being-called-for-your-package/</link>
					<comments>https://chrislo.ca/npm-install-lifecycle-scripts-preinstall-install-postinstall-not-being-called-for-your-package/#respond</comments>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Fri, 31 Jan 2025 22:32:26 +0000</pubDate>
				<category><![CDATA[Dev]]></category>
		<category><![CDATA[npm]]></category>
		<guid isPermaLink="false">https://chrislo.ca/?p=3360</guid>

					<description><![CDATA[<p>If you just added one of these lifecycle scripts to your package and published the changes to a registry:</p>



<p>In <code>package-lock.json</code> of the project that consumes your package as a dependency, ensure your package has <code>"hasInstallScript": true</code>.  On <code>npm ci</code> or <code>npm install</code>, <code>npm</code> looks for the existence of this flag in <code>package-lock.json</code> rather than scanning each library&#8230; <a href="https://chrislo.ca/npm-install-lifecycle-scripts-preinstall-install-postinstall-not-being-called-for-your-package/" class="read-more read-more-link btn btn-outline-secondary">Read more</a></p>]]></description>
										<content:encoded><![CDATA[
<p>If you just added one of these lifecycle scripts to your package and published the changes to a registry:</p>



<p>In <code>package-lock.json</code> of the project that consumes your package as a dependency, ensure your package has <code>"hasInstallScript": true</code>.  On <code>npm ci</code> or <code>npm install</code>, <code>npm</code> looks for the existence of this flag in <code>package-lock.json</code> rather than scanning each library&#8217;s <code>package.json</code> for &#8220;preinstall&#8221;, &#8220;install&#8221;, or &#8220;postinstall&#8221; scripts.</p>



<p>If your package is part of an organization, then it may be due to this bug: <a href="https://github.com/npm/cli/issues/4821">https://github.com/npm/cli/issues/4821</a></p>



<p>Or this bug: <a href="https://github.com/npm/cli/issues/5380">https://github.com/npm/cli/issues/5380</a></p>



<p>For what it&#8217;s worth, I am using both a custom registry with packages under an organization.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://chrislo.ca/npm-install-lifecycle-scripts-preinstall-install-postinstall-not-being-called-for-your-package/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3360</post-id>	</item>
		<item>
		<title>DeepL Translator Browser Extension/Add-on (en)</title>
		<link>https://chrislo.ca/deepl/</link>
					<comments>https://chrislo.ca/deepl/#comments</comments>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Sun, 29 Nov 2020 22:58:49 +0000</pubDate>
				<category><![CDATA[Dev]]></category>
		<category><![CDATA[Images]]></category>
		<category><![CDATA[My Work]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[add-on]]></category>
		<category><![CDATA[brave]]></category>
		<category><![CDATA[chrome]]></category>
		<category><![CDATA[extension]]></category>
		<category><![CDATA[FireFox]]></category>
		<guid isPermaLink="false">http://chrislo.ca/?p=3261</guid>

					<description><![CDATA[<p>Thank you for using or your interest in the DeepL Translator Browser Extension/Add-on!</p>



<p>If you have a question or feedback, please post it in the comment section below.</p>



<p>If you like this extension, please <a href="https://codeforthought.com">support my other work</a>.</p>



<p>Support <a href="https://www.deepl.com/">DeepL</a> as well for providing us a great alternative to Google Translate. I am not affiliated in any way with DeepL.</p>



<h2 class="wp-block-heading">Download Lin</h2>&#8230; <a href="https://chrislo.ca/deepl/" class="read-more read-more-link btn btn-outline-secondary">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p>Thank you for using or your interest in the DeepL Translator Browser Extension/Add-on!</p>



<p>If you have a question or feedback, please post it in the comment section below.</p>



<p>If you like this extension, please <a href="https://codeforthought.com">support my other work</a>.</p>



<p>Support <a href="https://www.deepl.com/">DeepL</a> as well for providing us a great alternative to Google Translate. I am not affiliated in any way with DeepL.</p>



<h2 class="wp-block-heading">Download Links</h2>



<p>For the Brave/Chrome extension, click here.</p>



<p>For the FireFox add-on, click <a href="https://addons.mozilla.org/en-US/firefox/addon/deepl-translator-hover/">here</a>.</p>



<h2 class="wp-block-heading">Frequently Asked Questions (FAQ)</h2>



<p>Nothing yet!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://chrislo.ca/deepl/feed/</wfw:commentRss>
			<slash:comments>21</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3261</post-id>	</item>
		<item>
		<title>Ionic 5 + React: positioning a popover relative to the clicked element</title>
		<link>https://chrislo.ca/ionic-5-react-positioning-a-popover-relative-to-the-clicked-element/</link>
					<comments>https://chrislo.ca/ionic-5-react-positioning-a-popover-relative-to-the-clicked-element/#respond</comments>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Tue, 04 Aug 2020 22:37:02 +0000</pubDate>
				<category><![CDATA[Dev]]></category>
		<category><![CDATA[Ionic 5]]></category>
		<category><![CDATA[React]]></category>
		<guid isPermaLink="false">http://chrislo.ca/?p=3238</guid>

					<description><![CDATA[<p>In the <a href="https://ionicframework.com/docs/api/popover">documentation</a>, the requirement to have an Ionic 5 popover present itself beside a clicked element that activates the popover, you need to pass the mouse event to it.</p>
<p>To do so, you must assign the <code>event</code> prop of the <code>IonPopover</code> with the event value.</p>
<p>However, since React also nullifies all event properties due to <a href="https://reactjs.org/docs/events.html#event-pooling">Event Pooling</a> and Ionic is looking for the eve&#8230; <a href="https://chrislo.ca/ionic-5-react-positioning-a-popover-relative-to-the-clicked-element/" class="read-more read-more-link btn btn-outline-secondary">Read more</a></p>]]></description>
										<content:encoded><![CDATA[<p>In the <a href="https://ionicframework.com/docs/api/popover">documentation</a>, the requirement to have an Ionic 5 popover present itself beside a clicked element that activates the popover, you need to pass the mouse event to it.</p>
<p>To do so, you must assign the <code>event</code> prop of the <code>IonPopover</code> with the event value.</p>
<p>However, since React also nullifies all event properties due to <a href="https://reactjs.org/docs/events.html#event-pooling">Event Pooling</a> and Ionic is looking for the event target to help position the IonPopover, you will first have to persist the event.</p>
<p>Extending the <a href="https://ionicframework.com/docs/api/popover">documentation</a>&#8216;s example to enable relative positioning of the popover in React, note the differences:</p>
<ol>
<li><code>showPopoverEvent</code> local state defaults to <code>null</code>, and will hold an event object.</li>
<li><code>isOpen</code> still expects a boolean, and is true if showPopoverEvent is not empty.</li>
<li>IonButton <code>onClick</code> must call persist on the event, and we set the local state to store the event.</li>
<li>Finally, the event prop on IonPopover is set to the event stored in the local state.</li>
</ol>
<pre class="brush: jscript; title: ; notranslate">
import React, { useState } from 'react';
import { IonPopover, IonButton } from '@ionic/react';

export const PopoverExample: React.FC = () =&gt; {
  const [showPopoverEvent, setShowPopoverEvent] = useState(null);

  return (
    &lt;&gt;
      &lt;IonPopover
        isOpen={!!showPopoverEvent}
        onDidDismiss={() =&gt; setShowPopoverEvent(null)}
        event={showPopoverEvent}
      &gt;
        &lt;p&gt;This is popover content&lt;/p&gt;
      &lt;/IonPopover&gt;
      &lt;IonButton onClick={(e) =&gt; { e.persist(); setShowPopoverEvent(e); }}&gt;
        Show Popover
      &lt;/IonButton&gt;
    &lt;/&gt;
  );
};
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://chrislo.ca/ionic-5-react-positioning-a-popover-relative-to-the-clicked-element/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3238</post-id>	</item>
		<item>
		<title>Ionic 5: animated collapsible searchbar using only SCSS</title>
		<link>https://chrislo.ca/ionic-5-animated-collapsible-searchbar-using-only-scss/</link>
					<comments>https://chrislo.ca/ionic-5-animated-collapsible-searchbar-using-only-scss/#respond</comments>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Thu, 02 Jul 2020 20:45:52 +0000</pubDate>
				<category><![CDATA[Dev]]></category>
		<category><![CDATA[Ionic 5]]></category>
		<guid isPermaLink="false">http://chrislo.ca/?p=3225</guid>

					<description><![CDATA[<p>Add this to your global.scss of your Ionic 5 project:</p>
<pre class="brush: css; title: ; notranslate">
ion-searchbar.collapsible {
  width: 55px; // match hard-coded padding-inline-end when collapsed
               // .searchbar-input {
               //   @include padding(6px, 55px);
               // }

  transition: width 0.5s ease-in-out;
  --border-radius: 4px;

  .searchbar-input.sc-ion-searchbar-md {
    transition-property: padding-inli</pre>&#8230; <a href="https://chrislo.ca/ionic-5-animated-collapsible-searchbar-using-only-scss/" class="read-more read-more-link btn btn-outline-secondary">Read more</a>]]></description>
										<content:encoded><![CDATA[<p>Add this to your global.scss of your Ionic 5 project:</p>
<pre class="brush: css; title: ; notranslate">
ion-searchbar.collapsible {
  width: 55px; // match hard-coded padding-inline-end when collapsed
               // .searchbar-input {
               //   @include padding(6px, 55px);
               // }

  transition: width 0.5s ease-in-out;
  --border-radius: 4px;

  .searchbar-input.sc-ion-searchbar-md {
    transition-property: padding-inline-end, -webkit-padding-end;
    transition-duration: 0.5s;
    transition-timing-function: ease-in-out;
  }

  &amp;.searchbar-has-focus,
  &amp;.searchbar-has-value {
    width: 100%;
  }

  &amp;:not(.searchbar-has-focus):not(.searchbar-has-value):hover input {
    cursor: pointer;
    background: var(--ion-color-step-100);
  }

  &amp;:not(.searchbar-has-focus),
  &amp;:not(.searchbar-has-value) {
    .searchbar-input.sc-ion-searchbar-md {
      -webkit-padding-end: 0;
      padding-inline-end: 0;
    }
  }
}
</pre>
<p>Then add the class <code>collapsible</code> to your <code>ion-searchbar</code>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://chrislo.ca/ionic-5-animated-collapsible-searchbar-using-only-scss/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3225</post-id>	</item>
		<item>
		<title>Ionic 5 + Angular: custom Alert enter and leave animations</title>
		<link>https://chrislo.ca/ionic-5-angular-custom-alert-enter-and-leave-animations/</link>
					<comments>https://chrislo.ca/ionic-5-angular-custom-alert-enter-and-leave-animations/#respond</comments>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Fri, 19 Jun 2020 19:04:33 +0000</pubDate>
				<category><![CDATA[Dev]]></category>
		<category><![CDATA[Ionic 5]]></category>
		<guid isPermaLink="false">http://chrislo.ca/?p=3220</guid>

					<description><![CDATA[<p>Since it&#8217;s hard to find an example in the documentation and elsewhere on the Internet, here&#8217;s a simple implemention of a custom fade-in animation for Ionic 5 alerts with Angular.  Note that we choose what elements of an ion-alert to animate by using querySelector on HTMLElement <code>baseEl</code>.  The div with class <code>alert-wrapper</code> is the dialog, but you can &#8230; <a href="https://chrislo.ca/ionic-5-angular-custom-alert-enter-and-leave-animations/" class="read-more read-more-link btn btn-outline-secondary">Read more</a></p>]]></description>
										<content:encoded><![CDATA[<p>Since it&#8217;s hard to find an example in the documentation and elsewhere on the Internet, here&#8217;s a simple implemention of a custom fade-in animation for Ionic 5 alerts with Angular.  Note that we choose what elements of an ion-alert to animate by using querySelector on HTMLElement <code>baseEl</code>.  The div with class <code>alert-wrapper</code> is the dialog, but you can also query select <code>ion-backdrop</code> for the backdrop.</p>
<pre class="brush: jscript; title: ; notranslate">
constructor(
  private alertCtrl: AlertController,
  private animationCtrl: AnimationController
) {

}

private async showAlert() {
  const alert = await this.alertCtrl.create({
    message: 'Hello world',
    enterAnimation: (baseEl: any, opts?: any) =&gt; {
      return(this.animationCtrl
        .create()
        .addElement(baseEl.querySelector('.alert-wrapper'))
        .duration(250)
        .keyframes([
          { offset: 0, opacity: '0' },
          { offset: 1, opacity: '1' }
        ]));
    }
  });

  await alert.present();
}
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://chrislo.ca/ionic-5-angular-custom-alert-enter-and-leave-animations/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3220</post-id>	</item>
		<item>
		<title>How to peruse Quora.com answers and links without signing in or creating an account</title>
		<link>https://chrislo.ca/how-to-peruse-quora-com-answers-and-links-without-signing-in-or-creating-an-account/</link>
					<comments>https://chrislo.ca/how-to-peruse-quora-com-answers-and-links-without-signing-in-or-creating-an-account/#respond</comments>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Sat, 21 Mar 2020 13:23:34 +0000</pubDate>
				<category><![CDATA[Images]]></category>
		<category><![CDATA[Technology]]></category>
		<category><![CDATA[lifehacks]]></category>
		<guid isPermaLink="false">http://chrislo.ca/?p=3207</guid>

					<description><![CDATA[<p>To dodge sending a Quora.com referring URL when following Quora links, simply:</p>
<ol>
<li><strong>Right-click the link you want to follow</strong></li>
<li><strong>&#8220;Copy link location&#8221; (FireFox) </strong>or<strong> &#8220;Copy Link URL&#8221; (Chrome)</strong></li>
<li><strong>Right-click the address bar</strong></li>
<li><strong>&#8220;Paste &#38; Go&#8221; (FireFox) </strong>or<strong>  &#8220;Paste and go to &#8230;&#8221; (Chrome)</strong></li>
</ol>
<p>That&#8217;s it, and now you&#8230; <a href="https://chrislo.ca/how-to-peruse-quora-com-answers-and-links-without-signing-in-or-creating-an-account/" class="read-more read-more-link btn btn-outline-secondary">Read more</a></p>]]></description>
										<content:encoded><![CDATA[<p>To dodge sending a Quora.com referring URL when following Quora links, simply:</p>
<ol>
<li><strong>Right-click the link you want to follow</strong></li>
<li><strong>&#8220;Copy link location&#8221; (FireFox) </strong>or<strong> &#8220;Copy Link URL&#8221; (Chrome)</strong></li>
<li><strong>Right-click the address bar</strong></li>
<li><strong>&#8220;Paste &amp; Go&#8221; (FireFox) </strong>or<strong>  &#8220;Paste and go to &#8230;&#8221; (Chrome)</strong></li>
</ol>
<p>That&#8217;s it, and now you&#8217;ve learned two context menu shortcuts in your web browser as well.</p>
<p style="text-align: center;">*     *     *</p>
<p>Why does it work?</p>
<p>Well you may have figured that out by reading another 2013 blog post (appears currently as a high ranked search result) that is technically correct saying that Quora.com tracks referring URLs.  If you were referred to the current Quora page from another Quora page, it will present you with a sign-in/create account overlay. That 2013 blog post provides a woefully unnecessary solution of using a plug-in to get around the problem.</p>
<p>If you directly enter the URL into the address bar, no referral URL is provided in the header sent to Quora by the browser.  This principle applies to anything that tracks by referring URL.  If you are super-paranoid and don&#8217;t want your browsing habits to be tracked, especially by search engines, you may want to start copying URLs from your search results and pasting them directly into the browser address bar.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://chrislo.ca/how-to-peruse-quora-com-answers-and-links-without-signing-in-or-creating-an-account/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3207</post-id>	</item>
		<item>
		<title>Backing up your Gitea Docker Installation</title>
		<link>https://chrislo.ca/backing-up-your-gitea-docker-installation/</link>
					<comments>https://chrislo.ca/backing-up-your-gitea-docker-installation/#respond</comments>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Thu, 05 Sep 2019 14:02:08 +0000</pubDate>
				<category><![CDATA[Dev]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[gitea]]></category>
		<guid isPermaLink="false">http://chrislo.ca/?p=3136</guid>

					<description><![CDATA[<p><strong>TL;DR:</strong><br />
<code class="language-none">docker exec -u git -it -w /data/gitea/backup $(docker ps -qf "name=gitea_server_1") bash -c '/app/gitea/gitea dump -c /data/gitea/conf/app.ini'</code><br />
The dump will be found in ./gitea/gitea/backup relative to directory containing your docker-compose.yml. You should create this &#8220;backup&#8221; directory first otherwise you will get a &#&#8230; <a href="https://chrislo.ca/backing-up-your-gitea-docker-installation/" class="read-more read-more-link btn btn-outline-secondary">Read more</a></p>]]></description>
										<content:encoded><![CDATA[<p><strong>TL;DR:</strong><br />
<code class="language-none">docker exec -u git -it -w /data/gitea/backup $(docker ps -qf "name=gitea_server_1") bash -c '/app/gitea/gitea dump -c /data/gitea/conf/app.ini'</code><br />
The dump will be found in ./gitea/gitea/backup relative to directory containing your docker-compose.yml. You should create this &#8220;backup&#8221; directory first otherwise you will get a &#8220;no such directory&#8221; error.</p>
<p>&nbsp;</p>
<p><strong>Long version</strong><br />
How we arrived at the above command via Gitea&#8217;s <a href="https://docs.gitea.io/en-us/backup-and-restore/">docs</a>:</p>
<p><code class="language-none">docker exec -u &lt;OS_USERNAME&gt; -it -w &lt;--tempdir&gt; $(docker ps -qf "name=&lt;NAME_OF_DOCKER_CONTAINER&gt;") bash -c '/app/gitea/gitea dump -c &lt;/path/to/app.ini&gt;'</code></p>
<p>Here&#8217;s what you should replace each &lt;&#8230;&gt; with, assuming you installed from Docker following the instructions <a href="https://docs.gitea.io/en-us/install-with-docker/">here</a> (note where docker mounts the container&#8217;s &#8220;data&#8221; folder is found under &#8220;services:&#8221;/&#8221;server:&#8221;/&#8221;volumes:&#8221;, it is likely the path <code>./gitea</code>, relative to where your docker-compose.yml resides):</p>
<p><strong>&lt;OS_USERNAME&gt;: </strong>Look inside <code>gitea/conf/app.ini</code> in the folder where docker mounts its &#8220;data&#8221; folder (<code>./gitea/gitea/conf/app.ini</code> relative to where your docker-compose.yml resides). It is the value matching <strong>RUN_USER</strong>, and this is usually <span style="text-decoration: underline;"><strong>git</strong></span> unless you changed it.</p>
<p><strong>&lt;&#8211;tempdir&gt;</strong>: Leaving it as is will store it in <code>/tmp</code> in the container, which is not mounted to a local folder.  Create a &#8220;backup&#8221; directory <code>./gitea/gitea/backup</code> relative to where your docker-compose.xml resides. Replace &lt;&#8211;tempdir&gt; with <code>/data/gitea/backup</code>, as this is a path referenced in the container.</p>
<p><strong>&lt;NAME_OF_DOCKER_CONTAINER&gt;</strong>: run <code>docker ps</code> and note the NAME (last column) containing gitea.  This is usually &#8220;gitea_server_1&#8221;.</p>
<p><strong>&lt;/path/to/app.ini&gt;</strong>: This is a path referenced in the container, so it needs to use <code>/data/gitea/conf/app.ini</code> (i.e. this points to the local <code>./gitea/gitea/conf/app.ini</code> relative to where your docker-compose.yml resides).</p>
]]></content:encoded>
					
					<wfw:commentRss>https://chrislo.ca/backing-up-your-gitea-docker-installation/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3136</post-id>	</item>
		<item>
		<title>Start a new Meetup group: 30% off?  Why not 100% off?! (Casual Events)</title>
		<link>https://chrislo.ca/start-a-new-meetup-group-30-off-why-not-100-off/</link>
					<comments>https://chrislo.ca/start-a-new-meetup-group-30-off-why-not-100-off/#respond</comments>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Tue, 20 Aug 2019 13:48:35 +0000</pubDate>
				<category><![CDATA[Images]]></category>
		<category><![CDATA[My Work]]></category>
		<category><![CDATA[casual events]]></category>
		<category><![CDATA[meetup alternative]]></category>
		<guid isPermaLink="false">http://chrislo.ca/?p=3129</guid>

					<description><![CDATA[<p>Meetup has a &#8220;sale&#8221; going on: 30% off new groups!</p>
<p>Wait &#8230; why should it cost you anything at all if you will be doing all the heavy lifting and event organizing?</p>
<p>You&#8217;re already volunteering your time and energy. Don&#8217;t volunteer more money than you need to too.</p>
<p>Help spread the word about <a href="https://casual.events">Casual Events — an event platform that is</a>&#8230; <a href="https://chrislo.ca/start-a-new-meetup-group-30-off-why-not-100-off/" class="read-more read-more-link btn btn-outline-secondary">Read more</a></p>]]></description>
										<content:encoded><![CDATA[<p>Meetup has a &#8220;sale&#8221; going on: 30% off new groups!</p>
<p>Wait &#8230; why should it cost you anything at all if you will be doing all the heavy lifting and event organizing?</p>
<p>You&#8217;re already volunteering your time and energy. Don&#8217;t volunteer more money than you need to too.</p>
<p>Help spread the word about <a href="https://casual.events">Casual Events — an event platform that is <strong>free for</strong> <strong>everybody</strong></a>.  <em>(Except me of course, the developer. I still have to pay server costs and invest hundreds of hours of my labour. But if you join will be worth it!)</em></p>
]]></content:encoded>
					
					<wfw:commentRss>https://chrislo.ca/start-a-new-meetup-group-30-off-why-not-100-off/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3129</post-id>	</item>
		<item>
		<title>Navigating to a specific URL in your Angular PWA if its service worker is not yet registered</title>
		<link>https://chrislo.ca/navigating-to-a-specific-url-in-your-angular-pwa-if-its-service-worker-is-not-yet-registered/</link>
					<comments>https://chrislo.ca/navigating-to-a-specific-url-in-your-angular-pwa-if-its-service-worker-is-not-yet-registered/#comments</comments>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Tue, 16 Jul 2019 21:43:52 +0000</pubDate>
				<category><![CDATA[Dev]]></category>
		<category><![CDATA[Images]]></category>
		<category><![CDATA[Angular]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[Nginx]]></category>
		<guid isPermaLink="false">http://chrislo.ca/?p=3097</guid>

					<description><![CDATA[<p>If you host an Angular Progressive Web App (PWA) on a web server, you will frequently have URLs that are handled by the Angular router to navigate to specific pages within the app.</p>
<p>However, without the service worker registered, navigation requests are not intercepted and sent to the PWA. In other words, if a user has never ran the PWA yet in his or her browser, a&#8230; <a href="https://chrislo.ca/navigating-to-a-specific-url-in-your-angular-pwa-if-its-service-worker-is-not-yet-registered/" class="read-more read-more-link btn btn-outline-secondary">Read more</a></p>]]></description>
										<content:encoded><![CDATA[<p>If you host an Angular Progressive Web App (PWA) on a web server, you will frequently have URLs that are handled by the Angular router to navigate to specific pages within the app.</p>
<p>However, without the service worker registered, navigation requests are not intercepted and sent to the PWA. In other words, if a user has never ran the PWA yet in his or her browser, and he or she tries to navigate to a specific page in the PWA (e.g. https://mywebapp.abc/foo/bar), your web server will likely return a 404 error. We would like to serve the PWA first, then relay the <code>/foo/bar</code> path to the Angular router.</p>
<h2>Nginx config solution</h2>
<p>If you have access and can configure the static serving of files on your web server, you will want to use the <code>try_files</code> directive:</p>
<pre class="brush: plain; title: ; notranslate">
location / {
  try_files $uri $uri/ /index.html;
}
</pre>
<p>Using this directive, Nginx will attempt to serve the static file based on the URI path (e.g. /images/foo.png will load correctly), but in all other instances where it cannot find a static file to serve if it is instead a PWA Angular route (e.g. /foo/bar), it will serve index.html of your PWA. This will eliminate <strong>all</strong> 404 errors and will serve up the PWA entry page, retaining the &#8220;404&#8221; URL in the browser (/foo/bar) so the Angular router can handle it. </p>
<p>The downside to this is that all legitimate 404 errors now always serve the PWA&#8217;s index.html, which may not be desirable. Say your server was actually missing /images/foo.png, then it will instead serve index.html in its place rather than returning a 404.</p>
<h2>404 page solution</h2>
<p>Another possible solution is to create a custom 404 page containing the following Javascript that accomplishes three things:</p>
<ol>
<li>Checks if there is a <code>ngsw-worker.js</code> to register.</li>
<li>Loads the PWA in an iframe, so that the service worker is installed.</li>
<li>Once the iframe is done loading, refresh the page with the intended URL. The PWA router will now pick up the navigation request to <code>/foo/bar</code> and transition to the correct page after load.</li>
</ol>
<pre class="brush: jscript; title: ; notranslate">
      var request = new XMLHttpRequest();  

      request.open('GET', '/ngsw-worker.js', true);
      request.onreadystatechange = function(){
        if (request.readyState === 4){
          if (request.status === 200) {
            var iframe = document.createElement('iframe');

            document.body.append(iframe);

            iframe.style.setProperty('position', 'fixed');
            iframe.style.setProperty('left', '0');
            iframe.style.setProperty('top', '0');
            iframe.style.setProperty('width', '100%');
            iframe.style.setProperty('height', '100%');
            iframe.style.setProperty('border', '0');
            iframe.style.setProperty('background-color', '#f7f7f7');

            iframe.src = '/';
            iframe.onload = function() {
              location.reload();
            };
          }  
        }
      };
      request.send();
</pre>
<p>As of the time of this writing, this alternate solution does not work with FireFox but works well in Chrome.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://chrislo.ca/navigating-to-a-specific-url-in-your-angular-pwa-if-its-service-worker-is-not-yet-registered/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3097</post-id>	</item>
		<item>
		<title>Casual Events: how to host meetups for free (even if your group exceeds 50 members)</title>
		<link>https://chrislo.ca/casual-events-free-for-all-event-organizers-and-groups-over-50-members/</link>
					<comments>https://chrislo.ca/casual-events-free-for-all-event-organizers-and-groups-over-50-members/#respond</comments>
		
		<dc:creator><![CDATA[Chris]]></dc:creator>
		<pubDate>Thu, 04 Jul 2019 16:21:07 +0000</pubDate>
				<category><![CDATA[Images]]></category>
		<category><![CDATA[My Work]]></category>
		<category><![CDATA[casual events]]></category>
		<category><![CDATA[meetup alternative]]></category>
		<guid isPermaLink="false">http://chrislo.ca/?p=3084</guid>

					<description><![CDATA[<p>If you&#8217;re looking for an alternative to Meetup (meetup.com) where you don&#8217;t have to pay a hefty monthly or semi-annual fee just to host events, refer your group to <a href="https://casual.events">Casual Events</a>!</p>
<p><a href="https://casual.events">Casual Events</a> is a new platform that needs your help to grow. Since it is maintained by one person (me) you don&#8217;t have to subsidize conglomerate salaries to use th&#8230; <a href="https://chrislo.ca/casual-events-free-for-all-event-organizers-and-groups-over-50-members/" class="read-more read-more-link btn btn-outline-secondary">Read more</a></p>]]></description>
										<content:encoded><![CDATA[<p>If you&#8217;re looking for an alternative to Meetup (meetup.com) where you don&#8217;t have to pay a hefty monthly or semi-annual fee just to host events, refer your group to <a href="https://casual.events">Casual Events</a>!</p>
<p><a href="https://casual.events">Casual Events</a> is a new platform that needs your help to grow. Since it is maintained by one person (me) you don&#8217;t have to subsidize conglomerate salaries to use the platform. Additionally, you&#8217;ll have the opportunity to have the app tailored to your needs since the platform is young and you will have direct one-on-one access with the developer for future features and fixes.</p>
<p style="text-align: center;"><a href="https://casual.events"><img decoding="async" class="aligncenter size-full wp-image-3088" src="http://chrislo.ca/wp-content/uploads/2019/07/casual-events.png" alt="" width="252" height="61" /></a></p>
<p>Nearly everything you do with Meetup can be done with Casual Events. We just need to spread the word!  You can access the app on your <a href="https://itunes.apple.com/us/app/casual-events/id1465176926">iPhone</a>, <a href="https://play.google.com/store/apps/details?id=events.casual.app">Android phone</a> or on any other device or desktop via the <a href="https://app.casual.events">web app</a>.</p>
<p>Best of all, the platform is ethical — something you won&#8217;t find with big tech and Silicon Valley. The site and ads don&#8217;t track you with cookies. It is purposely dissociated from social media so that your privacy is protected: no data mining, no invasive snooping, no selling your habits to third parties, no Big Brother.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://chrislo.ca/casual-events-free-for-all-event-organizers-and-groups-over-50-members/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3084</post-id>	</item>
	</channel>
</rss>
