Simplify site

This commit is contained in:
Erick Ruiz de Chavez 2021-08-10 04:50:07 -04:00
parent fa1030236b
commit fc0108d7e8
No known key found for this signature in database
GPG key ID: 18853A33FA62DDC9
26 changed files with 10 additions and 6045 deletions

View file

@ -1,57 +1,11 @@
const pluginTailwindCSS = require("eleventy-plugin-tailwindcss");
const pluginRss = require("@11ty/eleventy-plugin-rss");
const cacheBuster = require("@mightyplow/eleventy-plugin-cache-buster");
module.exports = function (eleventyConfig) {
eleventyConfig.addPassthroughCopy("_content/images");
eleventyConfig.addPlugin(pluginTailwindCSS, {
src: "_includes/**/*.css",
keepFolderStructure: false,
});
eleventyConfig.addPlugin(pluginRss);
const cacheBusterOptions = {
createResourceHash() {
return Date.now();
},
// outputDirectory: "_site",
};
eleventyConfig.addPlugin(cacheBuster(cacheBusterOptions));
eleventyConfig.addCollection("archive", (collectionApi) => {
const formatter = new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
});
const now = Date.now();
return collectionApi
.getFilteredByGlob("**/blog/*.md")
.reverse()
.filter((item) => item.date.getTime() <= now)
.reduce((agg, item) => {
const group = formatter.format(item.date);
if (!agg[group]) {
agg[group] = [];
}
agg[group].push(item);
return agg;
}, {});
});
eleventyConfig.addFilter("dateFormat", function (value) {
return new Intl.DateTimeFormat("en-US", { dateStyle: "full" }).format(
value
);
});
return {
dir: {
input: "_content",

View file

@ -1,20 +0,0 @@
---
title: Blog
---
<header>
<h1>{{ title }}</h1>
</header>
{% from "macros.njk" import excerpt %}
{% for dateStr, items in collections.archive %}
<section>
<header>
<h2>{{ dateStr }}</h2>
</header>
{% for post in items %}
{{ excerpt(post) }}
{% endfor %}
</section>
{% endfor %}

View file

@ -1,124 +0,0 @@
---
title: Web Workers Embebidos
date: 2018-09-15
tags:
- javascript
- showdev
- spanish
- tutorial
lang: es
---
Recientemente en la oficina estuve trabajando con algunos problemas de rendimiento en unos componentes con tareas pesadas para el procesador, y entre las lluvias de ideas que tuvimos para solucionar los mismos salió el tema de los Web Workers; debo de confesar que usarlos siempre me ha dado curiosidad pero a la fecha no había tenido oportunidad mas que de probarlos en un par de ocaciones y solo por experimentar.
<!--more-->
En [MDN](https://developer.mozilla.org/es/docs/Web/API/Web_Workers_API):
> Los Web Workers hacen posible ejecutar la operación de un script en un hilo en segundo plano separado de la ejecución el hilo principal de la aplicación web. La ventaja de esto es que un proceso laborioso puede actuar en un hilo separado, permitiendo al hilo principal (normlamente la UI) ejecutarse sin ser bloqueado o ralentizado.
Dado que los workers requieren un archivo independiente (o así lo muestran la mayoría de los casos) estaba preocupado de tener que reconfigurar y hacer (literalmente) malabares con nuestros archivos de Webpack y Angular pues dicho trabajo es para una aplicación en Angular 6 que **no** usa `ng-cli` mas que para generar algunos archivos :facepalm:.
Investigando y leyendo la excelente documentación de MDN me tope con [una guía de los mismos, muy completa](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) que me soluciono la vida: Como hacer un Web Worker embebido en mi código, esto es, sin tener que usar un archivo adicional :clap::tada:.
Para hacerlo necesitas:
- Un browser con soporte de Web Workers, [Blobs](https://developer.mozilla.org/en-US/docs/Web/API/Blob) y [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL).
- Un problema que requiera procesamiento intensivo.
- Un poco de tiempo para experimentar.
Sea cual sea tu framework, esta técnica se puede usar sin tener que hacer ninguna configuración extra; lo que si debes tener en cuenta que aunque no vamos a tener un archivo adicional para el worker, este mismo **no** va a tener acceso a las librerías que uses en el código, ni al scope en donde se defina la función. Estamos literalmente haciendo un archivo de Web Worker embebido en nuestro código.
Para definir nuestro Web Worker lo haremos como si lo hiciéramos en cualquier ejemplo, con la única diferencia que estará dentro de una función:
// Un Web Worker, envuelto en una función
function trabajador() {
onmessage = (event) => {
console.log(`Recibimos un mensaje del programa principal: ${event.data}`);
// Respondemos con otro mensaje
postMessage('¡Hola Mundo!');
}
}
Hasta aquí lo único nuevo es que estamos envolviendo el código del worker en una función, pero lo interesante es esto:
// Regresa una URL a partir de una función
function deFuncionAUrl(fn) {
const blob = new Blob([`(${fn.toString()})()`], { type: 'application/javascript' })
return URL.createObjectURL(blob);
}
¿Qué esta pasando aquí? Se ve mas complejo de lo que realmente es, pero veámoslo a detalle:
- Tomamos nuestra (cualquier) función `fn` y la convertimos a texto, la cual literalmente va a contener el código de nuestra función como un string.
- Hecho esto, lo envolvemos en una IIFE, y creamos un `Blob` (básicamente un archivo) con dicho contenido.
- Finalmente usamos `URL` para generar una URL a partir de este blob.
Hecho esto, cuando llamemos `deFuncionAUrl` pasándole una función como parámetro nos va a regresar una URL con nuestro &#8220;archivo&#8221; del worker, así que ahora podemos usarla como cualquier otro Web Worker:
const url = deFuncionAUrl(trabajador);
const worker = new Worker(url);
worker.onmessage = (event) => {
// Recibimos y hacemos algo con la respuesta del worker
console.log(`Recibimos un mensaje del worker: ${event.data}`);
// Una vez que ya no necesitamos el worker, lo terminamos
worker.terminate();
};
// Enviamos el mensaje inicial al worker para empezar el trabajo
worker.postMessage('¡Hola Worker!');
Finalmente, poniendo todas las piezas juntas, aquí esta un ejemplo:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Web Worker Embebido</title>
</head>
<body>
Abre la consola de JavaScript para ver los mensajes.
<script>
// Un Web Worker, envuelto en una función
function trabajador() {
onmessage = (event) => {
console.log(`Recibimos un mensaje del programa principal: ${event.data}`);
// Respondemos con otro mensaje
postMessage('¡Hola Mundo!');
}
}
// Regresa una URL a partir de una función
function deFuncionAUrl(fn) {
const blob = new Blob([`(${fn.toString()})()`], { type: 'application/javascript' })
return URL.createObjectURL(blob);
}
const url = deFuncionAUrl(trabajador);
const worker = new Worker(url);
worker.onmessage = (event) => {
// Recibimos y hacemos algo con la respuesta del worker
console.log(`Recibimos un mensaje del worker: ${event.data}`);
// Una vez que ya no necesitamos el worker, lo terminamos
worker.terminate();
};
// Enviamos el mensaje inicial al worker para empezar el trabajo
worker.postMessage('¡Hola Worker!');
</script>
</body>
</html>
Espero que encuentren tan útil esta información como la encontré yo, y aunque al final terminamos no usando web workers, pude finalmente aprender un poco mas sobre estos y experimentar con un problema de la vida real.

View file

@ -1,140 +0,0 @@
---
title: Automation with CLI and Node
date: 2019-07-19
tags:
- javascript
- productivity
- showdev
---
In this article you will read about my love and experience about automation, the experiences I have had for several years with different teams, a real live case of automation, and how I solved it from manual process to a personal tool, to a sharable CLI tool for my team.
---
If you have had a tech discussion with me or you listen to [my podcast](https://devnights.mx) (in Spanish 🇲🇽), you&#8217;ll most likely know I love automation. Almost every time I have to do something manually, I&#8217;ll find a way to do it as automated as possible, so I do not have to keep repeating the same things over and over again. That is what computers are for, right? Do repetitive tasks, do it fast, and do it right.
Over the years, I&#8217;ve seen two kinds of people, those of us who love automation, and the rest of the world who don&#8217;t care at all. Surprisingly, it is the same with tech- and non-tech-savvy people, I would love to say all my co-workers on my different jobs are automation lovers, but the reality is that most of them are on the other side of the coin.
One thing I have noticed about the tech-savvy people is that we usually like using tools to do our job. I have some excellent automation tools on my belt: Shell Scripts, Automator, Alfred, Keyboard Maestro (my favorite) to mention some and recently I&#8217;ve had the opportunity to save myself much time on a task we have to do regularly. In a nutshell, every time we finish a feature, we need to hand over the feature to our QA team so they can validate it, but given the complexity of the project and the build pipeline, there is no easy way to know when a feature will be deployed to a testing environment. Someone has to find the latest commit SHA manually and then go into the CI/CD tool, look at the most recent build, open the build log, and verify what the latest commit used was. So I decided to put on my Automator hat (as if I ever took it off, 🤣) and do something about it.
For the sake of this story/demo, let&#8217;s say that for some reason I need to go to a GitHub repo to get the most recent commit SHA, who did it, and the CI/CD status, and that there are no APIs available to me to accomplish this task. I know there are APIs for GitHub, but bear with me, it is only for demo purposes.
The first step was to manually do what I needed to do, but not the usual repetitive, almost automated way; instead, I did pay close attention to all my actions, clicks, etc. Once I figured out what I was doing, I went ahead with the first automation step.
My first automated take at this was to use [Keyboard Maestro](https://www.keyboardmaestro.com/main/). I was able to automate Safari to open a new tab, go to the URL I needed, using JavaScript get the information I needed from the website and show a notification and put it on the clipboard so I could paste it later on another place.
[![](/images/Image-7-19-19-at-9.47-AM.jpg)](/images/Image-7-19-19-at-9.47-AM.jpg)
And when run, it looks like this:
[![](/images/Screen-Shot-2019-07-19-at-10.10.29-AM.png)](/images/Screen-Shot-2019-07-19-at-10.10.29-AM.png)
After some testing and a couple of real-life uses, I noticed one thing. I do not need to authenticate to this page to grab the information, so I do not need Safari (or KM).
<pre><code class="javascript">const request = require('request');
const cheerio = require('cheerio');
const url = 'https://github.com/microsoft/vscode/commits/master';
request.get(url, (error, response, body) =&gt; {
const $ = cheerio.load(body);
const author = $('.commit-author')
.first()
.text();
const build = $('.commit-indicator summary')
.first()
.attr('class')
.replace('text-', '')
.replace('color-yellow-7', 'yellow');
const sha = $('.sha')
.first()
.text()
.trim();
console.log(`Author ${author}, Build: ${build}, SHA: ${sha}`);
});
</code></pre>
Again, success. I was able to get the same information from the terminal (or from KM if I want to skip the terminal altogether).
Then, I realized this could be slightly harder to share because a single script has no package.json attached to it, so my next thought was to create a repo on our internal Git and share the tool bur wait, if I am already doing a repo and a script, why no open the gate to more contributions from my co-workers? So I went one step further and instead of just doing a script, I went ahead and created a simple CLI tool.
A quick search took me to [oclif](https://oclif.io), a Node.js CLI Framework from Heroku. Very handy, and easy to understand, and it uses TypeScript, so I tested it immediately (spoiler alert, loved it). After quickly following their getting started guide I was able to add a new command (I went with the multi command version) and almost entirely copy/paste my script&#8217;s code.
<pre><code class="typescript">import { Command } from '@oclif/command';
import cheerio from 'cheerio';
import cli from 'cli-ux';
import request from 'request-promise-native';
const url = 'https://github.com/microsoft/vscode/commits';
export default class LatestSha extends Command {
static description = 'get the author, build status, and sha of the latest commit of a given branch';
static args = [{ name: 'branch' }];
async run() {
const { args } = this.parse(LatestSha);
const branch = args.branch || 'master';
let body = '';
cli.action.start('loading commit data');
try {
body = await request.get(`${url}/${branch}`);
cli.action.stop('done\n');
} catch (error) {
cli.action.stop('fail\n');
this.error('unable to load commit data from GitHub');
this.error(error);
this.exit(1);
}
try {
const $ = cheerio.load(body);
const author = $('.commit-author')
.first()
.text();
const build = $('.commit-indicator summary')
.first()
.attr('class')
.replace('text-', '')
.replace('color-yellow-7', 'yellow');
const sha = $('.sha')
.first()
.text()
.trim();
this.log(`Author ${author}, Build: ${build}, SHA: ${sha}`);
} catch (error) {
this.error('unable to read commit info from GitHub');
this.error(error);
this.exit(1);
}
}
}
</code></pre>
After linking the cli tool using `npm link` I can simply run it as any other CLI tool:
<pre><code class="shell">erick.ruizdechavez@90-R1BQJG5M-3G7:~
➤ demo-tool latest-sha
loading commit data... done
Author aeschli, Build: green, SHA: c8f0b1c
</code></pre>
The full demo-tool code is in a [GitHub repo](https://github.com/eruizdechavez/demo-tool) if you want to take a look 👀.
In the end, happy automator 🤓 shares tool with now happier co-workers 👏🏻.
---
Photo by [Franck V.](https://unsplash.com/@franckinjapan) on [Unsplash](https://unsplash.com/search/photos/automation)

View file

@ -1,102 +0,0 @@
---
title: Writing a Link Shortener with PHP and Airtable
date: 2020-02-03
tags:
- beginners
- php
- showdev
- tutorial
---
## What you&#8217;ll get
A custom URL shortener with your own domain 🎉.
## What you&#8217;ll need
- A server with Apache and PHP.
- An [Airtable](https://airtable.com) account.
## Some background
Over the weekend I was working on a small side project for a meetup I started attending to a few weeks ago. I am printing a QR code (with a link) for a small flyer and adding an NFC tag (with the same link) to it as well. All of the sudden I was thinking 🤔 _I need a practical way to update a my link without having to print again my QR code and reprogram the NFC tag_.
My initial thinking was to just use one of the many services out there but then I thought _why not build a really simple one so I can update it as I need?_, I also wanted to have a quick way of updating it without having to republish the code, or jumping into an ssh session to change some file on a server.
The answer 💡 I came up was to write a pretty small, yet effective, PHP script that gets an ID, fetches the real link from Airtable, and ends up redirecting the browser to the real link.
To make it look fancier, I used Apache&#8217;s `mod_rewrite` so I get a clean URL, but it is really not required.
## How to put everything together
We start by preparing Airtable. If you do not have an account, get one, it is free and really useful. Once you are ready with your account, you can [copy my Base](https://airtable.com/shrwIKP1OeFO24DYk) into your account. This will save us some time and will allow you to familiarize yourself with Airtable at your own pace. The table has some fields, but the important ones are ID and URL. `ID` is using Airtable&#8217;s record ID (removing the letters `rec` from the beginning) and `URL` is a string with your URL.
After you are ready with your Base and Table, you need to get your Base ID and API Token. To get the Base ID go to https://airtable.com/api, click on your base name and you should see an API explorer with the ID of your base in the first paragraphs of the Introduction. To get your API Token go to https://airtable.com/account and you should have an obfuscated field in the API section, to get the token just click on it and it will reveal the token. I am assuming you copied my base so I&#8217;ll assume your Table name is the same as mine (if not, you can change the name on the `config.ini` file).
With this 3 pieces of information we are ready to get some magic done 🧙🏻‍♂️. We&#8217;ll start by adding a folder on our web server; since it is a link &#8220;shortener&#8221; it made sense to me to create a folder named `s`. Inside this folder I have the following files:
`.htaccess`
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !index.php
RewriteRule .* index.php?id=$0 [QSA,L]
`index.php`
<pre><code class="php">&lt;?php
$config = parse_ini_file('config.ini');
$id = $_GET['id'];
$request_url = 'https://api.airtable.com/v0/' . $config['base_id'] . '/' . urlencode($config['table_name']) . '/rec' . $id;
$options = [
'http' =&gt; [
'method' =&gt; 'GET',
'header' =&gt;
'Authorization: Bearer ' . $config['token'] . "\r\n",
],
];
$context = stream_context_create($options);
$response = file_get_contents($request_url, false, $context);
$record = json_decode($response, true);
$url = $record['fields']['URL'];
if ($_GET['debug']) {
echo '&lt;pre&gt;';
var_dump($options);
var_dump($response);
var_dump($record);
var_dump($url);
echo '&lt;/pre&gt;';
} else {
header("location: ${url}");
}
</code></pre>
`config.ini`
<pre><code class="ini">base_id=your-airtable-base-id-here
table_name=Your Table Name
token=your-api-token-here
</code></pre>
Once these 3 files are in place, there is not much else to do. We just need to have at least a link in the base, copy the ID and use it in our browser using the correct domain and path to your server. In this example, our server domain is `example.com` and our `s` folder is in the root of the public folder, so we could try going to `http://example.com/s/SOMEID` (assuming also that `SOMEID` is a valid ID in our table) and get redirected to the real URL 💥.
If for some reason I need to debug 🐛 the code and see what is going on, I can add `?debug=true` to the URL and the script will show me some variables instead of doing the redirect.
There you go! A custom link shortener with very few code and you can also change the URL whenever you need.
## Pending items
Airtable allows you to do 5 requests per second, so you might need to use a different storage if you are thinking to go to production with this code (which I do not recommend for big or heavy usage).
There is no error handling at all, nor logging, so again you need to think twice before going to production with this code 😉.
## Closing thoughts
I still like PHP a lot for small and quick solutions like this. I it very simple to get it up and running and I do not need to setup any monitor or daemon to keep my process running as I would need to do with Node.js.
Airtable is also a very nice tool I use pretty often both from the Web UI and programatically.
I hope you like and find this example useful. If so, please leave a comment and share with it with your friends 🙂.

View file

@ -1,123 +0,0 @@
---
title: Overlay text on images with PHP
date: 2020-06-23
tags:
- beginners
- php
- showdev
---
Yesterday, [someone ask in the #help](https://dev.to/iamvp7/how-to-create-certificate-templates-1gpi) how to create a list of diplomas using a static background image and a list of names. The problem was simple enough to be implemented in less than 30 mins, so I decided to give a try and here is what I ended up with.
This is a simple PHP script with some assumptions that can be run directly in the command line. It can easily be tweaked to fulfill other more complex requirements, like water marking images, mobile optimized images, reading data from Google Spreadsheets or Airtable, etc., so feel free to fork the repo or download the source and hack it as you need it.
With the code below I go:
| From this&#8230; | &#8230;to this |
| ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------- |
| ![Empty Diploma](https://dev-to-uploads.s3.amazonaws.com/i/14p65vonnof0ccetb2i5.jpg) | ![Filled in Diploma](https://dev-to-uploads.s3.amazonaws.com/i/yudnnptvvf2lfocgm5yd.jpg) |
---
To the code.
<pre><code class="php">&lt;?php
</code></pre>
First, some variables so we can tweak the script behavior without having to figure out where in the code are this things setup
<pre><code class="php">$csv_file = 'names.csv';
$background = 'background.jpg';
$signature = 'sig.png';
$font = 'ConeriaScript.ttf';
</code></pre>
Then, we read the CSV file and parse so it is easier to read later by converting it to an associative array with the column names as the keys.
<pre><code class="php">// CSV code from https://www.php.net/manual/en/function.str-getcsv.php
$csv = array_map('str_getcsv', file($csv_file));
array_walk($csv, function (&$a) use ($csv) {
$a = array_combine($csv[0], $a);
});
array_shift($csv);
</code></pre>
For this specific background we will need the current date as three strings to be placed in three different places.
<pre><code class="php">$day = date('j');
$month = date('F');
$year = date('Y');
</code></pre>
Now we start having fun with PHP&#8217;s image functions. First, we need to read our background image to get some basic info and do some preparation, like setting the text color, the image width (for centering the text), and reading the signature image and its sizes.
<pre><code class="php">$image = imagecreatefromjpeg($background);
$color = imagecolorallocate($image, 0, 0, 0);
$width = imagesx($image);
$signature = imagecreatefrompng($signature);
$signature_width = imagesx($signature);
$signature_height = imagesy($signature);
</code></pre>
Now that we have the image data ready, we will start reading each name in our list so we can generate individual images with all the overlaid text. For easier reading, I am saving the name and reason in their own variables.
<pre><code class="php">foreach ($csv as $row) {
$name = $row['Name'];
$reason = $row['Reason'];
</code></pre>
Here, I am reading the background image again. For the first item in the loop this does not make sense because I already read it above, but since all the `image*` functions alter their input, if I do not do this I will end every image in the list will end up all the previous text from all the previous images overlapped; not good. I am also getting the text &#8220;bound boxes&#8221; which are basically the positions of the four corners of my text; I do this to be able to center the text in the image.
<pre><code class="php"> $image = imagecreatefromjpeg($background);
$name_box = imagettfbbox(40, 0, $font, $name);
$reason_box = imagettfbbox(40, 0, $font, $reason);
</code></pre>
Now it is time to actually render the text on the image. This is done by providing some information to PHP, including the destination image, text size, angle, x and y position, color, font (yes you can use your fonts!), and last but not least, the actual text.
<pre><code class="php"> imagettftext($image, 40, 0, ($width - $name_box[2]) / 2, 635, $color, $font, $name);
imagettftext($image, 40, 0, ($width - $reason_box[2]) / 2, 790, $color, $font, $reason);
imagettftext($image, 32, 0, 400, 895, $color, $font, $day);
imagettftext($image, 32, 0, 600, 895, $color, $font, $month);
imagettftext($image, 32, 0, 600, 975, $color, $font, $year);
</code></pre>
Once the text is overlaid correctly, I need to add the &#8220;signature&#8221; image on top of the text so it looks nicer, so I tell PHP to copy the signature image (a transparent PNG file) on top, as you can see, I had to not only provide position, but also size.
<pre><code class="php"> imagecopy($image, $signature, 400, 980, 0, 0, $signature_width, $signature_height);
</code></pre>
Almost there! Here I am saving the resulting image to disk by providing a name. I I did not provide the name, PHP would just output the content, which could be used instead as a download link by also providing the correct `header`.
<pre><code class="php"> imagejpeg($image, "diplomas/$name.jpg");
</code></pre>
I do not want to clutter my memory, so once I am done with the image, it is destroyed.
<pre><code class="php"> imagedestroy($image);
</code></pre>
And done, to the next item on the list!
<pre><code class="php">}
</code></pre>
---
As you can see this is a fairly simple yet powerful script that can be used for many different purposes. The full project can be seen (and forked) at https://github.com/eruizdechavez/php-diploma-generator and these are the links to the image functions used in this example:
- [imagecreatefromjpeg](https://www.php.net/manual/en/function.imagecreatefromjpeg.php)
- [imagecolorallocate](https://www.php.net/manual/en/function.imagecolorallocate.php)
- [imagesx](https://www.php.net/manual/en/function.imagesx.php)
- [imagesy](https://www.php.net/manual/en/function.imagesy.php)
- [imagecreatefrompng](https://www.php.net/manual/en/function.imagecreatefrompng.php)
- [imagecreatefrompng](https://www.php.net/manual/en/function.imagecreatefrompng.php)
- [imagettfbbox](https://www.php.net/manual/en/function.imagettfbbox.php)
- [imagettftext](https://www.php.net/manual/en/function.imagettftext.php)
- [imagecopy](https://www.php.net/manual/en/function.imagecopy.php)
- [imagejpeg](https://www.php.net/manual/en/function.imagejpeg.php)
- [imagedestroy](https://www.php.net/manual/en/function.imagedestroy.php)
---
I hope you enjoy this post and find it useful!

View file

@ -1,52 +0,0 @@
---
title: Dear new (and not so new) developers
date: 2020-07-01
tags:
- beginners
- career
- learning
- tips
---
I understand not everyone has or will have the privileges of good mentors, internet access, or even knowing how to read and understand English, but for those who have them, is that I want to share these _bites of wisdom_ that have helped me advance my career over 15 years now.
Let me give you a bit of context about me. I graduated from CS 15 years ago, in a small university at my home town (Puebla, Mexico); and paid most of it by working in a small computer repair store near my home. Most of what I know today I either learned it from great mentors I have had over the years, because of someone answering my question in some form of online platform, or self thought.
---
When you start learning something, a programming language, a library you want to use, heck, even a new code editor, you will have that feeling of uncertainty and that is OK, do not run away from it, embrace it. If you know how to channel this feeling into questions and searches, you will go far. There is nothing more exciting and intimidating that start a new project, in an empty folder, with an empty file; so many possibilities but where and how do I start?
You do not have to define a _bullet-proof, future-proof architecture_ from day 1, that is impossible. Yes, there are many companies out there with great ideas, but those ideas that worked for them will not necessarily work for you; even those big companies change direction 180º every so often, so why should you not? Just follow the Unix philosophy, take a big problem and break it in small manageable problems, solve those small problems, and eventually your big problem won&#8217;t be so big, and will be simple to solve.
Do you want to build a web site from scratch? There is absolutely no need to start with Docker on day one. You don&#8217;t need Webpack to start your project. Don&#8217;t waste your time thinking if TailwindCSS or Redux are your best options. Start small, start with a plain HTML file and as you progress you will eventually find what you need and what you don&#8217;t.
### Asking a question is hard, asking good question is even harder
At some point you will find a blocker. Not a small one, a big one. One of those that stop your progress for hours, or even days. But there is nothing to fear about it. It is ok to not know the answer to your problem. It is ok even to not understand your problem. We are not geniuses (at least I am not) so stop thinking you have to know everything. You don&#8217;t. One of the best skills you will learn and master over the years is how to ask a good question, and not just to your friends or coworkers, but also to one of the many search engines out there.
You might be tempted to just copy and paste your errors in the search engine, or dump a big chunk of code on a forum or chat window and wait for someone to magically understand it, fix it, and send it back to you. I can tell you this might work one or two times, but it will eventually wear-of and those who were willing to help you will start ignoring you. Instead, follow again the Unix philosophy, start breaking your problem in smaller ones. Remove complexities, remove unnecessary details until you end up with the bare minimum, and then give it a try one more time.
Having issues fixing or adding a new style to an existing element in a webpage? Remove all styles from the element and try to get yours working. Something in your JavaScript code not working? Isolating the problem by doing something similar in a blank playground works wonders, be it a CodePen, or just a new HTML and JS file on your computer. If after isolating it you still can&#8217;t figure out what the problem is, then you have a good, clean example to ask (politely) someone else.
If your problem or your question is too generic, or on the contrary, too specific, be prepared to answer some questions you will get back to you. A good way to know if you are headed on the right direction to receive help is if the person who is helping you is asking you things, context is everything! If they are just throwing random &#8220;solutions&#8221; at you, most of the time is someone who only cares about &#8220;giving an answer&#8221; first, not actually solving your problem. Our problems are many times as unique as we are, so trying to solve someone&#8217;s problem requires most times a good chunk of time and a conversation to fully understand what you need to do and why.
### Understand your problem, understand your solution
In cases where you feel in a rush or specially when starting with new projects or technologies you might be tempted to ask for a how-to or tutorial, more so if you feel like you are running in circles or you are running out of time, but believe me, a tutorial or a how-to will hurt you more than it will help you. Why? Because a tutorial or a how-to is tailored to a very specific use case, with a very specific goal in mind. Stop rushing yourself and start reading more. I know, not all projects have great documentation, but it will pay forward to read the official documentation to understand what you are doing instead of just blindly following someone&#8217;s &#8220;Ho to build a To Do with X and Y&#8221; guide. Don&#8217;t get me wrong, this is not a RTFM, you do not need to read the whole thing from back to back, but at the very least take the time to read and understand the functionality you are trying to use.
If you get some generic advice or links instead of a tutorial teaching you exactly what you are doing, give that person a honest Thank You and take the time to read whatever was shared with you. That person wants you to learn by giving you the right direction instead of just spitting out the first thing that crossed their mind that may or may not work at all for you.
Do not ask someone to solve your problem, ask them to explain you what you do not understand and point you in the right direction so you can solve your problem yourself.
---
This was slightly longer than I anticipated, but believe me, I have been there more than a handful of times in my 15 years of professional experience. To summarize:
- Ask good questions
- Read the official documentation
- Divide your problems in smaller ones
- Isolate your problems to remove external actors
---
If you made it all the way here, I owe you a big thank you. Thank you for reading all these, I honestly and from the bottom of my hearth hope these words take you as far as they have taken me today. See you in the [#help](https://dev.to/t/help) and in the [interwebs](https://erickruizdechavez.com)!

View file

@ -1,81 +0,0 @@
---
title: Buenas prácticas para presentaciones
date: 2020-12-16
tags:
- productivity
- remote
- spanish
- watercooler
lang: es
alt:
lang: en
link: /good-practices-for-presentations/
---
Hace algunos días, mientras hacía limpieza de notas y lluvia de ideas para temas de posts me encontré una lista de buenas practicas para compartir pantalla que escribí hace algunos años y, aunque ya estamos bien entrados en la nueva era del trabajo remoto, decidí compartirlas pues no dudo que aun haya a quien le hacen falta.
Espero que te sean de ayuda a ti o a alguna persona cercana a ti.
---
## Oculta las barras del sistema e iconos del escritorio
**Objetivo**: Privacidad, Evita distracciones
Este punto es probablemente algo subjetivo, pero me sobran los dedos de una mano para contar las veces que he atendido a una presentación donde las barras del sistema y/o iconos del escritorio están ocultos, y debo decir que es un cambio radical el evitar distracciones tan sutiles como estas. ¿Cuántas veces no han pensado _Yo también uso ese programa_, _Yo conozco esos iconos de la barra de tareas_, o incluso _¿Qué programa será ese?_ ?.
Es muy fácil ocultar temporalmente todos estos iconos y barras, y mientras menos razones le demos a nuestra audiencia para distraerse, mejor nos pondrán atención.
## Mueve todas tus ventanas a un monitor secundario
**Objetivo**: Evita distracciones, Rendimiento
Hoy en día la mayoría de los ambientes de escritorio (Windows, macOS, diferentes sabores de Linux) tienen la opción de manejar multiples escritorios, así que porque no aprovecharlos cuando presentamos. Si necesitas por algún motivo tener varios programas abiertos durante tu presentación, pero no necesitas mostrarlos, has buen uso de esta funcionalidad del sistema operativo y mueve todo lo que no necesites a otro escritorio.
Si tienes el privilegio de tener un segundo monitor, esto es incluso más fácil ya que puedes tener estas ventas a la vista (para ti) sin mostrarlas al compartir tu pantalla.
## Cambia tu disponibilidad a &#8220;No Molestar&#8221;
**Objetivo**: Privacidad, Evita distracciones
Es muy fácil distraerse y perder el hilo de lo que estamos diciendo cuando recibimos notificaciones, alertas, ventanas emergentes, etc. así que en este caso lo mejor es ponernos en modo &#8220;No Molestar&#8221; (DND), y no solo en nuestros dispositivos móviles sino también en nuestra computadora. Nuevamente, la mayoría de los entornos de escritorio y sistemas operativos actuales tienen esta opción al alcance de un o dos clicks desde donde estes.
Además de evitar distracciones para quien presenta y quienes ven la presentación, otra ventaja es evitar posibles problemas de privacidad, pues las notificaciones de mensajes casi siempre vienen acompañadas de vistas previas de dichos mensajes, o de imágenes adjuntas.
## Cierra todos los programas que no sean necesarios
**Objetivo**: Privacidad, Evita distracciones, Rendimiento
Es muy común tener varios programas abiertos cuando estamos trabajando en la computadora ya sea que tengan o no que ver con el trabajo que estamos haciendo, como diferentes pestañas del navegador con nuestro correo, búsquedas anteriores, música, videos, material de referencia, etc. pero estos en raras ocasiones son necesarios durante tu presentación, así que lo mejor es cerrar todo lo que no sea estrictamente necesario para la misma. Además de evitar distracciones, también te salvas de posibles problemas de privacidad y mejoras el rendimiento de tu computadora y tu conexión a internet.
## Usa el modo privado de tu navegador
**Objetivo**: Privacidad, Evita distracciones, Rendimiento
Muy similar al punto de ocultar barras e iconos del sistema. En este caso, si tienes que usar el navegador para presentar algo, lo recomendable es usar el modo privado, de esta forma evitas que se muestren de forma accidental tu historial de navegación, búsquedas y/o descargas; como _bonus point_, el modo privado generalmente desactiva todas las extensiones que tengas instaladas lo cual va a liberar un poco de recursos para el navegador y también va a quitar iconos y otras distracciones.
Otra técnica que no depende del modo privado es crear un perfil de usuario adicional en tu navegador, para pruebas o demos. Esta opción está disponible en la mayoría de los navegadores hoy en día, y es en mí opinion la más flexible, pero requiere un poco más de configuración.
## Prepara con suficiente anticipación todo lo que vas a presentar
**Objetivo**: Rendimiento, Plan de contingencia
No hay como no tener la liga necesaria o tener un internet repentinamente lento para tirar tu concentración (y el resto de tu presentación) por la ventana.
Evita problemas y perdidas de tiempo preparando no solo lo que vas a decir sino todo lo que vayas a presentar en pantalla con anticipación; de ser posible abre todos los links en pestañas nuevas, y si tienes que mostrar interacciones de lo que estes demostrando abre cada paso en una pestaña o ventana nueva a la que puedas pasar rápidamente si algo no funciona como esperabas.
Recuerda la ley de Murphy, _cualquier cosa que pueda salir mal, va a salir mal_.
## Toma capturas de pantalla en un ambiente controlado
**Objetivo**: Rendimiento, Plan de contingencia
Dando seguimiento al punto anterior, de tener el tiempo y las herramientas necesarias, no hay nada mejor que preparar una serie de capturas de pantalla o un video de lo que se va a demostrar como plan de contingencia; _una imagen vale más que mil palabras, un video vale mas que mil imágenes_.
Si decides integrar dichas imágenes o videos en tu presentación en ves de solo usarlas como plan de contingencia, estarás elevando la calidad de tu presentación todavía mas, ya que estas se toman (generalmente) en un ambiente controlado, esto es, sin ruido de fondo, con una buena conexión de internet, tamaño ideal de pantalla, datos ideales a mostrar, etc. Algunas de las mejores presentaciones técnicas que he visto, tanto presenciales como remotas, incluyen uno o más videos de las interacciones que me están describiendo.
---
Espero que encuentres estas recomendaciones tan útiles para tus presentaciones, como las he encontrado yo a lo largo de los años. No tienes que ponerlas todas en practica al mismo tiempo, pero si te recomiendo que las empieces a usar y estoy seguro que tendrás resultados inmediatos.
¿Te gustó mi post?, ¿Tienes otras recomendaciones que te han salvado tu presentación o han mejorado la calidad de la misma? ¡Compártelas conmigo y el resto de la comunidad DEV en los comentarios!

View file

@ -1,79 +0,0 @@
---
title: Good practices for presentations
date: 2020-12-16
tags:
- productivity
- remote
- watercooler
alt:
lang: es
link: /buenas-practicas-para-presentaciones/
---
A few days ago while I was doing some note cleanup and brainstorming for post ideas, I found a list of good practices for presentations and screen sharing I wrote some years ago and, even though we are already well into the new remote age, I decided to share them with you.
I hope you (or someone close to you) find them helpful.
---
## Hide all system bars and desktop icons
**Objective**: Privacy, Avoid distractions
This is a subjective one, but I can count with the fingers in my hand the times I have attended a presentation where the system bars and desktop icons were hidden, and I must say it was a big difference to avoid all these subtle distractions. How many times have you though _I use that app too!_, _I know those icons!_, or even worse _What program is that?_ ?.
It is fairly easy to temporarily hide all this UI elements, and, the less reasons we give our audience to be distracted, the better.
## Move all windows to a secondary monitor
**Objective**: Avoid distractions, Performance
Nowadays most desktop environments (Windows, macOS, different Linux flavors) support multiple virtual desktops which we can take advantage of when presenting. If for some reason you need various programs or windows open during your presentation, but you do not have to share them, use this feature to move such windows out of view.
If you have the privilege of a second display this becomes even easier, just move those windows out of the shared screen while still keeping them visible to you.
## Turn on &#8220;Do Not Disturb&#8221;
**Objective**: Privacy, Avoid distractions
It is easy to get distracted and lose our train of though when we get notifications, alerts, popups, etc. therefore it is best to use the &#8220;Do Not Disturb&#8221; feature, not only on our mobile devices but also on our computer. Most OS today have this feature available to you in a couple of clicks, no matter where you are or what you are doing.
Besides avoiding the mentioned distractions for the presenter and the audience, most notifications include text excerpts, previews or images; DND is also useful to avoid potential privacy problems.
## Quit all nonessential programs
**Objective**: Privacy, Avoid distractions, Performance
It is pretty common to keep several programs running while we are working on the computer whether they are related to our daily activities or not, like different browser tabs, email client, music, videos, reference material, etc. but most of these are not required during a presentation, hence it is better to just quit all programs that are not strictly required for the presentation. In addition to avoid distractions, it saves us from potential privacy problems and it also frees some resources to improve the computer (and internet connection) performance.
## Make use of private browsing
**Objective**: Privacy, Avoid distractions, Performance
In line with hiding system bars and desktop icons. If you are using your internet browser to present, it is recommended to use the private (incognito) browsing mode preventing this way to unintentionally sharing your browsing history, downloads, search history, etc.; as a _bonus point_, private browsing also disables all browser extensions/plugins which will also free up some resources for the browser itself and avoid extra icons and other distractions.
One more slightly advanced tip is to create an alternate browser profile for testing or presentations. This option is available in most browsers and, in my opinion it is the most flexible one, but it requires some extra fiddling and setup.
## Prepare your screen before hand
**Objective**: Performance, Contingency plan
There is nothing like not having the right link or a sudden slow internet to throw your focus (and the rest of your presentation) out the window.
Evade problems and time waste by preparing not only what you have to say, but also what you have to share; if possible, open all links on new tabs, and if you have to demo a series of interactions, open each step on a new tab or window you can use to quickly switch to if something unexpected happens.
Remember Murphy&#8217;s law, _anything that can go wrong, will go wrong_.
## Take screenshots in a controlled environment
**Objective**: Performance, Contingency plan
Expanding and improving the previous point, if you have the time and tools, there is nothing better than take a series of screenshots or video captures of what you will present as a contingency plan; _a picture is worth a thousand words, a video is worth a thousand pictures_.
If you choose to use these images or videos on your presentation as opposed to just use them as a back plan you will be leveling up your presentation quality even more before these are taken (usually) on a controlled environment, that is, in a quiet place, with a good internet connection, ideal screen size, ideal data, etc. Some of the best technical presentations I have assisted to included one or more video captures from the presented topics.
---
I really hope you find all these recommendations as useful for your presentations as I have along the years. You do not have to implement all of them at once, but I definitely recommend you to start using some of them and you will see immediate results.
Did you like my post?, Do you have some life- and presentation-saving tips and recomendaciones that helped you? Share them with me and the rest of the DEV community in the comments!

View file

@ -1,15 +0,0 @@
---
title: Personal Code of Conduct
date: 2021-02-17
tags:
- watercooler
- ethics
---
I am a big fan of having, following, and enforcing a Code of Conduct (CoC) in all projects where I participate. If by any chance they do not have one, I always seek to collaborate with the project mantainers to adopt one that aligns properly with their values. So far I have seen great results, we all grow together, and ensure a safe and civilized space for everyone.
I also recently got the idea of having a Personal Code of Conduct. If you think about it, many of us may already have one, although probably not written and publicly visible; you might call it your values, your ethics, etc., but in the end we like to align ourselves with it and strive to follow it.
This is why a few days ago I decided to adapt one of my favorite CoC, [Contributor Covenant](https://www.contributor-covenant.org) and today I am publishing it on my site as my [Personal CoC](/code-of-conduct/).
What do you think of this idea? Do you think it does not make sense? Would you adopt a similar Personal CoC?

View file

@ -1,18 +0,0 @@
---
title: Ephemeral Tweets
date: 2021-04-08
tags:
- watercooler
- privacy
- discuss
- technology
lang: en
---
I have a complicated relationship with social networks 🤦‍♂️. I do like the idea of sharing and connecting, but there are times where the content in them becomes so toxic ☢️ that I prefer to stay away from them. I haven't had a Facebook account for several years, and I have tried Mastodon and Pleroma as well for a couple of years. Still, the social network I always end up returning to is Twitter.
Tweets are, in my opinion, ephemeral. Something you write today will likely be lost in the noise by tomorrow (and I am not talking about fleets). Heck, if someone didn't read it in the following 10 minutes, it is probably gone for good. Yes, you can still open someone's profile and skim through that person's content. Still, unless you are committed to finding 🕵️‍♂️ what you are looking for (Are you an ex? A sneaky recruiter?), you will likely not find it. This is why since a couple of years ago, I decided to really make my tweets ephemeral; that is, they will eventually (and automatically) be deleted after some time.
There are many tools and services around to achieve this purpose. My weapon of choice is called [ephemeral](https://github.com/victoriadrake/ephemeral). It is a minimal but highly effective Golang program by [Victoria Drake](https://twitter.com/victoriadotdev). It deletes all my old tweets. It also can keep some specific tweets, either by their ID or by hashtags. It was initially designed to be run as an AWS Lambda function but later adapted to run locally, precisely how I run it. The binary is sitting on my NAS, and it is executed every day at midnight. The tool is currently configured to keep my pinned tweet and some hashtags (that I have not used yet). I started by deleting everything older than 15 days, then 30 days, and I think the sweet spot, for now, is 3 months. This way, you get enough context from my tweets in a conversation or thread, but not old enough that you can read my very first tweet back in 2007.
What do you think about this? Do you like the idea of self-destructing 💣 content, or do you prefer your tweets to stay alive 🧟 (and maybe even chase you) forever?

View file

@ -1,4 +0,0 @@
{
"layout": "post",
"eleventyExcludeFromCollections": false
}

View file

@ -1,53 +0,0 @@
---
title: Code of Conduct
---
## My Pledge
In the interest of fostering an open and welcoming environment, I pledge to make my participation in any and all projects and communities a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Standards
Examples of behavior that you should **always** expect from me include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior you should **never** expect from me:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Responsibilities
I will always accept and follow the Code of Conduct of the projects and communities where I participate, and if there is no such Code of Conduct, I will do my best to recommend the project maintainers to institute, follow, and enforce one that aligns with their values.
Project maintainers are responsible for clarifying the standards of acceptable behavior in their projects and are expected to take appropriate and fair corrective action towards me in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject any of my comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban me temporarily or permanently for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies within all project spaces where I participate, and it also applies when I am representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the corresponding project team. If possible, I also encourage and appreciate to be notified of such behavior using [my contact form](/contact/).
All complaints I receive will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. I will maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html)
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
[https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq)

View file

@ -1,34 +0,0 @@
---
layout:
metadata:
url: https://erickruizdechavez.com/
feedUrl: https://erickruizdechavez.com/feed.xml
permalink: feed.xml
---
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>{{ site.name }}</title>
<subtitle>{{ site.tagline }}</subtitle>
<link href="{{ metadata.feedUrl }}" rel="self"/>
<link href="{{ metadata.url }}"/>
<updated>{{ collections.all | rssLastUpdatedDate }}</updated>
<id>{{ metadata.url }}</id>
<author>
<name>{{ site.name }}</name>
</author>
{%- for post in collections.all %}
{% set absolutePostUrl %}{{ post.url | url | absoluteUrl(metadata.url) }}{% endset %}
<entry>
<title>{{ post.data.title }}</title>
<link href="{{ absolutePostUrl }}"/>
{%- if post.data.tags %}
{%- for tag in post.data.tags %}
<category term="{{ tag }}"/>
{%- endfor %}
{%- endif %}
<updated>{{ post.date | rssDate }}</updated>
<id>{{ absolutePostUrl }}</id>
<content type="html">{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content>
</entry>
{%- endfor %}
</feed>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

View file

@ -1,18 +0,0 @@
---
pagination:
data: collections
size: 1
alias: tag
permalink: /tags/{{ tag }}/
---
<header>
<h1>Tagged "#{{ tag }}"</h1>
</header>
{% set taglist = collections[ tag ] %}
{% from "macros.njk" import excerpt %}
{% for post in taglist | reverse %}
{{ excerpt(post) }}
{% endfor %}

View file

@ -5,12 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ title + " - " if title }}{{ site.name }}</title>
<link rel="stylesheet" href="/styles.css" />
<link rel="alternate" type="application/atom+xml" title="" href="/feed.xml" />
{%- if headscripts %}
{%- for script in headscripts %}
<script src="{{ script }}" async defer></script>
{%- endfor %}
{%- endif %}
</head>
<body class="bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400">
@ -21,11 +15,9 @@
{{ site.tagline }}
</div>
</div>
<div class="w-80 flex flex-col md:flex-row text-center justify-between pt-4 md:pt-0">
<a href="/" class="{{ "no-underline" if page.fileSlug == "" }}">Home</a>
<a href="/blog/" class="{{ "no-underline" if page.fileSlug == "blog" }}">Blog</a>
<div class="flex flex-col md:flex-row text-center md:text-right pt-4 md:pt-0">
<a href="/" class="{{ "no-underline" if page.fileSlug == "" }} md:pr-4">Home</a>
<a href="/contact/" class="{{ "no-underline" if page.fileSlug == "contact" }}">Contact</a>
<a href="/code-of-conduct/" class="{{ "no-underline" if page.fileSlug == "code-of-conduct" }}">Code of Conduct</a>
</div>
</header>

View file

@ -1,31 +0,0 @@
{% macro languagePill(lang = "en") %}
<span class="bg-gray-300 text-gray-500 dark:bg-gray-600 dark:text-gray-400 text-xs rounded-full border-black px-1 cursor-default" title="{{ "Español" if lang === "es" else "English" }}">{{ lang }}</span>
{% endmacro %}
{% macro excerpt(post) %}
<article>
<header class="flex items-baseline">
<h3>
<a href="{{ post.url | url }}">{{ post.data.title }}</a>
</h3>
<div class="ml-4 hidden md:block">
{% if post.data.lang === "es" %}
{{ languagePill("es") }}
{% else %}
{{ languagePill("en") }}
{% endif %}
{% if post.data.alt.lang === "es" %}
{{ languagePill("es") }}
{% elif post.data.alt.lang === "en" %}
{{ languagePill("en") }}
{% endif %}
</div>
</header>
{{ post.templateContent | striptags(true) | truncate | safe }}
<footer class="mt-2">
<a class="text-sm italic" href="{{ post.url | url }}">Read more</a>
</footer>
</article>
{% endmacro %}

View file

@ -1,31 +0,0 @@
---
layout: default
---
<header class="mb-8">
<h1>{{ title }}</h1>
<div class="text-sm italic">
<time datetime="{{ page.date.toISOString() }}">{{ page.date | dateFormat }}</time>
</div>
{% if tags %}
<div class="text-sm italic mt-2">
{% for tag in tags %}
<a href="/tags/{{tag}}">#{{tag}}</a>
{% endfor %}
</div>
{% endif %}
{% if alt.lang and alt.link %}
<div class="text-sm italic mt-2">
{% if alt.lang === "es" %}
Este post también esta disponible en <a href="{{ alt.link }}">Español</a>.
{% else %}
This post is also available in <a href="{{ alt.link }}">English</a>.
{% endif %}
</div>
{% endif %}
</header>
{{ content | safe }}

5067
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,8 @@
},
"devDependencies": {
"@11ty/eleventy": "^0.11.1",
"@11ty/eleventy-plugin-rss": "^1.0.9",
"@mightyplow/eleventy-plugin-cache-buster": "^1.1.3",
"@tailwindcss/typography": "^0.3.1",
"eleventy-plugin-tailwindcss": "^0.3.0"
}
},
"dependencies": {}
}