mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:29:32 -05:00 
			
		
		
		
	feat(web): meta tags for share links (#1290)
* feat(web): meta tags for share links * refactor: svelte head tags * chore: clean up * chore: linting
This commit is contained in:
		
							parent
							
								
									a3688fe642
								
							
						
					
					
						commit
						fa31a6e441
					
				@ -1,10 +1,18 @@
 | 
			
		||||
import { api, AddAssetsResponseDto, AssetResponseDto } from '@api';
 | 
			
		||||
import { api, AddAssetsResponseDto, AssetResponseDto, ThumbnailFormat } from '@api';
 | 
			
		||||
import {
 | 
			
		||||
	notificationController,
 | 
			
		||||
	NotificationType
 | 
			
		||||
} from '$lib/components/shared-components/notification/notification';
 | 
			
		||||
import { downloadAssets } from '$lib/stores/download';
 | 
			
		||||
 | 
			
		||||
export const getThumbnailUrl = (assetId: string, format: ThumbnailFormat, key?: string) => {
 | 
			
		||||
	let url = `/api/asset/thumbnail/${assetId}?format=${format}`;
 | 
			
		||||
	if (key) {
 | 
			
		||||
		url += `&key=${key}`;
 | 
			
		||||
	}
 | 
			
		||||
	return url;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const addAssetsToAlbum = async (
 | 
			
		||||
	albumId: string,
 | 
			
		||||
	assetIds: Array<string>,
 | 
			
		||||
 | 
			
		||||
@ -77,6 +77,25 @@
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>{$page.data.meta?.title} - Immich</title>
 | 
			
		||||
	{#if $page.data.meta}
 | 
			
		||||
		<meta name="description" content={$page.data.meta.description} />
 | 
			
		||||
 | 
			
		||||
		<!-- Facebook Meta Tags -->
 | 
			
		||||
		<meta property="og:type" content="website" />
 | 
			
		||||
		<meta property="og:title" content={$page.data.meta.title} />
 | 
			
		||||
		<meta property="og:description" content={$page.data.meta.description} />
 | 
			
		||||
		<meta property="og:image" content={$page.data.meta.imageUrl} />
 | 
			
		||||
 | 
			
		||||
		<!-- Twitter Meta Tags -->
 | 
			
		||||
		<meta name="twitter:card" content="summary_large_image" />
 | 
			
		||||
		<meta name="twitter:title" content={$page.data.meta.title} />
 | 
			
		||||
		<meta name="twitter:description" content={$page.data.meta.description} />
 | 
			
		||||
		<meta name="twitter:image" content={$page.data.meta.imageUrl} />
 | 
			
		||||
	{/if}
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<main on:dragenter={() => (showUploadCover = true)}>
 | 
			
		||||
	{#if canShow}
 | 
			
		||||
		<div in:fade={{ duration: 100 }}>
 | 
			
		||||
 | 
			
		||||
@ -2,11 +2,6 @@
 | 
			
		||||
	import { goto } from '$app/navigation';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Welcome 🎉 - Immich</title>
 | 
			
		||||
	<meta name="description" content="Immich Web Interface" />
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section class="h-screen w-screen flex place-items-center place-content-center">
 | 
			
		||||
	<div class="flex flex-col place-items-center gap-8 text-center max-w-[350px]">
 | 
			
		||||
		<div class="flex place-items-center place-content-center ">
 | 
			
		||||
 | 
			
		||||
@ -7,4 +7,11 @@ export const load: PageLoad = async ({ parent }) => {
 | 
			
		||||
	if (user) {
 | 
			
		||||
		throw redirect(302, '/photos');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		meta: {
 | 
			
		||||
			title: 'Welcome 🎉',
 | 
			
		||||
			description: 'Immich Web Interface'
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -26,10 +26,6 @@
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Administration - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<NavigationBar user={$page.data.user} />
 | 
			
		||||
 | 
			
		||||
<main>
 | 
			
		||||
 | 
			
		||||
@ -9,4 +9,10 @@ export const load: PageServerLoad = async ({ parent }) => {
 | 
			
		||||
	} else if (!user.isAdmin) {
 | 
			
		||||
		throw redirect(302, '/photos');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		meta: {
 | 
			
		||||
			title: 'Job Status'
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,6 @@
 | 
			
		||||
	import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Jobs Status - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section>
 | 
			
		||||
	<JobsPanel />
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
@ -13,5 +13,10 @@ export const load: PageServerLoad = async ({ parent }) => {
 | 
			
		||||
 | 
			
		||||
	const { data: allUsers } = await serverApi.userApi.getAllUsers(false);
 | 
			
		||||
 | 
			
		||||
	return { allUsers };
 | 
			
		||||
	return {
 | 
			
		||||
		allUsers,
 | 
			
		||||
		meta: {
 | 
			
		||||
			title: 'Server Status'
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -3,10 +3,6 @@
 | 
			
		||||
	import { page } from '$app/stores';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Server Status - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
{#if $page.data.allUsers}
 | 
			
		||||
	<ServerStatsPanel allUsers={$page.data.allUsers} />
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
@ -10,5 +10,10 @@ export const load: PageServerLoad = async ({ parent }) => {
 | 
			
		||||
		throw redirect(302, '/photos');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return { user };
 | 
			
		||||
	return {
 | 
			
		||||
		user,
 | 
			
		||||
		meta: {
 | 
			
		||||
			title: 'System Settings'
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -17,10 +17,6 @@
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>System Settings - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section class="">
 | 
			
		||||
	{#await getConfig()}
 | 
			
		||||
		<LoadingSpinner />
 | 
			
		||||
 | 
			
		||||
@ -13,5 +13,11 @@ export const load: PageServerLoad = async ({ parent }) => {
 | 
			
		||||
 | 
			
		||||
	const { data: allUsers } = await serverApi.userApi.getAllUsers(false);
 | 
			
		||||
 | 
			
		||||
	return { user, allUsers };
 | 
			
		||||
	return {
 | 
			
		||||
		user,
 | 
			
		||||
		allUsers,
 | 
			
		||||
		meta: {
 | 
			
		||||
			title: 'User Management'
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -101,10 +101,6 @@
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>User Management - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section>
 | 
			
		||||
	{#if shouldShowCreateUserForm}
 | 
			
		||||
		<FullScreenModal on:clickOutside={() => (shouldShowCreateUserForm = false)}>
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,10 @@ export const load: PageServerLoad = async ({ parent }) => {
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			user: user,
 | 
			
		||||
			albums: albums
 | 
			
		||||
			albums: albums,
 | 
			
		||||
			meta: {
 | 
			
		||||
				title: 'Albums'
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		throw redirect(302, '/auth/login');
 | 
			
		||||
 | 
			
		||||
@ -34,10 +34,6 @@
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Albums - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section>
 | 
			
		||||
	<NavigationBar user={data.user} shouldShowUploadButton={false} />
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,10 @@ export const load: PageServerLoad = async ({ parent, params }) => {
 | 
			
		||||
	try {
 | 
			
		||||
		const { data: album } = await serverApi.albumApi.getAlbumInfo(albumId);
 | 
			
		||||
		return {
 | 
			
		||||
			album
 | 
			
		||||
			album,
 | 
			
		||||
			meta: {
 | 
			
		||||
				title: album.albumName
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		throw redirect(302, '/albums');
 | 
			
		||||
 | 
			
		||||
@ -5,10 +5,6 @@
 | 
			
		||||
	export let data: PageData;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>{data.album.albumName} - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<div class="immich-scrollbar">
 | 
			
		||||
	<AlbumViewer album={data.album} />
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
@ -14,10 +14,6 @@
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Change Password - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section class="h-screen w-screen flex place-items-center place-content-center">
 | 
			
		||||
	<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
 | 
			
		||||
		<ChangePasswordForm user={data.user} on:success={onSuccessHandler} />
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,10 @@ export const load: PageLoad = async () => {
 | 
			
		||||
 | 
			
		||||
		if (userInfo.shouldChangePassword) {
 | 
			
		||||
			return {
 | 
			
		||||
				user: userInfo
 | 
			
		||||
				user: userInfo,
 | 
			
		||||
				meta: {
 | 
			
		||||
					title: 'Change Password'
 | 
			
		||||
				}
 | 
			
		||||
			};
 | 
			
		||||
		} else {
 | 
			
		||||
			throw redirect(302, '/photos');
 | 
			
		||||
 | 
			
		||||
@ -9,5 +9,9 @@ export const load: PageServerLoad = async () => {
 | 
			
		||||
		throw redirect(302, '/auth/register');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return;
 | 
			
		||||
	return {
 | 
			
		||||
		meta: {
 | 
			
		||||
			title: 'Login'
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -5,10 +5,6 @@
 | 
			
		||||
	import LoginForm from '$lib/components/forms/login-form.svelte';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Login - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section class="h-screen w-screen flex place-items-center place-content-center">
 | 
			
		||||
	<div in:fade={{ duration: 100 }} out:fade={{ duration: 100 }}>
 | 
			
		||||
		<LoginForm
 | 
			
		||||
 | 
			
		||||
@ -9,5 +9,9 @@ export const load: PageServerLoad = async () => {
 | 
			
		||||
		throw redirect(302, '/auth/login');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return;
 | 
			
		||||
	return {
 | 
			
		||||
		meta: {
 | 
			
		||||
			title: 'Admin Registration'
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,6 @@
 | 
			
		||||
	import AdminRegistrationForm from '$lib/components/forms/admin-registration-form.svelte';
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Admin Registration - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section class="h-screen w-screen flex place-items-center place-content-center">
 | 
			
		||||
	<AdminRegistrationForm />
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,10 @@ export const load: PageServerLoad = async ({ parent }) => {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			user
 | 
			
		||||
			user,
 | 
			
		||||
			meta: {
 | 
			
		||||
				title: 'Photos'
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		console.log('Photo page load error', e);
 | 
			
		||||
 | 
			
		||||
@ -116,10 +116,6 @@
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Photos - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section>
 | 
			
		||||
	{#if $isMultiSelectStoreState}
 | 
			
		||||
		<ControlAppBar
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,8 @@
 | 
			
		||||
export const prerender = false;
 | 
			
		||||
import { error } from '@sveltejs/kit';
 | 
			
		||||
 | 
			
		||||
import { serverApi } from '@api';
 | 
			
		||||
import { getThumbnailUrl } from '$lib/utils/asset-utils';
 | 
			
		||||
import { serverApi, ThumbnailFormat } from '@api';
 | 
			
		||||
import type { PageServerLoad } from './$types';
 | 
			
		||||
 | 
			
		||||
export const load: PageServerLoad = async ({ params }) => {
 | 
			
		||||
@ -9,7 +10,20 @@ export const load: PageServerLoad = async ({ params }) => {
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		const { data: sharedLink } = await serverApi.shareApi.getMySharedLink({ params: { key } });
 | 
			
		||||
		return { sharedLink };
 | 
			
		||||
 | 
			
		||||
		const assetCount = sharedLink.assets.length;
 | 
			
		||||
		const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			sharedLink,
 | 
			
		||||
			meta: {
 | 
			
		||||
				title: sharedLink.album ? sharedLink.album.albumName : 'Public Share',
 | 
			
		||||
				description: sharedLink.description || `${assetCount} shared photos & videos.`,
 | 
			
		||||
				imageUrl: assetId
 | 
			
		||||
					? getThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
 | 
			
		||||
					: 'feature-panel.png'
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		throw error(404, {
 | 
			
		||||
			message: 'Invalid shared link'
 | 
			
		||||
 | 
			
		||||
@ -1,22 +1,20 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
	import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
 | 
			
		||||
	import { AlbumResponseDto } from '../../../api';
 | 
			
		||||
	import { AlbumResponseDto } from '@api';
 | 
			
		||||
	import type { PageData } from './$types';
 | 
			
		||||
 | 
			
		||||
	export let data: PageData;
 | 
			
		||||
 | 
			
		||||
	const { sharedLink } = data;
 | 
			
		||||
 | 
			
		||||
	let album: AlbumResponseDto | null = null;
 | 
			
		||||
	if (data.sharedLink.album) {
 | 
			
		||||
		album = { ...data.sharedLink.album, assets: data.sharedLink.assets };
 | 
			
		||||
	if (sharedLink.album) {
 | 
			
		||||
		album = { ...sharedLink.album, assets: sharedLink.assets };
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>{data.sharedLink.album?.albumName || 'Public Shared'} - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
{#if album}
 | 
			
		||||
	<div class="immich-scrollbar">
 | 
			
		||||
		<AlbumViewer {album} sharedLink={data.sharedLink} />
 | 
			
		||||
		<AlbumViewer {album} {sharedLink} />
 | 
			
		||||
	</div>
 | 
			
		||||
{/if}
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,10 @@ export const load: PageServerLoad = async ({ parent }) => {
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			user: user,
 | 
			
		||||
			sharedAlbums: sharedAlbums
 | 
			
		||||
			sharedAlbums,
 | 
			
		||||
			meta: {
 | 
			
		||||
				title: 'Albums'
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		throw redirect(302, '/auth/login');
 | 
			
		||||
 | 
			
		||||
@ -33,10 +33,6 @@
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Albums - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section>
 | 
			
		||||
	<NavigationBar user={data.user} shouldShowUploadButton={false} />
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,10 @@ export const load: PageServerLoad = async ({ parent }) => {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			user
 | 
			
		||||
			user,
 | 
			
		||||
			meta: {
 | 
			
		||||
				title: 'Shared Links'
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		throw redirect(302, '/auth/login');
 | 
			
		||||
 | 
			
		||||
@ -67,10 +67,6 @@
 | 
			
		||||
	};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Shared links - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<ControlAppBar backIcon={ArrowLeft} on:close-button-click={() => goto('/sharing')}>
 | 
			
		||||
	<svelte:fragment slot="leading">Shared links</svelte:fragment>
 | 
			
		||||
</ControlAppBar>
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,10 @@ export const load: PageServerLoad = async ({ parent }) => {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return {
 | 
			
		||||
			user: user
 | 
			
		||||
			user,
 | 
			
		||||
			meta: {
 | 
			
		||||
				title: 'Settings'
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
	} catch (e) {
 | 
			
		||||
		throw redirect(302, '/auth/login');
 | 
			
		||||
 | 
			
		||||
@ -7,10 +7,6 @@
 | 
			
		||||
	export let data: PageData;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
	<title>Settings - Immich</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<section>
 | 
			
		||||
	<NavigationBar user={data.user} shouldShowUploadButton={false} />
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								web/static/feature-panel.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/static/feature-panel.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 48 KiB  | 
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user