Make photo tag text encoding more resilient
This commit is contained in:
parent
c0b041bf4f
commit
ab8d088df5
@ -15,7 +15,11 @@ interface Props {
|
|||||||
params: { tag: string }
|
params: { tag: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function PhotoPageEdit({ params: { tag } }: Props) {
|
export default async function PhotoPageEdit({
|
||||||
|
params: { tag: tagFromParams } }: Props
|
||||||
|
) {
|
||||||
|
const tag = decodeURIComponent(tagFromParams);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
count,
|
count,
|
||||||
photos,
|
photos,
|
||||||
|
|||||||
@ -6,15 +6,17 @@ import {
|
|||||||
getPhotosTagDataCached,
|
getPhotosTagDataCached,
|
||||||
getPhotosTagDataCachedWithPagination,
|
getPhotosTagDataCachedWithPagination,
|
||||||
} from '@/tag/data';
|
} from '@/tag/data';
|
||||||
import { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
interface TagProps {
|
interface TagProps {
|
||||||
params: { tag: string }
|
params: { tag: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params: { tag },
|
params: { tag: tagFromParams },
|
||||||
}: TagProps): Promise<Metadata> {
|
}: TagProps): Promise<Metadata> {
|
||||||
|
const tag = decodeURIComponent(tagFromParams);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
photos,
|
photos,
|
||||||
count,
|
count,
|
||||||
@ -49,9 +51,11 @@ export async function generateMetadata({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function TagPage({
|
export default async function TagPage({
|
||||||
params: { tag },
|
params: { tag: tagFromParams },
|
||||||
searchParams,
|
searchParams,
|
||||||
}:TagProps & PaginationParams) {
|
}:TagProps & PaginationParams) {
|
||||||
|
const tag = decodeURIComponent(tagFromParams);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
photos,
|
photos,
|
||||||
count,
|
count,
|
||||||
|
|||||||
@ -7,15 +7,17 @@ import {
|
|||||||
getPhotosTagDataCached,
|
getPhotosTagDataCached,
|
||||||
getPhotosTagDataCachedWithPagination,
|
getPhotosTagDataCachedWithPagination,
|
||||||
} from '@/tag/data';
|
} from '@/tag/data';
|
||||||
import { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
interface TagProps {
|
interface TagProps {
|
||||||
params: { tag: string }
|
params: { tag: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params: { tag },
|
params: { tag: tagFromParams },
|
||||||
}: TagProps): Promise<Metadata> {
|
}: TagProps): Promise<Metadata> {
|
||||||
|
const tag = decodeURIComponent(tagFromParams);
|
||||||
|
|
||||||
const [
|
const [
|
||||||
photos,
|
photos,
|
||||||
count,
|
count,
|
||||||
@ -50,9 +52,11 @@ export async function generateMetadata({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function Share({
|
export default async function Share({
|
||||||
params: { tag },
|
params: { tag: tagFromParams },
|
||||||
searchParams,
|
searchParams,
|
||||||
}: TagProps & PaginationParams) {
|
}: TagProps & PaginationParams) {
|
||||||
|
const tag = decodeURIComponent(tagFromParams);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
photos,
|
photos,
|
||||||
count,
|
count,
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export type CameraWithCount = {
|
|||||||
export type Cameras = CameraWithCount[];
|
export type Cameras = CameraWithCount[];
|
||||||
|
|
||||||
export const createCameraKey = ({ make, model }: Camera) =>
|
export const createCameraKey = ({ make, model }: Camera) =>
|
||||||
parameterize(`${make}-${model}`);
|
parameterize(`${make}-${model}`, true);
|
||||||
|
|
||||||
// Assumes no makes ('Fujifilm,' 'Apple,' 'Canon', etc.) have dashes
|
// Assumes no makes ('Fujifilm,' 'Apple,' 'Canon', etc.) have dashes
|
||||||
export const getCameraFromKey = (cameraKey: string): Camera => {
|
export const getCameraFromKey = (cameraKey: string): Camera => {
|
||||||
|
|||||||
@ -72,11 +72,11 @@ export default function TagInput({
|
|||||||
onChange?.([
|
onChange?.([
|
||||||
...selectedOptions,
|
...selectedOptions,
|
||||||
option.startsWith(CREATE_LABEL)
|
option.startsWith(CREATE_LABEL)
|
||||||
? option.slice(CREATE_LABEL.length, -1)
|
? option.match(new RegExp(`^${CREATE_LABEL} "(.+)"$`))?.[1] ?? option
|
||||||
: option,
|
: option,
|
||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map(parameterize)
|
.map(item => parameterize(item))
|
||||||
.join(','));
|
.join(','));
|
||||||
}
|
}
|
||||||
setSelectedOptionIndex(undefined);
|
setSelectedOptionIndex(undefined);
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
revalidateAdminPaths,
|
revalidateAdminPaths,
|
||||||
revalidateAllKeysAndPaths,
|
revalidateAllKeysAndPaths,
|
||||||
revalidatePhotosKey,
|
revalidatePhotosKey,
|
||||||
|
revalidateTagsKey,
|
||||||
} from '@/photo/cache';
|
} from '@/photo/cache';
|
||||||
import { PATH_ADMIN_PHOTOS, PATH_ADMIN_TAGS, PATH_ROOT } from '@/site/paths';
|
import { PATH_ADMIN_PHOTOS, PATH_ADMIN_TAGS, PATH_ROOT } from '@/site/paths';
|
||||||
import { extractExifDataFromBlobPath } from './server';
|
import { extractExifDataFromBlobPath } from './server';
|
||||||
@ -105,6 +106,7 @@ export async function renamePhotoTagGloballyAction(formData: FormData) {
|
|||||||
if (tag && updatedTag && tag !== updatedTag) {
|
if (tag && updatedTag && tag !== updatedTag) {
|
||||||
await sqlRenamePhotoTagGlobally(tag, updatedTag);
|
await sqlRenamePhotoTagGlobally(tag, updatedTag);
|
||||||
revalidatePhotosKey();
|
revalidatePhotosKey();
|
||||||
|
revalidateTagsKey();
|
||||||
redirect(PATH_ADMIN_TAGS);
|
redirect(PATH_ADMIN_TAGS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -173,8 +173,8 @@ const sqlGetPhotosTagCount = async (tag: string) => sql`
|
|||||||
const sqlGetPhotosCameraCount = async (camera: Camera) => sql`
|
const sqlGetPhotosCameraCount = async (camera: Camera) => sql`
|
||||||
SELECT COUNT(*) FROM photos
|
SELECT COUNT(*) FROM photos
|
||||||
WHERE
|
WHERE
|
||||||
LOWER(make)=${parameterize(camera.make)} AND
|
LOWER(make)=${parameterize(camera.make, true)} AND
|
||||||
LOWER(REPLACE(model, ' ', '-'))=${parameterize(camera.model)} AND
|
LOWER(REPLACE(model, ' ', '-'))=${parameterize(camera.model, true)} AND
|
||||||
hidden IS NOT TRUE
|
hidden IS NOT TRUE
|
||||||
`.then(({ rows }) => parseInt(rows[0].count, 10));
|
`.then(({ rows }) => parseInt(rows[0].count, 10));
|
||||||
|
|
||||||
@ -203,8 +203,8 @@ const sqlGetPhotosCameraDateRange = async (camera: Camera) => sql`
|
|||||||
SELECT MIN(taken_at_naive) as start, MAX(taken_at_naive) as end
|
SELECT MIN(taken_at_naive) as start, MAX(taken_at_naive) as end
|
||||||
FROM photos
|
FROM photos
|
||||||
WHERE
|
WHERE
|
||||||
LOWER(make)=${parameterize(camera.make)} AND
|
LOWER(make)=${parameterize(camera.make, true)} AND
|
||||||
LOWER(REPLACE(model, ' ', '-'))=${parameterize(camera.model)} AND
|
LOWER(REPLACE(model, ' ', '-'))=${parameterize(camera.model, true)} AND
|
||||||
hidden IS NOT TRUE
|
hidden IS NOT TRUE
|
||||||
`.then(({ rows }) => rows[0] as PhotoDateRange);
|
`.then(({ rows }) => rows[0] as PhotoDateRange);
|
||||||
|
|
||||||
@ -347,8 +347,8 @@ export const getPhotos = async (options: GetPhotosOptions = {}) => {
|
|||||||
if (camera) {
|
if (camera) {
|
||||||
wheres.push(`LOWER(make)=$${valueIndex++}`);
|
wheres.push(`LOWER(make)=$${valueIndex++}`);
|
||||||
wheres.push(`LOWER(REPLACE(model, ' ', '-'))=$${valueIndex++}`);
|
wheres.push(`LOWER(REPLACE(model, ' ', '-'))=$${valueIndex++}`);
|
||||||
values.push(parameterize(camera.make));
|
values.push(parameterize(camera.make, true));
|
||||||
values.push(parameterize(camera.model));
|
values.push(parameterize(camera.model, true));
|
||||||
}
|
}
|
||||||
if (simulation) {
|
if (simulation) {
|
||||||
wheres.push(`film_simulation=$${valueIndex++}`);
|
wheres.push(`film_simulation=$${valueIndex++}`);
|
||||||
|
|||||||
@ -2,9 +2,9 @@ export const convertStringToArray = (
|
|||||||
string?: string,
|
string?: string,
|
||||||
shouldParameterize = true,
|
shouldParameterize = true,
|
||||||
) => string
|
) => string
|
||||||
? string.split(',').map(tag => shouldParameterize
|
? string.split(',').map(item => shouldParameterize
|
||||||
? parameterize(tag)
|
? parameterize(item)
|
||||||
: tag.trim())
|
: item.trim())
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
export const capitalize = (string: string) =>
|
export const capitalize = (string: string) =>
|
||||||
@ -16,14 +16,22 @@ export const capitalizeWords = (string = '') =>
|
|||||||
.map(capitalize)
|
.map(capitalize)
|
||||||
.join(' ');
|
.join(' ');
|
||||||
|
|
||||||
export const parameterize = (string: string) =>
|
export const parameterize = (
|
||||||
|
string: string,
|
||||||
|
shouldRemoveNonAlphanumeric?: boolean,
|
||||||
|
) =>
|
||||||
string
|
string
|
||||||
.trim()
|
.trim()
|
||||||
// Replaces spaces, underscores, and dashes with dashes
|
// Replaces spaces, underscores, and dashes with dashes
|
||||||
.replaceAll(/[\s_–—]/gi, '-')
|
.replaceAll(/[\s_–—]/gi, '-')
|
||||||
// Removes all non-alphanumeric characters
|
// Removes all non-alphanumeric characters
|
||||||
.replaceAll(/([^a-z0-9-])/gi, '')
|
.replaceAll(
|
||||||
.toLowerCase();
|
shouldRemoveNonAlphanumeric
|
||||||
|
? /([^a-z0-9-])/gi
|
||||||
|
: /''/gi,
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
.toLocaleLowerCase();
|
||||||
|
|
||||||
export const formatCount = (count: number) => `× ${count}`;
|
export const formatCount = (count: number) => `× ${count}`;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user