Marty McGuire

Posts Tagged webdev

2023
Tue May 23

A little "bookmarklets toolset"

Browser bookmarks can run little bits of javascript on the current page if you use a "javascript:" URL. Folks often refer to these as "bookmarklets". (I wish they worked well in mobile browsers like iOS Safari, but that's a rant...)

Constructing scripts in a URL-safe format can be super frustrating! But today I expanded my "dev tools" for building bookmarklets:

The Firefox dev tools console is a great way to try out your code in-place on the actual page where you want to run it. Press "Ctrl+b" (or "Cmd+b" if you are a Mac person) and the console editor goes into multiline mode! This makes it much easier to edit little scripts! In multiline mode, you "submit" by pressing "Ctrl+Enter" (or "Cmd+Enter" etc).

Duckduckgo has a built-in tool for Javascript beautifying and minifying! It pops up when you search duckduckgo for "javascript minifier". This makes it easy to go from a readable script with newlines and other formatting in your console, to a minimized compact scriptlet suitable for a bookmarklet. Even better, it lets you go back and forth between a minimized script and a readable one so you can tweak your bookmarklets in the future.

Finally, and in some ways most importantly, bookmarklets need to be properly URL-encoded. Enter Marek Gibney's Bookmarklet Editor, which uses a single textarea and lets you convert between a script and its encoded "javascript:..." version.

The minifier/beautifier and bookmarklet editor are web tools, so the usual caveats apply about data privacy. In theory they are running purely in your browser and not sending data anywhere. But they could! So keep that in your threat model.

Sun Mar 19

Go Time

This is one of those posts about a tech issue comes up from time to time for me that I find difficult to search for. Sorry (not sorry) if it is not helpful to anyone else!

Too much context about my site building process

My site is built with Hugo, a static site generator known for being quite fast, and with a powerful (and challenging) templating system.

I use my site for lots of stuff, which sometimes means showing content from other sites in my posts. For example:

In either case, my site's build process includes a step where it finds new links that need this context - to be displayed as a preview or displayed as a like/comment/etc., and it fetches that stuff. To do this, my site fetches the link content, then parses it using a handy library called aaronpk/XRay, then saving it in a place where Hugo can find it at build time.

For example, the RSVP post I mentioned gets a little chunk of data like this:

{
  "fetched_at": "2019-10-02T23:29:15-04:00",
  "xray": {
    "data": {
      "type": "entry",
      "published": "2019-10-02 19:53-0700",
      "url": "https://tantek.com/2019/275/t1/indiewebcamp-new-york-city",
      "content": {
        "text": "going to #IndieWebCamp NYC this weekend ...",
...

I’ve cut out a lot of stuff here and highlighted the two things relevant for my particular time issue.

In my templates for previews and responses I try to show the published time from the post itself. If XRay wasn't able to find a value for published or (spoiler) Hugo can't parse that value as a time, the template will give up and omit the published time.

What is Web Time?

This is such a ridiculously fraught question that I refuse to answer it properly. Suffice to say:

What is (Hu)go Time?

Okay, finally, the point of this post has been found. To archive this lil' set of facts for my future frustrations.

From an unrelated Hugo date parsing issue, I learned about the list of time formats that Hugo tries when you ask it to parse something as a time, last updated August 2022:

var (
  timeFormats = []timeFormat{
    {time.RFC3339, timeFormatNumericTimezone},
    {"2006-01-02T15:04:05", timeFormatNoTimezone}, // iso8601 without timezone
    {time.RFC1123Z, timeFormatNumericTimezone},
    {time.RFC1123, timeFormatNamedTimezone},
    {time.RFC822Z, timeFormatNumericTimezone},
    {time.RFC822, timeFormatNamedTimezone},
    {time.RFC850, timeFormatNamedTimezone},
    {"2006-01-02 15:04:05.999999999 -0700 MST", timeFormatNumericAndNamedTimezone}, // Time.String()
    {"2006-01-02T15:04:05-0700", timeFormatNumericTimezone}, // RFC3339 without timezone hh:mm colon
    {"2006-01-02 15:04:05Z0700", timeFormatNumericTimezone}, // RFC3339 without T or timezone hh:mm colon
    {"2006-01-02 15:04:05", timeFormatNoTimezone},
    {time.ANSIC, timeFormatNoTimezone},
    {time.UnixDate, timeFormatNamedTimezone},
    {time.RubyDate, timeFormatNumericTimezone},
    {"2006-01-02 15:04:05Z07:00", timeFormatNumericTimezone},
    {"2006-01-02", timeFormatNoTimezone},
    {"02 Jan 2006", timeFormatNoTimezone},
    {"2006-01-02 15:04:05 -07:00", timeFormatNumericTimezone},
    {"2006-01-02 15:04:05 -0700", timeFormatNumericTimezone},
    {time.Kitchen, timeFormatTimeOnly},
    {time.Stamp, timeFormatTimeOnly},
    {time.StampMilli, timeFormatTimeOnly},
    {time.StampMicro, timeFormatTimeOnly},
    {time.StampNano, timeFormatTimeOnly},
  }
)

There's a lot of time.Whatever constants in that list. These are part of the Go language's time package :

const (
  Layout = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order.
  ANSIC = "Mon Jan _2 15:04:05 2006"
  UnixDate = "Mon Jan _2 15:04:05 MST 2006"
  RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
  RFC822 = "02 Jan 06 15:04 MST"
  RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
  RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
  RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
  RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
  RFC3339 = "2006-01-02T15:04:05Z07:00"
  RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
  Kitchen = "3:04PM"
  // Handy time stamps.
  Stamp = "Jan _2 15:04:05"
  StampMilli = "Jan _2 15:04:05.000"
  StampMicro = "Jan _2 15:04:05.000000"
  StampNano = "Jan _2 15:04:05.000000000"
  DateTime = "2006-01-02 15:04:05"
  DateOnly = "2006-01-02"
  TimeOnly = "15:04:05"
)

If your brain just can't help but puzzle-solve, you may have noticed that the published time from the example above:

"2019-10-02 19:53-0700"

Is close to but does not match either the time.RFC3339 or time.DateTime formats.

The issue? Neither Hugo nor Go fully support ISO 8601 time, instead supporting very close time formats which do not allow omitting the seconds value. There are definitely other formats they don't support, which I've seen commonly, like "January 2, 2006".

Workaround (Deprecated)

When I first built out the Hugo templates for my site (2018, so back in the 0.4x or 0.5x days, maybe?), if you asked Hugo to parse a time, and it couldn't, it would give you back a string instead with an error message beginning "unable to parse date: ...". This was gnarly, but I could code around it by asking Hugo to convert a value to a time and checking if the result starts with "unable to parse". Something like:

{{ $safe_published := (time $item_published) -}}
{{ if not (hasPrefix $safe_start "unable to parse") -}}
  {{/* ... it parsed, hooray, use it */}}
{{ else -}}
  {{/* failed to parse so do a fallback or ignore it or whatever */}}
{{ end -}}

Time passes and Hugo has since changed this behavior. Now, if you ask it to convert a value to a time, and it cannot parse it with one of the known format strings, Hugo bails and the entire build fails. It throws an error something like:

ERROR 2023/03/19 13:02:05 render of "page" failed:
  "/home/schmarty/me/martymcgui.re/themes/mmgre-2015/layouts/_default/single.html:3:7":
  execute of template failed: template: _default/single.html:3:7:
  executing "main" at <partial "post/post.html" (...)>:
  error calling partial: "/home/schmarty/me/martymcgui.re/themes/mmgre-2015/layouts/partials/post/post.html:20:36":
  execute of template failed: template: partials/post/post.html:20:36:
  executing "partials/post/post.html" at <partial "link-preview/link-preview.html" (...)>:
  error calling partial: "/home/schmarty/me/martymcgui.re/themes/mmgre-2015/layouts/partials/link-preview/link-preview.html:26:9":
  execute of template failed: template: partials/link-preview/link-preview.html:26:9:
  executing "partials/link-preview/link-preview.html" at <partial (printf "link-preview/%s.html" "xray") (...)>:
  error calling partial: "/home/schmarty/me/martymcgui.re/themes/mmgre-2015/layouts/partials/link-preview/xray.html:30:28":
  execute of template failed: template: partials/link-preview/xray.html:30:28:
  executing "partials/link-preview/xray.html" at <partial "util/safe-time.html" $item.published>:
  error calling partial: "/home/schmarty/me/martymcgui.re/themes/mmgre-2015/layouts/partials/util/safe-time.html:1:4":
  execute of template failed: template: partials/util/safe-time.html:1:4:
  executing "partials/util/safe-time.html" at <time .>:
   error calling time: unable to parse date: 2019-10-02 19:53-0700

Believe it or not I actually cleaned up that error a lot, removed duplicates, and highlighted the key issue in bold.

I hate this.

Workaround (Derogatory)

For now I check my site's build logs from time to time looking for build errors like the one above. I'll then:

  1. Use a tool like grep to search my saved reply context data for the problematic time string (in this case "2019-10-02 19:53-0700").
  2. Find the file it's in, and "fix" it so Hugo can parse it. In this case, "2019-10-02 19:53:00-0700".
  3. With the "fix" in place, I'll rebuild the site.

Either it builds successfully (hooray!) or I find the next time parsing failure and repeat.

I seriously dislike this.

A Future Fix

What I actually need to do here is update my build system to sanitize the value of published if it exists.

Like Hugo's list, I'll need to decide what date string formats I want to support, have the build system try them all on that published value, and store a sanitized version for Hugo.

Okay! Now I have this post to refer to in the future when I get next get cranky about this.

2021
Tue Mar 30
🔖 Bookmarked Adactio: Journal—The principle of most availability https://adactio.com/journal/17987

“No matter what operating system I’m using, or what email software I’ve chosen, email works. It gets more complicated when you introduce HTML email. My response to that is the same as the old joke; you know the one: “Doctor, it hurts when I do this.” (“Well, don’t do that.”)”

2020
Wed Nov 25
🔖 Bookmarked 3D Model Accessibility – Scott Vinkle https://scottvinkle.me/blogs/work/3d-model-accessibility

“3D models on the web… how are we going to make those accessible? How do you convey a “3D model”, let alone provide access via assistive technology?”

Mon Sep 14
🔖 Bookmarked Webster’s Dictionary Defines “View Source” As... - Jim Nielsen’s Weblog https://blog.jim-nielsen.com/2020/the-meaning-of-view-source/

“The “before” HTML I looked at was the raw HTML sent over the wire. The “after” HTML I looked at was a stringification of the DOM after a couple seconds of first requesting the URL. The string is a representation of what the browser decides to output if you click “Edit as HTML” on the root DOM node and then copy it. In other words, it’s the browser’s version of saying “you input HTML, I parsed it, executed relevant JavaScript, and now have this HTML representation.””

Mon May 25

A hole in browser Autofill support

If you've ever seen your browser automatically fill in your shipping address, or seen your iPhone offer to scan a credit card on an e-commerce site, you're seeing Autofill in action.

Autofill has been part of the WHATWG HTML Standard for some years now. This 2016 write-up by Jason Grigsby gives a pretty good sense of what can be done with it.

The spec describes ways that an HTML <input> element can use the "autocomplete" attribute to hint to the browser that it should offer to fill it with specific Autofill data, if the browser has it and if Autofill is enabled. There's a long list of values related to names, addresses, phone numbers, dates, and more. Additionally, since users might have more than one of a thing, these can be scoped with values like "home", "work", etc. It's possible to further group addresses by "shipping" and "billing", and even to group larger chunks of forms by named sections.

An example might look like:

<input name="home-street-address" autocomplete="shipping home street-address">

An IndieWeb use-case for Autofill

Web sign-in is a very IndieWeb concept where you sign into websites using your personal web address, rather than an email address or username.

The sign-in form for webmention.io asks you to sign in using a URL.

As with any repetitive tasks, typing my site's URL into these login forms gets annoying. My main browser (Firefox) is pretty smart. Autocomplete kicks in after I type a few characters from my URL and it will offer to fill in URLs that I've typed before. However, since most URLs start with "https://", autocomplete suggestions aren't very useful until I've typed out 9 or more characters (or if I start typing from somewhere in the middle).

Helpfully, "url" is one of the many attributes in the WHATWG Autofill spec! It's described like so:

Home page or other Web page corresponding to the company, person, address, or contact information in the other fields associated with this field

In theory, it should be possible for sites with Web Sign-in to improve this process with the help of the browser and Autofill. For example:

<input name="url" type="url" autocomplete="url">

Or more specifically, use your "home" (personal) URL:

<input name="url" type="url" autocomplete="home url">

It's my thinking that, with this in place, a browser should automatically suggest my URL without me typing anything at all!

A URL-shaped hole in Autofill

I tried this out by setting up url autocomplete suggestions on two different apps with Web Sign-in. (Specifically, my personal instance of Aperture, and the IndieWeb webring).

I then tried signing in and out several times to both sites, using the same URL each time. Browsers tested include Firefox, Chromium, and iOS Safari, all with Autofill enabled.

I am sad to report that none of the tested browsers attempted to automatically fill in the URL value. The extra autocomplete attribute didn't break the default autocompletion, but I still see it suggest every URL it knows about rather than learning one.

I have had trouble finding documentation on how specific browsers implement Autofill. One note in Jason's 2016 article suggests that browsers may need multiple "hints" before it will decide that a particular input is part of a group which should be auto-filled.

Another hint comes from Chromium's settings for managing Autofill data. This is what the form looks like for adding a new address:

Chromium address dialog with fields for name, street address, and more. There is no field for URL.

Notice a field that isn't there?

2018
Mon Jan 22

Love my local bike share. Finding my account a little hard to navigate…