Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
1533e9be98
|
|||
f506a11416
|
|||
3da5b1a974
|
|||
76c282c2cb
|
|||
245995c32d
|
@ -10,8 +10,8 @@ BASE_URL = 'https://moshingmammut.phlaym.net'
|
||||
VERBOSE = false
|
||||
DEBUG_LOG = false
|
||||
IGNORE_USERS = @moshhead@metalhead.club
|
||||
WEBSUB_HUB = 'http://pubsubhubbub.superfeedr.com'
|
||||
|
||||
PUBLIC_WEBSUB_HUB = 'http://pubsubhubbub.superfeedr.com'
|
||||
PUBLIC_REFRESH_INTERVAL = 10000
|
||||
PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME = 'Metalhead.club'
|
||||
PORT = 3001
|
@ -19,7 +19,7 @@
|
||||
<meta name="theme-color" content="#17063b" media="(prefers-color-scheme: dark)" />
|
||||
<meta name="theme-color" content="#BCB9B2" media="(prefers-color-scheme: light)" />
|
||||
<link rel="alternate" type="application/atom+xml" href="/feed.xml" title="Atom Feed" />
|
||||
<link rel="hub" href="https://pubsubhubbub.superfeedr.com" />
|
||||
<link rel="hub" href="%sveltekit.env.PUBLIC_WEBSUB_HUB%" />
|
||||
%sveltekit.head%
|
||||
<style>
|
||||
body {
|
||||
|
@ -3,10 +3,12 @@
|
||||
|
||||
interface Props {
|
||||
account: Account;
|
||||
lazyLoadImages: Boolean;
|
||||
}
|
||||
|
||||
let { account }: Props = $props();
|
||||
let { account, lazyLoadImages = true }: Props = $props();
|
||||
let avatarDescription: string = $derived(`Avatar for ${account.acct}`);
|
||||
let loadingProp = $derived(lazyLoadImages ? 'lazy' : 'eager');
|
||||
let sourceSetHtml: string = $derived.by(() => {
|
||||
// Sort thumbnails by file type. This is important, because the order of the srcset entries matter.
|
||||
// We need the best format to be first
|
||||
@ -41,7 +43,7 @@
|
||||
|
||||
<picture>
|
||||
{@html sourceSetHtml}
|
||||
<img src={account.avatar} alt={avatarDescription} loading="lazy" width="50" height="50" />
|
||||
<img src={account.avatar} alt={avatarDescription} loading={loadingProp} width="50" height="50" />
|
||||
</picture>
|
||||
|
||||
<style>
|
||||
|
@ -8,9 +8,10 @@
|
||||
|
||||
interface Props {
|
||||
post: Post;
|
||||
lazyLoadImages: Boolean;
|
||||
}
|
||||
|
||||
let { post }: Props = $props();
|
||||
let { post, lazyLoadImages = true }: Props = $props();
|
||||
let displayRelativeTime = $state(false);
|
||||
const absoluteDate = new Date(post.created_at).toLocaleString();
|
||||
const timePassed = secondsSince(new Date(post.created_at));
|
||||
@ -20,6 +21,7 @@
|
||||
}
|
||||
return absoluteDate;
|
||||
});
|
||||
let loadingProp = $derived(lazyLoadImages ? 'lazy' : 'eager');
|
||||
|
||||
const songs = filterDuplicates(post.songs ?? []);
|
||||
|
||||
@ -115,7 +117,7 @@
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="avatar"><AvatarComponent account={post.account} /></div>
|
||||
<div class="avatar"><AvatarComponent account={post.account} {lazyLoadImages} /></div>
|
||||
<div class="account"><AccountComponent account={post.account} /></div>
|
||||
<div class="meta">
|
||||
<small><a href={post.url} target="_blank" title={absoluteDate}>{dateCreated}</a></small>
|
||||
@ -127,7 +129,12 @@
|
||||
<div class="info-wrapper">
|
||||
<picture>
|
||||
{@html getSourceSetHtml(song)}
|
||||
<img class="bgimage" src={song.thumbnailUrl} loading="lazy" alt="Blurred cover" />
|
||||
<img
|
||||
class="bgimage"
|
||||
src={song.thumbnailUrl}
|
||||
loading={loadingProp}
|
||||
alt="Blurred cover"
|
||||
/>
|
||||
</picture>
|
||||
<a href={song.pageUrl ?? song.postedUrl} target="_blank">
|
||||
<div class="info">
|
||||
@ -136,7 +143,7 @@
|
||||
<img
|
||||
src={song.thumbnailUrl}
|
||||
alt="Cover for {song.artistName} - {song.title}"
|
||||
loading="lazy"
|
||||
loading={loadingProp}
|
||||
width={song.thumbnailWidth}
|
||||
height={song.thumbnailHeight}
|
||||
/>
|
||||
@ -193,6 +200,12 @@
|
||||
border-radius: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.cover img {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
object-fit: contain;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.bgimage {
|
||||
display: none;
|
||||
background-color: var(--color-bg);
|
||||
@ -231,6 +244,10 @@
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
.cover img {
|
||||
max-width: 60px;
|
||||
max-height: 60px;
|
||||
}
|
||||
.bgimage {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
@ -1037,6 +1037,12 @@ function getCachedThumbnail(thumbnailUrl: string): SongThumbnailImage[] | null {
|
||||
}
|
||||
|
||||
function cacheThumbnail(thumbnailUrl: string, thumbnails: SongThumbnailImage[]) {
|
||||
if (!thumbnails) {
|
||||
// This usually means, that the data is being saved to cached,
|
||||
// while the thumbnail generation is not finished yet
|
||||
logger.debug('will not cache empty thumbnail list', thumbnailUrl);
|
||||
return;
|
||||
}
|
||||
const now = new Date().getTime();
|
||||
const initialSize = thumbnailCache.size;
|
||||
if (initialSize >= maxThumbnailCacheSize) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { BASE_URL, WEBSUB_HUB } from '$env/static/private';
|
||||
import { BASE_URL } from '$env/static/private';
|
||||
import { PUBLIC_WEBSUB_HUB } from '$env/static/public';
|
||||
import { PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME } from '$env/static/public';
|
||||
import type { Post } from '$lib//mastodon/response';
|
||||
import { Logger } from '$lib/log';
|
||||
@ -10,7 +11,7 @@ const logger = new Logger('RSS');
|
||||
|
||||
export function createFeed(posts: Post[]): Feed {
|
||||
const baseUrl = BASE_URL.endsWith('/') ? BASE_URL : BASE_URL + '/';
|
||||
const hub = WEBSUB_HUB ? WEBSUB_HUB : undefined;
|
||||
const hub = PUBLIC_WEBSUB_HUB ? PUBLIC_WEBSUB_HUB : undefined;
|
||||
const feed = new Feed({
|
||||
title: `${PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME} music feed`,
|
||||
description: `Posts about music on ${PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME}`,
|
||||
@ -51,15 +52,15 @@ export function createFeed(posts: Post[]): Feed {
|
||||
}
|
||||
export async function saveAtomFeed(feed: Feed) {
|
||||
await fs.writeFile('feed.xml', feed.atom1(), { encoding: 'utf8' });
|
||||
if (!WEBSUB_HUB || !PROD) {
|
||||
logger.info('Skipping Websub publish. hub configured?', WEBSUB_HUB, 'Production?', PROD);
|
||||
if (!PUBLIC_WEBSUB_HUB || !PROD) {
|
||||
logger.info('Skipping Websub publish. hub configured?', PUBLIC_WEBSUB_HUB, 'Production?', PROD);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const param = new FormData();
|
||||
param.append('hub.mode', 'publish');
|
||||
param.append('hub.url', `${BASE_URL}/feed.xml`);
|
||||
await fetch(WEBSUB_HUB, {
|
||||
await fetch(PUBLIC_WEBSUB_HUB, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: param
|
||||
|
@ -335,7 +335,7 @@ export class TimelineReader {
|
||||
50,
|
||||
3,
|
||||
account.url,
|
||||
['webp', 'avif', 'jpeg'],
|
||||
['avif', 'jpeg'],
|
||||
avatar
|
||||
)
|
||||
);
|
||||
@ -363,7 +363,7 @@ export class TimelineReader {
|
||||
200,
|
||||
3,
|
||||
song.thumbnailUrl,
|
||||
['webp', 'avif', 'jpeg'],
|
||||
['avif', 'jpeg'],
|
||||
avatar,
|
||||
SongThumbnailImageKind.Big
|
||||
)
|
||||
@ -374,7 +374,7 @@ export class TimelineReader {
|
||||
60,
|
||||
3,
|
||||
song.thumbnailUrl,
|
||||
['webp', 'avif', 'jpeg'],
|
||||
['avif', 'jpeg'],
|
||||
avatar,
|
||||
SongThumbnailImageKind.Small
|
||||
)
|
||||
|
@ -163,7 +163,7 @@
|
||||
{#if posts.length === 0}
|
||||
Sorry, no posts recommending music have been found yet
|
||||
{/if}
|
||||
{#each posts as post (post.url)}
|
||||
{#each posts as post, index (post.url)}
|
||||
<div
|
||||
class="post"
|
||||
transition:edgeFly|global={{
|
||||
@ -173,7 +173,7 @@
|
||||
easing: cubicInOut
|
||||
}}
|
||||
>
|
||||
<PostComponent {post} />
|
||||
<PostComponent {post} lazyLoadImages={index >= 4} />
|
||||
</div>
|
||||
{/each}
|
||||
<LoadMoreComponent
|
||||
|
@ -2,9 +2,9 @@ import type { Post } from '$lib/mastodon/response';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ fetch, setHeaders }) => {
|
||||
const p = await fetch('/');
|
||||
const p = await fetch('/api/posts?count=5');
|
||||
setHeaders({
|
||||
'cache-control': 'public,max-age=60'
|
||||
'cache-control': 'public,max-age=300'
|
||||
});
|
||||
const j: Post[] = await p.json();
|
||||
return {
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { Logger } from '$lib/log';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
const logger = new Logger('+server.ts /');
|
||||
|
||||
export const GET = (async ({ fetch, setHeaders }) => {
|
||||
const start = performance.now();
|
||||
setHeaders({
|
||||
'cache-control': 'max-age=10'
|
||||
});
|
||||
const afterHeaders = performance.now();
|
||||
logger.debug('Headers took', afterHeaders - start, 'ms');
|
||||
const f = await fetch('api/posts?count=5');
|
||||
const afterFetch = performance.now();
|
||||
logger.debug('Fetch took', afterFetch - afterHeaders, 'ms');
|
||||
return f;
|
||||
}) satisfies RequestHandler;
|
@ -7,7 +7,10 @@ import { performance } from 'perf_hooks';
|
||||
|
||||
const logger = new Logger('+server.ts API');
|
||||
|
||||
export const GET = (async ({ url }) => {
|
||||
export const GET = (async ({ url, setHeaders }) => {
|
||||
setHeaders({
|
||||
'cache-control': 'max-age=10'
|
||||
});
|
||||
const start = performance.now();
|
||||
const since = url.searchParams.get('since');
|
||||
const before = url.searchParams.get('before');
|
||||
|
Reference in New Issue
Block a user