Online defaults to order:hot, with an Account toggle

New online_hot_default preference (on by default, so guests and accounts
that never saved preferences get it): opening /online without a search
replaces the URL with ?tags=order:hot — e621's metatag for the order the
Hot page uses — so it is visible in the search field and shareable.
Existing searches are never touched: they live in the URL, so a refresh
or back/forward keeps them, while a fresh visit (nav pill, first time,
after a long time away) gets the hot default again. Turning the toggle
off opens Online on the site-wide newest posts as before.

The toggle sits in Account -> Browsing preferences and saves with the
rest of the settings; the backend validates the new boolean
(400 for non-boolean input).
This commit is contained in:
2026-09-17 23:46:14 -05:00
parent 2df001b477
commit dc79349d9e
4 changed files with 40 additions and 1 deletions
@@ -170,6 +170,28 @@ export function PreferencesCard() {
/>
</label>
<label className="flex items-start gap-2 text-sm text-ctp-subtext0">
<input
type="checkbox"
checked={values.online_hot_default ?? true}
onChange={(event) =>
setValues((current) => ({
...current,
online_hot_default: event.target.checked,
}))
}
className="mt-0.5 accent-ctp-mauve"
/>
<span>
Online starts with{" "}
<span className="font-mono text-ctp-blue">order:hot</span>
<span className="mt-0.5 block text-[11px] leading-relaxed text-ctp-overlay0">
Used when you open Online without a search. With it off, Online
opens on the site-wide newest posts.
</span>
</span>
</label>
<div>
<Button onClick={() => void handleSave()} disabled={saving}>
{saving ? "Saving…" : "Save preferences"}
+15 -1
View File
@@ -1,6 +1,6 @@
import { keepPreviousData, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { X } from "lucide-react";
import { useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { Link, useLocation, useSearchParams } from "react-router-dom";
import { CollapsibleSidebar } from "@/components/CollapsibleSidebar";
@@ -18,6 +18,7 @@ import {
type E621Post,
} from "@/lib/e621";
import { useE621 } from "@/store/e621";
import { useAuth } from "@/store/auth";
import { useUi } from "@/store/ui";
import { toast } from "@/store/toasts";
@@ -34,11 +35,24 @@ export default function OnlinePage() {
const credentials = useE621((state) => state.credentials);
const blacklist = useE621((state) => state.blacklist);
const saveBlacklist = useE621((state) => state.saveBlacklist);
const user = useAuth((state) => state.user);
const authReady = useAuth((state) => state.ready);
const queryClient = useQueryClient();
const [draft, setDraft] = useState<string | null>(null);
const [blacklistDraft, setBlacklistDraft] = useState("");
const tagInput = draft ?? tags;
const zoom = useUi((state) => state.zoom);
const appliedHotDefault = useRef(false);
// Landing on Online without a search uses the order:hot default (on unless
// the account turns it off). Existing searches are left alone.
useEffect(() => {
if (!authReady || appliedHotDefault.current) return;
appliedHotDefault.current = true;
if (tags) return;
if (user?.preferences?.online_hot_default === false) return;
setSearchParams(buildParams("order:hot", 1), { replace: true });
}, [authReady, tags, user, setSearchParams]);
const blacklistMutation = useMutation({
mutationFn: (lines: string[]) => saveBlacklist(lines),
+1
View File
@@ -4,6 +4,7 @@ export interface UserPreferences {
ordering?: string;
per_page?: number;
zoom?: number;
online_hot_default?: boolean;
}
export interface User {