<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/assets/atom.xsl" type="text/xsl"?><feed
	xmlns="http://www.w3.org/2005/Atom"
	xmlns:thr="http://purl.org/syndication/thread/1.0"
	xml:lang="en-US"
	><title>Sacha Chua - category - nodejs</title>
	<subtitle>Emacs, sketches, and life</subtitle>
	<link rel="self" type="application/atom+xml" href="https://sachachua.com/blog/category/nodejs/feed/atom/index.xml" />
  <link rel="alternate" type="text/html" href="https://sachachua.com/blog/category/nodejs" />
  <id>https://sachachua.com/blog/category/nodejs/feed/atom/index.xml</id>
  <generator uri="https://11ty.dev">11ty</generator>
	<updated>2023-10-16T15:04:18Z</updated>
<entry>
		<title type="html">Getting Mermaid JS and ob-mermaid running on my system - needed to symlink Chromium for Puppeteer</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2023/10/getting-mermaid-js-running-on-my-system-needed-to-symlink-chromium-for-puppeteer/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2023-10-16T15:04:18Z</updated>
    <published>2023-10-16T15:04:18Z</published>
    <category term="emacsconf" />
<category term="nodejs" />
<category term="org" />
<category term="emacs" />
		<id>https://sachachua.com/blog/2023/10/getting-mermaid-js-running-on-my-system-needed-to-symlink-chromium-for-puppeteer/</id>
		<content type="html"><![CDATA[<p>
I wanted to use <a href="https://mermaid.js.org/">Mermaid</a> to make diagrams, but I ran into this issue when trying to run it:
</p>

<pre class="example" id="org22c47ee">
Error: Could not find Chromium (rev. 1108766). This can occur if either
 1. you did not perform an installation before running the script (e.g. `npm install`) or
 2. your cache path is incorrectly configured (which is: /home/sacha/.cache/puppeteer).
For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration.
</pre>

<p>
It turns out that I needed to do the following:
</p>

<div class="org-src-container">
<pre class="src src-sh">sudo npm install -g puppeteer mermaid @mermaid-js/mermaid-cli &#45;&#45;unsafe-perm
<span class="org-comment-delimiter"># </span><span class="org-comment">Cache Chromium for my own user</span>
node /usr/lib/node_modules/puppeteer/install.js &#45;&#45;unsafe-perm
sudo npm install -g mermaid @mermaid-js/mermaid-cli
ln -s ~/.cache/puppeteer/chrome/linux-117.0.5938.149 ~/.cache/puppeteer/chrome/linux-1108766
ln -s ~/.cache/puppeteer/chrome/linux-117.0.5938.149/chrome-linux64 ~/.cache/puppeteer/chrome/linux-117.0.5938.149/chrome-linux
</pre>
</div>

<p>
(The exact versions might be different for your installation.)
</p>

<p>
Then I could make a Mermaid file and try it out with <code>mmdc -i input.mmd -o output.svg</code>,
and then I could confirm that it works directly from Org with <a target="_blank" href="https://melpa.org/#/ob-mermaid">ob-mermaid</a>:
</p>

<div class="org-src-container">
<pre class="src src-mermaid"><span class="org-keyword">sequenceDiagram</span>
    input <span class="org-function-name">-&gt;&gt;</span> res: original.mp4;
    res <span class="org-function-name">-&gt;&gt;</span> backstage: reencoded.webm;
    backstage <span class="org-function-name">-&gt;&gt;</span> laptop: cached files;
    laptop <span class="org-function-name">-&gt;&gt;</span> backstage: index;
    input <span class="org-function-name">-&gt;&gt;</span> res: main.vtt;
    res <span class="org-function-name">-&gt;&gt;</span> backstage: main.webm;
    backstage <span class="org-function-name">-&gt;&gt;</span> laptop: cached files;
    laptop <span class="org-function-name">-&gt;&gt;</span> backstage: index;
    input <span class="org-function-name">-&gt;&gt;</span> res: normalized.opus;
    res <span class="org-function-name">-&gt;&gt;</span> backstage: main.webm;
    backstage <span class="org-function-name">-&gt;&gt;</span> laptop: cached files;
    laptop <span class="org-function-name">-&gt;&gt;</span> backstage: index;
    backstage <span class="org-function-name">-&gt;&gt;</span> media: public files;
</pre>
</div>


<figure id="org1888f11">
<img src="https://sachachua.com/blog/2023/10/getting-mermaid-js-running-on-my-system-needed-to-symlink-chromium-for-puppeteer/media-flow.png" alt="media-flow.png">

</figure>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2023%2F10%2Fgetting-mermaid-js-running-on-my-system-needed-to-symlink-chromium-for-puppeteer%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry><entry>
		<title type="html">Compiling selected blog posts into HTML and EPUB so I can annotate them</title>
		<link rel="alternate" type="text/html" href="https://sachachua.com/blog/2023/01/compiling-selected-blog-posts-into-html-and-epub-so-i-can-annotate-them/"/>
		<author><name><![CDATA[Sacha Chua]]></name></author>
		<updated>2023-01-04T14:11:38Z</updated>
    <published>2023-01-04T14:11:38Z</published>
    <category term="blogging" />
<category term="11ty" />
<category term="nodejs" />
<category term="supernote" />
		<id>https://sachachua.com/blog/2023/01/compiling-selected-blog-posts-into-html-and-epub-so-i-can-annotate-them/</id>
		<content type="html"><![CDATA[<div class="update" id="org3e47f19">
<p>
<span class="timestamp-wrapper"><span class="timestamp">[2023-01-04 Wed] </span></span> Added a screenshot showing annotation.
</p>

</div>

<p>
I was thinking about <a class="photoswipe sketch-link" href="https://sketches.sachachua.com/filename/2023-01-03-04%20Preparing%20for%20my%2010-year%20review%20%23planning%20%23life.png" data-src="https://sketches.sachachua.com/static/2023-01-03-04%20Preparing%20for%20my%2010-year%20review%20%23planning%20%23life.png" data-title="how to prepare for my next 10-year review" data-w="2808" data-h="3744">how to prepare for my next 10-year review</a>, since
I'll turn 40 this year. I've been writing <a href="https://sachachua.com/blog/category/yearly/">yearly reviews</a> with some
regularity and <a href="https://sachachua.com/blog/category/monthly">monthly reviews</a> sporadically, and I figured it would be
nice to have those posts in an EPUB so that I can read them on my
e-reader and annotate them as I do my review.
</p>

<p>
I use the <a href="https://github.com/11ty/eleventy/">11ty static site generator</a> to publish my blog as HTML files,
since I currently can't keep more than Emacs Lisp, Javascript, and
Python in my brain. (No Hugo or Jekyll for me at the moment.) I
briefly thought about getting 11ty to create that archive for me, but
I realized it might be easier to just write it as an external script
instead of trying to figure out how to get 11ty to export one thing
conditionally.
</p>

<p>
One of the things I've configured 11ty to make is a JSON file that
includes <a href="https://sachachua.com/blog/all/index.json">all of my posts with dates, titles, permalinks, and categories</a>. It
was easy to then parse this list and filter it to get the posts I
wanted. I parsed the HTML out of the <code>_site</code> directory that 11ty
produces instead of fetching the pages from my webserver. I got the
images from my webserver, though, and I made a local cache and rewrote
the URLs. That way, the EPUB conversion could include the images.
</p>

<p>
<a href="https://sachachua.com/blog/2023/01/compiling-selected-blog-posts-into-html-and-epub-so-i-can-annotate-them/blog.js">Download blog.js</a>
</p>
<details class="code-details" style="padding: 1em;
                 border-radius: 15px;
                 font-size: 0.9em;
                 box-shadow: 0.05em 0.1em 5px 0.01em  #00000057;">
                  <summary><strong>blog.js</strong></summary>
<div class="org-src-container">
<pre class="src src-js"><span class="org-keyword">const</span> <span class="org-variable-name">blog</span> = require(<span class="org-string">'/home/sacha/proj/static-blog/_site/blog/all/index.json'</span>);
<span class="org-keyword">const</span> <span class="org-variable-name">cheerio</span> = require(<span class="org-string">'cheerio'</span>);
<span class="org-keyword">const</span> <span class="org-variable-name">base</span> = <span class="org-string">'/home/sacha/proj/static-blog/_site'</span>;
<span class="org-keyword">const</span> <span class="org-variable-name">fs</span> = require(<span class="org-string">'fs'</span>);
<span class="org-keyword">const</span> <span class="org-variable-name">path</span> = require(<span class="org-string">'path'</span>);

<span class="org-keyword">function</span> <span class="org-function-name">slugify</span>(<span class="org-variable-name">p</span>) {
  <span class="org-keyword">return</span> p.permalink.replace(<span class="org-string">'/blog'</span>, <span class="org-string">'post-'</span>).replace(<span class="org-string">/\//</span>g, <span class="org-string">'-'</span>);
}

<span class="org-keyword">async</span> <span class="org-keyword">function</span> processPost(<span class="org-variable-name">p</span>) {
  console.log(<span class="org-string">'Processing '</span>+ p.permalink);
  <span class="org-keyword">let</span> <span class="org-variable-name">$</span> = cheerio.load(fs.readFileSync(base + p.permalink + <span class="org-string">'index.html'</span>));
  $(<span class="org-string">'#comment'</span>).remove();
  <span class="org-keyword">let</span> <span class="org-variable-name">images</span> = $(<span class="org-string">'article img'</span>);
  <span class="org-keyword">await</span> Promise.all(images.map((i, e) =&gt; {
    <span class="org-keyword">let</span> <span class="org-variable-name">url</span> = $(e).attr(<span class="org-string">'src'</span>);
    <span class="org-keyword">const</span> <span class="org-variable-name">outputFileName</span> = <span class="org-string">'images/'</span> + path.basename(url).replace(<span class="org-string">/ |%20|%23/</span>g, <span class="org-string">'-'</span>);
    $(e).attr(<span class="org-string">'src'</span>, outputFileName);
    $(e).attr(<span class="org-string">'style'</span>, <span class="org-string">'max-height: 100%; max-width: 100%; '</span> + ($(e).attr(<span class="org-string">'style'</span>) || <span class="org-string">''</span>));
    $(e).attr(<span class="org-string">'srcset'</span>, <span class="org-constant">null</span>);
    $(e).attr(<span class="org-string">'sizes'</span>, <span class="org-constant">null</span>);
    $(e).attr(<span class="org-string">'width'</span>, <span class="org-constant">null</span>);
    $(e).attr(<span class="org-string">'height'</span>, <span class="org-constant">null</span>);
    <span class="org-keyword">if</span> (!fs.existsSync(outputFileName)) {
      console.log(<span class="org-string">'fetch'</span>, outputFileName);
      <span class="org-keyword">return</span> fetch(url).then(res =&gt; res.arrayBuffer()).then(data =&gt; {
        <span class="org-keyword">const</span> <span class="org-variable-name">buffer</span> = Buffer.from(data);
        <span class="org-keyword">return</span> fs.createWriteStream(outputFileName).write(buffer);
      });
    } <span class="org-keyword">else</span> {
      console.log(outputFileName, <span class="org-string">'exists'</span>);
      <span class="org-keyword">return</span> <span class="org-constant">null</span>;
    }
  }));
  console.log(<span class="org-string">'Done '</span> + p.permalink);
  <span class="org-keyword">let</span> <span class="org-variable-name">slug</span> = slugify(p);
  $(<span class="org-string">'article h2'</span>).attr(<span class="org-string">'id'</span>, slug);
  <span class="org-keyword">let</span> <span class="org-variable-name">header</span> = $(<span class="org-string">'article header'</span>).html();
  <span class="org-keyword">let</span> <span class="org-variable-name">entry</span> = $(<span class="org-string">'article .entry'</span>).html();
  <span class="org-keyword">return</span> <span class="org-string">`&lt;article&gt;${header}${entry}&lt;/article&gt;`</span>;
}

<span class="org-keyword">let</span> <span class="org-variable-name">last10</span> = blog.filter((p) =&gt; p.date &gt;= <span class="org-string">'2013-08-01'</span>);
<span class="org-keyword">let</span> <span class="org-variable-name">posts</span> = last10.filter((p) =&gt; p.categories.indexOf(<span class="org-string">'yearly'</span>) &gt;= 0)
    .concat(blog.filter((p) =&gt; p.title == <span class="org-string">'Turning 30: A review of the last decade'</span>))
    .concat(last10.filter((p) =&gt; p.categories.indexOf(<span class="org-string">'monthly'</span>) &gt;= 0));

<span class="org-keyword">let</span> <span class="org-variable-name">toc</span> = <span class="org-string">'&lt;h1&gt;Table of Contents&lt;/h1&gt;&lt;ul&gt;'</span> + posts.map((p) =&gt; {
  <span class="org-keyword">return</span> <span class="org-string">`&lt;li&gt;&lt;a href="#${slugify(p)}"&gt;${p.title}&lt;/a&gt;&lt;/li&gt;\n`</span>;
}).join(<span class="org-string">''</span>) + <span class="org-string">'&lt;/ul&gt;'</span>;

<span class="org-keyword">let</span> <span class="org-variable-name">content</span> = posts.reduce(<span class="org-keyword">async</span> (prev, val) =&gt; {
    <span class="org-keyword">return</span> <span class="org-keyword">await</span> prev + <span class="org-keyword">await</span> processPost(val);
  }, <span class="org-string">''</span>);
content.then((data) =&gt; {
  fs.writeFileSync(<span class="org-string">'archive.html'</span>,
                   <span class="org-string">`&lt;html&gt;&lt;body&gt;${toc}${data}&lt;/body&gt;&lt;/html&gt;`</span>);

});
</pre>
</div>


</details>

<p>
This created an <code>archive.html</code> with my posts, using the <code>images/</code>
directory for the images. Then I used my <a href="https://sachachua.com/blog/2023/01/building-up-my-tech-notes/">shell script for converting
and copying files</a> to convert it to EPUB and copy it over.
</p>

<p>
On the SuperNote, I can highlight text by drawing square brackets
around it. If I tap that text, I can write or draw underneath it.
Here's what that looks like:
</p>


<figure id="orgec2a3f5">
<img src="https://sachachua.com/blog/2023/01/compiling-selected-blog-posts-into-html-and-epub-so-i-can-annotate-them/20230104_090739.png" alt="20230104_090739.png">

<figcaption><span class="figure-number">Figure 1: </span>Writing an annotation</figcaption>
</figure>

<p>
These notes are collected into a "Digest" view, and I can export
things from there. (Example: <a href="https://sachachua.com/blog/2023/01/compiling-selected-blog-posts-into-html-and-epub-so-i-can-annotate-them/archive.pdf">archive.pdf</a>)
</p>


<figure id="org6d6a5ae">
<img src="https://sachachua.com/blog/2023/01/compiling-selected-blog-posts-into-html-and-epub-so-i-can-annotate-them/2023-01-04_09-23-57.png" alt="2023-01-04_09-23-57.png">

<figcaption><span class="figure-number">Figure 2: </span>Here's what that digest is like when exported.</figcaption>
</figure>

<p>
(Hmm, maybe I should ask them about hiding the pencil icon&#x2026;)
</p>

<p>
Anyway, I think that might be a good starting point for my review.
</p>
<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2023%2F01%2Fcompiling-selected-blog-posts-into-html-and-epub-so-i-can-annotate-them%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></content>
		</entry>
</feed>