A public collection of notes and learnings that might help and inspire others.
Table of Contents (Skip)
- #28 - Batch-replace a string in many folders and update GIT
- #27 - Azure SWA: Function host is not running.
- #26 - 11ty: 'draft' status in front matter
- #25 - Get OneDrive ‘driveId’ of a private Teams channel
- #24 - Vite: HTTPS on localhost (basic SSL)
- #23 - Remove SentinelOne Browser Extension
- #22 - Restore WSL2 distro after user profile migration
- #21 - SSH: no matching host key type found. Their offer: ssh-rsa
- #20 - Vue: 'Array.push()' used in Vuex mutation not triggering 'watch()'
- #19 - Windows 10: process 'System' causing constant high disk activity
- #18 - Netlify: secure HTTP headers
- #17 - Vue: limiting the number of array items inside 'v-for'
- #16 - JavaScript: native Date formatting
- #15 - CSS: scroll-behavior
- #14 - GIT: SSH with multiple identities for the same host
- #13 - Get mobile browsers to display the correct Favicon
- #12 - Fauna DB: response size and pagination
- #11 - Fauna DB: EPIPE error
- #10 - Vue3: handle page refresh (F5) in your application
- #9 - GoTrue Admin methods
- #8 - Vue3: <select> default value
- #7 - Vue3: "click outside" directive
- #6 - Hugo: RSS feed duplication
- #5 - Android: use ADB to uninstall bloatware
- #4 - Shell Script: launch VS Code for a specific folder/repository
- #3 - Vue2: handle component's click event in its parent
- #2 - Manjaro Linux mount script: BitLocker encrypted external drive
- #1 - Manjaro Linux: .service file error when setting up KeeWeb
#28 Batch-replace a string in many folders and update GIT
Posted:
Needed to replace Skypack with another CDN across many projects.
This script automates the taks and should serve as a starting point for many similar use cases:
#!/bin/bash
for D in *; do
if [ -d "${D}" ]; then
cd ${D}
echo "processing ${D}..."
( shopt -s globstar dotglob
sed -i -- 's/cdn[.]skypack[.]dev/esm.sh/g' **/*.js
sed -i -- 's/cdn[.]skypack[.]dev/esm.sh/g' **/*.html
)
echo "updating git..."
git add --all
git commit -a -m "replace skypack"
git push
echo "${D} processed!"
cd ..
fi
done
#27 Azure SWA: Function host is not running.
Posted:
An Azure Static Web App (SWA) last deployed in late August 2022 showed the message “Function host is not running.” after a recent deployment.
Local testing with Azure’s SWA CLI also showed errors. Further investigation showed that the “Minimum extension version” in host.json
had to be changed:
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[2.*, 3.0.0)"
}
Reason: EOL for extended support of the runtime version for functions on December 13, 2022.
Source: Azure Functions Docs
#26 11ty: 'draft' status in front matter
Posted:
My site didn’t have anay status for content yet and I felt like unpublishing some older posts. Now (ab)using ‘draft’ status for that, this is how (in .eleventy.js
):
const publishedContent = (item) => !item.data.draft
//...
config.addCollection('blog', async (collection) => {
return collection.getFilteredByGlob('./src/blog/*.md').filter(publishedContent)
})
#25 Get OneDrive ‘driveId’ of a private Teams channel
Posted:
URL decode what’s displayed in your browser’s address bar when viewing the root page of your Teams channel’s documents library in SharePoint. We need channelId
and groupId
:
https://your_company-my.sharepoint.com/personal/THIS_IS_YOU/_layouts/15/onedrive.aspx?id=/sites/TEAMS_CHANNEL_NAME_FULL/Shared Documents/CHANNEL_NAME&listurl=https://your_company.sharepoint.com/sites/TEAMS_CHANNEL_NAME_FULL/Shared Documents&remoteItem={"mp":{"webAbsoluteUrl":"https://your_company.sharepoint.com/sites/TEAM_NAME","listFullUrl":"https://your_company.sharepoint.com/sites/TEAM_NAME/Shared Documents","rootFolder":"","channelId":"CHANNEL_ID_","groupId":"GROUP_GUID","displayName":"CHANNEL_NAME"},"rsi":{"listFullUrl":"https://your_company.sharepoint.com/sites/TEAMS_CHANNEL_NAME_FULL/Shared Documents","rootFolder":"/sites/TEAMS_CHANNEL_NAME_FULL/Shared Documents/CHANNEL_NAME","webAbsoluteUrl":"https://your_company.sharepoint.com/sites/CHANNEL_NAME_FULL"}}
Head to MS Graph Explorer at: developer.microsoft.com/en-us/graph/graph-explorer#
Use the endpoint at /v1.0/teams/{teamId}/channels/{channelId}/filesFolder
with the two ids previously obtained from the URL.
NB: you might have to set some permissions for the request to go through.
Eventually, the request should lead to a response that contains the driveId
we were looking for:
"parentReference": {
"driveId": "b!...yourDriveId",
"driveType": "documentLibrary"
},
#24 Vite: HTTPS on localhost (basic SSL)
Posted:
Had to look this up today, here’s how to do it.
First install a Vite plugin:
npm install -D @vitejs/plugin-basic-ssl
Then use it in vite.config.ts
:
import basicSsl from '@vitejs/plugin-basic-ssl'
export default {
plugins: [
basicSsl()
]
}
NB: works with Astro too, simply create the Vite config file in case it’s not there yet.
#23 Remove SentinelOne Browser Extension
Posted:
I don’t tolerate spyware, no matter where it comes from. Here’s what you can do if you’re on Windows and got S1 forced into your browser/s:
- Open
regedit
- Navigate to
HKLM\SOFTWARE\Policies\[Vendor]\[Browser]
- Get rid of whatever keys you find in
ExtensionInstallForcelist
- Close your browser/s
- Clean out your user profile’s extensions folder:
Edge:[LocalUserProfile]\AppData\Local\Microsoft\Edge\User Data\[EdgeProfile]\Extensions\[S1ExtensionID]\
Firefox:[LocalUserProfile]\AppData\Roaming\Mozilla\Firefox\Profiles\[FFProfile]\extensions\sentinelone_visibility@sentinelone.com.xpi
NB: survives reboots, will have to see whether or not those policies are ever getting reapplied.
#22 Restore WSL2 distro after user profile migration
Posted:
Recently got my work notebook’s user profile changed to a different one. WSL was still installed and working, but didn’t recognize my Manjaro installation anymore (despite of it being in the exact same physical location on a separate non-OS partition)
This is what I did to get it running again:
- Set WSL2 as default:
wsl --set-default-version 2
(this is crucial) - Install a clean new instance
- Make sure it’s installed properly, then run
wsl —shutdown
- Remove the newly created vhdx file and replace with your old one
- Run something like
Manjaro.exe config --default-user <username>
to make your previous user the default user - Set Windows Terminal as WSL’s default:
{InstanceName}.exe config --default-term wt
- Restore your WT settings (i.e. from your old user profile, a path like
C:\Users\user.name\AppData\Local\Packages\Microsoft.WindowsTerminal_123xyz\LocalState
)
NB: really pleasant surprise to find everything just as I’d left it after having figured out to above steps :)
#21 SSH: no matching host key type found. Their offer: ssh-rsa
Posted:
Got this error when trying to use sftp
in a Linux terminal.
Resolution:
Edit /etc/ssh/ssh_config
, add those lines:
HostKeyAlgorithms ssh-rsa,ssh-dss
PubkeyAcceptedKeyTypes ssh-rsa,ssh-dss
NB: might break some other stuff (GitLab in my case), so be careful and remember you added those line.
#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
#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.
12/2021 - Refer to the following commits on how to block page refresh when handling unsaved changes:
#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:
- Get ADB platform tools for your OS
- On your phone: enable developer mode and USB debudding
- Connect phone
- Test if connected successfully and allow debugging (will be prompted):
adb devices
adb shell
to get into the phone- List packages with
pm list packages | grep 'pkg.name.etc'
or search for them like this:pm list packages -f TESTPKG
- Uninstall bloatware like this:
pm uninstall -k --user 0 com.android.google.youtube
- 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
.