sc
Scott avatar
ttwillsey

Links and Pins

Not long ago, I created a Links page which I used to hold links I wanted to keep temporarily, like a little pinboard. But then I realized “HEY!! THIS IS BAD!!” People expect a links page to be things like blogrolls and stuff they might like, not the stupid stuff Scott Willsey wants to remember for a day or month or year or three.

So as of today, I now have Links, which is what people expect of a links page, and Pins, which is what I apparently expect of a links page.

Links is now, just to clarify, a permanent-ish list of links to things that YOU, the reader, might also find interesting.

Pins is, just to also clarify, where I’ll stick ephemeral stuff that I want to get back to but doesn’t make sense to stick in a read later app.

Astro Icon 1.x Upgrade

Part of the Astro series

If you started using Nate More’s Astro Icon with Astro early on and are using a version lower than 1.0, you will be in for a surprise when you upgrade to the latest version of Astro Icon and suddenly find a lot of breaking errors related to it.

Migrating from 0.x to 1.x is simple but can be a little tedious depending on how many icons you’re using. A couple key things to understand are that you have to import packages for each icon set that you use now, and that this is now an Astro integration. Don’t worry if you’re not sure how that affects you. You’ll handle adjusting to those changes in the upgrade steps below.

By the way, Nate has a great upgrade guide already at Upgrade to Astro Icon v1 | Astro Icon. I just thought I’d write about it too, since I’ve done this on a couple different sites now and I still have a couple to go.

Contents

Upgrading to Astro Icon 1.x from 0.x

  1. Upgrade to the latest version packages of astro and astro-icon (astro-icon >= 1.0)
  2. Import and integrate astro-icon in your astro.config.mjs
  3. Install @iconify-json/icon-family for every icon package you use
  4. On every page you import astro-icons, change import { Icon } from "astro-icon"; to import { Icon } from "astro-icon/components";
  5. Find and replace [astro-icon] tags with [data-icon] in your css
  6. Find and replace any CSS width or height properties on [data-icon] entries and replace with font-size
  7. Now go through every single instance of [data-icon] in your CSS and tweak the font-size that you just added.

1. Upgrade to the latest Astro and Astro Icon versions

Terminal window
scott@Songoku:friendswithbrews astro-icon 4h31m
ncu
Checking /Users/scott/Sites/friendswithbrews/package.json
[====================] 21/21 100%
@astrojs/rss ^4.0.1 ^4.0.2
@astrojs/sitemap ^3.0.3 ^3.0.4
astro ^4.0.7 ^4.1.2
astro-eslint-parser ^0.16.0 ^0.16.1
astro-icon ^0.8.2 ^1.0.2
astro-pagefind ^1.3.0 ^1.4.0
date-fns ^3.0.6 ^3.2.0
prettier ^3.1.1 ^3.2.2
prettier-plugin-astro ^0.12.2 ^0.12.3
sharp ^0.33.1 ^0.33.2
Run ncu -u to upgrade package.json

I use npm-check-updates to update npm packages, but you can do it the standard way. The key thing to note is that in this instance I’m upgrading from astro-icon 0.8.2 to 1.0.2.

2. Import and integrate astro-icon in your astro.config.mjs

No explanation needed here – import astro-icon and add to the list of integrations.

astro.config.mjs
import { defineConfig } from "astro/config";
import icon from "astro-icon";
import pagefind from "astro-pagefind";
import sitemap from "@astrojs/sitemap";
// https://astro.build/config
export default defineConfig({
site: "https://friendswithbeer.com/",
integrations: [ icon(), pagefind(), sitemap(),],
});

3. Install @iconify-json/icon-family for every icon package you use

This one requires you to scour through every place you specify an icon package name. Search for [astro-icon in your project and look for instances of things like <Icon name="simple-icons:mastodon" /> or CSS instances like [astro-icon="bi:hand-thumbs-up-fill"] and take note of the package names (in these examples, simple-icons and bi).

Now install these by installing @iconify-json/package-name for each. E.g,

Terminal window
npm install @iconify-json/simple-icons @iconify-json/bi

That will install the two packages in my example above.

4. Change your astro-icon import statements on Astro pages

The old way to import astro-icons in your .astro files is

import { Icon } from "astro-icon";

Change all instances of this in your astro files to

import { Icon } from "astro-icon/components";

5. Change all references to [astro-icon] in your CSS to [data-icon]

Another search and replace activity – [astro-icon] references have been replaced with [data-icon] references.

Old:

<style>
[astro-icon="simple-icons:mastodon"] {
color: var(--color-five);
width: 2.2rem;
}
</style>

New:

<style>
[data-icon="simple-icons:mastodon"] {
color: var(--color-five);
font-size: 2.2rem;
}
</style>

6. Use font-size instead of width or height to set the icon sizes

You may have noticed in the previous section that I not only changed [astro-icon] to [data-icon] in my CSS reference to it, but I also removed the width property and replaced it with a font-size property instead.

After performing the first 5 steps, you’ll see wildly sized icons all over the place until you do this.

7. Tweak font-size on all CSS for your icons

Step 6 will get you in the ballpark, but you’ll now find that some of your sizes, while closer, are still off. Just play with font-size values until you get what you like. CSS specificity is key here – be specific about which icons you’re setting your CSS for and use scoped CSS by putting them in <style> tags in specific astro pages.

The first example below affects all icons, the second one affects the bi:hand-thumbs-up-fill icon specifically.

[data-icon] {
font-size: 1em;
}
[data-icon="bi:hand-thumbs-up-fill"] {
margin-bottom: 0.1rem;
}

Summarium

That’s pretty much it. Once change you will notice when running the dev server is that the first time you try to load the site in the browser, Astro Icon has to load all the icon sets and it takes a little bit. Don’t worry. It will work. 🙂

Grin and Bear It

Mac

Part of the Mac series

Back when the iPad Pro was my main mobile “work” device,1 Drafts played a huge role in my writing and blogging. For one thing, it has extensive automation and scripting capabilities, and those came in super handy on iPadOS. I created automations to let me choose photos from my photos library and add them to blog posts, including creating proper links and adding them to my site repo.

But now I have an Apple Silicon MacBook Pro and I’ve long since given up trying to fight my way to success through all the limitations of iPadOS. It’s hard to overstate how much faster and easier so many things are on the Mac, and given the current state of Apple’s Shortcuts, having tons of other reliable methods of automating things, as you do on the Mac, is incredibly nice.

All that to say, I realized today I haven’t really done anything in Drafts except type out blog posts and then paste them into VSCode when it was time to publish them. I’m no longer making use of all the great Drafts automations and integrations that I used to. And given THAT fact, I may as well do my writing in something that looks nicer and feels more modern as a writing app.

I own iA Writer but it bugs me in certain ways. I don’t like the non-standard footnote default that makes you manually type the footnote reference at the bottom yourself if you don’t want an inline footnote, and it’s also very limited on font choices.

Ulysses is also weird. It shows you some of the Markdown but tries to hide other Markdown, like URLs, in very inconvenient ways. It’s been a long time since I thought I was a fan of Ulysses.

Before Ulysses, it was Byword for me. To say it looks a little basic now is a bit of an understatement.

That leaves Bear as the only realistic Markdown writing option for me, and I have to say, I like how the editor looks. That’s important to me. If it wasn’t, I’d still be using Drafts because it’s a great app and I do really like Greg Pierce and the work he does on it. It’s just not as important to me on the Mac now as it was on iPadOS.

I’m a person who has a lot of idiosyncrasies, and one of them is that I need a nice looking editor to be able to enjoy the writing process. Bear definitely looks nice. The defaults are nice, customizing it is simple, and things like images and links look really nice in it.

Below is an image showing both the light and dark themes I’m currently using in Bear.

My Bear dark and light themes

There’s not a lot I need to do in order to incorporate Bear into my writing workflow. I already have a script that names my images and puts copies of them in the right locations to both be optimized for the blog and to be able to link to the original. I’ll need to put an automation somewhere in the publish chain that updates the post’s Markdown file with those image paths and creates the links to the larger, original images. But that was also something I needed to do for my Drafts workflow, and hadn’t yet.

Also I think playing with Bear has given me some ideas for improving my site CSS a bit… 🤔

Footnotes

  1. Not my WORK work device, but my personal project work device

Auto-Generated Last Modified Date in Astro

Part of the Astro series

I’m trying to figure out how to use remark plugins in Astro to modify a couple things in posts for me automatically, and along the way I’ve used remark to add a couple quality of life improvements. The first is an auto-generated table of contents for longer posts that I feel need one, and the second is an auto-generated last modified date for pages based on git commit timestamps.

The benefit of an automatically generated last modified date is that I don’t have to remember to update it when I make changes to a page that displays it, like my now page or my links page. I can just commit my changes and the last modified date will update automatically. It’s simple, but it’s kind of beautiful.

I basically did exactly what Astro’s documentation says to do in a helpful recipe called Add last modified time. I created a file called remark-modified-time.mjs and put it in my src/components/utilities folder.1

remark-modified-time.mjs
import { execSync } from "child_process";
export function remarkModifiedTime() {
return function (tree, file) {
const filepath = file.history[0];
const result = execSync(`git log -1 --pretty="format:%cI" "${filepath}"`);
file.data.astro.frontmatter.lastModified = result.toString();
};
}

Then I updated my astro.config.mjs file to reference this as a remark plugin, and to use it in my markdown processing.

astro.config.mjs
import { defineConfig } from "astro/config";
import expressiveCode from "astro-expressive-code";
import icon from "astro-icon";
import pagefind from "astro-pagefind";
import { remarkModifiedTime } from './src/components/utilities/remark-modified-time.mjs';
import remarkToc from 'remark-toc';
/** @type {import('astro-expressive-code').AstroExpressiveCodeOptions} */
const astroExpressiveCodeOptions = {
// Example: Change the themes
themes: ["material-theme-ocean", "light-plus", "github-dark-dimmed"],
themeCssSelector: (theme) => `[data-theme='${theme.name}']`,
}
// https://astro.build/config
export default defineConfig({
site: "https://scottwillsey.com/",
integrations: [expressiveCode(astroExpressiveCodeOptions), icon(), pagefind()],
markdown: {
remarkPlugins: [ remarkModifiedTime, [remarkToc, { heading: "contents" } ] ],
},
});

Once those are set up, it’s just a matter of referencing remarkPluginFrontMatter.lastModified in the Astro template.

now.astro
---
import { getEntry } from "astro:content";
import { Icon } from "astro-icon/components";
import Base from "../layouts/Base.astro";
import { modifieddate } from "../components/utilities/DateFormat.js";
const now = await getEntry("now", "now");
const { Content, remarkPluginFrontmatter } = await now.render();
let title = now.data.title;
let description = now.data.description;
---
<Base title={title} description={description}>
<article>
<h1>{title}</h1>
<p class="now">
The <a href="https://nownownow.com/about">"now page"</a> concept comes from
an idea by <a href="https://sive.rs">Derek Sivers</a> to have people communicate
what they're focused on <b>now</b> at this point in their lives.
</p>
<div class="time">
<Icon name="bi:calendar2-week-fill" />
<time datetime={now.data.date}>
<a href={`/${now.slug}`}
>Last updated {modifieddate(remarkPluginFrontmatter.lastModified)}</a
>
</time>
</div>
<div class="now">
<Content />
</div>
</article>
</Base>

That’s it. Now I have auto-generated last modified dates for any page I feel needs one, thanks to Astro using remark to render markdown and therefore making adding remark plugins super simple.

Last updated indicator

Footnotes

  1. You can put your remark plugin anywhere you want, as long as you reference the path correctly in astro.config.mjs.

Now Page

I don’t remember how I stumbled across the now page movement started by Derek Sivers, but I immediately thought “what a great idea!” and decided to add one to my site.

The concept is simple – it’s a page that lists some current interests, activities, or projects that you’re into or doing. I decided to title mine “Current and Recent Things” because it seemed more appropriate when talking about food I’ve recently enjoyed or movies I’ve recently watched to label those as “recent” instead of “right this second”.

This is the kind of thing I like about personal websites. I think I a lot of people are realizing that blogging isn’t dead yet, it’s not even just resting, and additions to personal sites like now pages make personal blogs and websites even more fun to maintain.

You should try it, if you’re not already.

Anyway… here it is. My now page.

Default Browser Switching

Raycast

Part of the Raycast series

As you saw from my Default Apps December 2023 post, I use Safari for personal use and Chrome for web work and for some administrative and server-related tools that work best in it. The downside to this is that links go to the default browser from things like 1Password, Fastmarks, and email messages, for example. This means when I’m using Chrome, what I really need is for it to be my default browser, and the rest of the time, Safari to be.

Because I’m a Raycast user, I was intrigued by the Raycast Script Commands GitHub repo. The script command examples in this repo include system commands that include default browser scripts for Arc, Chrome, Chromium, Firefox, and Safari.

Raycast Script Commands

Raycast script commands are basically scripts that are registered in Raycast and have hooks that let it interact with them to pass parameters and show output in Raycast, if desired. These can be Bash scripts, AppleScript, Swift, Python, Ruby, or Node.js scripts.

Raycast Create Script Command menu

Raycast Default Browser Script Commands

The default browser scripts in the Raycast script command repo rely on a very short Objective-C program that you compile on your Mac called defaultbrowser, which lets you change your default browser from the command line. Given this, you might be surprised to learn that these script commands are AppleScript, and not Bash scripts. The reason is simple: buttons.

When you tell your Mac to switch default browsers using defaultbrowser, you are presented with a dialog box giving you the option to set whatever browser you wanted as your new default, or to keep whatever browser is the current default. Bash scripts can’t click buttons, but AppleScript can. It can also run Bash scripts, which lets us call defaultbrowser from within the AppleScript.

Here’s the default-browser-safari.applescript script from the Raycast script commands repo:

#!/usr/bin/osascript
# Dependency: requires defaultbrowser (https://github.com/kerma/defaultbrowser)
# Install via Homebrew: `brew install defaultbrowser`
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Default to Safari
# @raycast.mode silent
# @raycast.packageName Browser
# Optional parameters:
# @raycast.icon images/safari.png
# Documentation:
# @raycast.author Marcos Sánchez-Dehesa
# @raycast.authorURL https://github.com/dehesa
# @raycast.description Set Safari as the default browser.
set repeatCount to 0
tell application "System Events"
try
my changeDefaultBrowser("safari")
repeat until button 2 of window 1 of process "CoreServicesUIAgent" exists
delay 0.01
set repeatCount to repeatCount + 1
if repeatCount = 15 then exit repeat
end repeat
try
click button 2 of window 1 of process "CoreServicesUIAgent"
log "Safari is now your default browser"
on error
log "Safari is already your default browser"
end try
on error
log "The \"defaultbrowser\" CLI tool is required: https://github.com/kerma/defaultbrowser

The top half is information for Raycast. Then the AppleScript portion gets going. First it calls a function1 that runs the defaultbrowser command line program with the string safari as a parameter value. Then it runs a loop waiting for the confirmation dialog box to pop up. It waits until either the window exists or it’s looped 15 times. Finally, it tries to click button 2 of the dialog box. The reason it tries to click button 2 is because the dialog looks like the following image – or at least, it’s supposed to. More on that later.

Default browser change confirmation dialog box

This means when you send it a request to change your default browser from Safari to Chrome, for example, you’ll get a dialog box with two buttons, the first of which cancels the change and the second of which executes the change to Chrome.

I created new Raycast script commands using the appropriately named “Create Script Command” command in Raycast, and then copied the AppleScripts from the repo into them. Hooray! End of blog post, right?

Except there was a problem.

Chrome Does It Differently

Everything worked great whenever I tried switching from any browser that wasn’t Chrome to any other browser. But whenever I tried switching FROM Chrome back to something else, it never worked.

A simple test using defaultbrowser directly in the command line showed me why. Chrome’s confirmation dialog box is different. It looks like this:

Dialog box when switching from Chrome to a different default browser

You see the problem. The command scripts assume that button 2 is the button for making the switch, but in the case of Chrome’s dialog box, it’s the other way around. Button 1 makes the change and button 2 keeps the current default browser setting.

I honestly don’t know whose responsibility this is, Google’s or Apple’s, and I don’t really care. I presume it’s Google’s because I presume at one point the Raycast versions of these script commands worked, even when switching away from Chrome as the default browser, so my guess is Google changed something about Chrome’s confirmation dialog. I don’t know. Honestly, I would have thought this was entirely handled by macOS.

Modifying the Default Browser Scripts to Handle the Chrome Dialog Box

The solution is simple: if switching from Chrome, click button 1. Otherwise, click button 2. Since the script could be switching to a non-Chrome browser from a different non-Chrome browser, this means I need to check. And that sent me down a rabbit hole that I refuse to admit the time duration of, because it was long enough to need to add an “s” to “hour”.

But it goes like this:

On macOS, your default browser setting is one of many settings saved in com.apple.launchservices.secure.plist in your local Library/Prefences folder (~/Library/Preferences). You can search for it. If you type the following, you’ll get a LONG output that includes two lines you care about:

Terminal window
plutil -p ~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist

Those two lines are:

Terminal window
"LSHandlerRoleAll" => "com.apple.safari"
"LSHandlerURLScheme" => "https"

That’s if Safari is your current default browser, of course. It could be org.mozilla.firefox or com.google.chrome, for example. But you want to search for a line with LSHandlerRoleAll set to some browser, followed by a line called LSHandlerURLScheme set to https.

Fortunately, awk was made for things like this, and also fortunately, AppleScript can run shell commands. So I made another function for my default browser AppleScripts to see what the current browser is.

to getCurrentDefaultBrowser()
set filePath to "~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist"
set output to do shell script "plutil -p " & filePath & " | awk '/LSHandlerRoleAll/{a=$3}/LSHandlerURLScheme/{if($3==\"\\\"https\\\"\") print a}'"
return output
end getCurrentDefaultBrowser

Now, before running the part of the script that runs defaultbrowser before waiting for a button to click, it checks which browser is the current default browser. If it’s com.google.chrome, then my script clicks button 1 for me. Otherwise it clicks button 2.

#!/usr/bin/osascript
# Dependency: requires defaultbrowser (https://github.com/kerma/defaultbrowser)
# Install via Homebrew: `brew install defaultbrowser`
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Set Default Safari
# @raycast.mode silent
# @raycast.packageName Browser
# Optional parameters:
# @raycast.icon images/safari.png
# Documentation:
# @raycast.author scott_willsey
# @raycast.authorURL https://raycast.com/scott_willsey
set currentDefaultBrowser to my getCurrentDefaultBrowser()
set repeatCount to 0
tell application "System Events"
try
my changeDefaultBrowser()
repeat until button 2 of window 1 of process "CoreServicesUIAgent" exists
delay 0.01
set repeatCount to repeatCount + 1
if repeatCount = 15 then exit repeat
end repeat
try
if currentDefaultBrowser contains "com.google.chrome" then
click button 1 of window 1 of process "CoreServicesUIAgent"
else
click button 2 of window 1 of process "CoreServicesUIAgent"
end if
log "Safari is now your default browser"
on error
log "Safari is already your default browser"
end try
on error
log "The \"defaultbrowser\" CLI tool is required: https://github.com/kerma/defaultbrowser"
end try
end tell
to getCurrentDefaultBrowser()
set filePath to "~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist"
set output to do shell script "plutil -p " & filePath & " | awk '/LSHandlerRoleAll/{a=$3}/LSHandlerURLScheme/{if($3==\"\\\"https\\\"\") print a}'"
return output
end getCurrentDefaultBrowser
to changeDefaultBrowser()
do shell script "
if ! command -v defaultbrowser &> /dev/null; then
exit 1
fi
defaultbrowser " & "safari" & "
exit 0
"
end changeDefaultBrowser

Now when I have Chrome set as my default browser and I don’t want that anymore, my Raycast default browser command scripts work as intended, and will actually manage to set my default browser to the desired one.

A Parameterized Version of the Default Browser Command Script

Just in case anyone else cares, I also created a parameterized version of the script that lets me type in the browser name as a raycast command parameter so that the same script can switch to any browser I want.

Parameterized default browser script command

#!/usr/bin/osascript
# Required parameters:
# @raycast.schemaVersion 1
# @raycast.title Set Default Browser
# @raycast.mode silent
# Optional parameters:
# @raycast.icon images/Safari.png
# @raycast.argument1 { "type": "text", "placeholder": "Browser Name" }
# Documentation:
# @raycast.author scott_willsey
# @raycast.authorURL https://raycast.com/scott_willsey
on run argv
# check for null or empty item 1 of argv
if (item 1 of argv) is "" then
log "You must enter a browser name."
else
set currentDefaultBrowser to my getCurrentDefaultBrowser()
set browserName to item 1 of argv
set repeatCount to 0
tell application "System Events"
try
my changeDefaultBrowser(browserName)
repeat until button 2 of window 1 of process "CoreServicesUIAgent" exists
delay 0.01
set repeatCount to repeatCount + 1
if repeatCount = 15 then exit repeat
end repeat
try
if currentDefaultBrowser contains "com.google.chrome" then
click button 1 of window 1 of process "CoreServicesUIAgent"
else
click button 2 of window 1 of process "CoreServicesUIAgent"
end if
log browserName & " is now your default browser"
on error
log browserName & " is already your default browser"
end try
on error
log "The \"defaultbrowser\" CLI tool is required: https://github.com/kerma/defaultbrowser"
end try
end tell
end if
end run
to getCurrentDefaultBrowser()
set filePath to "~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist"
set output to do shell script "plutil -p " & filePath & " | awk '/LSHandlerRoleAll/{a=$3}/LSHandlerURLScheme/{if($3==\"\\\"https\\\"\") print a}'"
return output
end getCurrentDefaultBrowser
to changeDefaultBrowser(browserName)
do shell script "
if ! command -v /opt/homebrew/bin/defaultbrowser &> /dev/null; then
exit 1
fi
defaultbrowser " & browserName & "
exit 0
"
end changeDefaultBrowser

Footnotes

  1. Custom handler in AppleScript parlance, apparently

Default Apps December 2023

Once upon a time,1 the Hemispheric Views podcast held the first ever Duel of the Defaults in which they decided who was the winner at using the most default apps for several categories. Subsequently, Gabz posted his own default app list, and then Robb Knight took it to 11 with his App Defaults page.

In the spirit of this new fashion of blogging about one’s default apps, here are mine as of December, 2023.

NOTE: These are my personal use apps. They have nothing to do with my job at Monolith 3000, which is an all-Microsoft shop.

Official Categories

  • 📮 Mail Server
  • 📨 Mail Client
    • Apple Mail
  • 📝 Notes
  • To-Do
    • Reminders App
  • 📷 iPhone Photo Shooting
    • iOS Camera
  • 🟦 Photo Management
    • Photos App
  • 📅 Calendar
    • Apple Calendar
  • 📂 Cloud File Storage
    • iCloud Drive
  • 📖 RSS
  • 👩‍🦲 Contacts
    • Apple Contacts App
  • 🌐 Browser
    • Safari, Chrome for web work
  • 💬 Chat
    • Messages, Signal, Discord, Slack
  • 🔖 Bookmarks
  • 📑 Read It Later
  • 📜 Word Processing
    • Pages
    • Google Docs
    • Microsoft Word
  • 📈 Spreadsheets
    • Excel (I’m sorry, but Numbers is the most useless excuse for a spreadsheet app I’ve ever seen)
  • 📊 Presentations
    • Keynote
  • 🛒 Shopping List
    • Reminders
  • 🍴 Meal Planning
  • 💰 Budgeting and Personal Finance
    • Spreadsheet
  • 📰 News
    • RSS
    • Mastodon
    • Apple News
  • 🎵 Music
    • Apple Music
  • 🎤 Podcasts
  • 🔐 Password Management

Additional Categories

Footnotes

  1. November 2nd, 2023, in fact