Custom Goodreads Bookshelf Rendering in Astro
TLDR: You can get all of your Goodreads bookshelf data by using their RSS feed. In this article I will show you how to fetch this data, parse it using xml2js, and render it in an Astro component.
Data fetching
On goodreads.com, navigate to your bookshelves page (found somewhere on your profile). At the bottom of this page there is an RSS link, this will be our datasource. Copy it’s value and save it as a const
in an Astro component. I call mine reading.astro
. We then send a fetch request to the url, and parse the response using the xml2js library.
<span><span style="color: var(--shiki-token-comment)">---</span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">GOODREADS_URL</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"https://www.goodreads.com/review/list_rss/USER_ID?key=YOUR_KEY&shelf=%23ALL%23"</span><span style="color: var(--shiki-color-text)">;</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">response</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">fetch</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">GOODREADS_URL</span><span style="color: var(--shiki-color-text)">);</span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">data</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">response</span><span style="color: var(--shiki-token-function)">.text</span><span style="color: var(--shiki-color-text)">();</span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">result</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">xml2js</span><span style="color: var(--shiki-token-function)">.parseStringPromise</span><span style="color: var(--shiki-color-text)">(data);</span></span>
<span><span style="color: var(--shiki-token-comment)">---</span></span>
<span></span>
Parsing the results
Take a look at your newly created result
array. This contains a lot of data from Goodreads which we do not need.
I start by filtering out the books from my to read shelf, as I don’t indent to show these on my website:
<span><span style="color: var(--shiki-color-text)">const filteredBooks = result.rss.channel[0].item.filter((item) => {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">user_shelves</span><span style="color: var(--shiki-token-function)">.includes</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">"to-read"</span><span style="color: var(--shiki-color-text)">);</span></span>
<span><span style="color: var(--shiki-color-text)">});</span></span>
<span></span>
Next, I categorize the books by creating an object with the books respective shelves (currently-reading, 2023, 2022, ..., earlier), like so:
<span><span style="color: var(--shiki-color-text)">const categorizedBooks = filteredBooks.reduce((acc, item) => {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> title</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.title[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> shelves</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.user_shelves</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> date_read</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.user_read_at</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> rating</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.user_rating[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> author_name</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.author_name</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> book_image_url</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.book_image_url[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> book_id</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.book_id[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> };</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">shelves</span><span style="color: var(--shiki-token-function)">.includes</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">"currently-reading"</span><span style="color: var(--shiki-color-text)">)) {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-color-text)">acc[</span><span style="color: var(--shiki-token-string-expression)">"Currently Reading"</span><span style="color: var(--shiki-color-text)">]) {</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[</span><span style="color: var(--shiki-token-string-expression)">"Currently Reading"</span><span style="color: var(--shiki-color-text)">] </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> [];</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[</span><span style="color: var(--shiki-token-string-expression)">"Currently Reading"</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-function)">.push</span><span style="color: var(--shiki-color-text)">(book);</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> acc;</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">yearShelf</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">shelves</span><span style="color: var(--shiki-token-function)">.find</span><span style="color: var(--shiki-color-text)">((shelf) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-token-string-expression)"> /</span><span style="color: var(--shiki-token-keyword)">^</span><span style="color: var(--shiki-token-string-expression)">\d</span><span style="color: var(--shiki-token-keyword)">{4}$</span><span style="color: var(--shiki-token-string-expression)">/</span><span style="color: var(--shiki-token-function)">.test</span><span style="color: var(--shiki-color-text)">(shelf));</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (yearShelf) {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-color-text)">acc[yearShelf]) {</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[yearShelf] </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> [];</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[yearShelf]</span><span style="color: var(--shiki-token-function)">.push</span><span style="color: var(--shiki-color-text)">(book);</span></span>
<span><span style="color: var(--shiki-color-text)"> } </span><span style="color: var(--shiki-token-keyword)">else</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-color-text)">acc[</span><span style="color: var(--shiki-token-string-expression)">"Earlier"</span><span style="color: var(--shiki-color-text)">]) {</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[</span><span style="color: var(--shiki-token-string-expression)">"Earlier"</span><span style="color: var(--shiki-color-text)">] </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> [];</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[</span><span style="color: var(--shiki-token-string-expression)">"Earlier"</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-function)">.push</span><span style="color: var(--shiki-color-text)">(book);</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> acc;</span></span>
<span><span style="color: var(--shiki-color-text)">}, {});</span></span>
<span></span>
At the end, I sort the keys in the order I want, with currently-reading first and earlier last.
<span><span style="color: var(--shiki-color-text)">const sortedKeys = Object.keys(categorizedBooks).sort((a, b) => {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (a </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"Currently Reading"</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (b </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"Currently Reading"</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (a </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"Earlier"</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (b </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"Earlier"</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> b </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-color-text)"> a;</span></span>
<span><span style="color: var(--shiki-color-text)">});</span></span>
<span></span>
In your astro component, you can loop through the sorted keys like so, and render them the way you want:
<span><span style="color: var(--shiki-token-comment)">---</span></span>
<span><span style="color: var(--shiki-token-comment)">// imports, data fetching and parsing</span></span>
<span><span style="color: var(--shiki-token-comment)">// ...</span></span>
<span><span style="color: var(--shiki-token-comment)">---</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"><</span><span style="color: var(--shiki-token-string-expression)">main</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">sortedKeys</span><span style="color: var(--shiki-token-function)">.map</span><span style="color: var(--shiki-color-text)">((key) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> (</span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">h2</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"text-lg font-bold mt-4 mb-2 border-b"</span><span style="color: var(--shiki-color-text)">>{key}</</span><span style="color: var(--shiki-token-string-expression)">h2</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">ul</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"flex flex-col gap-2"</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> {categorizedBooks[key]</span><span style="color: var(--shiki-token-function)">.map</span><span style="color: var(--shiki-color-text)">((book) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-color-text)"> (</span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">li</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"flex gap-2"</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">a</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">href</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)">{</span><span style="color: var(--shiki-token-string-expression)">`https://www.goodreads.com/book/show/</span><span style="color: var(--shiki-token-keyword)">${</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)">.book_id</span><span style="color: var(--shiki-token-keyword)">}</span><span style="color: var(--shiki-token-string-expression)">`</span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"w-64 truncate"</span></span>
<span><span style="color: var(--shiki-color-text)"> ></span></span>
<span><span style="color: var(--shiki-color-text)"> {</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)">.title}</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">a</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">span</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"text-gray-700 w-32 truncate"</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"block"</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> {</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)">.author_name}</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">span</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> {</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)">.date_read[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">] </span><span style="color: var(--shiki-token-keyword)">!==</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">""</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&&</span><span style="color: var(--shiki-color-text)"> (</span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">span</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"text-gray-700 w-32"</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> {</span><span style="color: var(--shiki-token-function)">formatDate</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)">.date_read)}</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">span</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> )}</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">li</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> ))}</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">ul</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> );</span></span>
<span><span style="color: var(--shiki-color-text)"> })</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"></</span><span style="color: var(--shiki-token-string-expression)">main</span><span style="color: var(--shiki-color-text)">></span></span>
<span></span>
The formatDate
function looks like so:
<span><span style="color: var(--shiki-token-keyword)">function</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">formatDate</span><span style="color: var(--shiki-color-text)">(dateString) {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">monthNames</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> [</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'jan'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'feb'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'mar'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'apr'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'may'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'jun'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'jul'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'aug'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'sep'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'oct'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'nov'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'dec'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> ]</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">date</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Date</span><span style="color: var(--shiki-color-text)">(dateString)</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">day</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">date</span><span style="color: var(--shiki-token-function)">.getUTCDate</span><span style="color: var(--shiki-color-text)">()</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">month</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> monthNames[</span><span style="color: var(--shiki-token-constant)">date</span><span style="color: var(--shiki-token-function)">.getUTCMonth</span><span style="color: var(--shiki-color-text)">()]</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">year</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">date</span><span style="color: var(--shiki-token-function)">.getUTCFullYear</span><span style="color: var(--shiki-color-text)">()</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">`</span><span style="color: var(--shiki-token-keyword)">${</span><span style="color: var(--shiki-color-text)">day</span><span style="color: var(--shiki-token-keyword)">}</span><span style="color: var(--shiki-token-string-expression)"> </span><span style="color: var(--shiki-token-keyword)">${</span><span style="color: var(--shiki-color-text)">month</span><span style="color: var(--shiki-token-keyword)">}</span><span style="color: var(--shiki-token-string-expression)"> </span><span style="color: var(--shiki-token-keyword)">${</span><span style="color: var(--shiki-color-text)">year</span><span style="color: var(--shiki-token-keyword)">}</span><span style="color: var(--shiki-token-string-expression)">`</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
Final component
<span><span style="color: var(--shiki-token-comment)">---</span></span>
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> BaseLayout </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"../layouts/BaseLayout.astro"</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> { SEO } </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"astro-seo"</span><span style="color: var(--shiki-color-text)">;</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> xml2js </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"xml2js"</span><span style="color: var(--shiki-color-text)">;</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">GOODREADS_URL</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"https://www.goodreads.com/review/list_rss/USER_ID?key=YOUR_KEY&shelf=%23ALL%23"</span><span style="color: var(--shiki-color-text)">;</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">response</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">fetch</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">GOODREADS_URL</span><span style="color: var(--shiki-color-text)">);</span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">data</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">response</span><span style="color: var(--shiki-token-function)">.text</span><span style="color: var(--shiki-color-text)">();</span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">result</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">await</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">xml2js</span><span style="color: var(--shiki-token-function)">.parseStringPromise</span><span style="color: var(--shiki-color-text)">(data);</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">filteredBooks</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">result</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-constant)">rss</span><span style="color: var(--shiki-color-text)">.channel[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">].</span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-token-function)">.filter</span><span style="color: var(--shiki-color-text)">((item) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">user_shelves</span><span style="color: var(--shiki-token-function)">.includes</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">"to-read"</span><span style="color: var(--shiki-color-text)">);</span></span>
<span><span style="color: var(--shiki-color-text)">});</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">categorizedBooks</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">filteredBooks</span><span style="color: var(--shiki-token-function)">.reduce</span><span style="color: var(--shiki-color-text)">((acc</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> item) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> title</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.title[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> shelves</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.user_shelves</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> date_read</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.user_read_at</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> rating</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.user_rating[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> author_name</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.author_name</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> book_image_url</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.book_image_url[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> book_id</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">item</span><span style="color: var(--shiki-color-text)">.book_id[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> };</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">shelves</span><span style="color: var(--shiki-token-function)">.includes</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-string-expression)">"currently-reading"</span><span style="color: var(--shiki-color-text)">)) {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-color-text)">acc[</span><span style="color: var(--shiki-token-string-expression)">"Currently Reading"</span><span style="color: var(--shiki-color-text)">]) {</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[</span><span style="color: var(--shiki-token-string-expression)">"Currently Reading"</span><span style="color: var(--shiki-color-text)">] </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> [];</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[</span><span style="color: var(--shiki-token-string-expression)">"Currently Reading"</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-function)">.push</span><span style="color: var(--shiki-color-text)">(book);</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> acc;</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">yearShelf</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-token-function)">.</span><span style="color: var(--shiki-token-constant)">shelves</span><span style="color: var(--shiki-token-function)">.find</span><span style="color: var(--shiki-color-text)">((shelf) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-token-string-expression)"> /</span><span style="color: var(--shiki-token-keyword)">^</span><span style="color: var(--shiki-token-string-expression)">\d</span><span style="color: var(--shiki-token-keyword)">{4}$</span><span style="color: var(--shiki-token-string-expression)">/</span><span style="color: var(--shiki-token-function)">.test</span><span style="color: var(--shiki-color-text)">(shelf));</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (yearShelf) {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-color-text)">acc[yearShelf]) {</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[yearShelf] </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> [];</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[yearShelf]</span><span style="color: var(--shiki-token-function)">.push</span><span style="color: var(--shiki-color-text)">(book);</span></span>
<span><span style="color: var(--shiki-color-text)"> } </span><span style="color: var(--shiki-token-keyword)">else</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">!</span><span style="color: var(--shiki-color-text)">acc[</span><span style="color: var(--shiki-token-string-expression)">"Earlier"</span><span style="color: var(--shiki-color-text)">]) {</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[</span><span style="color: var(--shiki-token-string-expression)">"Earlier"</span><span style="color: var(--shiki-color-text)">] </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> [];</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> acc[</span><span style="color: var(--shiki-token-string-expression)">"Earlier"</span><span style="color: var(--shiki-color-text)">]</span><span style="color: var(--shiki-token-function)">.push</span><span style="color: var(--shiki-color-text)">(book);</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> acc;</span></span>
<span><span style="color: var(--shiki-color-text)">}</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> {});</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">sortedKeys</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">Object</span><span style="color: var(--shiki-token-function)">.keys</span><span style="color: var(--shiki-color-text)">(categorizedBooks)</span><span style="color: var(--shiki-token-function)">.sort</span><span style="color: var(--shiki-color-text)">((a</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> b) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (a </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"Currently Reading"</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (b </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"Currently Reading"</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (a </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"Earlier"</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> (b </span><span style="color: var(--shiki-token-keyword)">===</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"Earlier"</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-token-constant)">1</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> b </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-color-text)"> a;</span></span>
<span><span style="color: var(--shiki-color-text)">});</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">function</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">formatDate</span><span style="color: var(--shiki-color-text)">(dateString) {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">monthNames</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> [</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"jan"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"feb"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"mar"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"apr"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"may"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"jun"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"jul"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"aug"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"sep"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"oct"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"nov"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">"dec"</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> ];</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">date</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Date</span><span style="color: var(--shiki-color-text)">(dateString);</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">day</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">date</span><span style="color: var(--shiki-token-function)">.getUTCDate</span><span style="color: var(--shiki-color-text)">();</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">month</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> monthNames[</span><span style="color: var(--shiki-token-constant)">date</span><span style="color: var(--shiki-token-function)">.getUTCMonth</span><span style="color: var(--shiki-color-text)">()];</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">year</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">date</span><span style="color: var(--shiki-token-function)">.getUTCFullYear</span><span style="color: var(--shiki-color-text)">();</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">`</span><span style="color: var(--shiki-token-keyword)">${</span><span style="color: var(--shiki-color-text)">day</span><span style="color: var(--shiki-token-keyword)">}</span><span style="color: var(--shiki-token-string-expression)"> </span><span style="color: var(--shiki-token-keyword)">${</span><span style="color: var(--shiki-color-text)">month</span><span style="color: var(--shiki-token-keyword)">}</span><span style="color: var(--shiki-token-string-expression)"> </span><span style="color: var(--shiki-token-keyword)">${</span><span style="color: var(--shiki-color-text)">year</span><span style="color: var(--shiki-token-keyword)">}</span><span style="color: var(--shiki-token-string-expression)">`</span><span style="color: var(--shiki-color-text)">;</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-token-comment)">---</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)"><</span><span style="color: var(--shiki-token-string-expression)">html</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">lang</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"en"</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">head</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">meta</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">charset</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"utf-8"</span><span style="color: var(--shiki-color-text)"> /></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">link</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">rel</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"icon"</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">type</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"image/svg+xml"</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">href</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"/favicon.ico"</span><span style="color: var(--shiki-color-text)"> /></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">meta</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">name</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"viewport"</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">content</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"width=device-width"</span><span style="color: var(--shiki-color-text)"> /></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">meta</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">name</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"generator"</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">content</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)">{</span><span style="color: var(--shiki-token-constant)">Astro</span><span style="color: var(--shiki-color-text)">.generator} /></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-constant)">SEO</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">title</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"reading"</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">description</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"My virtual bookshelf!"</span><span style="color: var(--shiki-color-text)"> /></span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">head</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">body</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-constant)">BaseLayout</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">title</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"reading"</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">pathname</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"/reading"</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">sortedKeys</span><span style="color: var(--shiki-token-function)">.map</span><span style="color: var(--shiki-color-text)">((key) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> (</span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">h2</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"text-lg font-bold mt-4 mb-2 border-b"</span><span style="color: var(--shiki-color-text)">>{key}</</span><span style="color: var(--shiki-token-string-expression)">h2</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">ul</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"flex flex-col gap-2"</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> {categorizedBooks[key]</span><span style="color: var(--shiki-token-function)">.map</span><span style="color: var(--shiki-color-text)">((book) </span><span style="color: var(--shiki-token-keyword)">=></span><span style="color: var(--shiki-color-text)"> (</span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">li</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"flex gap-2"</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">a</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">href</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)">{</span><span style="color: var(--shiki-token-string-expression)">`https://www.goodreads.com/book/show/</span><span style="color: var(--shiki-token-keyword)">${</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)">.book_id</span><span style="color: var(--shiki-token-keyword)">}</span><span style="color: var(--shiki-token-string-expression)">`</span><span style="color: var(--shiki-color-text)">}</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"w-64 truncate"</span></span>
<span><span style="color: var(--shiki-color-text)"> ></span></span>
<span><span style="color: var(--shiki-color-text)"> {</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)">.title}</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">a</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">span</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"text-gray-700 w-32 truncate"</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"block"</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> {</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)">.author_name}</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">span</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> {</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)">.date_read[</span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)">] </span><span style="color: var(--shiki-token-keyword)">!==</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">""</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&&</span><span style="color: var(--shiki-color-text)"> (</span></span>
<span><span style="color: var(--shiki-color-text)"> <</span><span style="color: var(--shiki-token-string-expression)">span</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">class</span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-token-string-expression)">"text-gray-700 w-32"</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> {</span><span style="color: var(--shiki-token-function)">formatDate</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-constant)">book</span><span style="color: var(--shiki-color-text)">.date_read)}</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">span</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> )}</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">li</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> ))}</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">ul</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">div</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> );</span></span>
<span><span style="color: var(--shiki-color-text)"> })</span></span>
<span><span style="color: var(--shiki-color-text)"> }</span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-constant)">BaseLayout</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"> </</span><span style="color: var(--shiki-token-string-expression)">body</span><span style="color: var(--shiki-color-text)">></span></span>
<span><span style="color: var(--shiki-color-text)"></</span><span style="color: var(--shiki-token-string-expression)">html</span><span style="color: var(--shiki-color-text)">></span></span>
<span></span>