Kahlil Lechelt

Wrote a thing.

✍️ Salary Negotiations for JavaScript Developers (and Anybody Else)

www.kahlillechelt.com/2019/01/0…

Micro.blog Import Script

I made a Node.js script to import my Markdown with Frontmatter post from my Hugo blog into Micro.blog.

You are welcome to use it and here is how you set it up.

  • You need to have Node and npm installed.
  • Create a folder with a name like “microblog-import”.
  • Proceed to that folder in your terminal and install the required modules with npm install gray-matter got query-string.
  • In that folder you have to have a folder called “posts”.
  • That folder has to contain all of your Markdown posts.
  • The Frontmatter properties that the script is interested in are title, date and draft. It will ignore all files with draft: true, for all other files it will import them to Micro.blog titled with the value in the title property and dated with the value in the date property.
  • Now create a file called index.js in your microblog-import folder and copy-paste the following code into it:
  • Go to micro.blog/account/a…
  • Where it says “App name:” type in “Import Script” and then click on “Generate Token”.
  • Copy the generated token and substitute the string <YOUR_AUTH_TOKEN> with that token in the index.js file.
  • In the terminal, in that folder, run the script with node ..

That should do it. If needed, this could be extended and made into a command-line tool or even an Electron app with a GUI.

Happy new year 🎉.

‪I ❤️ blogs. It’s awesome to see so many devs getting back to blogging. I got serious about blogging again in the end of this year and 2019 is gonna be all blogs.‬

‪How awesome is RSS and JSON Feed? It’s powering podcasts and keeps them open and blogs are back w/ a vengance.‬

Carrying my 2yo daughter until she falls back asleep, after she woke up in the middle of the night, is still my favorite night-time activity. 🌛

All Dark Mode everything.

Just ported all my posts from www.kahlillechelt.com over to Micro.blog with a little Node script. Will post a little how-to soon.

Hi! I am posting this from my terminal with a curl command. Working on a script to import all my Hugo blog posts into Micro.blog.

This is how you post with curl:

curl micro.blog/micropub -H 'Authorization: Bearer <YOUR-APP-TOKEN>'  -d 'h=entry&content=<YOUR POST CONTENT URL-ENCODED>'

“Life is Eazi, Vol. 2 - Lagos to London” by @mreazi is a really nice, round product with a bunch of hits. Super crisp, listen-through, no-skip style.

My favorites are “Chicken Curry” feat. @Sneakbo & “She Loves Me” feat. @ChronixxMusic. 🎵

On the weekend I started reading It Doesn’t Have to Be Crazy at Work by @jasonfried and @dhh.

It’s such a joy to read. You can sense how each little paragraph was mulled over to get across a point in the most piercing clarity. 📚

The Kiko Micro.blog Theme Refined: "Kiko: System"

Update: I made a few tweaks to the CSS. The code below is updated.

It‘s happening 🎉. I am moving my personal blog to Micro.blog. I was hesitant at first because I really wanted to port my current blog design over. But after seeing the theme customization @dialog did I thought: hey that is not half bad!

They are using the Kiko Theme with the Roboto font, which is endlessly appropriate of course because they make the Micro.blog Android app and Roboto is the Android system font.

I am calling my Kiko customization “Kiko: System” because I am using a system font stack in order to use the system font depending on the OS the page is being viewed on.

This means the site will use San Francisco on Mac and iOS, Segoe on Windows and something like Ubuntu on Linux etc.

These fonts really look good in their respective OS environments.

On top of that I also refined the home page layout as well as the single post layout a little bit for readability and – in my eyes – beauty.

If you want the same look for your Micro.blog or base your theme customization on mine you can just set “Kiko” as your theme and paste this pile of CSS into your custom CSS field:

 html {
  font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
  font-weight: 400;
  color: #515151;
}

h1, h2, h3, h4, h5, h6 {
    letter-spacing: 0;
    font-weight: 700;
    line-height: 1.3;
}

h1 {
    font-size: 1.5rem;
}

header {
  margin-bottom: 2rem;
}

a {
    color: #515151;
    text-decoration: none;
    border-bottom: 1px solid #515151;
}

a:hover, a:focus {
    text-decoration: none;
}

.container {
    max-width: 30rem;
    padding: 2rem 1rem 2rem 1rem;
}

.masthead-title {
	font-weight: 900;
    text-transform: uppercase;
    text-align: center;
    padding-top: 2rem;
  	font-size: 2rem;
}

/* Portrait */
@media only screen 
  and (min-device-width: 320px) 
  and (max-device-width: 568px)
  and (-webkit-min-device-pixel-ratio: 2)
  and (orientation: portrait) {
    .masthead-title {
        font-size: 1.7rem;
    }
}

.masthead-title a {
    color: white;
    border-bottom: none;
    background: #333;
    padding: 5px 10px 7px 10px;
}

.masthead .masthead-nav {
	text-align: center;
    margin-bottom: 4rem;
}

.masthead .masthead-nav a {
    color: #aaa;
    font-weight: 400;
    text-transform: none;
    font-size: .9rem;
    border-bottom: none;
}

.masthead .masthead-nav a:hover, .masthead .masthead-nav a:focus {
    color: #8f8f8f;
}

.list-item.h-entry {
    margin-bottom: 3rem;
    border-bottom: 1px solid #f0f0f0;
    padding-bottom: 2rem;
}

.list-post-date a {
    color: #268bd2;
    text-decoration: none;
    border-bottom: none;
}

.masthead-title--small a {
    color: white;
    position: relative;
    border-bottom: none;
}

.masthead-title--small a:before {
    content: "← home";
    position: absolute;
    left: 0;
    color: #ccc;
    font-weight: 400;
    text-transform: none;
    font-size: .75rem;
    -webkit-transition: all .2s ease;
    -moz-transition: all .2s ease;
    transition: all .2s ease
}

.masthead-title--small a:hover:before {
  color: #8f8f8f;
}

.page-title {
    margin-bottom: .5rem;
    font-size: 1.7rem;
}

.post-date {
    color: #ccc;
}

.home-title {
  font-size: 1.5rem;
  margin-bottom: 1rem;
}

Manton Reece announced that Micro.blog supports Mastodon now. This makes the two microblogging alternatives compatible with each other to some extent and effectively connects the two networks. I think this could be a really significant step for at least Micro.blog. Congrats on the release @manton!

We launched an engineering blog at LogMeIn. I contributed one of the first articles in which I share some quick tips on how to boost accessibility on your legacy web app.

I always thought that accessibility on the web was super hard but I was surprised at how easy it can be. Yay for web standards and WAI-ARIA 🙌.

Mr. Eazi “Property” is my jam right now!

Trying to get my Micro.blog to GitHub mirror to work.

The library is growing.

Reactive Podcast Episode 100: "guts" Was A Little Gross

After a brief interruption:

We are back! Henning talks about finally launching the first site with the “unified platform”. It’s a Vue.js app talking to multiple APIs. Kahlil wrote about enums and made a small CLI tool for common Git tasks called “belly”.

Pick it up anywhere you get your podcasts or just listen in the browser.

I just learned about accesskey the other day and it blew my mind. Add keyboard shortcuts to your HTML like it’s nothing!

A thunderstorm in Berlin.

We’re looking for a new third co-host for our podcast @reactivepod. Unfortunately @rockbot was way to busy with her job at @slack to continue on a regular basis. Any ideas who might be a good fit?

Feeling the new Stefflon Don 🎧

Gave a talk about How To Build JavaScript Apps today. In front of students who were mainly interested in backend development. Needless to say they were thrilled.

The Tig Notaro Netflix Special is really great. Something about her comedy is downright genius. It’s the way how she plays with the tension in the room.

Currently hanging at the FrankfurtJS meetup. Golo Roden and Mathias Wagler are really killing it with wolkenkit.

Sup!? This is me testing out micro.blog for realz.

ZeroFux - A Stateless Unidirectional Data Flow Implemented With Custom Events

Undirectional Data Flow, Flux, Redux, Whateverux is essentially this:

  • something happens
  • what happened is being described with an action object
  • that action object is being dispatched through a central point, the dispatcher
  • on the other side of that dispatcher actions are matched to reducers
  • the reducers take the information of the actions and return state

This allows you to manage interactions in your UI in a stateless and synchronous manner.

As a developer, the only thing that really interests you are the actions and the reducers, all the rest is just implementation detail. Actions and reducers shape the app‘s state.

In the following I describe a really simple way how you can implement this type of state management with Custom Events.

Actions

First let’s define what an action is. An action is a JavaScript object that has one required property with the key „type“ and two optional ones with the keys „payload“ and „error“. Here an action defined as a TypeScript interface.

{
  type: string;
  payload?: any;
  error?: boolean;
}

I think we can agree that the JavaScript community mostly agrees on this definition pioneered by Redux, maybe minus the error property. I stole that from redux-observable.

Dispatcher

So, now that we have actions how do we dispatch them through the dispatcher and what is the dispatcher?

We want to use Custom Events. Those event get dispatched on a DOM element with the dispatchEvent method. That means our dispatcher is a DOM element. It is really not important which one but let’s just use the body element since that element is present on any web app.

const dispatcher = document.querySelector('body');

Great. Now that we have the dispatcher how do we dispatch an action? That’s where the Custom Events come in. We‘re using Custom Events because they allow us to add Custom event data (which we will, sneakily, call actions).

dispatcher.dispatchEvent(
  // The first argument of the Custom Event is 
  // the event name. The event name is the same
  // as the action type.
  // The second argument are options.
  new CustomEvent('SOME_ACTION', {
    // Here goes our custom data, the action object.
    detail: { 
      // Action type and event name are the same.
      type: 'SOME_ACTION', 
      // Here goes the optional data.
      payload: someData, 
      error: false,
    },
  })
)

So now that all these actions are being piped through one point in the DOM via Custom Events, we can match them to reducers.

In your component that expects some some state, take an array of action names that the component is interested in and set up event listeners. In the event listeners callback match a reducer with the same name per action to update the component state:

// Some example action names.
const ACTIONS = [
  'INCREMENT',
  'DECREMENT',
  'ADD_TEN',
];

...

// As a convention components need a setter and 
// a getter for the state property. 
// That allows you to call a render function or similar
// whenever state is set to a new value.
set state(s) {
  this._state = s;
  // Use lit-html or some other library that efficiently
  // can update DOM in the render function. 
  this.render();
}

get state() {
  return this._state;
}

// This is a method on some component.
setReducers() {
  ACTIONS.forEach(ACTION => {
    if (reducers[ACTION]) {
      // Again we are using the reference to the body
      // element as the dispatcher.
      dispatcher.addEventListener(ACTION, e => {
        // Reducers are kept in an object and matched
        // via action name.
        this.state = reducers[ACTION](e.detail, this.state);
      });
    } else {
      throw new Error(
        `Please add a reducer for the "${ACTION}" action.`
      );
    }
  });	
}

...

The ZeroFux Library

The code above is a little boilerplate-y so let’s make a simple library out of it. No Flux plus no Redux euquals ZeroFux:

export class ZeroFux {
  constructor(element) {
    if (element) {
      this.dispatcher = element;
    } else {
      this.dispatcher = document.querySelector('body');
    }
  }

  // The dipatch method takes an action argument
  // of the previously defined action type.
  dispatch(action) {
    this.dispatcher.dispatchEvent(
      new CustomEvent(action.type, {
        detail: action,
        // In case you set a custom dispatcher element
        // and want them to bubble.
        bubbles: true,
        // In case your custom dispatcher is in the
        // Shadow DOM and you want them to bubble between
        // the borders or Shadow DOM and regular DOM.
        compose: true,
      })
    )
  }

  // This method takes an array of action types
  // that can influence a component's state,
  // an object with reducers with the same names
  // as the action types and a reference
  // to the component on which we want to set
  // the state propery.
  setReducers(actionTypes, reducers, component) {
    actionTypes.forEach(actionType => {
      if (reducers[actionType]) {
        this.on(actionType, e => {
          const action = e.detail;
          component.state = reducers[actionType](component.state, action);
        });
      } else {
        throw new Error(
          `Please add a reducer for the "${actionType}" action.`
        );
      }
    });
  }
}

export const zeroFux = new ZeroFux();

🎉 tadaa!

It’s up on Github and npm right now if you want to try it.

You can see ZeroFux in action in this CodePen.

Side Effects

“Ah-haaa! How do we manage side effects with ZeroFux?”, you may ask. Well, there is actually a simple zero-fux way to deal with this.

Since these custom events are all streaming through one point in the DOM, the point that we can access via zeroFux.dispatcher, we can just listen to these events separately and fire effects on certain actions.

These side effects have to fire an action themselves when they are done with whatever they were doing. That’s how we introduce data coming from theses side effects synchronously back into the the data flow.

This is how your SideEffects class could look:

import { zeroFux } from 'zero-fux';

export class SideEffects {
  run() {
    const on = zeroFux.dispatcher.addEventListener;
    on('SOME_ACTION', () => { 
      someApi.doSomethingAsync()
        .then(data => zeroFux.dispatch({
          type: 'SOME_RESPONSE_ACTION',
          payload: data,
        }));
    });
    
    ...
  }
}

See it in action in the CodePen.

Conclusion

So there it is, a bare bones, straight forward unidirectional data flow implementation using custom events.

It uses the same principle I have also used in oddstream, a unidirectional data flow library implemented with RxJS: matching a “stream of actions” to reducers. This just has zero dependencies and is practically no code.

In Node this could be implemented using EventEmitter.

I think this solution for a unidirectional data flow could be used in apps of any size because ultimately all you have to manage and think about is actions and reducers, same as in any other unidirectional data flow solutions.