How do I want to get better at learning out loud? Part 1 of 4: Starting

| sharing, blogging, writing

Nudged by Thierry Stoehr's toot about my 23rd blogiversary, I've been thinking about how much I've learned thanks to blogging, and how I can get even better at learning out loud. I'm curious about what this could become over the next twenty years, when I'm in my sixties.

The first part of the text from the sketch is duplicated and expanded in the list below. There are a lot of different aspects I want to get better at, so I'm not going to try to work on all of them in one go, but it's fun mapping out so much room for growth.

Besides, maybe one of these aspects will resonate with you as either something you're learning or something you've figured out something about, and then you'll get in touch, and then we'll both learn more. Wouldn't that be cool?

I'm experimenting with getting stuff out in smaller chunks, so this is part 1 of 4: Starting.

Noticing

I think of this as seeing the opportunity for learning, which I sometimes miss out on because I take things for granted or I don't connect the dots. I can get better at this by slowing down and by borrowing other people's questions. I've been giving myself more time to write and draw these days. It feels a little weird ignoring the other tasks on my TODO lists that are more clearly defined or that are related to other people's requests, but I like the way this feels.

Imagining

It's easy for me to come up with all sorts of ideas for things I want to tweak about Emacs. I can get better at this by reading more about what other people are doing and what other capabilities are there.

I can also get better at exploring ideas for non-Emacs topics, like ways to respond to parenting situations and things I can do support the causes I care about. I can expand my toolbox by reading books and blog posts, and depending on the topic, I can also listen to podcasts and videos.

Bumping into things

It's useful to bump into things I might not think of looking for. One way to do that would be to save various manuals on my e-ink notebook and phone so that I can read them during quiet moments.

I've added some randomness to shuffle old blog posts and tasks, although browsing through this tends to be low-priority. (I never get to the bottom of my reading/thinking list!)

"On this day" might be interesting too. This is more for fun and serendipity. I used to have it on my blog, and it should be pretty easy to reimplement using 11ty.

Learning from others

I'd like to spend more time thinking about and building on other people's ideas, maybe starting with Emacs and then branching out to other topics.

I also want to get back to reading people's blogs through an RSS reader so that I can get a slightly wider view of people's interests and learn more about non-Emacs things. I've added Feeder to my phone.

Taking notes

I capture a lot of snippets in my Org Mode inbox. I'd like to get better at adding some more context and quick thoughts when I create a note so that it's easier to pick up the idea later on. I do most of this capturing on my phone, so I'm getting the hang of slowing down and adding some more notes.

I also want to get better at actually reviewing and refining those notes. My inbox tends to grow and grow, especially when I get interrupted by an interesting idea. I have some writing/editing time while I keep A+ company during virtual school, so it'll be fun revisiting the notes I stashed.

Collecting

This is about putting a bunch of related notes together, which I usually do by refiling them. It's probably also related to clustering, which I'll get to in the next post about thinking.

I do most of this collecting on my computer, so I can write a few Emacs functions to make it easier. For example, I have some code to do the opposite of refiling so that while I'm looking at a topic, I can pull in a subtree from somewhere else.

Some buckets collect thoughts for blog posts, some for projects, some just for areas I'm interested in. I feel like I tend to lose track of the buckets that I'm collecting thoughts into–the list of slightly-less-active thoughts, as the active thoughts are easily findable. Maybe this is okay.

Expanding

I want to get better at going from a microblog post/toot or a quick index-card-type sketch to a longer blog post or sketchnote.

Actually, since my current workflow focuses mostly on blog posts, I think this part is more about contracting: picking out a small thought that I can share right now instead of waiting until I write the rest of it. This idea might also include picking a medium-sized chunk and making it the first post in a series, and the current post is an experiment in doing so. I'm a little hesitant to do so because my brain tends to wander off towards the end of a series, but it might be worth an experiment.

I'll also need some code to make it easier to add links between things in a series, which could be manual (handy for other non-series links too) or possibly something handled on the 11ty side (like How to build a blog series with 11ty/Eleventy).

I can also experiment with spreading posts out by scheduling them as Org tasks. I've theoretically added support for holding back future-dated posts in my 11ty config, but I think managing that on the Org side might be easier for now.

Boosting

This is linked to learning from others. Boosting what other people have said or thought can be a quick and easy way of learning out loud and enlarging the conversation. I can do more of that through Mastodon toots, rolling them up into my blog. I often add snippets to my config based on the things I come across in Emacs News.

I also like the commentary on blogs like Irreal and would like to grow into things like that.

Next up: thinking, making, and sharing

Over the next little while, I'm looking forward to fleshing out the next sections. Let's see if breaking things up into posts works…

  • Thinking: trying; reflecting; developing thoughts; shifting; reviewing; clustering; building on; searching (local, web, exact, approximate); questioning; reframing
  • Making: organizing; writing; editing; drawing (sketchnotes, doodles); diagramming; plotting; charts; coding; showing
  • Sharing: designing; linking; mapping thoughts; joining the conversation
View org source for this post

Wednesday weblog: Toots ending 2024-11-06

| review, roundup
  • embark-org-insert-link-to - 2024-11-02T15:49:09.420Z

    Ooh, it looks like `consult-org-heading` already lets me use embark-act with the shortcut `j` to call `embark-org-insert-link-to`. It doesn't feel like a "j" shortcut, though, so I'll just bind it to `L` for "link" instead: `(keymap-set embark-org-heading-map "L" #'embark-org-insert-link-to)`

  • EmacsConf progress: intros - 2024-11-01T18:32:21.973Z

    #emacsconf progress: I've recorded intros for all the talks so that speakers can review them, and some of the talk videos have started coming in. I'll ask the speakers for feedback after the video upload target date. Still slightly stressed about the prospect of replacing or re-setting-up BigBlueButton; corwin has taken over the stressing out about it at the moment.

  • Planet Emacslife CSS tweaks - 2024-11-01T13:14:52.582Z

    More tweaks to https://planet.emacslife.com: now using flex layout, so it should be a bit more responsive to screen size; sticky headers on large screens so that you can see which post it is when you scroll down. Does it make sense to do sticky headers on small screens? I don't want long titles taking up too much screen space on phones…

  • Emacs commits in Planet Emacslife? - 2024-11-01T11:14:53.651Z

    How do people feel about automatically including #Emacs commits affecting etc/NEWS and Org Mode's ORG-NEWS in https://planet.emacslife.com ? Handy for staying up to date? Too much, since it's easy to subscribe separately?

  • Thinking about navigating a large archive - 2024-10-30T14:48:16.127Z

    I use the 11ty static site generator to publish my blog as plain HTML pages. I have a lot of posts in some categories, like Emacs. I want to generate some of the pages for easier browsing, but I'm not sure it makes sense to generate all of them. Right now I generate 5 pages of posts and then a page that links to all of them. (Ex: https://sachachua.com/blog/category/emacs). It occurred to me that it might not be obvious that there are more than 5 pages of posts (since we're more used to dynamic systems that paginate as much as needed). I wonder how I can make that clearer - oh, maybe I can add the number of posts.

    There's probably stuff I can do to make the All Posts easier to explore, too. I've started making topic pages. I'm also curious about implementing the stacked-cards navigation you see in digital gardens like https://notes.andymatuschak.org/ .

    Ideas? Pointers to other statically generated blogs with large categories who've figured some of this out?

  • Living in the shallows - 2024-10-30T00:24:48.668Z

    Juxtaposing "The Shallows: What the Internet Is Doing to Our Brains" (Nicholas Carr), "Deep Work" (Cal Newport), and "Four Thousand Weeks: Time Management for Mortals" (Oliver Burkeman), I find myself leaning more towards Burkeman's acceptance of limits and lack of control. I'd rather figure out how to embrace these shallows than to write off large portions of my life: parenting a young child with all the attendant interruptions (which I am learning to welcome) and preparing for eventual old age.

    One of the things I've learned while doing Emacs News is that even things I can do in the shallows can be useful. Organizing information and passing it along does not require deep reflection or a quiet mind.

    I can read in short bursts here and there, take notes, and share them.

    Most of my Emacs tweaks are short, but they accumulate.

    Now I am learning to write small thoughts. They are not amazing insights, but they are enough for me, and sometimes they resonate with other people.

    Besides, even when I had full autonomy during my experiment with semi-retirement, it's not like I did amazingly deep stuff either. So that's all cool and I don't have to kid myself or feel like I'm missing out. Instead, I can enjoy this time in the shallows, when doing the dishes or tidying up is pretty much on the same level as many other things on my list of things I could do (probably more useful than most things, even). I can let myself be interruptible, and I can play with the fragments of my attention.

    Follow-up post: Embracing the shallows

  • The Imagination Muscle - 2024-11-02T21:22:36.486Z

    AoM podcast on The Imagination Muscle:

    My notes:

    • Observation is the start of imagination.
    • Commonplace book, sketches
    • Observational closure
    • Reading many books at the same time, connecting commonplace books - this makes me think of dancer curiosity
    • History of coffee shops in London, clusters

    I wonder how I can use sketches and/or microblog posts and my Org files as commonplace books…

  • Doodling in blog posts - 2024-11-04T21:17:03.208Z

    I had fun breaking up the monotony of text with doodles: https://sachachua.com/blog/2024/11/a-year-with-my-cargo-bike/ Also, yay #biking!

View org source for this post

Interactively recolor a sketch

I wanted to be able to change the colours used in a sketch, all from Emacs. For this, I can reuse my Python script for analyzing colours and changing them and just add some Emacs Lisp to pick colours from Emacs.

2024-11-05-14-15-55.svg
Figure 1: Selecting the colour to replace
2024-11-05-14-16-04.svg
Figure 2: Selecting the new colour
(defvar my-recolor-command "/home/sacha/bin/recolor.py")

(defun my-image-colors-by-frequency (file)
  "Return the colors in FILE."
  (with-temp-buffer
    (call-process my-recolor-command nil t nil (expand-file-name file))
    (goto-char (point-min))
    (delete-region (point-min) (1+ (line-end-position)))
    (mapcar (lambda (o) (concat "#" (car (split-string o "[ \t]"))))
            (split-string (string-trim (buffer-string)) "\n"))))

(defun my-completing-read-color (prompt list)
  "Display PROMPT and select a color from LIST."
  (completing-read
   (or prompt "Color: ")
   (mapcar (lambda (o)
             (faces--string-with-color o o))
           list)))

(defun my-image-recolor-interactively (file)
  (interactive (list (read-file-name "File: " (concat my-sketches-directory "/") nil t
                                     nil
                                     (lambda (file) (string-match "\\.png\\'" file)))))
  (save-window-excursion
    (find-file file)

    ;; Identify the colors by frequency
    (let (choice done)
      (while (not done)
        (let* ((by-freq (my-image-colors-by-frequency file))
               (old-color (my-completing-read-color "Old color: " by-freq))
               (new-color (read-color "New color: " t))
               (temp-file (make-temp-file "recolor" nil (concat "." (file-name-extension file))))
               color-map)
          (when (string-match "#\\(..\\)..\\(..\\)..\\(..\\).." new-color)
            (setq new-color (concat (match-string 1 new-color)
                                    (match-string 2 new-color)
                                    (match-string 3 new-color))))
          (setq color-map (replace-regexp-in-string "#" "" (concat old-color "," new-color)))
          (call-process my-recolor-command nil nil nil
                        (expand-file-name file)
                        "--colors"
                        color-map
                        "--output" temp-file)
          (find-file temp-file)
          (pcase (read-char-choice "(y)es, (m)ore, (r)edo, (c)ancel: " "yrc")
            (?y
             (kill-buffer)
             (rename-file temp-file file t)
             (setq done t))
            (?m
             (kill-buffer)
             (rename-file temp-file file t))
            (?r
             (kill-buffer)
             (delete-file temp-file))
            (?c
             (kill-buffer)
             (delete-file temp-file)
             (setq done t))))))))

It would be nice to update the preview as I selected things in the completion, which I might be able to do by making a consult–read command for it. It would be extra cool if I could use this webkit-based color picker. Maybe someday!

View org source for this post

Emacs: Extract part of an image to another file

| emacs

It turns out that image-mode allows you to open an image and then crop it with i c (image-crop), all within Emacs. I want to select a region and then write it to a different file. I think the ability to select a portion of an image by drawing/moving a rectangle is generally useful, so let's start by defining a function for that. The heavy lifting is done by image-crop--crop-image-1, which tracks the mouse and listens for events.

;; Based on image-crop.
(defun my-image-select-rect (op)
  "Select a region of the current buffer's image.

`q':   Exit without changing anything.
`RET': Select this region.
`m':   Make mouse movements move the rectangle instead of altering the
       rectangle shape.
`s':   Same as `m', but make the rectangle into a square first."
  (unless (image-type-available-p 'svg)
    (error "SVG support is needed to crop and cut images"))
  (let ((image (image--get-image)))
    (unless (imagep image)
      (user-error "No image under point"))
    (when (overlays-at (point))
      (user-error "Can't edit images that have overlays"))
    ;; We replace the image under point with an SVG image that looks
    ;; just like that image.  That allows us to draw lines over it.
    ;; At the end, we replace that SVG with a cropped version of the
    ;; original image.
    (let* ((data (cl-getf (cdr image) :data))
           (type (cond
                  ((cl-getf (cdr image) :format)
                   (format "%s" (cl-getf (cdr image) :format)))
                  (data
                   (image-crop--content-type data))))
           (image-scaling-factor 1)
           (orig-point (point))
           (size (image-size image t))
           (svg (svg-create (car size) (cdr size)
                            :xmlns:xlink "http://www.w3.org/1999/xlink"
                            :stroke-width 5))
           ;; We want to get the original text that's covered by the
           ;; image so that we can restore it.
           (image-start
            (save-excursion
              (let ((match (text-property-search-backward 'display image)))
                (if match
                    (prop-match-end match)
                  (point-min)))))
           (image-end
            (save-excursion
              (let ((match (text-property-search-forward 'display image)))
                (if match
                    (prop-match-beginning match)
                  (point-max)))))
           (text (buffer-substring image-start image-end))
           (inhibit-read-only t)
           orig-data svg-end)
      (with-temp-buffer
        (set-buffer-multibyte nil)
        (if (null data)
            (insert-file-contents-literally (cl-getf (cdr image) :file))
          (insert data))
        (let ((image-crop-exif-rotate nil))
          (image-crop--possibly-rotate-buffer image))
        (setq orig-data (buffer-string))
        (setq type (image-crop--content-type orig-data))
        (image-crop--process image-crop-resize-command
                             `((?w . 600)
                               (?f . ,(cadr (split-string type "/")))))
        (setq data (buffer-string)))
      (svg-embed svg data type t
                 :width (car size)
                 :height (cdr size))
      (with-temp-buffer
        (svg-insert-image svg)
        (switch-to-buffer (current-buffer))
        (setq svg-end (point))
        ;; Area
        (let ((area
               (condition-case _
                   (save-excursion
                     (forward-line 1)
                     (image-crop--crop-image-1
                      svg op))
                 (quit nil))))
          (when area
            ;;  scale to original
            (let* ((image-scaling-factor 1)
                   (osize (image-size (create-image orig-data nil t) t))
                   (factor (/ (float (car osize)) (car size)))
                   ;; width x height + left + top
                   (width (abs (truncate (* factor (- (cl-getf area :right)
                                                      (cl-getf area :left))))))
                   (height (abs (truncate (* factor (- (cl-getf area :bottom)
                                                       (cl-getf area :top))))))
                   (left (truncate (* factor (min (cl-getf area :left)
                                                  (cl-getf area :right)))))
                   (top (truncate (* factor (min (cl-getf area :top)
                                                 (cl-getf area :bottom))))))
              (list :left left :top top
                    :width width :height height
                    :right (+ left width)
                    :bottom (+ top height)))))))))

Then we can use it to select part of an image, and then use ImageMagick to extract that part of the image:

(defun my-image-write-region ()
  "Copy a section of the image under point to a different file.
This command presents the image with a rectangular area superimposed
on it, and allows moving and resizing the area to define which
part of it to crop.

While moving/resizing the cropping area, the following key bindings
are available:

`q':   Exit without changing anything.
`RET': Save the image.
`m':   Make mouse movements move the rectangle instead of altering the
       rectangle shape.
`s':   Same as `m', but make the rectangle into a square first."
  (interactive)
  (when-let* ((orig-data (buffer-string))
              (area (my-image-select-rect "write"))
              (inhibit-read-only t)
              (type (image-crop--content-type orig-data))
              (left (plist-get area :left))
              (top (plist-get area :top))
              (width (plist-get area :width))
              (height (plist-get area :height)))
    (with-temp-file (read-file-name "File: ")
      (set-buffer-multibyte nil)
      (insert orig-data)
      (image-crop--process image-crop-crop-command
                           `((?l . ,left)
                             (?t . ,top)
                             (?w . ,width)
                             (?h . ,height)
                             (?f . ,(cadr (split-string type "/"))))))))

i w seems like a sensible shortcut for writing a region of an image.

(with-eval-after-load 'image
  (keymap-set image-mode-map "i w" #'my-image-write-region))
This is part of my Emacs configuration.
View org source for this post

A year with my cargo bike

| decision, life

Summary: Life with a cargo bike has been working out really well for our family.

Stroller

I used to walk for an hour to get to some of A+'s playdates, pushing her in the Thule bike trailer / stroller that she still fit into. I liked bringing popsicles during the summer so that A+ could share them with her friends, so I often balanced a small cooler on top of the stroller and walked as briskly as I could. The popsicles were usually still reasonably cold by the time I got to the park. We'd spend a few hours playing there, and then there would be another hour's walk back. A+ usually napped on the way, so it was a chance for me to listen to podcasts.

Car

Sometimes we biked to the playdate instead. That was much faster in terms of getting there, even with a popsicle break halfway through. Those popsicles were only for us, since I couldn't bring a cooler on my bike. Also, A+ was usually too tired to bike back, or it was too dark for her to be safe biking on the busy streets between the park and our house, so we often waited in the mall parking lot for W- to pick up A+ and her bike in his car. Then I biked back by myself.

Dumped

We'd been considering cargo bikes for a while, and eventually things lined up to make it possible. It was a carefully-considered decision. I did a bunch of test rides using different models of cargo bikes. My height (or lack of it) ruled out many of the models designed for taller people. A+ was quite vocal about her preference for the suspension on the R&M Load cargo bikes, and she liked the view from the front-loaders more than the longtails. I rented the Load 75 and the Load 60 to try them out, accidentally tipping over onto the side an embarrassing number of times; A+ was safely buckled in but very grumpy about it.

When we confirmed that a cargo bike fit into our life, I bought a Riese & Müller Load 75 from Curbside Cycle. We picked the Load 75 over the Load 60 because the rain cover was nicer and the extra room could give us more years of use as A+ grows.

Loaded up

I love it. Biking is my favourite way to get around. There's just something so cheerful about it. A+ and I sing as we go around town. We smile at dogs in sweaters. She takes pictures of trees. Sometimes there are cargo bikes in front of us as we wait at the traffic light, and we wave and nod.

We got the Bakkie bag, too. It's designed to tow a kid's bike. That way, A+ can bike wherever she wants. When she gets tired, she can hop into the cargo bike and I can buckle her bike into the Bakkie bag, towing it all the way home. We've been able to go on more bike adventures by ourselves and together with W- because we don't have to worry about exceeding A+'s range.

Hot chocolate

Since we could get to the playground in 15 minutes instead of 60, it was a lot easier to bring snacks to share. We pretty much kept the playground kids well-supplied with free popsicles (and the occasional much-coveted ice cream treat) all summer, and the ice packs came in handy for treating the occasional bumps too. We even brought disposable cups and insulated bottles of hot water for making hot chocolate and instant apple cider in the colder months.

Potting mix

Aside from taking A+ to a wider range of places, we've also used it to bring several bags of potting mix or a propane tank home from the hardware store, carry other bulky items, and take lots of stuff to the community environment days for recycling/donation.

We are very lucky to have cargo biking as an option. When people ask me how much it is, I ruefully tell them, "Well, it's less than a second car." We weren't actually choosing between this and a second car; even though W- rarely uses his car these days, I'm too anxious to drive. My brain gets a little squirrelly and is prone to attentional hiccups. I don't want a moment of distraction to result in someone's death or serious injury. I'm still on alert when I bike, but it feels a lot more like something I can handle. And biking is so fast and convenient. I don't have to nudge A+ out of a playdate so that we can make it out before the subway gets packed like sardines, or shepherd A+ back home from the subway station ("I'm tiiiired.").

I got the bike in November 2023. Here's how much I biked over the past year:

Month KM
Nov 208
Dec 157
Jan 69
Feb 78
Mar 176
Apr 82
May 106
Jun 143
Jul 135
Aug 96
Sep 212
Oct 120
2024-11-04T14:04:00.159647 image/svg+xml Matplotlib v3.6.3, https://matplotlib.org/
Figure 1: Graph of kilometres by month

I was pleasantly surprised that even during the cold months (and A+'s reluctance to go outside if it was very cold or slushy), and even during the schoolweek, we still managed to get out on the bike.

2024-11-04T13:43:31.432403 image/svg+xml Matplotlib v3.6.3, https://matplotlib.org/
Figure 2: Kilometres by date

I got data from the ebike-connect site using Spookfox using the code below.

Javascript code for extracting distances and times
[...document.querySelectorAll('.activities__ride-menu')].map((o) => {
  return {
    date: o.querySelector('.activities__menu-details > span').textContent,
    distance: o.querySelector('.activities__menu-distance-text').textContent.trim(),
    time: o.querySelector('.activities__menu-details > span:nth-child(2) > span:nth-child(2)').textContent,
  }
});
Emacs Lisp to group distance by month
(let ((by-month (seq-group-by
  (lambda (row)
    (let ((date (plist-get row :date)))
      (when (string-match "[0-9][0-9]\\.\\([0-9][0-9]\\)\\.\\([0-9][0-9]\\) [0-9][0-9]:[0-9][0-9]"
                          date)
        (format "20%s-%s-01"
                (match-string 2 date)
                (match-string 1 date)))))
  trips)))
  (append
   '(("Month" "Distance")
     hline)
   (mapcar
    (lambda (row)
      (list (format-time-string "%b" (date-to-time (car row)))
            (format
             "%d"
             (round (apply '+
                           (mapcar (lambda (entry) (string-to-number (plist-get entry :distance)))
                                   (cdr row)))))))
    (reverse (seq-filter (lambda (o) (string< (car o) "2024-11")) by-month)))))
Emacs Lisp to group distance by date
(let ((by-day (seq-group-by
  (lambda (row)
    (let ((date (plist-get row :date)))
      (when (string-match "\\([0-9][0-9]\\)\\.\\([0-9][0-9]\\)\\.\\([0-9][0-9]\\) [0-9][0-9]:[0-9][0-9]"
                          date)
        (format "20%s-%s-%s"
                (match-string 3 date)
                (match-string 2 date)
                (match-string 1 date)))))
  trips)))
  (json-encode (mapcar
   (lambda (row)
     (cons (car row)
           (format
            "%d"
            (round (apply '+
                          (mapcar (lambda (entry) (string-to-number (plist-get entry :distance)))
                                  (cdr row)))))))
   (reverse (seq-filter (lambda (o) (string< (car o) "2024-11")) by-day)))))
Python code for making a bar graph of distance by month
import pandas as pd
import datetime
import matplotlib.pyplot as plt
import seaborn as sns
import json

data = trips
df = pd.DataFrame(data, columns=["Month", "Distance"])
df.set_index('Month')
df['Distance'] = df['Distance'].astype(float)
plt.figure(figsize=(8, 6), dpi=100)
sns.barplot(data=df, y='Distance', x='Month')
plt.savefig('biking-distance-by-month.svg')
Python code for making a heatmap
import pandas as pd
import datetime
import matplotlib.pyplot as plt
import seaborn as sns
import json

start_date = datetime.datetime(2023, 11, 1)
end_date = datetime.datetime(2024, 11, 1)
dates = pd.date_range(start=start_date, end=end_date, freq='D')
data = json.loads(trips)
df = pd.DataFrame.from_dict(data, orient='index')
df.index = pd.to_datetime(df.index)
df[0] = df[0].astype(float)
# Create calendar heatmap
plt.figure(figsize=(16, 3), dpi=100)
pivoted = df.pivot_table(index=df.index.day_name(), columns=df.index.strftime('%Y-%W'))
all_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
pivoted.index = pd.Categorical(pivoted.index, all_days, ordered=True)
pivoted = pivoted.sort_index()
heatmap = sns.heatmap(
    pivoted,
    cmap="crest",
    linewidths=0.5,
    linecolor='white'
)

# Set the x-axis tick labels to show only the months
month_labels = df.index.strftime('%b').unique()
month_ticks = [i * 4 for i in range(len(month_labels))]
plt.xticks(
    month_ticks,
    month_labels,
    rotation=90
)
tick_positions = [i + 0.5 for i in range(len(all_days))]
plt.yticks(tick_positions, all_days)
plt.title('Distance on dates')
plt.xlabel('November 2023 - November 2024')
plt.ylabel('')
plt.xticks(rotation=90)
plt.savefig('biking-by-day.svg')

I like our cargo bike a lot. I hope to ride it for many years to come.

View org source for this post

2024-11-04 Emacs news

| emacs, emacs-news

Reminder: Emacs News is moving to info-gnu-emacs instead of emacs-tangents. If you're subscribed through emacs-tangents, you may want to subscribe to info-gnu-emacs as well.

Links from reddit.com/r/emacs, r/orgmode, r/spacemacs, r/planetemacs, Mastodon #emacs, Hacker News, lobste.rs, programming.dev, lemmy.world, lemmy.ml, communick.news, 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

Org Mode: Format Libby book highlights exported as JSON

| emacs, org

The Toronto Public Library (and many other libraries) offers e-book access through Overdrive, which I can read through the Libby app on my phone. It turns out that I can select passages to highlight. It also turns out that I can use the Reading Journey view to export the highlights as JSON, even for books I've returned. This is what the JSON looks like.

{
  "version": 1,
  "readingJourney": {
    "cover": {
      "contentType": "image/jpeg",
      "url": "https://img1.od-cdn.com/ImageType-100/7635-1/{B41A3269-BC2A-4497-8C71-0A3F1FA3C694}Img100.jpg",
      "title": "How to Take Smart Notes",
      "color": "#D9D9D9",
      "format": "ebook"
    },
    "title": {
      "text": "How to Take Smart Notes",
      "url": "https://share.libbyapp.com/title/5796521",
      "titleId": "5796521"
    },
    "author": "Sönke Ahrens",
    "publisher": "Sönke Ahrens",
    "isbn": "9781393776819",
    "percent": 0.313229455252918
  },
  "highlights": [
    {
      "timestamp": 1729898852000,
      "chapter": "13       Share Your Insight",
      "percent": 0.824912451361868,
      "color": "#FFB",
      "quote": "For every document I write, I have another called “xy-rest.doc,” and every single time I cut something, I copy it into the other document, convincing myself that I will later look through it and add it back where it might fit. Of"
    },
    {
      "timestamp": 1729898760000,
      "chapter": "13       Share Your Insight",
      "percent": 0.801566108949416,
      "color": "#FFB",
      "quote": "I always work on different manuscripts at the same time. With this method, to work on different things simultaneously, I never encounter any mental blockages"
    },
    ...
 ]
}

I want to save those highlights in my books.org file for easy searching, grouping the highlights by chapter. The following code helps with that:

(defun my-org-insert-book-highlights-from-libby (url)
  (interactive "MURL: ")
  (let-alist (plz 'get url :as #'json-read)
    (insert
     "* "
     .readingJourney.title.text
     " - "
     .readingJourney.author
     "\n")
    (org-set-property "ISBN" .readingJourney.isbn)
    (org-set-property "COVER" .readingJourney.cover.url)
    (org-set-property "TITLE" .readingJourney.title.text)
    (org-set-property "AUTHOR" .readingJourney.author)
    (insert (org-link-make-string .readingJourney.title.url .readingJourney.cover.url)
            "\n")
    ;; sort the highlights by chapter
    (insert
     (mapconcat
      (lambda (row)
        (concat "** " (replace-regexp-in-string " +" " " (car row)) "\n"
                (mapconcat (lambda (quote)
                             (concat "#+begin_quote\n"
                                     (alist-get 'quote quote)
                                     "\n#+end_quote\n\n"))
                           (cdr row)
                           "")
                "\n\n"))
      (seq-group-by
       (lambda (o) (alist-get 'chapter o))
       (sort .highlights
             :key (lambda (o) (alist-get 'percent o))))))))

This is what the resulting document looks like:

* How to Take Smart Notes - Sönke Ahrens
:PROPERTIES:
:ISBN:     9781393776819
:COVER:    https://img1.od-cdn.com/ImageType-100/7635-1/{B41A3269-BC2A-4497-8C71-0A3F1FA3C694}Img100.jpg
:TITLE:    How to Take Smart Notes
:AUTHOR:   Sönke Ahrens
:END:
https://img1.od-cdn.com/ImageType-100/7635-1/{B41A3269-BC2A-4497-8C71-0A3F1FA3C694}Img100.jpg
** 1  Everything You Need to Know
#+begin_quote
 never force myself to do anything I don’t feel like. Whenever I am stuck, I do something else.”
#+end_quote

#+begin_quote
Luhmann’s only real help was a housekeeper who cooked for him and his children during the week, not that extraordinary considering he had to raise three children on his own after his wife died early.
#+end_quote

...
This is part of my Emacs configuration.
View org source for this post