e621 polish: cached results, search context, DText descriptions
- e621 list/post queries use a 5 minute staleTime and 30 minute gcTime, so opening a post and coming back shows the same results with no refetch; global gcTime raised to 30 minutes - post card links carry their originating search in route state; the detail back link returns to it and related-post links keep it - new navigations scroll to top while history back/forward keeps the previous position - DText renderer for e621 descriptions (b/i/u/s, sup/sub, code, spoiler, quote, color, url/wiki/thumb, headings, lists, sections, expand blocks, named and bare links) built as React elements, no raw HTML - Spoiler moved to its own component; trailing punctuation no longer swallowed into links
This commit is contained in:
@@ -8,12 +8,13 @@ import {
|
||||
HeartOff,
|
||||
} from "lucide-react";
|
||||
import { useMemo } from "react";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import { Button, EmptyState, Spinner } from "@/components/ui";
|
||||
import { TagChip } from "@/components/TagChip";
|
||||
import { api } from "@/lib/api";
|
||||
import { cn } from "@/lib/cn";
|
||||
import { renderDText } from "@/lib/dtext";
|
||||
import {
|
||||
e621ErrorMessage,
|
||||
effectiveCredentials,
|
||||
@@ -46,6 +47,8 @@ export default function OnlineDetailPage() {
|
||||
const { id = "" } = useParams();
|
||||
const postId = Number(id);
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const from = (location.state as { from?: string } | null)?.from ?? "/online";
|
||||
const queryClient = useQueryClient();
|
||||
const credentials = useE621((state) => state.credentials);
|
||||
|
||||
@@ -54,6 +57,8 @@ export default function OnlineDetailPage() {
|
||||
queryFn: ({ signal }) =>
|
||||
fetchPost(effectiveCredentials(credentials), postId, signal),
|
||||
enabled: Number.isFinite(postId) && postId > 0,
|
||||
staleTime: 5 * 60_000,
|
||||
gcTime: 30 * 60_000,
|
||||
});
|
||||
|
||||
const post = query.data;
|
||||
@@ -79,6 +84,8 @@ export default function OnlineDetailPage() {
|
||||
queryFn: ({ signal }) =>
|
||||
fetchPostsByIds(effectiveCredentials(credentials), relatedIds, signal),
|
||||
enabled: relatedIds.length > 0,
|
||||
staleTime: 5 * 60_000,
|
||||
gcTime: 30 * 60_000,
|
||||
});
|
||||
|
||||
const lookupQuery = useQuery({
|
||||
@@ -136,7 +143,7 @@ export default function OnlineDetailPage() {
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<Link
|
||||
to="/online"
|
||||
to={from}
|
||||
className="inline-flex items-center gap-1 text-ctp-subtext0 transition hover:text-ctp-text"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" /> Online
|
||||
@@ -300,9 +307,9 @@ export default function OnlineDetailPage() {
|
||||
<summary className="cursor-pointer text-sm font-semibold text-ctp-subtext1">
|
||||
Description
|
||||
</summary>
|
||||
<p className="mt-2 whitespace-pre-line text-sm text-ctp-subtext0">
|
||||
{post.description}
|
||||
</p>
|
||||
<div className="mt-2 text-sm leading-relaxed text-ctp-subtext0">
|
||||
{renderDText(post.description)}
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
) : null}
|
||||
@@ -362,6 +369,7 @@ export default function OnlineDetailPage() {
|
||||
<Link
|
||||
key={related.id}
|
||||
to={`/online/view/${related.id}`}
|
||||
state={{ from }}
|
||||
className={cn(
|
||||
"overflow-hidden rounded-md border bg-ctp-mantle transition",
|
||||
relatedBorder[related.rating] ?? "border-ctp-surface0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||
import { useMemo, useState } from "react";
|
||||
import { Link, useSearchParams } from "react-router-dom";
|
||||
import { Link, useLocation, useSearchParams } from "react-router-dom";
|
||||
|
||||
import { CollapsibleSidebar } from "@/components/CollapsibleSidebar";
|
||||
import { TagChip } from "@/components/TagChip";
|
||||
@@ -23,8 +23,10 @@ const EMPTY_POSTS: E621Post[] = [];
|
||||
|
||||
export default function OnlinePage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const location = useLocation();
|
||||
const tags = searchParams.get("tags") ?? "";
|
||||
const page = Math.max(1, Number(searchParams.get("page") ?? "1") || 1);
|
||||
const from = `${location.pathname}${location.search}`;
|
||||
|
||||
const credentials = useE621((state) => state.credentials);
|
||||
const blacklist = useE621((state) => state.blacklist);
|
||||
@@ -41,7 +43,8 @@ export default function OnlinePage() {
|
||||
signal,
|
||||
}),
|
||||
placeholderData: keepPreviousData,
|
||||
staleTime: 30_000,
|
||||
staleTime: 5 * 60_000,
|
||||
gcTime: 30 * 60_000,
|
||||
});
|
||||
|
||||
const posts = postsQuery.data ?? EMPTY_POSTS;
|
||||
@@ -270,6 +273,7 @@ export default function OnlinePage() {
|
||||
key={post.id}
|
||||
post={post}
|
||||
inLibrary={inLibrary.has(post.file.md5)}
|
||||
from={from}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -20,13 +20,16 @@ const ratingPill: Record<string, string> = {
|
||||
export function PostCard({
|
||||
post,
|
||||
inLibrary,
|
||||
from,
|
||||
}: {
|
||||
post: E621Post;
|
||||
inLibrary: boolean;
|
||||
from?: string;
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
to={`/online/view/${post.id}`}
|
||||
state={from ? { from } : undefined}
|
||||
className={cn(
|
||||
"group overflow-hidden rounded-lg border bg-ctp-base transition",
|
||||
ratingBorder[post.rating] ?? "border-ctp-surface0",
|
||||
|
||||
Reference in New Issue
Block a user