v3: fixed inconsistent page titles
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s

This commit is contained in:
khannurien
2026-06-28 06:51:42 +00:00
parent 551e29c564
commit c8c7b05c25
20 changed files with 598 additions and 470 deletions

View File

@@ -24,6 +24,7 @@ import {
} from "../model.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { relativeTime } from "../utils/relativeTime.ts";
import { useWS } from "../hooks/useWS.ts";
import { Avatar } from "../components/Avatar.tsx";
@@ -55,6 +56,14 @@ export function Dump() {
const [op, setOp] = useState<PublicUser | null>(null);
const [playlistModalOpen, setPlaylistModalOpen] = useState(false);
useDocumentTitle(
dumpState.status === "loaded"
? dumpState.dump.title
: dumpState.status === "error"
? null
: undefined,
);
const [comments, setComments] = useState<Comment[]>([]);
const [relatedDumps, setRelatedDumps] = useState<Dump[]>([]);

View File

@@ -7,6 +7,7 @@ import { API_URL, VALIDATION } from "../config/api.ts";
import type { Dump, RawDump, UpdateDumpRequest } from "../model.ts";
import { deserializeDump, parseAPIResponse } from "../model.ts";
import { useRequiredAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { formatBytes } from "../utils/format.ts";
import { dumpFileUrl, dumpThumbnailUrl, dumpUrl } from "../utils/urls.ts";
import { PageShell } from "../components/PageShell.tsx";
@@ -41,6 +42,15 @@ export function DumpEdit() {
const { authFetch, token } = useRequiredAuth();
const [state, setState] = useState<DumpEditState>({ status: "loading" });
useDocumentTitle(
state.status === "loaded"
? t`Editing ${state.dump.title}`
: state.status === "error"
? null
: undefined,
);
const [confirmDelete, setConfirmDelete] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [thumbUploading, setThumbUploading] = useState(false);

View File

@@ -28,6 +28,7 @@ import {
import { friendlyFetchError } from "../utils/apiError.ts";
import { useFeedCache } from "../hooks/useFeedCache.ts";
import { useScrollSave } from "../hooks/useScrollSave.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useWS } from "../hooks/useWS.ts";
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
@@ -55,6 +56,8 @@ export function Index() {
const justDeletedId = (location.state as { deletedDumpId?: string } | null)
?.deletedDumpId;
useDocumentTitle(null);
const { user, token } = useAuth();
const {
voteCounts,

View File

@@ -1,8 +1,11 @@
import { Link } from "react-router";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { PageShell } from "../components/PageShell.tsx";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
export function NotFound() {
useDocumentTitle(t`Page not found`);
return (
<PageShell centered>
<h1>404</h1>

View File

@@ -6,6 +6,7 @@ import { relativeTime } from "../utils/relativeTime.ts";
import { API_URL, NOTIFICATIONS_PAGE_SIZE } from "../config/api.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { ErrorCard } from "../components/ErrorCard.tsx";
import { Tooltip } from "../components/Tooltip.tsx";
import { useWS } from "../hooks/useWS.ts";
@@ -256,6 +257,7 @@ function groupByDate(
}
export function Notifications() {
useDocumentTitle(t`Notifications`);
const { authFetch } = useAuth();
const { clearUnreadNotifications, lastNotification } = useWS();
const [state, setState] = useState<State>({ status: "loading" });

View File

@@ -27,6 +27,7 @@ import {
import { playlistUrl } from "../utils/urls.ts";
import { can } from "../utils/permissions.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { useWS } from "../hooks/useWS.ts";
import { relativeTime } from "../utils/relativeTime.ts";
import { DumpCard } from "../components/DumpCard.tsx";
@@ -71,6 +72,14 @@ export function PlaylistDetail() {
// Stable UUID for WS comparisons — avoids re-running effects on every state change
const playlistUUID = state.status === "loaded" ? state.playlist.id : null;
useDocumentTitle(
state.status === "loaded"
? state.playlist.title
: state.status === "error"
? null
: undefined,
);
// activeDumpIds: which dumps are currently in the playlist (the canonical set)
const [activeDumpIds, setActiveDumpIds] = useState<Set<string>>(new Set());

View File

@@ -4,6 +4,7 @@ import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { API_URL, VALIDATION } from "../config/api.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { PageShell } from "../components/PageShell.tsx";
import {
expectOk,
@@ -20,6 +21,7 @@ interface Values {
}
export function ResetPassword() {
useDocumentTitle(t`Reset password`);
const [params] = useSearchParams();
const navigate = useNavigate();
const token = params.get("token") ?? "";

View File

@@ -8,6 +8,7 @@ import { DumpCard } from "../components/DumpCard.tsx";
import { PlaylistCard } from "../components/PlaylistCard.tsx";
import { ErrorCard } from "../components/ErrorCard.tsx";
import { useAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { useWS } from "../hooks/useWS.ts";
import { useInfiniteScroll } from "../hooks/useInfiniteScroll.ts";
import { useTabParam } from "../hooks/useTabParam.ts";
@@ -46,6 +47,7 @@ type SearchState =
};
export function Search() {
useDocumentTitle(t`Search`);
const [searchParams] = useSearchParams();
const q = searchParams.get("q") ?? "";
const [tab, setTab] = useTabParam<Tab>(SEARCH_TABS, "dumps");

View File

@@ -4,6 +4,7 @@ import { Trans } from "@lingui/react/macro";
import { Link, useParams } from "react-router";
import { useAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { useWS } from "../hooks/useWS.ts";
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
import { usePositionAwareSync } from "../hooks/usePositionAwareSync.ts";
@@ -19,6 +20,8 @@ export function UserDumps() {
const { user: me } = useAuth();
const { voteCounts, myVotes, lastDumpEvent, castVote, removeVote } = useWS();
useDocumentTitle(username ?? null);
const { state, setItems, sentinelRef } = useUserDumpFeed(
username,
"dumps",

View File

@@ -10,6 +10,7 @@ import {
type RawAuthResponse,
} from "../model.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { PageShell } from "../components/PageShell.tsx";
import {
expectOk,
@@ -30,6 +31,7 @@ interface ResetValues {
}
export function UserLogin() {
useDocumentTitle(t`Log in`);
const navigate = useNavigate();
const { login } = useAuth();
const [showReset, setShowReset] = useState(false);

View File

@@ -23,6 +23,7 @@ import {
hydratePlaylist,
} from "../model.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { useWS } from "../hooks/useWS.ts";
import { usePlaylistListSync } from "../hooks/usePlaylistListSync.ts";
import { usePositionAwareSync } from "../hooks/usePositionAwareSync.ts";
@@ -60,6 +61,8 @@ function initialFeed(items: Playlist[], hasMore: boolean): PlaylistFeed {
export function UserPlaylists() {
const { username } = useParams();
const { user: me, authFetch, token } = useAuth();
useDocumentTitle(username ?? null);
const { lastPlaylistEvent } = useWS();
const { cached: cachedCreated, saveState: saveCreated } = useFeedCache<

View File

@@ -38,6 +38,7 @@ import { NewPlaylistForm } from "../components/NewPlaylistForm.tsx";
import { PageShell } from "../components/PageShell.tsx";
import { PageError } from "../components/PageError.tsx";
import { useAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { useTheme } from "../hooks/useTheme.ts";
import { useDefaultFeedTab } from "../hooks/useDefaultFeedTab.ts";
import { useWS } from "../hooks/useWS.ts";
@@ -211,6 +212,14 @@ export function UserPublicProfile() {
const [state, setState] = useState<ProfileState>({ status: "loading" });
useDocumentTitle(
state.status === "loaded"
? state.user.username
: state.status === "error"
? null
: undefined,
);
const profileUserId = state.status === "loaded" ? state.user.id : null;
const isOwnProfile = me?.id === profileUserId;

View File

@@ -10,6 +10,7 @@ import {
type RegisterRequest,
} from "../model.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { PageShell } from "../components/PageShell.tsx";
import { ErrorCard } from "../components/ErrorCard.tsx";
import {
@@ -33,6 +34,7 @@ interface Values {
}
export function UserRegister() {
useDocumentTitle(t`Register`);
const navigate = useNavigate();
const { login } = useAuth();
const [searchParams] = useSearchParams();

View File

@@ -7,6 +7,7 @@ import { API_URL } from "../config/api.ts";
import type { Dump } from "../model.ts";
import { deserializeDump } from "../model.ts";
import { useAuth } from "../hooks/useAuth.ts";
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
import { useWS } from "../hooks/useWS.ts";
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
import { useFading } from "../hooks/useFading.ts";
@@ -21,6 +22,8 @@ export function UserUpvoted() {
const { user: me } = useAuth();
const { voteCounts, myVotes, lastVoteEvent, castVote, removeVote } = useWS();
useDocumentTitle(username ?? null);
const [votedIds, setVotedIds] = useState<Set<string>>(new Set());
const { fading, startFading, cancelFading, cancelAll } = useFading();
const prevMyVotesRef = useRef<Set<string> | null>(null);