diff --git a/src/imageresizer/src/index.ts b/src/imageresizer/src/index.ts index 569b242..f58ccd3 100644 --- a/src/imageresizer/src/index.ts +++ b/src/imageresizer/src/index.ts @@ -46,7 +46,7 @@ app.get("/cdn/:dir/:filename", async (req: Request<{ dir: string; filename: stri } // Validate parameters - const validFormats = ["avif", "webp", "jpg"]; + const validFormats = ["avif", "jpg"]; const validWidths = ["150", "300", "768", "1024", "original"]; if (width && !validWidths.includes(width.toString())) { @@ -57,11 +57,11 @@ app.get("/cdn/:dir/:filename", async (req: Request<{ dir: string; filename: stri if (format && !validFormats.includes(format.toString())) { logger.error(`Invalid format parameter: "${format}"`); - res.status(400).send("Invalid format parameter, must be one of: avif, webp, jpg"); + res.status(400).send("Invalid format parameter, must be one of: avif, jpg"); return; } - const targetFormat = (format as "avif" | "webp" | "jpg") || "jpg"; + const targetFormat = (format as "avif" | "jpg") || "jpg"; const targetWidth = width ? (width === "original" ? undefined : parseInt(width.toString())) : undefined; const originalFilePath = path.join(cdnFiles, filename); diff --git a/src/nginx/nginx.conf b/src/nginx/nginx.conf index 4a17fb8..b6f056c 100644 --- a/src/nginx/nginx.conf +++ b/src/nginx/nginx.conf @@ -6,6 +6,37 @@ events { worker_connections 1024; } http { include mime.types; + + gzip on; + gzip_disable "msie6"; + + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_buffers 16 8k; + gzip_http_version 1.1; + gzip_min_length 256; + gzip_types + application/atom+xml + application/geo+json + application/javascript + application/x-javascript + application/json + application/ld+json + application/manifest+json + application/rdf+xml + application/rss+xml + application/xhtml+xml + application/xml + font/eot + font/otf + font/ttf + image/svg+xml + text/css + text/javascript + text/plain + text/xml; + limit_req_zone $http_x_forwarded_for zone=myhigherlimit:10m rate=10r/s; limit_req_zone $http_x_forwarded_for zone=mylimit:10m rate=3r/s; @@ -17,16 +48,9 @@ http { listen 80; listen [::]:80; - root /var/www/data; - index index.html; - access_log /var/log/nginx/access_custom.log compression; access_log /dev/stdout compression; - location ~ /\.ht { - deny all; - } - location ^~ /cdn/ { proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $http_x_forwarded_for; @@ -43,6 +67,16 @@ http { proxy_pass http://frontend:3000; } + location ~ /(brammen\.jpg|chris\.jpg|jay\.jpg|peter\.jpg|sep\.jpg|ps\.png|reddit\-logo\.svg|threads\-logo\.svg|twitch\-logo\.svg)$ { + limit_req zone=mylimit burst=50 nodelay; + + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $http_x_forwarded_for; + proxy_pass http://frontend:3000; + + expires 365d; + } + location / { limit_req zone=mylimit burst=50 nodelay; @@ -51,4 +85,4 @@ http { proxy_pass http://frontend:3000; } } -} \ No newline at end of file +} diff --git a/src/psaggregator/package-lock.json b/src/psaggregator/package-lock.json index 77dc4da..62dd91c 100644 --- a/src/psaggregator/package-lock.json +++ b/src/psaggregator/package-lock.json @@ -1,12 +1,12 @@ { "name": "psaggregator", - "version": "1.18.0", + "version": "1.19.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "psaggregator", - "version": "1.18.0", + "version": "1.19.0", "dependencies": { "@internationalized/date": "^3.5.5", "@sentry/sveltekit": "^8.41.0", @@ -2843,16 +2843,15 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.8.5.tgz", - "integrity": "sha512-5ry1jPd4r9knsphDK2eTYUFPhFZMqF0PHFfa8MdMQCqWaKwLSXdFMU/Vevih1I7C1/VNB5MvTuFl1kXu5vx8UA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.9.0.tgz", + "integrity": "sha512-W3E7ed3ChB6kPqRs2H7tcHp+Z7oiTFC6m+lLyAQQuyXeqw6LdNuuwEUla+5VM0OGgqQD+cYD6+7Xq80vVm17Vg==", "hasInstallScript": true, - "license": "MIT", "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", "devalue": "^5.1.0", - "esm-env": "^1.0.0", + "esm-env": "^1.2.1", "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", @@ -2869,9 +2868,9 @@ "node": ">=18.13" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.3" + "vite": "^5.0.3 || ^6.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte": { @@ -4339,9 +4338,9 @@ } }, "node_modules/esm-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", - "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz", + "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==" }, "node_modules/espree": { "version": "9.6.1", diff --git a/src/psaggregator/package.json b/src/psaggregator/package.json index a08c459..c65c790 100644 --- a/src/psaggregator/package.json +++ b/src/psaggregator/package.json @@ -1,6 +1,6 @@ { "name": "psaggregator", - "version": "1.18.0", + "version": "1.19.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/src/psaggregator/src/lib/components/BigHeader.svelte b/src/psaggregator/src/lib/components/BigHeader.svelte index 038aa2c..e003845 100644 --- a/src/psaggregator/src/lib/components/BigHeader.svelte +++ b/src/psaggregator/src/lib/components/BigHeader.svelte @@ -8,7 +8,12 @@
- psaggregator logo, pietsmiet logo turned upside down, green game controller + psaggregator logo, pietsmiet logo turned upside down, green game controller
diff --git a/src/psaggregator/src/lib/components/CDNImage.svelte b/src/psaggregator/src/lib/components/CDNImage.svelte index 201cd15..e18f883 100644 --- a/src/psaggregator/src/lib/components/CDNImage.svelte +++ b/src/psaggregator/src/lib/components/CDNImage.svelte @@ -11,16 +11,28 @@ let classes = ""; export { classes as class }; - function getPreferredImageFormat() { + let imageSrc = ""; + + function supportsAVIF() { + return new Promise((resolve) => { + const avifImage = + "data:image/avif;base64,AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A="; + const img = new Image(); + img.onload = () => resolve(true); + img.onerror = () => resolve(false); + img.src = avifImage; + }); + } + + async function getPreferredImageFormat() { if (!browser || !window || !window.matchMedia) { return "jpg"; } - if (window.matchMedia && window.matchMedia("(image-avif)").matches) { + if (await supportsAVIF()) { return "avif"; - } else if (window.matchMedia && window.matchMedia("(image-webp)").matches) { - return "webp"; } + return "jpg"; // fallback } @@ -50,9 +62,13 @@ return "300"; // fallback } - $: imageSrc = `${src}?format=${getPreferredImageFormat()}&width=${getPreferredImageSize()}`; + async function calculateImageSrc(src: string | null | undefined) { + imageSrc = `${src}?format=${await getPreferredImageFormat()}&width=${getPreferredImageSize()}`; + } + + $: browser && calculateImageSrc(src); -{#if browser} - {alt +{#if imageSrc} + {alt {/if} diff --git a/src/psaggregator/src/lib/components/InstagramPost.svelte b/src/psaggregator/src/lib/components/InstagramPost.svelte index e256c13..26ff395 100644 --- a/src/psaggregator/src/lib/components/InstagramPost.svelte +++ b/src/psaggregator/src/lib/components/InstagramPost.svelte @@ -6,6 +6,7 @@ import CdnImage from "./CDNImage.svelte"; export let post: Information & { InformationResource: InformationResource[] }; + export let loading: "lazy" | "eager" = "lazy"; let video: HTMLVideoElement; $: isVideoOnly = @@ -59,7 +60,13 @@ {/each}
{:else if post.imageUri} - + {/if}
diff --git a/src/psaggregator/src/lib/components/InstagramStoriesContainer.svelte b/src/psaggregator/src/lib/components/InstagramStoriesContainer.svelte index 30cc5ff..bb398ed 100644 --- a/src/psaggregator/src/lib/components/InstagramStoriesContainer.svelte +++ b/src/psaggregator/src/lib/components/InstagramStoriesContainer.svelte @@ -184,62 +184,70 @@ {#if active} -
+
-
- {#each stories as story, index} - {@const duration = - story.InformationResource?.length && story.InformationResource[0].videoDuration - ? story.InformationResource[0].videoDuration - : 15} -
index} - class="progress" - on:animationend={playNext} - on:click={() => { - playSpecific(index); - }} - on:keydown={void 0} - role="button" - tabindex="0"> -
- {/each} -
-
-
- {filterKey} - {titleCase(filterKey)} +
+
+ {#each stories as story, index} + {@const duration = + story.InformationResource?.length && story.InformationResource[0].videoDuration + ? story.InformationResource[0].videoDuration + : 15} +
index} + class="progress" + on:animationend={playNext} + on:click={() => { + playSpecific(index); + }} + on:keydown={void 0} + role="button" + tabindex="0"> +
+ {/each}
-
- {#if selectedStory && selectedStory.InformationResource?.length && selectedStory.InformationResource[0].videoUri} - + {/if} + - {/if} - +
+ class="rounded-[20px] object-contain"> {:else} + class="rounded-[20px] object-contain" /> {/if} {/if}
@@ -278,7 +286,7 @@ on:keydown={void 0} role="button" tabindex="0"> - {filterKey} + {filterKey}
{/if} diff --git a/src/psaggregator/src/lib/components/NewsBig.svelte b/src/psaggregator/src/lib/components/NewsBig.svelte index 052e751..b5ab50c 100644 --- a/src/psaggregator/src/lib/components/NewsBig.svelte +++ b/src/psaggregator/src/lib/components/NewsBig.svelte @@ -88,15 +88,15 @@ }); -
+
YouTube
- {#each youtubeCommunityPosts as youtube} - + {#each youtubeCommunityPosts as youtube, index} + {/each}
{#if loading[ImportType.YouTube]} @@ -109,8 +109,8 @@ Instagram
- {#each instagramPosts as instagram} - + {#each instagramPosts as instagram, index} + {/each}
{#if loading[ImportType.Instagram]} @@ -123,8 +123,8 @@ Twitter
- {#each twitterPosts as twitter} - + {#each twitterPosts as twitter, index} + {/each}
{#if loading[ImportType.Twitter]} diff --git a/src/psaggregator/src/lib/components/NewsSmall.svelte b/src/psaggregator/src/lib/components/NewsSmall.svelte index 50e3497..2b7b5d0 100644 --- a/src/psaggregator/src/lib/components/NewsSmall.svelte +++ b/src/psaggregator/src/lib/components/NewsSmall.svelte @@ -1,8 +1,7 @@ @@ -20,7 +21,7 @@
{#if entry.imageUri} - + {/if}
diff --git a/src/psaggregator/src/lib/components/TwitterPost.svelte b/src/psaggregator/src/lib/components/TwitterPost.svelte index c0e0731..d03a080 100644 --- a/src/psaggregator/src/lib/components/TwitterPost.svelte +++ b/src/psaggregator/src/lib/components/TwitterPost.svelte @@ -6,6 +6,7 @@ import CdnImage from "./CDNImage.svelte"; export let post: Information & { InformationResource: InformationResource[] }; + export let loading: "lazy" | "eager" = "lazy"; function titleCase(str: string | null) { if (!str) return ""; @@ -42,7 +43,7 @@ src={resource.imageUri} alt={resource.remoteId} title={resource.id} - loading="lazy" /> + {loading} /> {/if}
{/each} diff --git a/src/psaggregator/src/lib/components/YouTubeCommunityPost.svelte b/src/psaggregator/src/lib/components/YouTubeCommunityPost.svelte index b5a5ba7..f6d63c0 100644 --- a/src/psaggregator/src/lib/components/YouTubeCommunityPost.svelte +++ b/src/psaggregator/src/lib/components/YouTubeCommunityPost.svelte @@ -6,6 +6,7 @@ import CdnImage from "./CDNImage.svelte"; export let post: Information; + export let loading: "lazy" | "eager" = "lazy"; @@ -15,7 +16,13 @@ {/if}
{post.text}
{#if post.imageUri} - + {/if}
diff --git a/src/psaggregator/src/routes/+page.svelte b/src/psaggregator/src/routes/+page.svelte index 0c633a1..54ac575 100644 --- a/src/psaggregator/src/routes/+page.svelte +++ b/src/psaggregator/src/routes/+page.svelte @@ -9,11 +9,105 @@ import MediaQuery from "$lib/utils/MediaQuery.svelte"; import InstagramPost from "$lib/components/InstagramPost.svelte"; import TwitchEntry from "$lib/components/TwitchEntry.svelte"; - import { version } from "$app/environment"; + import { browser, version } from "$app/environment"; import TwitterPost from "$lib/components/TwitterPost.svelte"; - import { GITHUB_URL, MAIL_TO_URL } from "../config/config"; + import { GITHUB_URL, LOW_DATA_MODE, MAIL_TO_URL } from "../config/config"; + import { invalidateAll } from "$app/navigation"; + import { onDestroy, onMount } from "svelte"; + import FaviconNotification from "favicon-notification"; + import type { ContentPiece, Information, ScheduledContentPiece } from "@prisma/client"; + import moment from "moment"; export let data: PageServerData; + + let reloadInterval: number | NodeJS.Timeout | undefined = undefined; + let isNotificationVisible = false; + + function findNewestDate() { + const dates = [ + ...(data.today?.map((entry: ScheduledContentPiece) => moment(entry.startDate)) ?? []), + ...(data.youtubeCommunityPosts?.map((entry: Information) => moment(entry.date)) ?? []), + ...(data.instagramPosts?.map((entry: Information) => moment(entry.date)) ?? []), + ...(data.twitterPosts?.map((entry: Information) => moment(entry.date)) ?? []), + ...(data.redditPosts?.map((entry: RedditPost) => moment(entry.date)) ?? []), + ...(data.videos?.map((entry: ContentPiece) => moment(entry.startDate)) ?? []), + ...(data.upcomingStreams?.map((entry: ScheduledContentPiece) => moment(entry.startDate)) ?? []), + moment((data.twitchStatus as TwitchStatus)?.startedAt ?? 0) + ]; + + return Math.max(...dates); + } + + async function reload() { + if ($LOW_DATA_MODE) return; + + const currentLastDate = findNewestDate(); + const currentLastUploadPlanEntriesWithLink = data.today.filter((entry: ScheduledContentPiece) => entry.href).length; + + await invalidateAll(); + + const newLastDate = findNewestDate(); + const newUploadPlanEntriesWithLink = data.today.filter((entry: ScheduledContentPiece) => entry.href).length; + + if ( + (currentLastDate !== newLastDate && currentLastDate && newLastDate) || + currentLastUploadPlanEntriesWithLink !== newUploadPlanEntriesWithLink + ) { + isNotificationVisible = true; + try { + FaviconNotification.add(); + } catch (e) { + console.error(e); + } + } + } + + function onUserActive() { + if (!isNotificationVisible) return; + + isNotificationVisible = false; + try { + FaviconNotification.remove(); + } catch (e) { + console.error(e); + } + } + + function handleVisibilityChange() { + if (document.visibilityState === "visible") { + onUserActive(); + } + } + + function handleUserInteraction() { + onUserActive(); + } + + onMount(() => { + if (browser) { + reloadInterval = setInterval(reload, 1000 * 60 * 5); + + FaviconNotification.init({ + color: "#ff0000", + lineColor: "#000000", + url: "/favicon.png" + }); + + document.addEventListener("visibilitychange", handleVisibilityChange); + window.addEventListener("mousemove", handleUserInteraction); + window.addEventListener("focus", handleUserInteraction); + } + }); + + onDestroy(() => { + if (browser) { + if (reloadInterval) clearInterval(reloadInterval); + + document.removeEventListener("visibilitychange", handleVisibilityChange); + window.removeEventListener("mousemove", handleUserInteraction); + window.removeEventListener("focus", handleUserInteraction); + } + });
+

Version 1.19.0

+
+

Neue Features

+
+ Automatisches Neuladen + Die Startseite lädt nun alle fünf Minuten automatisch neu, um neue Inhalte anzuzeigen. +
+

Überarbeitungen

+
+ Verbesserte Ladezeiten + Weitere Optimierungen wurden vorgenommen, um die Ladezeiten zu verbessern. +
+
+

Version 1.18.0

Überarbeitungen

@@ -58,15 +72,15 @@ Nach oben scrollen Wenn auf mobilen Endgeräten nun auf den oberen Bildschirmrand getippt wird, wird die Seite nach oben gescrollt.
-
-

Überarbeitungen

-
- Einheitlichere Überschriften - Die Überschriften wurden einheitlicher gestaltet. -
-
- Mehr Footer-Informationen - Auf mobilen Endgeräten wird nun mehr Information im Footer angezeigt. +

Überarbeitungen

+
+ Einheitlichere Überschriften + Die Überschriften wurden einheitlicher gestaltet. +
+
+ Mehr Footer-Informationen + Auf mobilen Endgeräten wird nun mehr Information im Footer angezeigt. +

Version 1.15.0

diff --git a/src/psaggregator/src/routes/news/+page.svelte b/src/psaggregator/src/routes/news/+page.svelte index ba8cfa0..12f2123 100644 --- a/src/psaggregator/src/routes/news/+page.svelte +++ b/src/psaggregator/src/routes/news/+page.svelte @@ -9,8 +9,8 @@ -
-
+
+
diff --git a/src/psaggregator/src/routes/randomvideo/+page.svelte b/src/psaggregator/src/routes/randomvideo/+page.svelte index c61cf4c..098ea3e 100644 --- a/src/psaggregator/src/routes/randomvideo/+page.svelte +++ b/src/psaggregator/src/routes/randomvideo/+page.svelte @@ -11,7 +11,7 @@
- +
- {#each data.videos as video} + {#each data.videos as video, index} {#if video.startDate} {@const newMonth = checkMonth(video.startDate)} {#if newMonth} @@ -228,7 +228,10 @@ {/if} {/if} {#if $VIDEO_COMPLEXE_VIEW} - + {:else} + title={video.title} + loading={index < 10 ? "eager" : "lazy"} /> {/if} {/each}