The RikVerse rebuild: the book store bits

tl;dr: Rik builds an online (£FREE) book store for all his publications

This is the sixth in a series of blog posts detailing my journey to rebuild my poetry website - The RikVerse - using Svelte.js, Page.js and Tailwind CSS. Other posts in this series:

  1. Introduction to the project
  2. Setting up the shiny
  3. The client is not the server - 404 errors
  4. Building the blog reader
  5. Fun with Cookies!
  6. The book store bits
  7. Sharing is Caring - social media share buttons
  8. … And finally: the poems!

The code developed in this blog post can be found in this branch on GitHub.

Where were we?

In the previous post I had some fun with cookie consents, learning about Svelte stores along the way.

Here’s a quick reminder of my goals for this site rebuild:

  • [This post] Make each publication page that book’s keystone page
  • [This post] Let people read the books in-site
  • [Next post] Add social media share buttons to each page
  • [NEW - Next post] Fix the metadata
  • [Final post] Index of all the poems available to read
  • [Final post] Tag filtering functionality on the poems index page
  • [Final post] Easy for the user to access a poem’s associated media files (images, audio, video)
  • [Final post] The landing page should display a randomly selected poem
  • [Done] Simpler, more minimal design; better fonts
  • [Done] Navigation needs to be massively simplified
  • [Done] Avoid 404 Page Not Found errors at all costs!
  • [Done] Add in poetry-and-writing-related blog posts
  • [Done] Get rid of the database!
  • [Done] Keep the donate button; make it More Fun To Use
  • [Done] Add in cookie consent (meeting UK ICO guidance for explicit opt-in)

Building the book store

While the RikVerse book store will look very different to the blog reader I built back in my fourth post of this series, the functionality between the two is pretty much the same:

  • A page - at /publications - to display all of the books
  • Each book will have its own page at /book/title-of-book-slug

The chief differences between the two builds are:

  1. We won’t need to fetch any copy for the books - all the book data will be held in the ./src/data/bookData.mjs file; and

  2. Users will be able to read the books in-site (making use of the .pdf files I have for each book). This will happen at a separate end point for each book - /read/title-of-book-slug

I won’t bore people with a detailed explanation of how I built the book store pages - like I said, it’s much the same as for the blog reader build. Instead I’ll show some highlights of the code, alongside some pretty images of the final results.

Routing

Here are the changes I made to the ./src/routes.js file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...

import Publications from "./pages/Publications.svelte";
import Book from "./pages/Book.svelte";
import ReadBook from "./pages/ReadBook.svelte";

...

const routes = [

...

}, {
// Index of links to each of Rik's books
path: '/publications',
component: Publications

}, {
// Book details and download page
path: '/book/:slug',
component: Book

}, {
// Redirect to the publications page on this dead end
path: '/book',
component: Publications

}, {
// "Read the book" page
path: '/read/:slug',
component: ReadBook

}, {
// Redirect to the publications page on this dead end
path: '/read',
component: Publications

}, {

...
];

...

While the individual blog post end points - /blog/:slug - are in the same directory as the blog posts listings page /blog, for the book store I’m doing things differently. This leads to a couple of dead-end end points (/book and /read) which, if the user navigated to them (probably out of spite), would result in those users seeing an error page.

So instead I’m telling the router to load the publications index page when a user hits those dead ends. I also added replica ‘publications’ objects for these end points to the ./src/data/pageData.mjs file, so I can generate redirect pages for them when I run the yarn build terminal command (thus beating the 404 Page Not Found trap when users refresh their browsers).

… Yes, it’s not very clever. But it works!

Data structure

The structure of the objects describing the books - kept in the ./src/data/bookData.mjs file - is similar to the pageData and blogpostData objects, but holds a lot of additional information. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const bookData = [
{
id: "to-posterity",
title: "To Posterity",
tabTitle: "RikVerse Book",
description: "The wierd and the wonderful (Episode 3)",
publishdate: "2010-06-01",
imageUrl: "/images/to-posterity_share.jpg",
imageText: "Cover for 'To Posterity'",
coverimage: "/images/to-posterity_cover.jpg",
blurb: `<p>Dedicated to those who are gone before their time, too many of whom I knew and loved.</p>`,
link_smashwords: "https://www.smashwords.com/books/view/80210",
link_ibook: "https://books.apple.com/us/book/to-posterity/id458566091",
link_gplay: "https://play.google.com/store/books/details/Rik_Roots_To_Posterity?id=xTXsKVeqcmgC",
link_amazon: "",
link_lulu: "http://www.lulu.com/shop/rik-roots/to-posterity/paperback/product-12551425.html",
download_epub: "/downloads/to-posterity.epub",
download_mobi: "/downloads/to-posterity.mobi",
download_pdf: "/downloads/to-posterity.pdf",
is_chapbook: true
},

...

{
id: "the-story-portraits",
title: "The Story Portraits",
tabTitle: "RikVerse Book",
description: "That doctor told me Frank's got maggots in his arse, Dot! What's Frank doing with maggots in his arse?",
publishdate: "2011-09-01",
imageUrl: "/images/the-story-portraits_share.jpg",
imageText: "Cover for 'The Story Portraits'",
coverimage: "/images/the-story-portraits_cover.jpg",
blurb: `<p>Welcome to this, my first collection of micro-fiction. Within these pages you will discover a range of characters looking to tell their stories – some more surreal than others – in various styles and voices. Suffer alongside Frank as he recovers in hospital from an accident; watch young Jacob discover a new form of entertainment; follow Jack as he breaks free of the confines of his bathroom.</p><p>But most importantly, enjoy these stories. After all, I wrote them for you.</p>`,
link_smashwords: "https://www.smashwords.com/books/view/91940",
link_ibook: "https://books.apple.com/us/book/the-story-portraits/id471784354",
link_gplay: "https://play.google.com/store/books/details/Rik_Roots_The_Story_Portraits?id=ujZOsA6YYWEC",
link_amazon: "",
link_lulu: "http://www.lulu.com/shop/rik-roots/the-story-portraits/paperback/product-17340798.html",
download_epub: "/downloads/the-story-portraits.epub",
download_mobi: "/downloads/the-story-portraits.mobi",
download_pdf: "",
is_chapbook: false
},

...

];

export default bookData;

I publish my books in several different venues - principally Smashwords, but also other places the Smashwords distribution network cannot reach. This is a good way of gathering the links together in one place.

Also, because I give away all my books for £FREE, I include links to various eBook format files so people can download them directly from the page.

Results

The /publications end point, which uses the ./src/pages/Publications.svelte and ./src/components/PublicationCard.svelte Svelte files to construct its display:

The /book/:stub end point, built entirely from the ./src/pages/Book.svelte file:

And finally the rather less glamourous yet more useful /read/:stub end point, generated from the ./src/pages/ReadBook.svelte file:

To display a .pdf file in a web page we still have to go “old-skool” - I haven’t used an <object> element in a web page for years … until now!

Here’s how to embed a pdf file into a web page (with some help from Svelte):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<div>

{#if book.download_pdf}
<!-- if we have a pdf file, create an object element to put it in -->
<object
data="{book.download_pdf}"
title="RikVerse embedded pdf file reader"
type="application/pdf"
width="100%"
height="600px">

<!--
Inside the <object> element, some alternative markup
- in case the browser doesn't support displaying pdf files
- (I'm looking at you, Chrome on Android!)
-->
<section>
Oops! It looks like your browser doesn't support viewing pdf files.
The good news is you can download this book for reading later.
</section>

<ul>
{#if book.download_epub}
<li>
<a
href="{book.download_epub}"
type="application/epub+zip"
download>.ePub format
</a> (for most eBook readers)
</li>
{/if}
{#if book.download_mobi}
<li>
<a
href="{book.download_mobi}"
type="application/vnd.amazon.mobi8-ebook"
download>.mobi format
</a> (for Kindle devices)
</li>
{/if}
{#if book.download_pdf}
<li>
<a
href="{book.download_pdf}"
type="application/pdf"
download>.pdf format
</a> (old-school offline eBook reading)
</li>
{/if}
</ul>
</object>

{:else}
<!-- if the pdf file is missing, some alternate markup -->
<section>
My apologies - I don't have a pdf file of this book available
for online reading. Instead you can download this book
in the following formats, for reading later.
</section>

<ul>
{#if book.download_epub}
<li>
<a
href="{book.download_epub}"
type="application/epub+zip"
download>.ePub format
</a> (for most eBook readers)
</li>
{/if}
{#if book.download_mobi}
<li>
<a
href="{book.download_mobi}"
type="application/vnd.amazon.mobi8-ebook"
download>.mobi format
</a> (for Kindle devices)
</li>
{/if}
</ul>
{/if}
</div>

In the next post, I shall be working out how to let people share RikVerse pages on Twitter and Facebook.