The RikVerse rebuild: Sharing is Caring!

tl;dr: Rik works out how to add Facebook share, and Twitter tweet, buttons to the RikVerse site - while not breaking any nasty Cookie laws

This is the penultimate 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 on GitHub.

Where were we?

In the previous post I built a book store for my free publications. Now I need to add social media buttons to the site so people can spread the good news!

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

  • [This post] Add social media share buttons to each page
  • [This post] Fix the metadata
  • [Next post] Index of all the poems available to read
  • [Next post] Tag filtering functionality on the poems index page
  • [Next post] Easy for the user to access a poem’s associated media files
  • [Next 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)
  • [Done] Make each publication page that book’s keystone page
  • [Done] Let people read the books in-site

What do we need to do to be social?

There’s many, many different ways to be social. Each social media channel has its own way of doing things and, as the years have rolled by, each channel has developed a variety of ways to achieve pretty much the same thing: publish details of a page on a website, that a user is currently viewing, into that user’s news channel in the social media site.

Facebook paved the way with their share buttons - snippets of code people could add to their website that displayed a Facebook-branded button which, when clicked by a visitor, would share that page on the visitor’s Facebook News Feed. Heady and magical stuff!

The price to pay? Nothing was as easy as Facebook claimed it would be. The code could fail for many reasons, and for no reason at all. Not forgetting: social graphs; tracking cookies; targeted adverts; Big Brother!

It turns out that Facebook has published many different types of ‘share-like’ buttons over the years: Share, Like, Send, Save, Quote … which send stuff to different places such as a user’s news feed. Or their story. Or a group they manage.

Confusion alert! … So what is it that I actually want to do?

I want a Facebook share button, customised for every page of my site - just as they do for the big news channels sites like the BBC or the Guardian or the Washington Post. Click the button and a Facebook box appears with the news story’s title and pretty image loaded, everything ready for the user to add comments and click ‘share’.

And Facebook is not the only social media platform out there. You want some share buttons? Here’s some share buttons for you:

It’s no surprise that companies have come along to build a business out of simplifying the whole sharing process for website owners and web developers. For instance, ShareThis. I’ve used this company’s services in the past and can testify that their code has saved me from many headaches: it really is just copy-and-forget stuff.

But everything - even “free” services - comes at a price. ShareThis make their money by collecting and selling the aggregated data that passes through their servers - as they make clear in their (not easy to find) privacy statement, publisher information and terms of use pages.

And everything runs on cookies. Which is now a problem, given the UK’s Information Commissioner’s Office’s updated guidance on what website owners need to do when asking users to agree to cookies - via the Dreaded Cookie Banner - if they want to avoid (potentially massive) fines.

The upshot of all this is that I won’t be using a service like ShareThis on the RikVerse site. I’m going to have to do the coding myself.

The practicalities of adding Facebook and Twitter share buttons to a site

The constraints for this work are as follows:

  • Users must agree to Facebook and/or Twitter cookies before they can share a RikVerse page to Facebook Friends and/or Twitter Followers.

  • Cookie agreement should be sought only once per session, or less if the user explicitly agrees the RikVerse can remember their choice for future visits.

  • Facebook and Twitter share buttons must appear on every page on the site.

  • Facebook and/or Twitter buttons should NOT appear on pages if the user decides not to allow the related cookies onto their browser or device.

  • The shares should include text and images relevant to the page being shared - not a single, generic site-landing-page share that appears on every page.

The good news is I’ve already done the work required for the first two constraints. All I need to do now is code up the buttons and associated metadata, making sure that my code doesn’t sneak cookies onto the user’s browser or device before they’ve agreed to them.

I went away and did some research: Facebook and Twitter developer-related pages; Google searches for best practices; reviews of other sites (BBC, Guardian, Washington Post, etc) to see how their developers had coded up this stuff. Here’s what I discovered:

  • Facebook requires that I register the RikVerse website as an “app” before I can attempt to share anything on their site. Twitter has no such requirement.

  • Both Facebook and Twitter require that <metadata> tags be added to each web page’s <head> element - these tags hold information such as page title, full URL, description, URL of related images, etc.

  • Both networks do their magic stuff through Javascript libraries (SDK software development kits) which need to be added to the page via <script> elements; Facebook also requires that an additional function should be added to the browser’s window object before its SDK loads.

  • The networks will add cookies to the user’s browser/device only after their SDK libraries are initialized.

  • Finally, while both companies have brand guidelines about how their share buttons should look, I’m free to use my own button designs (within limits).

Fun with Metadata

I added the required <metadata> elements to the RikVerse’s index.html page (which lives at ./public/index.html). They look like this:

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>

<title>The RikVerse</title>

<!-- general page metadata -->
<meta name="author" content="Rik Roots">
<meta name="description" content="The RikVerse - books and poems by Rik Roots">

<!-- Facebook metadata -->
<meta property="fb:app_id" content="210285656772999" />
<meta property="og:url" content="http://rikverse2020.rikweb.org.uk" />
<meta property="og:type" content="article" />
<meta property="og:title" content="RikVerse poetry website" />
<meta property="og:description" content="Read a poem today!" />
<meta property="og:image" content="http://rikverse2020.rikweb.org.uk/images/RV-home_share.png" />

<!-- Twitter metadata -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="RikVerse poetry website">
<meta name="twitter:description" content="Read a poem today!">
<meta name="twitter:image" content="http://rikverse2020.rikweb.org.uk/images/RV-home_share.png">
<meta name="twitter:image:alt" content="Image advertising the RikVerse website">

<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/build/bundle.css'>

<script defer src='/build/bundle.js'></script>
</head>

<body>
<!-- App.svelte will be added below when the Svelte-generated Javascript runs -->
</body>
</html>

While it looks complex and horrid, I quickly realised that I only needed to supply five pieces of information to individualise each RikVerse page:

1
2
3
4
5
-> title:       // a page name, or blogpost title, or book title, or poem title,
-> description: // a summary of the page, or the first line of a poem
-> pageUrl: // the absolute URL of where the page lives on the server
-> imageUrl: // the absolute URL of where the associated image lives on the server
-> imageText: // some alternative text for the image

… Like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- general page metadata -->
<meta name="author" content="Rik Roots">
<meta name="description" content="[TITLE] - [DESCRIPTION]">

<!-- Facebook metadata -->
<meta property="fb:app_id" content="210285656772999" />
<meta property="og:url" content="[PAGE-URL]" />
<meta property="og:type" content="article" />
<meta property="og:title" content="[TITLE]" />
<meta property="og:description" content="[DESCRIPTION]" />
<meta property="og:image" content="[IMAGE-URL]" />

<!-- Twitter metadata -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="[TITLE]">
<meta name="twitter:description" content="[DESCRIPTION]">
<meta name="twitter:image" content="[IMAGE-URL]">
<meta name="twitter:image:alt" content="[IMAGE-TEXT]">

A small Svelte gotcha!

My first attempt to do this work … was a failure. Svelte includes a markup gizmo called <svelte:head>. This does an excellent job of allowing me to update the <title> element in each page’s <head> element, so the text that appears in the user’s browser’s tab changes according to the page they’re viewing:

1
2
3
<svelte:head>
<title>{book.tabTitle}</title>
</svelte:head>

I thought … if I made a new Svelte <Metadata> component to hold all the metadata tags, then included that component in the <svelte:head> component, it would work:

1
2
3
4
<svelte:head>
<title>{book.tabTitle}</title>
<Metadata {book} />
</svelte:head>

It does work! Except it worked by adding, instead of replacing, the metadata elements to the index page <head>; after spending some minutes on the site, browsing between various pages, the <head> element became the proud parent of several hundred <metadata> elements.

… This is not the functionality I was looking for.

Doing metadata with a JS module

For my second attempt to solve the problem, I developed a new Javascript module file at ./src/handleMetadata.js - similar to the module I developed to help handle cookie functionality. Here I did things the oldskool way, getting handles to each of the <metadata> elements and updating their content= attributes according to the data supplied to the function:

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
// JS object which will hold handles to DOM metadata elements
let metadataHandles;

// Walk through the DOM to get handles to the metadata elements in the head element
// - do this only once per SPA index.html page load
const gatherMetadataHandles = () => {

let head = document.head;

let handles = {

// generic metatags
description: head.querySelector('meta[name="description"]'),

// Facebook metatags
ogUrl: head.querySelector('meta[property="og:url"]'),
ogTitle: head.querySelector('meta[property="og:title"]'),
ogDescription: head.querySelector('meta[property="og:description"]'),
ogImage: head.querySelector('meta[property="og:image"]'),

// Twitter metatags
twitterTitle: head.querySelector('meta[name="twitter:title"]'),
twitterDescription: head.querySelector('meta[name="twitter:description"]'),
twitterImage: head.querySelector('meta[name="twitter:image"]'),
twitterImageAlt: head.querySelector('meta[name="twitter:image:alt"]'),
}

if (handles.description && handles.ogUrl && handles.twitterTitle) metadataHandles = handles;
};

// Exported function to update metadate element content attributes
const updateMetadata = (data) => {

// We only want to gather the metadata element handles once per load event
if (!metadataHandles) gatherMetadataHandles();

if (metadataHandles) {

let domain = window.location.origin,
url = window.location.href;

// generic metatags
metadataHandles.description.setAttribute('content', `${data.title} - ${data.description}`);

// Facebook metatags
metadataHandles.ogUrl.setAttribute('content', `${url}`);
metadataHandles.ogTitle.setAttribute('content', data.title);
metadataHandles.ogDescription.setAttribute('content', data.description);
metadataHandles.ogImage.setAttribute('content', `${domain}${data.imageUrl}`);

// Twitter metatags
metadataHandles.twitterTitle.setAttribute('content', data.title);
metadataHandles.twitterDescription.setAttribute('content', data.description);
metadataHandles.twitterImage.setAttribute('content', `${domain}${data.imageUrl}`);
metadataHandles.twitterImageAlt.setAttribute('content', data.imageText);
}
};

export {
updateMetadata,
}

The data I need for each different page … I’m already pulling that data into the page from the appropriate data .mjs file (./src/data/pageData.mjs, ./src/data/blogpostData.mjs, ./src/data/bookData.mjs).

Thus I can update each of my Svelte page components (in the ./src/pages/ directory) with code to update the metadata each time the client-side Page.js router loads the page into the index.html file. For example:

1
2
3
4
5
6
import pageData from '../data/pageData.mjs';
import { updateMetadata } from '../handleMetadata.js';

let pageMetadata = pageData.filter(item => item.id === 'blog')[0];

updateMetadata(pageMetadata);

Don’t forget the semi-static pages!

Both Facebook and Twitter employ web crawlers as part of their sharing functionalities. These spiders will, when a request to share a page is received by the social media servers, go away and investigate the page - in particular to retrieve data from the <metadata> elements in the page’s <head> element.

The web crawlers will have no idea that the page to be shared is part of a Svelte Single Page Application (SPA), which only has an index.html page on the server. Instead they will visit the Absolute URL address supplied as part of the share request; if the request results in a “404 Page Not Found” response, the share attempt will fail.

… I’ve already addressed the 404 situation, as I explained in my third post - The client is not the server - 404 errors. To summarise: I’m not able to access my web host’s shared server to add redirect rules to it, and I’m not prepared to put in the effort to code up, using tech like a headless browser, a development toolchain to generate full, standalone static site pages for every end point URL.

Instead, the RikVerse is a semi-static site with a single-point-entry SPA page (index.html) and a static “redirect” page for every end point in the site.

My developer toolchain solution was a Node script which loads up the metadata modules - ./src/data/pageData.mjs, ./src/data/blogpostData.mjs, ./src/data/bookData.mjs - and uses them to generate the semi-static pages (every time I run yarn build in the terminal).

Those modules already contain all the data I need to generate page-specific <metadata> elements. So I adapted my script - ./pageBuilder.mjs - to do exactly that:

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
console.log('page builder called');

import fs from 'fs';

// index.html template
const buildIndexFile = (data) => {

let domain = 'https://rikverse2020.rikweb.org.uk';
let indexfileLocation = '`${location.origin}/?p=${location.pathname.substring(1)}`';

return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>${data.tabTitle}</title>

<!-- to prevent caching -->
<meta http-equiv="cache-control" content="max-age=0">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="-1">
<meta http-equiv="expires" content="Tue, 01 Jan 1980 11:00:00 GMT">
<meta http-equiv="pragma" content="no-cache">

<!-- general page metadata -->
<meta name="author" content="Rik Roots">
<meta name="description" content="${data.title} - ${data.description}">

<!-- Facebook metadata -->
<meta property="fb:app_id" content="210285656772999" />
<meta property="og:url" content="${domain}/${data.path}" />
<meta property="og:type" content="article" />
<meta property="og:title" content="${data.title}" />
<meta property="og:description" content="${data.description}" />
<meta property="og:image" content="${domain}${data.imageUrl}" />

<!-- Twitter metadata -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${data.title}">
<meta name="twitter:description" content="${data.description}">
<meta name="twitter:image" content="${domain}${data.imageUrl}">
<meta name="twitter:image:alt" content="${data.imageText}">

</head>
<body>
<script>
if (location.pathname) location.href = ${indexfileLocation};
else location.href = location.origin;
</script>
</body>
</html>`;
};

// This function creates the directory if it doesn't already exist
const checkDirectory = (dir) => {

return new Promise((resolve, reject) => {

fs.access(dir, fs.constants.F_OK, (err) => {

if (err) fs.mkdir(dir, { recursive: false }, (err) => {

if (err) reject(`failed to create ${dir}`);
else resolve(`created ${dir}`);
});

else resolve(`${dir} already exists and can access`);
});
});
};

// This function generates index pages
const writeIndexFile = (data, directory) => {

return new Promise((resolve, reject) => {

fs.open(`${directory}/index.html`, 'wx', (fileError, fd) => {

if (fileError && fileError.code !== 'EEXIST') reject(`error for ${directory}/index.html - ${fileError.code}, ${fileError.message}`);

fs.writeFile(`${directory}/index.html`, buildIndexFile(data, directory), 'utf8', (writeError) => {

if (writeError) reject(`failed to write ${directory}/index.html file: ${writeError.code}, ${writeError.message}`);

else resolve(`${directory}/index.html file updated`)
});
});
});
};

// Process the router base pages index files
import pageData from './src/data/pageData.mjs';
pageData.forEach(page => {

page.path = page.id;

checkDirectory(`./public/${page.id}`)
.then(res => writeIndexFile(page, `./public/${page.id}`))
.then(res => console.log(res))
.catch(err => console.log(err));
});

// Process the blogpost files
import blogpostData from './src/data/blogpostData.mjs';
blogpostData.forEach(post => {

post.path = `blog/${post.id}`;

checkDirectory(`./public/blog/${post.id}`)
.then(res => writeIndexFile(post, `./public/blog/${post.id}`))
.then(res => console.log(res))
.catch(err => console.log(err));
});

// Process the book files
import bookData from './src/data/bookData.mjs';
bookData.forEach(book => {

book.path = `book/${book.id}`;

checkDirectory(`./public/book/${book.id}`)
.then(res => writeIndexFile(book, `./public/book/${book.id}`))
.then(res => checkDirectory(`./public/read/${book.id}`))
.then(res => {

book.path = `read/${book.id}`;
return writeIndexFile(book, `./public/read/${book.id}`);
})
.then(res => console.log(res))
.catch(err => console.log(err));
});

(Happy dance) … Problem solved!

Adding the Javascript SDK libraries

For both Facebook and Twitter, I don’t intend to add in their SDK libraries until after the user has agreed to cookies. Once that happens, I get Svelte to serve up different share buttons (see below) which include appropriate <script> tags, which then load the libraries.

For Twitter, this is all that needs to happen.

The Facebook SDK, however, expects an additional piece of code to already be in place: a function attached to the window object which the SDK can invoke to get everything set up for sharing and stuff.

Adding this code to the ./public/index.html file does not, in itself, trigger the generation of unexpected cookies; that only happens after the function is invoked. So I went ahead and updated the index 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
<!DOCTYPE html>
<html lang="en">
<head>

[metadata and stuff]

</head>

<body>
<script>
window.fbAsyncInit = function() {

FB.init({
appId : '210285656772999',
autoLogAppEvents : true,
xfbml : true,
version : 'v5.0'
});
};
</script>

<!-- App.svelte will be added below when the Svelte-generated Javascript runs -->
</body>
</html>

And finally - the share buttons!

I was adamant that share buttons should appear on every single page in the RikVerse site. So the obvious place to add them is in Svelte’s root component - ./src/App.svelte:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<style global>
[... Tailwind-related styling code ...]
</style>

<script>
[... Page.js routing and other scripting code ...]
</script>

<!-- Everything below is the App.svelte file's template -->
<Navigation />

<SocialMedia />

<main>
<svelte:component this={page} {params} />
</main>

<Footer />

<SocialMedia> - a container for the share buttons - is a new Svelte component, whose file can be found at ./src/components/SocialMedia.svelte:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
import TwitterButton from './TwitterButton.svelte';
import FacebookButton from './FacebookButton.svelte';
</script>

<style>
div {
@apply w-full text-right my-2;
}
</style>

<div>
<FacebookButton />
<TwitterButton />
</div>

The Facebook share button

Each share button lives inside its own Svelte component. This allows me to contain the functionality associated with each inside the component.

Both components make use of the Svelte cookie stores I set up in my Fun with Cookies! post.

The button designs - I appropriated these from the BBC website. I pay my TV licence thus, in my view, I’m entitled! I tweaked the buttons so that when a user first visits the RikVerse they appear greyed out - because: user has not yet agreed to cookies. Once they agree to cookies the buttons will appear in their brand colours.

If the user actively refuses to have the cookies in their browser/device, the code will not generate the buttons, and no cookies will be added.

Here’s the code for the Facebook button - ./src/components/FacebookButton.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
83
84
85
86
87
<script>
import { navigateTo } from '../utilities.js';

import { facebookCookies } from '../handleCookies.js';

let dPath = "M5.73,17 L5.73,9.246 L8.333,9.246 L8.723,6.223 L5.73,6.223 L5.73,4.294 C5.73,3.419 5.973,2.823 7.228,2.823 L8.828,2.822 L8.828,0.119 C8.551,0.082 7.601,0 6.496,0 C4.189,0 2.609,1.408 2.609,3.995 L2.609,6.223 L0,6.223 L0,9.246 L2.609,9.246 L2.609,17 L5.73,17 Z";

const redirectToCookies = (e) => navigateTo('/cookies');

const setupFacebook = (e) => {

FB.ui({
method: 'share_open_graph',
action_type: 'og.likes',
action_properties: JSON.stringify({object: `${window.location.href}`})
}, response => console.log(response));
};
</script>

<style>
button {
@apply bg-white rounded inline-block border-0;
}
button:hover {
background-color: #3b5998;
}

svg {
fill: #3b5998;
}
svg:hover {
fill: #ffffff;
}

.cookies-required {
@apply bg-gray-300 rounded inline-block border-0;
}
.cookies-required:hover {
@apply bg-gray-500;
}

.cookies-required svg {
fill: #A0AEC0;
}
.cookies-required svg:hover {
fill: #E2E8F0;
}
</style>

{#if $facebookCookies === 'yes'}
<!-- User has given consent to Facebook cookies being placed on their device -->
<script async defer src="https://connect.facebook.net/en_US/sdk.js"></script>

<button
on:click={setupFacebook}
title="Share with Facebook Friends">
<svg
viewBox="-17 -13 44 44"
enable-background="new 0 0 44 44"
width="44px" height="44px"
aria-hidden="true" focusable="false">
<g>
<path d={dPath}></path>
</g>
</svg>
</button>

{:else if $facebookCookies !== 'no'}
<!-- User has not yet made a decision about Facebook cookies -->
<button
class="cookies-required"
on:click={redirectToCookies}
title="Enable Facebook cookies to share with Facebook Friends">
<svg
viewBox="-17 -13 44 44"
enable-background="new 0 0 44 44"
width="44px" height="44px"
aria-hidden="true" focusable="false">
<g>
<path d={dPath}></path>
</g>
</svg>
</button>

{:else}
<!-- User has declined Facebook cookies -->
{/if}

The Twitter share button

While the Facebook share button uses a <button> element, Twitter has to be different. For their button they insist on using an anchor <a> element with a truly scary href attribute.

The href attribute is scary because, URL-encoded into it, is the following information:

  • The page’s title;
  • The page’s description; and
  • The page’s absolute URL address

The address data is easy enough to find - we can use window.location.href.

The other data, however, we need to obtain from the current page’s metadata. And the simplest way to do that is to stuff the data into a Svelte store when we’re doing the metadata update work, and import those stores into the <TwitterButton> component.

I adapted the .src/handleMetadata.js file to make this happen:

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
import { writable } from 'svelte/store';

[...]

// Exported function to update metadate element content attributes
const updateMetadata = (data) => {

[... gatherMetadataHandles stuff ...]

if (metadataHandles) {

metaTitle.set(data.title);
metaDescription.set(data.description);

[... setAttribute code ...]

}
};

const metaTitle = writable('');
const metaDescription = writable('');

export {
updateMetadata,

metaTitle,
metaDescription,
}

Now we can build the component ./src/components/TwitterButton.svelte 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
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
<script>
import { metaTitle, metaDescription } from '../handleMetadata.js';
import { twitterCookies } from '../handleCookies.js';

let dPath = "M5.80573373,15 C12.7721527,15 16.581877,9.22887915 16.581877,4.22385671 C16.581877,4.06002242 16.581877,3.89618812 16.5714931,3.73466135 C17.3122088,3.19816171 17.9525471,2.53359441 18.4602026,1.77326482 C17.7690988,2.08016568 17.0364595,2.28092039 16.28536,2.36976011 C17.0756874,1.89671742 17.6675677,1.15138674 17.9502395,0.274527115 C17.2072164,0.715264453 16.3938137,1.02678037 15.5457981,1.19407596 C14.1105174,-0.331198284 11.7118448,-0.405039095 10.1865706,1.0290879 C9.20241101,1.95440555 8.78590269,3.33315194 9.09049603,4.64844138 C6.04571636,4.4961447 3.20861397,3.05740266 1.28529161,0.691035437 C0.280364327,2.42167943 0.793788713,4.63574999 2.45751448,5.74682343 C1.85525036,5.72951699 1.26567764,5.56683646 0.738408105,5.27262698 L0.738408105,5.32108501 C0.739561868,7.12441605 2.00985456,8.67622684 3.77741896,9.03389326 C3.2201516,9.18618993 2.63519393,9.20811142 2.06754269,9.09850397 C2.56366064,10.6410847 3.98509624,11.6979313 5.60613279,11.7290828 C4.26430681,12.7824682 2.60750362,13.3547344 0.902242404,13.3535807 C0.601110348,13.3524269 0.299978293,13.3339667 7.10542736e-15,13.2982001 C1.73295152,14.4104273 3.74742113,15 5.80573373,14.9965387";
</script>

<style>
a {
@apply bg-white rounded inline-block border-0;
}
a:hover {
background-color: #1da1f2;
}

svg {
fill: #1da1f2;
}
svg:hover {
fill: #ffffff;
}

.cookies-required {
@apply bg-gray-300 rounded inline-block border-0;
}
.cookies-required:hover {
@apply bg-gray-500;
}

.cookies-required svg {
fill: #A0AEC0;
}
.cookies-required svg:hover {
fill: #E2E8F0;
}
</style>

{#if $twitterCookies === 'yes'}
<!-- User has given consent to Twitter cookies being placed on their device -->
<script type="text/javascript" async src="https://platform.twitter.com/widgets.js"></script>

<a
href="https://twitter.com/intent/tweet?text={encodeURIComponent(`${$metaTitle} - ${$metaDescription}`)}&url={encodeURIComponent(window.location.href)}"
title="Share with Twitter Followers">

<svg
viewBox="-13 -15 44 44"
enable-background="new 0 0 44 44"
width="44px" height="44px"
aria-hidden="true" focusable="false">
<g>
<path d={dPath}></path>
</g>
</svg>
</a>

{:else if $twitterCookies !== 'no'}
<!-- User has not yet made a decision about Twitter cookies -->
<a
class="cookies-required"
href="/cookies"
title="Enable Twitter cookies to share with Twitter Followers">

<svg
viewBox="-13 -15 44 44"
enable-background="new 0 0 44 44"
width="44px" height="44px"
aria-hidden="true" focusable="false">
<g>
<path d={dPath}></path>
</g>
</svg>
</a>

{:else}
<!-- User has declined Twitter cookies -->
{/if}

To save you from scrolling the code window, here’s the anchor’s href attribute:

href="https://twitter.com/intent/tweet
?text={encodeURIComponent(`${$metaTitle} - ${$metaDescription}`)}
&url={encodeURIComponent(window.location.href)}"

… And that brings me to the end of this penultimate post in my Rebuild the RikVerse series. The final post will be about something that’s currently missing from the site … something that’s pretty fundamental: the poems!