About this Site - The Terminal

I’m Back!

I’ve been plan­ning on con­tin­u­ing my About this Site” se­ries for a while now (like over 2 months), but I guess I’ve been dis­tracted with other things (as a high schooler does). Regardless, partly due to the prompt­ing of a friend, here is the third in­stall­ment.

Table of Contents

Inspiration

I be­lieve I have waxed po­etic about the com­mand line be­fore. I most def­i­nitely will in the fu­ture if I have not al­ready. I was, there­fore, kind of in­fat­u­ated with the idea of a web­site that has a lit­tle com­mand line built into it. This was im­ple­mented in my orig­i­nal web­site, where I even added scan lines and a sub­tle flicker to re­ally drive home the retro feel.

My old web­site

For this re­fresh, though, I wanted to make some­thing cleaner. More im­por­tantly, I wanted to make some­thing that re­quired very lit­tle main­te­nance.

Problems

First, let’s go through some of the prob­lems with the pre­vi­ous Javascript terminal. For one thing, there were er­rors that were straight up ig­nored (to be fair, this er­ror did­n’t break any func­tion­al­ity).

Look at all those er­rors

Also, just about every­thing, from the com­mands to the avail­able files, was hard­coded. This meant if I were to add a new sec­tion to my site, I would have to man­u­ally up­date that code. What a drag. It ended up with code that looked like this:

if (parsed.cmd === 'open') {
var target = '#' + parsed.arg;
$.smoothScroll({
scrollTarget: target
});
} else if (parsed.cmd === 'ls') {
out = 'about     projects     contact';
} else {
out = 'That command is not yet supported. Try ls or open.';
}

Ewwwww.

Specs

So for this new and im­proved ter­mi­nal, I wanted sev­eral fea­tures:

  1. Better de­sign (this meant a vis­i­ble, blink­ing cur­sor)
  2. Procedurally gen­er­ated files (this proved to be the most chal­leng­ing)
  3. Extensible com­mand sys­tem (I used ES6 classes for this)

Let’s Get Down to Business

We’re not go­ing to de­feat the Huns, but we are go­ing to make our new and im­proved ter­mi­nal.

Better Design

Okay, so this one is kind of vague. I mocked up the de­sign, which was heav­ily in­spired by my ac­tual ter­mi­nal.

The most dif­fi­cult part was prob­a­bly hid­ing the cur­sor and re­plac­ing it with a block cur­sor. I still don’t have a per­fect im­ple­men­ta­tion - in case you did­n’t re­al­ize, you can’t ac­tu­ally move the cur­sor around on the line. It just stays at the end of the line.

The thing about the cur­sor (or caret, if you’d like) is that it is al­ways the same color as your text. There’s no way to style it in­di­vid­u­ally. The trick here is to make the text trans­par­ent. Then, just add a text-shadow with your text color and no off­set. This keeps every­thing the same ex­cept for the caret, which is now trans­par­ent!

#input {
    color: transparent;
    text-shadow: 0 0 0 #0EFF1F;
}

Wow!! Nice.

Another big prob­lem was mak­ing scrolling work nicely. This was more dif­fi­cult than you would ini­tially ex­pect, be­cause the in­put is a contentEditable div, while the rest of the text is a sep­a­rate div. A cou­ple of overflow rules did the job nicely, though.

Procedural Generation

This is the cool part! My biggest gripe with the old im­ple­men­ta­tion was how much was hard­coded. I wanted to be able to more or less browse the en­tire site with the ter­mi­nal, so hard­cod­ing every­thing was not a vi­able op­tion.

This means I needed an API or some­thing to query for all of the dif­fer­ent parts of the site. Fortunately, I have Jekyll!

Here’s the amaz­ing thing about Jekyll: it can gen­er­ate any doc­u­ment. This includes not just Markdown or HTML files. The trick is to use Jekyll to gen­er­ate a JSON file with in­for­ma­tion about the web­site.

mind = blown

Here are the en­tire con­tents of terminal.json, which is the rel­e­vant JSON file:

---
---
{% capture nl %}
{% endcapture %}
{% assign files = site.documents %}
{% for page in site.html_pages %}
{% assign files = files | push: page %}
{% endfor %}
{
    {% for page in files %}
    "{{ page.url }}": {
        "content": "{{ page.content | replace: '"', "'" | normalize_whitespace}}"
    }{% unless forloop.last %},{% endunless %}
    {% endfor %}
}

That’s all there is to it. This gen­er­ates a JSON ob­ject of all of the pages in the web­site, with the url as the key and in­clud­ing the con­tent as the sole prop­erty of each page.

You end up with a JSON file look­ing like this.

Now, this is still not won­der­ful, be­cause all we have now is a bunch of file paths. This does­n’t en­code in­for­ma­tion about what is and is not in the cur­rent di­rec­tory.

So I added a pwd vari­able. I now re­al­ize pwd stands for print work­ing di­rec­tory”, so it does­n’t re­ally make sense to name a vari­able that, but in my mind, it just means our cur­rent work­ing di­rec­tory”, and ocwd just does­n’t have the same ring to it. I use this to keep track of where in the tree I am at the mo­ment

I then search down the list of files to see which paths are di­rect chil­dren of the pwd.

Everything else is pretty straight­for­ward: I just in­ject the content of the se­lected path.

This is what gets me the pro­ce­du­rally gen­er­ated func­tion­al­ity of the ter­mi­nal. That’s not all I wanted though; I wanted it to be even more dy­namic. I wanted the ter­mi­nal to be ex­ten­si­ble, so that I would be able to add new com­mands re­ally eas­ily.

Well then. Let’s get started.

Extensibility

I de­cided on us­ing ES6 classes to or­ga­nize the JavaScript into a self-con­tained unit.I’ve ex­per­i­mented with this be­fore to some suc­cess.

The ac­tual im­ple­men­ta­tion was very stright­for­ward. I ba­si­cally have a map of pos­si­ble com­mands mapped to the func­tions that they call. All of the com­mands have ac­cess to the in­stance vari­ables. Easy, clean stuff.

Wrap-up

So I guess that sums up the tech com­ponenet of the ter­mi­nal easter egg. Even though it was just an easter egg, it was prob­a­bly one of the things I spent the most time on, just to get every­thing work­ing okay.

Improvements

There are still some im­prove­ments to be made; some (obviously) less triv­ial than oth­ers.

  1. I want tab com­ple­tion.
  2. I want to be able to reg­is­ter new com­mands out­side of the ac­tual class
  3. Lists ex­tend hor­i­zon­tally, not ver­ti­cally.

Overall, I’m re­ally happy with how it turned out, though, and I think this is a great foun­da­tion to keep build­ing on.