Skip to content

Updated bililiteRange

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.

Classic Editor

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

Updating WordPress with the command line

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.

Prism on a CDN

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!

A programmable color scheme

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!

Implementing the History interface

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.

Moving bililiteRange into the modern age.

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.

Extending Parsedown: attributes

Markdown Extra (including Parsedown Extra) allows for attributes to be applied to certain elements: headers, fenced code blocks, links, and images. I'd like to be able to apply them to any element. I'm going to use the syntax of Python Markdown attributes, but the attribute lists go before the elements. For block level elements, they go on their own line before the element.

My attribute lists start with {: (not just {) and end with }. Anything that would be legal in HTML (as is fine, since I didn't write my own parser. I just used DOMDocument. There are three special cases:

  • .foo is changed to class="foo". Note that this is different from the Python code, which appends class names that start with .. Repeated attribute names in actual HTML are ignored, so to use two classes, use class="foo bar", not .foo .bar.
  • #foo is changed to id="foo".
  • Two letters alone are changed to lang=xx, since I use that attribute so much.

Continue reading ›

Extending Parsedown: Block elements

Continuing work on extending Parsedown.

See the actual code.

Adding block-level elements is not much different from adding inline elements. Kavanot.name originally used <footer> elements to indicate the source of a block quote:

<blockquote>
    Do or do not. There is no try.
  </blockquote>
  <footer>Yoda</footer>
</blockquote>

While marking blockquotes this way is now acceptable, for a long time it wasn't, and the recommended way was with <figure> and <figcaption>. KavanotParsedown uses the latter model:

<figure>
  <blockquote>
    Do or do not. There is no try.
  <figcaption>Yoda</figcaption>
<figure>

But I start with creating a <footer> and then modifying the DOM. So I want to have a block element that I will indicate with "--" at the start of the line.

function __construct(){
  $this->BlockTypes['-'][] = 'Source'; // only line needed to indicate a block level element
  // ... rest of the constructor
}

protected function blockSource($Line, $Block = null){
  if (preg_match('/^--[ ]*(.+)/', $Line['text'], $matches)) {
    return array(
      'element' => array(
        'name' => 'footer',
        'handler' => array(
          'function' => 'lineElements',
          'argument' => $matches[1],
          'destination' => 'elements'
        ),
        'attributes' => array('class' => 'source') // for styling, add a class automatically
      )
    );
  }
}

so

>Do or do not. There is no try.
--Yoda

becomes

<blockquote>
    Do or do not. There is no try.
  <footer class="source" >Yoda</footer>
</blockquote>

I realized that I might want to add an attribution to an image as well, without it being in a <blockquote>, as

<p>
  <img src=/blog/blogfiles/pdf/smiley.png alt="Smile!"/>
  <footer class="source" >Some file I found on the web</footer>
</p>

But as it stands,

[Smile!](/blog/blogfiles/pdf/smiley.png)
--Some file I found on the web

doesn't work; the <p> ends before the <footer> starts:

<p>
  <img src=/blog/blogfiles/pdf/smiley.png alt="Smile!"/>
</p>
<footer class="source" >Some file I found on the web</footer>

so we need to check that the previous block wasn't a paragraph. If it was, then parse this line and add it to the paragraph as an internal element:

protected function blockSource($Line, $Block = null){
  if (preg_match('/^--[ ]*(.+)/', $Line['text'], $matches)) {
    if ($Block && $Block['type'] === 'Paragraph'){
      $Block['element']['handler']['argument'] .= "\n".$this->element($this->blockSource($Line)['element']);
      return $Block;
    }
    return array(
      'element' => array(
        'name' => 'footer',
        'handler' => array(
          'function' => 'lineElements',
          'argument' => $matches[1],
          'destination' => 'elements'
        ),
        'attributes' => array('class' => 'source') // for styling, add a class automatically
      )
    );
  }
}

and now it works, except that the footer is a child of the <p> instead of the <blockquote>. We'll have to fix that.

Extending Parsedown: Inline elements

Extending Parsedown involves adding elements to the $InlineTypes and $BlockTypes arrays, then adding methods to handle them.

See the actual code.

Italics

I use <i> a lot, to indicate transliterated words. So I use could use "/" to indicate that:
/Shabbat/ is a Hebrew word becomes <i>Shabbat</i> is a Hebrew word. To do that:
do

class myParsedown extends Parsedown{
  function __construct(){
    $this->InlineTypes['/'] []= 'Italic';
    // after adding all the new inline types, create the list of characters
    $this->inlineMarkerList = implode ('', array_keys($this->InlineTypes));
    // allow the character to be escaped by '\'
    $this->specialCharacters []= '/';
  }

  protected function inlineItalic($excerpt){
    if (preg_match('#^/(.+?)/#', $excerpt['text'], $matches)) {
      return array(
        'extent' => strlen($matches[0]), 
        'element' => array(
          'name' => 'i',
          'handler' => array(
            'function' => 'lineElements',
            'argument' => $matches[1],
            'destination' => 'elements'
          )
        )
      );
    }
}

Now, my transliterated words are almost always Hebrew, so I can automatically add the lang=he attribute:


  protected function inlineItalic($excerpt){
    if (preg_match('#^/(.+?)/#', $excerpt['text'], $matches)) {
      return array(
        'extent' => strlen($matches[0]), 
        'element' => array(
          'name' => 'i',
          'handler' => array(
            'function' => 'lineElements',
            'argument' => $matches[1],
            'destination' => 'elements'
          ),
          'attributes' => array('lang' => 'he') // Add attributes
        )
      );
    }
}

and now /Shabbat/ is a Hebrew word becomes <i lang=he>Shabbat</i> is a Hebrew word.

Cite

I also use the <cite>. I'm running out of single characters to indicate elements, so I'm going to redefine "-". I don't need two different markers for <em>.

  function __construct(){
    $this->InlineTypes['_'] = ['Cite']; // redefinition; I am replacing the old array (which was ['Emphasis'])
    // ... rest of the constructor as above
  }

  protected function inlineCite($excerpt){
    if (preg_match('#^_(.+?)_#', $excerpt['text'], $matches)) {
      return array(
        'extent' => strlen($matches[0]), 
        'element' => array(
          'name' => 'cite',
          'handler' => array(
            'function' => 'lineElements',
            'argument' => $matches[1],
            'destination' => 'elements'
          )
        )
      );
    }

And now _A Tale of Two Cities_ becomes <cite>A Tale of Two Cities</cite>