Categories: geek

RSS - Atom - Subscribe via email

2025-09-01 Emacs news

| emacs, emacs-news

People continue to share interesting Emacs elevator pitches. The Emacs Carnival September theme is "Obscure packages." Recommend a hidden gem!

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View org source for this post

Nginx maps: How I redirect sketch IDs to blog posts or sketches

| geek, tech

Assumed audience: I'm mostly writing these notes for myself, because I'd forgotten how this actually works. Maybe it might be interesting for people who also like identifying their posts or sketches and who use Nginx.

Since August 2022, I've been identifying sketches with YYYY-MM-DD-NN, where NN is a number derived from my journaling system. I used to use just YYYY-MM-DD and the title, but sometimes I renamed sketches. Then I used YYYY-MM-DDa (ex: 2022-08-01f). Sometimes optical character recognition had a hard time distinguishing my hand-drawn "d" from "a", though. Numbers were generally more reliable. And besides, that way, there's room for growth in case I have more than 26 journal entries or sketches in a day. (Not that I've gone anywhere near that volume.)

Anyway, I want to have short URLs like sachachua.com/2025-07-31-10 or sach.ac/2025-07-31-10 go to a blog post if one's available, or just the sketch if I haven't written about it yet, like sach.ac/2025-01-16-01. Here's how I do it.

To publish my site, I export HTML files from Org Mode and I use the 11ty static site generator to put them together in a blog.

One of my 11ty files is map.11ty.cjs, which has the following code:

map.11ty.cjs
module.exports = class AllPosts {
  data() {
    return {
      permalink: '/conf/nginx.map',
      eleventyExcludeFromCollections: true
    };
  }
  async render (data) {
    let list = data.collections._posts;
    let cats = data.siteCategoriesWithParents;
    let nested = cats.filter(o => o.parent);
    let hash = {};
    await list.reduce(async (prev, o) => {
      await prev;
      if (o.data?.zid) {
        hash[o.data.zid] = o.url;
      }
      let matches = (await o.template.inputContent).matchAll(/{% +sketchFull +\"([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9](-[0-9][0-9]|[a-z]))?/g);
      for (const m of matches) {
        if (m[1] && !hash[m[1]]) {
          hash[m[1]] = o.url;
        }
      }
    });
    let byZIDs = Object.entries(hash).map((o) => `/${o[0]} ${o[1]};\n`).join('');
    let byBlogID = list.filter(o => o.data.id).map((o) => `/blog/p/${o.data.id} ${o.url};`).join("\n") + "\n";
    let nestedCategories = nested.map((o) => {
      let slugPath = o.parentPath.map((p) => p.slug).join('/') + '/' + o.slug;
      let catPage = `/blog/category/${slugPath} /blog/category/${o.slug};\n`;
      let catFeed = `/blog/category/${slugPath}/feed/ /blog/category/${o.slug}/feed/;\n`;
      let catAtom = `/blog/category/${slugPath}/feed/atom/ /blog/category/${o.slug}/feed/atom/;\n`;
      return catAtom + catFeed + catPage;
    }).join('');
    const result = byBlogID + nestedCategories + byZIDs;
    return result;
  }
};

The code analyzes each of my blog posts and looks for full-sized sketches. If it finds a full-size sketch, it writes a rule that redirects that ID to that blog post. This is what part of the resulting nginx.map looks like:

/2025-07-31-10 /blog/2025/08/monthly-review-july-2025/;
/2025-08-04-01 /blog/2025/08/turning-42-life-as-a-41-year-old/;
/2025-08-03-08 /blog/2025/08/turning-42-life-as-a-41-year-old/;
/2025-08-31-01 /blog/2025/08/emacs-elevator-pitch-tinkerers-unite/;

In my Nginx web server config (nginx.conf), I have:

http {
  # ...
  include /etc/nginx/redirections.map;
  # ...
}

And that redirections.map uses the map module and looks like this:

map $request_uri $new_uri {
   default "";
   include /var/www/static-blog/conf/nginx.map;
   / /blog;
   # ...
}

Then in my Nginx site configuration, I redirect to that $new_uri if available. As a fallback, I also detect yyyy-mm-dd-nn URLs and redirect them to my sketchbook.

server {
   # ... other rules
   if ($new_uri) {
       return 302 $new_uri;
   }
   location ~ ^/([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9](a-z|-[0-9][0-9])?)$ {
      return 302 https://sketches.sachachua.com/$1;
   }
}

The Node app that runs sketches.sachachua.com searches by ID if it gets one. Here's the code in the Express router:

router.get('/:filename', controller.serveImagePageByFilename);

which calls this controller:

exports.serveImagePageByFilename = async (req, res) => {
  if (req.params.filename) {
    let f = await findImageByFilename(req.params.filename);
    if (f) {
      renderSingle(req, res, f);
    } else {
      res.sendStatus(404);
    }
  } else {
    res.sendStatus(404);
  }
};

which uses this function:

const findImageByFilename = (filename) => {
  return getSketches().then((sketches) => {
    let id = filename.match(/^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]([a-z]|-[0-9][0-9])/);
    let m = filename.match(/^[^#]+/);
    let file = sketches.find((x) => path.parse(x.filename).name == filename);
    if (!file) {
      file = sketches.find((x) => x.filename.startsWith(id));
    }
    if (!file && m) {
      file = sketches.find((x) => x.filename.startsWith(m[0]));
    }
    if (!file) {
      file = sketches.find((x) => path.parse(x.filename).name.replace(' #.*') == filename);
    }
    if (!file) {
      file = sketches.find((x) => x.filename.startsWith(filename));
    }
    return file;
  });
};

(getSketches just reads the JSON I've saved.)

So my workflow for a public sketch is:

  1. Draw the sketch.
  2. In my journaling system, write the title and tags for the sketch. Get an ID.
  3. Write the ID on the sketch.
  4. Export the sketch from the iPad as a JPEG.
  5. Wait for things to sync in the background.
  6. Process the sketch. I can use my-image-rename-current-image-based-on-id to rename it manually, or I can use my-sketch-process to recognize the ID and process the text. (My Emacs configuration)
  7. Wait for things to sync in the background. In the meantime, edit the text version of the sketch.
  8. Reload my public sketches, which regenerates the sketches.json that lists all the sketches.
  9. Use my-write-about-sketch or my-insert-sketch-and-text to include the sketch and the text in a draft blog post. (My Emacs configuration) Do my usual blogging process.
View org source for this post

Emacs elevator pitch: tinkerers unite

| emacs, community

This is for the Emacs Carnival 2025-08: Your Elevator Pitch for Emacs hosted by Jeremy Friesen. Emacs is a text editor, but people have made it so much more.

Text and links from sketch

Emacs elevator pitch https://sach.ac/2025-08-31-01

That's the theme for the August Emacs Carnival.

I don't spend much time in elevators these days, and I didn't talk much to strangers even during the before-times.

So let's imagine this is more of, say, a meetup. (Someday I'll get back to going to those.) Could be tech, could be something else. Could be online, could be in person.

I don't have to convince everyone. I don't even have to convince a single person. My goal is to listen for the tinkerers: the ones who like to ask "Why?" and "What if?" and who try things out. They're interesting.

I can find them by:

  • watching talks (sketchnotes are a great thank-you gift)
  • eavesdropping or asking questions
  • sharing what I'm tinkering with

No: Why would anyone do that? Yes: Have you thought of trying xyz?

For tinkerers, the juice might be worth the squeeze. Emacs can be challenging, but it can also pay off. It can even be fun.

Even when I find a fellow tinkerer, the conversation isn't "Have you tried Emacs? You should try Emacs." It'll probably be more like:

"I'd love to keep hearing about your experiments. Do you have something I can subscribe to or follow?" (Side-quest: Try to convince them to blog.)

and then the conversation can unfold over time:

  • "Ooh, I like that idea. Here's my take on it."
  • "How did you do that? What's that?!"
  • "Oh, yeah, Emacs. It's very programmable, so I can get it to do all sorts of stuff for me. Wanna see?"

…and sometimes they fall into the rabbit hole themselves, as tinkerers often do. But even if they don't try Emacs (or don't stick with it), cross-pollination is great. And sometimes Emacs changes their life.

To get a sense of the kinds of things someone has gotten Emacs to do, check out Alvaro Ramirez's post. I have a list like that at emacs. On the topic of cross-pollination, I like Jeremy Friesen's EmacsConf 2023 talk on Mentoring VS-Coders as an Emacsian (or How to show not tell people about the wonders of Emacs).

It's always fun to come across a fellow tinkerer. I love seeing what people come up with. Emacs works out really well for tinkerers. It's not just about taking advantage of the technical capabilities (and you can do a surprising amount with text, images, and interaction), it's also about being part of a great community that's in it long-term. Good stuff.

Feel free to use this sketch under the Creative Commons Attribution License.

View org source for this post

2025-08-25 Emacs news

| emacs, emacs-news

I'm experimenting with commentary and formatting! =) The Emacs Carnival blogging theme of elevator pitch seems to have resonated with a lot of people: 18 entries so far, which is perfect timing because there's also a new online book for Emacs beginners. Also, if you're looking for a "Can your text editor do this?!" sort of example, there was a lively discussion about clipping videos with Emacs on Hacker News. (Be prepared for bewilderment, though.) On the other hand, if you're here for serious stuff and want a technical deep dive, check out Yuan Fu's post on Emacs tree-sitter integration. Here are the other links:

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View org source for this post

2025-08-18 Emacs news

| emacs, emacs-news

If you're looking for something to write about, consider this month's Emacs Carnival theme: your elevator pitch for Emacs.

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View org source for this post

2025-08-11 Emacs news

| emacs, emacs-news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View org source for this post

2025-08-04 Emacs news

| emacs, emacs-news

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Mastodon #emacs, Bluesky #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, planet.emacslife.com, YouTube, the Emacs NEWS file, Emacs Calendar, and emacs-devel. Thanks to Andrés Ramírez for emacs-devel links. Do you have an Emacs-related link or announcement? Please e-mail me at sacha@sachachua.com. Thank you!

View org source for this post