Reading List

The most recent articles from a list of feeds I subscribe to.

The CSS Mindset

Ah yes, CSS. Hardly a week passes without it being the topic of a heated online discussion. It's too hard. It's too simple. It's unpredictable. It's not a real programming language. Peter Griffin struggles with blinds dot gif.

I don’t know why CSS sparks so many different emotions in developers, but I have a hunch as to why it can sometimes seem illogical or frustrating: You need a certain mindset to write good CSS.

Now, you probably need a mindset for coding in general, but the declarative nature of CSS makes it particularly difficult to grasp, especially if you think about it in terms of a “traditional” programming language.

Robin Rendle makes a very good point in this CSS-Tricks Newsletter where he finds that CSS lives somewhere between rigid, logical systems like Math and flexible, adaptive systems like natural languages:

a diagram with three boxes on an axis. the first box lists properties of mathematics: rigid; emperical; logical. The last says natural language is flexible; adaptive; illogical. CSS sits in between.
Comparison of Math, CSS and Language by Robin Rendle, 2019

Other programming languages often work in controlled environments, like servers. They expect certain conditions to be true at all times, and can therefore be understood as concrete instructions as to how a program should execute.

CSS on the other hand works in a place that can never be fully controlled, so it has to be flexible by default. It’s less about “programming the appearance” and more about translating a design into a set of rules that communicate the intent behind it. Leave enough room, and the browser will do the heavy lifting for you.

For most people who write CSS professionally, the mindset just comes naturally after a while. Many developers have that “aha!” moment when things finally start to click. It’s not just about knowing all the technical details, it’s more about a general sense of the ideas behind the language.

This is true whether you write CSS-in-JS, Sass or plain vanilla stylesheets. The output will always be CSS - so knowing these concepts will be helpful regardless of your tooling choice.

I tried to list some of them here.

Everything is a Rectangle

Permalink to “Everything is a Rectangle”

This seems obvious, given that the box model is probably one of the first things people learn about CSS. But picturing each DOM element as a box is crucial to understanding why things layout the way they do. Is it inline or block level? Is it a flex item? How will it grow/shrink/wrap in different contexts?

Open your devtools and hover over elements to see the boxes they're drawing, or use a utility style like outline: 2px dotted hotpink to visualize its hidden boundaries.

The Cascade is your Friend

Permalink to “The Cascade is your Friend”

The Cascade - a scary concept, I know. Say “Cascade” three times in front of a mirror and somewhere, some unrelated styling will break.

While there are legitimate reasons to avoid the cascade, it doesn’t mean that it’s all bad. In fact, when used correctly, it can make your life a lot easier.

The important part is to know which styles belong on the global scope and which are better restricted to a component. It also helps to know the defaults that are passed down, to avoid declaring unnecessary rules.

As much as necessary, as little as possible

Permalink to “As much as necessary, as little as possible”

Aim to write the minimal amount of rules necessary to achieve a design. Fewer properties mean less inheritance, less restriction and less trouble with overrides down the line. Think about what your selector should essentially do, then try to express just that. There’s no point in declaring width: 100% on an element that’s already block-level. There’s no need to set position: relative if you don’t need a new stacking context.

Avoid unnecessary styles, and you avoid unintended consequences.

Shorthands have long effects

Permalink to “Shorthands have long effects”

Some CSS features can be written in “shorthand” notation. This makes it possible to declare a bunch of related properties together. While this is handy, be aware that using the shorthand will also declare the default value for each property you don’t explicitly set. Writing background: white; will effectively result in all these properties being set:

background-color: white;
background-image: none;
background-position: 0% 0%;
background-size: auto auto;
background-repeat: repeat;
background-origin: padding-box;
background-clip: border-box;
background-attachment: scroll;

It's better to be explicit. If you want to change the background color, use background-color.

Always Be Flexible

Permalink to “Always Be Flexible”

CSS deals with a large amount of unknown variables: screen size, dynamic content, device capabilities - the list goes on. If your styles are too narrow or restrictive, chances are one of these variables will trip you up. That’s why a key aspect in writing good CSS is to embrace its flexibility.

Your goal is to write a set of instructions that is comprehensive enough to describe what you want to achieve, yet flexible enough to let the browser figure out the how by itself. That’s why its usually best to avoid “magic numbers”.

Magic numbers are random hard values. Something like:

.thing {
    width: 218px; /* why? */
}

Whenever you find yourself tapping the arrow key in your devtools, adjusting a pixel value to make something fit - that’s probably a magic number. These are rarely the solution to a CSS problem, because they restrict styles to a very specific usecase. If the constraints change, that number will be off.

Instead, think about what you actually want to achieve in that situation. Alignment? An aspect ratio? Distributing equal amounts of space? All of these have flexible solutions.

In most cases, it's better to define a rule for the intent, rather than hard-code the computed solution to it.

Context is Key

Permalink to “Context is Key”

For many layout concepts it’s imperative to understand the relationship between elements and their container. Most components are sets of parent and child nodes. Styles applied to the parent can affect the descendants, which might make them ignore other rules. Flexbox, Grid and position:absolute are common sources of such errors.

When in doubt about a particular element behaving different than you'd want it to, look at the context it's in. Chances are something in its ancestry is affecting it.

Content will change

Permalink to “Content will change”

The number one mistake made by designers and developers alike is assuming that things will always look like they do in the static mockup. I can assure you, they will not.

Strings may be longer than intended or contain special characters, images might be missing or have weird dimensions. Displays may be very narrow or extremely wide. Those are all states you need to anticipate.

Always be aware that what you see is just one UI state in a bigger spectrum. Instead of styling the thing on your screen, try to build a "blueprint" of the component. Then make sure that whatever you throw at it won't break your styling.

Find Patterns and re-use them

Permalink to “Find Patterns and re-use them”

When you set out to turn a design mockup into code, it’s often helpful to take inventory of the different patterns included first. Analyse each screen and take note of any concept that occurs more than one. It might be something small like a typographic style, or large like a certain layout pattern. What can be abstracted? What’s unique?

Thinking of pieces in a design as standalone things makes them easier to reason about, and helps to draw the boundaries between components.

Use consistent Names

Permalink to “Use consistent Names”

A surprisingly large part of programming in general is coming up with good names for stuff. In CSS, it helps to stick to a convention. Naming schemes like BEM or SMACSS can be very helpful; but even if you don’t use them, stick to a certain vocabulary. You’ll find that certain component patterns come up over and over - but is it called a “hero” or a “stage”? Is it “slider” or “carousel”?

Establish a routine in how you name parts of your UI, then stick to that convention in all your projects. When working in a team, it can be helpful to agree on component names early on and document them somewhere for new team members.


All these things were important for me to understand, but your personal experience as to what matters most might be different. Did you have another “aha” moment that made you understand CSS better? Let me know!

Update: I did a talk about the CSS Mindset at CSS-Minsk-JS in September. There’s also a video recording available, if you prefer that.

Further Reading

Permalink to “Further Reading”

A Webring Kit

After Tatiana Mac proposed to bring webrings back, I hacked something new together over the weekend: A starter kit for hosting your own webring!

What’s a Webring?

Permalink to “What’s a Webring?”

It’s a blast from the past: In the 90s, sites about a common topic could join together in a central index. To be a member, you had to embed a little widget on your page that contained a “forward”, a “backward”, and a “random” button. These buttons would then link to the next or previous site in the ring.

Since the term "webring" is trademarked in the US, this needs another cool name. Know any? Please add it to this thread!

A curated community

Permalink to “A curated community”

To keep the ring from getting spammed or flooded with trolls, it has to be curated. The project does that by hosting the member index on Github, in a simple JSON file. Admins can accept or decline pull requests from people who want to join the ring, after reviewing their sites. There’s also a Code of Conduct that every member has to follow in order to be part of the ring.

For people who are not technical enough to submit a pull request, there’s also a simple signup form (using Netlify forms) to send the admin your site’s info via email and let them add you.

a card showing the webring description and memberlist
You can build webrings for anything

Free and Open

Permalink to “Free and Open”

I wanted to make this as easy as possible, so people can start linking their personal sites together straight away. So I made the boilerplate using Eleventy. After forking the codebase, the proud webring admin only needs to set a title and a bit of meta data.

Eleventy then generates a site like this that lists all the members, shows the Code of Conduct and the instructions on how to join.

You can deploy it to Netlify, a free static site host, with just a few clicks. Netlify also lets you either use one of their subdomains, or a custom one you own.

A central widget

Permalink to “A central widget”

Members of the ring can copy a code snippet to embed a banner on their site. I borrowed a bit from Twitters embed widget here: The basic markup is just a link to the index, and the prev/random/next links. But if you also include the script tag, it will replace that with a custom web component, designed by the ring admin.

<webring-banner>
    <p>Member of the <a href="https://webringdemo.netlify.com">An Example Webring</a> webring</p>
    <a href="https://webringdemo.netlify.com/prev">Previous</a>
    <a href="https://webringdemo.netlify.com/random">Random</a>
    <a href="https://webringdemo.netlify.com/next">Next</a>
</webring-banner>
<script async src="https://webringdemo.netlify.com/embed.js" charset="utf-8"></script>

This will automatically show the title, member count, maybe a logo. And it can be edited from a central location. It might look something like this:

Member of the An Example Webring webring

Previous Random Next

RSS Feeds

Permalink to “RSS Feeds”

If a member publishes an RSS feed on their site, they can add that to the ring as well: the index page will generate an OPML file, so people can subscribe to all members at once.

Host your own Ring!

Permalink to “Host your own Ring!”

If you want to start your own webring, go ahead! Fork the repository on Github and follow the instructions there - It’s free and doesn’t take long!

Read More

Permalink to “Read More”

On Simplicity

In the 1997 movie "Contact", Jodie Foster discovers an alien signal that contains the construction plans for a spaceship. Trying to assemble it, the engineers are surprised to find that the crew capsule is just an empty metal pod.

“That shit’s unsafe”, they say (I’m paraphrasing), so they attach a complicated wall-mounted seat to the inside. When the ship launches, that seat starts to pick up heavy vibrations and violently breaks apart. Foster releases her seatbelt seconds before it kills her and ultimately finds that the design was perfect all along, enjoying the rest of the ride in smooth anti-gravity.

We assume that complex problems always require complex solutions. We try to solve complexity by inventing tools and technologies to address a problem; but in the process we create another layer of complexity that, in turn, causes its own set of issues.

Simplicity as a Feature

Permalink to “Simplicity as a Feature”

Obviously not every problem has a simple solution, and most complex tools exist because of real usecases. But I think there’s a lot of value in actively questioning the need for complexity. Sometimes the smarter way to build things is to try and take some pieces away, rather than add more to it.

Static sites are on the rise again now, precisely because they are simple. They don’t try to manage serverside code with clever abstractions - they don’t have any. They don’t try to prevent security breaches with advanced firewalls - they get rid of the database entirely.

Some of the most important things in the world are intentionally designed “simple”. In any system, the potential for error directly increases with its complexity - that’s why most elections still work by putting pieces of paper in a box.

Think for Yourself, Question Complexity

Permalink to “Think for Yourself, Question Complexity”

Developers are obsessed with the notion of “best practice”.
It implies that there is one correct way of doing things, and all other solutions are either imperfect or, at worst, “anti-patterns”. But the definition of best practice changes everytime a new technology arises, rendering the previous solution worthless garbage (even though it still gets the job done).

There is an undeniable ego factor to the way we use technology in our projects. To show everyone else how clever we are, we come up with harder and harder ways to achieve our tasks. And of course they all solve specific problems - but that does not mean they are always the best solution, regardless of context.

It’s cool to use the latest and greatest tech; but we should always ask if our choices really benefit the user, or if we do it mostly for ourselves. After all, the “Developer Experience” is only a means to an end.

And if we’re talking DX - I’ll take simplicity over features any day.

Using Webmentions in Eleventy

In last week's post, I talked about syndicating content from a static site to Twitter. But getting content out is only half the challenge.

The real value of social media (apart from the massive ad revenue and dystopian data mining) is in the reactions we get from other people. The likes, reposts and replies - they’re what makes it “social”. To gain control over our own content, we need to capture these interactions as well and pull them back to our sites. In indieweb terms, that’s known as “backfeed”.

Hello Webmentions

Permalink to “Hello Webmentions”

A Webmention is an open standard for a reaction to something on the web. It's currently in W3C recommendation status. When you link to a website, you can send it a Webmention to notify it.

It's comparable to pingbacks, except that webmentions contain a lot more information than a simple "ping". They can be used to express likes, reposts, comments or other things.

To make a site support webmentions, it needs to declare an endpoint to accept them. That endpoint can be a script hosted on your own server, or in the case of static sites, a third-party service like webmention.io.

Webmention.io is a free service made by indieweb pioneer Aaron Parecki that does most of the groundwork of receiving, storing and organizing incoming webmentions for you. It’s awesome!

To use it, sign up for a free account there using the IndieAuth process, then include a link tag in the head of your site:

<link rel="pingback" href="https://webmention.io/mxb.dev/xmlrpc">
<link rel="webmention" href="https://webmention.io/mxb.dev/webmention">

Turning social media interactions into webmentions

Permalink to “Turning social media interactions into webmentions”

Cool. So that’s all very nice, but the real party is still over at [currently hip social network], you say. Nobody ever sends me any webmentions.

Well, while your platform of choice is still around, you can use a tool to automatically turn social media interactions into beautiful open webmentions. Bridgy is another free service that can monitor your Twitter, Facebook or Instagram activity and send a webmention for every like, reply or repost you receive.

So if you were to publish a tweet that contains a link back to your site, and somebody writes a comment on it, Bridgy will pick that up and send it as a webmention to your endpoint!

The resulting entry on webmention.io then looks something like this:

    {
      "type": "entry",
      "author": {
        "type": "card",
        "name": "Sara Soueidan",
        "photo": "https://webmention.io/avatar/pbs.twimg.com/579a474c9b858845a9e64693067e12858642fa71059d542dce6285aed5e10767.jpg",
        "url": "https://sarasoueidan.com"
      },
      "url": "https://twitter.com/SaraSoueidan/status/1022009419926839296",
      "published": "2018-07-25T06:43:28+00:00",
      "wm-received": "2018-07-25T07:01:17Z",
      "wm-id": 537028,
      "wm-source": "https://brid-gy.appspot.com/comment/twitter/mxbck/1022001729389383680/1022009419926839296",
      "wm-target": "https://mxb.dev/blog/layouts-of-tomorrow/",
      "content": {
        "content-type": "text/plain",
        "value": "This looks great!",
        "text": "This looks great!"
      },
      "in-reply-to": "https://mxb.dev/blog/layouts-of-tomorrow/",
      "wm-property": "in-reply-to",
      "wm-private": false
    }

But wait, there’s more!

Permalink to “But wait, there’s more!”

The beauty of webmentions is that unlike with regular social media, reactions to your content are not limited to users of one site. You can combine comments from Facebook and Twitter with replies people posted on their own blogs. You can mix retweets and shares with mentions of your content in newsletters or forum threads.

You also have complete control over who and what is allowed in your mentions. Content silos often only allow muting or blocking on your own timeline, everyone else can still see unwanted or abusive @-replies. With webmentions, you’re free to moderate reactions however you see fit. Fuck off, Nazis!

Including webmentions in static sites

Permalink to “Including webmentions in static sites”

Once the webmention endpoint is in place, we still need to pull the aggregated data down to our site and display it in a meaningful way.

The way to do this depends on your setup. Webmention.io offers an API that provides data as a JSON feed, for example. You can query mentions for a specific URL, or get everything associated with a particular domain (allthough the latter is only available to site owners.)

My site uses Eleventy, which has a conventient way to pull in external data at build time. By providing a custom function that queries the API, Eleventy will fetch my webmentions and expose them to the templates when generating the site.

// data/webmentions.js
const API_ORIGIN = 'https://webmention.io/api/mentions.jf2'

module.exports = async function() {
    const domain = 'mxb.dev'
    const token = process.env.WEBMENTION_IO_TOKEN
    const url = `${API_ORIGIN}?domain=${domain}&token=${token}`

    try {
        const response = await fetch(url)
        if (response.ok) {
            const feed = await response.json()
            return feed
        }
    } catch (err) {
        console.error(err)
        return null
    }
}

The feed can now be accessed in the {{ webmentions }} variable.

Here’s the complete function if you’re interested. Other static site generators offer similiar methods to fetch external data.

Parsing and Filtering

Permalink to “Parsing and Filtering”

Now that the raw data is available, we can mold it into any shape we’d like. For my site, the processing steps look like this:

  • Filter the raw data for each post, only include mentions targeting that URL.
  • Only allow “mentions” and “replies” in the comment section. Likes and Reposts go somewhere else.
  • Remove entries that dont have any content to display.
  • Sanitize the output - strip HTML tags, truncate long content, etc.
// filters.js
const sanitizeHTML = require('sanitize-html')

function getWebmentionsForUrl(webmentions, url) {
    const allowedTypes = ['mention-of', 'in-reply-to']

    const hasRequiredFields = entry => {
        const { author, published, content } = entry
        return author.name && published && content
    }
    const sanitize = entry => {
        const { content } = entry
        if (content['content-type'] === 'text/html') {
            content.value = sanitizeHTML(content.value)
        }
        return entry
    }

    return webmentions
        .filter(entry => entry['wm-target'] === url)
        .filter(entry => allowedTypes.includes(entry['wm-property']))
        .filter(hasRequiredFields)
        .map(sanitize)
}

In Eleventy’s case, I can set that function as a custom filter to use in my post templates.
Each post will then loop over its webmentions and output them underneath.

<!-- webmentions.njk -->
{% set mentions = webmentions | getWebmentionsForUrl(absoluteUrl) %}
<ol id="webmentions">
{% for webmention in mentions %}
    <li class="webmentions__item">
        {% include 'webmention.njk' %}
    </li>
{% endfor %}
</ol>

You can see the result by scrolling down to the end of this post (if there are any replies 😉).

Client-Side Rendering

Permalink to “Client-Side Rendering”

Because static sites are, well, static - it’s possible that new mentions have happened since the last build. To keep the webmention section up-to-date, there’s an extra step we can take: client side rendering.

Remember I said the webmention.io API can be used to only fetch mentions for a specific URL? That comes in handy now. After the page has loaded, we can fetch the latest mentions for the current URL and re-render the static webmention section with them.

On my site, I used Preact to do just that. It has a very small (~3kB) footprint and lets me use React’s mental model and JSX syntax. It would probably also have been possible to re-use the existing nunjucks templates, but this solution was the easiest and most lightweight for me.

I essentially used the same logic here as I did in the static build, to ensure matching results. The rendering only starts after the API call returned valid data though - if anything goes wrong or the API is unavailable, there will still be the static content as a fallback.

// webmentions/index.js
import { h, render } from 'preact'
import App from './App'

...
const rootElement = document.getElementById('webmentions')
if (rootElement) {
    fetchMentions()
        .then(data => {
            if (data.length) {
                render(<App webmentions={data} />, rootElement)
            }
        })
        .catch(err => {
            console.error(err)
        })
}

I also made an Eleventy Starter Template with basic webmention support, using some of the techniques in this post. Check it out!

There are of course still some missing pieces, most notably the ability to send outgoing webmentions to URLs linked to in your own blog posts. I might have to look into that.

Update: Outgoing Webmentions!

Permalink to “Update: Outgoing Webmentions!”

Remy Sharp has recently published a very useful new tool that takes care of handling outgoing webmentions for you. Webmention.app is a platform agnostic service that will check a given URL for links to other sites, discover if they support webmentions, then send a webmention to the target.

You can use that service in a number of ways, including your own command line. If you host your site on Netlify though, it’s also very straightforward to integrate it using deployment webhooks!

Jekyll Plugin

Permalink to “Jekyll Plugin”

My implementation was heavily inspired by Aaron Gustafson’s excellent Jekyll Plugin (link below), which goes even further with customization and caching options. If you’re running a Jekyll site, use that for almost instant webmention support 👍.

Further Resources

Permalink to “Further Resources”

Syndicating Content to Twitter

One of the core principles of the IndieWeb is that people should own their own content. Controlling how and where they publish makes users more independent from big content silos.

However, the main reason why people publish on Twitter / Medium or other platforms is that they can reach a much bigger audience there - everyone’s on them, so you have to be too. Publishing on a personal site can cut you off from those readers. That’s why it might be a good idea to automatically post copies of your content on these sites whenever you publish something new.

This practice is known as “POSSE” (Publish on your Own Site, Syndicate Elsewhere). It enables authors to reach people on other platforms while still keeping control of the original content source.

For the recent relaunch of my personal website, I wanted to embrace some of these ideas. I included a section called notes featuring small, random pieces of content - much like tweets. These notes are perfect candidates for syndication to Twitter.

Syndication on Static Sites

Permalink to “Syndication on Static Sites”

My site is built with Eleventy, a static site generator based on node, and hosted on Netlify. Static sites are awesome for a variety of reasons, but interacting with other platforms typically requires some serverside code - which they don’t have.

Luckily though, Netlify provides a service called “Functions”, which lets you write custom AWS lambda functions without the hassle of dealing with AWS directly. Perfect! 🤘

A content feed

Permalink to “A content feed”

The first step is to publish a machine-readable feed of the content we want to syndicate. That’s exactly what RSS-Feeds are for - but they’re usually in XML format, which is not ideal in this case.

For my own site, I chose to provide notes as a simple JSON object. I already have an atom feed for content readers, and JSON makes the note processing easier later on.

My feed looks something like this:

// notes.json
[
    {
        "id": 1,
        "date": "2018-12-02T14:20:17",
        "url": "https://mxb.dev/notes/2018-12-02/",
        "content": "Here's my first note!",
        "syndicate": true
    },
    {...}
]

All entries also include a custom syndicate flag that overrides the auto-publishing behaviour if necessary.

Event-Triggered Functions

Permalink to “Event-Triggered Functions”

Now for the tricky part: we need to write a lambda function to push new notes to Twitter. I won’t go into detail on how to build lambda functions on Netlify, there are already some great tutorials about this:

Be sure to also check out the netlify-lambda cli, a very handy tool to test and build your functions in development.

To trigger our custom function everytime a new version of the site was successfully deployed, we just need to name it deploy-succeeded.js. Netlify will then automatically fire it after each new build, while also making sure it’s not executable from the outside.

Whenever that function is invoked, it should fetch the list of published notes from the JSON feed. It then needs to check if any new notes were published, and whether they should be syndicated to Twitter.

// deploy-succeeded.js
exports.handler = async () => {
    return fetch('https://mxb.dev/notes.json')
        .then(response => response.json())
        .then(processNotes)
        .catch(err => ({
            statusCode: 422,
            body: String(err)
        }))
}

Since we will have to interact with the Twitter API, it’s a good idea to use a dedicated helper class to take some of that complexity off our hands. The twitter package on npm does just that. We will have to register for a developer account on Twitter first though, to get the necessary API keys and tokens. Store those in your project’s .env file.

TWITTER_CONSUMER_KEY=YourTwitterConsumerKeyHere
TWITTER_CONSUMER_SECRET=YourTwitterConsumerSecretStringHere
TWITTER_ACCESS_TOKEN_KEY=12345678-YourTwitterAccessTokenKeyHere
TWITTER_ACCESS_TOKEN_SECRET=YourTwitterAccessTokenSecretStringHere

Use these keys to initialize your personal Twitter client, which will handle the posting for your account.

// Configure Twitter API Client
const twitter = new Twitter({
    consumer_key: process.env.TWITTER_CONSUMER_KEY,
    consumer_secret: process.env.TWITTER_CONSUMER_SECRET,
    access_token_key: process.env.TWITTER_ACCESS_TOKEN_KEY,
    access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET
})

Right. Now we need to look at the notes array and figure out what to do. To keep it simple, let’s assume the latest note is a new one we just pushed. Since the JSON feed lists notes in descending date order, that would be the first item in the array.

We can then search twitter for tweets containing the latest note’s URL (we will include that in every syndicated tweet to link back to the original source). If we find anything, then it’s already been published and we don’t need to do anything. If not, we’ll go ahead.

const processNotes = async notes => {
    // assume the last note was not yet syndicated
    const latestNote = notes[0]

    // check if the override flag for this note is set
    if (!latestNote.syndicate) {
        return {
            statusCode: 400,
            body: 'Latest note has disabled syndication.'
        }
    }

    // check twitter for any tweets containing note URL.
    // if there are none, publish it.
    const search = await twitter.get('search/tweets', { q: latestNote.url })
    if (search.statuses && search.statuses.length === 0) {
        return publishNote(latestNote)
    } else {
        return {
            statusCode: 400,
            body: 'Latest note was already syndicated.'
        }
    }
}

Next, we need to prepare the tweet we want to send. Since our self-published note does not have the same restrictions that twitter has, we should format its content first.

My implementation simply strips all HTML tags from the content, makes sure it is not too long for Twitter’s limit, and includes the source url at the end. It’s also worth noting that Eleventy will escape the output in the JSON feed, so characters like " will be encoded to &quot; entities. We need to reverse that before posting.

// Prepare the content string for tweet format
const prepareStatusText = note => {
    const maxLength = 200

    // strip html tags and decode entities
    let text = note.content.trim().replace(/<[^>]+>/g, '')
    text = entities.decode(text)

    // truncate note text if its too long for a tweet.
    if (text.length > maxLength) {
        text = text.substring(0, maxLength) + '...'
    }

    // include the note url at the end.
    text = text + ' ' + note.url
    return text
}

When everything is done, we just need to send our note off to Twitter:

// Push a new note to Twitter
const publishNote = async note => {
    const statusText = prepareStatusText(note)
    const tweet = await twitter.post('statuses/update', {
        status: statusText
    })
    if (tweet) {
        return {
            statusCode: 200,
            body: `Note ${note.date} successfully posted to Twitter.`
        }
    }
}

Hopefully that all worked, and you should end up with something like this in your timeline:

🎉 You can find the finished lambda function here.

Further Resources

Permalink to “Further Resources”