- Extract the optimistic + debounced-flush feed-priority flow into a shared useChannelPriority hook (mirrors useChannelTagActions); the manager and the channel-detail page now share it instead of two drifting copies. - NavSidebar: drop the always-true `slim` const and its dead expanded-rail branches; refresh the now-stale pinned/collapsible comments. - Remove the orphaned `navCollapsed` localStorage key (no readers/writers left after the always-slim rail). - Channel page: drop the wrapper native `title` that duplicated PriorityStepper's own Tooltip. The keyboard-focus nav-label finding needed no change: the popover platform already wires useFocus (visibleOnly), so a real Tab press shows the flyout label (verified in-browser).
80 lines
3.3 KiB
TypeScript
80 lines
3.3 KiB
TypeScript
import { useCallback, useEffect, useRef } from "react";
|
|
import { api } from "./api";
|
|
|
|
/** The shared feed-priority flow used by both the channel manager and the channel detail page:
|
|
* an optimistic bump/set with a debounced server flush (~400ms after the last notch) so a
|
|
* press-and-hold sends ONE request, not one per step, and pending flushes fire on unmount so a
|
|
* last-moment bump isn't lost. The cache-shape-specific bits are injected (the manager maps over a
|
|
* ManagedChannel[]; the detail patches one ChannelDetail), so the two call sites can't drift — the
|
|
* same drift the sibling `useChannelTagActions` hook prevents for tags.
|
|
*
|
|
* Timers are keyed by channel id, so a pending flush survives the caller switching channels: it
|
|
* fires later and reads the right cache via `read(id)`, rather than needing the caller to flush on
|
|
* every id change. `read`/`optimistic`/`onSuccess`/`onError` are read through a ref so the returned
|
|
* callbacks stay stable — the unmount flush then runs only on unmount, never on every render. */
|
|
export function useChannelPriority(opts: {
|
|
/** Current priority for this channel from whichever cache the caller owns (undefined if absent). */
|
|
read: (channelId: string) => number | undefined;
|
|
/** Apply the optimistic priority to the caller's cache. */
|
|
optimistic: (channelId: string, next: number) => void;
|
|
/** Called after the server write lands (e.g. invalidate sibling caches). Optional. */
|
|
onSuccess?: (channelId: string) => void;
|
|
/** Called if the server write fails — revert by refetching the owning cache. */
|
|
onError: (channelId: string) => void;
|
|
/** Debounce window in ms; defaults to 400. */
|
|
delay?: number;
|
|
}) {
|
|
const timers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());
|
|
const optsRef = useRef(opts);
|
|
optsRef.current = opts;
|
|
|
|
const flush = useCallback((channelId: string) => {
|
|
const latest = optsRef.current.read(channelId);
|
|
if (latest == null) return;
|
|
api
|
|
.updateChannel(channelId, { priority: latest })
|
|
.then(() => optsRef.current.onSuccess?.(channelId))
|
|
.catch(() => optsRef.current.onError(channelId));
|
|
}, []);
|
|
|
|
// Set an absolute priority (used by both the arrows — via bumpPriority — and the inline edit),
|
|
// optimistically + a debounced flush.
|
|
const setPriority = useCallback(
|
|
(channelId: string, next: number) => {
|
|
optsRef.current.optimistic(channelId, next);
|
|
const pending = timers.current.get(channelId);
|
|
if (pending) clearTimeout(pending);
|
|
timers.current.set(
|
|
channelId,
|
|
setTimeout(() => {
|
|
timers.current.delete(channelId);
|
|
flush(channelId);
|
|
}, optsRef.current.delay ?? 400),
|
|
);
|
|
},
|
|
[flush],
|
|
);
|
|
|
|
// Read the LATEST value from the cache each step (not a render closure) so a hold-repeat
|
|
// accumulates correctly.
|
|
const bumpPriority = useCallback(
|
|
(channelId: string, delta: number) => {
|
|
setPriority(channelId, (optsRef.current.read(channelId) ?? 0) + delta);
|
|
},
|
|
[setPriority],
|
|
);
|
|
|
|
useEffect(() => {
|
|
const pending = timers.current;
|
|
return () => {
|
|
pending.forEach((timer, channelId) => {
|
|
clearTimeout(timer);
|
|
flush(channelId);
|
|
});
|
|
pending.clear();
|
|
};
|
|
}, [flush]);
|
|
|
|
return { setPriority, bumpPriority };
|
|
}
|