Notes

A public collection of notes and learnings that might help and inspire others.

Table of Contents   (Skip)

#20   Vue: 'Array.push()' used in Vuex mutation not triggering 'watch()'

Posted:

I was doing this in Vuex:

ADD_RECIPE_USER(state, value) {
state.userRecipes.push(value)
}

It was working fine, but a watch() effect used in a Vue component didn’t catch the update while going back and forth between routes made the updated data appear as if it was always there.

After adding a bunch of cheap console.log() ‘breakpoints’, I eventually got to the bottom of it and changed my code to this:

ADD_RECIPE_USER(state, value) {
state.userRecipes = [...state.userRecipes, value]
}

Conclusion: watch() paired with Vuex getters seems to require the state.key = newValue assignment.

Seems a bit weird, but might have been mentioned in the docs somewhere.

#19   Windows 10: process 'System' causing constant high disk activity

Posted:

Symptoms:

  • Fresh Windows 10 installation on a new device (build 19043)
  • Drive C:\ BitLocker encryted
  • Drive D:\ not encrypted but on the same hardware as drive C:\ (Intel SSD)
  • Constant disk activity of > 50% by “System” process, starting right after boot
  • Read/write to HardDiskVolume4 (D:\ in my case)

Resolution:

Turned on BitLocker for drive D:\ as well.

No more constant high disk activity after reboots, system feels more responsive in general.

NB: found this when systematically stopping running services due to the lack of a better approach.

#18   Netlify: secure HTTP headers

Posted:

Sensible default config that returns an A+ in Mozilla Observatory:

[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = "default-src 'self'; img-src *; frame-ancestors 'none'"
Referrer-Policy = "same-origin"
Strict-Transport-Security = "max-age=63072000; includeSubdomains; preload"
X-Content-Type-Options = "nosniff"
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"

NB: adjust CSP as necessary when working on sites that actually load scripts.

#17   Vue: limiting the number of array items inside 'v-for'

Posted:

Didn’t know that this could work, but it does:

<Component v-for="(item, index) in list.slice(0, 20)" ... />

Use case: getting the first 20 items of an array.

If the array is shorter, all items will be displayed.

#16   JavaScript: native Date formatting

Posted:

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:

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?

*,
*::before,
*::after
{
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:

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@github.com:me/repo.git

GIT config should then contain this automatically:

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

Source: stackoverflow.com/a/41947805

#13   Get mobile browsers to display the correct Favicon

Posted:

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:

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:

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

FetchError: request to https://db.fauna.com/ 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:

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:

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:

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>
</select>

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:

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) => {
e.stopPropagation();
if(!el.contains(e.target) && !e.target.classList.contains('click-outside-ignore')) {
binding.value();
}
}
document.addEventListener('click', handleOutsideClick);
document.addEventListener('touchstart', handleOutsideClick);
},
beforeUnmount() {
document.removeEventListener('click', handleOutsideClick);
document.removeEventListener('touchstart', handleOutsideClick);
}
});

#6   Hugo: RSS feed duplication

Posted:

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:

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

To this:

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

#5   Android: use ADB to uninstall bloatware

Posted:

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 'pkg.name.etc' or search for them like this: pm list packages -f TESTPKG
  7. Uninstall bloatware like this: pm uninstall -k --user 0 com.android.google.youtube
  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:

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:

#!/bin/bash

# get param
repo=$1

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

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

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:

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:

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:

#!/bin/bash

# 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:

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.