Today I Learned...

#16   JavaScript: native Date formatting

Posted: April 13, 2021

I needed to format a date from new Date() recently and using any libs (like date-fns or moment.js) and was not an option; doing that would have been overkill for the small project I was working on.

After a bit of research, I came across the Date.prototype.toLocaleDateString method and implemented it like this:

const getDate = () => {
let date = new Date();

let dateOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'

return date.toLocaleDateString('en-US', dateOptions);

Result: Tuesday, April 13, 2021

Source: Natively Format JavaScript Dates and Times

#15   CSS: scroll-behavior

Posted: April 05, 2021

I was aware of the existence of the scroll-behavior attribute, but I never really used it so far (‘thanks’ to jQuery, scrolling libs or framework internals handling it themselves).

When added to a site (like this one), it’ll make all your anchor-based navigation feel buttery smooth - who wouldn’t want that?

scroll-behavior: smooth;
/* etc. */

And that’s it. This one line covers everything: from simple “back to top” links to anchor-tag navigation across pages - they’ll all be smooth now.

Browser support: caniuse/css-scroll-behavior

PS: you can try how it feels right away; just click on any of the #-prefixed anchors in front of the TIL item titles on this page.

#14   GIT: SSH with multiple identities for the same host

Posted: February 08, 2021

Had some issues working with 2 different accounts at GitHub recently.

Initial git clone with the desired identity (specified explicitly) sets up the repository correctly:

git clone -c core.sshCommand="/usr/bin/ssh -i /home/me/.ssh/id_rsa_foo"

GIT config should then contain this automatically:

sshcommand = /usr/bin/ssh -i /home/me/.ssh/id_rsa_foo


#13   Get mobile browsers to display the correct Favicon

Posted: February 07, 2021

Mobile browsers were driving me crazy for a while - they kept choosing the one icon with a solid background (that apple touch thing) over all the other icons.

Looks like the issue was caused by the fact that the lazily copy/pasted code from a very well known Favicon generator did not account for the larger icons in the <head>. When I added the bigger icons there, mobile Chromium-based browsers started correctly displaying the desired icon.

For future reference:

<link rel="manifest" href="/site.webmanifest">
<meta name="theme-color" content="#012B48">

<link rel="icon" type="image/png" sizes="512x512" href="/android-chrome-512x512.png">
<link rel="icon" type="image/png" sizes="192x192" href="/android-chrome-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">

<link rel="apple-touch-icon" sizes="180x180" href="/apple-180x180-solid.png">
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">

<meta name="masapplication-config" content="/browserconfig.xml">

Other findings during the process of researching Favicon details:

  • keep favicon.ico in the site root, no need to reference it in the <head>
  • unless “Add to Homescreen” is used, the icons in site.webmanifest don’t seem to matter
  • mobile browser icon caching is wonky, always test on multiple devices and/or with freshly installed browsers

Good read on this topic: the-essentials-of-favicons

#12   Fauna DB: response size and pagination

Posted: January 25, 2021

Maybe I’m lazy, but I didn’t really feel like implementing proper pagination for DB query results that hardly ever reach the 2 digit KB numbers.

To make sure you’ll get all the queried DB rows in the response, Faunas’s Paginate() function needs a size parameter:

exports.handler = (event, context, callback) => {
// init stuff
return client.query(q.Paginate(q.Match(q.Index('indexName'), 'term'), { size: 500 }))
.then((response) => {
// the rest of your code
}).catch((error) => {
// the rest of your code

Keep in mind that you’ll only get 64 results if this parameter is not specified!

#11   Fauna DB: EPIPE error

Posted: January 25, 2021

Quick note on an eror I got from Fauna DB recently:

FetchError: request to failed, reason: write EPIPE

Apparently that happens due to a known issue related to AWS Lambda connections (which are also used to execute Netlify functions).

To fix that “frozen execution context”, simply change the function to this syntax:

exports.handler = (event, context, callback) => {
const client = new faunadb.Client({
secret: process.env.FAUNA_SECRET
const q = faunadb.query;

// the rest of your code

#10   Vue3: handle page refresh (F5) in your application

Posted: December 01, 2020

I recently noticed that there was an issue with cross device data in WATCH3R. Adding/removing list items on one device would not update the app’s (cached) state on another device. Logging out and back in would resolve this issue, but refreshing the page wouldn’t.

After a bit of research, I found out that there wasn’t really a “best practice” on how to handle page refresh in Vue, so I simply tried some things that seemed promising.

What I ended up with is the following:

const updateList = () => {
if (mode.value) { // double check 'mode' here, just in case
store.dispatch('list/readList', mode.value);

onMounted(() => {
setTimeout(updateList, 250) // timeout is required here; otherwise 'route.meta.mode' is undefined

I placed this code directly in App.vue, which is the entrypoint of my application and gets re-loaded on page refresh. route.meta.mode controls which list’s data to load - I decided against re-loading all list data, as that wouldn’t be in line with the app’s caching strategy.

#9   GoTrue Admin methods

Posted: November 26, 2020

Something I noticed trying to get a list of my app’s users from Netlify’s Identity service.

It’s not a big challenge to write the necessary function (see: GitHub readme), but what it doesn’t mention (clearly enough…) is that this admin function needs to be deployed to your site to work.

I struggled for a bit, looking for mistakes that weren’t there while receiving 401 authorization errors…

NB: deploy the admin function/s, keep the UI to use them offline and disable the function when not needed anymore (I resorted to renaming it function.js.bak just in case I need it again in the future).

Update Dec. 3rd 2020: I created a pull request and the readme (see link above) was updated.

#8   Vue3: <select> default value

Posted: November 06, 2020

Cost me more time than I anticipated, esp. due to the fact that it only happened for production builds.

So, in order for a <select> (with an Object-based v-model) to properly display its default value in Vue 3 (3.0.2), this had to be done:

<select ... v-model.lazy="selected" @change="update(selected)">
<option disabled :value="{}" :selected="selected === {}">Select Something...</option>
<option v-for="(item, index) in list" :key="index" :value="item"></option>

The :selected="selected === {}" was necessary, as the <select> would otherwise display as a blank box once the v-for rendered <option> elements came in.

Again, this only happened in production, local dev builds did not behave that way which made it extremely frustrating to debug.

Probably worthy of further investigation and a report to the Vue team, but I don’t have time to try and get a reproducible example done at the moment.

#7   Vue3: "click outside" directive

Posted: October 10, 2020

Directives are quite different in Vue 3 - see: Vue 3 docs

Here’s what I came up with for a simple “close an element (modal etc.) when a click outside of this element registers” directive. Clicks on elements within the target element and elements with the class click-outside-ignore will be ignored.

The function/method to close the element is registered as binding.value, so when using it on a component, it should look like this: <Component v-click-outside="closeComponent" />

let handleOutsideClick = null;

app.directive('click-outside', {
beforeMount(el, binding, vnode) {
handleOutsideClick = (e) => {
if(!el.contains( && !'click-outside-ignore')) {
document.addEventListener('click', handleOutsideClick);
document.addEventListener('touchstart', handleOutsideClick);
beforeUnmount() {
document.removeEventListener('click', handleOutsideClick);
document.removeEventListener('touchstart', handleOutsideClick);

#6   Hugo: RSS feed duplication

Posted: September 01, 2020

Checking Google Search Console earlier this week made me notice that Hugo created duplicates of my RSS feed in strange places like /tags/feed.xml and /tags/tagname/feed.xml.

After a quick look at the Hugo documentation, I found out that this behaviour seems to be the default for pages of the kind taxonomy and term - seems I forgot about that or never even noticed it in the first place…

Conclusion: I had to change the [outputs] section of my config.toml from this:

home = ["HTML","RSS"]
section = ["HTML"]

To this:

home = ["HTML","RSS"]
section = ["HTML"]
taxonomy = ["HTML"]
term = ["HTML"]

#5   Android: use ADB to uninstall bloatware

Posted: August 14, 2020

I recently got the Android 10 update and had to factory reset my phone thanks to the phone’s language settings not sticking anymore. Factory reset unearthed all the bloatware again that I had gotten rid of years ago, so I also had to do that again.

Here’s a quick reminder of how to do that:

  1. Get ADB platform tools for your OS
  2. On your phone: enable developer mode and USB debudding
  3. Connect phone
  4. Test if connected successfully and allow debugging (will be prompted): adb devices
  5. adb shell to get into the phone
  6. List packages with pm list packages | grep '' or search for them like this: pm list packages -f TESTPKG
  7. Uninstall bloatware like this: pm uninstall -k --user 0
  8. See bloatware disappear 😁

Based on this guide at xda: How to Uninstall Carrier/OEM Bloatware Without Root Access

#4   Shell Script: launch VS Code for a specific folder/repository

Posted: July 24, 2020

I wanted to have an easy way to launch VS Code for specific repositories and came across the code PATH-TO-REPO command.

This little shell script will open a specific folder/repository based on an argument given to it or based on a directory listing of the /home/user/Repos folder:


# get param

if [ -z "$repo" ]
# check first
ls /home/USER/Repos/

# ask user for input
echo -n "which repo: "
read repo
echo "opening "$repo

code /home/USER/Repos/$repo

I’ve also configured an alias for that script; simply typing lcode REPO-NAME now opens VS Code for that folder/repository.

#3   Vue2: handle component's click event in its parent

Posted: July 17, 2020

Use case: a component creates nothing but a <button> with a slot that handles its display state internally but hasn’t got any actual functionality; the handler method is defined in the parent.

In this case, @click.native has to be used to call the method defined in the parent:

// in parent component

<cButton @click.native="theMethod(praram)">Button Name</cButton>

#2   Manjaro Linux mount script: BitLocker encrypted external drive

Posted: July 12, 2020

Got a new external USB 3.0 drive and set up BitLocker encryption. Manjaro (Dolphin) couldn’t really do anything with it, kept prompting for the pwd but didn’t mount it.

I ended up writing my own mount script that works well so far:


# check first
lsblk -f

# ask user for input
echo -n "input details (fstype bitlocker): "
read device
read -sp 'Magic powder: ' mgk

# mount according to user input
sudo fusermount -u /media/data/drive/ # unmount in case it's still mounted for some reason
sudo dislocker -V /dev/$device -u$mgk -- /media/data/drive
sudo mount -o loop /media/data/drive/dislocker-file /media/data/drive-mnt

echo mounted drive

It lists connected block devices first (lsblk), so it’s not hardcoded to sda/sdb in case that ever changes. Need selection of whatever mentions BitLocker, then asks for the encryption key/pwd and proceeds to mount the drive with dislocker. Folders used in /media/data/ need to exist before the script is executed.

#1   Manjaro Linux: .service file error when setting up KeeWeb

Posted: July 10, 2020

Installed KeeWeb on Manjaro and got the following error:

The name org.freedesktop.secrets was not provided by any .service file

Resolution: had to install a package called gnome-keyring.