feat: dynamic media pages

This commit is contained in:
Cory Dransfeldt 2024-10-17 19:47:44 -07:00
parent ae973948c8
commit 0187aaa766
No known key found for this signature in database
13 changed files with 560 additions and 360 deletions

View file

@ -89,7 +89,6 @@ export default async function (eleventyConfig) {
sortAttributes: true,
sortClassName: true,
useShortDoctype: true,
processScripts: ['application/ld+json'],
})
}
return content

69
package-lock.json generated
View file

@ -28,6 +28,7 @@
"html-minifier-terser": "^7.2.0",
"html-to-text": "^9.0.5",
"ics": "^3.8.1",
"linkedom": "0.18.5",
"luxon": "^3.5.0",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.2.0",
@ -1418,6 +1419,13 @@
"dev": true,
"license": "CC0-1.0"
},
"node_modules/cssom": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
"integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
"dev": true,
"license": "MIT"
},
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
@ -2198,6 +2206,13 @@
"node": ">= 0.4"
}
},
"node_modules/html-escaper": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
"integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==",
"dev": true,
"license": "MIT"
},
"node_modules/html-minifier-terser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
@ -2554,6 +2569,53 @@
"url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/linkedom": {
"version": "0.18.5",
"resolved": "https://registry.npmjs.org/linkedom/-/linkedom-0.18.5.tgz",
"integrity": "sha512-JGLaGGtqtu+eOhYrC1wkWYTBcpVWL4AsnwAtMtgO1Q0gI0PuPJKI0zBBE+a/1BrhOE3Uw8JI/ycByAv5cLrAuQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"css-select": "^5.1.0",
"cssom": "^0.5.0",
"html-escaper": "^3.0.3",
"htmlparser2": "^9.1.0",
"uhyphen": "^0.2.0"
}
},
"node_modules/linkedom/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/linkedom/node_modules/htmlparser2": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
"integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
"dev": true,
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.1.0",
"entities": "^4.5.0"
}
},
"node_modules/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
@ -4570,6 +4632,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/uhyphen": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/uhyphen/-/uhyphen-0.2.0.tgz",
"integrity": "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==",
"dev": true,
"license": "ISC"
},
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",

View file

@ -1,6 +1,6 @@
{
"name": "coryd.dev",
"version": "1.1.1",
"version": "1.2.0",
"description": "The source for my personal site. Built using 11ty (and other tools).",
"type": "module",
"engines": {
@ -44,6 +44,7 @@
"html-minifier-terser": "^7.2.0",
"html-to-text": "^9.0.5",
"ics": "^3.8.1",
"linkedom": "0.18.5",
"luxon": "^3.5.0",
"markdown-it": "^14.1.0",
"markdown-it-anchor": "^9.2.0",

View file

@ -11,7 +11,7 @@ article {
}
& h3,
&:not(:has(h3)) p {
&:not(:has(h3)) p:first-of-type {
margin-top: 0;
}

View file

@ -36,7 +36,11 @@ export default async function () {
try {
const movies = await fetchAllMovies()
const favoriteMovies = movies.filter(movie => movie['favorite'])
const recentlyWatchedMovies = movies.filter(movie => movie['last_watched'] && year - DateTime.fromISO(movie['last_watched']).year <= 3)
const now = DateTime.now();
const recentlyWatchedMovies = movies.filter(movie => {
const lastWatched = movie['last_watched']
return (lastWatched && now.diff(DateTime.fromISO(lastWatched), 'months').months <= 6)
})
return {
movies,

View file

@ -46,7 +46,7 @@ export default async function () {
return {
shows,
recentlyWatched: episodes.slice(0, 225),
recentlyWatched: episodes.slice(0, 125),
favorites: shows.filter(show => show.favorite).sort((a, b) => a.title.localeCompare(b.title)),
}
} catch (error) {

View file

@ -4,26 +4,12 @@
{%- assign pageTitle = post.title -%}
{%- elsif title -%}
{%- assign pageTitle = title | append: ' / ' | append: globals.site_name -%}
{%- elsif artist.name -%}
{%- assign pageTitle = 'Artists / ' | append: artist.name | append: ' / ' | append: globals.site_name -%}
{%- elsif schema == 'music-index' -%}
{%- assign pageTitle = 'Music / ' | append: globals.site_name -%}
{%- elsif schema == 'music-period' -%}
{%- assign pageTitle = 'Music / ' | append: page.title | append: globals.site_name -%}
{%- elsif genre.name -%}
{%- assign pageTitle = 'Music / ' | append: genre.name | append: ' / ' | append: globals.site_name -%}
{%- elsif book.title -%}
{%- assign pageTitle = 'Books / ' | append: book.title | append: ' by ' | append: book.author | append: ' / ' | append: globals.site_name -%}
{%- elsif year.value -%}
{%- assign pageTitle = ' / Books ' | prepend: year.value | append: ' / ' | append: globals.site_name -%}
{%- elsif movie.title -%}
{%- assign pageTitle = 'Movies / ' | append: movie.title -%}
{%- if movie.rating -%}
{%- assign pageTitle = pageTitle | append: ' (' | append: movie.rating | append: ')' -%}
{%- endif -%}
{%- assign pageTitle = pageTitle | append: ' / ' | append: globals.site_name -%}
{%- elsif show.title -%}
{%- assign pageTitle = 'Shows / ' | append: show.title | append: ' / ' | append: globals.site_name -%}
{%- elsif page.description -%}
{%- assign pageTitle = page.title | append: ' / ' | append: globals.site_name -%}
{%- endif -%}
@ -31,16 +17,6 @@
{%- assign pageDescription = globals.site_description -%}
{%- if schema == 'blog' -%}
{%- assign pageDescription = post.description | markdown | strip_html -%}
{%- elsif artist.description -%}
{%- assign pageDescription = artist.description | markdown | strip_html | htmlTruncate -%}
{%- elsif book.description -%}
{%- assign pageDescription = book.review | markdown | strip_html | default: book.description | htmlTruncate -%}
{%- elsif movie.description -%}
{%- assign pageDescription = movie.review | markdown | strip_html | default: movie.description | htmlTruncate -%}
{%- elsif show.description -%}
{%- assign pageDescription = show.review | markdown | strip_html | default: show.description | htmlTruncate -%}
{%- elsif genre.description -%}
{%- assign pageDescription = genre.description | markdown | strip_html | htmlTruncate -%}
{%- elsif page.description -%}
{%- assign pageDescription = page.description -%}
{%- elsif description -%}
@ -53,10 +29,6 @@
{%- assign ogImage = globals.cdn_url | append: page.image -%}
{%- when 'music-index' -%}
{%- assign ogImage = globals.cdn_url | append: music.week.artists[0].grid.image -%}
{%- when 'artist' -%}
{%- assign ogImage = globals.cdn_url | append: artist.grid.image -%}
{%- when 'genre' -%}
{%- assign ogImage = globals.cdn_url | append: genre.artists[0].grid.image -%}
{%- when 'watching' -%}
{%- assign featuredMovie = movies.recentlyWatched | first -%}
{%- assign ogImage = globals.cdn_url | append: featuredMovie.grid.backdrop -%}
@ -75,15 +47,6 @@
{%- when 'books-year' -%}
{%- assign featuredBook = books.currentYear | first -%}
{%- assign ogImage = globals.cdn_url | append: featuredBook.grid.image -%}
{%- when 'book' -%}
{%- assign ogImage = globals.cdn_url | append: book.grid.image -%}
{%- when 'movie' -%}
{%- assign ogImage = globals.cdn_url | append: movie.grid.backdrop -%}
{%- when 'show' -%}
{%- assign ogImage = globals.cdn_url | append: show.grid.backdrop -%}
{%- when 'genre' -%}
{%- assign genreArtist = genre.artists | shuffleArray | first -%}
{%- assign ogImage = globals.cdn_url | append: genreArtist.grid.image -%}
{%- endcase -%}
{%- assign escapedPageDescription = pageDescription | escape -%}

View file

@ -1,70 +0,0 @@
---
layout: default
pagination:
data: books.all
size: 1
alias: book
permalink: "{{ book.url }}/index.html"
schema: book
---
{%- capture alt -%}
{{ book.title }}{% if book.author %} by {{ book.author }}{% endif %}
{%- endcapture -%}
<a class="icon-link" href="/books" title="Go back to the books index page">{% tablericon "arrow-left" %} Back to books</a>
<article class="book-focus">
<div class="book-display">
<img
srcset="
{{ globals.cdn_url }}{{ book.image }}?class=verticalsm&type=webp 200w,
{{ globals.cdn_url }}{{ book.image }}?class=verticalmd&type=webp 400w,
{{ globals.cdn_url }}{{ book.image }}?class=verticalbase&type=webp 800w
"
sizes="(max-width: 450px) 203px,
(max-width: 850px) 406px,
(max-width: 1000px) 812px,
812px"
src="{{ globals.cdn_url }}{{ book.image }}?class=verticalsm&type=webp"
alt="{{ alt | replaceQuotes }}"
loading="lazy"
decoding="async"
width="200"
height="307"
/>
<div class="book-meta">
<p class="title"><strong>{{ book.title }}</strong></p>
{% if book.rating %}<p>{{ book.rating }}</p>{% endif %}
{% if book.author %}
<p class="sub-meta">By {{ book.author }}</p>
{% endif %}
{%- if book.favorite -%}
<p class="sub-meta favorite">{% tablericon "heart" %} This is one of my favorite books!</p>
{%- endif -%}
{%- if book.tattoo -%}
<p class="sub-meta tattoo">{% tablericon "needle" %} I have a tattoo inspired by this book!</p>
{%- endif -%}
{% if book.status == 'finished' %}
<p class="sub-meta">Finished on: <strong class="highlight-text">{{ book.date_finished | date: "%B %e, %Y" }}</strong></p>
{% endif %}
{% if book.status == 'started' %}
{%- assign percentage = book.progress | append: '%' -%}
{% render "partials/media/progress-bar.liquid", percentage:percentage %}
{% endif %}
</div>
</div>
{% if book.review %}
{% render "partials/blocks/banners/warning.liquid", text: "There are probably spoilers after this banner — this is a warning about them." %}
<h2>My thoughts</h2>
{{ book.review | markdown }}
<hr />
{% endif %}
{% render "partials/blocks/associated-media.liquid", posts:book.posts %}
{% render "partials/blocks/associated-media.liquid", books:book.related_books %}
{% render "partials/blocks/associated-media.liquid", artists:book.artists %}
{% render "partials/blocks/associated-media.liquid", movies:book.movies %}
{% render "partials/blocks/associated-media.liquid", shows:book.shows %}
{% render "partials/blocks/associated-media.liquid", genres:book.genres %}
{% if book.description %}
<h2>Overview</h2>
{{ book.description | markdown }}
{% endif %}
</article>

View file

@ -1,114 +0,0 @@
---
layout: default
pagination:
data: artists
size: 1
alias: artist
permalink: "{{ artist.url }}/index.html"
updated: "now"
schema: artist
---
{%- capture alt -%}
{{ artist.name }} / {{ artist.country }}
{%- endcapture -%}
{%- capture playLabel -%}
{%- if artist.total_plays == 1 -%}
play
{%- else -%}
plays
{%- endif -%}
{%- endcapture -%}
<noscript><style>[data-toggle-content].text-toggle-hidden {height: unset !important;overflow: unset !important;margin-bottom: unset !important;}[data-toggle-content].text-toggle-hidden::after {display: none !important;}</style></noscript>
<a class="icon-link" href="/music" title="Go back to the music index page">{% tablericon "arrow-left" %} Back to music</a>
<article class="artist-focus">
<div class="artist-display">
<img
srcset="
{{ globals.cdn_url }}{{ artist.image }}?class=w200&type=webp 200w,
{{ globals.cdn_url }}{{ artist.image }}?class=w600&type=webp 400w,
{{ globals.cdn_url }}{{ artist.image }}?class=w800&type=webp 800w
"
sizes="(max-width: 450px) 200px,
(max-width: 850px) 400px,
800px"
src="{{ globals.cdn_url }}{{ artist.image }}?class=w200&type=webp"
alt="{{ alt | replaceQuotes }}"
loading="eager"
decoding="async"
width="200"
height="200"
/>
<div class="artist-meta">
<p class="title"><strong>{{ artist.name }}</strong></p>
<p class="sub-meta country">{% tablericon "map-pin" %} {{ artist.country }}</p>
{%- if artist.favorite -%}
<p class="sub-meta favorite">{% tablericon "heart" %} This is one of my favorite artists!</p>
{%- endif -%}
{%- if artist.tattoo -%}
<p class="sub-meta tattoo">{% tablericon "needle" %} I have a tattoo inspired by this artist!</p>
{%- endif -%}
{%- if artist.total_plays > 0 -%}
<p class="sub-meta"><strong class="highlight-text">{{ artist.total_plays | formatNumber }} {{ playLabel }}</strong></p>
{%- endif -%}
<p class="sub-meta">
<a href="{{ artist.genre.url }}" title="Learn more about {{ artist.genre.name | escape }}">
{{ artist.genre.name }}
</a>
</p>
</div>
</div>
{% render "partials/blocks/associated-media.liquid", posts:artist.posts %}
{% render "partials/blocks/associated-media.liquid", artists:artist.related_artists %}
{% render "partials/blocks/associated-media.liquid", books:artist.books %}
{% render "partials/blocks/associated-media.liquid", movies:artist.movies %}
{% render "partials/blocks/associated-media.liquid", shows:artist.shows %}
{%- if artist.description -%}
<h2>Overview</h2>
<div data-toggle-content class="text-toggle-hidden">{{ artist.description | markdown }}</div>
<button data-toggle-button>Show more</button>
{%- endif -%}
{%- if artist.concerts -%}
<hr />
<p id="concerts" class="concerts">
{% tablericon "device-speaker" %}
I've seen this artist live!
</p>
<ul>
{% for concert in artist.concerts %}
{%- capture venue -%}
{% if concert.venue_name %}
{% if concert.venue_latitude and concert.venue_longitude %}
<a href="https://www.openstreetmap.org/?mlat={{ concert.venue_latitude }}&mlon={{ concert.venue_longitude }}#map=18/{{ concert.venue_latitude }}/{{ concert.venue_longitude }}">{{ concert.venue_name_short }}</a>
{% else %}
{{ concert.venue_name_short }}
{% endif %}
{% endif %}
{%- endcapture -%}
<li>
On <strong class="highlight-text">{{ concert.date | date: "%B %e, %Y" }}</strong>
{% if venue %} at {{ venue }}{% endif %}
{%- if concert.notes -%}
{% assign notes = concert.notes | prepend: "### Notes\n" | markdown %}
{% render "partials/blocks/modal.liquid", label:"Concert info", icon:"info-circle", content:notes, id:concert.id %}
{%- endif -%}
</li>
{% endfor %}
</ul>
{%- endif -%}
{%- if artist.books or artist.concerts or artist.movies -%}<hr />{%- endif -%}
<table>
<tr>
<th>Album</th>
<th>Plays</th>
<th>Year</th>
</tr>
{% for album in artist.albums %}
<tr>
<td>{{ album.name }}</td>
<td>{{ album.total_plays }}</td>
<td>{{ album.release_year }}</td>
</tr>
{% endfor %}
</table>
<p><em>These are the albums by this artist that are in my collection, not necessarily a comprehensive discography.</em></p>
</article>

View file

@ -1,69 +0,0 @@
---
layout: default
pagination:
data: movies.movies
size: 1
alias: movie
permalink: "{{ movie.url }}/index.html"
schema: movie
---
{%- capture alt -%}
{{ movie.title }} / {{ movie.year }}{% if move.rating %} ({{ movie.rating }}){% endif %}
{%- endcapture -%}
<a class="icon-link" href="/watching" title="Go back to the watching index page">{% tablericon "arrow-left" %} Back to watching</a>
<article class="watching focus">
<img
srcset="
{{ globals.cdn_url }}{{ movie.backdrop }}?class=bannersm&type=webp 256w,
{{ globals.cdn_url }}{{ movie.backdrop }}?class=bannermd&type=webp 512w,
{{ globals.cdn_url }}{{ movie.backdrop }}?class=bannerbase&type=webp 1024w
"
sizes="(max-width: 450px) 256px,
(max-width: 850px) 512px,
1024px"
src="{{ globals.cdn_url }}{{ movie.backdrop }}?class=bannersm&type=webp"
alt="{{ alt | replaceQuotes }}"
class="image-banner"
loading="eager"
decoding="async"
width="256"
height="180"
/>
<div class="meta">
<p class="title"><strong>{{ movie.title }}</strong>{%- if movie.year and not movie.rating %} ({{ movie.year }}){%- endif -%}</p>
{%- if movie.rating -%}
<p>
{{ movie.rating }}
{%- if movie.year %}
({{ movie.year }})
{%- endif -%}
</p>
{% endif -%}
{%- if movie.favorite -%}
<p class="sub-meta favorite">{% tablericon "heart" %} This is one of my favorite movies!</p>
{%- endif -%}
{%- if movie.tattoo -%}
<p class="sub-meta tattoo">{% tablericon "needle" %} I have a tattoo inspired by this movie!</p>
{%- endif -%}
{%- if movie.collected -%}
<p class="sub-meta collected">{% tablericon "circle-check" %} This movie is in my collection!</p>
{%- endif -%}
{%- if movie.lastWatched -%}<p class="sub-meta">Last watched on {{ movie.lastWatched | date: "%B %e, %Y" }}.</p>{%- endif -%}
</div>
{% if movie.review %}
{% render "partials/blocks/banners/warning.liquid", text: "There are probably spoilers after this banner — this is a warning about them." %}
<h2>My thoughts</h2>
{{ movie.review | markdown }}
<hr />
{% endif %}
{% render "partials/blocks/associated-media.liquid", posts:movie.posts %}
{% render "partials/blocks/associated-media.liquid", movies:movie.related_movies %}
{% render "partials/blocks/associated-media.liquid", shows:movie.shows %}
{% render "partials/blocks/associated-media.liquid", artists:movie.artists %}
{% render "partials/blocks/associated-media.liquid", books:movie.books %}
{% render "partials/blocks/associated-media.liquid", genres:movie.genres %}
{% if movie.description %}
<h2>Overview</h2>
{{ movie.description | markdown }}
{% endif %}
</article>

View file

@ -1,65 +0,0 @@
---
layout: default
pagination:
data: tv.shows
size: 1
alias: show
permalink: "{{ show.url }}/index.html"
schema: show
---
{%- capture alt -%}
{{ show.title }} / {{ show.year }}
{%- endcapture -%}
<a class="icon-link" href="/watching" title="Go back to the watching index page">{% tablericon "arrow-left" %} Back to watching</a>
<article class="watching focus">
<img
srcset="
{{ globals.cdn_url }}{{ show.backdrop }}?class=bannersm&type=webp 256w,
{{ globals.cdn_url }}{{ show.backdrop }}?class=bannermd&type=webp 512w,
{{ globals.cdn_url }}{{ show.backdrop }}?class=bannerbase&type=webp 1024w
"
sizes="(max-width: 450px) 256px,
(max-width: 850px) 512px,
1024px"
src="{{ globals.cdn_url }}{{ show.backdrop }}?class=bannersm&type=webp"
alt="{{ alt | replaceQuotes }}"
class="image-banner"
loading="eager"
decoding="async"
width="256"
height="180"
/>
<div class="meta">
<p class="title"><strong>{{ show.title }}</strong>{%- if show.year %} ({{ show.year }}){%- endif -%}</p>
{%- if show.favorite -%}
<p class="sub-meta favorite">{% tablericon "heart" %} This is one of my favorite shows!</p>
{%- endif -%}
{%- if show.tattoo -%}
<p class="sub-meta tattoo">{% tablericon "needle" %} I have a tattoo inspired by this show!</p>
{%- endif -%}
{%- if show.collected -%}
<p class="sub-meta collected">{% tablericon "circle-check" %} This show is in my collection!</p>
{%- endif -%}
{%- if show.episode.formatted_episode -%}
{%- capture lastWatchedText -%}
I last watched <strong class="highlight-text">{{ show.episode.formatted_episode }}</strong> on {{ show.episode.last_watched_at | date: "%B %e, %Y" }}.
{%- endcapture -%}
<p class="sub-meta">{{ lastWatchedText }}</p>
{%- endif -%}
</div>
{% if show.review %}
{% render "partials/blocks/banners/warning.liquid", text: "There are probably spoilers after this banner — this is a warning about them." %}
<h2>My thoughts</h2>
{{ show.review | markdown }}
<hr />
{% endif %}
{% render "partials/blocks/associated-media.liquid", posts:show.posts %}
{% render "partials/blocks/associated-media.liquid", artists:show.artists %}
{% render "partials/blocks/associated-media.liquid", shows:show.related_shows %}
{% render "partials/blocks/associated-media.liquid", movies:show.movies %}
{% render "partials/blocks/associated-media.liquid", books:show.books %}
{% if show.description %}
<h2>Overview</h2>
{{ show.description | markdown }}
{% endif %}
</article>

View file

@ -0,0 +1,457 @@
import { createClient } from '@supabase/supabase-js'
import { parseHTML } from 'linkedom'
import markdownIt from 'markdown-it'
import truncateHtml from 'truncate-html'
import { convert } from 'html-to-text'
const md = markdownIt({ html: true, linkify: true })
const ICON_MAP = {
alertTriangle: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-alert-triangle"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 9v4" /><path d="M10.363 3.591l-8.106 13.534a1.914 1.914 0 0 0 1.636 2.871h16.214a1.914 1.914 0 0 0 1.636 -2.87l-8.106 -13.536a1.914 1.914 0 0 0 -3.274 0z" /><path d="M12 16h.01" /></svg>`,
arrowLeft: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l14 0"/><path d="M5 12l6 6"/><path d="M5 12l6 -6"/></svg>`,
article: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-article"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 4m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M7 8h10" /><path d="M7 12h10" /><path d="M7 16h10" /></svg>`,
books: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-books"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M9 4m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v14a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z" /><path d="M5 8h4" /><path d="M9 16h4" /><path d="M13.803 4.56l2.184 -.53c.562 -.135 1.133 .19 1.282 .732l3.695 13.418a1.02 1.02 0 0 1 -.634 1.219l-.133 .041l-2.184 .53c-.562 .135 -1.133 -.19 -1.282 -.732l-3.695 -13.418a1.02 1.02 0 0 1 .634 -1.219l.133 -.041z" /><path d="M14 9l4 -1" /><path d="M16 16l3.923 -.98" /></svg>`,
circleCheck: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-circle-check"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"/><path d="M9 12l2 2l4 -4"/></svg>`,
circleX: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-circle-x"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M10 10l4 4m0 -4l-4 4" /></svg>`,
deviceSpeaker: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-device-speaker"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 3m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z" /><path d="M12 14m-3 0a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" /><path d="M12 7l0 .01" /></svg>`,
deviceTvOld: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-device-tv-old"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 7m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M16 3l-4 4l-4 -4" /><path d="M15 7v13" /><path d="M18 15v.01" /><path d="M18 12v.01" /></svg>`,
film: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-movie"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2z" /><path d="M8 4l0 16" /><path d="M16 4l0 16" /><path d="M4 8l4 0" /><path d="M4 16l4 0" /><path d="M4 12l16 0" /><path d="M16 8l4 0" /><path d="M16 16l4 0" /></svg>`,
headphones: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-headphones"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M15 13m0 2a2 2 0 0 1 2 -2h1a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-1a2 2 0 0 1 -2 -2z" /><path d="M4 15v-3a8 8 0 0 1 16 0v3" /></svg>`,
heart: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-heart"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19.5 12.572l-7.5 7.428l-7.5 -7.428a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572"/></svg>`,
infoCircle: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-info-circle"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /><path d="M12 9h.01" /><path d="M11 12h1v4h1" /></svg>`,
link: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-link"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 15l6 -6" /><path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" /><path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" /></svg>`,
mapPin: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-map-pin"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 11a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" /><path d="M17.657 16.657l-4.243 4.243a2 2 0 0 1 -2.827 0l-4.244 -4.243a8 8 0 1 1 11.314 0z" /></svg>`,
needle: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-needle"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 21c-.667 -.667 3.262 -6.236 11.785 -16.709a3.5 3.5 0 1 1 5.078 4.791c-10.575 8.612 -16.196 12.585 -16.863 11.918z"/><path d="M17.5 6.5l-1 1"/></svg>`,
movie: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-movie"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 6h16M4 12h16M4 18h16M10 4v2M14 4v2M10 12v2M14 12v2M10 18v2M14 18v2"/></svg>`,
}
const regionNames = new Intl.DisplayNames(['en'], { type: 'region' })
const getCountryName = (countryCode) => regionNames.of(countryCode.trim()) || countryCode.trim()
const parseCountryField = (countryField) => {
if (!countryField) return null
const delimiters = [',', '/', '&', 'and']
let countries = [countryField]
delimiters.forEach(delimiter => countries = countries.flatMap(country => country.split(delimiter)))
return countries.map(getCountryName).join(', ')
}
const generateMediaLinks = (data, type, count = 10) => {
if (!data || !type) return ''
const dataSlice = data.slice(0, count)
if (dataSlice.length === 0) return null
const buildLink = (item) => {
switch (type) {
case 'genre':
return `<a href="${item['genre_url']}">${item['genre_name']}</a>`
case 'artist':
return `<a href="${item['url']}">${item['name']}</a>`
case 'book':
return `<a href="${item['url']}">${item['title']}</a>`
default:
return ''
}
}
if (dataSlice.length === 1) return buildLink(dataSlice[0])
const links = dataSlice.map(buildLink)
const allButLast = links.slice(0, -1).join(', ')
const last = links[links.length - 1]
return `${allButLast} and ${last}`
}
async function fetchDataByUrl(supabase, table, url) {
const { data, error } = await supabase.from(table).select('*').eq('url', url).single()
if (error) {
console.error(`Error fetching from ${table}:`, error)
return null
}
return data
}
async function fetchGlobals(supabase) {
const { data, error } = await supabase.from('optimized_globals').select('*').single()
if (error) {
console.error('Error fetching globals:', error)
return {}
}
return data
}
function generateMetadata(data, type, globals) {
let title = globals['site_name']
let description = data.description || globals.site_description
const canonicalUrl = data.url ? `${globals.url}${data.url}` : globals.url
const ogImage = `${globals.cdn_url}${data.image || globals.avatar}?class=w800`
description = convert(truncateHtml(md.render(description), 100, {
byWords: true,
ellipsis: '...'
}),
{
wordwrap: false,
selectors: [
{ selector: 'a', options: { ignoreHref: true } },
{ selector: 'h1', options: { uppercase: false } },
{ selector: 'h2', options: { uppercase: false } },
{ selector: 'h3', options: { uppercase: false } },
{ selector: '*', format: 'block' }
]
}).replace(/\s+/g, ' ').trim()
switch (type) {
case 'artist':
title = `Artists / ${data['name']} / ${globals['site_name']}`
break
case 'genre':
title = `Genre / ${data['name']} / ${globals['site_name']}`
break
case 'book':
title = `Books / ${data['title']} by ${data.author} / ${globals['site_name']}`
break
case 'movie':
title = `Movies / ${data['title']} (${data.year}) / ${globals['site_name']}`
break
case 'show':
title = `Shows / ${data['title']} / ${globals['site_name']}`
break
default:
title = `${data['title'] || globals['site_name']}`
}
return {
title,
description,
'og:title': title,
'og:description': description,
'og:image': ogImage,
'og:url': canonicalUrl,
'canonical': canonicalUrl
}
}
function updateDynamicContent(html, metadata, mediaHtml) {
const { document } = parseHTML(html)
const titleTag = document.querySelector('title[data-dynamic="title"]')
if (titleTag) titleTag.textContent = metadata['title']
const dynamicMetaSelectors = [
{ selector: 'meta[data-dynamic="description"]', attribute: 'content', value: metadata.description },
{ selector: 'meta[data-dynamic="og:title"]', attribute: 'content', value: metadata['og:title'] },
{ selector: 'meta[data-dynamic="og:description"]', attribute: 'content', value: metadata['og:description'] },
{ selector: 'meta[data-dynamic="og:image"]', attribute: 'content', value: metadata['og:image'] },
{ selector: 'meta[data-dynamic="og:url"]', attribute: 'content', value: metadata.canonical },
]
dynamicMetaSelectors.forEach(({ selector, attribute, value }) => {
const element = document.querySelector(selector)
if (element) element.setAttribute(attribute, value)
})
const canonicalLink = document.querySelector('link[rel="canonical"]')
if (canonicalLink) canonicalLink.setAttribute('href', metadata.canonical)
const pageElement = document.querySelector('[data-dynamic="page"]')
if (pageElement) pageElement.innerHTML = mediaHtml
return document.toString()
}
const warningBanner = `<div class="banner warning"><p>${ICON_MAP['alertTriangle']}There are probably spoilers after this banner — this is a warning about them.</p></div>`
function generateAssociatedMediaHTML(data, isGenre = false) {
const sections = [
{ key: 'artists', icon: 'headphones', category: 'music', title: 'Related Artist(s)' },
{ key: 'books', icon: 'books', category: 'books', title: 'Related Book(s)' },
{ key: 'genres', icon: 'headphones', category: 'music', title: 'Related Genre(s)' },
{ key: 'related_movies', icon: 'film', category: 'movies', title: 'Related Movie(s)' },
{ key: 'posts', icon: 'article', category: 'article', title: 'Related Post(s)' },
{ key: 'shows', icon: 'deviceTvOld', category: 'tv', title: 'Related Show(s)' }
]
return sections
.filter(({ key }) => !(isGenre && key === 'artists'))
.map(({ key, category, icon, title }) =>
data[key] && data[key].length
? `<div class="associated-media">
<p class="${category}">${ICON_MAP[icon]} ${title}</p>
<ul>${data[key].map(item => `<li><a href="${item.url}">${item.name || item.title} ${item.year ? `(${item.year})` : ''}</a></li>`).join('')}</ul>
</div>`
: ''
)
.join('')
}
function generateWatchingHTML(media, globals, type) {
const isShow = type === 'show'
const label = isShow ? 'show' : 'movie'
const lastWatched = media.lastWatched || (isShow && media.episode?.last_watched_at)
return `
<a class="icon-link" href="/watching">${ICON_MAP.arrowLeft} Back to watching</a>
<article class="watching focus">
<img
srcset="
${globals.cdn_url}${media.backdrop}?class=bannersm&type=webp 256w,
${globals.cdn_url}${media.backdrop}?class=bannermd&type=webp 512w,
${globals.cdn_url}${media.backdrop}?class=bannerbase&type=webp 1024w
"
sizes="(max-width: 450px) 256px,
(max-width: 850px) 512px,
1024px"
src="${globals.cdn_url}${media.backdrop}?class=bannersm&type=webp"
alt="${media.title} / ${media.year}"
class="image-banner"
loading="eager"
decoding="async"
width="256"
height="180"
/>
<div class="meta">
<p class="title"><strong>${media.title}</strong> (${media.year})</p>
${media.favorite ? `<p class="sub-meta favorite">${ICON_MAP.heart} This is one of my favorite ${label}s!</p>` : ''}
${media.tattoo ? `<p class="sub-meta tattoo">${ICON_MAP.needle} I have a tattoo inspired by this ${label}!</p>` : ''}
${media.collected ? `<p class="sub-meta collected">${ICON_MAP.circleCheck} This ${label} is in my collection!</p>` : ''}
${lastWatched ? `<p class="sub-meta">Last watched on ${new Date(lastWatched).toLocaleDateString()}</p>` : ''}
</div>
${media.review ? `${warningBanner}<h2>My thoughts</h2><p>${md.render(media.review)}</p>` : ''}
${generateAssociatedMediaHTML(media)}
${media.description ? `<h2>Overview</h2><p>${md.render(media.description)}</p>` : ''}
</article>
`
}
function generateConcertModal(concert) {
const venue = concert.venue_name
? concert.venue_latitude && concert.venue_longitude
? `<a href="https://www.openstreetmap.org/?mlat=${concert.venue_latitude}&mlon=${concert.venue_longitude}#map=18/${concert.venue_latitude}/${concert.venue_longitude}">${concert.venue_name_short}</a>`
: concert.venue_name_short
: ''
const notesModal = concert.notes
? `<input class="modal-input" id="${concert.id}" type="checkbox" tabindex="0" />
<label class="modal-toggle" for="${concert.id}">${ICON_MAP['infoCircle']}</label>
<div class="modal-wrapper">
<div class="modal-body">
<label class="modal-close" for="${concert.id}">${ICON_MAP['circleX']}</label>
<div>
<h3>Notes</h3>
${md.render(concert.notes)}
</div>
</div>
</div>`
: ''
return `
<li>
<strong class="highlight-text">${new Date(concert.date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</strong> at ${venue}
${notesModal}
</li>
`
}
function generateArtistHTML(artist, globals) {
const playLabel = artist?.total_plays === 1 ? 'play' : 'plays'
const concertsList = artist.concerts?.length
? `<hr />
<p id="concerts" class="concerts">
${ICON_MAP['deviceSpeaker']}
I've seen this artist live!
</p>
<ul>${artist.concerts.map(generateConcertModal).join('')}</ul>`
: ''
const albumsTable = artist.albums?.length
? `<table>
<tr><th>Album</th><th>Plays</th><th>Year</th></tr>
${artist.albums.map(album => `
<tr>
<td>${album.name}</td>
<td>${album.total_plays || 0}</td>
<td>${album.release_year}</td>
</tr>`).join('')}
</table>
<p><em>These are the albums by this artist that are in my collection, not necessarily a comprehensive discography.</em></p>
`
: ''
return `
<a class="icon-link" href="/music">${ICON_MAP.arrowLeft} Back to music</a>
<article class="artist-focus">
<div class="artist-display">
<img
srcset="
${globals.cdn_url}${artist.image}?class=w200&type=webp 200w,
${globals.cdn_url}${artist.image}?class=w600&type=webp 400w,
${globals.cdn_url}${artist.image}?class=w800&type=webp 800w
"
sizes="(max-width: 450px) 200px,
(max-width: 850px) 400px,
800px"
src="${globals.cdn_url}${artist.image}?class=w200&type=webp"
alt="${artist.name} / ${artist.country}"
loading="eager"
decoding="async"
width="200"
height="200"
/>
<div class="artist-meta">
<p class="title"><strong>${artist.name}</strong></p>
<p class="sub-meta country">${ICON_MAP['mapPin']} ${parseCountryField(artist.country)}</p>
${artist.favorite ? `<p class="sub-meta favorite">${ICON_MAP['heart']} This is one of my favorite artists!</p>` : ''}
${artist.tattoo ? `<p class="sub-meta tattoo">${ICON_MAP['needle']} I have a tattoo inspired by this artist!</p>` : ''}
${artist.total_plays ? `<p class="sub-meta"><strong class="highlight-text">${artist.total_plays} ${playLabel}</strong></p>` : ''}
<p class="sub-meta">${artist.genre ? `<a href="${artist.genre.url}">${artist.genre.name}</a>` : ''}</p>
</div>
</div>
${artist.description ? `
<h2>Overview</h2>
<div data-toggle-content class="text-toggle-hidden">${md.render(artist.description)}</div>
<button data-toggle-button>Show more</button>` : ''
}
${concertsList}
${albumsTable}
</article>
`
}
function generateBookHTML(book, globals) {
const alt = `${book.title}${book.author ? ` by ${book.author}` : ''}`
const percentage = book.progress ? `${book.progress}%` : ''
const status = book.status === 'finished'
? `Finished on <strong>${new Date(book.date_finished).toLocaleDateString()}</strong>`
: percentage
? `<div class="progress-bar-wrapper" title="${percentage}">
<div style="width:${percentage}" class="progress-bar"></div>
</div>`
: ''
return `
<a class="icon-link" href="/books">${ICON_MAP.arrowLeft} Back to books</a>
<article class="book-focus">
<div class="book-display">
<img
srcset="
${globals.cdn_url}${book.image}?class=verticalsm&type=webp 200w,
${globals.cdn_url}${book.image}?class=verticalmd&type=webp 400w,
${globals.cdn_url}${book.image}?class=verticalbase&type=webp 800w
"
sizes="(max-width: 450px) 203px, (max-width: 850px) 406px, 812px"
src="${globals.cdn_url}${book.image}?class=verticalsm&type=webp"
alt="${alt}"
loading="lazy"
decoding="async"
width="200"
height="307"
/>
<div class="book-meta">
<p class="title"><strong>${book.title}</strong></p>
${book.rating ? `<p>${book.rating}</p>` : ''}
${book.author ? `<p class="sub-meta">By ${book.author}</p>` : ''}
${book.favorite ? `<p class="sub-meta favorite">${ICON_MAP.heart} This is one of my favorite books!</p>` : ''}
${book.tattoo ? `<p class="sub-meta tattoo">${ICON_MAP.needle} I have a tattoo inspired by this book!</p>` : ''}
${status ? `<p class="sub-meta">${status}</p>` : ''}
</div>
</div>
${book.review ? `${warningBanner}<h2>My thoughts</h2><p>${book.review}</p>` : ''}
${generateAssociatedMediaHTML(book)}
<h2>Overview</h2>
<p>${md.render(book.description)}</p>
</article>
`
}
function generateGenreHTML(genre) {
const artistCount = genre.artists?.length || 0
const connectingWords = artistCount > 1 ? 'artists are' : 'artist is'
const mediaLinks = generateMediaLinks(genre.artists, 'artist', 5)
return `
<a class="icon-link" href="/music">${ICON_MAP.arrowLeft} Back to music</a>
<h2>${genre.name}</h2>
<article class="genre-focus">
${mediaLinks ? `
<p>My top <strong class="highlight-text">${genre.name}</strong> ${connectingWords} ${mediaLinks}. I've listened to <strong class="highlight-text">${genre.total_plays}</strong> tracks from this genre.</p>
<hr />` : ''}
${generateAssociatedMediaHTML(genre, true)}
${genre.description ? `
<h3>Overview</h3>
<div data-toggle-content class="text-toggle-hidden">
${md.render(genre.description)}
<p><a href="${genre.wiki_link}">Continue reading at Wikipedia.</a></p>
<p><em>Wikipedia content provided under the terms of the <a href="https://creativecommons.org/licenses/by-sa/3.0/">Creative Commons BY-SA license</a>.</em></p>
</div>
<button data-toggle-button>Show more</button>` : ''}
</article>
`
}
export default {
async fetch(request, env) {
const url = new URL(request.url)
const path = url.pathname.replace(/\/$/, '')
const supabaseUrl = env.SUPABASE_URL
const supabaseKey = env.SUPABASE_KEY
const supabase = createClient(supabaseUrl, supabaseKey)
let data, type
if (path === '/books' || path === '/books/') return fetch('https://coryd.dev/books/')
if (path.startsWith('/books/years/')) return fetch(`https://coryd.dev${path}`)
if (path.startsWith('/watching/movies/')) {
data = await fetchDataByUrl(supabase, 'optimized_movies', path)
type = 'movie'
} else if (path.startsWith('/watching/shows/')) {
data = await fetchDataByUrl(supabase, 'optimized_shows', path)
type = 'show'
} else if (path.startsWith('/music/artists/')) {
data = await fetchDataByUrl(supabase, 'optimized_artists', path)
type = 'artist'
} else if (path.startsWith('/music/genres/')) {
data = await fetchDataByUrl(supabase, 'optimized_genres', path)
type = 'genre'
} else if (path.startsWith('/books/')) {
data = await fetchDataByUrl(supabase, 'optimized_books', path)
type = 'book'
} else {
return Response.redirect('https://coryd.dev/404', 302)
}
if (!data) return Response.redirect('https://coryd.dev/404', 302)
const globals = await fetchGlobals(supabase)
let mediaHtml
switch (type) {
case 'artist':
mediaHtml = generateArtistHTML(data, globals)
break
case 'genre':
mediaHtml = generateGenreHTML(data, globals)
break
case 'book':
mediaHtml = generateBookHTML(data, globals)
break
default:
mediaHtml = generateWatchingHTML(data, globals, type)
break
}
const templateResponse = await fetch('https://coryd.dev/dynamic.html')
const template = await templateResponse.text()
const metadata = generateMetadata(data, type, globals)
const html = updateDynamicContent(template, metadata, mediaHtml)
const headers = new Headers({
'Content-Type': 'text/html',
'Cache-Control': 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=86400',
})
return new Response(html, { headers })
}
}

View file

@ -0,0 +1,25 @@
name = "dynamic-media-worker"
main = "./index.js"
compatibility_date = "2023-01-01"
account_id = "${CF_ACCOUNT_ID}"
workers_dev = true
[observability]
enabled = true
[env.production]
name = "dynamic-media-worker-production"
routes = [
{ pattern = "https://coryd.dev/watching/movies/*", zone_id = "${CF_ZONE_ID}" },
{ pattern = "https://coryd.dev/watching/shows/*", zone_id = "${CF_ZONE_ID}" },
{ pattern = "https://coryd.dev/music/artists/*", zone_id = "${CF_ZONE_ID}" },
{ pattern = "https://coryd.dev/music/genres/*", zone_id = "${CF_ZONE_ID}" },
{ pattern = "https://coryd.dev/books/*", zone_id = "${CF_ZONE_ID}" },
]
[[env.production.excludes]]
routes = [
{ pattern = "https://coryd.dev/books", zone_id = "${CF_ZONE_ID}" },
{ pattern = "https://coryd.dev/books/", zone_id = "${CF_ZONE_ID}" },
]