diff --git a/README.md b/README.md
index eeec7040..d9f56e91 100644
--- a/README.md
+++ b/README.md
@@ -110,7 +110,6 @@ Application behavior can be changed by configuring the following environment var
- `NEXT_PUBLIC_MATTE_PHOTOS = 1` constrains the size of each photo, and enables a surrounding border (potentially useful for photos with tall aspect ratios)
- `NEXT_PUBLIC_BLUR_DISABLED = 1` prevents image blur data being stored and displayed (potentially useful for limiting Postgres usage)
- `NEXT_PUBLIC_GEO_PRIVACY = 1` disables collection/display of location-based data (⚠️ re-compresses uploaded images in order to remove GPS information)
-- `NEXT_PUBLIC_HIDE_TITLE_FALLBACK_TEXT = 1` prevents showing "Untitled" for photos without titles
- `NEXT_PUBLIC_IGNORE_PRIORITY_ORDER = 1` prevents `priority_order` field affecting photo order
- `NEXT_PUBLIC_PUBLIC_API = 1` enables public API available at `/api`
- `NEXT_PUBLIC_HIDE_REPO_LINK = 1` removes footer link to repo
diff --git a/src/camera/CameraHeader.tsx b/src/camera/CameraHeader.tsx
index 82675100..150d3a64 100644
--- a/src/camera/CameraHeader.tsx
+++ b/src/camera/CameraHeader.tsx
@@ -1,6 +1,6 @@
import { Photo, PhotoDateRange } from '@/photo';
import { pathForCameraShare } from '@/site/paths';
-import PhotoSetHeader from '@/photo/PhotoSetHeader';
+import PhotoHeader from '@/photo/PhotoHeader';
import { Camera, cameraFromPhoto } from '.';
import PhotoCamera from './PhotoCamera';
import { descriptionForCameraPhotos } from './meta';
@@ -22,9 +22,9 @@ export default function CameraHeader({
}) {
const camera = cameraFromPhoto(photos[0], cameraProp);
return (
- }
- entityVerb="Photo"
entityDescription={
descriptionForCameraPhotos(photos, undefined, count, dateRange)}
photos={photos}
diff --git a/src/focal/FocalLengthHeader.tsx b/src/focal/FocalLengthHeader.tsx
index b40f459a..e30d68f3 100644
--- a/src/focal/FocalLengthHeader.tsx
+++ b/src/focal/FocalLengthHeader.tsx
@@ -1,7 +1,7 @@
import { Photo, PhotoDateRange } from '@/photo';
import { descriptionForFocalLengthPhotos } from '.';
import { pathForFocalLengthShare } from '@/site/paths';
-import PhotoSetHeader from '@/photo/PhotoSetHeader';
+import PhotoHeader from '@/photo/PhotoHeader';
import PhotoFocalLength from './PhotoFocalLength';
export default function FocalLengthHeader({
@@ -20,7 +20,8 @@ export default function FocalLengthHeader({
dateRange?: PhotoDateRange
}) {
return (
- }
entityDescription={descriptionForFocalLengthPhotos(
photos,
diff --git a/src/photo/InfinitePhotoScroll.tsx b/src/photo/InfinitePhotoScroll.tsx
index dfb85f37..b0739287 100644
--- a/src/photo/InfinitePhotoScroll.tsx
+++ b/src/photo/InfinitePhotoScroll.tsx
@@ -10,11 +10,9 @@ import {
import SiteGrid from '@/components/SiteGrid';
import Spinner from '@/components/Spinner';
import { getPhotosCachedAction, getPhotosAction } from '@/photo/actions';
-import { Photo } from '.';
+import { Photo, PhotoSetAttributes } from '.';
import { clsx } from 'clsx/lite';
import { useAppState } from '@/state/AppState';
-import { Camera } from '@/camera';
-import { FilmSimulation } from '@/simulation';
import { GetPhotosOptions } from './db';
export type RevalidatePhoto = (
@@ -38,9 +36,6 @@ export default function InfinitePhotoScroll({
initialOffset: number
itemsPerPage: number
sortBy?: GetPhotosOptions['sortBy']
- tag?: string
- camera?: Camera
- simulation?: FilmSimulation
cacheKey: string
wrapMoreButtonInGrid?: boolean
useCachedPhotos?: boolean
@@ -50,7 +45,7 @@ export default function InfinitePhotoScroll({
onLastPhotoVisible: () => void
revalidatePhoto?: RevalidatePhoto
}) => ReactNode
-}) {
+} & PhotoSetAttributes) {
const { swrTimestamp, isUserSignedIn } = useAppState();
const key = `${swrTimestamp}-${cacheKey}`;
diff --git a/src/photo/PhotoDetailPage.tsx b/src/photo/PhotoDetailPage.tsx
index ed70dec8..ecfd14d7 100644
--- a/src/photo/PhotoDetailPage.tsx
+++ b/src/photo/PhotoDetailPage.tsx
@@ -1,18 +1,15 @@
import AnimateItems from '@/components/AnimateItems';
-import { Photo, PhotoDateRange } from '.';
+import { Photo, PhotoDateRange, PhotoSetAttributes } from '.';
import PhotoLarge from './PhotoLarge';
import SiteGrid from '@/components/SiteGrid';
import PhotoGrid from './PhotoGrid';
-import { clsx } from 'clsx/lite';
-import PhotoLinks from './PhotoLinks';
import TagHeader from '@/tag/TagHeader';
-import { Camera } from '@/camera';
import CameraHeader from '@/camera/CameraHeader';
-import { FilmSimulation } from '@/simulation';
import FilmSimulationHeader from '@/simulation/FilmSimulationHeader';
import { TAG_HIDDEN } from '@/tag';
import HiddenHeader from '@/tag/HiddenHeader';
import FocalLengthHeader from '@/focal/FocalLengthHeader';
+import PhotoHeader from './PhotoHeader';
export default function PhotoDetailPage({
photo,
@@ -31,77 +28,69 @@ export default function PhotoDetailPage({
photo: Photo
photos: Photo[]
photosGrid?: Photo[]
- tag?: string
- camera?: Camera
- simulation?: FilmSimulation
- focal?: number
indexNumber?: number
count?: number
dateRange?: PhotoDateRange
shouldShare?: boolean
includeFavoriteInAdminMenu?: boolean
-}) {
+} & PhotoSetAttributes) {
+ let customHeader: JSX.Element | undefined;
+
+ if (tag) {
+ customHeader = tag === TAG_HIDDEN
+ ?
+ : ;
+ } else if (camera) {
+ customHeader = ;
+ } else if (simulation) {
+ customHeader = ;
+ } else if (focal) {
+ customHeader = ;
+ }
+
return (
- {tag &&
-
- :
}
- />}
- {camera &&
-
}
- />}
- {simulation &&
-
}
- />}
- {focal &&
-
}
+
}
+ />
}
- contentSide={
-
- ,
- ]}
- />}
/>
);
diff --git a/src/photo/PhotoGrid.tsx b/src/photo/PhotoGrid.tsx
index d0d998c0..d5678a49 100644
--- a/src/photo/PhotoGrid.tsx
+++ b/src/photo/PhotoGrid.tsx
@@ -1,11 +1,9 @@
'use client';
-import { Photo } from '.';
+import { Photo, PhotoSetAttributes } from '.';
import PhotoMedium from './PhotoMedium';
import { clsx } from 'clsx/lite';
import AnimateItems from '@/components/AnimateItems';
-import { Camera } from '@/camera';
-import { FilmSimulation } from '@/simulation';
import { GRID_ASPECT_RATIO, HIGH_DENSITY_GRID } from '@/site/config';
import { useAppState } from '@/state/AppState';
import SelectTileOverlay from '@/components/SelectTileOverlay';
@@ -31,10 +29,6 @@ export default function PhotoGrid({
}: {
photos: Photo[]
selectedPhoto?: Photo
- tag?: string
- camera?: Camera
- simulation?: FilmSimulation
- focal?: number
photoPriority?: boolean
fast?: boolean
animate?: boolean
@@ -46,7 +40,7 @@ export default function PhotoGrid({
canSelect?: boolean
onLastPhotoVisible?: () => void
onAnimationComplete?: () => void
-}) {
+} & PhotoSetAttributes) {
const {
isUserSignedIn,
selectedPhotoIds,
diff --git a/src/photo/PhotoGridContainer.tsx b/src/photo/PhotoGridContainer.tsx
index dd29fafd..95ee522f 100644
--- a/src/photo/PhotoGridContainer.tsx
+++ b/src/photo/PhotoGridContainer.tsx
@@ -38,7 +38,7 @@ export default function PhotoGridContainer({
return (
{header &&
photo.id === selectedPhoto.id)
+ : undefined;
+
+ const paginationLabel =
+ (indexNumber || (selectedPhotoIndex ?? 0 + 1)) + ' of ' +
+ (count ?? photos.length);
+
+ const renderPrevNext = () =>
+ ;
+
+ const renderDateRange = () =>
+
+ {start === end
+ ? start
+ : <>{end}
– {start}>}
+ ;
+
+ return (
+
+
+ {entity ?? (selectedPhoto
+ ?
+ {selectedPhoto.title || formatDate(selectedPhoto.takenAt, 'tiny')}
+
+ : undefined)}
+
+
+ {entity && <>
+ {selectedPhotoIndex !== undefined
+ ?
+ {entityVerb || 'PHOTO'} {paginationLabel}
+
+ : entityDescription}
+ {selectedPhotoIndex === undefined && sharePath &&
+ }
+ >}
+
+
+ {selectedPhoto
+ ? renderPrevNext()
+ : renderDateRange()}
+
+ ,
+ ]}
+ />
+ );
+}
diff --git a/src/photo/PhotoLarge.tsx b/src/photo/PhotoLarge.tsx
index cda6ac5b..01fb21c3 100644
--- a/src/photo/PhotoLarge.tsx
+++ b/src/photo/PhotoLarge.tsx
@@ -25,10 +25,7 @@ import PhotoFilmSimulation from '@/simulation/PhotoFilmSimulation';
import { sortTags } from '@/tag';
import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid';
import PhotoLink from './PhotoLink';
-import {
- SHOULD_PREFETCH_ALL_LINKS,
- SHOW_PHOTO_TITLE_FALLBACK_TEXT,
-} from '@/site/config';
+import { SHOULD_PREFETCH_ALL_LINKS } from '@/site/config';
import AdminPhotoMenuClient from '@/admin/AdminPhotoMenuClient';
import { RevalidatePhoto } from './InfinitePhotoScroll';
import { useRef } from 'react';
@@ -38,11 +35,13 @@ import { useAppState } from '@/state/AppState';
export default function PhotoLarge({
photo,
+ className,
primaryTag,
priority,
prefetch = SHOULD_PREFETCH_ALL_LINKS,
prefetchRelatedLinks = SHOULD_PREFETCH_ALL_LINKS,
revalidatePhoto,
+ showTitle = true,
showCamera = true,
showSimulation = true,
shouldShare = true,
@@ -55,11 +54,13 @@ export default function PhotoLarge({
onVisible,
}: {
photo: Photo
+ className?: string
primaryTag?: string
priority?: boolean
prefetch?: boolean
prefetchRelatedLinks?: boolean
revalidatePhoto?: RevalidatePhoto
+ showTitle?: boolean
showCamera?: boolean
showSimulation?: boolean
shouldShare?: boolean
@@ -85,10 +86,13 @@ export default function PhotoLarge({
const { arePhotosMatted, isUserSignedIn } = useAppState();
+ const hasTitle =
+ showTitle &&
+ Boolean(photo.title);
+
const hasTitleContent =
- photo.title ||
- SHOW_PHOTO_TITLE_FALLBACK_TEXT ||
- photo.caption;
+ hasTitle ||
+ Boolean(photo.caption);
const hasMetaContent =
showCameraContent ||
@@ -102,6 +106,7 @@ export default function PhotoLarge({
return (
- {(photo.title || SHOW_PHOTO_TITLE_FALLBACK_TEXT) &&
+ {hasTitle &&
{photo.caption &&
-
+
{photo.caption}
}
{(showCameraContent || showTagsContent) &&
@@ -224,7 +233,7 @@ export default function PhotoLarge({
photo={photo}
className={clsx(
'text-medium',
- // Prevent date collision with admin button
+ // Prevent collision with admin button
!hasNonDateContent && isUserSignedIn && 'md:pr-7',
)}
/>
diff --git a/src/photo/PhotoLink.tsx b/src/photo/PhotoLink.tsx
index 34bcca38..088ffc1d 100644
--- a/src/photo/PhotoLink.tsx
+++ b/src/photo/PhotoLink.tsx
@@ -1,13 +1,11 @@
'use client';
import { ReactNode } from 'react';
-import { Photo, titleForPhoto } from '@/photo';
+import { Photo, PhotoSetAttributes, titleForPhoto } from '@/photo';
import Link from 'next/link';
import { AnimationConfig } from '../components/AnimateItems';
import { useAppState } from '@/state/AppState';
import { pathForPhoto } from '@/site/paths';
-import { Camera } from '@/camera';
-import { FilmSimulation } from '@/simulation';
import { clsx } from 'clsx/lite';
export default function PhotoLink({
@@ -23,16 +21,12 @@ export default function PhotoLink({
children,
}: {
photo?: Photo
- tag?: string
- camera?: Camera
- simulation?: FilmSimulation
- focal?: number
scroll?: boolean
prefetch?: boolean
nextPhotoAnimation?: AnimationConfig
className?: string
children?: ReactNode
-}) {
+} & PhotoSetAttributes) {
const { setNextPhotoAnimation } = useAppState();
return (
diff --git a/src/photo/PhotoMedium.tsx b/src/photo/PhotoMedium.tsx
index 72fdd0a4..df1bab48 100644
--- a/src/photo/PhotoMedium.tsx
+++ b/src/photo/PhotoMedium.tsx
@@ -1,12 +1,15 @@
'use client';
-import { Photo, altTextForPhoto, doesPhotoNeedBlurCompatibility } from '.';
+import {
+ Photo,
+ PhotoSetAttributes,
+ altTextForPhoto,
+ doesPhotoNeedBlurCompatibility,
+} from '.';
import ImageMedium from '@/components/image/ImageMedium';
import Link from 'next/link';
import { clsx } from 'clsx/lite';
import { pathForPhoto } from '@/site/paths';
-import { Camera } from '@/camera';
-import { FilmSimulation } from '@/simulation';
import { SHOULD_PREFETCH_ALL_LINKS } from '@/site/config';
import { useRef } from 'react';
import useOnVisible from '@/utility/useOnVisible';
@@ -24,16 +27,12 @@ export default function PhotoMedium({
onVisible,
}: {
photo: Photo
- tag?: string
- camera?: Camera
- simulation?: FilmSimulation
- focal?: number
selected?: boolean
priority?: boolean
prefetch?: boolean
className?: string
onVisible?: () => void
-}) {
+} & PhotoSetAttributes) {
const ref = useRef
(null);
useOnVisible(ref, onVisible);
diff --git a/src/photo/PhotoLinks.tsx b/src/photo/PhotoPrevNext.tsx
similarity index 53%
rename from src/photo/PhotoLinks.tsx
rename to src/photo/PhotoPrevNext.tsx
index b2d86036..52e8fa3d 100644
--- a/src/photo/PhotoLinks.tsx
+++ b/src/photo/PhotoPrevNext.tsx
@@ -1,35 +1,38 @@
'use client';
import { useEffect } from 'react';
-import { Photo, getNextPhoto, getPreviousPhoto } from '@/photo';
+import {
+ Photo,
+ PhotoSetAttributes,
+ getNextPhoto,
+ getPreviousPhoto,
+} from '@/photo';
import PhotoLink from './PhotoLink';
import { useRouter } from 'next/navigation';
import { pathForPhoto } from '@/site/paths';
import { useAppState } from '@/state/AppState';
import { AnimationConfig } from '@/components/AnimateItems';
-import { Camera } from '@/camera';
-import { FilmSimulation } from '@/simulation';
+import { clsx } from 'clsx/lite';
+import { FiChevronLeft, FiChevronRight } from 'react-icons/fi';
const LISTENER_KEYUP = 'keyup';
const ANIMATION_LEFT: AnimationConfig = { type: 'left', duration: 0.3 };
const ANIMATION_RIGHT: AnimationConfig = { type: 'right', duration: 0.3 };
-export default function PhotoLinks({
+export default function PhotoPrevNext({
photo,
- photos,
+ photos = [],
+ className,
tag,
camera,
simulation,
focal,
}: {
- photo: Photo
- photos: Photo[]
- tag?: string
- camera?: Camera
- simulation?: FilmSimulation
- focal?: number
-}) {
+ photo?: Photo
+ photos?: Photo[]
+ className?: string
+} & PhotoSetAttributes) {
const router = useRouter();
const {
@@ -37,8 +40,8 @@ export default function PhotoLinks({
shouldRespondToKeyboardCommands,
} = useAppState();
- const previousPhoto = getPreviousPhoto(photo, photos);
- const nextPhoto = getNextPhoto(photo, photos);
+ const previousPhoto = photo ? getPreviousPhoto(photo, photos) : undefined;
+ const nextPhoto = photo ? getNextPhoto(photo, photos) : undefined;
useEffect(() => {
if (shouldRespondToKeyboardCommands) {
@@ -94,31 +97,47 @@ export default function PhotoLinks({
]);
return (
- <>
-
- PREV
-
-
- NEXT
-
- >
+
+
+
+
+ PREV
+
+
+ /
+
+
+
+ NEXT
+
+
+
);
};
diff --git a/src/photo/PhotoSetHeader.tsx b/src/photo/PhotoSetHeader.tsx
deleted file mode 100644
index 87b4da2f..00000000
--- a/src/photo/PhotoSetHeader.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { clsx } from 'clsx/lite';
-import { Photo, PhotoDateRange, dateRangeForPhotos } from '.';
-import ShareButton from '@/components/ShareButton';
-import AnimateItems from '@/components/AnimateItems';
-import { ReactNode } from 'react';
-import { HIGH_DENSITY_GRID } from '@/site/config';
-import DivDebugBaselineGrid from '@/components/DivDebugBaselineGrid';
-
-export default function PhotoSetHeader({
- entity,
- entityVerb,
- entityDescription,
- photos,
- selectedPhoto,
- sharePath,
- indexNumber,
- count,
- dateRange,
-}: {
- entity: ReactNode
- entityVerb?: string
- entityDescription: string
- photos: Photo[]
- selectedPhoto?: Photo
- sharePath?: string
- indexNumber?: number
- count?: number
- dateRange?: PhotoDateRange
-}) {
- const { start, end } = dateRangeForPhotos(photos, dateRange);
-
- const selectedPhotoIndex = selectedPhoto
- ? photos.findIndex(photo => photo.id === selectedPhoto.id)
- : undefined;
-
- return (
-
-
- {entity}
-
-
- {selectedPhotoIndex !== undefined
- // eslint-disable-next-line max-len
- ? `${entityVerb ? `${entityVerb} ` : ''}${indexNumber || (selectedPhotoIndex + 1)} of ${count ?? photos.length}`
- : entityDescription}
- {selectedPhotoIndex === undefined && sharePath &&
- }
-
-
- {start === end
- ? start
- : <>{end}
– {start}>}
-
- ]}
- />
- );
-}
diff --git a/src/photo/PhotoShareModal.tsx b/src/photo/PhotoShareModal.tsx
index b9a6f14c..8f577454 100644
--- a/src/photo/PhotoShareModal.tsx
+++ b/src/photo/PhotoShareModal.tsx
@@ -1,17 +1,11 @@
import PhotoOGTile from '@/photo/PhotoOGTile';
import { absolutePathForPhoto, pathForPhoto } from '@/site/paths';
-import { Photo } from '.';
+import { Photo, PhotoSetAttributes } from '.';
import ShareModal from '@/components/ShareModal';
-import { Camera } from '@/camera';
-import { FilmSimulation } from '@/simulation';
export default function PhotoShareModal(props: {
photo: Photo
- tag?: string
- camera?: Camera
- simulation?: FilmSimulation
- focal?: number
-}) {
+} & PhotoSetAttributes) {
return (
void
-}) {
+} & PhotoSetAttributes) {
const ref = useRef(null);
useOnVisible(ref, onVisible);
diff --git a/src/photo/db/index.ts b/src/photo/db/index.ts
index 2bc13658..cf2b1ec7 100644
--- a/src/photo/db/index.ts
+++ b/src/photo/db/index.ts
@@ -1,8 +1,6 @@
-import { Camera } from '@/camera';
-import { Lens } from '@/lens';
-import { FilmSimulation } from '@/simulation';
import { PRIORITY_ORDER_ENABLED } from '@/site/config';
import { parameterize } from '@/utility/string';
+import { PhotoSetAttributes } from '..';
export const GENERATE_STATIC_PARAMS_LIMIT = 1000;
export const PHOTO_DEFAULT_LIMIT = 100;
@@ -12,16 +10,11 @@ export type GetPhotosOptions = {
limit?: number
offset?: number
query?: string
- tag?: string
- camera?: Camera
- lens?: Lens
- simulation?: FilmSimulation
- focal?: number
takenBefore?: Date
takenAfterInclusive?: Date
updatedBefore?: Date
hidden?: 'exclude' | 'include' | 'only'
-};
+} & PhotoSetAttributes;
export const areOptionsSensitive = (options: GetPhotosOptions) =>
options.hidden === 'include' || options.hidden === 'only';
diff --git a/src/photo/index.ts b/src/photo/index.ts
index 646bd13e..bf3feab3 100644
--- a/src/photo/index.ts
+++ b/src/photo/index.ts
@@ -1,4 +1,6 @@
+import { Camera } from '@/camera';
import { formatFocalLength } from '@/focal';
+import { Lens } from '@/lens';
import { getNextImageUrlForRequest } from '@/services/next-image';
import { FilmSimulation } from '@/simulation';
import { HIGH_DENSITY_GRID, SHOW_EXIF_DATA } from '@/site/config';
@@ -99,6 +101,14 @@ export interface Photo extends PhotoDb {
takenAtNaiveFormatted: string
}
+export interface PhotoSetAttributes {
+ tag?: string
+ camera?: Camera
+ simulation?: FilmSimulation
+ focal?: number
+ lens?: Lens // Unimplemented as a set
+}
+
export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
const photoDb = camelcaseKeys(
photoDbRaw as unknown as Record
diff --git a/src/simulation/FilmSimulationHeader.tsx b/src/simulation/FilmSimulationHeader.tsx
index b0908572..df07a4f3 100644
--- a/src/simulation/FilmSimulationHeader.tsx
+++ b/src/simulation/FilmSimulationHeader.tsx
@@ -1,7 +1,7 @@
import { Photo, PhotoDateRange } from '@/photo';
import { FilmSimulation, descriptionForFilmSimulationPhotos } from '.';
import { pathForFilmSimulationShare } from '@/site/paths';
-import PhotoSetHeader from '@/photo/PhotoSetHeader';
+import PhotoHeader from '@/photo/PhotoHeader';
import PhotoFilmSimulation from
'@/simulation/PhotoFilmSimulation';
@@ -21,9 +21,9 @@ export default function FilmSimulationHeader({
dateRange?: PhotoDateRange
}) {
return (
- }
- entityVerb="Photo"
entityDescription={descriptionForFilmSimulationPhotos(
photos, undefined, count, dateRange)}
photos={photos}
diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx
index e1a82753..f1d4772a 100644
--- a/src/site/SiteChecklistClient.tsx
+++ b/src/site/SiteChecklistClient.tsx
@@ -59,7 +59,6 @@ export default function SiteChecklistClient({
arePhotosMatted,
isBlurEnabled,
isGeoPrivacyEnabled,
- showPhotoTitleFallbackText,
isPriorityOrderEnabled,
isAiTextGenerationEnabled,
aiTextAutoGeneratedFields,
@@ -507,15 +506,6 @@ export default function SiteChecklistClient({
collection/display of location-based data:
{renderEnvVars(['NEXT_PUBLIC_GEO_PRIVACY'])}
-
- Set environment variable to {'"1"'} to prevent
- showing {'"Untitled"'} for photos without titles:
- {renderEnvVars(['NEXT_PUBLIC_HIDE_TITLE_FALLBACK_TEXT'])}
-
export const getPathComponents = (pathname = ''): {
photoId?: string
- tag?: string
- camera?: Camera
- simulation?: FilmSimulation
- focal?: number
-} => {
+} & PhotoSetAttributes => {
const photoIdFromPhoto = pathname.match(
new RegExp(`^${PREFIX_PHOTO}/([^/]+)`))?.[1];
const photoIdFromTag = pathname.match(
diff --git a/src/tag/HiddenHeader.tsx b/src/tag/HiddenHeader.tsx
index feac25fb..244013be 100644
--- a/src/tag/HiddenHeader.tsx
+++ b/src/tag/HiddenHeader.tsx
@@ -1,5 +1,5 @@
import { Photo, photoQuantityText } from '@/photo';
-import PhotoSetHeader from '@/photo/PhotoSetHeader';
+import PhotoHeader from '@/photo/PhotoHeader';
import HiddenTag from './HiddenTag';
export default function HiddenHeader({
@@ -14,7 +14,7 @@ export default function HiddenHeader({
count: number
}) {
return (
- }
entityDescription={photoQuantityText(count, false)}
diff --git a/src/tag/TagHeader.tsx b/src/tag/TagHeader.tsx
index 4a9e3165..6eff23bc 100644
--- a/src/tag/TagHeader.tsx
+++ b/src/tag/TagHeader.tsx
@@ -2,7 +2,7 @@ import { Photo, PhotoDateRange } from '@/photo';
import PhotoTag from './PhotoTag';
import { descriptionForTaggedPhotos, isTagFavs } from '.';
import { pathForTagShare } from '@/site/paths';
-import PhotoSetHeader from '@/photo/PhotoSetHeader';
+import PhotoHeader from '@/photo/PhotoHeader';
import FavsTag from './FavsTag';
export default function TagHeader({
@@ -21,7 +21,8 @@ export default function TagHeader({
dateRange?: PhotoDateRange
}) {
return (
-
: }
diff --git a/src/utility/date.ts b/src/utility/date.ts
index f758dd8f..fdbe6454 100644
--- a/src/utility/date.ts
+++ b/src/utility/date.ts
@@ -1,5 +1,6 @@
import { format, parseISO, parse } from 'date-fns';
+const DATE_STRING_FORMAT_TINY = 'dd MMM yy';
const DATE_STRING_FORMAT_SHORT = 'dd MMM yyyy';
const DATE_STRING_FORMAT_MEDIUM = 'dd MMM yy h:mma';
const DATE_STRING_FORMAT = 'dd MMM yyyy h:mma';
@@ -7,10 +8,12 @@ const DATE_STRING_FORMAT_POSTGRES = 'yyyy-MM-dd HH:mm:ss';
type AmbiguousTimestamp = number | string;
-type Length = 'short' | 'medium' | 'long';
+type Length = 'tiny' | 'short' | 'medium' | 'long';
export const formatDate = (date: Date, length: Length = 'long') => {
switch (length) {
+ case 'tiny':
+ return format(date, DATE_STRING_FORMAT_TINY);
case 'short':
return format(date, DATE_STRING_FORMAT_SHORT);
case 'medium':