I thought I had a clever way of using tagged template literals with Array.prototype.reduce, but it turns out that String.raw does this already.

So my template function should be:

function template(strings, ...keys) {
  return (data) => String.raw( {raw: strings, ...keys.map (key => data[key]) );
}

Which is much simpler

I've finally joined the 21st century, and gone to serving the site on https. On NearlyFreeSpeech, it should be easy: just run tls-setup.sh from an SSH terminal. That sets everything up to use Let's Encrypt, but that was failing because I use the Apache httpd RewriteEngine to host kavanot.name, with that domain redirecting to a subdirectory in the bililite.com directory tree. But Let's Encrypt wants to have access to the .well-known directory in the main directory. So https://kavanot.name was failing, and messing everything up.

The answer was at https://members.nearlyfreespeech.net/forums/viewtopic.php?t=11218 (which unfortunately is only visible to NFS members): make sure Apache didn't rewrite the .well-known directory:


RewriteCond %{REQUEST_URI} !\.well-known
RewriteCond %{HTTP_HOST} ^kavanot
RewriteRule // etc.

(meaning if the URI does not contain ".well-known" and the domain starts with kavanot, then do the rewrite.

And now running tls-setup.sh works!

This seems obvious in retrospect, but I got this from Olivier Wietrich's tag-reduce.

Tagged Literal Templates are useful syntactic sugar. I fill out a lot of school physical forms (each school has its own form), so I need strings like `Weight: ${data.wt_lb} pounds (${data.wt_kg} kg), ${data.wt_pct} percentile`. That works fine if I am evaluating the template string when it is defined.

But I want to defer evaluating those strings until the data array is filled in (by parsing my EMR's note, a topic for another time).

Mozilla suggests something like


function template(strings, ...keys) {
  return (dict) => {
    const result = [strings[0]];
    keys.forEach((key, i) => {
      result.push(dict[key], strings[i + 1]);
    });
    return result.join('');
  };
}

and using as


weightTemplate = template`Weight: ${'wt_lb'} pounds (${'wt_kg'} kg), ${'wt_pct'} percentile`;
// later in code, when data dictionary is completed
finalString = weightTemplate (data);

But Olivier Wietrich suggests the much more elegant:


function template(strings, ...keys) {
  return (data) => strings.reduce ( (previousValue, currentValue, currentIndex) => previousValue + data[keys[currentIndex-1]] + currentValue);
  // (I'm using ECMA's parameter names for reduce, which are pretty verbose)
}

using Array.prototype.reduce, which should have been obvious.
Using template is the same as above.

It seems like a long time ago that I started updating bililiteRange but it was only 3 months ago. And I think I'm done (though projects are never done!). bililiteRange 3.2 is released, and requires history.js, toolbar.js, jquery.status.js and jquery.keymap.js for all the functionality, though really the core only requires history.js.

The documentation is at github.bililite.com/bililiteRange, with the documentation for the other repositories linked to at github.bililite.com.

Well, that was easy. The Classic Editor plugin restores the old editor, with all my custom buttons.

And installing it with the command line interface was just

wp plugin install classic-editor

Nearlyfreespeech.net has been great, but their security settings make it hard to update WordPress automatically. I just discovered that WP now has a command line interface that works fine over SSH.

  wp core update
  wp core update-dp

And done!. You can do wp db export to back up the database first.
And updating plugins (for those that can auto-update) is just wp plugin update-all.

My biggest complaint with WP 5.5 is the new post editor. It's a new, simpler editor but all my custom buttons are gone. I suppose I'll have to figure out how to restore the old editor.

This is something I didn't know about: Prism, the syntax highlighter I use, is available from a few free CDN's, so I don't have to host it. Cool!

I'm not a designer, and I've never been happy with the color scheme for kavanot.name, so I decided to try something new. I use scssphp with scssphp server (and yes, you need both) to generate the CSS, and it allows variables, so I can do:


$hue = 250;
$saturation = '40%';
$textcolor: hsl($hue, $saturation, 10%);
$displaytextcolor: hsl($hue, $saturation, 40%);
$backcolor: hsl($hue, $saturation, 95%);
$bordercolor: hsl($hue, $saturation, 90%);
$deepbackcolor: hsl($hue, $saturation, 85%);

to make all the colors a variation on one single color. And I like that. But what color? Fortunately, scssphp allows for importing variables, so my `styles.php` includes:


setcookie('hue', $_GET['hue'], time()+12*60*60*24*30, '/'); // one year expiration
setcookie('saturation', $_GET['saturation'], time()+12*60*60*24*30, '/'); // one year expiration

$scss = new Compiler();
$scss->setVariables(array(
	'hue' => intval($_GET['hue']),
	'saturation' => intval($_GET['saturation']).'%',
));

instead of the in-file $hue = 250; $saturation = '40%';. Now I can include those values in the URL for the stylesheet itself,
and the cookies mean that I don't have to write it each time.

To get the query variables to the stylesheet, I have to pass them from the main page:


	<link rel="stylesheet" id=mainstylesheet type="text/css" href="/css/style.php/style.scss?hue=<?=$_GET['hue']?>&saturation=<?=$_GET['saturation']?>" />

(I am ignoring the input sanitization you need to avoid XSS. intval is your friend here!)

But retyping http://kavanot.name?hue=250&saturation=40 with minor variations is a pain. Can I make it interactive? Yes, with a Javascript
color picker. <input type=color /> is supported in all the major browsers, so I can do:


var colorPicker = $('input[type=color]');
let baseHSL = getBaseHSL(); // get the body color as [hue, saturation, lightness]
baseHSL[2] = 50; // start with a color that can be easily seen, 50% lightness
colorPicker.val(hsl2hex(...baseHSL)); // set the initial value

const stylesheet = $('#mainstylesheet')[0];

colorPicker.on('input', function(){
	let [h, s] = hex2hsl(this.value);
	// change the stylesheet href and the styles change on the fly!
	stylesheet.href = stylesheet.href.replace(/hue=\d+/, `hue=${h}`).replace(/saturation=\d+/, `saturation=${s}`);
});

function getBaseHSL(){
	// getComputedStyle.color is rgb(r,g,b)
	let [, r, g, b] = /(\d+),\s*(\d+),\s*(\d+)/.exec(window.getComputedStyle(document.body).color);
	return rgb2hsl (r, g, b);
}

Now I need the functions rgb2hsl, hex2hsl and hsl2hex to convert between the various color spaces. Luckily,
Jon Kantner has done the work for me.


function hex2hsl(hex){
	let [, r, g, b] = /#(..)(..)(..)/.exec(hex);
	return rgb2hsl (+('0x'+r), +('0x'+g), +('0x'+b));
}
	
function rgb2hsl (r, g, b){
	// convert to fractions
	r = r/255;
	b = b/255;
	g = g/255;

	// find greatest and smallest channel values
	const cmin = Math.min(r,g,b);
	const cmax = Math.max(r,g,b);
	const delta = cmax - cmin;
	let h;

	// calculate hue
	if (delta == 0){ // no difference
		h = 0;
	}else if (cmax == r){ // red is max
		h = ((g - b) / delta) % 6;
	}else if (cmax == g){
		h = (b - r) / delta + 2; // green is max
	}else{
		h = (r - g) / delta + 4; // blue is max
	}
	h = Math.round(h * 60);
	if (h < 0) h += 360;

	// calculate lightness
	let l = (cmax + cmin) / 2;

	// calculate saturation
	let s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));

	// make s and l percents
	return [h, Math.round(s*100), Math.round(l*100)];
}

function hsl2hex(h, s, l){
	let c = (1 - Math.abs(2 * l/100 - 1)) * s/100;
	let x = c * (1 - Math.abs((h / 60) % 2 - 1));
	let m = l/100 - c/2;
	let r, g, b;
	
	if (0 <= h && h < 60) {
		r = c; g = x; b = 0;
	} else if (60 <= h && h < 120) {
		r = x; g = c; b = 0;
	} else if (120 <= h && h < 180) {
		r = 0; g = c; b = x;
	} else if (180 <= h && h < 240) {
		r = 0; g = x; b = c;
	} else if (240 <= h && h < 300) {
		r = x; g = 0; b = c;
	} else if (300 <= h && h < 360) {
		r = c; g = 0; b = x;
	}

	r = Math.round((r + m) * 255);
	g = Math.round((g + m) * 255);
	b = Math.round((b + m) * 255);
	
	return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}

function toHex(d) {
	d = d.toString(16);
	if (d.length == 1) d = `0${d}`;
	return d.toUpperCase();
}

and now I can play with colors to my heart's content!

The way browser history is implemented, with the History interface, is useful for other things than site navigation. The same idea, basically an extended stack that allows going back and forward, is the same algorithm used for undo/redo, and command line history.

It would be nice to be able to do const h = new History() and h.pushState(someData) with arbitrary states, but the browser won't let you construct a new instance of History. It's only accessible from window.history.

So I re-defined History to allow this.

See the details on the github site.

I haven't updated bililiteRange for years, but it's time to drop support for Internet Explorer and use modern constructs like Promise and input events. It should make things more simple. From here on in, I will only support Chrome, Firefox and Edge (the chromium-based one; I don't have Edge Legacy. I'm also working on moving all the documentation into the git repositories themselves, as github pages (see github.bililite.com, which is an alias for dwachss.github.io).

I've updated jquery.status.js, jquery.keymap.js, and timer events. The documentation for the timer events and status are done.