Leverage adjacent meta for about description
This commit is contained in:
parent
be859f380e
commit
8569347c27
@ -1,12 +1,11 @@
|
|||||||
import AdminAboutEditPage from '@/about/AdminAboutEditPage';
|
import AdminAboutEditPage from '@/about/AdminAboutEditPage';
|
||||||
import { getAbout } from '@/about/query';
|
import { getAboutData } from '@/about/data';
|
||||||
import { PRESERVE_ORIGINAL_UPLOADS } from '@/app/config';
|
import { PRESERVE_ORIGINAL_UPLOADS } from '@/app/config';
|
||||||
import { feedQueryOptions } from '@/feed';
|
import { feedQueryOptions } from '@/feed';
|
||||||
import {
|
import {
|
||||||
getPhotosCached,
|
getPhotosCached,
|
||||||
getPhotosMetaCached,
|
getPhotosMetaCached,
|
||||||
} from '@/photo/cache';
|
} from '@/photo/cache';
|
||||||
import { getPhoto } from '@/photo/query';
|
|
||||||
import { TAG_FAVS } from '@/tag';
|
import { TAG_FAVS } from '@/tag';
|
||||||
|
|
||||||
const PHOTO_CHOOSER_QUERY_OPTIONS = feedQueryOptions({
|
const PHOTO_CHOOSER_QUERY_OPTIONS = feedQueryOptions({
|
||||||
@ -25,24 +24,7 @@ export default async function AboutEditPage() {
|
|||||||
photosCount,
|
photosCount,
|
||||||
photosFavs,
|
photosFavs,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getAbout()
|
getAboutData()
|
||||||
.then(async about => {
|
|
||||||
const photoAvatar = about?.photoIdAvatar
|
|
||||||
? await getPhoto(about?.photoIdAvatar ?? '', true)
|
|
||||||
.catch(() => undefined)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const photoHero = about?.photoIdHero
|
|
||||||
? await getPhoto(about?.photoIdHero ?? '', true)
|
|
||||||
.catch(() => undefined)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
about,
|
|
||||||
photoAvatar,
|
|
||||||
photoHero,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch(() => ({
|
.catch(() => ({
|
||||||
about: undefined,
|
about: undefined,
|
||||||
photoAvatar: undefined,
|
photoAvatar: undefined,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
|
import { getDescriptionWithFallback } from '@/about';
|
||||||
import AboutPageClient from '@/about/AboutPageClient';
|
import AboutPageClient from '@/about/AboutPageClient';
|
||||||
import { getAboutCached } from '@/about/cache';
|
import { getAboutDataCached } from '@/about/data';
|
||||||
import { SHOW_ABOUT_PAGE } from '@/app/config';
|
import { SHOW_ABOUT_PAGE } from '@/app/config';
|
||||||
import { PATH_ROOT } from '@/app/path';
|
import { PATH_ROOT } from '@/app/path';
|
||||||
import { getDataForCategoriesCached } from '@/category/cache';
|
import { getDataForCategoriesCached } from '@/category/cache';
|
||||||
@ -7,11 +8,7 @@ import {
|
|||||||
getLastModifiedForCategories,
|
getLastModifiedForCategories,
|
||||||
NULL_CATEGORY_DATA,
|
NULL_CATEGORY_DATA,
|
||||||
} from '@/category/data';
|
} from '@/category/data';
|
||||||
import {
|
import { getPhotosMetaCached } from '@/photo/cache';
|
||||||
getPhotoCached,
|
|
||||||
getPhotosCached,
|
|
||||||
getPhotosMetaCached,
|
|
||||||
} from '@/photo/cache';
|
|
||||||
import PhotosEmptyState from '@/photo/PhotosEmptyState';
|
import PhotosEmptyState from '@/photo/PhotosEmptyState';
|
||||||
import { getAllPhotoIdsWithUpdatedAt } from '@/photo/query';
|
import { getAllPhotoIdsWithUpdatedAt } from '@/photo/query';
|
||||||
import { TAG_FAVS } from '@/tag';
|
import { TAG_FAVS } from '@/tag';
|
||||||
@ -34,26 +31,8 @@ export default async function AboutPage() {
|
|||||||
photos,
|
photos,
|
||||||
categories,
|
categories,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getAboutCached()
|
getAboutDataCached()
|
||||||
.then(async about => {
|
.catch(() => ({
|
||||||
const photoAvatar = await (about?.photoIdAvatar
|
|
||||||
? getPhotoCached(about?.photoIdAvatar ?? '', true)
|
|
||||||
: undefined);
|
|
||||||
const photoHero = await (about?.photoIdHero
|
|
||||||
? getPhotoCached(about?.photoIdHero ?? '', true)
|
|
||||||
// Fall back to favorite photos if no hero photo is set
|
|
||||||
: getPhotosCached({ tag: TAG_FAVS, limit: 1 })
|
|
||||||
.then(photos => photos.length > 0
|
|
||||||
? photos[0]
|
|
||||||
// Fall back to oldest photo if no favorite photos exist
|
|
||||||
: getPhotosCached({ limit: 1, sortBy: 'takenAtAsc' })
|
|
||||||
.then(photos => photos[0])));
|
|
||||||
return {
|
|
||||||
about,
|
|
||||||
photoAvatar,
|
|
||||||
photoHero,
|
|
||||||
};
|
|
||||||
}).catch(() => ({
|
|
||||||
about: undefined,
|
about: undefined,
|
||||||
photoAvatar: undefined,
|
photoAvatar: undefined,
|
||||||
photoHero: undefined,
|
photoHero: undefined,
|
||||||
@ -63,10 +42,15 @@ export default async function AboutPage() {
|
|||||||
getDataForCategoriesCached().catch(() => (NULL_CATEGORY_DATA)),
|
getDataForCategoriesCached().catch(() => (NULL_CATEGORY_DATA)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const description = about?.description
|
const description = getDescriptionWithFallback(about);
|
||||||
? <div dangerouslySetInnerHTML={{
|
|
||||||
__html: safelyParseFormattedHtml(about.description),
|
const descriptionHtml = description
|
||||||
}} />
|
? <div
|
||||||
|
className="text-medium [&>*>a]:underline"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: safelyParseFormattedHtml(description),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -88,7 +72,7 @@ export default async function AboutPage() {
|
|||||||
? <AboutPageClient
|
? <AboutPageClient
|
||||||
title={about?.title}
|
title={about?.title}
|
||||||
subhead={about?.subhead}
|
subhead={about?.subhead}
|
||||||
description={description}
|
descriptionHtml={descriptionHtml}
|
||||||
photosCount={photosMeta?.count}
|
photosCount={photosMeta?.count}
|
||||||
photosOldest={photosMeta?.dateRange?.start}
|
photosOldest={photosMeta?.dateRange?.start}
|
||||||
photoAvatar={photoAvatar}
|
photoAvatar={photoAvatar}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import AdminEmptyState from '@/admin/AdminEmptyState';
|
|||||||
export default function AboutPageClient({
|
export default function AboutPageClient({
|
||||||
title,
|
title,
|
||||||
subhead,
|
subhead,
|
||||||
description,
|
descriptionHtml,
|
||||||
photosCount = 0,
|
photosCount = 0,
|
||||||
photosOldest,
|
photosOldest,
|
||||||
photoAvatar,
|
photoAvatar,
|
||||||
@ -43,7 +43,7 @@ export default function AboutPageClient({
|
|||||||
}: {
|
}: {
|
||||||
title?: string
|
title?: string
|
||||||
subhead?: string
|
subhead?: string
|
||||||
description?: ReactNode
|
descriptionHtml?: ReactNode
|
||||||
photosCount?: number
|
photosCount?: number
|
||||||
photosOldest?: string
|
photosOldest?: string
|
||||||
photoAvatar?: Photo
|
photoAvatar?: Photo
|
||||||
@ -178,26 +178,24 @@ export default function AboutPageClient({
|
|||||||
</div>
|
</div>
|
||||||
{isUserSignedIn && <AdminAboutMenu />}
|
{isUserSignedIn && <AdminAboutMenu />}
|
||||||
</div>
|
</div>
|
||||||
{description
|
{descriptionHtml
|
||||||
? <div className="text-medium [&>*>a]:underline">
|
? descriptionHtml
|
||||||
{description}
|
|
||||||
</div>
|
|
||||||
: isUserSignedIn &&
|
: isUserSignedIn &&
|
||||||
<Link
|
<Link
|
||||||
href={PATH_ADMIN_ABOUT_EDIT}
|
href={PATH_ADMIN_ABOUT_EDIT}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex items-center justify-center gap-2.5',
|
'flex items-center justify-center gap-2.5',
|
||||||
'border border-dashed border-medium rounded-lg',
|
'border border-dashed border-medium rounded-lg',
|
||||||
)}
|
)}
|
||||||
>
|
|
||||||
<AdminEmptyState
|
|
||||||
icon={<LuCirclePlus size={22} />}
|
|
||||||
includeContainer={false}
|
|
||||||
className="gap-3! p-6!"
|
|
||||||
>
|
>
|
||||||
Add optional description
|
<AdminEmptyState
|
||||||
</AdminEmptyState>
|
icon={<LuCirclePlus size={22} />}
|
||||||
</Link>}
|
includeContainer={false}
|
||||||
|
className="gap-3! p-6!"
|
||||||
|
>
|
||||||
|
Add optional description
|
||||||
|
</AdminEmptyState>
|
||||||
|
</Link>}
|
||||||
<AnimateItems
|
<AnimateItems
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'grid gap-x-2 gap-y-6 grid-cols-2 lg:grid-cols-4',
|
'grid gap-x-2 gap-y-6 grid-cols-2 lg:grid-cols-4',
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
import { PATH_ABOUT } from '@/app/path';
|
import { PATH_ABOUT } from '@/app/path';
|
||||||
import LinkWithStatus from '@/components/LinkWithStatus';
|
import LinkWithStatus from '@/components/LinkWithStatus';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { About, AboutInsert } from '.';
|
import { About, AboutInsert, getDescriptionWithFallback } from '.';
|
||||||
import FieldsetWithStatus from '@/components/FieldsetWithStatus';
|
import FieldsetWithStatus from '@/components/FieldsetWithStatus';
|
||||||
import AdminChildPage from '@/components/AdminChildPage';
|
import AdminChildPage from '@/components/AdminChildPage';
|
||||||
import { updateAboutAction } from './actions';
|
import { updateAboutAction } from './actions';
|
||||||
@ -32,8 +32,6 @@ export default function AdminAboutEditPage({
|
|||||||
|
|
||||||
const [aboutForm, setAboutForm] = useState<Partial<AboutInsert>>(about ?? {});
|
const [aboutForm, setAboutForm] = useState<Partial<AboutInsert>>(about ?? {});
|
||||||
|
|
||||||
const convertUrlToPhotoId = (url?: string) => url?.split('/').pop();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AdminChildPage
|
<AdminChildPage
|
||||||
backPath={PATH_ABOUT}
|
backPath={PATH_ABOUT}
|
||||||
@ -48,7 +46,7 @@ export default function AdminAboutEditPage({
|
|||||||
<FieldsetPhotoChooser
|
<FieldsetPhotoChooser
|
||||||
id="photoIdAvatar"
|
id="photoIdAvatar"
|
||||||
label="Avatar"
|
label="Avatar"
|
||||||
value={aboutForm?.photoIdAvatar ?? ''}
|
value={aboutForm?.photoIdAvatar || photoAvatar?.id || ''}
|
||||||
onChange={photoIdAvatar => setAboutForm(form =>
|
onChange={photoIdAvatar => setAboutForm(form =>
|
||||||
({ ...form, photoIdAvatar }))}
|
({ ...form, photoIdAvatar }))}
|
||||||
photo={photoAvatar}
|
photo={photoAvatar}
|
||||||
@ -74,15 +72,16 @@ export default function AdminAboutEditPage({
|
|||||||
label="Description"
|
label="Description"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
value={aboutForm?.description ?? ''}
|
value={aboutForm?.description ?? ''}
|
||||||
|
placeholder={getDescriptionWithFallback(about)}
|
||||||
onChange={description => setAboutForm(form =>
|
onChange={description => setAboutForm(form =>
|
||||||
({ ...form, description }))}
|
({ ...form, description }))}
|
||||||
/>
|
/>
|
||||||
<FieldsetPhotoChooser
|
<FieldsetPhotoChooser
|
||||||
id="photoIdHero"
|
id="photoIdHero"
|
||||||
label="Hero"
|
label="Hero"
|
||||||
value={aboutForm?.photoIdHero ?? ''}
|
value={aboutForm?.photoIdHero || photoHero?.id || ''}
|
||||||
onChange={photoIdHero => setAboutForm(form =>
|
onChange={photoIdHero => setAboutForm(form =>
|
||||||
({ ...form, photoIdHero: convertUrlToPhotoId(photoIdHero) }))}
|
({ ...form, photoIdHero }))}
|
||||||
photo={photoHero}
|
photo={photoHero}
|
||||||
photos={photos}
|
photos={photos}
|
||||||
photosCount={photosCount}
|
photosCount={photosCount}
|
||||||
|
|||||||
37
src/about/data.ts
Normal file
37
src/about/data.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { getPhotoCached, getPhotosCached } from '@/photo/cache';
|
||||||
|
import { About } from '.';
|
||||||
|
import { TAG_FAVS } from '@/tag';
|
||||||
|
import { getAbout } from './query';
|
||||||
|
import { getAboutCached } from './cache';
|
||||||
|
|
||||||
|
const getAboutAvatar = (about?: About) =>
|
||||||
|
about?.photoIdAvatar
|
||||||
|
? getPhotoCached(about?.photoIdAvatar ?? '', true)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const getAboutHero = (about?: About) =>
|
||||||
|
about?.photoIdHero
|
||||||
|
? getPhotoCached(about?.photoIdHero ?? '', true)
|
||||||
|
// Fall back to favorite photos if no hero photo is set
|
||||||
|
: getPhotosCached({ tag: TAG_FAVS, limit: 1 })
|
||||||
|
.then(photos => photos.length > 0
|
||||||
|
? photos[0]
|
||||||
|
// Fall back to oldest photo if no favorite photos exist
|
||||||
|
: getPhotosCached({ limit: 1, sortBy: 'takenAtAsc' })
|
||||||
|
.then(photos => photos[0]));
|
||||||
|
|
||||||
|
export const getAboutData = () =>
|
||||||
|
getAbout()
|
||||||
|
.then(async about => ({
|
||||||
|
about,
|
||||||
|
photoAvatar: await getAboutAvatar(about),
|
||||||
|
photoHero: await getAboutHero(about),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const getAboutDataCached = () =>
|
||||||
|
getAboutCached()
|
||||||
|
.then(async about => ({
|
||||||
|
about,
|
||||||
|
photoAvatar: await getAboutAvatar(about),
|
||||||
|
photoHero: await getAboutHero(about),
|
||||||
|
}));
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { META_DESCRIPTION, SIDEBAR_TEXT } from '@/app/config';
|
||||||
|
|
||||||
export interface AboutInsert {
|
export interface AboutInsert {
|
||||||
id: number
|
id: number
|
||||||
title?: string
|
title?: string
|
||||||
@ -11,3 +13,8 @@ export interface About extends AboutInsert {
|
|||||||
createdAt: Date
|
createdAt: Date
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getDescriptionWithFallback = (about?: About) =>
|
||||||
|
about?.description ||
|
||||||
|
META_DESCRIPTION ||
|
||||||
|
SIDEBAR_TEXT;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user