web: partially pre-render web for page metadata
This commit is contained in:
commit
cef90219e0
12 changed files with 142 additions and 68 deletions
|
@ -4,7 +4,7 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="viewport-fit=cover, width=device-width, height=device-height, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<title>cobalt</title>
|
||||
%sveltekit.head%
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||
|
@ -18,18 +18,45 @@
|
|||
|
||||
<link crossorigin="use-credentials" rel="manifest" href="%sveltekit.assets%/manifest.json">
|
||||
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<style>
|
||||
body {
|
||||
background-color: black;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
<noscript>
|
||||
<style>
|
||||
#cobalt { opacity: 1 !important }
|
||||
</style>
|
||||
</noscript>
|
||||
|
||||
<style>
|
||||
body, #body-dark {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#body-light {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
background-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
#cobalt {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#cobalt.loaded {
|
||||
opacity: 1;
|
||||
transition: opacity .2s ease-out;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let settings = localStorage.getItem('settings'), theme;
|
||||
if (settings && ({ theme } = JSON.parse(settings)?.appearance)) {
|
||||
document.body.id = `body-${theme}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover" data-sveltekit-preload-code="eager">
|
||||
<div style="display: contents">
|
||||
%sveltekit.body%
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { goto } from "$app/navigation";
|
||||
import { browser } from "$app/environment";
|
||||
import { SvelteComponent, tick } from "svelte";
|
||||
|
||||
import env from "$lib/env";
|
||||
|
@ -43,7 +44,7 @@
|
|||
};
|
||||
|
||||
$: linkFromHash = $page.url.hash.replace("#", "") || "";
|
||||
$: linkFromQuery = $page.url.searchParams.get("u") || "";
|
||||
$: linkFromQuery = (browser ? $page.url.searchParams.get("u") : 0) || "";
|
||||
|
||||
$: if (linkFromHash || linkFromQuery) {
|
||||
if (validLink(linkFromHash)) {
|
||||
|
|
|
@ -1,45 +1,66 @@
|
|||
const ua = navigator.userAgent.toLowerCase();
|
||||
|
||||
const iPhone = ua.includes("iphone os");
|
||||
const iPad = !iPhone && ua.includes("mac os") && navigator.maxTouchPoints > 0;
|
||||
|
||||
const iOS = iPhone || iPad;
|
||||
const android = ua.includes("android") || ua.includes("diordna");
|
||||
|
||||
const mobile = iOS || android;
|
||||
|
||||
const language = navigator.language.toLowerCase().slice(0, 2);
|
||||
|
||||
const installed = window.matchMedia('(display-mode: standalone)').matches;
|
||||
|
||||
const reducedMotion = window.matchMedia(`(prefers-reduced-motion: reduce)`).matches;
|
||||
const reducedTransparency = window.matchMedia(`(prefers-reduced-transparency: reduce)`).matches;
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
const app = {
|
||||
is: {
|
||||
installed
|
||||
installed: false,
|
||||
}
|
||||
}
|
||||
|
||||
const device = {
|
||||
is: {
|
||||
iPhone: false,
|
||||
iPad: false,
|
||||
iOS: false,
|
||||
android: false,
|
||||
mobile: false,
|
||||
},
|
||||
prefers: {
|
||||
language: "en",
|
||||
reducedMotion: false,
|
||||
reducedTransparency: false,
|
||||
},
|
||||
supports: {
|
||||
share: false,
|
||||
directDownload: false,
|
||||
},
|
||||
userAgent: "sveltekit server",
|
||||
}
|
||||
|
||||
if (browser) {
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
|
||||
const iPhone = ua.includes("iphone os");
|
||||
const iPad = !iPhone && ua.includes("mac os") && navigator.maxTouchPoints > 0;
|
||||
|
||||
const iOS = iPhone || iPad;
|
||||
const android = ua.includes("android") || ua.includes("diordna");
|
||||
|
||||
const installed = window.matchMedia('(display-mode: standalone)').matches;
|
||||
|
||||
app.is = {
|
||||
installed,
|
||||
};
|
||||
|
||||
device.is = {
|
||||
iPhone,
|
||||
iPad,
|
||||
iOS,
|
||||
android,
|
||||
mobile,
|
||||
},
|
||||
prefers: {
|
||||
language,
|
||||
reducedMotion,
|
||||
reducedTransparency,
|
||||
},
|
||||
supports: {
|
||||
mobile: iOS || android,
|
||||
};
|
||||
|
||||
device.prefers = {
|
||||
language: navigator.language.toLowerCase().slice(0, 2) || "en",
|
||||
reducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
|
||||
reducedTransparency: window.matchMedia('(prefers-reduced-transparency: reduce)').matches,
|
||||
};
|
||||
|
||||
device.supports = {
|
||||
share: navigator.share !== undefined,
|
||||
directDownload: !(installed && iOS),
|
||||
},
|
||||
userAgent: navigator.userAgent,
|
||||
};
|
||||
|
||||
device.userAgent = navigator.userAgent;
|
||||
}
|
||||
|
||||
|
||||
export { device, app };
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
import { env } from "$env/dynamic/public";
|
||||
import * as _env from "$env/static/public";
|
||||
|
||||
const getEnv = (_key: string) => {
|
||||
const env = _env as Record<string, string | undefined>;
|
||||
const key = `PUBLIC_${_key}`;
|
||||
|
||||
if (key in env) {
|
||||
return env[key];
|
||||
}
|
||||
}
|
||||
|
||||
const variables = {
|
||||
HOST: env.PUBLIC_HOST,
|
||||
PLAUSIBLE_HOST: env.PUBLIC_PLAUSIBLE_HOST,
|
||||
PLAUSIBLE_ENABLED: env.PUBLIC_HOST && env.PUBLIC_PLAUSIBLE_HOST,
|
||||
DEFAULT_API: env.PUBLIC_DEFAULT_API,
|
||||
TURNSTILE_KEY: env.PUBLIC_TURNSTILE_KEY,
|
||||
HOST: getEnv('HOST'),
|
||||
PLAUSIBLE_HOST: getEnv('PLAUSIBLE_HOST'),
|
||||
PLAUSIBLE_ENABLED: getEnv('HOST') && getEnv('PLAUSIBLE_HOST'),
|
||||
DEFAULT_API: getEnv('DEFAULT_API'),
|
||||
TURNSTILE_KEY: getEnv('TURNSTILE_KEY'),
|
||||
}
|
||||
|
||||
const contacts = {
|
||||
|
|
|
@ -2,6 +2,7 @@ import mime from "mime";
|
|||
import LibAV, { type LibAV as LibAVInstance } from "@imput/libav.js-remux-cli";
|
||||
import type { FFmpegProgressCallback, FFmpegProgressEvent, FFmpegProgressStatus, FileInfo, RenderParams } from "./types/libav";
|
||||
import type { FfprobeData } from "fluent-ffmpeg";
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
export default class LibAVWrapper {
|
||||
libav: Promise<LibAVInstance> | null;
|
||||
|
@ -10,12 +11,12 @@ export default class LibAVWrapper {
|
|||
|
||||
constructor(onProgress?: FFmpegProgressCallback) {
|
||||
this.libav = null;
|
||||
this.concurrency = Math.min(4, navigator.hardwareConcurrency);
|
||||
this.concurrency = Math.min(4, browser ? navigator.hardwareConcurrency : 0);
|
||||
this.onProgress = onProgress;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.libav) {
|
||||
if (this.concurrency && !this.libav) {
|
||||
this.libav = LibAV.LibAV({
|
||||
yesthreads: true,
|
||||
base: '/_libav'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { derived, readable, type Updater } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
import { merge } from 'ts-deepmerge';
|
||||
|
||||
import type {
|
||||
|
@ -43,6 +44,9 @@ const migrate = (settings: AllPartialSettingsWithSchema): PartialSettings => {
|
|||
|
||||
|
||||
const loadFromStorage = () => {
|
||||
if (!browser)
|
||||
return {};
|
||||
|
||||
const settings = localStorage.getItem('settings');
|
||||
if (!settings) {
|
||||
const migrated = migrateOldSettings();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { readable, derived, type Readable } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
import settings from '$lib/state/settings';
|
||||
import { themeOptions } from '$lib/types/settings';
|
||||
|
@ -7,19 +8,25 @@ type Theme = typeof themeOptions[number];
|
|||
|
||||
let set: (_: Theme) => void;
|
||||
|
||||
const browserPreference = () =>
|
||||
window.matchMedia('(prefers-color-scheme: light)')
|
||||
.matches ? 'light' : 'dark';
|
||||
const browserPreference = () => {
|
||||
if (!browser || window.matchMedia('(prefers-color-scheme: light)').matches) {
|
||||
return 'light';
|
||||
}
|
||||
|
||||
return 'dark'
|
||||
}
|
||||
|
||||
const browserPreferenceReadable = readable(
|
||||
browserPreference(),
|
||||
_set => { set = _set }
|
||||
)
|
||||
|
||||
const matchMedia = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
if (browser) {
|
||||
const matchMedia = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
if (matchMedia.addEventListener) {
|
||||
matchMedia.addEventListener('change', () => set(browserPreference()));
|
||||
if (matchMedia.addEventListener) {
|
||||
matchMedia.addEventListener('change', () => set(browserPreference()));
|
||||
}
|
||||
}
|
||||
|
||||
export default derived(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { afterNavigate } from "$app/navigation";
|
||||
import { updated } from "$app/stores";
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
import env from "$lib/env";
|
||||
import settings from "$lib/state/settings";
|
||||
|
@ -46,9 +47,10 @@
|
|||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<div style="display: contents" data-theme={$currentTheme} lang={$locale}>
|
||||
<div style="display: contents" data-theme={browser ? $currentTheme : undefined} lang={$locale}>
|
||||
<div
|
||||
id="cobalt"
|
||||
class:loaded={browser}
|
||||
data-iphone={device.is.iPhone}
|
||||
data-reduce-motion={reduceMotion}
|
||||
data-reduce-transparency={reduceTransparency}
|
||||
|
@ -222,10 +224,6 @@
|
|||
overscroll-behavior-y: none;
|
||||
}
|
||||
|
||||
:global(body) {
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
|
||||
#cobalt {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export const prerender = true;
|
||||
export const ssr = false;
|
||||
export const ssr = true;
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
import settings from "$lib/state/settings";
|
||||
import { version } from "$lib/version";
|
||||
|
@ -38,7 +39,7 @@
|
|||
$: isMobile = screenWidth <= 750;
|
||||
$: isHome = $page.url.pathname === "/settings";
|
||||
$: {
|
||||
if (!isMobile && isHome) {
|
||||
if (browser && !isMobile && isHome) {
|
||||
goto(defaultSettingsPage(), { replaceState: true });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
import { device, app } from "$lib/device";
|
||||
import { version } from "$lib/version";
|
||||
import settings, { storedSettings } from "$lib/state/settings";
|
||||
|
||||
import { goto } from "$app/navigation";
|
||||
import { defaultSettingsPage } from "$lib/settings/defaults";
|
||||
|
||||
$: {
|
||||
onMount(() => {
|
||||
if (!$settings.advanced.debug) {
|
||||
goto(defaultSettingsPage(), { replaceState: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if $settings.advanced.debug}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { t } from "$lib/i18n/translations";
|
||||
import { page } from "$app/stores";
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
import { t } from "$lib/i18n/translations";
|
||||
import { getAllChangelogs } from "$lib/changelogs";
|
||||
import type { ChangelogImport } from "$lib/types/changelogs";
|
||||
import type { Optional } from "$lib/types/generic";
|
||||
import type { ChangelogImport } from "$lib/types/changelogs";
|
||||
|
||||
import ChangelogEntry from "$components/changelog/ChangelogEntry.svelte";
|
||||
|
||||
|
@ -36,7 +37,10 @@
|
|||
page: changelogs[version]() as Promise<ChangelogImport>,
|
||||
};
|
||||
|
||||
window.location.hash = version;
|
||||
if (browser) {
|
||||
window.location.hash = version;
|
||||
}
|
||||
|
||||
await changelog.page;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue