v3: type checking and lint pass clean — thumbnail placeholder takes file-dump props, no manual memo the compiler can't keep, no setState straight in effect bodies
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 43s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 43s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -527,12 +527,12 @@ export function ChatModal({ onClose }: { onClose: () => void }) {
|
|||||||
return out;
|
return out;
|
||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
const typingLabel = useMemo(() => {
|
const typingNames = typingOthers.map((u) => u.username);
|
||||||
const names = typingOthers.map((u) => u.username);
|
const typingLabel = typingNames.length === 1
|
||||||
if (names.length === 1) return t`${names[0]} is typing…`;
|
? t`${typingNames[0]} is typing…`
|
||||||
if (names.length === 2) return t`${names[0]} and ${names[1]} are typing…`;
|
: typingNames.length === 2
|
||||||
return t`Several people are typing…`;
|
? t`${typingNames[0]} and ${typingNames[1]} are typing…`
|
||||||
}, [typingOthers]);
|
: t`Several people are typing…`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal title={t`Chat`} onClose={onClose} wide>
|
<Modal title={t`Chat`} onClose={onClose} wide>
|
||||||
|
|||||||
@@ -333,7 +333,8 @@ export function DumpCreateModal(
|
|||||||
const toggleCategory = (id: string) =>
|
const toggleCategory = (id: string) =>
|
||||||
setSelectedCategoryIds((prev) => {
|
setSelectedCategoryIds((prev) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
next.has(id) ? next.delete(id) : next.add(id);
|
if (next.has(id)) next.delete(id);
|
||||||
|
else next.add(id);
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { type ComponentProps, useState } from "react";
|
||||||
import { canProxyImage, proxyImageUrl } from "../utils/proxyImage.ts";
|
import { canProxyImage, proxyImageUrl } from "../utils/proxyImage.ts";
|
||||||
import ThumbnailPlaceholder from "./ThumbnailPlaceholder.tsx";
|
import ThumbnailPlaceholder from "./ThumbnailPlaceholder.tsx";
|
||||||
|
|
||||||
@@ -9,12 +9,7 @@ interface ThumbnailProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
loading?: "lazy" | "eager";
|
loading?: "lazy" | "eager";
|
||||||
/** Passed through to the placeholder when there's nothing to show. */
|
/** Passed through to the placeholder when there's nothing to show. */
|
||||||
placeholder: {
|
placeholder: Omit<ComponentProps<typeof ThumbnailPlaceholder>, "className">;
|
||||||
url?: string;
|
|
||||||
accentColor?: string;
|
|
||||||
faviconUrl?: string;
|
|
||||||
siteName?: string;
|
|
||||||
};
|
|
||||||
/** Extra class for the placeholder element only. */
|
/** Extra class for the placeholder element only. */
|
||||||
placeholderClassName?: string;
|
placeholderClassName?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,22 +10,30 @@ import {
|
|||||||
type RawCategory,
|
type RawCategory,
|
||||||
} from "../model.ts";
|
} from "../model.ts";
|
||||||
|
|
||||||
|
async function fetchCategories(
|
||||||
|
signal?: AbortSignal,
|
||||||
|
): Promise<Category[] | null> {
|
||||||
|
const res = await fetch(`${API_URL}/api/categories`, { signal });
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||||
|
const body = await res.json();
|
||||||
|
if (!body.success) return null;
|
||||||
|
return (body.data as RawCategory[]).map(deserializeCategory);
|
||||||
|
}
|
||||||
|
|
||||||
export function CategoriesProvider({ children }: { children: ReactNode }) {
|
export function CategoriesProvider({ children }: { children: ReactNode }) {
|
||||||
const [categories, setCategories] = useState<Category[]>([]);
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
const [isLoaded, setIsLoaded] = useState(false);
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
|
|
||||||
const load = useCallback(async (signal?: AbortSignal) => {
|
const load = useCallback((signal?: AbortSignal) =>
|
||||||
try {
|
fetchCategories(signal)
|
||||||
const res = await fetch(`${API_URL}/api/categories`, { signal });
|
.then((data) => {
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
if (!data) return;
|
||||||
const body = await res.json();
|
setCategories(data);
|
||||||
if (!body.success) return;
|
setIsLoaded(true);
|
||||||
setCategories((body.data as RawCategory[]).map(deserializeCategory));
|
})
|
||||||
setIsLoaded(true);
|
.catch((err) => {
|
||||||
} catch (err) {
|
if ((err as Error).name !== "AbortError") setIsLoaded(true);
|
||||||
if ((err as Error).name !== "AbortError") setIsLoaded(true);
|
}), []);
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
|||||||
@@ -304,7 +304,8 @@ function DumpEditForm({
|
|||||||
const toggleCategory = (id: string) =>
|
const toggleCategory = (id: string) =>
|
||||||
setSelectedCategoryIds((prev) => {
|
setSelectedCategoryIds((prev) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
next.has(id) ? next.delete(id) : next.add(id);
|
if (next.has(id)) next.delete(id);
|
||||||
|
else next.add(id);
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
const form = useApiForm<EditValues>({
|
const form = useApiForm<EditValues>({
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ export function FollowedFeed({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [token, usersState.status, playlistsState.status]);
|
}, [token, usersState.status, playlistsState.status, catQuery]);
|
||||||
|
|
||||||
// Scroll save
|
// Scroll save
|
||||||
useScrollSave(
|
useScrollSave(
|
||||||
@@ -298,7 +298,7 @@ export function FollowedFeed({
|
|||||||
s.status === "loaded" ? { ...s, loadingMore: false } : s
|
s.status === "loaded" ? { ...s, loadingMore: false } : s
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, [usersState, token]);
|
}, [usersState, token, catQuery]);
|
||||||
|
|
||||||
const loadMorePlaylists = useCallback(() => {
|
const loadMorePlaylists = useCallback(() => {
|
||||||
if (
|
if (
|
||||||
@@ -333,7 +333,7 @@ export function FollowedFeed({
|
|||||||
s.status === "loaded" ? { ...s, loadingMore: false } : s
|
s.status === "loaded" ? { ...s, loadingMore: false } : s
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, [playlistsState, token]);
|
}, [playlistsState, token, catQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="followed-feed">
|
<div className="followed-feed">
|
||||||
|
|||||||
Reference in New Issue
Block a user